@ctrl-ui/react 0.8.10 → 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/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 +2 -1
- package/dist/styles/recipes/accordion.css +13 -1
- 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/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/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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { Input } from "./input.js";
|
|
4
|
+
import { createContext, useContext, useRef, useState } from "react";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import { Toolbar } from "@base-ui/react/toolbar";
|
|
7
|
+
import { Radio } from "@base-ui/react/radio";
|
|
8
|
+
import { RadioGroup } from "@base-ui/react/radio-group";
|
|
9
|
+
//#region src/ui/icon-picker.tsx
|
|
10
|
+
const IconPickerContext = createContext(null);
|
|
11
|
+
function useIconPicker() {
|
|
12
|
+
const picker = useContext(IconPickerContext);
|
|
13
|
+
if (!picker) throw new Error("IconPicker parts must be rendered inside <IconPicker>.");
|
|
14
|
+
return picker;
|
|
15
|
+
}
|
|
16
|
+
function IconPicker({ items, columns = 8, value, defaultValue, onValueChange, className, style, children, ...props }) {
|
|
17
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
|
18
|
+
const [search, setSearch] = useState("");
|
|
19
|
+
const selectIcon = (selectedValue) => {
|
|
20
|
+
if (value === void 0) setUncontrolledValue(selectedValue);
|
|
21
|
+
onValueChange?.(selectedValue);
|
|
22
|
+
};
|
|
23
|
+
const pickerStyle = {
|
|
24
|
+
"--_icon-picker-columns": columns,
|
|
25
|
+
...style
|
|
26
|
+
};
|
|
27
|
+
return /* @__PURE__ */ jsx(IconPickerContext.Provider, {
|
|
28
|
+
value: {
|
|
29
|
+
items,
|
|
30
|
+
columns,
|
|
31
|
+
selectedValue: value ?? uncontrolledValue,
|
|
32
|
+
selectIcon,
|
|
33
|
+
search,
|
|
34
|
+
setSearch
|
|
35
|
+
},
|
|
36
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
37
|
+
"data-control-ui": "icon-picker",
|
|
38
|
+
"data-control-family": "icon-picker",
|
|
39
|
+
"data-slot": "root",
|
|
40
|
+
className,
|
|
41
|
+
style: pickerStyle,
|
|
42
|
+
...props,
|
|
43
|
+
children
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function IconPickerSearch({ placeholder = "Search icons…", "aria-label": ariaLabel = "Search icons", className, onChange, ...props }) {
|
|
48
|
+
const picker = useIconPicker();
|
|
49
|
+
return /* @__PURE__ */ jsx(Input, {
|
|
50
|
+
type: "search",
|
|
51
|
+
size: "sm",
|
|
52
|
+
placeholder,
|
|
53
|
+
"aria-label": ariaLabel,
|
|
54
|
+
autoComplete: "off",
|
|
55
|
+
spellCheck: false,
|
|
56
|
+
className: cn("mx-2 mt-2 w-[calc(100%-var(--spacing)*4)]", className),
|
|
57
|
+
...props,
|
|
58
|
+
value: picker.search,
|
|
59
|
+
onChange: (event) => {
|
|
60
|
+
onChange?.(event);
|
|
61
|
+
if (!event.defaultPrevented) picker.setSearch(event.target.value);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function IconPickerContent({ className, empty = "No icons found.", "aria-label": ariaLabel = "Icons", ...props }) {
|
|
66
|
+
const picker = useIconPicker();
|
|
67
|
+
const buttonsRef = useRef(/* @__PURE__ */ new Map());
|
|
68
|
+
const normalizedSearch = picker.search.trim().toLocaleLowerCase();
|
|
69
|
+
const filteredItems = picker.items.filter((item) => [item.label, ...item.keywords ?? []].some((text) => text.toLocaleLowerCase().includes(normalizedSearch)));
|
|
70
|
+
const moveGridFocus = (event) => {
|
|
71
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
72
|
+
const currentIndex = filteredItems.findIndex((item) => buttonsRef.current.get(item.value) === event.target);
|
|
73
|
+
if (currentIndex < 0) return;
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
const direction = event.key === "ArrowDown" ? 1 : -1;
|
|
76
|
+
const nextIndex = Math.max(0, Math.min(filteredItems.length - 1, currentIndex + direction * picker.columns));
|
|
77
|
+
buttonsRef.current.get(filteredItems[nextIndex].value)?.focus();
|
|
78
|
+
};
|
|
79
|
+
return /* @__PURE__ */ jsx("div", {
|
|
80
|
+
"data-control-ui": "icon-picker",
|
|
81
|
+
"data-control-family": "icon-picker",
|
|
82
|
+
"data-slot": "content",
|
|
83
|
+
className,
|
|
84
|
+
...props,
|
|
85
|
+
children: filteredItems.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
86
|
+
"data-control-ui": "icon-picker",
|
|
87
|
+
"data-control-family": "icon-picker",
|
|
88
|
+
"data-slot": "empty",
|
|
89
|
+
role: "status",
|
|
90
|
+
children: empty
|
|
91
|
+
}) : /* @__PURE__ */ jsx(Toolbar.Root, {
|
|
92
|
+
"data-control-ui": "icon-picker",
|
|
93
|
+
"data-control-family": "icon-picker",
|
|
94
|
+
"data-slot": "grid",
|
|
95
|
+
"aria-label": ariaLabel,
|
|
96
|
+
loopFocus: false,
|
|
97
|
+
onKeyDownCapture: moveGridFocus,
|
|
98
|
+
children: filteredItems.map((item) => /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
99
|
+
type: "button",
|
|
100
|
+
"aria-label": item.label,
|
|
101
|
+
"aria-pressed": picker.selectedValue === item.value,
|
|
102
|
+
"data-control-ui": "icon-picker",
|
|
103
|
+
"data-control-family": "icon-picker",
|
|
104
|
+
"data-slot": "icon",
|
|
105
|
+
ref: (button) => {
|
|
106
|
+
if (!button) return;
|
|
107
|
+
buttonsRef.current.set(item.value, button);
|
|
108
|
+
return () => {
|
|
109
|
+
buttonsRef.current.delete(item.value);
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
onClick: () => picker.selectIcon(item.value),
|
|
113
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
114
|
+
"aria-hidden": "true",
|
|
115
|
+
children: item.icon
|
|
116
|
+
})
|
|
117
|
+
}, item.value))
|
|
118
|
+
})
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function IconPickerColors({ colors, "aria-label": ariaLabel = "Icon color", ...props }) {
|
|
122
|
+
return /* @__PURE__ */ jsx(RadioGroup, {
|
|
123
|
+
"data-control-ui": "icon-picker",
|
|
124
|
+
"data-control-family": "icon-picker",
|
|
125
|
+
"data-slot": "colors",
|
|
126
|
+
"aria-label": ariaLabel,
|
|
127
|
+
...props,
|
|
128
|
+
children: colors.map((color) => /* @__PURE__ */ jsx(Radio.Root, {
|
|
129
|
+
value: color.value,
|
|
130
|
+
"aria-label": color.label,
|
|
131
|
+
"data-control-ui": "icon-picker",
|
|
132
|
+
"data-control-family": "icon-picker",
|
|
133
|
+
"data-slot": "color",
|
|
134
|
+
style: { color: color.color },
|
|
135
|
+
children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true" })
|
|
136
|
+
}, color.value))
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
export { IconPicker, IconPickerColors, IconPickerContent, IconPickerSearch };
|
|
@@ -35,17 +35,13 @@ function MarkdownCode({ className, children, node: _node, ...props }) {
|
|
|
35
35
|
if (lang === "diff" && value.includes("@@")) return /* @__PURE__ */ jsx(CodeDiff, {
|
|
36
36
|
patch: value,
|
|
37
37
|
lang: "diff",
|
|
38
|
-
diffStyle: "unified"
|
|
39
|
-
className: "my-4"
|
|
40
|
-
});
|
|
41
|
-
return /* @__PURE__ */ jsxs(Code, {
|
|
42
|
-
className: "my-4",
|
|
43
|
-
children: [/* @__PURE__ */ jsxs(CodeHeader, { children: [lang ? /* @__PURE__ */ jsx(CodeTitle, { children: lang }) : null, /* @__PURE__ */ jsx(CodeActions, { children: /* @__PURE__ */ jsx(CodeCopy, { value }) })] }), /* @__PURE__ */ jsx(CodeContent, {
|
|
44
|
-
code: value,
|
|
45
|
-
lang,
|
|
46
|
-
highlight: lang ? "auto" : "none"
|
|
47
|
-
})]
|
|
38
|
+
diffStyle: "unified"
|
|
48
39
|
});
|
|
40
|
+
return /* @__PURE__ */ jsxs(Code, { children: [/* @__PURE__ */ jsxs(CodeHeader, { children: [lang ? /* @__PURE__ */ jsx(CodeTitle, { children: lang }) : null, /* @__PURE__ */ jsx(CodeActions, { children: /* @__PURE__ */ jsx(CodeCopy, { value }) })] }), /* @__PURE__ */ jsx(CodeContent, {
|
|
41
|
+
code: value,
|
|
42
|
+
lang,
|
|
43
|
+
highlight: lang ? "auto" : "none"
|
|
44
|
+
})] });
|
|
49
45
|
}
|
|
50
46
|
function MarkdownH1({ className, node: _node, ...props }) {
|
|
51
47
|
return /* @__PURE__ */ jsx("h1", {
|
package/dist/ui/markdown.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { MarkdownKnobStyle } from "../knob-contracts/markdown-knobs.js";
|
|
2
2
|
import { CSSProperties, ComponentProps } from "react";
|
|
3
3
|
//#region src/ui/markdown.d.ts
|
|
4
|
-
export type
|
|
5
|
-
content: string;
|
|
4
|
+
export type MarkdownRootProps = Omit<ComponentProps<"div">, "style"> & {
|
|
6
5
|
style?: CSSProperties & MarkdownKnobStyle;
|
|
7
6
|
};
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
}
|
|
7
|
+
export type MarkdownProps = Omit<MarkdownRootProps, "children"> & {
|
|
8
|
+
content: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function MarkdownRoot({ className, ...props }: MarkdownRootProps): import("react").JSX.Element;
|
|
11
|
+
export declare function MarkdownFlow(props: MarkdownRootProps): import("react").JSX.Element;
|
|
11
12
|
export declare function Markdown({ content, className, ...props }: MarkdownProps): import("react").JSX.Element;
|
|
12
13
|
export type MarkdownComponentProps = ComponentProps<typeof Markdown>;
|
|
13
14
|
//#endregion
|
package/dist/ui/markdown.js
CHANGED
|
@@ -13,19 +13,27 @@ function MarkdownRoot({ className, ...props }) {
|
|
|
13
13
|
...props
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
|
+
function MarkdownFlow(props) {
|
|
17
|
+
return /* @__PURE__ */ jsx("div", {
|
|
18
|
+
"data-control-ui": "markdown",
|
|
19
|
+
"data-control-family": "markdown",
|
|
20
|
+
"data-slot": "flow",
|
|
21
|
+
...props
|
|
22
|
+
});
|
|
23
|
+
}
|
|
16
24
|
function Markdown({ content, className, ...props }) {
|
|
17
25
|
return /* @__PURE__ */ jsx(MarkdownRoot, {
|
|
18
26
|
className,
|
|
19
27
|
...props,
|
|
20
|
-
children: /* @__PURE__ */ jsx(Streamdown, {
|
|
28
|
+
children: /* @__PURE__ */ jsx(MarkdownFlow, { children: /* @__PURE__ */ jsx(Streamdown, {
|
|
21
29
|
mode: "streaming",
|
|
22
30
|
parseIncompleteMarkdown: true,
|
|
23
31
|
controls: false,
|
|
24
32
|
components: markdownComponents,
|
|
25
|
-
className: "
|
|
33
|
+
className: "cui-markdown-streamdown [&>[data-control-family]]:[margin-block:revert-layer]",
|
|
26
34
|
children: content
|
|
27
|
-
})
|
|
35
|
+
}) })
|
|
28
36
|
});
|
|
29
37
|
}
|
|
30
38
|
//#endregion
|
|
31
|
-
export { Markdown, MarkdownRoot };
|
|
39
|
+
export { Markdown, MarkdownFlow, MarkdownRoot };
|
package/dist/ui/popover.d.ts
CHANGED
|
@@ -18,6 +18,9 @@ export declare function PopoverTrigger({ className, ...props }: ComponentProps<t
|
|
|
18
18
|
}): import("react").JSX.Element;
|
|
19
19
|
export declare function PopoverAnchor(props: ComponentProps<typeof Popover$1.Trigger>): import("react").JSX.Element;
|
|
20
20
|
export declare function PopoverContent({ className, children, side, align, sideOffset, collisionPadding, padding, ...props }: PopoverPopupProps): import("react").JSX.Element;
|
|
21
|
+
export declare function PopoverViewport({ children, ref, ...props }: ComponentProps<"div"> & {
|
|
22
|
+
style?: CSSProperties & PopupKnobStyle;
|
|
23
|
+
}): import("react").JSX.Element;
|
|
21
24
|
export declare function PopoverHeader({ className, ...props }: ComponentProps<"div"> & {
|
|
22
25
|
style?: CSSProperties & PopupKnobStyle;
|
|
23
26
|
}): import("react").JSX.Element;
|
package/dist/ui/popover.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { controlEffectsAttribute } from "../skin.js";
|
|
3
3
|
import { cn } from "../lib/cn.js";
|
|
4
4
|
import { useSkin } from "../skin-provider.js";
|
|
5
|
+
import { useImperativeHandle, useLayoutEffect, useRef } from "react";
|
|
5
6
|
import { jsx } from "react/jsx-runtime";
|
|
6
7
|
import { Popover as Popover$1 } from "@base-ui/react/popover";
|
|
7
8
|
//#region src/ui/popover.tsx
|
|
@@ -45,6 +46,40 @@ function PopoverContent({ className, children, side = "bottom", align = "center"
|
|
|
45
46
|
})
|
|
46
47
|
}) });
|
|
47
48
|
}
|
|
49
|
+
function PopoverViewport({ children, ref, ...props }) {
|
|
50
|
+
const viewportRef = useRef(null);
|
|
51
|
+
const contentRef = useRef(null);
|
|
52
|
+
useImperativeHandle(ref, () => {
|
|
53
|
+
if (!viewportRef.current) throw new Error("PopoverViewport is not mounted.");
|
|
54
|
+
return viewportRef.current;
|
|
55
|
+
}, []);
|
|
56
|
+
useLayoutEffect(() => {
|
|
57
|
+
const viewport = viewportRef.current;
|
|
58
|
+
const content = contentRef.current;
|
|
59
|
+
if (!viewport || !content) return;
|
|
60
|
+
const updateHeight = () => viewport.style.setProperty("--_popover-viewport-height", `${content.offsetHeight}px`);
|
|
61
|
+
updateHeight();
|
|
62
|
+
const observer = new ResizeObserver(updateHeight);
|
|
63
|
+
observer.observe(content);
|
|
64
|
+
return () => observer.disconnect();
|
|
65
|
+
}, []);
|
|
66
|
+
return /* @__PURE__ */ jsx("div", {
|
|
67
|
+
"data-control-ui": "popover",
|
|
68
|
+
"data-control-family": "popup",
|
|
69
|
+
"data-popup-kind": "popover",
|
|
70
|
+
"data-slot": "viewport",
|
|
71
|
+
...props,
|
|
72
|
+
ref: viewportRef,
|
|
73
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
74
|
+
"data-control-ui": "popover",
|
|
75
|
+
"data-control-family": "popup",
|
|
76
|
+
"data-popup-kind": "popover",
|
|
77
|
+
"data-slot": "viewport-content",
|
|
78
|
+
ref: contentRef,
|
|
79
|
+
children
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
}
|
|
48
83
|
function PopoverHeader({ className, ...props }) {
|
|
49
84
|
return /* @__PURE__ */ jsx("div", {
|
|
50
85
|
"data-control-ui": "popover",
|
|
@@ -86,4 +121,4 @@ function PopoverClose({ className, ...props }) {
|
|
|
86
121
|
});
|
|
87
122
|
}
|
|
88
123
|
//#endregion
|
|
89
|
-
export { Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, popoverContentPaddings };
|
|
124
|
+
export { Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, PopoverViewport, popoverContentPaddings };
|
package/dist/ui/scroll-area.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { ScrollArea as ScrollArea$1 } from "@base-ui/react/scroll-area";
|
|
|
5
5
|
//#region src/ui/scroll-area.d.ts
|
|
6
6
|
export declare const scrollAreaScrollbarVisibilities: readonly ["scroll", "hover", "always"];
|
|
7
7
|
export type ScrollAreaScrollbarVisibility = (typeof scrollAreaScrollbarVisibilities)[number];
|
|
8
|
+
export declare const scrollAreaScrollbarGutters: readonly ["auto", "stable"];
|
|
9
|
+
export type ScrollAreaScrollbarGutter = (typeof scrollAreaScrollbarGutters)[number];
|
|
8
10
|
export type ScrollAreaLockAxis = "x" | "y" | "both";
|
|
9
11
|
export type ScrollAreaViewportProps = Omit<ComponentProps<"div">, "children" | "className" | "ref"> & {
|
|
10
12
|
"data-control-ui"?: string;
|
|
@@ -25,6 +27,7 @@ export type ScrollAreaProps = Omit<ComponentProps<"div">, "style"> & {
|
|
|
25
27
|
blurProps?: Pick<ProgressiveBlurProps, "style">;
|
|
26
28
|
lockAxis?: ScrollAreaLockAxis;
|
|
27
29
|
scrollbarVisibility?: ScrollAreaScrollbarVisibility;
|
|
30
|
+
scrollbarGutter?: ScrollAreaScrollbarGutter;
|
|
28
31
|
};
|
|
29
|
-
export declare function ScrollArea({ className, viewportClassName, contentClassName, viewportProps, viewportRef, maxHeight, mask, blur, blurProps, lockAxis, scrollbarVisibility, children, style, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }: ScrollAreaProps): import("react").JSX.Element;
|
|
32
|
+
export declare function ScrollArea({ className, viewportClassName, contentClassName, viewportProps, viewportRef, maxHeight, mask, blur, blurProps, lockAxis, scrollbarVisibility, scrollbarGutter, children, style, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }: ScrollAreaProps): import("react").JSX.Element;
|
|
30
33
|
//#endregion
|
package/dist/ui/scroll-area.js
CHANGED
|
@@ -10,14 +10,20 @@ const scrollAreaScrollbarVisibilities = [
|
|
|
10
10
|
"hover",
|
|
11
11
|
"always"
|
|
12
12
|
];
|
|
13
|
-
|
|
13
|
+
const scrollAreaScrollbarGutters = ["auto", "stable"];
|
|
14
|
+
function Scrollbar({ orientation, visibility, gutter, thumbStyle }) {
|
|
14
15
|
return /* @__PURE__ */ jsx(ScrollArea$1.Scrollbar, {
|
|
15
16
|
orientation,
|
|
17
|
+
keepMounted: gutter === "stable",
|
|
16
18
|
"data-control-ui": "scroll-area",
|
|
17
19
|
"data-control-family": "scroll-area",
|
|
18
20
|
"data-slot": "scrollbar",
|
|
19
21
|
"data-visibility": visibility,
|
|
20
22
|
className: cn("m-px flex touch-none select-none", orientation === "vertical" ? "justify-center" : "flex-col"),
|
|
23
|
+
style: gutter === "stable" ? {
|
|
24
|
+
position: "relative",
|
|
25
|
+
inset: "auto"
|
|
26
|
+
} : void 0,
|
|
21
27
|
children: /* @__PURE__ */ jsx(ScrollArea$1.Thumb, {
|
|
22
28
|
"data-control-ui": "scroll-area",
|
|
23
29
|
"data-control-family": "scroll-area",
|
|
@@ -68,9 +74,11 @@ function EdgeBlurs({ blurProps, lockX, lockY }) {
|
|
|
68
74
|
})
|
|
69
75
|
] });
|
|
70
76
|
}
|
|
71
|
-
function ScrollArea({ className, viewportClassName, contentClassName, viewportProps, viewportRef, maxHeight, mask = true, blur, blurProps, lockAxis, scrollbarVisibility
|
|
77
|
+
function ScrollArea({ className, viewportClassName, contentClassName, viewportProps, viewportRef, maxHeight, mask = true, blur, blurProps, lockAxis, scrollbarVisibility, scrollbarGutter, children, style, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }) {
|
|
72
78
|
const skin = useSkin();
|
|
73
79
|
const resolvedBlur = blur ?? skin.scrollAreaBlur ?? false;
|
|
80
|
+
const resolvedVisibility = scrollbarVisibility ?? skin.scrollAreaScrollbarVisibility ?? "hover";
|
|
81
|
+
const resolvedGutter = scrollbarGutter ?? skin.scrollAreaScrollbarGutter ?? "auto";
|
|
74
82
|
const lockX = lockAxis === "x" || lockAxis === "both";
|
|
75
83
|
const lockY = lockAxis === "y" || lockAxis === "both";
|
|
76
84
|
const { style: viewportPropsStyle, ...resolvedViewportProps } = viewportProps ?? {};
|
|
@@ -85,6 +93,7 @@ function ScrollArea({ className, viewportClassName, contentClassName, viewportPr
|
|
|
85
93
|
"data-mask": mask || void 0,
|
|
86
94
|
"data-blur": resolvedBlur || void 0,
|
|
87
95
|
"data-lock-axis": lockAxis,
|
|
96
|
+
"data-scrollbar-gutter": resolvedGutter,
|
|
88
97
|
className: cn("relative overflow-hidden", className),
|
|
89
98
|
style,
|
|
90
99
|
children: [
|
|
@@ -114,22 +123,28 @@ function ScrollArea({ className, viewportClassName, contentClassName, viewportPr
|
|
|
114
123
|
}),
|
|
115
124
|
!lockY && /* @__PURE__ */ jsx(Scrollbar, {
|
|
116
125
|
orientation: "vertical",
|
|
117
|
-
visibility:
|
|
126
|
+
visibility: resolvedVisibility,
|
|
127
|
+
gutter: resolvedGutter,
|
|
118
128
|
thumbStyle
|
|
119
129
|
}),
|
|
120
130
|
!lockX && /* @__PURE__ */ jsx(Scrollbar, {
|
|
121
131
|
orientation: "horizontal",
|
|
122
|
-
visibility:
|
|
132
|
+
visibility: resolvedVisibility,
|
|
133
|
+
gutter: resolvedGutter,
|
|
123
134
|
thumbStyle
|
|
124
135
|
}),
|
|
125
136
|
!lockX && !lockY && /* @__PURE__ */ jsx(ScrollArea$1.Corner, {
|
|
126
137
|
"data-control-ui": "scroll-area",
|
|
127
138
|
"data-control-family": "scroll-area",
|
|
128
139
|
"data-slot": "corner",
|
|
129
|
-
style:
|
|
140
|
+
style: resolvedGutter === "stable" ? {
|
|
141
|
+
...cornerStyle,
|
|
142
|
+
position: "relative",
|
|
143
|
+
inset: "auto"
|
|
144
|
+
} : cornerStyle
|
|
130
145
|
})
|
|
131
146
|
]
|
|
132
147
|
});
|
|
133
148
|
}
|
|
134
149
|
//#endregion
|
|
135
|
-
export { ScrollArea, scrollAreaScrollbarVisibilities };
|
|
150
|
+
export { ScrollArea, scrollAreaScrollbarGutters, scrollAreaScrollbarVisibilities };
|
package/dist/ui/tabs.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
|
-
import { Children, Fragment, createContext, isValidElement, useContext, useRef, useState } from "react";
|
|
3
|
+
import { Children, Fragment, createContext, isValidElement, useContext, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { Tabs as Tabs$1 } from "@base-ui/react/tabs";
|
|
6
6
|
//#region src/ui/tabs.tsx
|
|
7
|
+
function refreshTabsPanelHeight(panel) {
|
|
8
|
+
if (panel.node.inert || panel.node.hidden) return;
|
|
9
|
+
panel.height = panel.node.getBoundingClientRect().height;
|
|
10
|
+
}
|
|
7
11
|
const TabsPanelsContext = createContext(null);
|
|
8
|
-
function
|
|
9
|
-
const
|
|
10
|
-
if (!
|
|
11
|
-
return
|
|
12
|
+
function useTabsPanels() {
|
|
13
|
+
const context = useContext(TabsPanelsContext);
|
|
14
|
+
if (!context) throw new Error("TabsPanel must be rendered inside <Tabs>.");
|
|
15
|
+
return context;
|
|
12
16
|
}
|
|
13
17
|
function setRef(ref, value) {
|
|
14
18
|
if (typeof ref === "function") return ref(value);
|
|
@@ -16,30 +20,61 @@ function setRef(ref, value) {
|
|
|
16
20
|
}
|
|
17
21
|
function Tabs({ className, onValueChange, children, ...props }) {
|
|
18
22
|
const rootRef = useRef(null);
|
|
19
|
-
const
|
|
20
|
-
const
|
|
23
|
+
const activePanelRef = useRef(null);
|
|
24
|
+
const clearPreviousHeightFrame = useRef(0);
|
|
25
|
+
const [exitingValue, setExitingValue] = useState(null);
|
|
26
|
+
const [holdPreviousHeight] = useState(() => (height) => {
|
|
27
|
+
const root = rootRef.current;
|
|
28
|
+
if (!root) return;
|
|
29
|
+
root.style.setProperty("--aui-slide-prev-height", height);
|
|
30
|
+
cancelAnimationFrame(clearPreviousHeightFrame.current);
|
|
31
|
+
clearPreviousHeightFrame.current = requestAnimationFrame(() => {
|
|
32
|
+
clearPreviousHeightFrame.current = requestAnimationFrame(() => {
|
|
33
|
+
root.style.removeProperty("--aui-slide-prev-height");
|
|
34
|
+
root.removeAttribute("data-slide-starting");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
useEffect(() => () => cancelAnimationFrame(clearPreviousHeightFrame.current), []);
|
|
21
39
|
const [registerPanel] = useState(() => (value, node) => {
|
|
22
|
-
if (!node) return;
|
|
23
|
-
|
|
40
|
+
if (!(node && !node.inert && !node.hidden)) return;
|
|
41
|
+
const previousPanel = activePanelRef.current;
|
|
42
|
+
const root = rootRef.current;
|
|
43
|
+
if (root && previousPanel && previousPanel.value !== value) {
|
|
44
|
+
root.setAttribute("data-slide-starting", "");
|
|
45
|
+
holdPreviousHeight(root.style.getPropertyValue("--aui-slide-prev-height") || `${previousPanel.height}px`);
|
|
46
|
+
setExitingValue(previousPanel.value);
|
|
47
|
+
}
|
|
48
|
+
activePanelRef.current = {
|
|
49
|
+
value,
|
|
50
|
+
node,
|
|
51
|
+
height: node.getBoundingClientRect().height
|
|
52
|
+
};
|
|
53
|
+
if (root) {
|
|
54
|
+
root.style.setProperty("--_tabs-panel-top", `${node.offsetTop}px`);
|
|
55
|
+
root.style.setProperty("--_tabs-panel-left", `${node.offsetLeft}px`);
|
|
56
|
+
root.style.setProperty("--_tabs-panel-width", `${node.offsetWidth}px`);
|
|
57
|
+
root.style.setProperty("--_tabs-panel-height", `${node.offsetHeight}px`);
|
|
58
|
+
}
|
|
24
59
|
return () => {
|
|
25
|
-
|
|
60
|
+
const currentPanel = activePanelRef.current;
|
|
61
|
+
if (currentPanel && currentPanel.node === node) refreshTabsPanelHeight(currentPanel);
|
|
26
62
|
};
|
|
27
63
|
});
|
|
28
64
|
const handleValueChange = (value) => {
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
clearPrevHeight.current = requestAnimationFrame(() => {
|
|
35
|
-
clearPrevHeight.current = requestAnimationFrame(() => root.style.removeProperty("--aui-slide-prev-height"));
|
|
36
|
-
});
|
|
37
|
-
break;
|
|
65
|
+
const activePanel = activePanelRef.current;
|
|
66
|
+
if (activePanel) {
|
|
67
|
+
refreshTabsPanelHeight(activePanel);
|
|
68
|
+
holdPreviousHeight(`${activePanel.height}px`);
|
|
69
|
+
setExitingValue(activePanel.value);
|
|
38
70
|
}
|
|
39
71
|
onValueChange?.(value);
|
|
40
72
|
};
|
|
41
73
|
return /* @__PURE__ */ jsx(TabsPanelsContext.Provider, {
|
|
42
|
-
value:
|
|
74
|
+
value: {
|
|
75
|
+
registerPanel,
|
|
76
|
+
exitingValue
|
|
77
|
+
},
|
|
43
78
|
children: /* @__PURE__ */ jsx(Tabs$1.Root, {
|
|
44
79
|
"data-control-ui": "tabs",
|
|
45
80
|
"data-control-family": "tabs",
|
|
@@ -101,7 +136,7 @@ function TabsTab({ className, ...props }) {
|
|
|
101
136
|
});
|
|
102
137
|
}
|
|
103
138
|
function TabsPanel({ className, value, ref, ...props }) {
|
|
104
|
-
const registerPanel =
|
|
139
|
+
const { registerPanel, exitingValue } = useTabsPanels();
|
|
105
140
|
const panelRef = (node) => {
|
|
106
141
|
const unregister = registerPanel(value, node);
|
|
107
142
|
const cleanup = setRef(ref, node);
|
|
@@ -119,7 +154,8 @@ function TabsPanel({ className, value, ref, ...props }) {
|
|
|
119
154
|
"data-control-family": "tabs",
|
|
120
155
|
"data-slot": "panel",
|
|
121
156
|
"data-slide": "panel",
|
|
122
|
-
|
|
157
|
+
"data-slide-exiting": exitingValue === value ? "" : void 0,
|
|
158
|
+
className: cn("data-[hidden]:hidden [&[hidden]]:hidden [&[inert]:not([data-slide=panel][data-slide-exiting][data-ending-style])]:hidden [&[inert][data-ending-style]]:m-0", "not-supports-[anchor-name:--aui-slide-panel]:[&[inert]]:hidden not-supports-[anchor-scope:--aui-slide-panel]:[&[inert]]:hidden", className),
|
|
123
159
|
...props
|
|
124
160
|
});
|
|
125
161
|
}
|