@ctrl-ui/react 0.8.9 → 0.8.11
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/dist/knob-contracts/app-shell-header-knobs.d.ts +4 -0
- package/dist/knob-contracts/app-shell-header-knobs.js +10 -0
- package/dist/knob-contracts/emoji-picker-knobs.d.ts +1 -1
- package/dist/knob-contracts/emoji-picker-knobs.js +1 -0
- package/dist/knob-contracts/icon-picker-knobs.d.ts +4 -0
- package/dist/knob-contracts/icon-picker-knobs.js +9 -0
- package/dist/knob-contracts/markdown-knobs.d.ts +1 -1
- package/dist/knob-contracts/markdown-knobs.js +3 -1
- package/dist/skin.d.ts +2 -0
- package/dist/styles/index.css +8 -6
- package/dist/styles/recipes/accordion.css +13 -1
- package/dist/styles/recipes/app-shell-header.css +43 -0
- package/dist/styles/recipes/collapsible.css +15 -0
- package/dist/styles/recipes/emoji-picker.css +84 -1
- package/dist/styles/recipes/icon-picker.css +101 -0
- package/dist/styles/recipes/markdown.css +66 -0
- package/dist/styles/recipes/popup-motion.css +10 -0
- package/dist/styles/recipes/scroll-area.css +53 -27
- package/dist/styles/recipes/tabs.css +104 -0
- package/dist/styles/recipes/tree.css +13 -1
- package/dist/ui/app-shell.d.ts +16 -0
- package/dist/ui/app-shell.js +39 -0
- package/dist/ui/collapsible.js +0 -1
- package/dist/ui/emoji-icon-picker.d.ts +13 -0
- package/dist/ui/emoji-icon-picker.js +50 -0
- package/dist/ui/emoji-picker.d.ts +14 -3
- package/dist/ui/emoji-picker.js +194 -15
- package/dist/ui/icon-picker.d.ts +35 -0
- package/dist/ui/icon-picker.js +140 -0
- package/dist/ui/markdown-elements.js +6 -10
- package/dist/ui/markdown.d.ts +6 -5
- package/dist/ui/markdown.js +12 -4
- package/dist/ui/page-layout.d.ts +1 -0
- package/dist/ui/page-layout.js +6 -2
- package/dist/ui/popover.d.ts +3 -0
- package/dist/ui/popover.js +36 -1
- package/dist/ui/scroll-area.d.ts +4 -1
- package/dist/ui/scroll-area.js +21 -6
- package/dist/ui/tabs.js +58 -22
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
@layer components {
|
|
2
2
|
:where([data-control-family="tabs"][data-slot="root"]) {
|
|
3
|
+
position: relative;
|
|
3
4
|
--cui-tabs-trigger-radius: var(--radius-control);
|
|
4
5
|
--cui-tabs-list-padding: calc(var(--spacing) * 1);
|
|
5
6
|
--cui-tabs-list-radius: calc(
|
|
@@ -178,6 +179,109 @@
|
|
|
178
179
|
outline-style: none;
|
|
179
180
|
}
|
|
180
181
|
|
|
182
|
+
@supports ((anchor-name: --aui-slide-panel) and (anchor-scope: --aui-slide-panel)) {
|
|
183
|
+
:where([data-control-family="tabs"][data-slot="panel"][data-slide="panel"][inert][data-slide-exiting][data-ending-style]) {
|
|
184
|
+
display: block;
|
|
185
|
+
position: absolute;
|
|
186
|
+
top: var(--_tabs-panel-top);
|
|
187
|
+
left: var(--_tabs-panel-left);
|
|
188
|
+
right: auto;
|
|
189
|
+
bottom: auto;
|
|
190
|
+
width: var(--_tabs-panel-width);
|
|
191
|
+
height: var(--_tabs-panel-height);
|
|
192
|
+
margin: 0;
|
|
193
|
+
overflow: clip;
|
|
194
|
+
animation: aui-slide-exit-hold var(--duration-slow) var(--ease-emphasized) both;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@supports (height: anchor-size(height)) {
|
|
198
|
+
:where([data-control-family="tabs"][data-slot="panel"][data-slide="panel"][inert][data-slide-exiting][data-ending-style]) {
|
|
199
|
+
height: anchor-size(height);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
:where(
|
|
204
|
+
[data-control-family="tabs"][data-slot="panel"][data-slide="panel"][inert][data-ending-style][data-activation-direction="right"]
|
|
205
|
+
) {
|
|
206
|
+
translate: -100% 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
:where(
|
|
210
|
+
[data-control-family="tabs"][data-slot="panel"][data-slide="panel"][inert][data-ending-style][data-activation-direction="left"]
|
|
211
|
+
) {
|
|
212
|
+
translate: 100% 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
:where(
|
|
216
|
+
[data-control-family="tabs"][data-slot="panel"][data-slide="panel"][inert][data-ending-style][data-activation-direction="down"]
|
|
217
|
+
) {
|
|
218
|
+
translate: 0 -100%;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
:where([data-control-family="tabs"][data-slot="panel"][data-slide="panel"][inert][data-ending-style][data-activation-direction="up"]) {
|
|
222
|
+
translate: 0 100%;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
:where(
|
|
226
|
+
[data-control-family="tabs"][data-slot="root"]:has(> [data-slot="panel"][data-starting-style])
|
|
227
|
+
> [data-slot="panel"][inert][data-ending-style]
|
|
228
|
+
) {
|
|
229
|
+
translate: 0 0;
|
|
230
|
+
transition: none;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
:where([data-control-family="tabs"][data-slot="panel"][data-slide="panel"][data-starting-style]) {
|
|
234
|
+
transition: none;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
:where([data-control-family="tabs"][data-slot="root"][data-slide-starting] > [data-slot="panel"][data-slide="panel"]) {
|
|
238
|
+
transition: none;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
:where([data-control-family="tabs"][data-slot="root"][data-slide-starting] > [data-slot="panel"][inert]) {
|
|
242
|
+
translate: 0 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
:where(
|
|
246
|
+
[data-control-family="tabs"][data-slot="root"][data-slide-starting]
|
|
247
|
+
> [data-slot="panel"]:not([inert])[data-activation-direction="right"]
|
|
248
|
+
) {
|
|
249
|
+
translate: 100% 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
:where(
|
|
253
|
+
[data-control-family="tabs"][data-slot="root"][data-slide-starting]
|
|
254
|
+
> [data-slot="panel"]:not([inert])[data-activation-direction="left"]
|
|
255
|
+
) {
|
|
256
|
+
translate: -100% 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
:where(
|
|
260
|
+
[data-control-family="tabs"][data-slot="root"][data-slide-starting]
|
|
261
|
+
> [data-slot="panel"]:not([inert])[data-activation-direction="down"]
|
|
262
|
+
) {
|
|
263
|
+
translate: 0 100%;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
:where(
|
|
267
|
+
[data-control-family="tabs"][data-slot="root"][data-slide-starting]
|
|
268
|
+
> [data-slot="panel"]:not([inert])[data-activation-direction="up"]
|
|
269
|
+
) {
|
|
270
|
+
translate: 0 -100%;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@supports (height: calc-size(auto, size)) {
|
|
274
|
+
:where([data-control-family="tabs"][data-slot="root"][data-slide-starting] > [data-slot="panel"][data-slide="panel"]:not([inert])) {
|
|
275
|
+
height: var(--aui-slide-prev-height, calc-size(auto, size));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
:where([data-control-family="tabs"][data-slot="root"][data-orientation="vertical"] > [data-slot="panel"]) {
|
|
281
|
+
min-width: 0;
|
|
282
|
+
flex: 1;
|
|
283
|
+
}
|
|
284
|
+
|
|
181
285
|
:where(
|
|
182
286
|
[data-control-family="tabs"][data-slot="list"][data-orientation="horizontal"]:not([data-variant="browser"]) ~ [data-slot="panel"]
|
|
183
287
|
) {
|
|
@@ -93,7 +93,6 @@
|
|
|
93
93
|
|
|
94
94
|
:where([data-control-family="tree"][data-slot="item-content"]) {
|
|
95
95
|
height: var(--collapsible-panel-height);
|
|
96
|
-
overflow: hidden;
|
|
97
96
|
opacity: 1;
|
|
98
97
|
transition-property: height, opacity;
|
|
99
98
|
transition-duration: var(--duration-base);
|
|
@@ -102,9 +101,22 @@
|
|
|
102
101
|
|
|
103
102
|
:where([data-control-family="tree"][data-slot="item-content"]:is([data-starting-style], [data-ending-style])) {
|
|
104
103
|
height: 0;
|
|
104
|
+
overflow: clip;
|
|
105
105
|
opacity: 0;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
@supports (transition-behavior: allow-discrete) {
|
|
109
|
+
:where([data-control-family="tree"][data-slot="item-content"]) {
|
|
110
|
+
transition-property: height, opacity, overflow;
|
|
111
|
+
transition-timing-function: var(--ease-emphasized), var(--ease-emphasized), step-end;
|
|
112
|
+
transition-behavior: normal, normal, allow-discrete;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
:where([data-control-family="tree"][data-slot="item-content"][data-ending-style]) {
|
|
116
|
+
transition-timing-function: var(--ease-emphasized), var(--ease-emphasized), step-start;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
108
120
|
:where([data-control-family="tree"][data-slot="item-group"]) {
|
|
109
121
|
translate: 0 0;
|
|
110
122
|
transition: translate var(--duration-base) var(--ease-emphasized);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SidebarProviderProps } from "./sidebar-provider.js";
|
|
2
|
+
import { SidebarInsetProps } from "./sidebar.js";
|
|
3
|
+
import { AppShellHeaderKnobStyle } from "../knob-contracts/app-shell-header-knobs.js";
|
|
4
|
+
import { PageScroll } from "./page-layout.js";
|
|
5
|
+
import { CSSProperties, ComponentProps } from "react";
|
|
6
|
+
//#region src/ui/app-shell.d.ts
|
|
7
|
+
export type AppShellProps = SidebarProviderProps & {
|
|
8
|
+
scroll?: PageScroll;
|
|
9
|
+
};
|
|
10
|
+
export declare function AppShell({ scroll, layout, className, ...props }: AppShellProps): import("react").JSX.Element;
|
|
11
|
+
export declare function AppShellContent({ className, ...props }: SidebarInsetProps): import("react").JSX.Element;
|
|
12
|
+
export type AppShellHeaderProps = Omit<ComponentProps<"header">, "style"> & {
|
|
13
|
+
style?: CSSProperties & AppShellHeaderKnobStyle;
|
|
14
|
+
};
|
|
15
|
+
export declare function AppShellHeader({ className, ...props }: AppShellHeaderProps): import("react").JSX.Element;
|
|
16
|
+
//#endregion
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { SidebarProvider } from "./sidebar-provider.js";
|
|
4
|
+
import { SidebarInset } from "./sidebar.js";
|
|
5
|
+
import { PageScrollContext, usePageScroll } from "./page-layout.js";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
//#region src/ui/app-shell.tsx
|
|
8
|
+
function AppShell({ scroll = "auto", layout = "viewport", className, ...props }) {
|
|
9
|
+
const resolvedScroll = usePageScroll(scroll);
|
|
10
|
+
return /* @__PURE__ */ jsx(PageScrollContext.Provider, {
|
|
11
|
+
value: resolvedScroll,
|
|
12
|
+
children: /* @__PURE__ */ jsx(SidebarProvider, {
|
|
13
|
+
"data-app-shell": "",
|
|
14
|
+
"data-scroll": resolvedScroll,
|
|
15
|
+
layout,
|
|
16
|
+
className: cn(layout === "viewport" && (resolvedScroll === "page" ? "min-h-dvh" : "h-dvh min-h-0 overflow-hidden"), className),
|
|
17
|
+
...props
|
|
18
|
+
})
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function AppShellContent({ className, ...props }) {
|
|
22
|
+
const scroll = usePageScroll();
|
|
23
|
+
return /* @__PURE__ */ jsx(SidebarInset, {
|
|
24
|
+
"data-app-shell-content": "",
|
|
25
|
+
className: cn("min-h-0", scroll !== "page" && "overflow-hidden", className),
|
|
26
|
+
...props
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function AppShellHeader({ className, ...props }) {
|
|
30
|
+
return /* @__PURE__ */ jsx("header", {
|
|
31
|
+
"data-control-ui": "app-shell",
|
|
32
|
+
"data-control-family": "app-shell-header",
|
|
33
|
+
"data-slot": "root",
|
|
34
|
+
className: cn("sticky top-0 z-30 flex shrink-0 items-center gap-2", className),
|
|
35
|
+
...props
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { AppShell, AppShellContent, AppShellHeader };
|
package/dist/ui/collapsible.js
CHANGED
|
@@ -52,7 +52,6 @@ function CollapsibleTrigger({ render, className, children, ...props }) {
|
|
|
52
52
|
}
|
|
53
53
|
function CollapsibleContent({ className, children, ...props }) {
|
|
54
54
|
return /* @__PURE__ */ jsx(Collapsible$1.Panel, {
|
|
55
|
-
className: "h-[var(--collapsible-panel-height)] overflow-hidden data-ending-style:h-0 data-starting-style:h-0",
|
|
56
55
|
...props,
|
|
57
56
|
render: (renderProps, state) => /* @__PURE__ */ jsx("div", {
|
|
58
57
|
"data-control-ui": "collapsible",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { EmojiPicker } from "./emoji-picker.js";
|
|
2
|
+
import { IconPicker } from "./icon-picker.js";
|
|
3
|
+
import { Tabs, TabsList } from "./tabs.js";
|
|
4
|
+
import { ComponentProps } from "react";
|
|
5
|
+
//#region src/ui/emoji-icon-picker.d.ts
|
|
6
|
+
export declare function EmojiIconPicker({ children, defaultValue, ...props }: ComponentProps<typeof Tabs<"emoji" | "icons">>): import("react").JSX.Element;
|
|
7
|
+
export declare function EmojiIconPickerTabs({ emojiLabel, iconsLabel, ...props }: Omit<ComponentProps<typeof TabsList>, "children"> & {
|
|
8
|
+
emojiLabel?: string;
|
|
9
|
+
iconsLabel?: string;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
11
|
+
export declare function EmojiIconPickerEmoji(props: ComponentProps<typeof EmojiPicker>): import("react").JSX.Element;
|
|
12
|
+
export declare function EmojiIconPickerIcons(props: ComponentProps<typeof IconPicker>): import("react").JSX.Element;
|
|
13
|
+
//#endregion
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { PopoverViewport } from "./popover.js";
|
|
3
|
+
import { Tabs, TabsList, TabsPanel, TabsTab } from "./tabs.js";
|
|
4
|
+
import { EmojiPicker } from "./emoji-picker.js";
|
|
5
|
+
import { IconPicker } from "./icon-picker.js";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/ui/emoji-icon-picker.tsx
|
|
8
|
+
function EmojiIconPicker({ children, defaultValue = "emoji", ...props }) {
|
|
9
|
+
return /* @__PURE__ */ jsx(Tabs, {
|
|
10
|
+
"data-control-ui": "emoji-icon-picker",
|
|
11
|
+
"data-control-family": "tabs",
|
|
12
|
+
"data-slot": "root",
|
|
13
|
+
defaultValue,
|
|
14
|
+
...props,
|
|
15
|
+
children: /* @__PURE__ */ jsx(PopoverViewport, { children })
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function EmojiIconPickerTabs({ emojiLabel = "Emoji", iconsLabel = "Icons", ...props }) {
|
|
19
|
+
return /* @__PURE__ */ jsxs(TabsList, {
|
|
20
|
+
size: "xs",
|
|
21
|
+
className: "mx-2 mt-2",
|
|
22
|
+
"aria-label": "Choose emoji or icon",
|
|
23
|
+
...props,
|
|
24
|
+
children: [/* @__PURE__ */ jsx(TabsTab, {
|
|
25
|
+
value: "emoji",
|
|
26
|
+
children: emojiLabel
|
|
27
|
+
}), /* @__PURE__ */ jsx(TabsTab, {
|
|
28
|
+
value: "icons",
|
|
29
|
+
children: iconsLabel
|
|
30
|
+
})]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function EmojiIconPickerEmoji(props) {
|
|
34
|
+
return /* @__PURE__ */ jsx(TabsPanel, {
|
|
35
|
+
value: "emoji",
|
|
36
|
+
"data-slide": void 0,
|
|
37
|
+
className: "mt-0",
|
|
38
|
+
children: /* @__PURE__ */ jsx(EmojiPicker, { ...props })
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function EmojiIconPickerIcons(props) {
|
|
42
|
+
return /* @__PURE__ */ jsx(TabsPanel, {
|
|
43
|
+
value: "icons",
|
|
44
|
+
"data-slide": void 0,
|
|
45
|
+
className: "mt-0",
|
|
46
|
+
children: /* @__PURE__ */ jsx(IconPicker, { ...props })
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { EmojiIconPicker, EmojiIconPickerEmoji, EmojiIconPickerIcons, EmojiIconPickerTabs };
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { EmojiPickerKnobStyle } from "../knob-contracts/emoji-picker-knobs.js";
|
|
2
2
|
import { CSSProperties, ComponentProps } from "react";
|
|
3
|
-
import { EmojiPicker as EmojiPicker$1 } from "frimousse";
|
|
3
|
+
import { Emoji, EmojiPicker as EmojiPicker$1 } from "frimousse";
|
|
4
4
|
//#region src/ui/emoji-picker.d.ts
|
|
5
5
|
type EmojiPickerStyle = {
|
|
6
6
|
style?: CSSProperties & EmojiPickerKnobStyle;
|
|
7
7
|
};
|
|
8
8
|
export type EmojiPickerProps = Omit<ComponentProps<typeof EmojiPicker$1.Root>, "style"> & EmojiPickerStyle;
|
|
9
|
-
export declare function EmojiPicker({ className, columns, ...props }: EmojiPickerProps): import("react").JSX.Element;
|
|
9
|
+
export declare function EmojiPicker({ className, columns, style, ref, onEmojiSelect, resolveEmojiData, ...props }: EmojiPickerProps): import("react").JSX.Element;
|
|
10
|
+
export declare function EmojiPickerCategories({ "aria-label": ariaLabel, onKeyDown, ...props }: ComponentProps<"div">): import("react").JSX.Element | null;
|
|
11
|
+
export type EmojiPickerRecentProps = ComponentProps<"div"> & {
|
|
12
|
+
emojis: readonly Emoji[];
|
|
13
|
+
label?: string;
|
|
14
|
+
};
|
|
15
|
+
export type EmojiPickerReactionsProps = ComponentProps<"div"> & {
|
|
16
|
+
emojis: readonly Emoji[];
|
|
17
|
+
onEmojiSelect: (emoji: Emoji) => void;
|
|
18
|
+
};
|
|
19
|
+
export declare function EmojiPickerReactions({ emojis, onEmojiSelect, "aria-label": ariaLabel, onKeyDown, ...props }: EmojiPickerReactionsProps): import("react").JSX.Element;
|
|
20
|
+
export declare function EmojiPickerRecent({ emojis, label, ...props }: EmojiPickerRecentProps): import("react").JSX.Element | null;
|
|
10
21
|
export type EmojiPickerSearchProps = Omit<ComponentProps<typeof EmojiPicker$1.Search>, "style"> & EmojiPickerStyle;
|
|
11
22
|
export declare function EmojiPickerSearch({ className, placeholder, "aria-label": ariaLabel, ...props }: EmojiPickerSearchProps): import("react").JSX.Element;
|
|
12
23
|
export type EmojiPickerContentProps = Omit<ComponentProps<typeof EmojiPicker$1.Viewport>, "style"> & EmojiPickerStyle;
|
|
13
|
-
export declare function EmojiPickerContent({ className, ...props }: EmojiPickerContentProps): import("react").JSX.Element;
|
|
24
|
+
export declare function EmojiPickerContent({ className, ref, ...props }: EmojiPickerContentProps): import("react").JSX.Element;
|
|
14
25
|
export type EmojiPickerFooterProps = Omit<ComponentProps<"div">, "style" | "children"> & EmojiPickerStyle & {
|
|
15
26
|
placeholder?: string;
|
|
16
27
|
};
|
package/dist/ui/emoji-picker.js
CHANGED
|
@@ -1,18 +1,180 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { Spinner } from "./spinner.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { createContext, useContext, useImperativeHandle, useRef, useState } from "react";
|
|
5
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { FlagIcon, HandIcon, HashIcon, HeartIcon, LeafIcon, LightbulbIcon, PizzaIcon, PlaneIcon, SearchIcon, SmileIcon, VolleyballIcon } from "lucide-react";
|
|
7
|
+
import { Toolbar } from "@base-ui/react/toolbar";
|
|
8
|
+
import { EmojiPicker as EmojiPicker$1, defaultEmojiDataResolver } from "frimousse";
|
|
7
9
|
//#region src/ui/emoji-picker.tsx
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
const EmojiPickerContext = createContext(null);
|
|
11
|
+
function useEmojiPicker() {
|
|
12
|
+
const picker = useContext(EmojiPickerContext);
|
|
13
|
+
if (!picker) throw new Error("EmojiPicker parts must be rendered inside <EmojiPicker>.");
|
|
14
|
+
return picker;
|
|
15
|
+
}
|
|
16
|
+
function EmojiPicker({ className, columns = 8, style, ref, onEmojiSelect, resolveEmojiData = defaultEmojiDataResolver, ...props }) {
|
|
17
|
+
const rootRef = useRef(null);
|
|
18
|
+
const viewportRef = useRef(null);
|
|
19
|
+
const [loadState, setLoadState] = useState({ status: "loading" });
|
|
20
|
+
const [retryAttempt, setRetryAttempt] = useState(0);
|
|
21
|
+
useImperativeHandle(ref, () => {
|
|
22
|
+
if (!rootRef.current) throw new Error("EmojiPicker root is not mounted.");
|
|
23
|
+
return rootRef.current;
|
|
24
|
+
});
|
|
25
|
+
const loadEmojiData = async (locale, options) => {
|
|
26
|
+
setLoadState({ status: "loading" });
|
|
27
|
+
try {
|
|
28
|
+
const data = await resolveEmojiData(locale, options);
|
|
29
|
+
if (!options.signal?.aborted) setLoadState({
|
|
30
|
+
status: "ready",
|
|
31
|
+
data
|
|
32
|
+
});
|
|
33
|
+
return data;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (!options.signal?.aborted) setLoadState({ status: "error" });
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const retryEmojiData = () => {
|
|
40
|
+
setLoadState({ status: "loading" });
|
|
41
|
+
setRetryAttempt((attempt) => attempt + 1);
|
|
42
|
+
};
|
|
43
|
+
const selectEmoji = (emoji) => onEmojiSelect?.(emoji);
|
|
44
|
+
const pickerStyle = {
|
|
45
|
+
"--_emoji-picker-columns": columns,
|
|
46
|
+
...style
|
|
47
|
+
};
|
|
48
|
+
return /* @__PURE__ */ jsx(EmojiPickerContext.Provider, {
|
|
49
|
+
value: {
|
|
50
|
+
columns,
|
|
51
|
+
rootRef,
|
|
52
|
+
viewportRef,
|
|
53
|
+
loadState,
|
|
54
|
+
retryEmojiData,
|
|
55
|
+
selectEmoji
|
|
56
|
+
},
|
|
57
|
+
children: /* @__PURE__ */ jsx(EmojiPicker$1.Root, {
|
|
58
|
+
"data-control-ui": "emoji-picker",
|
|
59
|
+
"data-control-family": "emoji-picker",
|
|
60
|
+
"data-slot": "root",
|
|
61
|
+
columns,
|
|
62
|
+
className: cn("isolate flex flex-col", className),
|
|
63
|
+
...props,
|
|
64
|
+
ref: rootRef,
|
|
65
|
+
style: pickerStyle,
|
|
66
|
+
onEmojiSelect: selectEmoji,
|
|
67
|
+
resolveEmojiData: loadEmojiData
|
|
68
|
+
}, retryAttempt)
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function stopPickerNavigation(event) {
|
|
72
|
+
if (event.key !== "Escape" && event.key !== "Tab") event.stopPropagation();
|
|
73
|
+
}
|
|
74
|
+
const categoryIcons = /* @__PURE__ */ new Map([
|
|
75
|
+
[0, SmileIcon],
|
|
76
|
+
[1, HandIcon],
|
|
77
|
+
[3, LeafIcon],
|
|
78
|
+
[4, PizzaIcon],
|
|
79
|
+
[5, PlaneIcon],
|
|
80
|
+
[6, VolleyballIcon],
|
|
81
|
+
[7, LightbulbIcon],
|
|
82
|
+
[8, HeartIcon],
|
|
83
|
+
[9, FlagIcon]
|
|
84
|
+
]);
|
|
85
|
+
function emojiCategoryRows(emojiData, columns) {
|
|
86
|
+
let rowOffset = 0;
|
|
87
|
+
return emojiData.categories.flatMap((category) => {
|
|
88
|
+
const emojiCount = emojiData.emojis.filter((emoji) => emoji.category === category.index).length;
|
|
89
|
+
if (emojiCount === 0) return [];
|
|
90
|
+
const startRow = rowOffset;
|
|
91
|
+
rowOffset += Math.ceil(emojiCount / columns);
|
|
92
|
+
return [{
|
|
93
|
+
...category,
|
|
94
|
+
startRow
|
|
95
|
+
}];
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function EmojiPickerCategories({ "aria-label": ariaLabel = "Emoji categories", onKeyDown, ...props }) {
|
|
99
|
+
const { columns, loadState, rootRef, viewportRef } = useEmojiPicker();
|
|
100
|
+
if (loadState.status !== "ready") return null;
|
|
101
|
+
const categories = emojiCategoryRows(loadState.data, columns);
|
|
102
|
+
const scrollToCategory = (startRow, categoryIndex) => {
|
|
103
|
+
const root = rootRef.current;
|
|
104
|
+
const viewport = viewportRef.current;
|
|
105
|
+
if (!root || !viewport) return;
|
|
106
|
+
const metrics = getComputedStyle(root);
|
|
107
|
+
const rowHeight = Number.parseFloat(metrics.getPropertyValue("--frimousse-row-height"));
|
|
108
|
+
const headerHeight = Number.parseFloat(metrics.getPropertyValue("--frimousse-category-header-height"));
|
|
109
|
+
viewport.scrollTo({ top: startRow * rowHeight + categoryIndex * headerHeight });
|
|
110
|
+
};
|
|
111
|
+
return /* @__PURE__ */ jsx(Toolbar.Root, {
|
|
112
|
+
"data-control-ui": "emoji-picker",
|
|
113
|
+
"data-control-family": "emoji-picker",
|
|
114
|
+
"data-slot": "categories",
|
|
115
|
+
"aria-label": ariaLabel,
|
|
116
|
+
...props,
|
|
117
|
+
onKeyDown: (event) => {
|
|
118
|
+
onKeyDown?.(event);
|
|
119
|
+
stopPickerNavigation(event);
|
|
120
|
+
},
|
|
121
|
+
children: categories.map((category, categoryIndex) => {
|
|
122
|
+
const CategoryIcon = categoryIcons.get(category.index) ?? HashIcon;
|
|
123
|
+
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
124
|
+
type: "button",
|
|
125
|
+
"aria-label": category.label,
|
|
126
|
+
"data-control-ui": "emoji-picker",
|
|
127
|
+
"data-control-family": "emoji-picker",
|
|
128
|
+
"data-slot": "category",
|
|
129
|
+
onClick: () => scrollToCategory(category.startRow, categoryIndex),
|
|
130
|
+
children: /* @__PURE__ */ jsx(CategoryIcon, { "aria-hidden": "true" })
|
|
131
|
+
}, category.index);
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function EmojiPickerReactions({ emojis, onEmojiSelect, "aria-label": ariaLabel = "Reactions", onKeyDown, ...props }) {
|
|
136
|
+
return /* @__PURE__ */ jsx(Toolbar.Root, {
|
|
137
|
+
"aria-label": ariaLabel,
|
|
10
138
|
"data-control-ui": "emoji-picker",
|
|
11
139
|
"data-control-family": "emoji-picker",
|
|
12
140
|
"data-slot": "root",
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
141
|
+
"data-variant": "reactions",
|
|
142
|
+
...props,
|
|
143
|
+
onKeyDown: (event) => {
|
|
144
|
+
onKeyDown?.(event);
|
|
145
|
+
stopPickerNavigation(event);
|
|
146
|
+
},
|
|
147
|
+
children: emojis.map((emoji) => /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
148
|
+
type: "button",
|
|
149
|
+
"aria-label": emoji.label,
|
|
150
|
+
"data-control-ui": "emoji-picker",
|
|
151
|
+
"data-control-family": "emoji-picker",
|
|
152
|
+
"data-slot": "emoji",
|
|
153
|
+
onClick: () => onEmojiSelect(emoji),
|
|
154
|
+
children: emoji.emoji
|
|
155
|
+
}, emoji.emoji))
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function EmojiPickerRecent({ emojis, label = "Recently used", ...props }) {
|
|
159
|
+
const picker = useEmojiPicker();
|
|
160
|
+
if (emojis.length === 0) return null;
|
|
161
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
162
|
+
"data-control-ui": "emoji-picker",
|
|
163
|
+
"data-control-family": "emoji-picker",
|
|
164
|
+
"data-slot": "recent",
|
|
165
|
+
...props,
|
|
166
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
167
|
+
"data-control-ui": "emoji-picker",
|
|
168
|
+
"data-control-family": "emoji-picker",
|
|
169
|
+
"data-slot": "recent-label",
|
|
170
|
+
children: label
|
|
171
|
+
}), /* @__PURE__ */ jsx(EmojiPickerReactions, {
|
|
172
|
+
"data-control-ui": "emoji-picker",
|
|
173
|
+
"data-slot": "recent-grid",
|
|
174
|
+
emojis: emojis.slice(0, picker.columns),
|
|
175
|
+
"aria-label": label,
|
|
176
|
+
onEmojiSelect: picker.selectEmoji
|
|
177
|
+
})]
|
|
16
178
|
});
|
|
17
179
|
}
|
|
18
180
|
function EmojiPickerSearch({ className, placeholder = "Search emoji…", "aria-label": ariaLabel = "Search emoji", ...props }) {
|
|
@@ -49,6 +211,7 @@ function EmojiPickerRow({ children, className, ...props }) {
|
|
|
49
211
|
}
|
|
50
212
|
function EmojiPickerEmoji({ emoji, className, ...props }) {
|
|
51
213
|
return /* @__PURE__ */ jsx("button", {
|
|
214
|
+
type: "button",
|
|
52
215
|
"data-control-ui": "emoji-picker",
|
|
53
216
|
"data-control-family": "emoji-picker",
|
|
54
217
|
"data-slot": "emoji",
|
|
@@ -68,14 +231,30 @@ function EmojiPickerCategoryHeader({ category, className, ...props }) {
|
|
|
68
231
|
children: category.label
|
|
69
232
|
});
|
|
70
233
|
}
|
|
71
|
-
function EmojiPickerContent({ className, ...props }) {
|
|
72
|
-
|
|
234
|
+
function EmojiPickerContent({ className, ref, ...props }) {
|
|
235
|
+
const { viewportRef, loadState, retryEmojiData } = useEmojiPicker();
|
|
236
|
+
useImperativeHandle(ref, () => {
|
|
237
|
+
if (!viewportRef.current) throw new Error("EmojiPicker viewport is not mounted.");
|
|
238
|
+
return viewportRef.current;
|
|
239
|
+
});
|
|
240
|
+
return /* @__PURE__ */ jsx(EmojiPicker$1.Viewport, {
|
|
73
241
|
"data-control-ui": "emoji-picker",
|
|
74
242
|
"data-control-family": "emoji-picker",
|
|
75
243
|
"data-slot": "content",
|
|
76
|
-
className: cn("relative flex-1 outline-none", className),
|
|
244
|
+
className: cn("relative min-h-0 flex-1 outline-none", className),
|
|
77
245
|
...props,
|
|
78
|
-
|
|
246
|
+
ref: viewportRef,
|
|
247
|
+
children: loadState.status === "error" ? /* @__PURE__ */ jsxs("div", {
|
|
248
|
+
"data-control-ui": "emoji-picker",
|
|
249
|
+
"data-control-family": "emoji-picker",
|
|
250
|
+
"data-slot": "error",
|
|
251
|
+
role: "alert",
|
|
252
|
+
children: [/* @__PURE__ */ jsx("span", { children: "Couldn’t load emoji." }), /* @__PURE__ */ jsx("button", {
|
|
253
|
+
type: "button",
|
|
254
|
+
onClick: retryEmojiData,
|
|
255
|
+
children: "Try again"
|
|
256
|
+
})]
|
|
257
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
79
258
|
/* @__PURE__ */ jsx(EmojiPicker$1.Loading, {
|
|
80
259
|
"data-control-ui": "emoji-picker",
|
|
81
260
|
"data-control-family": "emoji-picker",
|
|
@@ -101,7 +280,7 @@ function EmojiPickerContent({ className, ...props }) {
|
|
|
101
280
|
CategoryHeader: EmojiPickerCategoryHeader
|
|
102
281
|
}
|
|
103
282
|
})
|
|
104
|
-
]
|
|
283
|
+
] })
|
|
105
284
|
});
|
|
106
285
|
}
|
|
107
286
|
function EmojiPickerFooter({ className, placeholder = "Pick an emoji…", ...props }) {
|
|
@@ -111,7 +290,7 @@ function EmojiPickerFooter({ className, placeholder = "Pick an emoji…", ...pro
|
|
|
111
290
|
"data-slot": "footer",
|
|
112
291
|
className: cn("flex min-w-0 items-center", className),
|
|
113
292
|
...props,
|
|
114
|
-
children: /* @__PURE__ */ jsx(EmojiPicker$1.ActiveEmoji, { children: ({ emoji }) => emoji ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
293
|
+
children: /* @__PURE__ */ jsx(EmojiPicker$1.ActiveEmoji, { children: ({ emoji }) => emoji ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
115
294
|
"data-control-ui": "emoji-picker",
|
|
116
295
|
"data-control-family": "emoji-picker",
|
|
117
296
|
"data-slot": "footer-emoji",
|
|
@@ -133,4 +312,4 @@ function EmojiPickerFooter({ className, placeholder = "Pick an emoji…", ...pro
|
|
|
133
312
|
});
|
|
134
313
|
}
|
|
135
314
|
//#endregion
|
|
136
|
-
export { EmojiPicker, EmojiPickerContent, EmojiPickerFooter, EmojiPickerSearch };
|
|
315
|
+
export { EmojiPicker, EmojiPickerCategories, EmojiPickerContent, EmojiPickerFooter, EmojiPickerReactions, EmojiPickerRecent, EmojiPickerSearch };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ControlledChoice } from "../control-props.js";
|
|
2
|
+
import { IconPickerKnobStyle } from "../knob-contracts/icon-picker-knobs.js";
|
|
3
|
+
import { Input } from "./input.js";
|
|
4
|
+
import { CSSProperties, ComponentProps, ReactNode } from "react";
|
|
5
|
+
//#region src/ui/icon-picker.d.ts
|
|
6
|
+
export type IconPickerItem = {
|
|
7
|
+
value: string;
|
|
8
|
+
label: string;
|
|
9
|
+
icon: ReactNode;
|
|
10
|
+
keywords?: readonly string[];
|
|
11
|
+
};
|
|
12
|
+
export type IconPickerColor = {
|
|
13
|
+
value: string;
|
|
14
|
+
label: string;
|
|
15
|
+
color: string;
|
|
16
|
+
};
|
|
17
|
+
type IconPickerStyle = {
|
|
18
|
+
style?: CSSProperties & IconPickerKnobStyle;
|
|
19
|
+
};
|
|
20
|
+
export type IconPickerProps = Omit<ComponentProps<"div">, "style" | "defaultValue" | "onChange"> & IconPickerStyle & ControlledChoice & {
|
|
21
|
+
items: readonly IconPickerItem[];
|
|
22
|
+
columns?: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function IconPicker({ items, columns, value, defaultValue, onValueChange, className, style, children, ...props }: IconPickerProps): import("react").JSX.Element;
|
|
25
|
+
export type IconPickerSearchProps = Omit<ComponentProps<typeof Input>, "value" | "defaultValue" | "onValueChange">;
|
|
26
|
+
export declare function IconPickerSearch({ placeholder, "aria-label": ariaLabel, className, onChange, ...props }: IconPickerSearchProps): import("react").JSX.Element;
|
|
27
|
+
export type IconPickerContentProps = Omit<ComponentProps<"div">, "style" | "children"> & IconPickerStyle & {
|
|
28
|
+
empty?: ReactNode;
|
|
29
|
+
};
|
|
30
|
+
export declare function IconPickerContent({ className, empty, "aria-label": ariaLabel, ...props }: IconPickerContentProps): import("react").JSX.Element;
|
|
31
|
+
export type IconPickerColorsProps = Omit<ComponentProps<"div">, "style" | "defaultValue" | "onChange" | "children"> & IconPickerStyle & ControlledChoice & {
|
|
32
|
+
colors: readonly IconPickerColor[];
|
|
33
|
+
};
|
|
34
|
+
export declare function IconPickerColors({ colors, "aria-label": ariaLabel, ...props }: IconPickerColorsProps): import("react").JSX.Element;
|
|
35
|
+
//#endregion
|