@hex-core/components 1.5.0 → 1.7.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.
@@ -0,0 +1,2 @@
1
+ export { SpeechRecognitionProps_alias_1 as SpeechRecognitionProps } from './_tsup-dts-rollup.js';
2
+ export { SpeechRecognition_alias_1 as SpeechRecognition } from './_tsup-dts-rollup.js';
@@ -0,0 +1,152 @@
1
+ "use client";
2
+ import * as React from 'react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ function cn(...inputs) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+ function getSpeechRecognitionCtor() {
11
+ if (typeof window === "undefined") return null;
12
+ const w = window;
13
+ return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
14
+ }
15
+ function SpeechRecognition({
16
+ isListening,
17
+ onListeningChange,
18
+ onTranscript,
19
+ onError,
20
+ lang = "en-US",
21
+ continuous = true,
22
+ interimResults = true,
23
+ startLabel = "Start dictation",
24
+ stopLabel = "Stop dictation",
25
+ notSupportedLabel = "Speech recognition not supported in this browser",
26
+ disabled,
27
+ className,
28
+ ...rest
29
+ }) {
30
+ const recognitionRef = React.useRef(null);
31
+ const [isSupported, setIsSupported] = React.useState(true);
32
+ const onTranscriptRef = React.useRef(onTranscript);
33
+ const onListeningChangeRef = React.useRef(onListeningChange);
34
+ const onErrorRef = React.useRef(onError);
35
+ onTranscriptRef.current = onTranscript;
36
+ onListeningChangeRef.current = onListeningChange;
37
+ onErrorRef.current = onError;
38
+ const mountedRef = React.useRef(true);
39
+ React.useEffect(
40
+ () => () => {
41
+ mountedRef.current = false;
42
+ },
43
+ []
44
+ );
45
+ const rebuildingRef = React.useRef(false);
46
+ const isListeningRef = React.useRef(isListening);
47
+ isListeningRef.current = isListening;
48
+ React.useEffect(() => {
49
+ const Ctor = getSpeechRecognitionCtor();
50
+ setIsSupported(Ctor !== null);
51
+ }, []);
52
+ React.useEffect(() => {
53
+ if (!isListening) return;
54
+ const Ctor = getSpeechRecognitionCtor();
55
+ if (!Ctor) return;
56
+ const instance = new Ctor();
57
+ instance.continuous = continuous;
58
+ instance.interimResults = interimResults;
59
+ instance.lang = lang;
60
+ instance.onresult = (event) => {
61
+ if (!mountedRef.current) return;
62
+ for (let i = event.resultIndex; i < event.results.length; i++) {
63
+ const result = event.results[i];
64
+ const transcript = result[0]?.transcript ?? "";
65
+ if (transcript) onTranscriptRef.current(transcript, result.isFinal);
66
+ }
67
+ };
68
+ instance.onerror = (event) => {
69
+ if (!mountedRef.current) return;
70
+ onErrorRef.current?.(event.error, event.message);
71
+ if (event.error !== "aborted") onListeningChangeRef.current(false);
72
+ };
73
+ instance.onend = () => {
74
+ if (!mountedRef.current) return;
75
+ if (rebuildingRef.current) return;
76
+ onListeningChangeRef.current(false);
77
+ };
78
+ recognitionRef.current = instance;
79
+ try {
80
+ instance.start();
81
+ } catch (err) {
82
+ onErrorRef.current?.("start-failed", err instanceof Error ? err.message : String(err));
83
+ onListeningChangeRef.current(false);
84
+ }
85
+ return () => {
86
+ rebuildingRef.current = isListeningRef.current;
87
+ instance.onresult = null;
88
+ instance.onerror = null;
89
+ instance.onend = null;
90
+ try {
91
+ instance.abort();
92
+ } catch {
93
+ }
94
+ recognitionRef.current = null;
95
+ queueMicrotask(() => {
96
+ rebuildingRef.current = false;
97
+ });
98
+ };
99
+ }, [isListening, continuous, interimResults, lang]);
100
+ const tooltip = !isSupported ? notSupportedLabel : isListening ? stopLabel : startLabel;
101
+ const accessibleName = isSupported ? startLabel : notSupportedLabel;
102
+ const isDisabled = disabled || !isSupported;
103
+ return /* @__PURE__ */ jsx(
104
+ "button",
105
+ {
106
+ type: "button",
107
+ ...rest,
108
+ disabled: isDisabled,
109
+ "aria-label": accessibleName,
110
+ "aria-pressed": isListening,
111
+ title: tooltip,
112
+ onClick: (event) => {
113
+ rest.onClick?.(event);
114
+ if (event.defaultPrevented || isDisabled) return;
115
+ onListeningChange(!isListening);
116
+ },
117
+ className: cn(
118
+ "inline-flex h-9 w-9 items-center justify-center rounded-md border bg-background",
119
+ "text-foreground transition-colors duration-[var(--duration-normal,200ms)] ease-out",
120
+ "hover:bg-accent hover:text-accent-foreground",
121
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
122
+ "disabled:cursor-not-allowed disabled:opacity-50",
123
+ isListening && "animate-pulse border-destructive text-destructive",
124
+ className
125
+ ),
126
+ children: /* @__PURE__ */ jsxs(
127
+ "svg",
128
+ {
129
+ "aria-hidden": true,
130
+ viewBox: "0 0 16 16",
131
+ width: "14",
132
+ height: "14",
133
+ fill: "none",
134
+ stroke: "currentColor",
135
+ strokeWidth: "1.5",
136
+ strokeLinecap: "round",
137
+ strokeLinejoin: "round",
138
+ children: [
139
+ /* @__PURE__ */ jsx("rect", { x: "6", y: "2", width: "4", height: "8", rx: "2" }),
140
+ /* @__PURE__ */ jsx("path", { d: "M3.5 7.5a4.5 4.5 0 0 0 9 0" }),
141
+ /* @__PURE__ */ jsx("path", { d: "M8 12v2" }),
142
+ /* @__PURE__ */ jsx("path", { d: "M5.5 14h5" })
143
+ ]
144
+ }
145
+ )
146
+ }
147
+ );
148
+ }
149
+
150
+ export { SpeechRecognition };
151
+ //# sourceMappingURL=speech-recognition.js.map
152
+ //# sourceMappingURL=speech-recognition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/ai/speech-recognition/speech-recognition.tsx"],"names":[],"mappings":"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","file":"speech-recognition.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Browser SpeechRecognition wrapper. Renders a mic toggle button that\n * starts/stops the Web Speech API and emits transcript chunks.\n *\n * Headless on data: `isListening` + `onListeningChange` are required so\n * the consumer keeps state where it fits (a `useChat` hook, redux,\n * local state). `onTranscript` fires per result with `isFinal` so the\n * consumer can append finalized phrases and replace interim ones.\n *\n * Falls back to a disabled button labeled `notSupportedLabel` when the\n * browser lacks `SpeechRecognition` (Firefox as of 2026, older Safari).\n *\n * @example\n * const [listening, setListening] = useState(false);\n * const [text, setText] = useState(\"\");\n * <SpeechRecognition\n * isListening={listening}\n * onListeningChange={setListening}\n * onTranscript={(chunk, isFinal) => {\n * if (isFinal) setText((t) => t + chunk);\n * }}\n * />\n */\ntype SpeechRecognitionConstructor = new () => SpeechRecognitionInstance;\n\ninterface SpeechRecognitionResultLike {\n\treadonly isFinal: boolean;\n\treadonly length: number;\n\treadonly [index: number]: { readonly transcript: string };\n}\n\ninterface SpeechRecognitionResultListLike {\n\treadonly length: number;\n\treadonly [index: number]: SpeechRecognitionResultLike;\n}\n\ninterface SpeechRecognitionEventLike {\n\treadonly resultIndex: number;\n\treadonly results: SpeechRecognitionResultListLike;\n}\n\ninterface SpeechRecognitionErrorEventLike {\n\treadonly error: string;\n\treadonly message?: string;\n}\n\ninterface SpeechRecognitionInstance {\n\tcontinuous: boolean;\n\tinterimResults: boolean;\n\tlang: string;\n\tonresult: ((event: SpeechRecognitionEventLike) => void) | null;\n\tonerror: ((event: SpeechRecognitionErrorEventLike) => void) | null;\n\tonend: (() => void) | null;\n\tstart(): void;\n\tstop(): void;\n\tabort(): void;\n}\n\nfunction getSpeechRecognitionCtor(): SpeechRecognitionConstructor | null {\n\tif (typeof window === \"undefined\") return null;\n\tconst w = window as unknown as {\n\t\tSpeechRecognition?: SpeechRecognitionConstructor;\n\t\twebkitSpeechRecognition?: SpeechRecognitionConstructor;\n\t};\n\treturn w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;\n}\n\nexport interface SpeechRecognitionProps\n\textends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"onError\"> {\n\t/** Controlled listening state. */\n\tisListening: boolean;\n\t/** Called when listening starts/stops (user toggle or browser auto-end). */\n\tonListeningChange: (listening: boolean) => void;\n\t/** Called per transcript chunk. `isFinal` indicates a finalized phrase. */\n\tonTranscript: (text: string, isFinal: boolean) => void;\n\t/** Called on browser error (e.g. \"not-allowed\", \"no-speech\", \"network\"). */\n\tonError?: (error: string, message?: string) => void;\n\t/** BCP-47 language tag. Default `\"en-US\"`. */\n\tlang?: string;\n\t/** Keep listening across pauses. Default `true`. */\n\tcontinuous?: boolean;\n\t/** Emit interim (in-progress) results. Default `true`. */\n\tinterimResults?: boolean;\n\t/** Accessible name when idle. Default `\"Start dictation\"`. */\n\tstartLabel?: string;\n\t/** Accessible name when listening. Default `\"Stop dictation\"`. */\n\tstopLabel?: string;\n\t/** Accessible name + tooltip when the browser lacks the API. */\n\tnotSupportedLabel?: string;\n}\n\n/**\n * Renders a mic toggle button wired to the Web Speech API.\n * @param props - controlled listening state + transcript callback\n * @returns A button element that toggles speech recognition\n */\nfunction SpeechRecognition({\n\tisListening,\n\tonListeningChange,\n\tonTranscript,\n\tonError,\n\tlang = \"en-US\",\n\tcontinuous = true,\n\tinterimResults = true,\n\tstartLabel = \"Start dictation\",\n\tstopLabel = \"Stop dictation\",\n\tnotSupportedLabel = \"Speech recognition not supported in this browser\",\n\tdisabled,\n\tclassName,\n\t...rest\n}: SpeechRecognitionProps) {\n\tconst recognitionRef = React.useRef<SpeechRecognitionInstance | null>(null);\n\tconst [isSupported, setIsSupported] = React.useState(true);\n\n\t// \"Latest ref\" pattern, assigned synchronously in render so a Web Speech\n\t// callback firing between commit and a useEffect can never see stale\n\t// closures. React permits ref mutation during render when the assignment\n\t// is purely a latest-value mirror.\n\tconst onTranscriptRef = React.useRef(onTranscript);\n\tconst onListeningChangeRef = React.useRef(onListeningChange);\n\tconst onErrorRef = React.useRef(onError);\n\tonTranscriptRef.current = onTranscript;\n\tonListeningChangeRef.current = onListeningChange;\n\tonErrorRef.current = onError;\n\n\t// Mounted guard: the engine fires onend asynchronously, sometimes after\n\t// the React tree has been torn down. Without this, a stale handler can\n\t// invoke setState on an unmounted parent.\n\tconst mountedRef = React.useRef(true);\n\tReact.useEffect(\n\t\t() => () => {\n\t\t\tmountedRef.current = false;\n\t\t},\n\t\t[],\n\t);\n\n\t// Set when the lifecycle effect's cleanup runs because of a prop change\n\t// (lang/continuous/interimResults), not user-initiated stop. The about-\n\t// to-fire onend should NOT bubble back as `onListeningChange(false)` in\n\t// that case — the new effect run is about to re-create the engine.\n\tconst rebuildingRef = React.useRef(false);\n\t// Latest `isListening` mirror so cleanup can read the NEW value (closure\n\t// captures OLD). NEW=true means this is a prop-change rebuild; NEW=false\n\t// means the user stopped.\n\tconst isListeningRef = React.useRef(isListening);\n\tisListeningRef.current = isListening;\n\n\t// SSR: ctor lookup must run after mount.\n\tReact.useEffect(() => {\n\t\tconst Ctor = getSpeechRecognitionCtor();\n\t\tsetIsSupported(Ctor !== null);\n\t}, []);\n\n\t// Toggle the engine on `isListening` change. Recreate per session so a\n\t// stuck session can't leak — Chrome's recognition is single-use after\n\t// onend in some failure modes.\n\tReact.useEffect(() => {\n\t\tif (!isListening) return;\n\n\t\tconst Ctor = getSpeechRecognitionCtor();\n\t\tif (!Ctor) return;\n\n\t\tconst instance = new Ctor();\n\t\tinstance.continuous = continuous;\n\t\tinstance.interimResults = interimResults;\n\t\tinstance.lang = lang;\n\n\t\tinstance.onresult = (event) => {\n\t\t\tif (!mountedRef.current) return;\n\t\t\tfor (let i = event.resultIndex; i < event.results.length; i++) {\n\t\t\t\tconst result = event.results[i];\n\t\t\t\tconst transcript = result[0]?.transcript ?? \"\";\n\t\t\t\tif (transcript) onTranscriptRef.current(transcript, result.isFinal);\n\t\t\t}\n\t\t};\n\t\tinstance.onerror = (event) => {\n\t\t\tif (!mountedRef.current) return;\n\t\t\tonErrorRef.current?.(event.error, event.message);\n\t\t\t// \"aborted\" is a normal stop signal — don't toggle off twice.\n\t\t\tif (event.error !== \"aborted\") onListeningChangeRef.current(false);\n\t\t};\n\t\tinstance.onend = () => {\n\t\t\tif (!mountedRef.current) return;\n\t\t\t// Skip the off-toggle when cleanup is from a prop-change rebuild;\n\t\t\t// the next effect run will re-start with the new options.\n\t\t\tif (rebuildingRef.current) return;\n\t\t\tonListeningChangeRef.current(false);\n\t\t};\n\n\t\trecognitionRef.current = instance;\n\t\ttry {\n\t\t\tinstance.start();\n\t\t} catch (err) {\n\t\t\t// Chrome throws if start() is called twice; surface as an error\n\t\t\t// rather than letting it crash the React tree.\n\t\t\tonErrorRef.current?.(\"start-failed\", err instanceof Error ? err.message : String(err));\n\t\t\tonListeningChangeRef.current(false);\n\t\t}\n\n\t\treturn () => {\n\t\t\t// Mark this teardown as a rebuild iff isListening is STILL true\n\t\t\t// in the latest render (only lang/continuous/interimResults\n\t\t\t// changed). On a real user-stop the NEW isListening is false and\n\t\t\t// any synchronous onend-from-abort should toggle parent state.\n\t\t\trebuildingRef.current = isListeningRef.current;\n\t\t\tinstance.onresult = null;\n\t\t\tinstance.onerror = null;\n\t\t\tinstance.onend = null;\n\t\t\ttry {\n\t\t\t\tinstance.abort();\n\t\t\t} catch {\n\t\t\t\t// abort() throws if the engine never started; safe to ignore.\n\t\t\t}\n\t\t\trecognitionRef.current = null;\n\t\t\t// Reset on next microtask so a follow-up effect run sees a clean slate.\n\t\t\tqueueMicrotask(() => {\n\t\t\t\trebuildingRef.current = false;\n\t\t\t});\n\t\t};\n\t}, [isListening, continuous, interimResults, lang]);\n\n\tconst tooltip = !isSupported\n\t\t? notSupportedLabel\n\t\t: isListening\n\t\t\t? stopLabel\n\t\t\t: startLabel;\n\t// aria-label is stable when supported so screen readers don't re-announce\n\t// the entire button on each toggle. State is conveyed via aria-pressed.\n\t// Only the unsupported case swaps the accessible name.\n\tconst accessibleName = isSupported ? startLabel : notSupportedLabel;\n\tconst isDisabled = disabled || !isSupported;\n\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\t{...rest}\n\t\t\tdisabled={isDisabled}\n\t\t\taria-label={accessibleName}\n\t\t\taria-pressed={isListening}\n\t\t\ttitle={tooltip}\n\t\t\tonClick={(event) => {\n\t\t\t\trest.onClick?.(event);\n\t\t\t\tif (event.defaultPrevented || isDisabled) return;\n\t\t\t\tonListeningChange(!isListening);\n\t\t\t}}\n\t\t\tclassName={cn(\n\t\t\t\t\"inline-flex h-9 w-9 items-center justify-center rounded-md border bg-background\",\n\t\t\t\t\"text-foreground transition-colors duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\"hover:bg-accent hover:text-accent-foreground\",\n\t\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\t\t\"disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\t\tisListening && \"animate-pulse border-destructive text-destructive\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t<svg\n\t\t\t\taria-hidden\n\t\t\t\tviewBox=\"0 0 16 16\"\n\t\t\t\twidth=\"14\"\n\t\t\t\theight=\"14\"\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t>\n\t\t\t\t<rect x=\"6\" y=\"2\" width=\"4\" height=\"8\" rx=\"2\" />\n\t\t\t\t<path d=\"M3.5 7.5a4.5 4.5 0 0 0 9 0\" />\n\t\t\t\t<path d=\"M8 12v2\" />\n\t\t\t\t<path d=\"M5.5 14h5\" />\n\t\t\t</svg>\n\t\t</button>\n\t);\n}\n\nexport { SpeechRecognition };\n"]}
package/dist/tag.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { TagProps_alias_1 as TagProps } from './_tsup-dts-rollup.js';
2
+ export { Tag_alias_1 as Tag } from './_tsup-dts-rollup.js';
3
+ export { tagVariants_alias_1 as tagVariants } from './_tsup-dts-rollup.js';
package/dist/tag.js ADDED
@@ -0,0 +1,107 @@
1
+ "use client";
2
+ import { cva } from 'class-variance-authority';
3
+ import * as React from 'react';
4
+ import { clsx } from 'clsx';
5
+ import { twMerge } from 'tailwind-merge';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ // src/primitives/tag/tag.tsx
9
+ function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }
12
+ var tagVariants = cva(
13
+ [
14
+ "inline-flex items-center gap-[var(--gap-xs,0.25rem)] rounded-full border px-2.5 py-0.5 text-xs font-medium",
15
+ "transition-all duration-[var(--duration-normal,200ms)] ease-out",
16
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
17
+ ].join(" "),
18
+ {
19
+ variants: {
20
+ variant: {
21
+ default: "border-transparent bg-primary text-primary-foreground",
22
+ secondary: "border-foreground/15 bg-secondary text-secondary-foreground hover:border-foreground/20",
23
+ destructive: "border-transparent bg-destructive text-destructive-foreground",
24
+ outline: "border-foreground/20 text-foreground hover:border-foreground/30"
25
+ }
26
+ },
27
+ defaultVariants: { variant: "default" }
28
+ }
29
+ );
30
+ function extractStringLabel(children) {
31
+ const parts = [];
32
+ const visit = (node) => {
33
+ if (node === null || node === void 0 || typeof node === "boolean") return;
34
+ if (typeof node === "string") {
35
+ parts.push(node);
36
+ return;
37
+ }
38
+ if (typeof node === "number") {
39
+ parts.push(String(node));
40
+ return;
41
+ }
42
+ if (Array.isArray(node)) {
43
+ for (const item of node) visit(item);
44
+ return;
45
+ }
46
+ if (React.isValidElement(node)) {
47
+ const props = node.props;
48
+ visit(props.children);
49
+ }
50
+ };
51
+ visit(children);
52
+ const joined = parts.join(" ").replace(/\s+/g, " ").trim();
53
+ return joined.length > 0 ? joined : null;
54
+ }
55
+ function Tag({
56
+ className,
57
+ variant,
58
+ icon,
59
+ onRemove,
60
+ removeLabel,
61
+ children,
62
+ ref,
63
+ ...props
64
+ }) {
65
+ const labelText = extractStringLabel(children);
66
+ const ariaLabel = removeLabel ?? (labelText ? `Remove ${labelText}` : "Remove");
67
+ return /* @__PURE__ */ jsxs("span", { ref, className: cn(tagVariants({ variant }), className), ...props, children: [
68
+ icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "-ml-0.5 [&_svg]:size-3 [&_svg]:shrink-0", children: icon }) : null,
69
+ /* @__PURE__ */ jsx("span", { children }),
70
+ onRemove ? /* @__PURE__ */ jsx(
71
+ "button",
72
+ {
73
+ type: "button",
74
+ onClick: onRemove,
75
+ "aria-label": ariaLabel,
76
+ className: cn(
77
+ "-mr-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full",
78
+ "transition-colors duration-[var(--duration-normal,200ms)] ease-out",
79
+ "hover:bg-foreground/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
80
+ "active:scale-[0.92]"
81
+ ),
82
+ children: /* @__PURE__ */ jsxs(
83
+ "svg",
84
+ {
85
+ xmlns: "http://www.w3.org/2000/svg",
86
+ viewBox: "0 0 24 24",
87
+ fill: "none",
88
+ stroke: "currentColor",
89
+ strokeWidth: "2.5",
90
+ strokeLinecap: "round",
91
+ strokeLinejoin: "round",
92
+ className: "size-3",
93
+ "aria-hidden": "true",
94
+ children: [
95
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
96
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
97
+ ]
98
+ }
99
+ )
100
+ }
101
+ ) : null
102
+ ] });
103
+ }
104
+
105
+ export { Tag, tagVariants };
106
+ //# sourceMappingURL=tag.js.map
107
+ //# sourceMappingURL=tag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/primitives/tag/tag.tsx"],"names":[],"mappings":";;;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACNA,IAAM,WAAA,GAAc,GAAA;AAAA,EACnB;AAAA,IACC,4GAAA;AAAA,IACA,iEAAA;AAAA,IACA;AAAA,GACD,CAAE,KAAK,GAAG,CAAA;AAAA,EACV;AAAA,IACC,QAAA,EAAU;AAAA,MACT,OAAA,EAAS;AAAA,QACR,OAAA,EAAS,uDAAA;AAAA,QACT,SAAA,EACC,wFAAA;AAAA,QACD,WAAA,EAAa,+DAAA;AAAA,QACb,OAAA,EAAS;AAAA;AACV,KACD;AAAA,IACA,eAAA,EAAiB,EAAE,OAAA,EAAS,SAAA;AAAU;AAExC;AA8BA,SAAS,mBAAmB,QAAA,EAA0C;AACrE,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,MAAM,KAAA,GAAQ,CAAC,IAAA,KAAgC;AAC9C,IAAA,IAAI,SAAS,IAAA,IAAQ,IAAA,KAAS,MAAA,IAAa,OAAO,SAAS,SAAA,EAAW;AACtE,IAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC7B,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AACf,MAAA;AAAA,IACD;AACA,IAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC7B,MAAA,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,IAAI,CAAC,CAAA;AACvB,MAAA;AAAA,IACD;AACA,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG;AACxB,MAAA,KAAA,MAAW,IAAA,IAAQ,IAAA,EAAM,KAAA,CAAM,IAAI,CAAA;AACnC,MAAA;AAAA,IACD;AACA,IAAA,IAAU,KAAA,CAAA,cAAA,CAAe,IAAI,CAAA,EAAG;AAC/B,MAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AACnB,MAAA,KAAA,CAAM,MAAM,QAAQ,CAAA;AAAA,IACrB;AAAA,EACD,CAAA;AACA,EAAA,KAAA,CAAM,QAAQ,CAAA;AACd,EAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,GAAG,EAAE,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA,CAAE,IAAA,EAAK;AACzD,EAAA,OAAO,MAAA,CAAO,MAAA,GAAS,CAAA,GAAI,MAAA,GAAS,IAAA;AACrC;AAqBA,SAAS,GAAA,CAAI;AAAA,EACZ,SAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAG;AACJ,CAAA,EAAa;AACZ,EAAA,MAAM,SAAA,GAAY,mBAAmB,QAAQ,CAAA;AAC7C,EAAA,MAAM,SAAA,GAAY,WAAA,KAAgB,SAAA,GAAY,CAAA,OAAA,EAAU,SAAS,CAAA,CAAA,GAAK,QAAA,CAAA;AAEtE,EAAA,uBACC,IAAA,CAAC,MAAA,EAAA,EAAK,GAAA,EAAU,SAAA,EAAW,EAAA,CAAG,WAAA,CAAY,EAAE,OAAA,EAAS,CAAA,EAAG,SAAS,CAAA,EAAI,GAAG,KAAA,EACtE,QAAA,EAAA;AAAA,IAAA,IAAA,uBACC,MAAA,EAAA,EAAK,aAAA,EAAY,QAAO,SAAA,EAAU,yCAAA,EACjC,gBACF,CAAA,GACG,IAAA;AAAA,oBACJ,GAAA,CAAC,UAAM,QAAA,EAAS,CAAA;AAAA,IACf,QAAA,mBACA,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACA,IAAA,EAAK,QAAA;AAAA,QACL,OAAA,EAAS,QAAA;AAAA,QACT,YAAA,EAAY,SAAA;AAAA,QACZ,SAAA,EAAW,EAAA;AAAA,UACV,+EAAA;AAAA,UACA,oEAAA;AAAA,UACA,4HAAA;AAAA,UACA;AAAA,SACD;AAAA,QAEA,QAAA,kBAAA,IAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACA,KAAA,EAAM,4BAAA;AAAA,YACN,OAAA,EAAQ,WAAA;AAAA,YACR,IAAA,EAAK,MAAA;AAAA,YACL,MAAA,EAAO,cAAA;AAAA,YACP,WAAA,EAAY,KAAA;AAAA,YACZ,aAAA,EAAc,OAAA;AAAA,YACd,cAAA,EAAe,OAAA;AAAA,YACf,SAAA,EAAU,QAAA;AAAA,YACV,aAAA,EAAY,MAAA;AAAA,YAEZ,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,IAAG,IAAA,EAAK,EAAA,EAAG,KAAI,EAAA,EAAG,GAAA,EAAI,IAAG,IAAA,EAAK,CAAA;AAAA,8BACpC,GAAA,CAAC,UAAK,EAAA,EAAG,GAAA,EAAI,IAAG,GAAA,EAAI,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK;AAAA;AAAA;AAAA;AACrC;AAAA,KACD,GACG;AAAA,GAAA,EACL,CAAA;AAEF","file":"tag.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","import { type VariantProps, cva } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\nconst tagVariants = cva(\n\t[\n\t\t\"inline-flex items-center gap-[var(--gap-xs,0.25rem)] rounded-full border px-2.5 py-0.5 text-xs font-medium\",\n\t\t\"transition-all duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t].join(\" \"),\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"border-transparent bg-primary text-primary-foreground\",\n\t\t\t\tsecondary:\n\t\t\t\t\t\"border-foreground/15 bg-secondary text-secondary-foreground hover:border-foreground/20\",\n\t\t\t\tdestructive: \"border-transparent bg-destructive text-destructive-foreground\",\n\t\t\t\toutline: \"border-foreground/20 text-foreground hover:border-foreground/30\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: { variant: \"default\" },\n\t},\n);\n\nexport interface TagProps\n\textends Omit<React.HTMLAttributes<HTMLSpanElement>, \"onRemove\">,\n\t\tVariantProps<typeof tagVariants> {\n\t/** Forwarded ref onto the root span element. */\n\tref?: React.Ref<HTMLSpanElement>;\n\t/** Optional leading icon (`<svg>` or component). Sized 12×12. */\n\ticon?: React.ReactNode;\n\t/**\n\t * Click handler for the close button. When provided, an inline ✕ button\n\t * is rendered after the children with an `aria-label` derived from the\n\t * children's string content (or a generic \"Remove\" if no string can be\n\t * extracted). Pass undefined for a non-interactive Tag — at that point,\n\t * prefer Badge directly.\n\t */\n\tonRemove?: () => void;\n\t/** Override the auto-derived `aria-label` on the close button. */\n\tremoveLabel?: string;\n}\n\n/**\n * Walk a `React.ReactNode` tree depth-first and collect all string +\n * number leaves into a single space-separated label. Used to derive\n * the close button's `aria-label` even when children are JSX\n * (`<strong>Bold</strong>` → `\"Bold\"`).\n *\n * @param children - React children passed to `<Tag>`.\n * @returns Concatenated string content, or null if no string leaves found.\n */\nfunction extractStringLabel(children: React.ReactNode): string | null {\n\tconst parts: string[] = [];\n\tconst visit = (node: React.ReactNode): void => {\n\t\tif (node === null || node === undefined || typeof node === \"boolean\") return;\n\t\tif (typeof node === \"string\") {\n\t\t\tparts.push(node);\n\t\t\treturn;\n\t\t}\n\t\tif (typeof node === \"number\") {\n\t\t\tparts.push(String(node));\n\t\t\treturn;\n\t\t}\n\t\tif (Array.isArray(node)) {\n\t\t\tfor (const item of node) visit(item);\n\t\t\treturn;\n\t\t}\n\t\tif (React.isValidElement(node)) {\n\t\t\tconst props = node.props as { children?: React.ReactNode };\n\t\t\tvisit(props.children);\n\t\t}\n\t};\n\tvisit(children);\n\tconst joined = parts.join(\" \").replace(/\\s+/g, \" \").trim();\n\treturn joined.length > 0 ? joined : null;\n}\n\n/**\n * An interactive tag / chip primitive — Badge with an optional dismiss\n * affordance. Mirrors {@link Badge}'s CVA variants so the visual sibling\n * is obvious; adds a built-in close button when `onRemove` is provided.\n *\n * For non-interactive labels (status indicators, counts) use {@link Badge}\n * directly. For \"click to filter\" state-bearing chips, use Toggle or\n * ToggleGroup — Tag is for \"this token represents a value the user can\n * dismiss\" (filters, multi-select selections, draft attachments).\n *\n * @example\n * ```tsx\n * <Tag variant=\"secondary\" onRemove={() => removeFilter(\"urgent\")}>\n * Urgent\n * </Tag>\n * ```\n *\n * @returns A span containing the label + optional icon + optional close button.\n */\nfunction Tag({\n\tclassName,\n\tvariant,\n\ticon,\n\tonRemove,\n\tremoveLabel,\n\tchildren,\n\tref,\n\t...props\n}: TagProps) {\n\tconst labelText = extractStringLabel(children);\n\tconst ariaLabel = removeLabel ?? (labelText ? `Remove ${labelText}` : \"Remove\");\n\n\treturn (\n\t\t<span ref={ref} className={cn(tagVariants({ variant }), className)} {...props}>\n\t\t\t{icon ? (\n\t\t\t\t<span aria-hidden=\"true\" className=\"-ml-0.5 [&_svg]:size-3 [&_svg]:shrink-0\">\n\t\t\t\t\t{icon}\n\t\t\t\t</span>\n\t\t\t) : null}\n\t\t\t<span>{children}</span>\n\t\t\t{onRemove ? (\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={onRemove}\n\t\t\t\t\taria-label={ariaLabel}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"-mr-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full\",\n\t\t\t\t\t\t\"transition-colors duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\t\t\"hover:bg-foreground/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n\t\t\t\t\t\t\"active:scale-[0.92]\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t<svg\n\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\tstrokeWidth=\"2.5\"\n\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\tclassName=\"size-3\"\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n\t\t\t\t\t\t<line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n\t\t\t\t\t</svg>\n\t\t\t\t</button>\n\t\t\t) : null}\n\t\t</span>\n\t);\n}\n\nexport { Tag, tagVariants };\n"]}
@@ -0,0 +1,8 @@
1
+ export { ToolbarProps_alias_1 as ToolbarProps } from './_tsup-dts-rollup.js';
2
+ export { Toolbar_alias_1 as Toolbar } from './_tsup-dts-rollup.js';
3
+ export { ToolbarButton_alias_1 as ToolbarButton } from './_tsup-dts-rollup.js';
4
+ export { ToolbarLink_alias_1 as ToolbarLink } from './_tsup-dts-rollup.js';
5
+ export { ToolbarSeparator_alias_1 as ToolbarSeparator } from './_tsup-dts-rollup.js';
6
+ export { ToolbarToggleGroup_alias_1 as ToolbarToggleGroup } from './_tsup-dts-rollup.js';
7
+ export { ToolbarToggleItem_alias_1 as ToolbarToggleItem } from './_tsup-dts-rollup.js';
8
+ export { toolbarVariants_alias_1 as toolbarVariants } from './_tsup-dts-rollup.js';
@@ -0,0 +1,120 @@
1
+ "use client";
2
+ import * as ToolbarPrimitive from '@radix-ui/react-toolbar';
3
+ import { cva } from 'class-variance-authority';
4
+ import { clsx } from 'clsx';
5
+ import { twMerge } from 'tailwind-merge';
6
+ import { jsx } from 'react/jsx-runtime';
7
+
8
+ function cn(...inputs) {
9
+ return twMerge(clsx(inputs));
10
+ }
11
+ var toolbarVariants = cva("flex items-center gap-[var(--gap-xs,0.25rem)] rounded-md border border-border bg-card p-[var(--space-1,0.25rem)]", {
12
+ variants: {
13
+ orientation: {
14
+ horizontal: "flex-row",
15
+ vertical: "flex-col items-stretch"
16
+ }
17
+ },
18
+ defaultVariants: { orientation: "horizontal" }
19
+ });
20
+ var toolbarItemBaseClasses = [
21
+ "inline-flex items-center justify-center gap-[var(--gap-xs,0.25rem)] rounded-sm",
22
+ "px-[var(--space-2,0.5rem)] h-[var(--control-height-sm,2.25rem)] text-sm font-medium",
23
+ "text-foreground/80 hover:text-foreground hover:bg-accent",
24
+ "transition-colors duration-[var(--duration-normal,200ms)] ease-out",
25
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
26
+ "disabled:pointer-events-none disabled:opacity-50",
27
+ "data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
28
+ "active:scale-[0.98]",
29
+ "[&_svg]:size-4 [&_svg]:shrink-0"
30
+ ].join(" ");
31
+ function Toolbar({ className, orientation = "horizontal", ref, ...props }) {
32
+ return /* @__PURE__ */ jsx(
33
+ ToolbarPrimitive.Root,
34
+ {
35
+ ref,
36
+ orientation,
37
+ className: cn(toolbarVariants({ orientation }), className),
38
+ ...props
39
+ }
40
+ );
41
+ }
42
+ function ToolbarButton({
43
+ className,
44
+ ref,
45
+ ...props
46
+ }) {
47
+ return /* @__PURE__ */ jsx(
48
+ ToolbarPrimitive.Button,
49
+ {
50
+ ref,
51
+ className: cn(toolbarItemBaseClasses, className),
52
+ ...props
53
+ }
54
+ );
55
+ }
56
+ function ToolbarLink({
57
+ className,
58
+ ref,
59
+ ...props
60
+ }) {
61
+ return /* @__PURE__ */ jsx(
62
+ ToolbarPrimitive.Link,
63
+ {
64
+ ref,
65
+ className: cn(toolbarItemBaseClasses, "underline-offset-4 hover:underline", className),
66
+ ...props
67
+ }
68
+ );
69
+ }
70
+ function ToolbarToggleGroup({
71
+ className,
72
+ ref,
73
+ ...props
74
+ }) {
75
+ return /* @__PURE__ */ jsx(
76
+ ToolbarPrimitive.ToggleGroup,
77
+ {
78
+ ref,
79
+ className: cn("flex items-center gap-[var(--gap-xs,0.25rem)]", className),
80
+ ...props
81
+ }
82
+ );
83
+ }
84
+ function ToolbarToggleItem({
85
+ className,
86
+ ref,
87
+ ...props
88
+ }) {
89
+ return /* @__PURE__ */ jsx(
90
+ ToolbarPrimitive.ToggleItem,
91
+ {
92
+ ref,
93
+ className: cn(toolbarItemBaseClasses, className),
94
+ ...props
95
+ }
96
+ );
97
+ }
98
+ function ToolbarSeparator({
99
+ className,
100
+ ref,
101
+ ...props
102
+ }) {
103
+ return /* @__PURE__ */ jsx(
104
+ ToolbarPrimitive.Separator,
105
+ {
106
+ ref,
107
+ className: cn(
108
+ "shrink-0 bg-border",
109
+ "data-[orientation=horizontal]:h-4 data-[orientation=horizontal]:w-px data-[orientation=horizontal]:mx-[var(--space-1,0.25rem)]",
110
+ "data-[orientation=vertical]:w-4 data-[orientation=vertical]:h-px data-[orientation=vertical]:my-[var(--space-1,0.25rem)]",
111
+ className
112
+ ),
113
+ ...props
114
+ }
115
+ );
116
+ }
117
+
118
+ export { Toolbar, ToolbarButton, ToolbarLink, ToolbarSeparator, ToolbarToggleGroup, ToolbarToggleItem, toolbarVariants };
119
+ //# sourceMappingURL=toolbar.js.map
120
+ //# sourceMappingURL=toolbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/components/toolbar/toolbar.tsx"],"names":[],"mappings":"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","file":"toolbar.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as ToolbarPrimitive from \"@radix-ui/react-toolbar\";\nimport { cva } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\nconst toolbarVariants = cva(\"flex items-center gap-[var(--gap-xs,0.25rem)] rounded-md border border-border bg-card p-[var(--space-1,0.25rem)]\", {\n\tvariants: {\n\t\torientation: {\n\t\t\thorizontal: \"flex-row\",\n\t\t\tvertical: \"flex-col items-stretch\",\n\t\t},\n\t},\n\tdefaultVariants: { orientation: \"horizontal\" },\n});\n\nconst toolbarItemBaseClasses = [\n\t\"inline-flex items-center justify-center gap-[var(--gap-xs,0.25rem)] rounded-sm\",\n\t\"px-[var(--space-2,0.5rem)] h-[var(--control-height-sm,2.25rem)] text-sm font-medium\",\n\t\"text-foreground/80 hover:text-foreground hover:bg-accent\",\n\t\"transition-colors duration-[var(--duration-normal,200ms)] ease-out\",\n\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n\t\"disabled:pointer-events-none disabled:opacity-50\",\n\t\"data-[state=on]:bg-accent data-[state=on]:text-accent-foreground\",\n\t\"active:scale-[0.98]\",\n\t\"[&_svg]:size-4 [&_svg]:shrink-0\",\n].join(\" \");\n\n/**\n * Toolbar root props. `aria-label` is required — Radix Toolbar.Root\n * mounts as a `role=\"toolbar\"` landmark, and AT users will hear an\n * unlabelled \"toolbar\" landmark when no visible heading sits adjacent.\n * If a visible heading IS present, pair it via `aria-labelledby` instead.\n */\nexport interface ToolbarProps\n\textends Omit<\n\t\tReact.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Root>,\n\t\t\"aria-label\"\n\t> {\n\t/** Forwarded ref onto the Radix `Toolbar.Root`. */\n\tref?: React.Ref<React.ElementRef<typeof ToolbarPrimitive.Root>>;\n\t/** Required accessible name for the toolbar landmark. */\n\t\"aria-label\": string;\n}\n\n/**\n * Root toolbar element. Wraps Radix `Toolbar.Root` with the canonical\n * Hex Core token + visual styling. Pass children consisting of\n * `ToolbarButton`, `ToolbarToggleGroup`, `ToolbarSeparator`, and\n * `ToolbarLink` — Radix handles arrow-key roving focus across them\n * automatically.\n *\n * @example\n * ```tsx\n * <Toolbar aria-label=\"Editor controls\">\n * <ToolbarButton onClick={onUndo}>Undo</ToolbarButton>\n * <ToolbarButton onClick={onRedo}>Redo</ToolbarButton>\n * <ToolbarSeparator />\n * <ToolbarToggleGroup type=\"single\" defaultValue=\"left\">\n * <ToolbarToggleItem value=\"left\">Left</ToolbarToggleItem>\n * <ToolbarToggleItem value=\"center\">Center</ToolbarToggleItem>\n * <ToolbarToggleItem value=\"right\">Right</ToolbarToggleItem>\n * </ToolbarToggleGroup>\n * </Toolbar>\n * ```\n */\nfunction Toolbar({ className, orientation = \"horizontal\", ref, ...props }: ToolbarProps) {\n\treturn (\n\t\t<ToolbarPrimitive.Root\n\t\t\tref={ref}\n\t\t\torientation={orientation}\n\t\t\tclassName={cn(toolbarVariants({ orientation }), className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\n/** A push button inside the toolbar. */\nfunction ToolbarButton({\n\tclassName,\n\tref,\n\t...props\n}: React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Button> & {\n\tref?: React.Ref<React.ElementRef<typeof ToolbarPrimitive.Button>>;\n}) {\n\treturn (\n\t\t<ToolbarPrimitive.Button\n\t\t\tref={ref}\n\t\t\tclassName={cn(toolbarItemBaseClasses, className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\n/** A link inside the toolbar — renders an `<a>` with toolbar focus semantics. */\nfunction ToolbarLink({\n\tclassName,\n\tref,\n\t...props\n}: React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Link> & {\n\tref?: React.Ref<React.ElementRef<typeof ToolbarPrimitive.Link>>;\n}) {\n\treturn (\n\t\t<ToolbarPrimitive.Link\n\t\t\tref={ref}\n\t\t\tclassName={cn(toolbarItemBaseClasses, \"underline-offset-4 hover:underline\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\n/** A group of mutually-exclusive (`type='single'`) or independent (`type='multiple'`) toggle items. */\nfunction ToolbarToggleGroup({\n\tclassName,\n\tref,\n\t...props\n}: React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.ToggleGroup> & {\n\tref?: React.Ref<React.ElementRef<typeof ToolbarPrimitive.ToggleGroup>>;\n}) {\n\treturn (\n\t\t<ToolbarPrimitive.ToggleGroup\n\t\t\tref={ref}\n\t\t\tclassName={cn(\"flex items-center gap-[var(--gap-xs,0.25rem)]\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\n/** Individual toggle item — exposes `data-state=\"on\"` for the on style. */\nfunction ToolbarToggleItem({\n\tclassName,\n\tref,\n\t...props\n}: React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.ToggleItem> & {\n\tref?: React.Ref<React.ElementRef<typeof ToolbarPrimitive.ToggleItem>>;\n}) {\n\treturn (\n\t\t<ToolbarPrimitive.ToggleItem\n\t\t\tref={ref}\n\t\t\tclassName={cn(toolbarItemBaseClasses, className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\n/** A vertical (or horizontal, in vertical toolbars) divider. */\nfunction ToolbarSeparator({\n\tclassName,\n\tref,\n\t...props\n}: React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Separator> & {\n\tref?: React.Ref<React.ElementRef<typeof ToolbarPrimitive.Separator>>;\n}) {\n\treturn (\n\t\t<ToolbarPrimitive.Separator\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\t\"shrink-0 bg-border\",\n\t\t\t\t\"data-[orientation=horizontal]:h-4 data-[orientation=horizontal]:w-px data-[orientation=horizontal]:mx-[var(--space-1,0.25rem)]\",\n\t\t\t\t\"data-[orientation=vertical]:w-4 data-[orientation=vertical]:h-px data-[orientation=vertical]:my-[var(--space-1,0.25rem)]\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nexport {\n\tToolbar,\n\tToolbarButton,\n\tToolbarLink,\n\tToolbarSeparator,\n\tToolbarToggleGroup,\n\tToolbarToggleItem,\n\ttoolbarVariants,\n};\n"]}
package/dist/tree.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { TreeNode_alias_1 as TreeNode } from './_tsup-dts-rollup.js';
2
+ export { TreeProps_alias_1 as TreeProps } from './_tsup-dts-rollup.js';
3
+ export { Tree_alias_1 as Tree } from './_tsup-dts-rollup.js';