@hex-core/components 1.5.0 → 1.6.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/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":"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","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';
package/dist/tree.js ADDED
@@ -0,0 +1,275 @@
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 flattenVisible(nodes, expanded) {
11
+ const out = [];
12
+ function walk(items, depth, parentId) {
13
+ for (const node of items) {
14
+ out.push({ node, depth, parentId });
15
+ if (node.children && expanded.has(node.id)) {
16
+ walk(node.children, depth + 1, node.id);
17
+ }
18
+ }
19
+ }
20
+ walk(nodes, 0, null);
21
+ return out;
22
+ }
23
+ function Tree({
24
+ data,
25
+ defaultExpanded,
26
+ expanded: expandedProp,
27
+ onExpandedChange,
28
+ selected: selectedProp,
29
+ onSelect,
30
+ "aria-label": ariaLabel,
31
+ className,
32
+ ref
33
+ }) {
34
+ const [internalExpanded, setInternalExpanded] = React.useState(
35
+ () => new Set(defaultExpanded ?? [])
36
+ );
37
+ const expanded = React.useMemo(
38
+ () => expandedProp ? new Set(expandedProp) : internalExpanded,
39
+ [expandedProp, internalExpanded]
40
+ );
41
+ const [internalSelected, setInternalSelected] = React.useState(null);
42
+ const selected = selectedProp ?? internalSelected;
43
+ const visible = React.useMemo(() => flattenVisible(data, expanded), [data, expanded]);
44
+ const [focusedId, setFocusedId] = React.useState(
45
+ () => visible[0]?.node.id ?? null
46
+ );
47
+ React.useEffect(() => {
48
+ if (focusedId && !visible.some((row) => row.node.id === focusedId)) {
49
+ setFocusedId(visible[0]?.node.id ?? null);
50
+ }
51
+ }, [visible, focusedId]);
52
+ const setExpandedSet = React.useCallback(
53
+ (next) => {
54
+ if (expandedProp) {
55
+ onExpandedChange?.([...next]);
56
+ } else {
57
+ setInternalExpanded(next);
58
+ onExpandedChange?.([...next]);
59
+ }
60
+ },
61
+ [expandedProp, onExpandedChange]
62
+ );
63
+ const toggleExpand = React.useCallback(
64
+ (id) => {
65
+ const next = new Set(expanded);
66
+ if (next.has(id)) next.delete(id);
67
+ else next.add(id);
68
+ setExpandedSet(next);
69
+ },
70
+ [expanded, setExpandedSet]
71
+ );
72
+ const activate = React.useCallback(
73
+ (id) => {
74
+ if (selectedProp === void 0) setInternalSelected(id);
75
+ onSelect?.(id);
76
+ },
77
+ [selectedProp, onSelect]
78
+ );
79
+ const handleKeyDown = React.useCallback(
80
+ (e) => {
81
+ if (!focusedId) return;
82
+ const idx = visible.findIndex((row2) => row2.node.id === focusedId);
83
+ if (idx < 0) return;
84
+ const row = visible[idx];
85
+ if (!row) return;
86
+ const node = row.node;
87
+ const isParent = !!node.children;
88
+ const isExpanded = expanded.has(node.id);
89
+ switch (e.key) {
90
+ case "ArrowDown": {
91
+ e.preventDefault();
92
+ const next = visible[idx + 1];
93
+ if (next) setFocusedId(next.node.id);
94
+ break;
95
+ }
96
+ case "ArrowUp": {
97
+ e.preventDefault();
98
+ const prev = visible[idx - 1];
99
+ if (prev) setFocusedId(prev.node.id);
100
+ break;
101
+ }
102
+ case "ArrowRight": {
103
+ e.preventDefault();
104
+ if (isParent && !isExpanded) toggleExpand(node.id);
105
+ else if (isParent && isExpanded) {
106
+ const next = visible[idx + 1];
107
+ if (next) setFocusedId(next.node.id);
108
+ }
109
+ break;
110
+ }
111
+ case "ArrowLeft": {
112
+ e.preventDefault();
113
+ if (isParent && isExpanded) toggleExpand(node.id);
114
+ else if (row.parentId) {
115
+ setFocusedId(row.parentId);
116
+ }
117
+ break;
118
+ }
119
+ case "Home": {
120
+ e.preventDefault();
121
+ if (visible[0]) setFocusedId(visible[0].node.id);
122
+ break;
123
+ }
124
+ case "End": {
125
+ e.preventDefault();
126
+ const last = visible[visible.length - 1];
127
+ if (last) setFocusedId(last.node.id);
128
+ break;
129
+ }
130
+ case "Enter":
131
+ case " ": {
132
+ e.preventDefault();
133
+ if (node.disabled) return;
134
+ if (isParent) toggleExpand(node.id);
135
+ activate(node.id);
136
+ break;
137
+ }
138
+ default:
139
+ return;
140
+ }
141
+ },
142
+ [focusedId, visible, expanded, toggleExpand, activate]
143
+ );
144
+ const isSelectable = onSelect !== void 0 || selectedProp !== void 0;
145
+ return /* @__PURE__ */ jsx(
146
+ "ul",
147
+ {
148
+ ref,
149
+ role: "tree",
150
+ "aria-label": ariaLabel,
151
+ className: cn("flex flex-col text-sm text-foreground", className),
152
+ onKeyDown: handleKeyDown,
153
+ children: data.map((node) => /* @__PURE__ */ jsx(
154
+ TreeItem,
155
+ {
156
+ node,
157
+ depth: 0,
158
+ expanded,
159
+ selected,
160
+ isSelectable,
161
+ focusedId,
162
+ onFocus: setFocusedId,
163
+ onToggleExpand: toggleExpand,
164
+ onActivate: activate
165
+ },
166
+ node.id
167
+ ))
168
+ }
169
+ );
170
+ }
171
+ function TreeItem({
172
+ node,
173
+ depth,
174
+ expanded,
175
+ selected,
176
+ isSelectable,
177
+ focusedId,
178
+ onFocus,
179
+ onToggleExpand,
180
+ onActivate
181
+ }) {
182
+ const children = node.children;
183
+ const isParent = children !== void 0;
184
+ const isExpanded = expanded.has(node.id);
185
+ const isSelected = selected === node.id;
186
+ const isFocused = focusedId === node.id;
187
+ const handleClick = () => {
188
+ if (node.disabled) return;
189
+ onFocus(node.id);
190
+ if (isParent) onToggleExpand(node.id);
191
+ onActivate(node.id);
192
+ };
193
+ const labelId = React.useId();
194
+ return /* @__PURE__ */ jsxs(
195
+ "li",
196
+ {
197
+ role: "treeitem",
198
+ "aria-labelledby": labelId,
199
+ "aria-level": depth + 1,
200
+ "aria-expanded": isParent ? isExpanded : void 0,
201
+ "aria-selected": isSelectable ? isSelected : void 0,
202
+ "aria-disabled": node.disabled || void 0,
203
+ tabIndex: isFocused ? 0 : -1,
204
+ onClick: (e) => {
205
+ e.stopPropagation();
206
+ handleClick();
207
+ },
208
+ onFocus: (e) => {
209
+ e.stopPropagation();
210
+ if (!node.disabled) onFocus(node.id);
211
+ },
212
+ className: cn(
213
+ "outline-none rounded-sm",
214
+ // H1: focus-visible-driven ring (NOT state-driven) — the ring
215
+ // only shows on keyboard focus, not on mouse clicks.
216
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
217
+ ),
218
+ children: [
219
+ /* @__PURE__ */ jsxs(
220
+ "div",
221
+ {
222
+ className: cn(
223
+ "flex cursor-pointer select-none items-center gap-[var(--gap-xs,0.25rem)] rounded-sm px-[var(--space-2,0.5rem)] py-[var(--space-1,0.25rem)]",
224
+ "transition-colors duration-[var(--duration-normal,200ms)] ease-out",
225
+ "hover:bg-accent hover:text-accent-foreground",
226
+ isSelected && "bg-accent text-accent-foreground font-medium",
227
+ node.disabled && "cursor-not-allowed opacity-50"
228
+ ),
229
+ style: { paddingLeft: `calc(${depth} * var(--space-4, 1rem) + var(--space-2, 0.5rem))` },
230
+ children: [
231
+ isParent ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-flex h-4 w-4 shrink-0 items-center justify-center", children: /* @__PURE__ */ jsx(
232
+ "svg",
233
+ {
234
+ xmlns: "http://www.w3.org/2000/svg",
235
+ viewBox: "0 0 24 24",
236
+ fill: "none",
237
+ stroke: "currentColor",
238
+ strokeWidth: "2",
239
+ strokeLinecap: "round",
240
+ strokeLinejoin: "round",
241
+ className: cn(
242
+ "h-3 w-3 transition-transform duration-[var(--duration-normal,200ms)] ease-out",
243
+ isExpanded ? "rotate-90" : "rotate-0"
244
+ ),
245
+ children: /* @__PURE__ */ jsx("polyline", { points: "9 18 15 12 9 6" })
246
+ }
247
+ ) }) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-block h-4 w-4 shrink-0" }),
248
+ node.icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-flex h-4 w-4 shrink-0 items-center justify-center [&_svg]:size-4", children: node.icon }) : null,
249
+ /* @__PURE__ */ jsx("span", { id: labelId, className: "truncate", children: node.label })
250
+ ]
251
+ }
252
+ ),
253
+ isParent && isExpanded && children ? /* @__PURE__ */ jsx("ul", { role: "group", "aria-labelledby": labelId, className: "flex flex-col", children: children.map((child) => /* @__PURE__ */ jsx(
254
+ TreeItem,
255
+ {
256
+ node: child,
257
+ depth: depth + 1,
258
+ expanded,
259
+ selected,
260
+ isSelectable,
261
+ focusedId,
262
+ onFocus,
263
+ onToggleExpand,
264
+ onActivate
265
+ },
266
+ child.id
267
+ )) }) : null
268
+ ]
269
+ }
270
+ );
271
+ }
272
+
273
+ export { Tree };
274
+ //# sourceMappingURL=tree.js.map
275
+ //# sourceMappingURL=tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/components/tree/tree.tsx"],"names":["row"],"mappings":"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","file":"tree.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 * One node in a Tree. Generic — the shape is content-agnostic so consumers\n * can render org charts, taxonomy pickers, navigation trees, etc.\n *\n * For filesystem-shaped data (with file-extension icon logic baked in),\n * prefer {@link FileTree} instead.\n */\nexport interface TreeNode {\n\t/** Stable unique id used as React key + ARIA target. */\n\tid: string;\n\t/** Display name. */\n\tlabel: React.ReactNode;\n\t/** Nested children. Presence (even empty array) marks the node as a parent. */\n\tchildren?: TreeNode[];\n\t/** Optional icon (default: a chevron + dot). */\n\ticon?: React.ReactNode;\n\t/** Disable selection + expand toggle. */\n\tdisabled?: boolean;\n}\n\nexport interface TreeProps {\n\t/** Forwarded ref onto the root `<ul role=\"tree\">`. */\n\tref?: React.Ref<HTMLUListElement>;\n\t/** Root nodes. */\n\tdata: TreeNode[];\n\t/** Uncontrolled initial expanded ids. */\n\tdefaultExpanded?: string[];\n\t/** Controlled expanded ids. */\n\texpanded?: string[];\n\t/** Fired when expanded set changes (array of ids). */\n\tonExpandedChange?: (ids: string[]) => void;\n\t/** Controlled selected node id. */\n\tselected?: string;\n\t/** Fired when the user activates a node (click, Enter, or Space). */\n\tonSelect?: (id: string) => void;\n\t/** Required accessible name. */\n\t\"aria-label\": string;\n\t/** Optional additional class names. */\n\tclassName?: string;\n}\n\n/** Recursively flatten a tree into the visible-row order (respecting collapsed parents). */\nfunction flattenVisible(nodes: TreeNode[], expanded: Set<string>): Array<{ node: TreeNode; depth: number; parentId: string | null }> {\n\tconst out: Array<{ node: TreeNode; depth: number; parentId: string | null }> = [];\n\tfunction walk(items: TreeNode[], depth: number, parentId: string | null) {\n\t\tfor (const node of items) {\n\t\t\tout.push({ node, depth, parentId });\n\t\t\tif (node.children && expanded.has(node.id)) {\n\t\t\t\twalk(node.children, depth + 1, node.id);\n\t\t\t}\n\t\t}\n\t}\n\twalk(nodes, 0, null);\n\treturn out;\n}\n\n/**\n * Generic hierarchical list with roving-tabindex keyboard navigation —\n * arrow keys move focus, → expands, ← collapses, Home / End jump to\n * first / last visible row, Enter / Space activates the focused node.\n *\n * Distinct from {@link FileTree} (which adds folder/file icon-by-extension\n * logic baked in) and {@link Accordion} (two-level groupings without\n * item-selection semantics).\n *\n * @example\n * ```tsx\n * <Tree\n * aria-label=\"Org chart\"\n * data={[\n * { id: \"ceo\", label: \"CEO\", children: [\n * { id: \"cto\", label: \"CTO\", children: [\n * { id: \"eng-lead\", label: \"Eng Lead\" },\n * ]},\n * { id: \"cmo\", label: \"CMO\" },\n * ]},\n * ]}\n * defaultExpanded={[\"ceo\"]}\n * onSelect={(id) => console.log(id)}\n * />\n * ```\n */\nfunction Tree({\n\tdata,\n\tdefaultExpanded,\n\texpanded: expandedProp,\n\tonExpandedChange,\n\tselected: selectedProp,\n\tonSelect,\n\t\"aria-label\": ariaLabel,\n\tclassName,\n\tref,\n}: TreeProps) {\n\tconst [internalExpanded, setInternalExpanded] = React.useState<Set<string>>(\n\t\t() => new Set(defaultExpanded ?? []),\n\t);\n\tconst expanded = React.useMemo(\n\t\t() => (expandedProp ? new Set(expandedProp) : internalExpanded),\n\t\t[expandedProp, internalExpanded],\n\t);\n\n\t\tconst [internalSelected, setInternalSelected] = React.useState<string | null>(null);\n\t\tconst selected = selectedProp ?? internalSelected;\n\n\t\tconst visible = React.useMemo(() => flattenVisible(data, expanded), [data, expanded]);\n\n\t\tconst [focusedId, setFocusedId] = React.useState<string | null>(\n\t\t\t() => visible[0]?.node.id ?? null,\n\t\t);\n\n\t\t// If the focused node disappeared (parent collapsed), retarget to the parent.\n\t\tReact.useEffect(() => {\n\t\t\tif (focusedId && !visible.some((row) => row.node.id === focusedId)) {\n\t\t\t\tsetFocusedId(visible[0]?.node.id ?? null);\n\t\t\t}\n\t\t}, [visible, focusedId]);\n\n\t\tconst setExpandedSet = React.useCallback(\n\t\t\t(next: Set<string>) => {\n\t\t\t\tif (expandedProp) {\n\t\t\t\t\tonExpandedChange?.([...next]);\n\t\t\t\t} else {\n\t\t\t\t\tsetInternalExpanded(next);\n\t\t\t\t\tonExpandedChange?.([...next]);\n\t\t\t\t}\n\t\t\t},\n\t\t\t[expandedProp, onExpandedChange],\n\t\t);\n\n\t\tconst toggleExpand = React.useCallback(\n\t\t\t(id: string) => {\n\t\t\t\tconst next = new Set(expanded);\n\t\t\t\tif (next.has(id)) next.delete(id);\n\t\t\t\telse next.add(id);\n\t\t\t\tsetExpandedSet(next);\n\t\t\t},\n\t\t\t[expanded, setExpandedSet],\n\t\t);\n\n\t\tconst activate = React.useCallback(\n\t\t\t(id: string) => {\n\t\t\t\tif (selectedProp === undefined) setInternalSelected(id);\n\t\t\t\tonSelect?.(id);\n\t\t\t},\n\t\t\t[selectedProp, onSelect],\n\t\t);\n\n\t\tconst handleKeyDown = React.useCallback(\n\t\t\t(e: React.KeyboardEvent<HTMLUListElement>) => {\n\t\t\t\tif (!focusedId) return;\n\t\t\t\tconst idx = visible.findIndex((row) => row.node.id === focusedId);\n\t\t\t\tif (idx < 0) return;\n\t\t\t\tconst row = visible[idx];\n\t\t\t\tif (!row) return;\n\t\t\t\tconst node = row.node;\n\t\t\t\tconst isParent = !!node.children;\n\t\t\t\tconst isExpanded = expanded.has(node.id);\n\n\t\t\t\tswitch (e.key) {\n\t\t\t\t\tcase \"ArrowDown\": {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tconst next = visible[idx + 1];\n\t\t\t\t\t\tif (next) setFocusedId(next.node.id);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"ArrowUp\": {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tconst prev = visible[idx - 1];\n\t\t\t\t\t\tif (prev) setFocusedId(prev.node.id);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"ArrowRight\": {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tif (isParent && !isExpanded) toggleExpand(node.id);\n\t\t\t\t\t\telse if (isParent && isExpanded) {\n\t\t\t\t\t\t\tconst next = visible[idx + 1];\n\t\t\t\t\t\t\tif (next) setFocusedId(next.node.id);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"ArrowLeft\": {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tif (isParent && isExpanded) toggleExpand(node.id);\n\t\t\t\t\t\telse if (row.parentId) {\n\t\t\t\t\t\t\tsetFocusedId(row.parentId);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"Home\": {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tif (visible[0]) setFocusedId(visible[0].node.id);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"End\": {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tconst last = visible[visible.length - 1];\n\t\t\t\t\t\tif (last) setFocusedId(last.node.id);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"Enter\":\n\t\t\t\t\tcase \" \": {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tif (node.disabled) return;\n\t\t\t\t\t\tif (isParent) toggleExpand(node.id);\n\t\t\t\t\t\tactivate(node.id);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t},\n\t\t\t[focusedId, visible, expanded, toggleExpand, activate],\n\t\t);\n\n\t// H3: emit boolean aria-selected when selection is wired (so AT can\n\t// announce \"1 of N, not selected\" on non-selected items); leave it\n\t// undefined entirely when nothing on the tree is selectable.\n\tconst isSelectable = onSelect !== undefined || selectedProp !== undefined;\n\n\treturn (\n\t\t<ul\n\t\t\tref={ref}\n\t\t\trole=\"tree\"\n\t\t\taria-label={ariaLabel}\n\t\t\tclassName={cn(\"flex flex-col text-sm text-foreground\", className)}\n\t\t\tonKeyDown={handleKeyDown}\n\t\t>\n\t\t\t{data.map((node) => (\n\t\t\t\t<TreeItem\n\t\t\t\t\tkey={node.id}\n\t\t\t\t\tnode={node}\n\t\t\t\t\tdepth={0}\n\t\t\t\t\texpanded={expanded}\n\t\t\t\t\tselected={selected}\n\t\t\t\t\tisSelectable={isSelectable}\n\t\t\t\t\tfocusedId={focusedId}\n\t\t\t\t\tonFocus={setFocusedId}\n\t\t\t\t\tonToggleExpand={toggleExpand}\n\t\t\t\t\tonActivate={activate}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</ul>\n\t);\n}\n\ninterface TreeItemProps {\n\tnode: TreeNode;\n\tdepth: number;\n\texpanded: Set<string>;\n\tselected: string | null;\n\tisSelectable: boolean;\n\tfocusedId: string | null;\n\tonFocus: (id: string) => void;\n\tonToggleExpand: (id: string) => void;\n\tonActivate: (id: string) => void;\n}\n\n/** One row inside a Tree. Recurses through children when expanded. */\nfunction TreeItem({\n\tnode,\n\tdepth,\n\texpanded,\n\tselected,\n\tisSelectable,\n\tfocusedId,\n\tonFocus,\n\tonToggleExpand,\n\tonActivate,\n}: TreeItemProps) {\n\tconst children = node.children;\n\tconst isParent = children !== undefined;\n\tconst isExpanded = expanded.has(node.id);\n\tconst isSelected = selected === node.id;\n\tconst isFocused = focusedId === node.id;\n\n\tconst handleClick = () => {\n\t\tif (node.disabled) return;\n\t\tonFocus(node.id);\n\t\tif (isParent) onToggleExpand(node.id);\n\t\tonActivate(node.id);\n\t};\n\n\tconst labelId = React.useId();\n\treturn (\n\t\t<li\n\t\t\trole=\"treeitem\"\n\t\t\taria-labelledby={labelId}\n\t\t\taria-level={depth + 1}\n\t\t\taria-expanded={isParent ? isExpanded : undefined}\n\t\t\t// H3: when the tree is selectable (caller wired onSelect or\n\t\t\t// selected), emit boolean aria-selected on every item — AT\n\t\t\t// announces \"1 of N, not selected\" instead of dropping the\n\t\t\t// attribute entirely. When nothing is selectable, leave it off.\n\t\t\taria-selected={isSelectable ? isSelected : undefined}\n\t\t\taria-disabled={node.disabled || undefined}\n\t\t\ttabIndex={isFocused ? 0 : -1}\n\t\t\tonClick={(e) => {\n\t\t\t\t// Stop bubbling so a parent treeitem doesn't refire on a\n\t\t\t\t// nested-child click — each li carries its own click handler.\n\t\t\t\te.stopPropagation();\n\t\t\t\thandleClick();\n\t\t\t}}\n\t\t\tonFocus={(e) => {\n\t\t\t\t// stopPropagation prevents the parent treeitem's onFocus from\n\t\t\t\t// also firing when a deeper child gains focus, racing the\n\t\t\t\t// focusedId state. Side effect: focus-trap libraries listening\n\t\t\t\t// at a wrapping container don't see nested-tree focus events.\n\t\t\t\t// In practice tree-inside-Drawer / Dialog still works because\n\t\t\t\t// those focus traps activate on outer focusin, not inner.\n\t\t\t\te.stopPropagation();\n\t\t\t\tif (!node.disabled) onFocus(node.id);\n\t\t\t}}\n\t\t\tclassName={cn(\n\t\t\t\t\"outline-none rounded-sm\",\n\t\t\t\t// H1: focus-visible-driven ring (NOT state-driven) — the ring\n\t\t\t\t// only shows on keyboard focus, not on mouse clicks.\n\t\t\t\t\"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n\t\t\t)}\n\t\t>\n\t\t\t{/*\n\t\t\t Row element — visible surface. Style only; the li carries the\n\t\t\t interactivity + focus + ARIA. aria-hidden on chevron + icon\n\t\t\t spans keeps the accessible name pinned to the labelled\n\t\t\t <span id={labelId}>.\n\t\t\t*/}\n\t\t\t<div\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"flex cursor-pointer select-none items-center gap-[var(--gap-xs,0.25rem)] rounded-sm px-[var(--space-2,0.5rem)] py-[var(--space-1,0.25rem)]\",\n\t\t\t\t\t\"transition-colors duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\t\"hover:bg-accent hover:text-accent-foreground\",\n\t\t\t\t\tisSelected && \"bg-accent text-accent-foreground font-medium\",\n\t\t\t\t\tnode.disabled && \"cursor-not-allowed opacity-50\",\n\t\t\t\t)}\n\t\t\t\tstyle={{ paddingLeft: `calc(${depth} * var(--space-4, 1rem) + var(--space-2, 0.5rem))` }}\n\t\t\t>\n\t\t\t\t{isParent ? (\n\t\t\t\t\t<span aria-hidden=\"true\" className=\"inline-flex h-4 w-4 shrink-0 items-center justify-center\">\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"h-3 w-3 transition-transform duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\t\t\t\tisExpanded ? \"rotate-90\" : \"rotate-0\",\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<polyline points=\"9 18 15 12 9 6\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</span>\n\t\t\t\t) : (\n\t\t\t\t\t<span aria-hidden=\"true\" className=\"inline-block h-4 w-4 shrink-0\" />\n\t\t\t\t)}\n\t\t\t\t{node.icon ? (\n\t\t\t\t\t<span aria-hidden=\"true\" className=\"inline-flex h-4 w-4 shrink-0 items-center justify-center [&_svg]:size-4\">\n\t\t\t\t\t\t{node.icon}\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t\t<span id={labelId} className=\"truncate\">\n\t\t\t\t\t{node.label}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t\t{isParent && isExpanded && children ? (\n\t\t\t\t<ul role=\"group\" aria-labelledby={labelId} className=\"flex flex-col\">\n\t\t\t\t\t{children.map((child) => (\n\t\t\t\t\t\t<TreeItem\n\t\t\t\t\t\t\tkey={child.id}\n\t\t\t\t\t\t\tnode={child}\n\t\t\t\t\t\t\tdepth={depth + 1}\n\t\t\t\t\t\t\texpanded={expanded}\n\t\t\t\t\t\t\tselected={selected}\n\t\t\t\t\t\t\tisSelectable={isSelectable}\n\t\t\t\t\t\t\tfocusedId={focusedId}\n\t\t\t\t\t\t\tonFocus={onFocus}\n\t\t\t\t\t\t\tonToggleExpand={onToggleExpand}\n\t\t\t\t\t\t\tonActivate={onActivate}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</ul>\n\t\t\t) : null}\n\t\t</li>\n\t);\n}\n\nexport { Tree };\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hex-core/components",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "AI-native React components for Hex UI — Radix UI + Tailwind CSS with machine-readable schemas. RSC-aware per-component bundles + tree-shakable.",
5
5
  "keywords": [
6
6
  "react",
@@ -77,6 +77,7 @@
77
77
  "@radix-ui/react-tabs": "^1.1.13",
78
78
  "@radix-ui/react-toggle": "^1.1.10",
79
79
  "@radix-ui/react-toggle-group": "^1.1.11",
80
+ "@radix-ui/react-toolbar": "^1.1.11",
80
81
  "@radix-ui/react-tooltip": "^1.2.8",
81
82
  "class-variance-authority": "^0.7.0",
82
83
  "clsx": "^2.1.0",