@vvedantb/ui 0.17.0 → 0.18.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/README.md +253 -3
- package/package.json +13 -5
- package/shell/src/SidebarResize.tsx +1 -4
- package/src/ai-elements/__snapshots__/queue.test.tsx.snap +11 -0
- package/src/ai-elements/activity-question-utils.ts +26 -0
- package/src/ai-elements/activity-question.tsx +159 -0
- package/src/ai-elements/activity-shared.tsx +459 -0
- package/src/ai-elements/activity-step-detail.tsx +115 -0
- package/src/ai-elements/activity-step-label.ts +981 -0
- package/src/ai-elements/activity-tasks-utils.ts +199 -0
- package/src/ai-elements/activity-tasks.tsx +770 -0
- package/src/ai-elements/artifact.tsx +162 -52
- package/src/ai-elements/attachments.tsx +62 -32
- package/src/ai-elements/code-block.tsx +76 -22
- package/src/ai-elements/context.tsx +370 -0
- package/src/ai-elements/conversation.tsx +186 -62
- package/src/ai-elements/file-chip.tsx +53 -0
- package/src/ai-elements/message.tsx +161 -50
- package/src/ai-elements/model-picker-content.tsx +505 -0
- package/src/ai-elements/model-picker-types.ts +56 -0
- package/src/ai-elements/model-picker.tsx +301 -0
- package/src/ai-elements/model-selector.tsx +102 -21
- package/src/ai-elements/plan.tsx +130 -0
- package/src/ai-elements/prompt-input-settings.tsx +144 -0
- package/src/ai-elements/prompt-input-speech.tsx +189 -0
- package/src/ai-elements/prompt-input-traits.tsx +281 -0
- package/src/ai-elements/prompt-input.tsx +320 -134
- package/src/ai-elements/provider-icon.tsx +133 -0
- package/src/ai-elements/queue.tsx +293 -9
- package/src/ai-elements/shimmer.tsx +72 -1
- package/src/ai-elements/simple-model-ladder.tsx +108 -0
- package/src/ai-elements/test-results.tsx +510 -0
- package/src/ai-elements/trace.tsx +243 -0
- package/src/ai-elements/use-streaming-disclosure.ts +50 -0
- package/src/ai-elements/web-preview.tsx +349 -0
- package/src/composer/input-area/ComposerSlashMenu.tsx +1 -1
- package/src/index.ts +66 -12
- package/src/kibo/avatar-stack.tsx +40 -0
- package/src/kibo/contribution-graph.tsx +539 -0
- package/src/kibo/cursor.tsx +72 -0
- package/src/kibo/gantt-features.tsx +439 -0
- package/src/kibo/gantt-header.tsx +181 -0
- package/src/kibo/gantt-provider.tsx +657 -0
- package/src/kibo/gantt-sidebar.tsx +157 -0
- package/src/kibo/gantt-timeline.tsx +154 -0
- package/src/kibo/index.ts +151 -0
- package/src/kibo/kanban.tsx +338 -0
- package/src/kibo/kanbanDropTarget.ts +25 -0
- package/src/kibo/list.tsx +162 -0
- package/src/kibo/theme-switcher.tsx +109 -0
- package/src/kibo/video-player.tsx +237 -0
- package/src/markdown/Markdown.tsx +112 -0
- package/src/markdown/MarkdownBlockquote.tsx +45 -0
- package/src/markdown/MarkdownCodeBlock.tsx +197 -0
- package/src/markdown/MarkdownInlineCode.tsx +40 -0
- package/src/markdown/MarkdownLink.tsx +92 -0
- package/src/markdown/codeFence.ts +57 -0
- package/src/markdown/codeHighlight.ts +138 -0
- package/src/markdown/incrementalBlocks.ts +182 -0
- package/src/markdown/index.ts +3 -0
- package/src/markdown/markdownClipboard.ts +178 -0
- package/src/markdown/mermaid.ts +155 -0
- package/src/markdown/rehypeGithubAlerts.ts +80 -0
- package/src/motion/presets.ts +24 -0
- package/src/ui/__snapshots__/alt-wrappers.test.tsx.snap +19 -0
- package/src/ui/__snapshots__/sonner-dom.test.tsx.snap +5 -0
- package/src/ui/_menu-parts.tsx +29 -0
- package/src/ui/accordion.alt.tsx +46 -53
- package/src/ui/accordion.tsx +59 -10
- package/src/ui/avatar.tsx +56 -0
- package/src/ui/badge.tsx +45 -21
- package/src/ui/border-beam.tsx +97 -0
- package/src/ui/button-group.tsx +50 -21
- package/src/ui/button.tsx +67 -45
- package/src/ui/calendar.tsx +48 -5
- package/src/ui/card.tsx +77 -8
- package/src/ui/carousel.tsx +60 -0
- package/src/ui/centered-spinner.tsx +30 -0
- package/src/ui/checkbox.tsx +34 -16
- package/src/ui/clear-input.alt.tsx +33 -75
- package/src/ui/clear-input.tsx +140 -15
- package/src/ui/collapsible.tsx +40 -2
- package/src/ui/command.tsx +143 -70
- package/src/ui/context-menu.tsx +159 -93
- package/src/ui/crossfade-icon.tsx +123 -0
- package/src/ui/dialog.tsx +154 -70
- package/src/ui/dropdown-menu.tsx +177 -117
- package/src/ui/hover-card.tsx +59 -17
- package/src/ui/input-group.alt.tsx +83 -83
- package/src/ui/input-group.tsx +129 -58
- package/src/ui/input.tsx +13 -4
- package/src/ui/label.tsx +11 -8
- package/src/ui/list-row.tsx +232 -0
- package/src/ui/loading-state.tsx +215 -0
- package/src/ui/popover.tsx +56 -23
- package/src/ui/progress.tsx +28 -16
- package/src/ui/refresh-spin-icon.tsx +41 -0
- package/src/ui/search-input.tsx +66 -15
- package/src/ui/select.tsx +171 -100
- package/src/ui/separator.tsx +23 -14
- package/src/ui/slider.tsx +21 -7
- package/src/ui/sonner.tsx +68 -30
- package/src/ui/spinner.tsx +229 -11
- package/src/ui/status-dot.tsx +84 -0
- package/src/ui/surface.tsx +49 -0
- package/src/ui/switch.tsx +29 -15
- package/src/ui/table.tsx +38 -16
- package/src/ui/tabs.tsx +123 -35
- package/src/ui/tabsSliding.ts +33 -1
- package/src/ui/textarea.tsx +13 -4
- package/src/ui/tooltip.tsx +50 -14
- package/src/ui/ui-config.tsx +149 -0
- package/src/utils/cat-color.ts +123 -0
- package/src/utils/dayjs.ts +6 -0
- package/src/utils/gesture.ts +59 -0
- package/src/utils/runtimeVisibility.ts +167 -0
- package/src/utils/sharedClock.ts +96 -0
- package/src/utils/surface-radius.ts +16 -0
- package/src/utils/useDragSensors.ts +58 -0
- package/tokens/examples/tokens-moved/Fixture.tsx +283 -0
- package/tokens/examples/tokens-moved/README.md +20 -0
- package/tokens/examples/tokens-moved/index.html +12 -0
- package/tokens/examples/tokens-moved/main.tsx +6 -0
- package/tokens/examples/tokens-moved/package.json +14 -0
- package/tokens/examples/tokens-moved/styles.css +12 -0
- package/tokens/examples/tokens-moved/tsconfig.json +14 -0
- package/tokens/examples/tokens-moved/vite.config.ts +11 -0
- package/tokens/src/css/border-beam.css +116 -0
- package/tokens/src/css/compat-shadcn-rgb.css +6 -0
- package/tokens/src/css/markdown.css +318 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { MermaidConfig } from "@streamdown/mermaid";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
import type { DiagramPlugin } from "streamdown";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mermaid for markdown, loaded on first use. `@streamdown/mermaid` imports
|
|
7
|
+
* mermaid statically, so a plain plugin import put mermaid in every chunk
|
|
8
|
+
* that renders markdown. Here the plugin is a dynamic import that runs only
|
|
9
|
+
* when a finished mermaid fence mounts.
|
|
10
|
+
*
|
|
11
|
+
* One render serves two readers: `MarkdownMermaid` waits for it to decide
|
|
12
|
+
* diagram or plain code block (a diagram that will not parse stays code), and
|
|
13
|
+
* Streamdown's diagram view gets the same cached SVG through `mermaidPlugin`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type MermaidTheme = Extract<MermaidConfig["theme"], "default" | "dark">;
|
|
17
|
+
/** `id` is the element id mermaid baked into `svg` (its styles and markers use it). */
|
|
18
|
+
type RenderResult = { svg: string; id: string };
|
|
19
|
+
type Entry = {
|
|
20
|
+
promise: Promise<RenderResult>;
|
|
21
|
+
status: "pending" | "done" | "error";
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Enough for every diagram on screen plus scroll-back; oldest dropped. */
|
|
25
|
+
const CACHE_LIMIT = 50;
|
|
26
|
+
|
|
27
|
+
const cache = new Map<string, Entry>();
|
|
28
|
+
const listeners = new Map<string, Set<() => void>>();
|
|
29
|
+
|
|
30
|
+
let pluginPromise: Promise<DiagramPlugin> | null = null;
|
|
31
|
+
/** `initialize` is global to mermaid, so set-theme-then-render runs one at a time. */
|
|
32
|
+
let queue: Promise<unknown> = Promise.resolve();
|
|
33
|
+
let renderCount = 0;
|
|
34
|
+
|
|
35
|
+
function loadPlugin(): Promise<DiagramPlugin> {
|
|
36
|
+
pluginPromise ??= import("@streamdown/mermaid").then(
|
|
37
|
+
(module) => module.mermaid,
|
|
38
|
+
(error) => {
|
|
39
|
+
// A failed chunk load may succeed later; do not cache the failure.
|
|
40
|
+
pluginPromise = null;
|
|
41
|
+
throw error;
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
return pluginPromise;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function renderNow(theme: MermaidTheme, code: string): Promise<RenderResult> {
|
|
48
|
+
const job = queue.then(async () => {
|
|
49
|
+
const plugin = await loadPlugin();
|
|
50
|
+
renderCount += 1;
|
|
51
|
+
// The `-svg` suffix keeps one id from being a prefix of another.
|
|
52
|
+
const id = `markdown-mermaid-${renderCount}-svg`;
|
|
53
|
+
const { svg } = await plugin.getMermaid({ theme }).render(id, code);
|
|
54
|
+
return { svg, id };
|
|
55
|
+
});
|
|
56
|
+
queue = job.catch(() => undefined);
|
|
57
|
+
return job;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function cacheKey(theme: MermaidTheme, code: string): string {
|
|
61
|
+
return `${theme}\u0000${code}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function store(key: string, entry: Entry): void {
|
|
65
|
+
cache.delete(key);
|
|
66
|
+
cache.set(key, entry);
|
|
67
|
+
// Drop the oldest entries nobody is watching; mounted diagrams stay.
|
|
68
|
+
for (const candidate of cache.keys()) {
|
|
69
|
+
if (cache.size <= CACHE_LIMIT) return;
|
|
70
|
+
if (!listeners.has(candidate)) cache.delete(candidate);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function request(theme: MermaidTheme, code: string): Entry {
|
|
75
|
+
const key = cacheKey(theme, code);
|
|
76
|
+
const hit = cache.get(key);
|
|
77
|
+
if (hit !== undefined) return hit;
|
|
78
|
+
|
|
79
|
+
const promise = renderNow(theme, code);
|
|
80
|
+
const entry: Entry = { promise, status: "pending" };
|
|
81
|
+
store(key, entry);
|
|
82
|
+
const settle = (status: Entry["status"]) => {
|
|
83
|
+
// An entry evicted (or replaced) while pending stays gone.
|
|
84
|
+
if (cache.get(key)?.promise !== promise) return;
|
|
85
|
+
store(key, { promise, status });
|
|
86
|
+
for (const notify of listeners.get(key) ?? []) notify();
|
|
87
|
+
};
|
|
88
|
+
promise.then(
|
|
89
|
+
() => settle("done"),
|
|
90
|
+
() => settle("error"),
|
|
91
|
+
);
|
|
92
|
+
return entry;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function subscribe(key: string, notify: () => void): () => void {
|
|
96
|
+
const set = listeners.get(key) ?? new Set();
|
|
97
|
+
set.add(notify);
|
|
98
|
+
listeners.set(key, set);
|
|
99
|
+
return () => {
|
|
100
|
+
set.delete(notify);
|
|
101
|
+
if (set.size === 0) listeners.delete(key);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The app theme lives as a `dark` class on `<html>` (neutral is dark-family). */
|
|
106
|
+
function readTheme(): MermaidTheme {
|
|
107
|
+
return document.documentElement.classList.contains("dark") ? "dark" : "default";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function subscribeTheme(notify: () => void): () => void {
|
|
111
|
+
const observer = new MutationObserver(notify);
|
|
112
|
+
observer.observe(document.documentElement, {
|
|
113
|
+
attributes: true,
|
|
114
|
+
attributeFilter: ["class"],
|
|
115
|
+
});
|
|
116
|
+
return () => observer.disconnect();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Streamdown's diagram plugin, backed by the shared cache. It renders in the
|
|
121
|
+
* current app theme; `MarkdownMermaid` remounts the diagram when that changes.
|
|
122
|
+
*/
|
|
123
|
+
export const mermaidPlugin: DiagramPlugin = {
|
|
124
|
+
name: "mermaid",
|
|
125
|
+
type: "diagram",
|
|
126
|
+
language: "mermaid",
|
|
127
|
+
getMermaid: () => ({
|
|
128
|
+
initialize: () => {},
|
|
129
|
+
// The cached SVG is shared, so give each copy the caller's unique id:
|
|
130
|
+
// two copies with one id (inline + fullscreen, or a repeated diagram)
|
|
131
|
+
// would share `#id` styles and `url(#id_marker)` references.
|
|
132
|
+
render: (id, code) =>
|
|
133
|
+
request(readTheme(), code).promise.then((result) => ({
|
|
134
|
+
svg: result.svg.replaceAll(result.id, id),
|
|
135
|
+
})),
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Renders `code` in the current theme once `enabled` (the fence is closed) and
|
|
141
|
+
* reports progress. Nothing loads while disabled, so a fence still streaming
|
|
142
|
+
* costs nothing and never surfaces a parse error.
|
|
143
|
+
*/
|
|
144
|
+
export function useMermaidRender(
|
|
145
|
+
code: string,
|
|
146
|
+
enabled: boolean,
|
|
147
|
+
): { theme: MermaidTheme; status: Entry["status"] } {
|
|
148
|
+
const theme = useSyncExternalStore(subscribeTheme, readTheme);
|
|
149
|
+
const key = enabled ? cacheKey(theme, code) : "";
|
|
150
|
+
const entry = useSyncExternalStore(
|
|
151
|
+
(notify) => (key === "" ? () => {} : subscribe(key, notify)),
|
|
152
|
+
() => (key === "" ? null : request(theme, code)),
|
|
153
|
+
);
|
|
154
|
+
return { theme, status: entry?.status ?? "pending" };
|
|
155
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub alerts: a blockquote whose first line is exactly `[!NOTE]`, `[!TIP]`,
|
|
3
|
+
* `[!IMPORTANT]`, `[!WARNING]` or `[!CAUTION]` renders as a callout.
|
|
4
|
+
*
|
|
5
|
+
* This runs on hast, after Streamdown's sanitiser: a remark-stage marker
|
|
6
|
+
* would be an attribute the sanitiser strips. It strips the marker line and
|
|
7
|
+
* tags the blockquote with `data-alert`, which `MarkdownBlockquote` reads.
|
|
8
|
+
* As on GitHub, `> [!NOTE] text` (marker not alone on its line) stays a quote.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const ALERT_KINDS = [
|
|
12
|
+
"note",
|
|
13
|
+
"tip",
|
|
14
|
+
"important",
|
|
15
|
+
"warning",
|
|
16
|
+
"caution",
|
|
17
|
+
] as const;
|
|
18
|
+
export type AlertKind = (typeof ALERT_KINDS)[number];
|
|
19
|
+
|
|
20
|
+
interface HastNode {
|
|
21
|
+
type: string;
|
|
22
|
+
tagName?: string;
|
|
23
|
+
value?: string;
|
|
24
|
+
properties?: Record<string, string | number | boolean | null | undefined | (string | number)[]>;
|
|
25
|
+
children?: HastNode[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MARKER = /^\[!(note|tip|important|warning|caution)\][ \t]*(\r?\n|$)/i;
|
|
29
|
+
|
|
30
|
+
export function isAlertKind(value: string): value is AlertKind {
|
|
31
|
+
return ALERT_KINDS.some((kind) => kind === value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isElement(node: HastNode | undefined, tagName: string): boolean {
|
|
35
|
+
return node?.type === "element" && node.tagName === tagName;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isBlank(node: HastNode): boolean {
|
|
39
|
+
return node.type === "text" && (node.value ?? "").trim() === "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Tags `blockquote` as an alert and strips its marker, if it has one. */
|
|
43
|
+
function convert(blockquote: HastNode): void {
|
|
44
|
+
const paragraph = blockquote.children?.find((child) => !isBlank(child));
|
|
45
|
+
if (!paragraph || !isElement(paragraph, "p") || !paragraph.children) return;
|
|
46
|
+
const [first, second] = paragraph.children;
|
|
47
|
+
if (first?.type !== "text") return;
|
|
48
|
+
const match = MARKER.exec(first.value ?? "");
|
|
49
|
+
const kind = match?.[1]?.toLowerCase();
|
|
50
|
+
if (!match || kind === undefined || !isAlertKind(kind)) return;
|
|
51
|
+
|
|
52
|
+
const endsLine = match[2] !== "";
|
|
53
|
+
const rest = (first.value ?? "").slice(match[0].length);
|
|
54
|
+
if (!endsLine) {
|
|
55
|
+
// Marker ran to the end of the text node: the line ends only if a hard
|
|
56
|
+
// break (remark-breaks) or nothing follows it.
|
|
57
|
+
if (second !== undefined && !isElement(second, "br")) return;
|
|
58
|
+
paragraph.children.splice(1, second === undefined ? 0 : 1);
|
|
59
|
+
const next = paragraph.children[1];
|
|
60
|
+
if (next?.type === "text") next.value = (next.value ?? "").replace(/^\r?\n/, "");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (rest === "") paragraph.children.shift();
|
|
64
|
+
else first.value = rest;
|
|
65
|
+
if (paragraph.children.every(isBlank)) {
|
|
66
|
+
blockquote.children = blockquote.children?.filter((child) => child !== paragraph);
|
|
67
|
+
}
|
|
68
|
+
blockquote.properties = { ...blockquote.properties, dataAlert: kind };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function visit(node: HastNode): void {
|
|
72
|
+
for (const child of node.children ?? []) {
|
|
73
|
+
if (isElement(child, "blockquote")) convert(child);
|
|
74
|
+
visit(child);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function rehypeGithubAlerts() {
|
|
79
|
+
return (tree: HastNode): void => visit(tree);
|
|
80
|
+
}
|
package/src/motion/presets.ts
CHANGED
|
@@ -51,3 +51,27 @@ export const fadeUpSpring: Variants = {
|
|
|
51
51
|
show: { opacity: 1, y: 0, transition: springTransition },
|
|
52
52
|
exit: { opacity: 0, y: 6, transition: springFastTransition },
|
|
53
53
|
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* `motion/react` transitions that match the CSS duration tokens
|
|
57
|
+
* `--motion-fast` (150ms), `--motion-base` (220ms) and `--motion-slow`
|
|
58
|
+
* (320ms) with `--motion-ease-out`. Seconds here, milliseconds in CSS: if an
|
|
59
|
+
* app changes a token, it changes the number here too.
|
|
60
|
+
*
|
|
61
|
+
* - `fast`: hover, focus, small reveals.
|
|
62
|
+
* - `base`: the default for anything entering or leaving.
|
|
63
|
+
* - `slow`: large surfaces and staggered page content.
|
|
64
|
+
*/
|
|
65
|
+
export const tokenTransition = {
|
|
66
|
+
fast: { duration: 0.15, ease: motionEase },
|
|
67
|
+
base: { duration: 0.22, ease: motionEase },
|
|
68
|
+
slow: { duration: 0.32, ease: motionEase },
|
|
69
|
+
} as const satisfies Record<"fast" | "base" | "slow", Transition>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A stagger step for list entrances, capped so a long list does not make the
|
|
73
|
+
* last row wait. Use as `delay: staggerDelay(index)`.
|
|
74
|
+
*/
|
|
75
|
+
export function staggerDelay(index: number, step: number = motionTiming.stagger, max = 0.2): number {
|
|
76
|
+
return Math.min(index * step, max);
|
|
77
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`AccordionAlt markup > multiple, classNames on every part 1`] = `"<div class="root-x" data-orientation="vertical"><div data-state="open" data-orientation="vertical" class="rounded-lg item-x"><h3 data-orientation="vertical" data-state="open" class="flex"><button type="button" aria-controls="radix-«id0»" aria-expanded="true" data-state="open" data-orientation="vertical" id="radix-«id1»" class="flex flex-1 items-center justify-between rounded-lg text-sm font-medium transition-[background-color,color,transform] duration-100 ease-out active:scale-[0.99] hover:bg-surface-tertiary/50 [&[data-state=open]>svg]:rotate-180 trigger-x py-2" data-radix-collection-item="">Open<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-chevron-down h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200"><path d="M6 9l6 6l6 -6"></path></svg></button></h3><div data-state="open" id="radix-«id0»" role="region" aria-labelledby="radix-«id1»" data-orientation="vertical" class="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" style="--radix-accordion-content-height: var(--radix-collapsible-content-height); --radix-accordion-content-width: var(--radix-collapsible-content-width); transition-duration: 0s; animation-name: none;"><div class="pt-0 text-muted-foreground content-x pb-2">Body</div></div></div><div data-state="closed" data-disabled="" data-orientation="vertical" class="rounded-lg"><h3 data-orientation="vertical" data-state="closed" data-disabled="" class="flex"><button type="button" aria-expanded="false" data-state="closed" data-disabled="" disabled="" data-orientation="vertical" id="radix-«id2»" class="flex flex-1 items-center justify-between rounded-lg py-4 text-sm font-medium transition-[background-color,color,transform] duration-100 ease-out active:scale-[0.99] hover:bg-surface-tertiary/50 [&[data-state=open]>svg]:rotate-180" data-radix-collection-item="">Disabled<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-chevron-down h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200"><path d="M6 9l6 6l6 -6"></path></svg></button></h3><div data-state="closed" data-disabled="" id="radix-«id3»" role="region" aria-labelledby="radix-«id2»" data-orientation="vertical" class="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" style="--radix-accordion-content-height: var(--radix-collapsible-content-height); --radix-accordion-content-width: var(--radix-collapsible-content-width); transition-duration: 0s; animation-name: none;"><div class="pb-4 pt-0 text-muted-foreground">Forced</div></div></div></div>"`;
|
|
4
|
+
|
|
5
|
+
exports[`AccordionAlt markup > single collapsible, one open 1`] = `"<div data-orientation="vertical"><div data-state="open" data-orientation="vertical" class="rounded-lg"><h3 data-orientation="vertical" data-state="open" class="flex"><button type="button" aria-controls="radix-«id0»" aria-expanded="true" data-state="open" data-orientation="vertical" id="radix-«id1»" class="flex flex-1 items-center justify-between rounded-lg py-4 text-sm font-medium transition-[background-color,color,transform] duration-100 ease-out active:scale-[0.99] hover:bg-surface-tertiary/50 [&[data-state=open]>svg]:rotate-180" data-radix-collection-item="">Open<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-chevron-down h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200"><path d="M6 9l6 6l6 -6"></path></svg></button></h3><div data-state="open" id="radix-«id0»" role="region" aria-labelledby="radix-«id1»" data-orientation="vertical" class="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" style="--radix-accordion-content-height: var(--radix-collapsible-content-height); --radix-accordion-content-width: var(--radix-collapsible-content-width); transition-duration: 0s; animation-name: none;"><div class="pb-4 pt-0 text-muted-foreground">Body A</div></div></div><div data-state="closed" data-orientation="vertical" class="rounded-lg"><h3 data-orientation="vertical" data-state="closed" class="flex"><button type="button" aria-expanded="false" data-state="closed" data-orientation="vertical" id="radix-«id2»" class="flex flex-1 items-center justify-between rounded-lg py-4 text-sm font-medium transition-[background-color,color,transform] duration-100 ease-out active:scale-[0.99] hover:bg-surface-tertiary/50 [&[data-state=open]>svg]:rotate-180" data-radix-collection-item="">Closed<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-chevron-down h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200"><path d="M6 9l6 6l6 -6"></path></svg></button></h3><div data-state="closed" id="radix-«id3»" hidden="" role="region" aria-labelledby="radix-«id2»" data-orientation="vertical" class="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" style="--radix-accordion-content-height: var(--radix-collapsible-content-height); --radix-accordion-content-width: var(--radix-collapsible-content-width);"></div></div></div>"`;
|
|
6
|
+
|
|
7
|
+
exports[`ClearInputAlt markup > empty 1`] = `"<div class="relative"><input class="flex h-10 w-full rounded-field border border-border bg-field-background px-3.5 py-2 text-sm transition-[border-color,background-color] duration-200 ease-smooth placeholder:text-field-placeholder disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-hidden focus-visible:border-focus-border focus-visible:ring-1 focus-visible:ring-focus-ring pr-9" type="text" value=""><button type="button" tabindex="-1" class="absolute right-2 top-1/2 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-[opacity,background-color,color,transform] duration-150 ease-out active:duration-100 active:ease-out active:scale-[0.97] hover:bg-surface-tertiary hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring before:absolute before:inset-[-8px] before:content-[''] pointer-events-none opacity-0" aria-hidden="true" aria-label="Clear"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-x"><path d="M18 6l-12 12"></path><path d="M6 6l12 12"></path></svg></button></div>"`;
|
|
8
|
+
|
|
9
|
+
exports[`ClearInputAlt markup > empty with placeholder 1`] = `"<div class="relative"><input placeholder="Find" class="flex h-10 w-full rounded-field border border-border bg-field-background px-3.5 py-2 text-sm transition-[border-color,background-color] duration-200 ease-smooth placeholder:text-field-placeholder disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-hidden focus-visible:border-focus-border focus-visible:ring-1 focus-visible:ring-focus-ring pr-9" type="text" value=""><button type="button" tabindex="-1" class="absolute right-2 top-1/2 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-[opacity,background-color,color,transform] duration-150 ease-out active:duration-100 active:ease-out active:scale-[0.97] hover:bg-surface-tertiary hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring before:absolute before:inset-[-8px] before:content-[''] pointer-events-none opacity-0" aria-hidden="true" aria-label="Clear"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-x"><path d="M18 6l-12 12"></path><path d="M6 6l12 12"></path></svg></button></div>"`;
|
|
10
|
+
|
|
11
|
+
exports[`ClearInputAlt markup > every prop 1`] = `"<div class="relative w-40"><span class="absolute left-2">L</span><input placeholder="Find" class="flex w-full rounded-field border border-border bg-field-background px-3.5 py-2 text-sm transition-[border-color,background-color] duration-200 ease-smooth placeholder:text-field-placeholder disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-hidden focus-visible:border-focus-border focus-visible:ring-1 focus-visible:ring-focus-ring pr-9 h-9" id="ci" disabled="" type="text" value="abc"><button type="button" tabindex="0" class="absolute right-2 top-1/2 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-[opacity,background-color,color,transform] duration-150 ease-out active:duration-100 active:ease-out active:scale-[0.97] hover:bg-surface-tertiary hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring before:absolute before:inset-[-8px] before:content-[''] opacity-100" aria-hidden="false" aria-label="Reset"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-x"><path d="M18 6l-12 12"></path><path d="M6 6l12 12"></path></svg></button></div>"`;
|
|
12
|
+
|
|
13
|
+
exports[`ClearInputAlt markup > filled 1`] = `"<div class="relative"><input class="flex h-10 w-full rounded-field border border-border bg-field-background px-3.5 py-2 text-sm transition-[border-color,background-color] duration-200 ease-smooth placeholder:text-field-placeholder disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-hidden focus-visible:border-focus-border focus-visible:ring-1 focus-visible:ring-focus-ring pr-9" type="text" value="abc"><button type="button" tabindex="0" class="absolute right-2 top-1/2 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-[opacity,background-color,color,transform] duration-150 ease-out active:duration-100 active:ease-out active:scale-[0.97] hover:bg-surface-tertiary hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring before:absolute before:inset-[-8px] before:content-[''] opacity-100" aria-hidden="false" aria-label="Clear"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-x"><path d="M18 6l-12 12"></path><path d="M6 6l12 12"></path></svg></button></div>"`;
|
|
14
|
+
|
|
15
|
+
exports[`InputGroupAlt markup > input className and props 1`] = `"<div data-input-group="" class="flex h-10 flex-wrap rounded-field border border-border bg-field-background text-sm transition-[border-color,box-shadow] duration-200 ease-smooth has-[textarea]:h-auto has-data-[slot=input-group-control]:h-auto has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-focus-visible:outline-hidden has-focus-visible:border-focus-border has-focus-visible:ring-1 has-focus-visible:ring-focus-ring"><input class="flex w-full max-sm:min-w-0 border-border text-base sm:text-sm transition-[border-color,box-shadow,background-color] duration-200 ease-smooth file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-field-placeholder disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-hidden focus-visible:border-focus-border focus-visible:ring-focus-ring h-full flex-1 rounded-none border-0 bg-transparent px-3 py-2 shadow-none focus-visible:border-0 focus-visible:ring-0 in-x" id="i" disabled=""></div>"`;
|
|
16
|
+
|
|
17
|
+
exports[`InputGroupAlt markup > input with every addon align and button size 1`] = `"<div data-input-group="" class="flex h-10 flex-wrap rounded-field border border-border bg-field-background text-sm transition-[border-color,box-shadow] duration-200 ease-smooth has-[textarea]:h-auto has-data-[slot=input-group-control]:h-auto has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-focus-visible:outline-hidden has-focus-visible:border-focus-border has-focus-visible:ring-1 has-focus-visible:ring-focus-ring"><div class="flex items-center flex-row ps-3"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-search"><path d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"></path><path d="M21 21l-6 -6"></path></svg></div><div data-align="inline-start" class="flex items-center flex-row ps-3"><span class="flex items-center text-sm text-muted-foreground/95">https://</span></div><input class="flex w-full max-sm:min-w-0 border-border text-base sm:text-sm transition-[border-color,box-shadow,background-color] duration-200 ease-smooth file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-field-placeholder disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-hidden focus-visible:border-focus-border focus-visible:ring-focus-ring h-full flex-1 rounded-none border-0 bg-transparent px-3 py-2 shadow-none focus-visible:border-0 focus-visible:ring-0" placeholder="Search"><div data-align="inline-end" class="flex items-center flex-row pe-3"><button class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium tracking-normal transition-[transform,background-color,box-shadow,opacity,border-color] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.96] [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 text-muted-foreground hover:bg-surface-tertiary hover:text-foreground py-2 shrink-0 shadow-none h-7 rounded-lg px-2 text-xs">Go</button><button class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium tracking-normal transition-[transform,background-color,box-shadow,opacity,border-color] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.96] [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 text-muted-foreground hover:bg-surface-tertiary hover:text-foreground py-2 shrink-0 shadow-none h-8 rounded-lg px-2.5 text-xs">Small</button><button class="inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium tracking-normal transition-[transform,background-color,box-shadow,opacity,border-color] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.96] [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 text-muted-foreground hover:bg-surface-tertiary hover:text-foreground px-4 py-2 shrink-0 shadow-none h-7 w-7 rounded-lg" aria-label="A"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-search"><path d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"></path><path d="M21 21l-6 -6"></path></svg></button><button class="inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium tracking-normal transition-[transform,background-color,box-shadow,opacity,border-color] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.96] [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:-translate-y-0.5 hover:opacity-90 px-4 py-2 shrink-0 shadow-none h-8 w-8 rounded-lg" type="submit" aria-label="B"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-search"><path d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"></path><path d="M21 21l-6 -6"></path></svg></button></div></div>"`;
|
|
18
|
+
|
|
19
|
+
exports[`InputGroupAlt markup > textarea with block addons, error and classNames 1`] = `"<div data-input-group="" class="flex h-10 flex-wrap rounded-field border bg-field-background text-sm transition-[border-color,box-shadow] duration-200 ease-smooth has-[textarea]:h-auto has-data-[slot=input-group-control]:h-auto has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-focus-visible:outline-hidden has-focus-visible:ring-1 border-danger-border has-focus-visible:border-danger has-focus-visible:ring-danger-ring root-x" id="g"><div data-align="block-start" class="flex items-center w-full flex-row border-b border-separator px-3 py-2 addon-x"><span class="flex items-center text-sm text-x">Title</span></div><textarea class="flex min-h-20 w-full max-sm:min-w-0 border-border text-base sm:text-sm placeholder:text-field-placeholder disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-hidden focus-visible:border-focus-border focus-visible:ring-focus-ring flex-1 resize-none rounded-none border-0 bg-transparent px-3 py-2.5 shadow-none focus-visible:border-0 focus-visible:ring-0 field-sizing-content ta-x" rows="2">v</textarea><div data-align="block-end" class="flex items-center w-full flex-row px-3 py-2"><button class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium tracking-normal transition-[transform,background-color,box-shadow,opacity,border-color] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.96] [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 text-muted-foreground hover:bg-surface-tertiary hover:text-foreground py-2 shrink-0 shadow-none h-7 rounded-lg px-2 text-xs btn-x" disabled="" data-loading="true"><svg class="h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>Send</button></div></div>"`;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`SonnerToaster DOM > renders the toaster and a toast 1`] = `"<section aria-label="Notifications alt+T" tabindex="-1" aria-live="polite" aria-relevant="additions text" aria-atomic="false" data-react-aria-top-layer="true"></section>"`;
|
|
4
|
+
|
|
5
|
+
exports[`SonnerToaster DOM > renders the toaster and a toast 2`] = `"<section aria-label="Notifications alt+T" tabindex="-1" aria-live="polite" aria-relevant="additions text" aria-atomic="false" data-react-aria-top-layer="true"><ol dir="ltr" tabindex="-1" class="toaster group !font-sans" data-sonner-toaster="true" data-sonner-theme="light" data-y-position="top" data-x-position="right" style="--front-toast-height: 0px; --width: 356px; --gap: 12px; --offset-top: 24px; --offset-right: 24px; --offset-bottom: 24px; --offset-left: 24px; --mobile-offset-top: 16px; --mobile-offset-right: 16px; --mobile-offset-bottom: 16px; --mobile-offset-left: 16px;"><li tabindex="0" class="group toast glass-panel-strong flex w-full items-center gap-3 rounded-lg px-4 py-3 text-sm text-foreground smooth-shadow-ring-md" data-sonner-toast="" data-styled="false" data-mounted="true" data-promise="false" data-swiped="false" data-removed="false" data-visible="true" data-y-position="top" data-x-position="right" data-index="0" data-front="true" data-swiping="false" data-dismissible="true" data-swipe-out="false" data-expanded="false" style="--index: 0; --toasts-before: 0; --z-index: 1; --offset: 0px; --initial-height: 0px;"><div data-content="" class=""><div data-title="" class="font-medium leading-snug">Saved</div><div data-description="" class="text-[13px] leading-snug text-muted-foreground">All changes stored</div></div></li></ol></section>"`;
|
package/src/ui/_menu-parts.tsx
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import type * as React from "react";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* App defaults shared by `DropdownMenu*` and `ContextMenu*` (see
|
|
5
|
+
* `UiConfigProvider`); each menu has its own entry.
|
|
6
|
+
*/
|
|
7
|
+
export interface MenuConfig {
|
|
8
|
+
/** Replaces the built-in classes of a part. */
|
|
9
|
+
classes?: {
|
|
10
|
+
trigger?: string;
|
|
11
|
+
content?: string;
|
|
12
|
+
subContent?: string;
|
|
13
|
+
subTrigger?: string;
|
|
14
|
+
item?: string;
|
|
15
|
+
checkboxItem?: string;
|
|
16
|
+
radioItem?: string;
|
|
17
|
+
label?: string;
|
|
18
|
+
separator?: string;
|
|
19
|
+
shortcut?: string;
|
|
20
|
+
};
|
|
21
|
+
/** Replace the built-in icons. */
|
|
22
|
+
icons?: {
|
|
23
|
+
/** `CheckboxItem` indicator. */
|
|
24
|
+
check?: React.ReactNode;
|
|
25
|
+
/** `RadioItem` indicator. */
|
|
26
|
+
radio?: React.ReactNode;
|
|
27
|
+
};
|
|
28
|
+
/** Default for `Content` `collisionPadding`. */
|
|
29
|
+
collisionPadding?: number | Partial<Record<"top" | "right" | "bottom" | "left", number>>;
|
|
30
|
+
}
|
|
31
|
+
|
|
3
32
|
/** `--vv-height` is optional: apps may set it to the visual viewport height. */
|
|
4
33
|
export const fitViewportClass =
|
|
5
34
|
"max-sm:max-h-[min(70dvh,var(--vv-height,70dvh))] max-sm:overflow-y-auto";
|
package/src/ui/accordion.alt.tsx
CHANGED
|
@@ -1,56 +1,49 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|
3
2
|
import { IconChevronDown } from "@tabler/icons-react";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
)
|
|
18
|
-
AccordionItem
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
3
|
+
import { Accordion as AccordionPrimitive } from "radix-ui";
|
|
4
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./accordion";
|
|
5
|
+
|
|
6
|
+
// The `Alt` look on the main parts: per-instance `classes` replace the main
|
|
7
|
+
// classes (and any `UiConfig.Accordion` entry), and `data-slot={undefined}`
|
|
8
|
+
// drops the main `data-slot` attributes, so the markup is the pre-wrapper one.
|
|
9
|
+
|
|
10
|
+
/** @deprecated Use `Accordion`. Kept for existing consumers; removed in a later minor after they move. */
|
|
11
|
+
function AccordionAlt(props: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
|
12
|
+
return <Accordion data-slot={undefined} {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** @deprecated Use `AccordionItem`. Kept for existing consumers; removed in a later minor after they move. */
|
|
16
|
+
function AccordionItemAlt(props: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
17
|
+
return <AccordionItem data-slot={undefined} classes={{ item: "rounded-lg" }} {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const triggerClasses = {
|
|
21
|
+
header: "flex",
|
|
22
|
+
trigger:
|
|
23
|
+
"flex flex-1 items-center justify-between rounded-lg py-4 text-sm font-medium transition-[background-color,color,transform] duration-100 ease-out active:scale-[0.99] hover:bg-surface-tertiary/50 [&[data-state=open]>svg]:rotate-180",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** @deprecated Use `AccordionTrigger`. Kept for existing consumers; removed in a later minor after they move. */
|
|
27
|
+
function AccordionTriggerAlt(props: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
|
28
|
+
return (
|
|
29
|
+
<AccordionTrigger
|
|
30
|
+
data-slot={undefined}
|
|
31
|
+
classes={triggerClasses}
|
|
32
|
+
icon={<IconChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />}
|
|
31
33
|
{...props}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
>
|
|
49
|
-
<div className={cn("pb-4 pt-0 text-muted-foreground", className)}>
|
|
50
|
-
{children}
|
|
51
|
-
</div>
|
|
52
|
-
</AccordionPrimitive.Content>
|
|
53
|
-
));
|
|
54
|
-
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
|
55
|
-
|
|
56
|
-
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const contentClasses = {
|
|
39
|
+
content:
|
|
40
|
+
"overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
|
|
41
|
+
contentInner: "pb-4 pt-0 text-muted-foreground",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** @deprecated Use `AccordionContent`. Kept for existing consumers; removed in a later minor after they move. */
|
|
45
|
+
function AccordionContentAlt(props: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
|
46
|
+
return <AccordionContent data-slot={undefined} classes={contentClasses} {...props} />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { AccordionAlt, AccordionItemAlt, AccordionTriggerAlt, AccordionContentAlt };
|
package/src/ui/accordion.tsx
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Accordion as AccordionPrimitive } from "radix-ui";
|
|
3
|
-
import {
|
|
3
|
+
import { useCn, useUiConfig } from "./ui-config";
|
|
4
|
+
|
|
5
|
+
type AccordionClasses = {
|
|
6
|
+
item?: string;
|
|
7
|
+
header?: string;
|
|
8
|
+
trigger?: string;
|
|
9
|
+
content?: string;
|
|
10
|
+
contentInner?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `UiConfig.Accordion`: `classes` replace the built-in part classes
|
|
15
|
+
* (`content` is the animated panel, `contentInner` the padded box inside it);
|
|
16
|
+
* `icon` is the default for the `AccordionTrigger` prop. Each part also takes
|
|
17
|
+
* a `classes` prop with its own keys, which wins over the config.
|
|
18
|
+
*/
|
|
19
|
+
export interface AccordionConfig {
|
|
20
|
+
classes?: AccordionClasses;
|
|
21
|
+
icon?: React.ReactNode;
|
|
22
|
+
}
|
|
4
23
|
|
|
5
24
|
function ChevronDown({ className }: { className?: string }) {
|
|
6
25
|
return (
|
|
@@ -29,12 +48,19 @@ function Accordion({
|
|
|
29
48
|
|
|
30
49
|
function AccordionItem({
|
|
31
50
|
className,
|
|
51
|
+
classes,
|
|
32
52
|
...props
|
|
33
|
-
}: React.ComponentProps<typeof AccordionPrimitive.Item>
|
|
53
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Item> & {
|
|
54
|
+
/** Replaces the item classes for this instance (wins over `UiConfig.Accordion.classes.item`). */
|
|
55
|
+
classes?: Pick<AccordionClasses, "item">;
|
|
56
|
+
}) {
|
|
57
|
+
const config = useUiConfig("Accordion")?.classes;
|
|
58
|
+
const item = classes?.item ?? config?.item;
|
|
59
|
+
const cn = useCn();
|
|
34
60
|
return (
|
|
35
61
|
<AccordionPrimitive.Item
|
|
36
62
|
data-slot="accordion-item"
|
|
37
|
-
className={cn("not-first:mt-6", className)}
|
|
63
|
+
className={cn(item ?? "not-first:mt-6", className)}
|
|
38
64
|
{...props}
|
|
39
65
|
/>
|
|
40
66
|
);
|
|
@@ -43,20 +69,33 @@ function AccordionItem({
|
|
|
43
69
|
function AccordionTrigger({
|
|
44
70
|
className,
|
|
45
71
|
children,
|
|
72
|
+
icon,
|
|
73
|
+
classes,
|
|
46
74
|
...props
|
|
47
|
-
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>
|
|
75
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Trigger> & {
|
|
76
|
+
/** Shown after the children. Default a chevron. */
|
|
77
|
+
icon?: React.ReactNode;
|
|
78
|
+
/** Replaces the header and trigger classes for this instance (wins over `UiConfig.Accordion.classes`). */
|
|
79
|
+
classes?: Pick<AccordionClasses, "header" | "trigger">;
|
|
80
|
+
}) {
|
|
81
|
+
const config = useUiConfig("Accordion");
|
|
82
|
+
const cn = useCn();
|
|
48
83
|
return (
|
|
49
|
-
<AccordionPrimitive.Header className="flex">
|
|
84
|
+
<AccordionPrimitive.Header className={classes?.header ?? config?.classes?.header ?? "flex"}>
|
|
50
85
|
<AccordionPrimitive.Trigger
|
|
51
86
|
data-slot="accordion-trigger"
|
|
52
87
|
className={cn(
|
|
53
|
-
|
|
88
|
+
classes?.trigger ??
|
|
89
|
+
config?.classes?.trigger ??
|
|
90
|
+
"flex flex-1 items-center justify-between py-4 font-medium transition-[color,transform] duration-150 ease-out active:scale-[0.99] hover:underline [&[data-state=open]>svg]:rotate-180",
|
|
54
91
|
className,
|
|
55
92
|
)}
|
|
56
93
|
{...props}
|
|
57
94
|
>
|
|
58
95
|
{children}
|
|
59
|
-
|
|
96
|
+
{icon ?? config?.icon ?? (
|
|
97
|
+
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-150 ease-out" />
|
|
98
|
+
)}
|
|
60
99
|
</AccordionPrimitive.Trigger>
|
|
61
100
|
</AccordionPrimitive.Header>
|
|
62
101
|
);
|
|
@@ -65,15 +104,25 @@ function AccordionTrigger({
|
|
|
65
104
|
function AccordionContent({
|
|
66
105
|
className,
|
|
67
106
|
children,
|
|
107
|
+
classes,
|
|
68
108
|
...props
|
|
69
|
-
}: React.ComponentProps<typeof AccordionPrimitive.Content>
|
|
109
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Content> & {
|
|
110
|
+
/** Replaces the panel and inner box classes for this instance (wins over `UiConfig.Accordion.classes`). */
|
|
111
|
+
classes?: Pick<AccordionClasses, "content" | "contentInner">;
|
|
112
|
+
}) {
|
|
113
|
+
const config = useUiConfig("Accordion")?.classes;
|
|
114
|
+
const cn = useCn();
|
|
70
115
|
return (
|
|
71
116
|
<AccordionPrimitive.Content
|
|
72
117
|
data-slot="accordion-content"
|
|
73
|
-
className=
|
|
118
|
+
className={
|
|
119
|
+
classes?.content ??
|
|
120
|
+
config?.content ??
|
|
121
|
+
"overflow-hidden text-sm transition-[height] data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
|
122
|
+
}
|
|
74
123
|
{...props}
|
|
75
124
|
>
|
|
76
|
-
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
|
125
|
+
<div className={cn(classes?.contentInner ?? config?.contentInner ?? "pb-4 pt-0", className)}>{children}</div>
|
|
77
126
|
</AccordionPrimitive.Content>
|
|
78
127
|
);
|
|
79
128
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Avatar as AvatarPrimitive } from "radix-ui";
|
|
3
|
+
import { useCn } from "./ui-config";
|
|
4
|
+
|
|
5
|
+
const Avatar = React.forwardRef<
|
|
6
|
+
React.ComponentRef<typeof AvatarPrimitive.Root>,
|
|
7
|
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
|
8
|
+
>(({ className, ...props }, ref) => {
|
|
9
|
+
const merge = useCn();
|
|
10
|
+
return (
|
|
11
|
+
<AvatarPrimitive.Root
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={merge(
|
|
14
|
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full border border-border bg-surface-secondary",
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
Avatar.displayName = AvatarPrimitive.Root.displayName;
|
|
22
|
+
|
|
23
|
+
const AvatarImage = React.forwardRef<
|
|
24
|
+
React.ComponentRef<typeof AvatarPrimitive.Image>,
|
|
25
|
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
|
26
|
+
>(({ className, ...props }, ref) => {
|
|
27
|
+
const merge = useCn();
|
|
28
|
+
return (
|
|
29
|
+
<AvatarPrimitive.Image
|
|
30
|
+
ref={ref}
|
|
31
|
+
className={merge("aspect-square h-full w-full", className)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
|
|
37
|
+
|
|
38
|
+
const AvatarFallback = React.forwardRef<
|
|
39
|
+
React.ComponentRef<typeof AvatarPrimitive.Fallback>,
|
|
40
|
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
|
41
|
+
>(({ className, ...props }, ref) => {
|
|
42
|
+
const merge = useCn();
|
|
43
|
+
return (
|
|
44
|
+
<AvatarPrimitive.Fallback
|
|
45
|
+
ref={ref}
|
|
46
|
+
className={merge(
|
|
47
|
+
"flex h-full w-full items-center justify-center rounded-full bg-secondary text-secondary-foreground",
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
|
55
|
+
|
|
56
|
+
export { Avatar, AvatarImage, AvatarFallback };
|