@mantine/emoji 0.0.0-stage → 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +20 -2
- package/cjs/adapters/emoji-mart-adapter.cjs +30 -0
- package/cjs/adapters/emoji-mart-adapter.cjs.map +1 -0
- package/cjs/adapters/emojibase-adapter.cjs +76 -0
- package/cjs/adapters/emojibase-adapter.cjs.map +1 -0
- package/cjs/categories.cjs +52 -0
- package/cjs/categories.cjs.map +1 -0
- package/cjs/components/EmojiPicker/EmojiButton.cjs +24 -0
- package/cjs/components/EmojiPicker/EmojiButton.cjs.map +1 -0
- package/cjs/components/EmojiPicker/EmojiPicker.cjs +289 -0
- package/cjs/components/EmojiPicker/EmojiPicker.cjs.map +1 -0
- package/cjs/components/EmojiPicker/EmojiPicker.context.cjs +7 -0
- package/cjs/components/EmojiPicker/EmojiPicker.context.cjs.map +1 -0
- package/cjs/components/EmojiPicker/EmojiPicker.module.cjs +22 -0
- package/cjs/components/EmojiPicker/EmojiPicker.module.cjs.map +1 -0
- package/cjs/components/EmojiPicker/EmojiPickerCategoryNav.cjs +38 -0
- package/cjs/components/EmojiPicker/EmojiPickerCategoryNav.cjs.map +1 -0
- package/cjs/components/EmojiPicker/EmojiPickerGrid.cjs +131 -0
- package/cjs/components/EmojiPicker/EmojiPickerGrid.cjs.map +1 -0
- package/cjs/components/EmojiPicker/EmojiPickerPreview.cjs +24 -0
- package/cjs/components/EmojiPicker/EmojiPickerPreview.cjs.map +1 -0
- package/cjs/components/EmojiPicker/EmojiPickerSearch.cjs +57 -0
- package/cjs/components/EmojiPicker/EmojiPickerSearch.cjs.map +1 -0
- package/cjs/components/EmojiPicker/EmojiPickerSkinTone.cjs +63 -0
- package/cjs/components/EmojiPicker/EmojiPickerSkinTone.cjs.map +1 -0
- package/cjs/components/EmojiPickerInput/EmojiPickerInput.cjs +190 -0
- package/cjs/components/EmojiPickerInput/EmojiPickerInput.cjs.map +1 -0
- package/cjs/components/EmojiPickerInput/EmojiPickerInput.module.cjs +11 -0
- package/cjs/components/EmojiPickerInput/EmojiPickerInput.module.cjs.map +1 -0
- package/cjs/components/EmojiPickerPopover/EmojiPickerPopover.cjs +59 -0
- package/cjs/components/EmojiPickerPopover/EmojiPickerPopover.cjs.map +1 -0
- package/cjs/components/EmojiPickerPopover/EmojiPickerPopover.module.cjs +6 -0
- package/cjs/components/EmojiPickerPopover/EmojiPickerPopover.module.cjs.map +1 -0
- package/cjs/hooks/use-emoji-grid-navigation.cjs +70 -0
- package/cjs/hooks/use-emoji-grid-navigation.cjs.map +1 -0
- package/cjs/hooks/use-emoji-search.cjs +21 -0
- package/cjs/hooks/use-emoji-search.cjs.map +1 -0
- package/cjs/index.cjs +22 -0
- package/cjs/types.cjs +20 -0
- package/cjs/types.cjs.map +1 -0
- package/cjs/utils/emoji-grid-utils.cjs +57 -0
- package/cjs/utils/emoji-grid-utils.cjs.map +1 -0
- package/cjs/utils/flatten-emoji-data.cjs +33 -0
- package/cjs/utils/flatten-emoji-data.cjs.map +1 -0
- package/cjs/utils/is-emoji-supported.cjs +64 -0
- package/cjs/utils/is-emoji-supported.cjs.map +1 -0
- package/cjs/utils/search-emojis.cjs +21 -0
- package/cjs/utils/search-emojis.cjs.map +1 -0
- package/esm/adapters/emoji-mart-adapter.mjs +30 -0
- package/esm/adapters/emoji-mart-adapter.mjs.map +1 -0
- package/esm/adapters/emojibase-adapter.mjs +76 -0
- package/esm/adapters/emojibase-adapter.mjs.map +1 -0
- package/esm/categories.mjs +47 -0
- package/esm/categories.mjs.map +1 -0
- package/esm/components/EmojiPicker/EmojiButton.mjs +24 -0
- package/esm/components/EmojiPicker/EmojiButton.mjs.map +1 -0
- package/esm/components/EmojiPicker/EmojiPicker.context.mjs +7 -0
- package/esm/components/EmojiPicker/EmojiPicker.context.mjs.map +1 -0
- package/esm/components/EmojiPicker/EmojiPicker.mjs +289 -0
- package/esm/components/EmojiPicker/EmojiPicker.mjs.map +1 -0
- package/esm/components/EmojiPicker/EmojiPicker.module.mjs +22 -0
- package/esm/components/EmojiPicker/EmojiPicker.module.mjs.map +1 -0
- package/esm/components/EmojiPicker/EmojiPickerCategoryNav.mjs +38 -0
- package/esm/components/EmojiPicker/EmojiPickerCategoryNav.mjs.map +1 -0
- package/esm/components/EmojiPicker/EmojiPickerGrid.mjs +131 -0
- package/esm/components/EmojiPicker/EmojiPickerGrid.mjs.map +1 -0
- package/esm/components/EmojiPicker/EmojiPickerPreview.mjs +24 -0
- package/esm/components/EmojiPicker/EmojiPickerPreview.mjs.map +1 -0
- package/esm/components/EmojiPicker/EmojiPickerSearch.mjs +57 -0
- package/esm/components/EmojiPicker/EmojiPickerSearch.mjs.map +1 -0
- package/esm/components/EmojiPicker/EmojiPickerSkinTone.mjs +63 -0
- package/esm/components/EmojiPicker/EmojiPickerSkinTone.mjs.map +1 -0
- package/esm/components/EmojiPickerInput/EmojiPickerInput.mjs +190 -0
- package/esm/components/EmojiPickerInput/EmojiPickerInput.mjs.map +1 -0
- package/esm/components/EmojiPickerInput/EmojiPickerInput.module.mjs +11 -0
- package/esm/components/EmojiPickerInput/EmojiPickerInput.module.mjs.map +1 -0
- package/esm/components/EmojiPickerPopover/EmojiPickerPopover.mjs +59 -0
- package/esm/components/EmojiPickerPopover/EmojiPickerPopover.mjs.map +1 -0
- package/esm/components/EmojiPickerPopover/EmojiPickerPopover.module.mjs +6 -0
- package/esm/components/EmojiPickerPopover/EmojiPickerPopover.module.mjs.map +1 -0
- package/esm/hooks/use-emoji-grid-navigation.mjs +70 -0
- package/esm/hooks/use-emoji-grid-navigation.mjs.map +1 -0
- package/esm/hooks/use-emoji-search.mjs +21 -0
- package/esm/hooks/use-emoji-search.mjs.map +1 -0
- package/esm/index.mjs +11 -0
- package/esm/types.mjs +20 -0
- package/esm/types.mjs.map +1 -0
- package/esm/utils/emoji-grid-utils.mjs +54 -0
- package/esm/utils/emoji-grid-utils.mjs.map +1 -0
- package/esm/utils/flatten-emoji-data.mjs +33 -0
- package/esm/utils/flatten-emoji-data.mjs.map +1 -0
- package/esm/utils/is-emoji-supported.mjs +63 -0
- package/esm/utils/is-emoji-supported.mjs.map +1 -0
- package/esm/utils/search-emojis.mjs +21 -0
- package/esm/utils/search-emojis.mjs.map +1 -0
- package/lib/adapters/emoji-mart-adapter.d.ts +23 -0
- package/lib/adapters/emojibase-adapter.d.ts +25 -0
- package/lib/adapters/index.d.ts +2 -0
- package/lib/categories.d.ts +7 -0
- package/lib/components/EmojiPicker/EmojiButton.d.ts +10 -0
- package/lib/components/EmojiPicker/EmojiPicker.context.d.ts +9 -0
- package/lib/components/EmojiPicker/EmojiPicker.d.ts +64 -0
- package/lib/components/EmojiPicker/EmojiPickerCategoryNav.d.ts +10 -0
- package/lib/components/EmojiPicker/EmojiPickerGrid.d.ts +23 -0
- package/lib/components/EmojiPicker/EmojiPickerPreview.d.ts +9 -0
- package/lib/components/EmojiPicker/EmojiPickerSearch.d.ts +14 -0
- package/lib/components/EmojiPicker/EmojiPickerSkinTone.d.ts +6 -0
- package/lib/components/EmojiPicker/index.d.ts +2 -0
- package/lib/components/EmojiPickerInput/EmojiPickerInput.d.ts +54 -0
- package/lib/components/EmojiPickerInput/index.d.ts +2 -0
- package/lib/components/EmojiPickerPopover/EmojiPickerPopover.d.ts +24 -0
- package/lib/components/EmojiPickerPopover/index.d.ts +2 -0
- package/lib/hooks/index.d.ts +2 -0
- package/lib/hooks/use-emoji-grid-navigation.d.ts +18 -0
- package/lib/hooks/use-emoji-search.d.ts +14 -0
- package/lib/index.d.mts +10 -0
- package/lib/index.d.ts +10 -0
- package/lib/types.d.ts +41 -0
- package/lib/utils/emoji-grid-utils.d.ts +17 -0
- package/lib/utils/flatten-emoji-data.d.ts +2 -0
- package/lib/utils/index.d.ts +5 -0
- package/lib/utils/is-emoji-supported.d.ts +4 -0
- package/lib/utils/search-emojis.d.ts +2 -0
- package/package.json +54 -4
- package/styles.css +228 -0
- package/styles.layer.css +229 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { applySkinTone } from "../../utils/emoji-grid-utils.mjs";
|
|
3
|
+
import { useEmojiPickerContext } from "./EmojiPicker.context.mjs";
|
|
4
|
+
import { memo } from "react";
|
|
5
|
+
import { UnstyledButton } from "@mantine/core";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
// packages/@mantine/emoji/src/components/EmojiPicker/EmojiButton.tsx
|
|
8
|
+
const EmojiButton = memo(function EmojiButton({ emoji, skinTone, emojiIndex, focused, id }) {
|
|
9
|
+
const ctx = useEmojiPickerContext();
|
|
10
|
+
return /* @__PURE__ */ jsx(UnstyledButton, {
|
|
11
|
+
...ctx.getStyles("emoji"),
|
|
12
|
+
role: "gridcell",
|
|
13
|
+
"aria-label": emoji.name,
|
|
14
|
+
title: emoji.name,
|
|
15
|
+
id,
|
|
16
|
+
tabIndex: -1,
|
|
17
|
+
"data-focused": focused || void 0,
|
|
18
|
+
"data-emoji-index": emojiIndex,
|
|
19
|
+
children: applySkinTone(emoji, skinTone).emoji
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
export { EmojiButton };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=EmojiButton.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmojiButton.mjs","names":[],"sources":["../../../src/components/EmojiPicker/EmojiButton.tsx"],"sourcesContent":["import { memo } from 'react';\nimport { UnstyledButton } from '@mantine/core';\nimport type { EmojiPickerEmoji } from '../../types';\nimport { applySkinTone } from '../../utils';\nimport { useEmojiPickerContext } from './EmojiPicker.context';\n\ninterface EmojiButtonProps {\n emoji: EmojiPickerEmoji;\n skinTone: number;\n emojiIndex: number;\n focused: boolean;\n id: string;\n}\n\nexport const EmojiButton = memo(function EmojiButton({\n emoji,\n skinTone,\n emojiIndex,\n focused,\n id,\n}: EmojiButtonProps) {\n const ctx = useEmojiPickerContext();\n\n return (\n <UnstyledButton\n {...ctx.getStyles('emoji')}\n role=\"gridcell\"\n aria-label={emoji.name}\n title={emoji.name}\n id={id}\n tabIndex={-1}\n data-focused={focused || undefined}\n data-emoji-index={emojiIndex}\n >\n {applySkinTone(emoji, skinTone).emoji}\n </UnstyledButton>\n );\n});\n"],"mappings":";;;;;;;AAcA,MAAa,cAAc,KAAK,SAAS,YAAY,EACnD,OACA,UACA,YACA,SACA,MACmB;CACnB,MAAM,MAAM,sBAAsB;CAElC,OACE,oBAAC,gBAAD;EACE,GAAI,IAAI,UAAU,OAAO;EACzB,MAAK;EACL,cAAY,MAAM;EAClB,OAAO,MAAM;EACT;EACJ,UAAU;EACV,gBAAc,WAAW,KAAA;EACzB,oBAAkB;EAEjB,UAAA,cAAc,OAAO,QAAQ,CAAC,CAAC;CAClB,CAAA;AAEpB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createSafeContext } from "@mantine/core";
|
|
3
|
+
// packages/@mantine/emoji/src/components/EmojiPicker/EmojiPicker.context.ts
|
|
4
|
+
const [EmojiPickerProvider, useEmojiPickerContext] = createSafeContext("EmojiPicker components must be used within EmojiPicker");
|
|
5
|
+
export { EmojiPickerProvider, useEmojiPickerContext };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=EmojiPicker.context.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmojiPicker.context.mjs","names":[],"sources":["../../../src/components/EmojiPicker/EmojiPicker.context.ts"],"sourcesContent":["import { createSafeContext, type GetStylesApi, type MantineSize } from '@mantine/core';\nimport type { EmojiPickerLabels } from '../../types';\nimport type { EmojiPickerFactory } from './EmojiPicker';\n\nexport interface EmojiPickerContextValue {\n getStyles: GetStylesApi<EmojiPickerFactory>;\n labels: Required<EmojiPickerLabels>;\n size: MantineSize | undefined;\n}\n\nexport const [EmojiPickerProvider, useEmojiPickerContext] =\n createSafeContext<EmojiPickerContextValue>(\n 'EmojiPicker components must be used within EmojiPicker'\n );\n"],"mappings":";;;AAUA,MAAa,CAAC,qBAAqB,yBACjC,kBACE,wDACF"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { DEFAULT_LABELS } from "../../types.mjs";
|
|
3
|
+
import { flattenEmojiData } from "../../utils/flatten-emoji-data.mjs";
|
|
4
|
+
import { filterSupportedEmojis } from "../../utils/is-emoji-supported.mjs";
|
|
5
|
+
import { applySkinTone, getEmojiItemIndex } from "../../utils/emoji-grid-utils.mjs";
|
|
6
|
+
import { useEmojiGridNavigation } from "../../hooks/use-emoji-grid-navigation.mjs";
|
|
7
|
+
import { useEmojiSearch } from "../../hooks/use-emoji-search.mjs";
|
|
8
|
+
import { EmojiPickerProvider } from "./EmojiPicker.context.mjs";
|
|
9
|
+
import { EmojiPickerCategoryNav } from "./EmojiPickerCategoryNav.mjs";
|
|
10
|
+
import { EmojiPickerGrid } from "./EmojiPickerGrid.mjs";
|
|
11
|
+
import { EmojiPickerPreview } from "./EmojiPickerPreview.mjs";
|
|
12
|
+
import { EmojiPickerSearch } from "./EmojiPickerSearch.mjs";
|
|
13
|
+
import { EmojiPickerSkinTone } from "./EmojiPickerSkinTone.mjs";
|
|
14
|
+
import EmojiPicker_module_default from "./EmojiPicker.module.mjs";
|
|
15
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
16
|
+
import { Box, createVarsResolver, factory, rem, useProps, useStyles } from "@mantine/core";
|
|
17
|
+
import { useUncontrolled } from "@mantine/hooks";
|
|
18
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
19
|
+
// packages/@mantine/emoji/src/components/EmojiPicker/EmojiPicker.tsx
|
|
20
|
+
const defaultProps = {
|
|
21
|
+
columns: 9,
|
|
22
|
+
withSearch: true,
|
|
23
|
+
withCategoryNav: true,
|
|
24
|
+
withPreview: true,
|
|
25
|
+
withSkinToneSelector: true,
|
|
26
|
+
withUnsupportedDetection: false,
|
|
27
|
+
autoFocus: false,
|
|
28
|
+
size: "sm"
|
|
29
|
+
};
|
|
30
|
+
const EMOJI_SIZES = {
|
|
31
|
+
xs: {
|
|
32
|
+
emojiSize: 28,
|
|
33
|
+
emojiFz: rem(16),
|
|
34
|
+
previewPy: rem(4),
|
|
35
|
+
skinToneSize: rem(20),
|
|
36
|
+
skinToneFz: rem(13)
|
|
37
|
+
},
|
|
38
|
+
sm: {
|
|
39
|
+
emojiSize: 32,
|
|
40
|
+
emojiFz: rem(18),
|
|
41
|
+
previewPy: rem(5),
|
|
42
|
+
skinToneSize: rem(22),
|
|
43
|
+
skinToneFz: rem(14)
|
|
44
|
+
},
|
|
45
|
+
md: {
|
|
46
|
+
emojiSize: 36,
|
|
47
|
+
emojiFz: rem(22),
|
|
48
|
+
previewPy: rem(7),
|
|
49
|
+
skinToneSize: rem(24),
|
|
50
|
+
skinToneFz: rem(16)
|
|
51
|
+
},
|
|
52
|
+
lg: {
|
|
53
|
+
emojiSize: 42,
|
|
54
|
+
emojiFz: rem(26),
|
|
55
|
+
previewPy: rem(8),
|
|
56
|
+
skinToneSize: rem(28),
|
|
57
|
+
skinToneFz: rem(18)
|
|
58
|
+
},
|
|
59
|
+
xl: {
|
|
60
|
+
emojiSize: 48,
|
|
61
|
+
emojiFz: rem(30),
|
|
62
|
+
previewPy: rem(10),
|
|
63
|
+
skinToneSize: rem(32),
|
|
64
|
+
skinToneFz: rem(20)
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const EMOJI_FONT_SIZE_RATIO = .6;
|
|
68
|
+
function getSizeValues(size) {
|
|
69
|
+
return EMOJI_SIZES[size || "sm"] || EMOJI_SIZES.sm;
|
|
70
|
+
}
|
|
71
|
+
const varsResolver = createVarsResolver((_, { columns, emojiSize, size }) => {
|
|
72
|
+
const sizeValues = getSizeValues(size);
|
|
73
|
+
return { root: {
|
|
74
|
+
"--ep-columns": columns?.toString() ?? "9",
|
|
75
|
+
"--ep-emoji-size": emojiSize ? rem(emojiSize) : rem(sizeValues.emojiSize),
|
|
76
|
+
"--ep-emoji-fz": emojiSize ? rem(Math.round(emojiSize * EMOJI_FONT_SIZE_RATIO)) : sizeValues.emojiFz,
|
|
77
|
+
"--ep-preview-py": sizeValues.previewPy,
|
|
78
|
+
"--ep-skin-tone-size": sizeValues.skinToneSize,
|
|
79
|
+
"--ep-skin-tone-fz": sizeValues.skinToneFz
|
|
80
|
+
} };
|
|
81
|
+
});
|
|
82
|
+
const EmojiPicker = factory((_props) => {
|
|
83
|
+
const props = useProps("EmojiPicker", defaultProps, _props);
|
|
84
|
+
const { classNames, className, style, styles, unstyled, vars, data, onEmojiSelect, columns, emojiSize, withSearch, withCategoryNav, withPreview, withSkinToneSelector, skinTone: skinToneProp, defaultSkinTone, onSkinToneChange, searchValue, onSearchChange, searchPlaceholder, emptySearchContent, withUnsupportedDetection, autoFocus, scrollAreaProps, categoryLabels, labels: labelsProp, size, searchInputProps, ...others } = props;
|
|
85
|
+
const getStyles = useStyles({
|
|
86
|
+
name: "EmojiPicker",
|
|
87
|
+
classes: EmojiPicker_module_default,
|
|
88
|
+
props,
|
|
89
|
+
classNames,
|
|
90
|
+
styles,
|
|
91
|
+
unstyled,
|
|
92
|
+
vars,
|
|
93
|
+
varsResolver,
|
|
94
|
+
stable: true
|
|
95
|
+
});
|
|
96
|
+
const [skinTone, setSkinTone] = useUncontrolled({
|
|
97
|
+
value: skinToneProp,
|
|
98
|
+
defaultValue: defaultSkinTone ?? 0,
|
|
99
|
+
onChange: onSkinToneChange
|
|
100
|
+
});
|
|
101
|
+
const cols = Math.max(columns ?? 9, 1);
|
|
102
|
+
const baseId = useId();
|
|
103
|
+
const idPrefix = `${baseId}-emoji`;
|
|
104
|
+
const gridId = `${baseId}-grid`;
|
|
105
|
+
const [activeDescendant, setActiveDescendant] = useState(void 0);
|
|
106
|
+
const filteredData = useMemo(() => {
|
|
107
|
+
if (!withUnsupportedDetection) return data;
|
|
108
|
+
return {
|
|
109
|
+
emojis: data.emojis,
|
|
110
|
+
categories: data.categories.map((cat) => ({
|
|
111
|
+
...cat,
|
|
112
|
+
emojis: filterSupportedEmojis(data.emojis, cat.emojis)
|
|
113
|
+
}))
|
|
114
|
+
};
|
|
115
|
+
}, [data, withUnsupportedDetection]);
|
|
116
|
+
const { query, setQuery, results, isSearching } = useEmojiSearch({
|
|
117
|
+
data: filteredData,
|
|
118
|
+
searchValue,
|
|
119
|
+
onSearchChange
|
|
120
|
+
});
|
|
121
|
+
const searchInputRef = useRef(null);
|
|
122
|
+
const scrollToItemRef = useRef(null);
|
|
123
|
+
const mergedLabels = useMemo(() => ({
|
|
124
|
+
...DEFAULT_LABELS,
|
|
125
|
+
...labelsProp
|
|
126
|
+
}), [labelsProp]);
|
|
127
|
+
const ctxValue = useMemo(() => ({
|
|
128
|
+
getStyles,
|
|
129
|
+
labels: mergedLabels,
|
|
130
|
+
size
|
|
131
|
+
}), [
|
|
132
|
+
getStyles,
|
|
133
|
+
mergedLabels,
|
|
134
|
+
size
|
|
135
|
+
]);
|
|
136
|
+
const categoryItems = useMemo(() => flattenEmojiData(filteredData, cols, categoryLabels), [
|
|
137
|
+
filteredData,
|
|
138
|
+
cols,
|
|
139
|
+
categoryLabels
|
|
140
|
+
]);
|
|
141
|
+
const searchItems = useMemo(() => flattenEmojiData({
|
|
142
|
+
emojis: filteredData.emojis,
|
|
143
|
+
categories: [{
|
|
144
|
+
id: "search",
|
|
145
|
+
label: mergedLabels.searchResults,
|
|
146
|
+
emojis: results.map((emoji) => emoji.id)
|
|
147
|
+
}]
|
|
148
|
+
}, cols), [
|
|
149
|
+
filteredData.emojis,
|
|
150
|
+
cols,
|
|
151
|
+
results,
|
|
152
|
+
mergedLabels.searchResults
|
|
153
|
+
]);
|
|
154
|
+
const flattenedItems = isSearching ? searchItems : categoryItems;
|
|
155
|
+
const navCategories = useMemo(() => categoryItems.reduce((acc, item) => {
|
|
156
|
+
if (item.type === "header") acc.push({
|
|
157
|
+
id: item.categoryId,
|
|
158
|
+
label: item.label
|
|
159
|
+
});
|
|
160
|
+
return acc;
|
|
161
|
+
}, []), [categoryItems]);
|
|
162
|
+
const scrollToItem = useCallback((itemIndex) => {
|
|
163
|
+
scrollToItemRef.current?.(itemIndex, "auto");
|
|
164
|
+
}, []);
|
|
165
|
+
const handleEmojiSelect = useCallback((emoji) => {
|
|
166
|
+
onEmojiSelect?.(emoji);
|
|
167
|
+
}, [onEmojiSelect]);
|
|
168
|
+
const { model, activeIndex, activeEmoji, keyboardActive, handleKeyDown, setPointerIndex, setAnchorIndex } = useEmojiGridNavigation({
|
|
169
|
+
items: flattenedItems,
|
|
170
|
+
resetKey: query,
|
|
171
|
+
skinTone,
|
|
172
|
+
onEmojiSelect: handleEmojiSelect,
|
|
173
|
+
scrollToItem
|
|
174
|
+
});
|
|
175
|
+
const handleGridKeyDown = useCallback((event) => {
|
|
176
|
+
if (event.key === "Escape") {
|
|
177
|
+
const focusStayedInGrid = event.currentTarget.contains(document.activeElement);
|
|
178
|
+
if (withSearch && focusStayedInGrid && searchInputRef.current) {
|
|
179
|
+
event.preventDefault();
|
|
180
|
+
searchInputRef.current.focus();
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
handleKeyDown(event);
|
|
185
|
+
}, [handleKeyDown, withSearch]);
|
|
186
|
+
const handleSearchKeyDown = useCallback((event) => {
|
|
187
|
+
if (event.nativeEvent.isComposing) return;
|
|
188
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp" || event.key === "Enter") handleKeyDown(event);
|
|
189
|
+
}, [handleKeyDown]);
|
|
190
|
+
const handleEmojiClick = useCallback((index) => {
|
|
191
|
+
const emoji = model.emojis[index];
|
|
192
|
+
if (emoji) {
|
|
193
|
+
setPointerIndex(index);
|
|
194
|
+
handleEmojiSelect(applySkinTone(emoji, skinTone));
|
|
195
|
+
}
|
|
196
|
+
}, [
|
|
197
|
+
model,
|
|
198
|
+
setPointerIndex,
|
|
199
|
+
handleEmojiSelect,
|
|
200
|
+
skinTone
|
|
201
|
+
]);
|
|
202
|
+
const scrollToCategory = useCallback((categoryId) => {
|
|
203
|
+
const itemIndex = flattenedItems.findIndex((item) => item.type === "header" && item.categoryId === categoryId);
|
|
204
|
+
if (itemIndex !== -1) {
|
|
205
|
+
setAnchorIndex(model.itemOffsets[itemIndex]);
|
|
206
|
+
scrollToItemRef.current?.(itemIndex, "start");
|
|
207
|
+
}
|
|
208
|
+
}, [
|
|
209
|
+
flattenedItems,
|
|
210
|
+
model,
|
|
211
|
+
setAnchorIndex
|
|
212
|
+
]);
|
|
213
|
+
const pendingCategoryRef = useRef(null);
|
|
214
|
+
const handleCategoryClick = useCallback((categoryId) => {
|
|
215
|
+
if (isSearching) {
|
|
216
|
+
pendingCategoryRef.current = categoryId;
|
|
217
|
+
setQuery("");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
scrollToCategory(categoryId);
|
|
221
|
+
}, [
|
|
222
|
+
isSearching,
|
|
223
|
+
setQuery,
|
|
224
|
+
scrollToCategory
|
|
225
|
+
]);
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
if (!isSearching && pendingCategoryRef.current !== null) {
|
|
228
|
+
const categoryId = pendingCategoryRef.current;
|
|
229
|
+
pendingCategoryRef.current = null;
|
|
230
|
+
scrollToCategory(categoryId);
|
|
231
|
+
}
|
|
232
|
+
}, [isSearching, scrollToCategory]);
|
|
233
|
+
return /* @__PURE__ */ jsx(EmojiPickerProvider, {
|
|
234
|
+
value: ctxValue,
|
|
235
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
236
|
+
...getStyles("root"),
|
|
237
|
+
...others,
|
|
238
|
+
children: [
|
|
239
|
+
withSearch && /* @__PURE__ */ jsx(EmojiPickerSearch, {
|
|
240
|
+
query,
|
|
241
|
+
onQueryChange: setQuery,
|
|
242
|
+
placeholder: searchPlaceholder,
|
|
243
|
+
autoFocus,
|
|
244
|
+
inputRef: searchInputRef,
|
|
245
|
+
onKeyDown: handleSearchKeyDown,
|
|
246
|
+
controls: flattenedItems.length > 0 ? gridId : void 0,
|
|
247
|
+
activeDescendant,
|
|
248
|
+
searchInputProps
|
|
249
|
+
}),
|
|
250
|
+
withCategoryNav && /* @__PURE__ */ jsx(EmojiPickerCategoryNav, {
|
|
251
|
+
categories: navCategories,
|
|
252
|
+
onCategoryClick: handleCategoryClick
|
|
253
|
+
}),
|
|
254
|
+
/* @__PURE__ */ jsx(EmojiPickerGrid, {
|
|
255
|
+
items: flattenedItems,
|
|
256
|
+
itemOffsets: model.itemOffsets,
|
|
257
|
+
columns: cols,
|
|
258
|
+
skinTone,
|
|
259
|
+
focusedIndex: keyboardActive ? activeIndex : -1,
|
|
260
|
+
focusedItemIndex: keyboardActive ? getEmojiItemIndex(model, activeIndex) : -1,
|
|
261
|
+
idPrefix,
|
|
262
|
+
gridId,
|
|
263
|
+
onActiveDescendantChange: setActiveDescendant,
|
|
264
|
+
emojiSize: emojiSize ?? getSizeValues(size).emojiSize,
|
|
265
|
+
emptyContent: emptySearchContent || mergedLabels.noResults,
|
|
266
|
+
onKeyDown: handleGridKeyDown,
|
|
267
|
+
onPointerIndex: setPointerIndex,
|
|
268
|
+
onEmojiClick: handleEmojiClick,
|
|
269
|
+
scrollToItemRef,
|
|
270
|
+
scrollAreaProps
|
|
271
|
+
}),
|
|
272
|
+
(withPreview || withSkinToneSelector) && /* @__PURE__ */ jsx(EmojiPickerPreview, {
|
|
273
|
+
emoji: activeEmoji,
|
|
274
|
+
skinTone,
|
|
275
|
+
withPreview: !!withPreview,
|
|
276
|
+
children: withSkinToneSelector && /* @__PURE__ */ jsx(EmojiPickerSkinTone, {
|
|
277
|
+
skinTone,
|
|
278
|
+
onSkinToneChange: setSkinTone
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
]
|
|
282
|
+
})
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
EmojiPicker.classes = EmojiPicker_module_default;
|
|
286
|
+
EmojiPicker.displayName = "@mantine/emoji/EmojiPicker";
|
|
287
|
+
export { EmojiPicker };
|
|
288
|
+
|
|
289
|
+
//# sourceMappingURL=EmojiPicker.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmojiPicker.mjs","names":["classes"],"sources":["../../../src/components/EmojiPicker/EmojiPicker.tsx"],"sourcesContent":["import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport {\n Box,\n BoxProps,\n createVarsResolver,\n ElementProps,\n factory,\n Factory,\n InputProps,\n MantineSize,\n rem,\n ScrollAreaProps,\n StylesApiProps,\n useProps,\n useStyles,\n} from '@mantine/core';\nimport { useUncontrolled } from '@mantine/hooks';\nimport { useEmojiGridNavigation } from '../../hooks/use-emoji-grid-navigation';\nimport { useEmojiSearch } from '../../hooks/use-emoji-search';\nimport {\n DEFAULT_LABELS,\n EmojiPickerData,\n EmojiPickerEmoji,\n EmojiPickerLabels,\n FlattenedRowItem,\n} from '../../types';\nimport {\n applySkinTone,\n filterSupportedEmojis,\n flattenEmojiData,\n getEmojiItemIndex,\n} from '../../utils';\nimport { EmojiPickerProvider } from './EmojiPicker.context';\nimport { EmojiPickerCategoryNav, EmojiPickerNavCategory } from './EmojiPickerCategoryNav';\nimport { EmojiPickerGrid, EmojiPickerScrollToItem } from './EmojiPickerGrid';\nimport { EmojiPickerPreview } from './EmojiPickerPreview';\nimport { EmojiPickerSearch } from './EmojiPickerSearch';\nimport { EmojiPickerSkinTone } from './EmojiPickerSkinTone';\nimport classes from './EmojiPicker.module.css';\n\nexport type EmojiPickerStylesNames =\n | 'root'\n | 'search'\n | 'searchInput'\n | 'categoryNav'\n | 'categoryNavButton'\n | 'content'\n | 'empty'\n | 'categoryLabel'\n | 'grid'\n | 'emoji'\n | 'preview'\n | 'previewEmoji'\n | 'previewName'\n | 'skinToneSelector'\n | 'skinToneDropdown'\n | 'skinToneButton';\n\nexport type EmojiPickerCssVariables = {\n root:\n | '--ep-columns'\n | '--ep-emoji-size'\n | '--ep-emoji-fz'\n | '--ep-preview-py'\n | '--ep-skin-tone-size'\n | '--ep-skin-tone-fz';\n};\n\nexport interface EmojiPickerProps\n extends BoxProps, StylesApiProps<EmojiPickerFactory>, ElementProps<'div'> {\n /** Emoji data object, use `emojiMartAdapter` or `emojibaseAdapter` to convert data */\n data: EmojiPickerData;\n\n /** Called when an emoji is selected */\n onEmojiSelect?: (emoji: EmojiPickerEmoji) => void;\n\n /** Number of emoji columns @default 9 */\n columns?: number;\n\n /** Custom emoji button size in px, overrides size-based value */\n emojiSize?: number;\n\n /** Determines whether the search input is displayed @default true */\n withSearch?: boolean;\n\n /** Determines whether the category navigation bar is displayed @default true */\n withCategoryNav?: boolean;\n\n /** Determines whether the emoji preview area is displayed @default true */\n withPreview?: boolean;\n\n /** Determines whether the skin tone selector is displayed @default true */\n withSkinToneSelector?: boolean;\n\n /** Controlled skin tone value (0–5) */\n skinTone?: number;\n\n /** Default skin tone value for uncontrolled state @default 0 */\n defaultSkinTone?: number;\n\n /** Called when skin tone changes */\n onSkinToneChange?: (skinTone: number) => void;\n\n /** Controlled search input value */\n searchValue?: string;\n\n /** Called when search value changes */\n onSearchChange?: (value: string) => void;\n\n /** Search input placeholder */\n searchPlaceholder?: string;\n\n /** Content displayed when search yields no results */\n emptySearchContent?: React.ReactNode;\n\n /** Determines whether unsupported emojis are filtered out @default false */\n withUnsupportedDetection?: boolean;\n\n /** Determines whether the search input is focused on mount @default false */\n autoFocus?: boolean;\n\n /** Props passed to the internal `ScrollArea` component */\n scrollAreaProps?: ScrollAreaProps;\n\n /** Override category labels by category id */\n categoryLabels?: Record<string, string>;\n\n /** Override UI labels (search placeholder, no results text, etc.) */\n labels?: EmojiPickerLabels;\n\n /** Controls size of all inner elements @default 'sm' */\n size?: MantineSize;\n\n /** Props passed to the search `Input` component */\n searchInputProps?: InputProps;\n}\n\nexport type EmojiPickerFactory = Factory<{\n props: EmojiPickerProps;\n ref: HTMLDivElement;\n stylesNames: EmojiPickerStylesNames;\n vars: EmojiPickerCssVariables;\n}>;\n\nconst defaultProps = {\n columns: 9,\n withSearch: true,\n withCategoryNav: true,\n withPreview: true,\n withSkinToneSelector: true,\n withUnsupportedDetection: false,\n autoFocus: false,\n size: 'sm',\n} satisfies Partial<EmojiPickerProps>;\n\nconst EMOJI_SIZES: Record<\n string,\n {\n emojiSize: number;\n emojiFz: string;\n previewPy: string;\n skinToneSize: string;\n skinToneFz: string;\n }\n> = {\n xs: {\n emojiSize: 28,\n emojiFz: rem(16),\n previewPy: rem(4),\n skinToneSize: rem(20),\n skinToneFz: rem(13),\n },\n sm: {\n emojiSize: 32,\n emojiFz: rem(18),\n previewPy: rem(5),\n skinToneSize: rem(22),\n skinToneFz: rem(14),\n },\n md: {\n emojiSize: 36,\n emojiFz: rem(22),\n previewPy: rem(7),\n skinToneSize: rem(24),\n skinToneFz: rem(16),\n },\n lg: {\n emojiSize: 42,\n emojiFz: rem(26),\n previewPy: rem(8),\n skinToneSize: rem(28),\n skinToneFz: rem(18),\n },\n xl: {\n emojiSize: 48,\n emojiFz: rem(30),\n previewPy: rem(10),\n skinToneSize: rem(32),\n skinToneFz: rem(20),\n },\n};\n\nconst EMOJI_FONT_SIZE_RATIO = 0.6;\n\nfunction getSizeValues(size: MantineSize | undefined) {\n return EMOJI_SIZES[size || 'sm'] || EMOJI_SIZES.sm;\n}\n\nconst varsResolver = createVarsResolver<EmojiPickerFactory>((_, { columns, emojiSize, size }) => {\n const sizeValues = getSizeValues(size);\n return {\n root: {\n '--ep-columns': columns?.toString() ?? '9',\n '--ep-emoji-size': emojiSize ? rem(emojiSize) : rem(sizeValues.emojiSize),\n '--ep-emoji-fz': emojiSize\n ? rem(Math.round(emojiSize * EMOJI_FONT_SIZE_RATIO))\n : sizeValues.emojiFz,\n '--ep-preview-py': sizeValues.previewPy,\n '--ep-skin-tone-size': sizeValues.skinToneSize,\n '--ep-skin-tone-fz': sizeValues.skinToneFz,\n },\n };\n});\n\nexport const EmojiPicker = factory<EmojiPickerFactory>((_props) => {\n const props = useProps('EmojiPicker', defaultProps, _props);\n const {\n classNames,\n className,\n style,\n styles,\n unstyled,\n vars,\n data,\n onEmojiSelect,\n columns,\n emojiSize,\n withSearch,\n withCategoryNav,\n withPreview,\n withSkinToneSelector,\n skinTone: skinToneProp,\n defaultSkinTone,\n onSkinToneChange,\n searchValue,\n onSearchChange,\n searchPlaceholder,\n emptySearchContent,\n withUnsupportedDetection,\n autoFocus,\n scrollAreaProps,\n categoryLabels,\n labels: labelsProp,\n size,\n searchInputProps,\n ...others\n } = props;\n\n const getStyles = useStyles<EmojiPickerFactory>({\n name: 'EmojiPicker',\n classes,\n props,\n classNames,\n styles,\n unstyled,\n vars,\n varsResolver,\n stable: true,\n });\n\n const [skinTone, setSkinTone] = useUncontrolled({\n value: skinToneProp,\n defaultValue: defaultSkinTone ?? 0,\n onChange: onSkinToneChange,\n });\n\n const cols = Math.max(columns ?? 9, 1);\n const baseId = useId();\n const idPrefix = `${baseId}-emoji`;\n const gridId = `${baseId}-grid`;\n const [activeDescendant, setActiveDescendant] = useState<string | undefined>(undefined);\n\n const filteredData = useMemo(() => {\n if (!withUnsupportedDetection) {\n return data;\n }\n return {\n emojis: data.emojis,\n categories: data.categories.map((cat) => ({\n ...cat,\n emojis: filterSupportedEmojis(data.emojis, cat.emojis),\n })),\n };\n }, [data, withUnsupportedDetection]);\n\n const { query, setQuery, results, isSearching } = useEmojiSearch({\n data: filteredData,\n searchValue,\n onSearchChange,\n });\n\n const searchInputRef = useRef<HTMLInputElement>(null);\n const scrollToItemRef = useRef<EmojiPickerScrollToItem | null>(null);\n\n const mergedLabels: Required<EmojiPickerLabels> = useMemo(\n () => ({ ...DEFAULT_LABELS, ...labelsProp }),\n [labelsProp]\n );\n\n const ctxValue = useMemo(\n () => ({ getStyles, labels: mergedLabels, size }),\n [getStyles, mergedLabels, size]\n );\n\n const categoryItems = useMemo<FlattenedRowItem[]>(\n () => flattenEmojiData(filteredData, cols, categoryLabels),\n [filteredData, cols, categoryLabels]\n );\n\n const searchItems = useMemo<FlattenedRowItem[]>(\n () =>\n flattenEmojiData(\n {\n emojis: filteredData.emojis,\n categories: [\n {\n id: 'search',\n label: mergedLabels.searchResults,\n emojis: results.map((emoji) => emoji.id),\n },\n ],\n },\n cols\n ),\n [filteredData.emojis, cols, results, mergedLabels.searchResults]\n );\n\n const flattenedItems = isSearching ? searchItems : categoryItems;\n\n const navCategories = useMemo(\n () =>\n categoryItems.reduce<EmojiPickerNavCategory[]>((acc, item) => {\n if (item.type === 'header') {\n acc.push({ id: item.categoryId, label: item.label });\n }\n return acc;\n }, []),\n [categoryItems]\n );\n\n const scrollToItem = useCallback((itemIndex: number) => {\n scrollToItemRef.current?.(itemIndex, 'auto');\n }, []);\n\n const handleEmojiSelect = useCallback(\n (emoji: EmojiPickerEmoji) => {\n onEmojiSelect?.(emoji);\n },\n [onEmojiSelect]\n );\n\n const {\n model,\n activeIndex,\n activeEmoji,\n keyboardActive,\n handleKeyDown,\n setPointerIndex,\n setAnchorIndex,\n } = useEmojiGridNavigation({\n items: flattenedItems,\n resetKey: query,\n skinTone,\n onEmojiSelect: handleEmojiSelect,\n scrollToItem,\n });\n\n const handleGridKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Escape') {\n const focusStayedInGrid = event.currentTarget.contains(document.activeElement);\n if (withSearch && focusStayedInGrid && searchInputRef.current) {\n event.preventDefault();\n searchInputRef.current.focus();\n }\n return;\n }\n handleKeyDown(event);\n },\n [handleKeyDown, withSearch]\n );\n\n const handleSearchKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.nativeEvent.isComposing) {\n return;\n }\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter') {\n handleKeyDown(event);\n }\n },\n [handleKeyDown]\n );\n\n const handleEmojiClick = useCallback(\n (index: number) => {\n const emoji = model.emojis[index];\n if (emoji) {\n setPointerIndex(index);\n handleEmojiSelect(applySkinTone(emoji, skinTone));\n }\n },\n [model, setPointerIndex, handleEmojiSelect, skinTone]\n );\n\n const scrollToCategory = useCallback(\n (categoryId: string) => {\n const itemIndex = flattenedItems.findIndex(\n (item) => item.type === 'header' && item.categoryId === categoryId\n );\n if (itemIndex !== -1) {\n setAnchorIndex(model.itemOffsets[itemIndex]);\n scrollToItemRef.current?.(itemIndex, 'start');\n }\n },\n [flattenedItems, model, setAnchorIndex]\n );\n\n const pendingCategoryRef = useRef<string | null>(null);\n\n const handleCategoryClick = useCallback(\n (categoryId: string) => {\n if (isSearching) {\n pendingCategoryRef.current = categoryId;\n setQuery('');\n return;\n }\n scrollToCategory(categoryId);\n },\n [isSearching, setQuery, scrollToCategory]\n );\n\n useEffect(() => {\n if (!isSearching && pendingCategoryRef.current !== null) {\n const categoryId = pendingCategoryRef.current;\n pendingCategoryRef.current = null;\n scrollToCategory(categoryId);\n }\n }, [isSearching, scrollToCategory]);\n\n return (\n <EmojiPickerProvider value={ctxValue}>\n <Box {...getStyles('root')} {...others}>\n {withSearch && (\n <EmojiPickerSearch\n query={query}\n onQueryChange={setQuery}\n placeholder={searchPlaceholder}\n autoFocus={autoFocus}\n inputRef={searchInputRef}\n onKeyDown={handleSearchKeyDown}\n controls={flattenedItems.length > 0 ? gridId : undefined}\n activeDescendant={activeDescendant}\n searchInputProps={searchInputProps}\n />\n )}\n\n {withCategoryNav && (\n <EmojiPickerCategoryNav\n categories={navCategories}\n onCategoryClick={handleCategoryClick}\n />\n )}\n\n <EmojiPickerGrid\n items={flattenedItems}\n itemOffsets={model.itemOffsets}\n columns={cols}\n skinTone={skinTone}\n focusedIndex={keyboardActive ? activeIndex : -1}\n focusedItemIndex={keyboardActive ? getEmojiItemIndex(model, activeIndex) : -1}\n idPrefix={idPrefix}\n gridId={gridId}\n onActiveDescendantChange={setActiveDescendant}\n emojiSize={emojiSize ?? getSizeValues(size).emojiSize}\n emptyContent={emptySearchContent || mergedLabels.noResults}\n onKeyDown={handleGridKeyDown}\n onPointerIndex={setPointerIndex}\n onEmojiClick={handleEmojiClick}\n scrollToItemRef={scrollToItemRef}\n scrollAreaProps={scrollAreaProps}\n />\n\n {(withPreview || withSkinToneSelector) && (\n <EmojiPickerPreview emoji={activeEmoji} skinTone={skinTone} withPreview={!!withPreview}>\n {withSkinToneSelector && (\n <EmojiPickerSkinTone skinTone={skinTone} onSkinToneChange={setSkinTone} />\n )}\n </EmojiPickerPreview>\n )}\n </Box>\n </EmojiPickerProvider>\n );\n});\n\nEmojiPicker.classes = classes;\nEmojiPicker.displayName = '@mantine/emoji/EmojiPicker';\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
// packages/@mantine/emoji/src/components/EmojiPicker/EmojiPicker.module.css
|
|
3
|
+
var EmojiPicker_module_default = {
|
|
4
|
+
"root": "m_873e66bb",
|
|
5
|
+
"search": "m_b2613941",
|
|
6
|
+
"categoryNav": "m_d6a2ef4c",
|
|
7
|
+
"categoryNavButton": "m_f214599e",
|
|
8
|
+
"content": "m_5d2ad400",
|
|
9
|
+
"empty": "m_5fd65a94",
|
|
10
|
+
"categoryLabel": "m_b987dedd",
|
|
11
|
+
"grid": "m_8739711f",
|
|
12
|
+
"emoji": "m_5fd6558d",
|
|
13
|
+
"preview": "m_117c834f",
|
|
14
|
+
"previewEmoji": "m_91abc637",
|
|
15
|
+
"previewName": "m_2e01327a",
|
|
16
|
+
"skinToneDropdown": "m_16864e19",
|
|
17
|
+
"skinToneSelector": "m_774cfde7",
|
|
18
|
+
"skinToneButton": "m_daf4133a"
|
|
19
|
+
};
|
|
20
|
+
export { EmojiPicker_module_default as default };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=EmojiPicker.module.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmojiPicker.module.mjs","names":[],"sources":["../../../src/components/EmojiPicker/EmojiPicker.module.css"],"sourcesContent":[".root {\n --ep-columns: 9;\n --ep-emoji-size: 32px;\n --ep-emoji-fz: 18px;\n --ep-preview-py: 5px;\n --ep-skin-tone-size: 22px;\n --ep-skin-tone-fz: 14px;\n\n display: flex;\n flex-direction: column;\n width: calc(var(--ep-columns) * var(--ep-emoji-size) + var(--mantine-spacing-xs) * 2 + 2px);\n border: 1px solid var(--mantine-color-default-border);\n border-radius: var(--mantine-radius-default);\n background-color: var(--mantine-color-body);\n}\n\n.search {\n padding: var(--mantine-spacing-xs);\n padding-bottom: 0;\n}\n\n.categoryNav {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n padding: 4px var(--mantine-spacing-xs);\n border-bottom: 1px solid var(--mantine-color-default-border);\n}\n\n.categoryNavButton {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1 1 calc(var(--ep-emoji-fz) + 4px);\n max-width: var(--ep-emoji-size);\n height: var(--ep-emoji-size);\n font-size: var(--ep-emoji-fz);\n border-radius: var(--mantine-radius-sm);\n cursor: pointer;\n\n @mixin hover {\n background-color: var(--mantine-color-default-hover);\n }\n}\n\n.content {\n padding: 0 var(--mantine-spacing-xs);\n\n & [role='grid']:focus-visible {\n outline: 2px solid var(--mantine-primary-color-filled);\n outline-offset: -2px;\n }\n}\n\n.empty {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--mantine-spacing-xs);\n text-align: center;\n}\n\n.categoryLabel {\n display: flex;\n align-items: center;\n font-size: var(--mantine-font-size-xs);\n font-weight: 600;\n color: var(--mantine-color-dimmed);\n padding: var(--mantine-spacing-xs) 0;\n background-color: var(--mantine-color-body);\n}\n\n.grid {\n display: grid;\n grid-template-columns: repeat(var(--ep-columns), var(--ep-emoji-size));\n}\n\n.emoji {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--ep-emoji-size);\n height: var(--ep-emoji-size);\n font-size: var(--ep-emoji-fz);\n border-radius: var(--mantine-radius-sm);\n cursor: pointer;\n line-height: 1;\n padding: 0;\n border: none;\n background: none;\n\n &:focus,\n &:focus-visible {\n outline: none;\n background: none;\n }\n\n &[data-focused] {\n background-color: var(--mantine-color-default-hover);\n outline: 2px solid var(--mantine-primary-color-filled);\n outline-offset: -2px;\n }\n\n @mixin hover {\n background-color: var(--mantine-color-default-hover);\n }\n}\n\n:global(.mantine-focus-auto).emoji:focus-visible {\n outline: none;\n}\n\n.preview {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--mantine-spacing-xs);\n padding: var(--ep-preview-py) var(--mantine-spacing-xs);\n border-top: 1px solid var(--mantine-color-default-border);\n}\n\n.previewEmoji {\n font-size: var(--ep-emoji-fz);\n line-height: 1;\n flex-shrink: 0;\n}\n\n.previewName {\n font-size: var(--mantine-font-size-sm);\n color: var(--mantine-color-dimmed);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n flex: 1;\n}\n\n.skinToneDropdown {\n padding: 4px;\n}\n\n.skinToneSelector {\n display: flex;\n gap: 2px;\n}\n\n.skinToneButton {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--ep-skin-tone-size);\n height: var(--ep-skin-tone-size);\n font-size: var(--ep-skin-tone-fz);\n border-radius: var(--mantine-radius-sm);\n cursor: pointer;\n line-height: 1;\n\n @mixin hover {\n background-color: var(--mantine-color-default-hover);\n }\n}\n\n.skinToneSelector .skinToneButton[data-active] {\n background-color: var(--mantine-color-default-hover);\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { getCanonicalCategoryIcon } from "../../categories.mjs";
|
|
3
|
+
import { useEmojiPickerContext } from "./EmojiPicker.context.mjs";
|
|
4
|
+
import { memo, useState } from "react";
|
|
5
|
+
import { UnstyledButton, createScopedKeydownHandler, useDirection } from "@mantine/core";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
// packages/@mantine/emoji/src/components/EmojiPicker/EmojiPickerCategoryNav.tsx
|
|
8
|
+
const EmojiPickerCategoryNav = memo(function EmojiPickerCategoryNav({ categories, onCategoryClick }) {
|
|
9
|
+
const ctx = useEmojiPickerContext();
|
|
10
|
+
const { dir } = useDirection();
|
|
11
|
+
const [tabStopId, setTabStopId] = useState(null);
|
|
12
|
+
const tabStopIndex = Math.max(categories.findIndex((category) => category.id === tabStopId), 0);
|
|
13
|
+
const handleKeyDown = createScopedKeydownHandler({
|
|
14
|
+
siblingSelector: "[data-category-nav-button]",
|
|
15
|
+
parentSelector: "[data-category-nav]",
|
|
16
|
+
orientation: "horizontal",
|
|
17
|
+
loop: true,
|
|
18
|
+
dir
|
|
19
|
+
});
|
|
20
|
+
return /* @__PURE__ */ jsx("div", {
|
|
21
|
+
...ctx.getStyles("categoryNav"),
|
|
22
|
+
"data-category-nav": true,
|
|
23
|
+
children: categories.map((category, index) => /* @__PURE__ */ jsx(UnstyledButton, {
|
|
24
|
+
...ctx.getStyles("categoryNavButton"),
|
|
25
|
+
"aria-label": category.label,
|
|
26
|
+
title: category.label,
|
|
27
|
+
tabIndex: index === tabStopIndex ? 0 : -1,
|
|
28
|
+
"data-category-nav-button": true,
|
|
29
|
+
onFocus: () => setTabStopId(category.id),
|
|
30
|
+
onKeyDown: handleKeyDown,
|
|
31
|
+
onClick: () => onCategoryClick(category.id),
|
|
32
|
+
children: getCanonicalCategoryIcon(category.id) || "📁"
|
|
33
|
+
}, category.id))
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
export { EmojiPickerCategoryNav };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=EmojiPickerCategoryNav.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmojiPickerCategoryNav.mjs","names":[],"sources":["../../../src/components/EmojiPicker/EmojiPickerCategoryNav.tsx"],"sourcesContent":["import { memo, useState } from 'react';\nimport { createScopedKeydownHandler, UnstyledButton, useDirection } from '@mantine/core';\nimport { getCanonicalCategoryIcon } from '../../categories';\nimport { useEmojiPickerContext } from './EmojiPicker.context';\n\nexport interface EmojiPickerNavCategory {\n id: string;\n label: string;\n}\n\ninterface EmojiPickerCategoryNavProps {\n categories: EmojiPickerNavCategory[];\n onCategoryClick: (categoryId: string) => void;\n}\n\nexport const EmojiPickerCategoryNav = memo(function EmojiPickerCategoryNav({\n categories,\n onCategoryClick,\n}: EmojiPickerCategoryNavProps) {\n const ctx = useEmojiPickerContext();\n const { dir } = useDirection();\n const [tabStopId, setTabStopId] = useState<string | null>(null);\n const tabStopIndex = Math.max(\n categories.findIndex((category) => category.id === tabStopId),\n 0\n );\n\n const handleKeyDown = createScopedKeydownHandler({\n siblingSelector: '[data-category-nav-button]',\n parentSelector: '[data-category-nav]',\n orientation: 'horizontal',\n loop: true,\n dir,\n });\n\n return (\n <div {...ctx.getStyles('categoryNav')} data-category-nav>\n {categories.map((category, index) => (\n <UnstyledButton\n key={category.id}\n {...ctx.getStyles('categoryNavButton')}\n aria-label={category.label}\n title={category.label}\n tabIndex={index === tabStopIndex ? 0 : -1}\n data-category-nav-button\n onFocus={() => setTabStopId(category.id)}\n onKeyDown={handleKeyDown}\n onClick={() => onCategoryClick(category.id)}\n >\n {getCanonicalCategoryIcon(category.id) || '📁'}\n </UnstyledButton>\n ))}\n </div>\n );\n});\n"],"mappings":";;;;;;;AAeA,MAAa,yBAAyB,KAAK,SAAS,uBAAuB,EACzE,YACA,mBAC8B;CAC9B,MAAM,MAAM,sBAAsB;CAClC,MAAM,EAAE,QAAQ,aAAa;CAC7B,MAAM,CAAC,WAAW,gBAAgB,SAAwB,IAAI;CAC9D,MAAM,eAAe,KAAK,IACxB,WAAW,WAAW,aAAa,SAAS,OAAO,SAAS,GAC5D,CACF;CAEA,MAAM,gBAAgB,2BAA2B;EAC/C,iBAAiB;EACjB,gBAAgB;EAChB,aAAa;EACb,MAAM;EACN;CACF,CAAC;CAED,OACE,oBAAC,OAAD;EAAK,GAAI,IAAI,UAAU,aAAa;EAAG,qBAAA;EACpC,UAAA,WAAW,KAAK,UAAU,UACzB,oBAAC,gBAAD;GAEE,GAAI,IAAI,UAAU,mBAAmB;GACrC,cAAY,SAAS;GACrB,OAAO,SAAS;GAChB,UAAU,UAAU,eAAe,IAAI;GACvC,4BAAA;GACA,eAAe,aAAa,SAAS,EAAE;GACvC,WAAW;GACX,eAAe,gBAAgB,SAAS,EAAE;GAEzC,UAAA,yBAAyB,SAAS,EAAE,KAAK;EAC5B,GAXT,SAAS,EAWA,CACjB;CACE,CAAA;AAET,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEmojiPickerContext } from "./EmojiPicker.context.mjs";
|
|
3
|
+
import { EmojiButton } from "./EmojiButton.mjs";
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import { ScrollArea } from "@mantine/core";
|
|
6
|
+
import { useIsomorphicEffect } from "@mantine/hooks";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
9
|
+
// packages/@mantine/emoji/src/components/EmojiPicker/EmojiPickerGrid.tsx
|
|
10
|
+
const HEADER_HEIGHT = 32;
|
|
11
|
+
const DEFAULT_SCROLL_AREA_HEIGHT = 300;
|
|
12
|
+
function getEmojiIndexFromEvent(event) {
|
|
13
|
+
const target = event.target.closest("[data-emoji-index]");
|
|
14
|
+
return target ? Number(target.dataset.emojiIndex) : -1;
|
|
15
|
+
}
|
|
16
|
+
function EmojiPickerGrid({ items, itemOffsets, columns, skinTone, focusedIndex, focusedItemIndex, idPrefix, gridId, onActiveDescendantChange, emojiSize, emptyContent, onKeyDown, onPointerIndex, onEmojiClick, scrollToItemRef, scrollAreaProps }) {
|
|
17
|
+
const ctx = useEmojiPickerContext();
|
|
18
|
+
const viewportRef = useRef(null);
|
|
19
|
+
const lastPointerRef = useRef(null);
|
|
20
|
+
const virtualizer = useVirtualizer({
|
|
21
|
+
count: items.length,
|
|
22
|
+
getScrollElement: () => viewportRef.current,
|
|
23
|
+
estimateSize: (index) => items[index].type === "header" ? HEADER_HEIGHT : emojiSize,
|
|
24
|
+
overscan: 10
|
|
25
|
+
});
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
virtualizer.measure();
|
|
28
|
+
}, [emojiSize, virtualizer]);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
scrollToItemRef.current = (itemIndex, align) => {
|
|
31
|
+
virtualizer.scrollToIndex(itemIndex, { align });
|
|
32
|
+
};
|
|
33
|
+
}, [virtualizer, scrollToItemRef]);
|
|
34
|
+
const virtualItems = virtualizer.getVirtualItems();
|
|
35
|
+
const focusedItemMounted = focusedIndex !== -1 && virtualItems.some((item) => item.index === focusedItemIndex);
|
|
36
|
+
const hasEmojis = items.length > 0;
|
|
37
|
+
const activeDescendant = hasEmojis && focusedItemMounted ? `${idPrefix}-${focusedIndex}` : void 0;
|
|
38
|
+
useIsomorphicEffect(() => {
|
|
39
|
+
onActiveDescendantChange(activeDescendant);
|
|
40
|
+
}, [activeDescendant, onActiveDescendantChange]);
|
|
41
|
+
const handleMouseMove = (event) => {
|
|
42
|
+
const last = lastPointerRef.current;
|
|
43
|
+
if (last && last.x === event.clientX && last.y === event.clientY) return;
|
|
44
|
+
lastPointerRef.current = {
|
|
45
|
+
x: event.clientX,
|
|
46
|
+
y: event.clientY
|
|
47
|
+
};
|
|
48
|
+
const index = getEmojiIndexFromEvent(event);
|
|
49
|
+
if (index !== -1) onPointerIndex(index);
|
|
50
|
+
};
|
|
51
|
+
const handleMouseDown = (event) => {
|
|
52
|
+
if (getEmojiIndexFromEvent(event) !== -1) event.preventDefault();
|
|
53
|
+
};
|
|
54
|
+
const handleClick = (event) => {
|
|
55
|
+
const index = getEmojiIndexFromEvent(event);
|
|
56
|
+
if (index !== -1) onEmojiClick(index);
|
|
57
|
+
};
|
|
58
|
+
return /* @__PURE__ */ jsx(ScrollArea, {
|
|
59
|
+
scrollbars: "y",
|
|
60
|
+
...scrollAreaProps,
|
|
61
|
+
...ctx.getStyles("content", { style: {
|
|
62
|
+
height: DEFAULT_SCROLL_AREA_HEIGHT,
|
|
63
|
+
...scrollAreaProps?.style
|
|
64
|
+
} }),
|
|
65
|
+
viewportRef,
|
|
66
|
+
viewportProps: {
|
|
67
|
+
...scrollAreaProps?.viewportProps,
|
|
68
|
+
...hasEmojis ? {
|
|
69
|
+
id: gridId,
|
|
70
|
+
role: "grid",
|
|
71
|
+
tabIndex: 0,
|
|
72
|
+
"aria-label": ctx.labels.emojis,
|
|
73
|
+
"aria-colcount": columns,
|
|
74
|
+
"aria-activedescendant": activeDescendant,
|
|
75
|
+
onKeyDown,
|
|
76
|
+
onMouseMove: handleMouseMove,
|
|
77
|
+
onMouseDown: handleMouseDown,
|
|
78
|
+
onClick: handleClick
|
|
79
|
+
} : { tabIndex: -1 }
|
|
80
|
+
},
|
|
81
|
+
children: hasEmojis ? /* @__PURE__ */ jsx("div", {
|
|
82
|
+
style: {
|
|
83
|
+
height: virtualizer.getTotalSize(),
|
|
84
|
+
width: "100%",
|
|
85
|
+
position: "relative"
|
|
86
|
+
},
|
|
87
|
+
children: virtualItems.map((virtualItem) => {
|
|
88
|
+
const item = items[virtualItem.index];
|
|
89
|
+
const positionStyle = {
|
|
90
|
+
position: "absolute",
|
|
91
|
+
top: 0,
|
|
92
|
+
left: 0,
|
|
93
|
+
width: "100%",
|
|
94
|
+
height: virtualItem.size,
|
|
95
|
+
transform: `translateY(${virtualItem.start}px)`
|
|
96
|
+
};
|
|
97
|
+
if (item.type === "header") return /* @__PURE__ */ jsx("div", {
|
|
98
|
+
role: "row",
|
|
99
|
+
...ctx.getStyles("categoryLabel", { style: positionStyle }),
|
|
100
|
+
"data-category": item.categoryId,
|
|
101
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
102
|
+
role: "columnheader",
|
|
103
|
+
"aria-colspan": columns,
|
|
104
|
+
children: item.label
|
|
105
|
+
})
|
|
106
|
+
}, `header-${item.categoryId}`);
|
|
107
|
+
const emojiOffset = itemOffsets[virtualItem.index];
|
|
108
|
+
return /* @__PURE__ */ jsx("div", {
|
|
109
|
+
role: "row",
|
|
110
|
+
...ctx.getStyles("grid", { style: positionStyle }),
|
|
111
|
+
children: item.emojis.map((emoji, colIndex) => {
|
|
112
|
+
const emojiIndex = emojiOffset + colIndex;
|
|
113
|
+
return /* @__PURE__ */ jsx(EmojiButton, {
|
|
114
|
+
emoji,
|
|
115
|
+
skinTone,
|
|
116
|
+
emojiIndex,
|
|
117
|
+
focused: emojiIndex === focusedIndex,
|
|
118
|
+
id: `${idPrefix}-${emojiIndex}`
|
|
119
|
+
}, emoji.id);
|
|
120
|
+
})
|
|
121
|
+
}, `row-${virtualItem.index}`);
|
|
122
|
+
})
|
|
123
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
124
|
+
...ctx.getStyles("empty"),
|
|
125
|
+
children: emptyContent
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
export { EmojiPickerGrid };
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=EmojiPickerGrid.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmojiPickerGrid.mjs","names":[],"sources":["../../../src/components/EmojiPicker/EmojiPickerGrid.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport { useVirtualizer } from '@tanstack/react-virtual';\nimport { ScrollArea, ScrollAreaProps } from '@mantine/core';\nimport { useIsomorphicEffect } from '@mantine/hooks';\nimport type { FlattenedRowItem } from '../../types';\nimport { EmojiButton } from './EmojiButton';\nimport { useEmojiPickerContext } from './EmojiPicker.context';\n\nexport type EmojiPickerScrollToItem = (itemIndex: number, align: 'auto' | 'start') => void;\n\ninterface EmojiPickerGridProps {\n items: FlattenedRowItem[];\n itemOffsets: number[];\n columns: number;\n skinTone: number;\n focusedIndex: number;\n focusedItemIndex: number;\n idPrefix: string;\n gridId: string;\n onActiveDescendantChange: (id: string | undefined) => void;\n emojiSize: number;\n emptyContent: React.ReactNode;\n onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;\n onPointerIndex: (index: number) => void;\n onEmojiClick: (index: number) => void;\n scrollToItemRef: React.RefObject<EmojiPickerScrollToItem | null>;\n scrollAreaProps?: ScrollAreaProps;\n}\n\nconst HEADER_HEIGHT = 32;\nconst DEFAULT_SCROLL_AREA_HEIGHT = 300;\n\nfunction getEmojiIndexFromEvent(event: React.SyntheticEvent) {\n const target = (event.target as HTMLElement).closest<HTMLElement>('[data-emoji-index]');\n return target ? Number(target.dataset.emojiIndex) : -1;\n}\n\nexport function EmojiPickerGrid({\n items,\n itemOffsets,\n columns,\n skinTone,\n focusedIndex,\n focusedItemIndex,\n idPrefix,\n gridId,\n onActiveDescendantChange,\n emojiSize,\n emptyContent,\n onKeyDown,\n onPointerIndex,\n onEmojiClick,\n scrollToItemRef,\n scrollAreaProps,\n}: EmojiPickerGridProps) {\n const ctx = useEmojiPickerContext();\n const viewportRef = useRef<HTMLDivElement>(null);\n const lastPointerRef = useRef<{ x: number; y: number } | null>(null);\n\n const virtualizer = useVirtualizer({\n count: items.length,\n getScrollElement: () => viewportRef.current,\n estimateSize: (index) => (items[index].type === 'header' ? HEADER_HEIGHT : emojiSize),\n overscan: 10,\n });\n\n useEffect(() => {\n virtualizer.measure();\n }, [emojiSize, virtualizer]);\n\n useEffect(() => {\n scrollToItemRef.current = (itemIndex, align) => {\n virtualizer.scrollToIndex(itemIndex, { align });\n };\n }, [virtualizer, scrollToItemRef]);\n\n const virtualItems = virtualizer.getVirtualItems();\n const focusedItemMounted =\n focusedIndex !== -1 && virtualItems.some((item) => item.index === focusedItemIndex);\n const hasEmojis = items.length > 0;\n const activeDescendant =\n hasEmojis && focusedItemMounted ? `${idPrefix}-${focusedIndex}` : undefined;\n\n useIsomorphicEffect(() => {\n onActiveDescendantChange(activeDescendant);\n }, [activeDescendant, onActiveDescendantChange]);\n\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\n const last = lastPointerRef.current;\n if (last && last.x === event.clientX && last.y === event.clientY) {\n return;\n }\n lastPointerRef.current = { x: event.clientX, y: event.clientY };\n const index = getEmojiIndexFromEvent(event);\n if (index !== -1) {\n onPointerIndex(index);\n }\n };\n\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\n if (getEmojiIndexFromEvent(event) !== -1) {\n event.preventDefault();\n }\n };\n\n const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {\n const index = getEmojiIndexFromEvent(event);\n if (index !== -1) {\n onEmojiClick(index);\n }\n };\n\n return (\n <ScrollArea\n scrollbars=\"y\"\n {...scrollAreaProps}\n {...ctx.getStyles('content', {\n style: { height: DEFAULT_SCROLL_AREA_HEIGHT, ...scrollAreaProps?.style },\n })}\n viewportRef={viewportRef}\n viewportProps={{\n ...scrollAreaProps?.viewportProps,\n ...(hasEmojis\n ? {\n id: gridId,\n role: 'grid',\n tabIndex: 0,\n 'aria-label': ctx.labels.emojis,\n 'aria-colcount': columns,\n 'aria-activedescendant': activeDescendant,\n onKeyDown,\n onMouseMove: handleMouseMove,\n onMouseDown: handleMouseDown,\n onClick: handleClick,\n }\n : { tabIndex: -1 }),\n }}\n >\n {hasEmojis ? (\n <div style={{ height: virtualizer.getTotalSize(), width: '100%', position: 'relative' }}>\n {virtualItems.map((virtualItem) => {\n const item = items[virtualItem.index];\n const positionStyle: React.CSSProperties = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: virtualItem.size,\n transform: `translateY(${virtualItem.start}px)`,\n };\n\n if (item.type === 'header') {\n return (\n <div\n key={`header-${item.categoryId}`}\n role=\"row\"\n {...ctx.getStyles('categoryLabel', { style: positionStyle })}\n data-category={item.categoryId}\n >\n <span role=\"columnheader\" aria-colspan={columns}>\n {item.label}\n </span>\n </div>\n );\n }\n\n const emojiOffset = itemOffsets[virtualItem.index];\n\n return (\n <div\n key={`row-${virtualItem.index}`}\n role=\"row\"\n {...ctx.getStyles('grid', { style: positionStyle })}\n >\n {item.emojis.map((emoji, colIndex) => {\n const emojiIndex = emojiOffset + colIndex;\n return (\n <EmojiButton\n key={emoji.id}\n emoji={emoji}\n skinTone={skinTone}\n emojiIndex={emojiIndex}\n focused={emojiIndex === focusedIndex}\n id={`${idPrefix}-${emojiIndex}`}\n />\n );\n })}\n </div>\n );\n })}\n </div>\n ) : (\n <div {...ctx.getStyles('empty')}>{emptyContent}</div>\n )}\n </ScrollArea>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { applySkinTone } from "../../utils/emoji-grid-utils.mjs";
|
|
3
|
+
import { useEmojiPickerContext } from "./EmojiPicker.context.mjs";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
// packages/@mantine/emoji/src/components/EmojiPicker/EmojiPickerPreview.tsx
|
|
6
|
+
function EmojiPickerPreview({ emoji, skinTone, withPreview, children }) {
|
|
7
|
+
const ctx = useEmojiPickerContext();
|
|
8
|
+
const displayEmoji = emoji ? applySkinTone(emoji, skinTone) : null;
|
|
9
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10
|
+
...ctx.getStyles("preview"),
|
|
11
|
+
role: withPreview ? "group" : void 0,
|
|
12
|
+
"aria-label": withPreview ? ctx.labels.preview : void 0,
|
|
13
|
+
children: [withPreview && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
14
|
+
...ctx.getStyles("previewEmoji"),
|
|
15
|
+
children: displayEmoji?.emoji
|
|
16
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
17
|
+
...ctx.getStyles("previewName"),
|
|
18
|
+
children: displayEmoji?.name
|
|
19
|
+
})] }), children]
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export { EmojiPickerPreview };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=EmojiPickerPreview.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmojiPickerPreview.mjs","names":[],"sources":["../../../src/components/EmojiPicker/EmojiPickerPreview.tsx"],"sourcesContent":["import type { EmojiPickerEmoji } from '../../types';\nimport { applySkinTone } from '../../utils';\nimport { useEmojiPickerContext } from './EmojiPicker.context';\n\ninterface EmojiPickerPreviewProps {\n emoji: EmojiPickerEmoji | null;\n skinTone: number;\n withPreview: boolean;\n children?: React.ReactNode;\n}\n\nexport function EmojiPickerPreview({\n emoji,\n skinTone,\n withPreview,\n children,\n}: EmojiPickerPreviewProps) {\n const ctx = useEmojiPickerContext();\n const displayEmoji = emoji ? applySkinTone(emoji, skinTone) : null;\n\n return (\n <div\n {...ctx.getStyles('preview')}\n role={withPreview ? 'group' : undefined}\n aria-label={withPreview ? ctx.labels.preview : undefined}\n >\n {withPreview && (\n <>\n <span {...ctx.getStyles('previewEmoji')}>{displayEmoji?.emoji}</span>\n <span {...ctx.getStyles('previewName')}>{displayEmoji?.name}</span>\n </>\n )}\n {children}\n </div>\n );\n}\n"],"mappings":";;;;;AAWA,SAAgB,mBAAmB,EACjC,OACA,UACA,aACA,YAC0B;CAC1B,MAAM,MAAM,sBAAsB;CAClC,MAAM,eAAe,QAAQ,cAAc,OAAO,QAAQ,IAAI;CAE9D,OACE,qBAAC,OAAD;EACE,GAAI,IAAI,UAAU,SAAS;EAC3B,MAAM,cAAc,UAAU,KAAA;EAC9B,cAAY,cAAc,IAAI,OAAO,UAAU,KAAA;EAHjD,UAAA,CAKG,eACC,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;GAAM,GAAI,IAAI,UAAU,cAAc;GAAI,UAAA,cAAc;EAAY,CAAA,GACpE,oBAAC,QAAD;GAAM,GAAI,IAAI,UAAU,aAAa;GAAI,UAAA,cAAc;EAAW,CAAA,CAClE,EAAA,CAAA,GAEH,QACE;;AAET"}
|