@exegia/corpora-ui 0.19.0 → 0.20.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.
Files changed (73) hide show
  1. package/dist-lib/components/blocks/nav/sidebar/__tests__/use-ai-sidebar.test.d.ts +1 -0
  2. package/dist-lib/components/blocks/nav/sidebar/ai-sidebar.d.ts +11 -2
  3. package/dist-lib/components/blocks/nav/sidebar/type.d.ts +83 -1
  4. package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar.d.ts +22 -31
  5. package/dist-lib/components/blocks/nav/sidebar/utils.d.ts +5 -0
  6. package/dist-lib/components/blocks/scaffold/constants.d.ts +5 -0
  7. package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +2 -0
  8. package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +1 -1
  9. package/dist-lib/components/blocks/scaffold/scaffold-tab.d.ts +6 -1
  10. package/dist-lib/components/blocks/scaffold/type.d.ts +28 -0
  11. package/dist-lib/components/blocks/scaffold/use-panel-visibility.d.ts +19 -0
  12. package/dist-lib/components/blocks/scaffold/utils.d.ts +6 -0
  13. package/dist-lib/components/composed/tree/__tests__/tree.test.d.ts +1 -0
  14. package/dist-lib/components/composed/tree/__tests__/use-tree.test.d.ts +1 -0
  15. package/dist-lib/components/composed/tree/constants.d.ts +35 -0
  16. package/dist-lib/components/composed/tree/index.d.ts +4 -0
  17. package/dist-lib/components/composed/tree/tree-context.d.ts +4 -0
  18. package/dist-lib/components/composed/tree/tree-node.d.ts +7 -0
  19. package/dist-lib/components/composed/tree/tree.d.ts +19 -0
  20. package/dist-lib/components/composed/tree/type.d.ts +192 -0
  21. package/dist-lib/components/composed/tree/use-tree-dnd.d.ts +7 -0
  22. package/dist-lib/components/composed/tree/use-tree.d.ts +20 -0
  23. package/dist-lib/components/composed/tree/utils.d.ts +28 -0
  24. package/dist-lib/components/user-avatar/audio-wave.d.ts +2 -0
  25. package/dist-lib/components/user-avatar/component.d.ts +10 -0
  26. package/dist-lib/components/user-avatar/fallback.d.ts +5 -0
  27. package/dist-lib/components/user-avatar/index.d.ts +13 -0
  28. package/dist-lib/components/user-avatar/type.d.ts +26 -0
  29. package/dist-lib/components/user-avatar/utils.d.ts +2 -0
  30. package/dist-lib/index.d.ts +1 -0
  31. package/dist-lib/index.js +2141 -1139
  32. package/dist-lib/index.js.map +1 -1
  33. package/package.json +2 -1
  34. package/src/components/beste/piece/browser-frame.tsx +9 -6
  35. package/src/components/blocks/nav/sidebar/__tests__/use-ai-sidebar.test.tsx +310 -0
  36. package/src/components/blocks/nav/sidebar/ai-sidebar.tsx +90 -60
  37. package/src/components/blocks/nav/sidebar/type.ts +105 -1
  38. package/src/components/blocks/nav/sidebar/use-ai-sidebar.ts +293 -116
  39. package/src/components/blocks/nav/sidebar/utils.ts +28 -0
  40. package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +160 -1
  41. package/src/components/blocks/scaffold/constants.ts +7 -0
  42. package/src/components/blocks/scaffold/panel-menu-button.tsx +3 -0
  43. package/src/components/blocks/scaffold/scaffold-canvas.tsx +48 -5
  44. package/src/components/blocks/scaffold/scaffold-inspector.tsx +3 -2
  45. package/src/components/blocks/scaffold/scaffold-main.tsx +1 -1
  46. package/src/components/blocks/scaffold/scaffold-panel.tsx +41 -5
  47. package/src/components/blocks/scaffold/scaffold-root.tsx +18 -3
  48. package/src/components/blocks/scaffold/scaffold-sidebar.tsx +1 -1
  49. package/src/components/blocks/scaffold/scaffold-tab.tsx +45 -7
  50. package/src/components/blocks/scaffold/type.ts +28 -0
  51. package/src/components/blocks/scaffold/use-panel-visibility.ts +180 -0
  52. package/src/components/blocks/scaffold/utils.ts +19 -0
  53. package/src/components/composed/tree/__tests__/tree.test.tsx +400 -0
  54. package/src/components/composed/tree/__tests__/use-tree.test.tsx +332 -0
  55. package/src/components/composed/tree/constants.ts +60 -0
  56. package/src/components/composed/tree/index.ts +13 -0
  57. package/src/components/composed/tree/tree-context.ts +13 -0
  58. package/src/components/composed/tree/tree-node.tsx +451 -0
  59. package/src/components/composed/tree/tree.tsx +278 -0
  60. package/src/components/composed/tree/type.ts +212 -0
  61. package/src/components/composed/tree/use-tree-dnd.ts +148 -0
  62. package/src/components/composed/tree/use-tree.ts +306 -0
  63. package/src/components/composed/tree/utils.ts +172 -0
  64. package/src/components/composed/user-avatar.tsx +4 -1
  65. package/src/components/docs/component-preview.tsx +11 -11
  66. package/src/components/user-avatar/audio-wave.tsx +22 -0
  67. package/src/components/user-avatar/component.tsx +53 -0
  68. package/src/components/user-avatar/fallback.tsx +26 -0
  69. package/src/components/user-avatar/index.ts +15 -0
  70. package/src/components/user-avatar/type.ts +35 -0
  71. package/src/components/user-avatar/utils.ts +8 -0
  72. package/src/index.css +211 -153
  73. package/src/index.ts +1 -0
@@ -1,2 +1,11 @@
1
- import { AISidebarProps } from './type';
2
- export declare function AISidebar({ renderIcon, renderMenu, renderActionsTrigger, ariaLabel, className, ...options }: AISidebarProps): import("react").JSX.Element;
1
+ import { AISidebarComponentProps } from './type';
2
+ /**
3
+ * Resource tree for the app rail: nested rows with drag-and-drop reorder,
4
+ * inline rename, per-row action menus and full keyboard navigation.
5
+ *
6
+ * Two ways to drive it. Pass the data and handlers as props and the block
7
+ * owns its state, or hold a `useAISidebar` controller and pass it as
8
+ * `controller` — the same rendering, but select, expand, rename, reorder
9
+ * and menu state are then callable from anywhere in your app.
10
+ */
11
+ export declare function AISidebar(props: AISidebarComponentProps): import("react").JSX.Element;
@@ -17,7 +17,10 @@ export interface SidebarResourceMenuControls {
17
17
  close: () => void;
18
18
  rename: () => void;
19
19
  }
20
- export interface AISidebarProps {
20
+ /** Options for `useAISidebar`. `items`, `activeId` and `expandedIds` are
21
+ * each controlled when passed and hook-owned via their `default*` twin
22
+ * otherwise. */
23
+ export interface UseAISidebarOptions {
21
24
  items?: SidebarResource[];
22
25
  defaultItems?: SidebarResource[];
23
26
  onItemsChange?: (items: SidebarResource[]) => void;
@@ -28,7 +31,77 @@ export interface AISidebarProps {
28
31
  activeId?: string | null;
29
32
  defaultActiveId?: string | null;
30
33
  onActiveChange?: (id: string) => void;
34
+ expandedIds?: string[];
31
35
  defaultExpandedIds?: string[];
36
+ onExpandedChange?: (ids: string[]) => void;
37
+ }
38
+ /** Everything the sidebar can do, callable from outside the block — a
39
+ * toolbar, a command palette, a route change. Returned by `useAISidebar`
40
+ * and accepted by `<AISidebar controller={…} />`. */
41
+ export interface AISidebarController {
42
+ items: SidebarResource[];
43
+ /** Visible rows in order, each with its depth and parent id. */
44
+ flat: FlatResource[];
45
+ getItem: (id: string) => SidebarResource | undefined;
46
+ selectedId: string | null;
47
+ select: (id: string) => void;
48
+ expandedIds: ReadonlySet<string>;
49
+ isExpanded: (id: string) => boolean;
50
+ expand: (id: string) => void;
51
+ collapse: (id: string) => void;
52
+ toggleExpanded: (id: string) => void;
53
+ expandAll: () => void;
54
+ collapseAll: () => void;
55
+ /** Open every ancestor of `id`, leaving the rest of the tree alone. */
56
+ reveal: (id: string) => void;
57
+ /** Roving-focus target. `focus` also moves DOM focus to the row. */
58
+ focusedId: string | null;
59
+ focus: (id: string) => void;
60
+ renamingId: string | null;
61
+ startRename: (id: string) => void;
62
+ cancelRename: () => void;
63
+ /** Commit a rename and leave rename mode. Applied optimistically and
64
+ * rolled back if `onRename` rejects. Blank and unchanged labels are
65
+ * dropped. */
66
+ rename: (id: string, label: string) => void;
67
+ menuOpenId: string | null;
68
+ openMenu: (id: string) => void;
69
+ closeMenu: () => void;
70
+ /** Reorder, applied optimistically and rolled back if `onMove` rejects.
71
+ * Refuses moves the data forbids (into itself, into a non-container). */
72
+ move: (move: SidebarResourceMove) => Promise<void>;
73
+ /** A move is in flight — further moves are refused until it settles. */
74
+ movePending: boolean;
75
+ /** Live-region text for the last move or rename outcome. */
76
+ announcement: string;
77
+ /** @internal Row wiring the view threads through — drag state, the
78
+ * hover pill and roving keyboard nav all belong to the rendering. */
79
+ dnd: AISidebarDndState;
80
+ hover: AISidebarHoverState;
81
+ onRowKeyDown: (event: KeyboardEvent<HTMLDivElement>, row: FlatResource) => void;
82
+ setRowRef: (id: string, node: HTMLDivElement | null) => void;
83
+ }
84
+ /** @internal */
85
+ export interface AISidebarDndState {
86
+ draggingId: string | null;
87
+ dropTarget: DropTarget | null;
88
+ onRootDragOver: (event: DragEvent<HTMLDivElement>) => void;
89
+ onDrop: (event: DragEvent<HTMLDivElement>) => void;
90
+ onRowDragStart: (event: DragEvent<HTMLDivElement>, id: string) => void;
91
+ onRowDragEnd: () => void;
92
+ onRowDragOver: (event: DragEvent<HTMLDivElement>, row: FlatResource) => void;
93
+ }
94
+ /** @internal */
95
+ export interface AISidebarHoverState {
96
+ hoveredId: string | null;
97
+ /** Shared motion layoutId that lets the pill travel between rows. */
98
+ layoutId: string;
99
+ onRowHover: (id: string, hovered: boolean) => void;
100
+ clear: () => void;
101
+ }
102
+ export interface AISidebarProps extends UseAISidebarOptions {
103
+ /** Never set on the data form — `controller` selects the other one. */
104
+ controller?: never;
32
105
  renderIcon?: (item: SidebarResource) => ReactNode;
33
106
  renderMenu?: (item: SidebarResource, controls: SidebarResourceMenuControls) => ReactNode;
34
107
  /**
@@ -39,6 +112,15 @@ export interface AISidebarProps {
39
112
  ariaLabel?: string;
40
113
  className?: string;
41
114
  }
115
+ /** Presentational props, shared by both forms of `<AISidebar>`. */
116
+ export type AISidebarViewProps = Pick<AISidebarProps, "renderIcon" | "renderMenu" | "renderActionsTrigger" | "ariaLabel" | "className">;
117
+ /** Drive the block from a `useAISidebar` controller instead of raw props —
118
+ * the controller carries the data and every handler. */
119
+ export interface AISidebarControllerProps extends AISidebarViewProps {
120
+ controller: AISidebarController;
121
+ }
122
+ /** Either form: raw props, or a `useAISidebar` controller. */
123
+ export type AISidebarComponentProps = AISidebarProps | AISidebarControllerProps;
42
124
  export interface FlatResource {
43
125
  item: SidebarResource;
44
126
  depth: number;
@@ -1,31 +1,22 @@
1
- import { DragEvent, KeyboardEvent } from 'react';
2
- import { AISidebarProps, DropTarget, FlatResource } from './type.ts';
3
- export type UseAISidebarOptions = Pick<AISidebarProps, "items" | "defaultItems" | "onItemsChange" | "onMove" | "onMoveError" | "onRename" | "activeId" | "defaultActiveId" | "onActiveChange" | "defaultExpandedIds">;
4
- export declare function useAISidebar({ items, defaultItems, onItemsChange, onMove, onMoveError, onRename, activeId, defaultActiveId, onActiveChange, defaultExpandedIds, }: UseAISidebarOptions): {
5
- flat: FlatResource[];
6
- selectedId: string | null;
7
- expandedIds: Set<string>;
8
- focusedId: string | null;
9
- setFocusedId: import('react').Dispatch<import('react').SetStateAction<string | null>>;
10
- draggingId: string | null;
11
- dropTarget: DropTarget | null;
12
- hoveredId: string | null;
13
- hoverLayoutId: string;
14
- handleRowHover: (id: string, hovered: boolean) => void;
15
- clearHover: () => void;
16
- menuOpenId: string | null;
17
- renamingId: string | null;
18
- setRenamingId: import('react').Dispatch<import('react').SetStateAction<string | null>>;
19
- announcement: string;
20
- select: (id: string) => void;
21
- toggle: (id: string) => void;
22
- handleKeyDown: (event: KeyboardEvent<HTMLDivElement>, row: FlatResource) => void;
23
- handleRootDragOver: (event: DragEvent<HTMLDivElement>) => void;
24
- handleDrop: (event: DragEvent<HTMLDivElement>) => void;
25
- handleRowDragStart: (event: DragEvent<HTMLDivElement>, id: string) => void;
26
- handleRowDragEnd: () => void;
27
- handleRowDragOver: (event: DragEvent<HTMLDivElement>, targetRow: FlatResource) => void;
28
- commitRename: (row: FlatResource, label: string) => void;
29
- handleMenuOpenChange: (row: FlatResource, open: boolean) => void;
30
- setRowRef: (id: string, node: HTMLDivElement | null) => void;
31
- };
1
+ import { AISidebarController, UseAISidebarOptions } from './type.ts';
2
+ export type { UseAISidebarOptions };
3
+ /**
4
+ * The AI sidebar's headless controller — every behaviour the block
5
+ * performs, callable from outside it: select, expand/collapse (one row,
6
+ * every row, or just the ancestors of one), move focus, rename, open a
7
+ * row's action menu, reorder.
8
+ *
9
+ * `items`, `activeId` and `expandedIds` are each controlled when passed
10
+ * and hook-owned via their `default*` twin otherwise. Reorder and rename
11
+ * are applied to the data optimistically either way and rolled back if
12
+ * `onMove`/`onRename` rejects, so a consumer never reduces the tree
13
+ * itself — `moveResource` and `renameResource` are exported for the ones
14
+ * who keep their data elsewhere.
15
+ *
16
+ * ```tsx
17
+ * const sidebar = useAISidebar({ defaultItems: resources })
18
+ * <AISidebar controller={sidebar} />
19
+ * <Button onClick={sidebar.collapseAll}>Collapse all</Button>
20
+ * ```
21
+ */
22
+ export declare function useAISidebar(options: UseAISidebarOptions): AISidebarController;
@@ -6,6 +6,11 @@ export declare const ROW_REVEAL: {
6
6
  export declare function canContain(item: SidebarResource): boolean;
7
7
  export declare function flattenResources(items: SidebarResource[], expanded: Set<string>, depth?: number, parentId?: string | null): FlatResource[];
8
8
  export declare function findResource(items: SidebarResource[], id: string): SidebarResource | undefined;
9
+ /** Ids of every ancestor of `id` (nearest last); empty when absent. */
10
+ export declare function ancestorIdsOf(items: SidebarResource[], id: string): string[];
11
+ /** Ids of every row that can hold children and has some — the set
12
+ * `expandAll` opens. */
13
+ export declare function expandableIdsOf(items: SidebarResource[]): string[];
9
14
  export declare function containsResource(item: SidebarResource, id: string): boolean;
10
15
  export declare function removeResource(items: SidebarResource[], id: string): {
11
16
  items: SidebarResource[];
@@ -5,6 +5,11 @@ export declare const SCAFFOLD_INSPECTOR_WIDTH = 272;
5
5
  /** Most panels the canvas holds at once — omit Actions' `onAdd` when the
6
6
  * count reaches it so the Add segment hides. */
7
7
  export declare const SCAFFOLD_PANEL_CAPACITY = 3;
8
+ /** Narrowest a panel may render, in px. When the canvas can't grant every
9
+ * panel this width, the scaffold hides panels until the rest fit. */
10
+ export declare const SCAFFOLD_PANEL_MIN_WIDTH = 320;
11
+ /** Gap in px between side-by-side panels on the canvas (`gap-2`). */
12
+ export declare const SCAFFOLD_CANVAS_GAP = 8;
8
13
  /** Gap in px between the drawer/actions and the viewport edges. */
9
14
  export declare const SCAFFOLD_EDGE_GUTTER = 10;
10
15
  /** Vertical room in px reserved above the canvas for the actions cluster. */
@@ -3,5 +3,7 @@ import * as React from "react";
3
3
  /**
4
4
  * The panel row below the action cluster: 8px gutters, 8px gaps, panels
5
5
  * side by side. Key each `Scaffold.Panel` child so closes animate out.
6
+ * Measures its own width and hides id'd panels (oldest activation first)
7
+ * whenever it can't grant each one `SCAFFOLD_PANEL_MIN_WIDTH`.
6
8
  */
7
9
  export declare function ScaffoldCanvas({ className, children, ...rest }: ScaffoldCanvasProps): React.ReactElement;
@@ -7,4 +7,4 @@ import * as React from "react";
7
7
  * the two cards with a layout morph — the strip glides up and grows into the
8
8
  * primary slot while the primary card shrinks down into the strip.
9
9
  */
10
- export declare function ScaffoldPanel({ children, SecondaryPanel, onSwap, onCloseSecondary, name, swapLabel, sound, className, }: ScaffoldPanelProps): React.ReactElement;
10
+ export declare function ScaffoldPanel({ id, children, SecondaryPanel, onSwap, onCloseSecondary, name, swapLabel, sound, className, }: ScaffoldPanelProps): React.ReactElement;
@@ -5,5 +5,10 @@ import * as React from "react";
5
5
  * affordance. Render one per open panel as Actions children, keyed —
6
6
  * closes animate out of the pill. Omit `onClose` on the last remaining
7
7
  * panel's tab; the canvas keeps at least one panel open.
8
+ *
9
+ * With `panelId` (matching a `Scaffold.Panel`'s `id`), the tab also fronts
10
+ * that panel's visibility: a hidden panel dims its tab behind an eye-off
11
+ * icon, and pressing the label toggles the panel — showing one past
12
+ * capacity hides the least-recently-activated panel in its place.
8
13
  */
9
- export declare function ScaffoldTab({ children, onClose, closeLabel, sound, className, ...rest }: ScaffoldTabProps): React.ReactElement;
14
+ export declare function ScaffoldTab({ children, panelId, onClose, closeLabel, sound, className, ...rest }: ScaffoldTabProps): React.ReactElement;
@@ -7,6 +7,25 @@ export interface ScaffoldContextValue {
7
7
  inspectorWidth: number;
8
8
  setInspectorOpen: (open: boolean) => void;
9
9
  toggleInspector: () => void;
10
+ /** How many panels currently fit side by side, from the measured canvas
11
+ * width at `SCAFFOLD_PANEL_MIN_WIDTH` per panel (1..`SCAFFOLD_PANEL_CAPACITY`). */
12
+ panelCapacity: number;
13
+ /** Ids of id'd panels the scaffold is currently hiding — auto-hidden by
14
+ * capacity pressure or toggled away from a tab. */
15
+ hiddenPanelIds: readonly string[];
16
+ isPanelHidden: (id: string) => boolean;
17
+ /** Show/hide an id'd panel. Showing past capacity auto-hides the
18
+ * least-recently-activated visible panel; the last visible one never hides. */
19
+ togglePanelVisibility: (id: string) => void;
20
+ /** Id of the panel whose tab the pointer is resting on — the canvas
21
+ * spotlights it by fading every other id'd panel to 0.35 opacity. */
22
+ hoveredPanelId: string | null;
23
+ /** @internal Tabs report pointer enter/leave on their label. */
24
+ setPanelHovered: (id: string, hovered: boolean) => void;
25
+ /** @internal Canvas reports its ordered panel ids. */
26
+ registerPanelIds: (ids: readonly string[]) => void;
27
+ /** @internal Canvas reports its measured width. */
28
+ setCanvasWidth: (width: number) => void;
10
29
  }
11
30
  export interface ScaffoldRootProps extends ComponentProps<"div"> {
12
31
  /** Controlled inspector state — pair with `onInspectorOpenChange`, or
@@ -41,6 +60,11 @@ export interface ScaffoldActionsProps extends MotionSafe<Omit<ComponentProps<"di
41
60
  }
42
61
  export type TScaffoldPanelChild<T extends ScaffoldSubPanelProps = ScaffoldSubPanelProps> = ReactElement<T, React.JSXElementConstructor<T>>;
43
62
  export interface ScaffoldPanelProps {
63
+ /** Stable id opting the panel into responsive hiding: the canvas hides
64
+ * id'd panels when it can't grant each `SCAFFOLD_PANEL_MIN_WIDTH`, and a
65
+ * `Scaffold.Tab` with the matching `panelId` reflects and toggles it.
66
+ * Omitted, the panel always renders. */
67
+ id?: string;
44
68
  /** The panel's content, rendered in a card with a drop shadow. */
45
69
  children: ReactNode;
46
70
  /** The panel's content, rendered in a card with a drop shadow. */
@@ -64,6 +88,10 @@ export interface ScaffoldPanelProps {
64
88
  export interface ScaffoldTabProps extends MotionSafe<Omit<ComponentProps<"div">, "children">> {
65
89
  /** The tab's label. */
66
90
  children?: ReactNode;
91
+ /** Id of the panel this tab fronts (`Scaffold.Panel`'s `id`). The tab
92
+ * then shows whether the panel is hidden and pressing its label toggles
93
+ * visibility — showing past capacity hides another panel in its place. */
94
+ panelId?: string;
67
95
  /** Renders the tab's close button. The button renders only while this
68
96
  * is present — omit it to pin the tab, e.g. on the last remaining
69
97
  * panel (the canvas keeps at least one open). */
@@ -0,0 +1,19 @@
1
+ export interface PanelVisibility {
2
+ /** How many panels currently fit side by side (1..capacity cap). */
3
+ panelCapacity: number;
4
+ /** Ids of panels currently hidden (auto + user), for tabs to reflect. */
5
+ hiddenPanelIds: readonly string[];
6
+ isPanelHidden: (id: string) => boolean;
7
+ togglePanelVisibility: (id: string) => void;
8
+ /** Id of the panel whose tab is hovered — the canvas spotlights it. */
9
+ hoveredPanelId: string | null;
10
+ /** @internal Tabs report pointer enter/leave on their label. */
11
+ setPanelHovered: (id: string, hovered: boolean) => void;
12
+ /** @internal Canvas reports its ordered panel ids each render. */
13
+ registerPanelIds: (ids: readonly string[]) => void;
14
+ /** @internal Canvas reports its measured width. */
15
+ setCanvasWidth: (width: number) => void;
16
+ }
17
+ /** Owns which panels are hidden as the canvas gains and loses room.
18
+ * Lives in `Scaffold.Root`; Canvas and Tab reach it through context. */
19
+ export declare function usePanelVisibility(): PanelVisibility;
@@ -1,6 +1,12 @@
1
1
  import { Variants } from 'motion/react';
2
2
  import { ClassNameValue } from 'tailwind-merge';
3
3
  import { TSubPanelVariant } from './type.ts';
4
+ /**
5
+ * How many panels fit on a canvas of `width` px, granting each
6
+ * `SCAFFOLD_PANEL_MIN_WIDTH` plus the gaps between them. Clamped to
7
+ * [1, SCAFFOLD_PANEL_CAPACITY]; an unmeasured canvas (`null`) fits the cap.
8
+ */
9
+ export declare function getPanelCapacity(width: number | null): number;
4
10
  /** Desktop backdrop the whole scaffold sits on — a soft warm-gray wash. */
5
11
  export declare const scaffoldBackgroundClass: ClassNameValue;
6
12
  /** Card surface shared by a panel's primary area and secondary strip. */
@@ -0,0 +1,35 @@
1
+ import { Transition, Variants } from 'motion/react';
2
+ /** `--ease-smooth-out` as a motion/react easing tuple. */
3
+ export declare const TREE_EASE: readonly [0.22, 1, 0.36, 1];
4
+ /** Branch open — `--duration-fast` territory (accordion open). */
5
+ export declare const TREE_OPEN_DURATION = 0.25;
6
+ /** Branch close — quicker than open, per open/close asymmetry. */
7
+ export declare const TREE_CLOSE_DURATION = 0.15;
8
+ /** Chevron flip and label fades. */
9
+ export declare const TREE_MICRO_DURATION = 0.15;
10
+ /** Sidebar rail label reveal/hide when `collapsed` flips. */
11
+ export declare const TREE_COLLAPSE_DURATION = 0.2;
12
+ /** The collapsed rail's width in px — an icon row plus its padding. Kept in
13
+ * sync with the `w-11` resting class the rail falls back to before its
14
+ * expanded width has been measured. */
15
+ export declare const RAIL_COLLAPSED_WIDTH = 44;
16
+ /** Rail label text fade — starts as the width growth is finishing, so the
17
+ * label never reads as clipped mid-grow. */
18
+ export declare const TREE_LABEL_REVEAL_DELAY = 0.01;
19
+ /** A branch's children group: grows open with a soft stagger, folds shut
20
+ * faster. Height rides layout ("auto"), paint fades. */
21
+ export declare const TREE_BRANCH_VARIANTS: Variants;
22
+ /** Branch close — a plain target, NOT the "closed" variant label: a
23
+ * labeled exit orchestrates every variant child and AnimatePresence then
24
+ * waits on the whole subtree before unmounting. */
25
+ export declare const TREE_BRANCH_EXIT: {
26
+ height: number;
27
+ opacity: number;
28
+ transition: {
29
+ duration: number;
30
+ ease: readonly [0.22, 1, 0.36, 1];
31
+ };
32
+ };
33
+ /** One row inside an opening branch — a short blurred rise. */
34
+ export declare const TREE_ROW_VARIANTS: Variants;
35
+ export declare const TREE_MICRO_TRANSITION: Transition;
@@ -0,0 +1,4 @@
1
+ export { Tree } from './tree';
2
+ export { useTree } from './use-tree';
3
+ export { moveNode, renameNode } from './utils';
4
+ export type { TreeController, TreeControllerProps, TreeDataProps, TreeDropPosition, TreeNode, TreeProps, TreeVariant, UseTreeOptions, } from './type';
@@ -0,0 +1,4 @@
1
+ import { TreeContextValue } from './type';
2
+ import * as React from "react";
3
+ export declare const TreeContext: React.Context<TreeContextValue | null>;
4
+ export declare function useTreeContext(): TreeContextValue;
@@ -0,0 +1,7 @@
1
+ import { TreeNode } from './type';
2
+ import * as React from "react";
3
+ export interface TreeRowProps {
4
+ node: TreeNode;
5
+ depth: number;
6
+ }
7
+ export declare function TreeRow({ node, depth }: TreeRowProps): React.ReactElement;
@@ -0,0 +1,19 @@
1
+ import { TreeProps } from './type';
2
+ import * as React from "react";
3
+ /**
4
+ * A nested item tree with four shapes: `navigation` (app nav — 3-level
5
+ * data promotes the top level to collapsible section names), `toc`
6
+ * (routes on top, in-page `#` anchors at the leaves), `sidebar` (a
7
+ * single-level rail that collapses to icons) and `files` (a compact file
8
+ * explorer with rename, drag-and-drop and trailing row actions).
9
+ *
10
+ * Two ways to drive it. Data-driven like the sidebar block: pass `items`,
11
+ * wire `onNavigate` into your router. Or hold a `useTree` controller and
12
+ * pass it as `tree` — same rendering, but expand, collapse, select, rename
13
+ * and reorder are then callable from anywhere in your app.
14
+ *
15
+ * Branches expand with a soft height morph and row stagger; the rail's
16
+ * labels fold away when `collapsed` flips. Arrow keys walk visible rows,
17
+ * expand and collapse; F2 renames in `files`.
18
+ */
19
+ export declare function Tree(props: TreeProps): React.ReactElement;
@@ -0,0 +1,192 @@
1
+ import type * as React from "react";
2
+ /** One node in the tree. Nesting depth carries meaning per variant — see
3
+ * `TreeProps["variant"]`. */
4
+ export interface TreeNode {
5
+ id: string;
6
+ label: string;
7
+ /** Leading icon. Required in practice for `sidebar` — it is all that
8
+ * remains of a row while the rail is collapsed. */
9
+ icon?: React.ReactNode;
10
+ /** Renders an anchor. Without one the row is a button; `toc` leaves
11
+ * default to `#{id}` so they scroll to the matching heading. */
12
+ href?: string;
13
+ target?: "_blank" | "_self" | "_parent" | "_top";
14
+ disabled?: boolean;
15
+ /** Start expanded. Ancestors of `activeId` expand regardless. */
16
+ defaultOpen?: boolean;
17
+ /** Trailing hint — a count, a "New" pill. */
18
+ badge?: React.ReactNode;
19
+ /** Fires on selection, before the tree-level `onNavigate`. */
20
+ onSelect?: () => void;
21
+ children?: TreeNode[];
22
+ }
23
+ export type TreeVariant = "navigation" | "toc" | "sidebar" | "files";
24
+ /** Where a dragged row lands relative to its drop target. */
25
+ export type TreeDropPosition = "before" | "after" | "inside";
26
+ interface TreeBaseProps {
27
+ items: TreeNode[];
28
+ /** Never set on the data form — `tree` selects the controller form. */
29
+ tree?: never;
30
+ /** `id` of the current entry — matches any depth. Its ancestors expand. */
31
+ activeId?: string;
32
+ /** Fires for every selection (rows and leaves alike), after the row's
33
+ * anchor default — wire your router's navigate here. */
34
+ onNavigate?: (node: TreeNode) => void;
35
+ /** Expand/collapse cues. Silent until `bindSounds()`. */
36
+ sound?: boolean;
37
+ ariaLabel?: string;
38
+ className?: string;
39
+ }
40
+ interface TreeReadonlyProps {
41
+ collapsed?: never;
42
+ onMove?: never;
43
+ onRename?: never;
44
+ renderTrailing?: never;
45
+ }
46
+ /** Drive the tree from a `useTree` controller instead of raw props. The
47
+ * controller carries the variant, the data and every handler; only the
48
+ * presentational props stay here. */
49
+ export interface TreeControllerProps {
50
+ tree: TreeController;
51
+ items?: never;
52
+ ariaLabel?: string;
53
+ className?: string;
54
+ /** Row actions revealed on hover/focus (`files` only — ignored by the
55
+ * other variants, which the controller form cannot type-gate). */
56
+ renderTrailing?: (node: TreeNode) => React.ReactNode;
57
+ }
58
+ /** Either form: raw props, or a `useTree` controller via `tree`. */
59
+ export type TreeProps = TreeDataProps | TreeControllerProps;
60
+ /** The four shapes a Tree takes. Editing props only exist on `files`;
61
+ * `collapsed` only on `sidebar` — the union is the contract. */
62
+ export type TreeDataProps = TreeBaseProps & (({
63
+ /** Nested app navigation. With 3 levels of nodes the top level
64
+ * becomes collapsible section names (styled as headings, not
65
+ * links); with 2 it renders plain link rows. */
66
+ variant: "navigation";
67
+ } & TreeReadonlyProps) | ({
68
+ /** Table of contents: top-level nodes are routes, leaf nodes are
69
+ * in-page anchors (`#{id}` unless the node carries an `href`). */
70
+ variant: "toc";
71
+ } & TreeReadonlyProps) | ({
72
+ /** Single-level icon rail. `collapsed` shrinks rows to their
73
+ * leading icon; nested children are ignored. */
74
+ variant: "sidebar";
75
+ collapsed?: boolean;
76
+ } & Omit<TreeReadonlyProps, "collapsed">) | {
77
+ /** File explorer: only leaves navigate, folders toggle. Compact
78
+ * spacing; rename, drag-and-drop and trailing actions enabled. */
79
+ variant: "files";
80
+ /** Enables drag-and-drop. Reorder `items` yourself: `parentId`
81
+ * `null` means the root list, `index` is the slot among the new
82
+ * siblings (computed on the list without the dragged node). */
83
+ onMove?: (id: string, parentId: string | null, index: number) => void;
84
+ /** Enables inline rename (double-click or F2). */
85
+ onRename?: (id: string, label: string) => void;
86
+ /** Row actions revealed on hover/focus — a menu, a delete icon. */
87
+ renderTrailing?: (node: TreeNode) => React.ReactNode;
88
+ collapsed?: never;
89
+ });
90
+ /** Options for `useTree`. Each of `items`, `activeId` and `collapsed` is
91
+ * controlled when passed and hook-owned when its `default*` twin is used
92
+ * instead. */
93
+ export interface UseTreeOptions {
94
+ /** Which shape the tree takes — gates rename/reorder (`files`) and the
95
+ * collapsible rail (`sidebar`). */
96
+ variant: TreeVariant;
97
+ /** Controlled data. With it, `rename`/`move` only report the edit unless
98
+ * you also pass `onItemsChange`. */
99
+ items?: TreeNode[];
100
+ /** Hook-owned data — `rename` and `move` apply the edit themselves. */
101
+ defaultItems?: TreeNode[];
102
+ /** Next tree after a `rename`/`move`. Also enables self-applying edits
103
+ * on top of controlled `items`. */
104
+ onItemsChange?: (items: TreeNode[]) => void;
105
+ /** Controlled selection. Ancestors of the active node expand. */
106
+ activeId?: string;
107
+ defaultActiveId?: string;
108
+ /** Fires for every selection (rows and leaves alike), after the node's
109
+ * own `onSelect` — wire your router's navigate here. */
110
+ onNavigate?: (node: TreeNode) => void;
111
+ /** Start with these ids expanded instead of the `defaultOpen` set. */
112
+ defaultExpandedIds?: Iterable<string>;
113
+ onExpandedChange?: (ids: string[]) => void;
114
+ /** Controlled rail fold (`sidebar` only). */
115
+ collapsed?: boolean;
116
+ defaultCollapsed?: boolean;
117
+ onCollapsedChange?: (collapsed: boolean) => void;
118
+ /** Enables rename (`files`). Not needed when the hook owns the data. */
119
+ onRename?: (id: string, label: string) => void;
120
+ /** Enables drag-and-drop (`files`). `parentId` `null` means the root
121
+ * list, `index` is the slot among the new siblings (computed on the list
122
+ * without the moved node). Not needed when the hook owns the data. */
123
+ onMove?: (id: string, parentId: string | null, index: number) => void;
124
+ /** Expand/collapse cues. Silent until `bindSounds()`. */
125
+ sound?: boolean;
126
+ }
127
+ /** Everything the tree can do, callable from outside the component.
128
+ * Returned by `useTree` and accepted by `<Tree tree={…} />`. */
129
+ export interface TreeController {
130
+ variant: TreeVariant;
131
+ items: TreeNode[];
132
+ /** `navigation` with 3 levels of nodes — depth 0 renders as sections. */
133
+ sectioned: boolean;
134
+ /** Ids of the section headings, empty unless `sectioned`. Pass one to
135
+ * `expand`/`collapse`/`toggleExpanded` to work a whole section. */
136
+ sectionIds: string[];
137
+ sound: boolean;
138
+ getNode: (id: string) => TreeNode | null;
139
+ expandedIds: ReadonlySet<string>;
140
+ isExpanded: (id: string) => boolean;
141
+ expand: (id: string) => void;
142
+ collapse: (id: string) => void;
143
+ toggleExpanded: (id: string) => void;
144
+ expandAll: () => void;
145
+ collapseAll: () => void;
146
+ /** Open every ancestor of `id`, leaving the rest of the tree alone. */
147
+ reveal: (id: string) => void;
148
+ /** Rail folded to icons — always `false` outside `sidebar`. */
149
+ collapsed: boolean;
150
+ setCollapsed: (collapsed: boolean) => void;
151
+ toggleCollapsed: () => void;
152
+ activeId?: string;
153
+ /** Select a node: runs its `onSelect`, then `onNavigate`. Inert on
154
+ * disabled and unknown nodes. */
155
+ select: (id: string) => void;
156
+ /** Whether rename is wired up at all (`files` + a handler or owned data). */
157
+ canRename: boolean;
158
+ /** Rename target id, `null` while idle. */
159
+ renamingId: string | null;
160
+ startRename: (id: string) => void;
161
+ cancelRename: () => void;
162
+ /** Commit a rename and leave rename mode. Blank or unchanged labels are
163
+ * dropped. */
164
+ rename: (id: string, label: string) => void;
165
+ /** Whether reorder is wired up at all. */
166
+ canMove: boolean;
167
+ move: (id: string, parentId: string | null, index: number) => void;
168
+ dnd: TreeDndContextValue;
169
+ }
170
+ /** @internal What a row needs from the root, threaded through context so
171
+ * the recursive rows stay prop-light. */
172
+ export interface TreeContextValue {
173
+ tree: TreeController;
174
+ renderTrailing?: (node: TreeNode) => React.ReactNode;
175
+ }
176
+ /** @internal Drag state shared by every row (all `null`/no-op outside
177
+ * `files`). */
178
+ export interface TreeDndContextValue {
179
+ enabled: boolean;
180
+ draggedId: string | null;
181
+ /** Row the pointer is over and where the drop would land. */
182
+ dropTarget: {
183
+ id: string;
184
+ position: TreeDropPosition;
185
+ } | null;
186
+ onRowDragStart: (event: React.DragEvent, id: string) => void;
187
+ onRowDragOver: (event: React.DragEvent, node: TreeNode) => void;
188
+ onRowDragLeave: (event: React.DragEvent) => void;
189
+ onRowDrop: (event: React.DragEvent, node: TreeNode) => void;
190
+ onRowDragEnd: () => void;
191
+ }
192
+ export {};
@@ -0,0 +1,7 @@
1
+ import { TreeDndContextValue, TreeNode, UseTreeOptions } from './type';
2
+ /** Owns drag state for the `files` variant: which row is in flight, which
3
+ * row the pointer rests on and where the drop would land. Everything is
4
+ * component state — `dataTransfer` is set for interop but never read, so
5
+ * the flow also works where the store is unavailable (tests, some
6
+ * webviews). Without `onMove` every handler is inert. */
7
+ export declare function useTreeDnd(items: readonly TreeNode[], onMove: UseTreeOptions["onMove"]): TreeDndContextValue;
@@ -0,0 +1,20 @@
1
+ import { TreeController, UseTreeOptions } from './type';
2
+ /**
3
+ * The tree's headless controller — every behaviour the component exposes,
4
+ * callable from outside it: expand/collapse (a node, a section, the whole
5
+ * tree), fold the `sidebar` rail, select a node, rename, reorder.
6
+ *
7
+ * Each piece of state is controlled-or-uncontrolled in the usual React
8
+ * shape: pass `items`/`activeId`/`collapsed` to own it yourself, or
9
+ * `defaultItems`/`defaultActiveId`/`defaultCollapsed` to let the hook. When
10
+ * the hook owns `items` (or you pass `onItemsChange`), `rename` and `move`
11
+ * apply the edit to the data themselves — otherwise they only report it
12
+ * through `onRename`/`onMove` and you reduce the tree yourself.
13
+ *
14
+ * ```tsx
15
+ * const tree = useTree({ variant: "files", defaultItems: files })
16
+ * <Tree tree={tree} />
17
+ * <Button onClick={tree.collapseAll}>Collapse all</Button>
18
+ * ```
19
+ */
20
+ export declare function useTree(options: UseTreeOptions): TreeController;