@tradalab/lyra 1.0.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 ADDED
@@ -0,0 +1,62 @@
1
+ # @tradalab/lyra
2
+
3
+ > Tradalab's UI kit — shadcn (`radix-lyra` style) primitives, composed
4
+ > components, and an app shell for **React 19 / Next 16 / Tailwind v4**.
5
+
6
+ ESM-only. Extracted from Tradalab's foundation kit so every company app shares
7
+ the same component surface.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @tradalab/lyra
13
+ # peers: react ^19, react-dom ^19 (next ^16 only if you use /shell)
14
+ ```
15
+
16
+ The stylesheet pulls in Tailwind v4, so your app needs `@tailwindcss/postcss`.
17
+
18
+ ```css
19
+ /* app/globals.css */
20
+ @import "@tradalab/lyra/styles.css";
21
+ ```
22
+
23
+ ## Subpaths
24
+
25
+ | Subpath | Exports | Boundary |
26
+ |---|---|---|
27
+ | `@tradalab/lyra/utils` | `cn` | server-safe |
28
+ | `@tradalab/lyra/hooks` | `useMobile` | client |
29
+ | `@tradalab/lyra/ui` | ~30 shadcn primitives + `AccountMenu` | client |
30
+ | `@tradalab/lyra/shell` | `AppShell`, `NavItem`, `NavGroup`, `Breadcrumbs`, `useActiveRoute` | client |
31
+ | `@tradalab/lyra/styles.css` | Tailwind v4 base + `radix-lyra` tokens (light/dark) | CSS |
32
+
33
+ Client subpaths carry a build-injected `"use client"`; import them inside a
34
+ client tree or pass via `children` — don't import them into a Server Component's
35
+ own module.
36
+
37
+ ```tsx
38
+ import { Button, Card, CardHeader } from "@tradalab/lyra/ui"
39
+ import { AppShell, NavItem } from "@tradalab/lyra/shell"
40
+ ```
41
+
42
+ ## Develop
43
+
44
+ ```bash
45
+ make install # pnpm install
46
+ make build # tsup + inject-use-client + verify-build-output
47
+ make verify # typecheck + lint + test
48
+ make ui-add <name> # scaffold a shadcn primitive, adapt, rebuild barrel
49
+ make ui-sync # re-adapt every primitive + rebuild barrel
50
+ ```
51
+
52
+ **Layout** — `src/<layer>/` with a barrel `index.ts`; one tsup entry per
53
+ subpath; ESM-only to `dist/`. **Layers** (enforced by `eslint-plugin-boundaries`):
54
+ `utils`/`hooks` (foundation) → `ui` → `shell`. No upward imports.
55
+
56
+ **`"use client"` is not in source** — it is injected onto the client entry
57
+ artifacts post-build by `scripts/inject-use-client.mjs`, then asserted by
58
+ `scripts/verify-build-output.mjs`. `scripts/sync-ui.mjs` adapts shadcn-CLI
59
+ output to repo conventions.
60
+
61
+ **Release** — `pnpm publish --no-git-checks` (the `prepublishOnly` hook builds
62
+ first). Published to the npm public registry.
@@ -0,0 +1,128 @@
1
+ import * as react_hook_form from 'react-hook-form';
2
+ import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
3
+ import * as React from 'react';
4
+ import { ReactNode, HTMLAttributes } from 'react';
5
+ import { Slot, Label } from 'radix-ui';
6
+ import { LucideIcon } from 'lucide-react';
7
+
8
+ declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, handleSubmit, unregister, control, register, setFocus, subscribe, }: react_hook_form.FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
9
+ declare const FormField: <TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ ...props }: ControllerProps<TFieldValues, TName>) => React.JSX.Element;
10
+ declare const useFormField: () => {
11
+ invalid: boolean;
12
+ isDirty: boolean;
13
+ isTouched: boolean;
14
+ isValidating: boolean;
15
+ error?: react_hook_form.FieldError;
16
+ id: string | undefined;
17
+ name: string;
18
+ formItemId: string;
19
+ formDescriptionId: string;
20
+ formMessageId: string;
21
+ };
22
+ declare function FormItem({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
23
+ declare function FormLabel({ className, ...props }: React.ComponentProps<typeof Label.Root>): React.JSX.Element;
24
+ declare function FormControl({ ...props }: React.ComponentProps<typeof Slot.Root>): React.JSX.Element;
25
+ declare function FormDescription({ className, ...props }: React.ComponentProps<"p">): React.JSX.Element;
26
+ declare function FormMessage({ className, ...props }: React.ComponentProps<"p">): React.JSX.Element | null;
27
+
28
+ type PanelItemType = {
29
+ key: string;
30
+ label: string;
31
+ icon?: LucideIcon;
32
+ danger?: boolean;
33
+ hasError?: boolean;
34
+ content: ReactNode;
35
+ };
36
+ declare function Panel({ name, items, enable_url }: {
37
+ name?: string;
38
+ items: PanelItemType[];
39
+ enable_url?: boolean;
40
+ }): React.JSX.Element | null;
41
+ interface PanelItemProps {
42
+ children: ReactNode;
43
+ active?: boolean;
44
+ icon?: LucideIcon;
45
+ danger?: boolean;
46
+ hasError?: boolean;
47
+ onClick?: () => void;
48
+ }
49
+ interface PanelCardProps {
50
+ title: string;
51
+ description?: string;
52
+ hint?: string;
53
+ action?: ReactNode;
54
+ children?: ReactNode;
55
+ }
56
+ declare function PanelCard({ title, description, hint, action, children }: PanelCardProps): React.JSX.Element;
57
+
58
+ type ConfirmOptions = {
59
+ title: string;
60
+ description?: string;
61
+ confirmText?: string;
62
+ cancelText?: string;
63
+ danger?: boolean;
64
+ };
65
+
66
+ type Props = {
67
+ options: ConfirmOptions;
68
+ onConfirm: () => void;
69
+ onCancel: () => void;
70
+ };
71
+ declare function ConfirmDialog({ options, onConfirm, onCancel }: Props): React.JSX.Element;
72
+
73
+ type ConfirmFn = (options: ConfirmOptions) => Promise<boolean>;
74
+ declare const ConfirmContext: React.Context<ConfirmFn | null>;
75
+ declare function ConfirmProvider({ children }: {
76
+ children: ReactNode;
77
+ }): React.JSX.Element;
78
+
79
+ declare const useConfirm: () => ConfirmFn;
80
+
81
+ type TreeProviderProps = {
82
+ children: ReactNode;
83
+ defaultExpandedIds?: string[];
84
+ showLines?: boolean;
85
+ showIcons?: boolean;
86
+ selectable?: boolean;
87
+ multiSelect?: boolean;
88
+ selectedIds?: string[];
89
+ onSelectionChange?: (selectedIds: string[]) => void;
90
+ expandedIds?: Set<string>;
91
+ onExpandedChange?: (expandedIds: Set<string>) => void;
92
+ indent?: number;
93
+ animateExpand?: boolean;
94
+ className?: string;
95
+ };
96
+ declare const TreeProvider: ({ children, defaultExpandedIds, showLines, showIcons, selectable, multiSelect, selectedIds, onSelectionChange, indent, animateExpand, className, expandedIds, onExpandedChange, }: TreeProviderProps) => React.JSX.Element;
97
+ type TreeViewProps = HTMLAttributes<HTMLDivElement>;
98
+ declare const TreeView: ({ className, children, ...props }: TreeViewProps) => React.JSX.Element;
99
+ type TreeNodeProps = HTMLAttributes<HTMLDivElement> & {
100
+ nodeId?: string;
101
+ level?: number;
102
+ isLast?: boolean;
103
+ parentPath?: boolean[];
104
+ children?: ReactNode;
105
+ };
106
+ declare const TreeNode: ({ nodeId: providedNodeId, level, isLast, parentPath, children, className, onClick: _onClick, ...props }: TreeNodeProps) => React.JSX.Element;
107
+ type TreeNodeTriggerProps = HTMLAttributes<HTMLDivElement> & {
108
+ expandOnClick?: boolean;
109
+ };
110
+ declare const TreeNodeTrigger: ({ children, className, onClick, expandOnClick, ...props }: TreeNodeTriggerProps) => React.JSX.Element;
111
+ declare const TreeLines: () => React.JSX.Element | null;
112
+ type TreeNodeContentProps = HTMLAttributes<HTMLDivElement> & {
113
+ hasChildren?: boolean;
114
+ };
115
+ declare const TreeNodeContent: ({ children, hasChildren, className, ...props }: TreeNodeContentProps) => React.JSX.Element | null;
116
+ type TreeExpanderProps = HTMLAttributes<HTMLDivElement> & {
117
+ hasChildren?: boolean;
118
+ };
119
+ declare const TreeExpander: ({ hasChildren, className, onClick, ...props }: TreeExpanderProps) => React.JSX.Element;
120
+ type TreeIconProps = HTMLAttributes<HTMLDivElement> & {
121
+ icon?: ReactNode;
122
+ hasChildren?: boolean;
123
+ };
124
+ declare const TreeIcon: ({ icon, hasChildren, className, ...props }: TreeIconProps) => React.JSX.Element | null;
125
+ type TreeLabelProps = HTMLAttributes<HTMLSpanElement>;
126
+ declare const TreeLabel: ({ className, ...props }: TreeLabelProps) => React.JSX.Element;
127
+
128
+ export { ConfirmContext, ConfirmDialog, type ConfirmFn, type ConfirmOptions, ConfirmProvider, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Panel, PanelCard, type PanelCardProps, type PanelItemProps, TreeExpander, type TreeExpanderProps, TreeIcon, type TreeIconProps, TreeLabel, type TreeLabelProps, TreeLines, TreeNode, TreeNodeContent, type TreeNodeContentProps, type TreeNodeProps, TreeNodeTrigger, type TreeNodeTriggerProps, TreeProvider, type TreeProviderProps, TreeView, type TreeViewProps, useConfirm, useFormField };
@@ -0,0 +1,421 @@
1
+ "use client";
2
+ import { Label, Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, Dialog, DialogOverlay, DialogContent, DialogTitle, DialogDescription } from '../chunk-BEAQYKGM.js';
3
+ import { Button } from '../chunk-KFQOYZ64.js';
4
+ import { cn } from '../chunk-XH55BHUU.js';
5
+ import * as React from 'react';
6
+ import { createContext, useMemo, useState, useEffect, useContext, useCallback, useId } from 'react';
7
+ import { Slot } from 'radix-ui';
8
+ import { FormProvider, Controller, useFormContext } from 'react-hook-form';
9
+ import { jsx, jsxs } from 'react/jsx-runtime';
10
+ import { useRouter, useSearchParams } from 'next/navigation';
11
+ import { ChevronRight, FolderOpen, Folder, File } from 'lucide-react';
12
+
13
+ var Form = FormProvider;
14
+ var FormFieldContext = React.createContext(null);
15
+ var FormField = ({
16
+ ...props
17
+ }) => {
18
+ return /* @__PURE__ */ jsx(FormFieldContext.Provider, { value: { name: props.name }, children: /* @__PURE__ */ jsx(Controller, { ...props }) });
19
+ };
20
+ var useFormField = () => {
21
+ const fieldContext = React.useContext(FormFieldContext);
22
+ const itemContext = React.useContext(FormItemContext);
23
+ const { getFieldState, formState } = useFormContext();
24
+ if (!fieldContext) {
25
+ throw new Error("useFormField should be used within <FormField>");
26
+ }
27
+ const fieldState = getFieldState(fieldContext.name, formState);
28
+ const { id } = itemContext || {};
29
+ return {
30
+ id,
31
+ name: fieldContext.name,
32
+ formItemId: `${id}-form-item`,
33
+ formDescriptionId: `${id}-form-item-description`,
34
+ formMessageId: `${id}-form-item-message`,
35
+ ...fieldState
36
+ };
37
+ };
38
+ var FormItemContext = React.createContext(null);
39
+ function FormItem({ className, ...props }) {
40
+ const id = React.useId();
41
+ return /* @__PURE__ */ jsx(FormItemContext.Provider, { value: { id }, children: /* @__PURE__ */ jsx("div", { "data-slot": "form-item", className: cn("grid gap-2", className), ...props }) });
42
+ }
43
+ function FormLabel({ className, ...props }) {
44
+ const { error, formItemId } = useFormField();
45
+ return /* @__PURE__ */ jsx(Label, { "data-slot": "form-label", "data-error": !!error, className: cn("data-[error=true]:text-destructive", className), htmlFor: formItemId, ...props });
46
+ }
47
+ function FormControl({ ...props }) {
48
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
49
+ return /* @__PURE__ */ jsx(
50
+ Slot.Root,
51
+ {
52
+ "data-slot": "form-control",
53
+ id: formItemId,
54
+ "aria-describedby": !error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`,
55
+ "aria-invalid": !!error,
56
+ ...props
57
+ }
58
+ );
59
+ }
60
+ function FormDescription({ className, ...props }) {
61
+ const { formDescriptionId } = useFormField();
62
+ return /* @__PURE__ */ jsx("p", { "data-slot": "form-description", id: formDescriptionId, className: cn("text-muted-foreground text-sm", className), ...props });
63
+ }
64
+ function FormMessage({ className, ...props }) {
65
+ const { error, formMessageId } = useFormField();
66
+ const body = error ? String(error?.message ?? "") : props.children;
67
+ if (!body) {
68
+ return null;
69
+ }
70
+ return /* @__PURE__ */ jsx("p", { "data-slot": "form-message", id: formMessageId, className: cn("text-destructive text-sm", className), ...props, children: body });
71
+ }
72
+ function Panel({ name, items, enable_url }) {
73
+ const router = useRouter();
74
+ const params = useSearchParams();
75
+ const defaultKey = items[0]?.key;
76
+ const panelKey = useMemo(() => `panel${name ? "-" + name : ""}`, [name]);
77
+ const panelFromUrl = useMemo(() => {
78
+ if (!enable_url) return defaultKey;
79
+ return params.get(panelKey) ?? defaultKey;
80
+ }, [enable_url, params, defaultKey, panelKey]);
81
+ const [active, setActive] = useState(panelFromUrl);
82
+ useEffect(() => {
83
+ if (!enable_url) return;
84
+ setActive(panelFromUrl);
85
+ }, [panelFromUrl, enable_url]);
86
+ const current = items.find((s) => s.key === active) ?? items[0];
87
+ if (!current) return null;
88
+ const navigate = (key) => {
89
+ if (key === active) return;
90
+ setActive(key);
91
+ if (!enable_url) return;
92
+ router.replace(`?${panelKey}=${key}`, { scroll: false });
93
+ };
94
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6 h-full min-h-0 flex flex-col", children: [
95
+ /* @__PURE__ */ jsx("div", { className: "lg:hidden", children: /* @__PURE__ */ jsx("div", { className: "inline-flex rounded-lg bg-muted p-1", children: items?.map((s) => /* @__PURE__ */ jsx(
96
+ "button",
97
+ {
98
+ onClick: () => navigate(s.key),
99
+ className: cn(
100
+ "rounded-md px-3 py-1.5 text-sm transition-all",
101
+ active === s.key ? "bg-background text-foreground shadow-sm" : s.danger ? "text-destructive" : "text-muted-foreground hover:text-foreground"
102
+ ),
103
+ children: /* @__PURE__ */ jsxs("span", { className: "relative flex items-center gap-2", children: [
104
+ s.icon && /* @__PURE__ */ jsx(s.icon, { className: "h-4 w-4" }),
105
+ s.label,
106
+ s.hasError && /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-2 flex h-2 w-2 rounded-full bg-destructive" })
107
+ ] })
108
+ },
109
+ s.key
110
+ )) }) }),
111
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-8 lg:grid-cols-[240px_1fr] flex-1 min-h-0", children: [
112
+ /* @__PURE__ */ jsx("aside", { className: "hidden space-y-1 lg:block overflow-y-auto min-h-0 pr-1 [overflow:overlay]", children: items?.map((s) => /* @__PURE__ */ jsx(PanelItem, { active: active === s.key, icon: s.icon, danger: s.danger, hasError: s.hasError, onClick: () => navigate(s.key), children: s.label }, s.key)) }),
113
+ /* @__PURE__ */ jsx("div", { className: "space-y-6 overflow-y-auto min-h-0 pr-1 [overflow:overlay]", children: current.content })
114
+ ] })
115
+ ] });
116
+ }
117
+ function PanelItem({ children, active, icon: Icon, danger, hasError, onClick }) {
118
+ return /* @__PURE__ */ jsxs(
119
+ "div",
120
+ {
121
+ onClick,
122
+ className: cn(
123
+ "cursor-pointer rounded-md px-3 py-2 text-sm transition-colors flex items-center justify-between",
124
+ active ? "bg-muted font-medium" : danger ? "text-destructive hover:bg-destructive/10" : "text-muted-foreground hover:bg-muted"
125
+ ),
126
+ children: [
127
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
128
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" }),
129
+ children
130
+ ] }),
131
+ hasError && /* @__PURE__ */ jsx("span", { className: "h-2 w-2 rounded-full bg-destructive" })
132
+ ]
133
+ }
134
+ );
135
+ }
136
+ function PanelCard({ title, description, hint, action, children }) {
137
+ return /* @__PURE__ */ jsxs(Card, { className: cn({ "pb-3": hint || action }), children: [
138
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
139
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: title }),
140
+ description && /* @__PURE__ */ jsx(CardDescription, { className: "max-w-2xl", children: description })
141
+ ] }),
142
+ children && /* @__PURE__ */ jsx(CardContent, { children }),
143
+ (hint || action) && /* @__PURE__ */ jsxs(CardFooter, { className: "flex items-center justify-between border-t !pt-3 !pb-0", children: [
144
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: hint }),
145
+ action
146
+ ] })
147
+ ] });
148
+ }
149
+ function ConfirmDialog({ options, onConfirm, onCancel }) {
150
+ return /* @__PURE__ */ jsxs(Dialog, { open: true, onOpenChange: (val) => !val && onCancel(), children: [
151
+ /* @__PURE__ */ jsx(DialogOverlay, { className: "fixed inset-0 bg-white/10" }),
152
+ /* @__PURE__ */ jsxs(DialogContent, { className: cn("fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2", "p-4 w-[400px]", "focus:outline-none"), children: [
153
+ /* @__PURE__ */ jsx(DialogTitle, { className: "text-sm font-medium", children: options.title }),
154
+ options.description && /* @__PURE__ */ jsx(DialogDescription, { className: "mt-2 text-sm text-muted-foreground", children: options.description }),
155
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex justify-end gap-2", children: [
156
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onCancel, children: options.cancelText ?? "Cancel" }),
157
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: onConfirm, className: cn(options.danger && "bg-red-600 hover:bg-red-700 text-white"), children: options.confirmText ?? "Confirm" })
158
+ ] })
159
+ ] })
160
+ ] });
161
+ }
162
+ var ConfirmContext = createContext(null);
163
+ function ConfirmProvider({ children }) {
164
+ const [state, setState] = useState(null);
165
+ const confirm = (options) => new Promise((resolve) => setState({ options, resolve }));
166
+ const close = (result) => {
167
+ state?.resolve(result);
168
+ setState(null);
169
+ };
170
+ return /* @__PURE__ */ jsxs(ConfirmContext.Provider, { value: confirm, children: [
171
+ children,
172
+ state && /* @__PURE__ */ jsx(ConfirmDialog, { options: state.options, onConfirm: () => close(true), onCancel: () => close(false) })
173
+ ] });
174
+ }
175
+ var useConfirm = () => {
176
+ const ctx = useContext(ConfirmContext);
177
+ if (!ctx) {
178
+ throw new Error("useConfirm must be used inside ConfirmProvider");
179
+ }
180
+ return ctx;
181
+ };
182
+ var TreeContext = createContext(void 0);
183
+ var useTree = () => {
184
+ const context = useContext(TreeContext);
185
+ if (!context) {
186
+ throw new Error("Tree components must be used within a TreeProvider");
187
+ }
188
+ return context;
189
+ };
190
+ var TreeNodeContext = createContext(void 0);
191
+ var useTreeNode = () => {
192
+ const context = useContext(TreeNodeContext);
193
+ if (!context) {
194
+ throw new Error("TreeNode components must be used within a TreeNode");
195
+ }
196
+ return context;
197
+ };
198
+ var TreeProvider = ({
199
+ children,
200
+ defaultExpandedIds = [],
201
+ showLines = true,
202
+ showIcons = true,
203
+ selectable = true,
204
+ multiSelect = false,
205
+ selectedIds,
206
+ onSelectionChange,
207
+ indent = 20,
208
+ animateExpand = true,
209
+ className,
210
+ expandedIds,
211
+ onExpandedChange
212
+ }) => {
213
+ const [internalExpandedIds, setInternalExpandedIds] = useState(new Set(defaultExpandedIds));
214
+ const [internalSelectedIds, setInternalSelectedIds] = useState(selectedIds ?? []);
215
+ const isControlledSelected = selectedIds !== void 0 && onSelectionChange !== void 0;
216
+ const currentSelectedIds = isControlledSelected ? selectedIds : internalSelectedIds;
217
+ const isControlledExpanded = expandedIds !== void 0 && onExpandedChange !== void 0;
218
+ const currentExpandedIds = isControlledExpanded ? expandedIds : internalExpandedIds;
219
+ const toggleExpanded = useCallback(
220
+ (nodeId) => {
221
+ const newSet = new Set(currentExpandedIds);
222
+ if (newSet.has(nodeId)) {
223
+ newSet.delete(nodeId);
224
+ } else {
225
+ newSet.add(nodeId);
226
+ }
227
+ if (isControlledExpanded) {
228
+ onExpandedChange?.(newSet);
229
+ } else {
230
+ setInternalExpandedIds(newSet);
231
+ }
232
+ },
233
+ [currentExpandedIds, isControlledExpanded, onExpandedChange]
234
+ );
235
+ const handleSelection = useCallback(
236
+ (nodeId, ctrlKey = false) => {
237
+ if (!selectable) {
238
+ return;
239
+ }
240
+ let newSelection;
241
+ if (multiSelect && ctrlKey) {
242
+ newSelection = currentSelectedIds.includes(nodeId) ? currentSelectedIds.filter((id) => id !== nodeId) : [...currentSelectedIds, nodeId];
243
+ } else {
244
+ newSelection = currentSelectedIds.includes(nodeId) ? [] : [nodeId];
245
+ }
246
+ if (isControlledSelected) {
247
+ onSelectionChange?.(newSelection);
248
+ } else {
249
+ setInternalSelectedIds(newSelection);
250
+ }
251
+ },
252
+ [selectable, multiSelect, currentSelectedIds, isControlledSelected, onSelectionChange]
253
+ );
254
+ return /* @__PURE__ */ jsx(
255
+ TreeContext.Provider,
256
+ {
257
+ value: {
258
+ expandedIds: currentExpandedIds,
259
+ selectedIds: currentSelectedIds,
260
+ toggleExpanded,
261
+ handleSelection,
262
+ showLines,
263
+ showIcons,
264
+ selectable,
265
+ multiSelect,
266
+ indent,
267
+ animateExpand
268
+ },
269
+ children: /* @__PURE__ */ jsx("div", { className: cn("w-full", className), children })
270
+ }
271
+ );
272
+ };
273
+ var TreeView = ({ className, children, ...props }) => /* @__PURE__ */ jsx("div", { className: cn("p-2", className), ...props, children });
274
+ var TreeNode = ({ nodeId: providedNodeId, level = 0, isLast = false, parentPath = [], children, className, onClick: _onClick, ...props }) => {
275
+ const generatedId = useId();
276
+ const nodeId = providedNodeId ?? generatedId;
277
+ const currentPath = level === 0 ? [] : [...parentPath];
278
+ if (level > 0 && parentPath.length < level - 1) {
279
+ while (currentPath.length < level - 1) {
280
+ currentPath.push(false);
281
+ }
282
+ }
283
+ if (level > 0) {
284
+ currentPath[level - 1] = isLast;
285
+ }
286
+ return /* @__PURE__ */ jsx(
287
+ TreeNodeContext.Provider,
288
+ {
289
+ value: {
290
+ nodeId,
291
+ level,
292
+ isLast,
293
+ parentPath: currentPath
294
+ },
295
+ children: /* @__PURE__ */ jsx("div", { className: cn("select-none", className), ...props, children })
296
+ }
297
+ );
298
+ };
299
+ var TreeNodeTrigger = ({ children, className, onClick, expandOnClick = false, ...props }) => {
300
+ const { selectedIds, handleSelection, toggleExpanded, indent } = useTree();
301
+ const { nodeId, level } = useTreeNode();
302
+ const isSelected = selectedIds.includes(nodeId);
303
+ return /* @__PURE__ */ jsxs(
304
+ "div",
305
+ {
306
+ className: cn(
307
+ "group relative mx-1 flex cursor-pointer items-center rounded-md px-3 transition-all duration-200",
308
+ "hover:bg-accent/50",
309
+ isSelected && "bg-accent/80",
310
+ className
311
+ ),
312
+ style: { paddingLeft: level * (indent ?? 0) + 8, paddingTop: "var(--tree-item-py)", paddingBottom: "var(--tree-item-py)" },
313
+ onClick: (e) => {
314
+ handleSelection(nodeId, e.ctrlKey || e.metaKey);
315
+ if (expandOnClick) toggleExpanded(nodeId);
316
+ onClick?.(e);
317
+ },
318
+ onDoubleClick: (e) => {
319
+ if (e.ctrlKey || e.metaKey) return;
320
+ toggleExpanded(nodeId);
321
+ },
322
+ ...props,
323
+ children: [
324
+ /* @__PURE__ */ jsx(TreeLines, {}),
325
+ children
326
+ ]
327
+ }
328
+ );
329
+ };
330
+ var TreeLines = () => {
331
+ const { showLines, indent } = useTree();
332
+ const { level, isLast, parentPath } = useTreeNode();
333
+ if (!showLines || level === 0) {
334
+ return null;
335
+ }
336
+ return /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute top-0 bottom-0 left-0", children: [
337
+ Array.from({ length: level }, (_, index) => {
338
+ const shouldHideLine = parentPath[index] === true;
339
+ if (shouldHideLine && index === level - 1) {
340
+ return null;
341
+ }
342
+ return /* @__PURE__ */ jsx(
343
+ "div",
344
+ {
345
+ className: "absolute top-0 bottom-0 border-border/40 border-l",
346
+ style: {
347
+ left: index * (indent ?? 0) + 12,
348
+ display: shouldHideLine ? "none" : "block"
349
+ }
350
+ },
351
+ index.toString()
352
+ );
353
+ }),
354
+ /* @__PURE__ */ jsx(
355
+ "div",
356
+ {
357
+ className: "absolute top-1/2 border-border/40 border-t",
358
+ style: {
359
+ left: (level - 1) * (indent ?? 0) + 12,
360
+ width: (indent ?? 0) - 4,
361
+ transform: "translateY(-1px)"
362
+ }
363
+ }
364
+ ),
365
+ isLast && /* @__PURE__ */ jsx(
366
+ "div",
367
+ {
368
+ className: "absolute top-0 border-border/40 border-l",
369
+ style: {
370
+ left: (level - 1) * (indent ?? 0) + 12,
371
+ height: "50%"
372
+ }
373
+ }
374
+ )
375
+ ] });
376
+ };
377
+ var TreeNodeContent = ({ children, hasChildren = false, className, ...props }) => {
378
+ const { expandedIds } = useTree();
379
+ const { nodeId } = useTreeNode();
380
+ const isExpanded = expandedIds.has(nodeId);
381
+ if (!hasChildren || !isExpanded) {
382
+ return null;
383
+ }
384
+ return /* @__PURE__ */ jsx("div", { className, ...props, children });
385
+ };
386
+ var TreeExpander = ({ hasChildren = false, className, onClick, ...props }) => {
387
+ const { expandedIds, toggleExpanded } = useTree();
388
+ const { nodeId } = useTreeNode();
389
+ const isExpanded = expandedIds.has(nodeId);
390
+ if (!hasChildren) {
391
+ return /* @__PURE__ */ jsx("div", { className: "mr-1 h-4 w-4" });
392
+ }
393
+ return /* @__PURE__ */ jsx(
394
+ "div",
395
+ {
396
+ className: cn("mr-1 flex h-4 w-4 items-center justify-center transition-transform duration-150", isExpanded && "rotate-90", className),
397
+ onClick: (e) => {
398
+ e.stopPropagation();
399
+ toggleExpanded(nodeId);
400
+ onClick?.(e);
401
+ },
402
+ ...props,
403
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3 text-muted-foreground" })
404
+ }
405
+ );
406
+ };
407
+ var TreeIcon = ({ icon, hasChildren = false, className, ...props }) => {
408
+ const { showIcons, expandedIds } = useTree();
409
+ const { nodeId } = useTreeNode();
410
+ const isExpanded = expandedIds.has(nodeId);
411
+ if (!showIcons) {
412
+ return null;
413
+ }
414
+ const getDefaultIcon = () => hasChildren ? isExpanded ? /* @__PURE__ */ jsx(FolderOpen, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Folder, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(File, { className: "h-4 w-4" });
415
+ return /* @__PURE__ */ jsx("div", { className: cn("mr-2 flex h-4 w-4 items-center justify-center text-muted-foreground transition-transform", className), ...props, children: icon || getDefaultIcon() });
416
+ };
417
+ var TreeLabel = ({ className, ...props }) => /* @__PURE__ */ jsx("span", { className: cn("font flex-1 truncate text-sm", className), ...props });
418
+
419
+ export { ConfirmContext, ConfirmDialog, ConfirmProvider, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Panel, PanelCard, TreeExpander, TreeIcon, TreeLabel, TreeLines, TreeNode, TreeNodeContent, TreeNodeTrigger, TreeProvider, TreeView, useConfirm, useFormField };
420
+ //# sourceMappingURL=index.js.map
421
+ //# sourceMappingURL=index.js.map