@exegia/corpora-ui 0.20.0 → 0.22.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 (165) hide show
  1. package/dist-lib/components/blocks/auth/auth-flow-atom.d.ts +77 -0
  2. package/dist-lib/components/blocks/auth/auth-session-atom.d.ts +28 -0
  3. package/dist-lib/components/blocks/auth/auth-state-type.d.ts +71 -0
  4. package/dist-lib/components/blocks/auth/auth-state.d.ts +9 -0
  5. package/dist-lib/components/blocks/auth/use-auth-state.d.ts +52 -0
  6. package/dist-lib/components/blocks/layout.d.ts +4 -1
  7. package/dist-lib/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.d.ts +1 -0
  8. package/dist-lib/components/blocks/nav/sidebar/ai-sidebar-atom.d.ts +217 -0
  9. package/dist-lib/components/blocks/nav/sidebar/index.d.ts +3 -1
  10. package/dist-lib/components/blocks/nav/sidebar/sidebar-context.d.ts +3 -0
  11. package/dist-lib/components/blocks/nav/sidebar/sidebar-row.d.ts +3 -1
  12. package/dist-lib/components/blocks/nav/sidebar/type.d.ts +109 -29
  13. package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar-state.d.ts +29 -0
  14. package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar.d.ts +6 -1
  15. package/dist-lib/components/blocks/nav/sidebar/utils.d.ts +1 -1
  16. package/dist-lib/components/blocks/profile/index.d.ts +7 -0
  17. package/dist-lib/components/blocks/profile/profile-card-atom.d.ts +68 -0
  18. package/dist-lib/components/blocks/profile/profile-card-block.d.ts +28 -39
  19. package/dist-lib/components/blocks/profile/type.d.ts +80 -0
  20. package/dist-lib/components/blocks/profile/use-profile-card-state.d.ts +24 -0
  21. package/dist-lib/components/blocks/profile/use-profile-card.d.ts +34 -0
  22. package/dist-lib/components/blocks/shell/__tests__/shell-fit-atom.test.d.ts +1 -0
  23. package/dist-lib/components/blocks/shell/__tests__/shell-metrics.test.d.ts +1 -0
  24. package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +1 -1
  25. package/dist-lib/components/blocks/shell/animated-panel.d.ts +1 -1
  26. package/dist-lib/components/blocks/shell/index.d.ts +4 -12
  27. package/dist-lib/components/blocks/shell/shell-fit-atom.d.ts +68 -0
  28. package/dist-lib/components/blocks/shell/shell-metrics.d.ts +54 -0
  29. package/dist-lib/components/blocks/shell/type.d.ts +115 -3
  30. package/dist-lib/components/blocks/shell/use-shell-fit-state.d.ts +25 -0
  31. package/dist-lib/components/blocks/shell/use-shell-fit.d.ts +14 -0
  32. package/dist-lib/components/blocks/shell/use-shell-panels.d.ts +12 -1
  33. package/dist-lib/components/blocks/shell/utils.d.ts +32 -0
  34. package/dist-lib/components/composed/__tests__/logo.test.d.ts +1 -0
  35. package/dist-lib/components/composed/logo.d.ts +34 -0
  36. package/dist-lib/components/composed/tree/__tests__/tree-atom.test.d.ts +1 -0
  37. package/dist-lib/components/composed/tree/constants.d.ts +4 -4
  38. package/dist-lib/components/composed/tree/index.d.ts +3 -1
  39. package/dist-lib/components/composed/tree/tree-atom.d.ts +160 -0
  40. package/dist-lib/components/composed/tree/tree-node.d.ts +6 -1
  41. package/dist-lib/components/composed/tree/type.d.ts +116 -18
  42. package/dist-lib/components/composed/tree/use-tree-dnd.d.ts +18 -7
  43. package/dist-lib/components/composed/tree/use-tree-state.d.ts +25 -0
  44. package/dist-lib/components/composed/tree/use-tree.d.ts +4 -0
  45. package/dist-lib/components/composed/user-avatar.d.ts +5 -28
  46. package/dist-lib/components/user-avatar/__tests__/user-avatar.test.d.ts +1 -0
  47. package/dist-lib/components/user-avatar/component.d.ts +1 -8
  48. package/dist-lib/components/user-avatar/index.d.ts +5 -0
  49. package/dist-lib/components/user-avatar/presence-badge.d.ts +16 -0
  50. package/dist-lib/components/user-avatar/type.d.ts +53 -0
  51. package/dist-lib/components/user-avatar/use-user-avatar-state.d.ts +26 -0
  52. package/dist-lib/components/user-avatar/use-user-avatar.d.ts +37 -0
  53. package/dist-lib/components/user-avatar/user-avatar-atom.d.ts +64 -0
  54. package/dist-lib/components/user-avatar/utils.d.ts +22 -0
  55. package/dist-lib/index.d.ts +6 -3
  56. package/dist-lib/index.js +3732 -2670
  57. package/dist-lib/index.js.map +1 -1
  58. package/dist-lib/state/exegia-provider.d.ts +51 -0
  59. package/dist-lib/state/index.d.ts +4 -0
  60. package/dist-lib/state/store.d.ts +17 -0
  61. package/package.json +15 -12
  62. package/src/components/blocks/auth/__tests__/auth-state-atom.test.tsx +247 -0
  63. package/src/components/blocks/auth/auth-flow-atom.ts +238 -0
  64. package/src/components/blocks/auth/auth-session-atom.ts +82 -0
  65. package/src/components/blocks/auth/auth-state-type.ts +97 -0
  66. package/src/components/blocks/auth/auth-state.ts +52 -0
  67. package/src/components/blocks/auth/use-auth-state.ts +127 -0
  68. package/src/components/blocks/layout.ts +30 -2
  69. package/src/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.tsx +325 -0
  70. package/src/components/blocks/nav/sidebar/ai-sidebar-atom.ts +698 -0
  71. package/src/components/blocks/nav/sidebar/ai-sidebar.tsx +48 -42
  72. package/src/components/blocks/nav/sidebar/index.ts +53 -1
  73. package/src/components/blocks/nav/sidebar/sidebar-context.ts +15 -0
  74. package/src/components/blocks/nav/sidebar/sidebar-row.tsx +119 -50
  75. package/src/components/blocks/nav/sidebar/type.ts +125 -30
  76. package/src/components/blocks/nav/sidebar/use-ai-sidebar-state.ts +123 -0
  77. package/src/components/blocks/nav/sidebar/use-ai-sidebar.ts +301 -248
  78. package/src/components/blocks/nav/sidebar/utils.ts +1 -1
  79. package/src/components/blocks/profile/__tests__/profile-card-block.test.tsx +194 -0
  80. package/src/components/blocks/profile/index.ts +28 -0
  81. package/src/components/blocks/profile/profile-card-atom.ts +247 -0
  82. package/src/components/blocks/profile/profile-card-block.tsx +153 -74
  83. package/src/components/blocks/profile/type.ts +95 -0
  84. package/src/components/blocks/profile/use-profile-card-state.ts +67 -0
  85. package/src/components/blocks/profile/use-profile-card.ts +126 -0
  86. package/src/components/blocks/shell/__tests__/shell-fit-atom.test.tsx +360 -0
  87. package/src/components/blocks/shell/__tests__/shell-layout.test.tsx +192 -3
  88. package/src/components/blocks/shell/__tests__/shell-metrics.test.ts +108 -0
  89. package/src/components/blocks/shell/animated-panel-inset.tsx +5 -1
  90. package/src/components/blocks/shell/animated-panel-provider.tsx +77 -11
  91. package/src/components/blocks/shell/animated-panel-trigger.tsx +4 -0
  92. package/src/components/blocks/shell/animated-panel.tsx +126 -29
  93. package/src/components/blocks/shell/index.ts +18 -13
  94. package/src/components/blocks/shell/shell-fit-atom.ts +243 -0
  95. package/src/components/blocks/shell/shell-layout.tsx +55 -53
  96. package/src/components/blocks/shell/shell-metrics.ts +79 -0
  97. package/src/components/blocks/shell/type.ts +130 -3
  98. package/src/components/blocks/shell/use-shell-fit-state.ts +49 -0
  99. package/src/components/blocks/shell/use-shell-fit.ts +135 -0
  100. package/src/components/blocks/shell/use-shell-panels.ts +57 -4
  101. package/src/components/blocks/shell/utils.ts +44 -4
  102. package/src/components/composed/__tests__/logo.test.tsx +60 -0
  103. package/src/components/composed/logo.tsx +159 -0
  104. package/src/components/composed/tree/CLAUDE.md +132 -0
  105. package/src/components/composed/tree/__tests__/tree-atom.test.tsx +217 -0
  106. package/src/components/composed/tree/__tests__/tree.test.tsx +162 -37
  107. package/src/components/composed/tree/__tests__/use-tree.test.tsx +2 -1
  108. package/src/components/composed/tree/constants.ts +4 -4
  109. package/src/components/composed/tree/index.ts +38 -0
  110. package/src/components/composed/tree/tree-atom.ts +590 -0
  111. package/src/components/composed/tree/tree-node.tsx +117 -78
  112. package/src/components/composed/tree/tree.tsx +24 -16
  113. package/src/components/composed/tree/type.ts +125 -15
  114. package/src/components/composed/tree/use-tree-dnd.ts +82 -51
  115. package/src/components/composed/tree/use-tree-state.ts +105 -0
  116. package/src/components/composed/tree/use-tree.ts +185 -184
  117. package/src/components/composed/user-avatar.tsx +11 -99
  118. package/src/components/user-avatar/__tests__/user-avatar.test.tsx +286 -0
  119. package/src/components/user-avatar/component.tsx +151 -22
  120. package/src/components/user-avatar/fallback.tsx +3 -1
  121. package/src/components/user-avatar/index.ts +19 -0
  122. package/src/components/user-avatar/presence-badge.tsx +56 -0
  123. package/src/components/user-avatar/type.ts +58 -0
  124. package/src/components/user-avatar/use-user-avatar-state.ts +60 -0
  125. package/src/components/user-avatar/use-user-avatar.ts +180 -0
  126. package/src/components/user-avatar/user-avatar-atom.ts +218 -0
  127. package/src/components/user-avatar/utils.ts +94 -0
  128. package/src/index.ts +35 -3
  129. package/src/state/exegia-provider.tsx +79 -0
  130. package/src/state/index.ts +4 -0
  131. package/src/state/store.ts +19 -0
  132. package/dist-lib/components/blocks/nav/sidebar-block.d.ts +0 -14
  133. package/dist-lib/components/blocks/nav/sidebar-nav-row.d.ts +0 -3
  134. package/dist-lib/components/blocks/nav/types.d.ts +0 -60
  135. package/dist-lib/components/blocks/nav/utils.d.ts +0 -1
  136. package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +0 -2
  137. package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +0 -2
  138. package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +0 -2
  139. package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +0 -2
  140. package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +0 -2
  141. package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +0 -2
  142. package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +0 -3
  143. package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +0 -2
  144. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +0 -3
  145. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +0 -2
  146. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +0 -2
  147. package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +0 -2
  148. package/src/components/blocks/nav/__tests__/sidebar-block.test.tsx +0 -125
  149. package/src/components/blocks/nav/sidebar-block.tsx +0 -142
  150. package/src/components/blocks/nav/sidebar-nav-row.tsx +0 -78
  151. package/src/components/blocks/nav/types.ts +0 -65
  152. package/src/components/blocks/nav/utils.ts +0 -4
  153. package/src/components/blocks/shell/animated-sidebar-content.tsx +0 -19
  154. package/src/components/blocks/shell/animated-sidebar-footer.tsx +0 -19
  155. package/src/components/blocks/shell/animated-sidebar-group-content.tsx +0 -16
  156. package/src/components/blocks/shell/animated-sidebar-group-label.tsx +0 -29
  157. package/src/components/blocks/shell/animated-sidebar-group.tsx +0 -16
  158. package/src/components/blocks/shell/animated-sidebar-header.tsx +0 -16
  159. package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +0 -151
  160. package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +0 -20
  161. package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +0 -85
  162. package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +0 -19
  163. package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +0 -44
  164. package/src/components/blocks/shell/animated-sidebar-menu.tsx +0 -26
  165. /package/dist-lib/components/blocks/{nav/__tests__/sidebar-block.test.d.ts → auth/__tests__/auth-state-atom.test.d.ts} +0 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The shell's layout arithmetic, as plain functions over px.
3
+ *
4
+ * Nothing here touches React or the DOM. This module IS the contract of who
5
+ * gets which column, which keeps the rule unit-testable on its own and lets
6
+ * the shell-fit atoms (`shellFitFitsAtom`, `shellFitPanelWidthAtom`) call it
7
+ * without restating it: `useShellFit` only supplies the measurements.
8
+ */
9
+ /** The px the shell lays itself out with. Every field is measured from a CSS
10
+ * variable (see `SHELL_WIDTHS`), never hard-coded, so a consumer's override
11
+ * flows straight through the rule. */
12
+ export interface ShellMetrics {
13
+ /** What the left rail occupies right now — its expanded width, its icon
14
+ * width, or 0 when there is no rail at all (or it is off canvas). */
15
+ rail: number;
16
+ /** The floor the body refuses to go below. */
17
+ insetMin: number;
18
+ /** The secondary panel's floor, which is also the width it opens at. */
19
+ panelMin: number;
20
+ /** The viewport all three columns share. */
21
+ viewport: number;
22
+ /** px the shell's own frame eats before any column gets a share: its
23
+ * padding, plus the gap between columns.
24
+ *
25
+ * Deliberately absent from `fitsPanel` — that rule is stated against the raw
26
+ * viewport — but subtracted from the resize ceiling, where ignoring it lets
27
+ * a full-width drag push the row past the shell by exactly this much. */
28
+ chrome: number;
29
+ }
30
+ /** px the shell needs before a secondary panel can exist: the rail as it
31
+ * stands, plus the body and the panel at their own floors. */
32
+ export declare function requiredWidth({ rail, insetMin, panelMin }: ShellMetrics): number;
33
+ /**
34
+ * Whether the shell can hold a secondary panel at all. Strictly `<`: a shell
35
+ * that fits its columns exactly has no room left to give one.
36
+ *
37
+ * An unmeasurable shell fails open. A server render, a `display: none` host
38
+ * or a test environment with no layout engine all report 0, and a panel must
39
+ * never disappear over a reading the layout could not produce.
40
+ */
41
+ export declare function fitsPanel(metrics: ShellMetrics): boolean;
42
+ /** How wide the secondary panel may be: never under its own floor, never past
43
+ * the slack the body holds above its floor. `max` never drops below `min`, so
44
+ * a shell that does not fit reports a degenerate range instead of an inverted
45
+ * one — `fitsPanel` is what hides the panel, not a negative bound. */
46
+ export declare function panelBounds(metrics: ShellMetrics): {
47
+ min: number;
48
+ max: number;
49
+ };
50
+ export declare function clampPanelWidth(width: number, metrics: ShellMetrics): number;
51
+ /** Resizes fire per pointer move and per resize event, most of them landing
52
+ * on the same numbers — comparing fields keeps those from re-rendering the
53
+ * whole shell. */
54
+ export declare function metricsEqual(a: ShellMetrics, b: ShellMetrics): boolean;
@@ -1,5 +1,68 @@
1
- import { ReactNode, ButtonHTMLAttributes, CSSProperties, HTMLAttributes } from 'react';
1
+ import { ReactNode, RefObject, ButtonHTMLAttributes, CSSProperties, HTMLAttributes } from 'react';
2
2
  import { HTMLMotionProps } from 'motion/react';
3
+ import { ShellMetrics } from './shell-metrics';
4
+ /** The key a shell's fit state is filed under. Pass your own to reach it from
5
+ * anywhere (`useShellFitState("app-shell")`); leave it out and the provider
6
+ * generates one that dies with it. */
7
+ export type ShellFitInstanceId = string;
8
+ /** The range a secondary-panel resize may land in, in px. */
9
+ export interface ShellFitPanelBounds {
10
+ min: number;
11
+ max: number;
12
+ }
13
+ /** What the shell measured of itself — readable by id through
14
+ * `useShellFitState`. */
15
+ export interface ShellFitState {
16
+ /** The px the shell last measured, or null before its first measurement
17
+ * (a server render, a host with no layout). */
18
+ metrics: ShellMetrics | null;
19
+ /** Whether the shell can hold a secondary panel. True while unmeasured, so
20
+ * a panel never flickers out over a reading that has not happened yet. */
21
+ fits: boolean;
22
+ /** The secondary panel's width in px, already clamped to `bounds` — null
23
+ * before the first measurement, where the panel falls back to
24
+ * `--panel-width`. */
25
+ panelWidth: number | null;
26
+ /** The range a resize may land in. */
27
+ bounds: ShellFitPanelBounds;
28
+ }
29
+ /** Writes only — drive a shell's secondary panel by id through
30
+ * `useShellFitActions` without re-rendering when it moves. */
31
+ export interface ShellFitActions {
32
+ /** Resize the secondary panel. Clamped on the way in, so a drag past the
33
+ * gutter parks at the bound instead of banking travel it has to give back. */
34
+ resizePanel: (width: number) => void;
35
+ /** Back to the width the shell mounted with — `defaultPanelWidth`, or
36
+ * `--panel-width` when there was none. */
37
+ resetPanelWidth: () => void;
38
+ }
39
+ /** What `useShellFit` hands the provider: the state, the actions and the id
40
+ * they are filed under. */
41
+ export interface ShellFitController extends ShellFitState, ShellFitActions {
42
+ shellId: ShellFitInstanceId;
43
+ }
44
+ export interface UseShellFitOptions {
45
+ /** File the fit under this id so it can be read elsewhere and outlive the
46
+ * shell. Generated (and dropped on unmount) when omitted. */
47
+ shellId?: ShellFitInstanceId;
48
+ /** The element the shell's CSS variables live on (the provider wrapper). */
49
+ hostRef: RefObject<HTMLElement | null>;
50
+ /** Whether the left rail is expanded right now — a render input, so a fold
51
+ * counts the moment React commits it rather than a frame later when the
52
+ * width animation has moved. */
53
+ railOpen: boolean;
54
+ /** The width the secondary panel opens at, in px, instead of
55
+ * `--panel-width`. Read once, on mount. */
56
+ defaultPanelWidth?: number;
57
+ /** Fired whenever a measurement lands on "no room": the shell uses it to
58
+ * retire the panel's open state instead of parking it. */
59
+ onUnfit?: () => void;
60
+ }
61
+ /** @internal What an instance starts from, restored by `resetShellPanelWidthAtom`. */
62
+ export interface ShellFitSeed {
63
+ /** The requested panel width at mount — null for `--panel-width`. */
64
+ panelWidth: number | null;
65
+ }
3
66
  export interface ShellAction {
4
67
  id: string;
5
68
  label: string;
@@ -38,27 +101,53 @@ export type TPanelMap<Side extends TPanelSide = TPanelSide> = Partial<Record<Sid
38
101
  * AnimatedPanelProvider — every prop is keyed by side, there are no
39
102
  * explicit per-side props. `useShellPanels` produces the controlled subset
40
103
  * of these as `providerProps`. */
41
- export type ShellPanelControlProps = Pick<AnimatedSidebarProviderProps, "open" | "defaultOpen" | "onOpenChange" | "openMobile" | "defaultOpenMobile" | "onOpenMobileChange">;
104
+ export type ShellPanelControlProps = Pick<AnimatedSidebarProviderProps, "open" | "defaultOpen" | "onOpenChange" | "openMobile" | "defaultOpenMobile" | "onOpenMobileChange" | "shellId" | "defaultPanelWidth">;
42
105
  export interface UseShellPanelsOptions {
106
+ /** The id the shell's fit state is filed under. Name it to read the same
107
+ * shell from elsewhere (`useShellFitState("app-shell")`) and to keep a
108
+ * dragged panel width across a route change; otherwise the hook generates
109
+ * one that dies with it. */
110
+ shellId?: ShellFitInstanceId;
43
111
  /** Initial desktop open state per side, merged over
44
112
  * `{ left: true, right: false }`. */
45
113
  defaultOpen?: AnimatedSidebarProviderProps["defaultOpen"];
46
114
  /** Initial mobile overlay state per side — every side starts closed. */
47
115
  defaultOpenMobile?: AnimatedSidebarProviderProps["defaultOpenMobile"];
116
+ /** The width the secondary panel opens at, in px, instead of
117
+ * `--panel-width`. */
118
+ defaultPanelWidth?: number;
48
119
  /** Panel change event returning both the next open state and the side of
49
120
  * the panel the change comes from. Mobile overlay changes report the same
50
121
  * side as their desktop counterpart. */
51
122
  onPanelChange?: (open: boolean, side: TPanelSide) => void;
52
123
  }
53
124
  export interface ShellPanelControls {
125
+ /** The id the shell's fit state is filed under — hand it to
126
+ * `useShellFitState` / `useShellFitActions` anywhere below `ExegiaProvider`. */
127
+ shellId: ShellFitInstanceId;
128
+ /** The viewport cannot hold the secondary panel beside the rail and the
129
+ * body at their floors, so the shell has dropped the right panel and its
130
+ * trigger — UI outside the shell should stand down with them. Read straight
131
+ * out of the shell's fit atoms; it is only ever `true` once the shell is
132
+ * mounted and measured. */
133
+ isNarrow: boolean;
134
+ /** The secondary panel's current width in px, or null until the shell has
135
+ * measured itself (the panel then sits at `--panel-width`). */
136
+ panelWidth: number | null;
137
+ /** Resize the secondary panel from outside the shell — clamped to the room
138
+ * the shell has. */
139
+ resizePanel: (width: number) => void;
54
140
  /** Live desktop open state, keyed by side. */
55
141
  open: Record<SidebarSide, boolean>;
56
142
  /** Live mobile overlay state, keyed by side. */
57
143
  openMobile: Record<SidebarSide, boolean>;
144
+ /** Refuses to OPEN the right panel while `isNarrow` — there is nothing on
145
+ * screen to open. Closing it always goes through. */
58
146
  setOpen: (open: boolean, side: SidebarSide) => void;
59
147
  setOpenMobile: (open: boolean, side: SidebarSide) => void;
60
148
  /** Desktop-only convenience — the in-shell triggers already pick the
61
- * mobile state themselves when the viewport is narrow. */
149
+ * mobile state themselves when the viewport is narrow. Carries the same
150
+ * `isNarrow` refusal as `setOpen`. */
62
151
  toggle: (side: SidebarSide) => void;
63
152
  /** Spread onto ShellLayout (or AnimatedPanelProvider directly). */
64
153
  providerProps: ShellPanelControlProps;
@@ -80,6 +169,10 @@ export type SidebarVariant = "sidebar" | "floating" | "inset";
80
169
  export type SidebarCollapsible = "offcanvas" | "icon" | "none";
81
170
  export interface AnimatedSidebarContextValue {
82
171
  isMobile: boolean;
172
+ /** What the shell measured of itself: whether it can hold a secondary panel
173
+ * at all, how wide that panel is, and the range a resize may land in. The
174
+ * right panel and its trigger stand down when `fit.fits` is false. */
175
+ fit: ShellFitController;
83
176
  layoutId: string;
84
177
  /** Desktop open state, keyed by side. */
85
178
  open: Record<SidebarSide, boolean>;
@@ -93,6 +186,14 @@ export interface AnimatedSidebarContextValue {
93
186
  triggerRefs: Record<SidebarSide, React.RefObject<HTMLButtonElement | null>>;
94
187
  }
95
188
  export interface AnimatedSidebarProviderProps extends HTMLAttributes<HTMLDivElement> {
189
+ /** The id this shell's fit state (whether a secondary panel fits, how wide
190
+ * it is) is filed under in the store. Name it to read or drive the shell
191
+ * from elsewhere and to keep a dragged width across a route change; omit it
192
+ * and the provider generates one that is dropped on unmount. */
193
+ shellId?: ShellFitInstanceId;
194
+ /** The width the secondary panel opens at, in px, instead of
195
+ * `--panel-width`. Read once, on mount. */
196
+ defaultPanelWidth?: number;
96
197
  /** Controlled desktop open state, keyed by the side. A side left undefined
97
198
  * stays uncontrolled. */
98
199
  open?: SidebarOpenState;
@@ -104,12 +205,23 @@ export interface AnimatedSidebarProviderProps extends HTMLAttributes<HTMLDivElem
104
205
  /** Initial mobile overlay state — every side starts closed. */
105
206
  defaultOpenMobile?: SidebarOpenState;
106
207
  onOpenMobileChange?: (open: boolean, side: SidebarSide) => void;
208
+ /** Fires when the shell crosses the width a secondary panel needs (rail +
209
+ * body + panel at their floors). An imperative escape hatch for a consumer
210
+ * that mounts the provider on its own; anything under `ExegiaProvider` can
211
+ * subscribe by id instead with `useShellFitState(shellId).fits`. */
212
+ onNarrowChange?: (isNarrow: boolean) => void;
107
213
  style?: SidebarProviderStyle;
108
214
  }
109
215
  export type SidebarProviderStyle = CSSProperties & {
216
+ /** Left rail, expanded. */
110
217
  "--sidebar-width"?: string;
218
+ /** Left rail, folded to icons. */
111
219
  "--sidebar-width-icon"?: string;
112
220
  "--sidebar-width-mobile"?: string;
221
+ /** The secondary panel's floor, and the width it opens at. */
222
+ "--panel-width"?: string;
223
+ /** The body's floor — the secondary panel may never squeeze it past this. */
224
+ "--inset-min-width"?: string;
113
225
  };
114
226
  export type AnimatedSidebarInsetProps = HTMLMotionProps<"main">;
115
227
  export interface AnimatedSidebarTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
@@ -0,0 +1,25 @@
1
+ import { ShellFitActions, ShellFitInstanceId, ShellFitState } from './type';
2
+ /**
3
+ * Read the fit of the shell registered under `shellId` from anywhere below
4
+ * `ExegiaProvider` — no controller, no props, no provider of its own.
5
+ *
6
+ * ```tsx
7
+ * const { fits, panelWidth } = useShellFitState("app-shell")
8
+ * ```
9
+ *
10
+ * This returns the whole state object, so the caller re-renders on every
11
+ * measurement. A component that reads one field should subscribe to that
12
+ * field's atom instead: `useAtomValue(shellFitFitsAtom("app-shell"))`.
13
+ */
14
+ export declare function useShellFitState(shellId: ShellFitInstanceId): ShellFitState;
15
+ /**
16
+ * Drive the secondary panel of the shell registered under `shellId` from
17
+ * anywhere. Writes only — the caller never re-renders when the shell moves,
18
+ * so this is what a command palette or a keyboard shortcut should reach for.
19
+ *
20
+ * ```tsx
21
+ * const shell = useShellFitActions("app-shell")
22
+ * <Button onClick={() => shell.resizePanel(480)}>Wide inspector</Button>
23
+ * ```
24
+ */
25
+ export declare function useShellFitActions(shellId: ShellFitInstanceId): ShellFitActions;
@@ -0,0 +1,14 @@
1
+ import { ShellFitController, UseShellFitOptions } from './type';
2
+ /**
3
+ * Measures the shell and decides what the secondary panel may do: whether it
4
+ * exists at all, and how wide it may be dragged.
5
+ *
6
+ * The three things that move the answer are all observed here — the viewport
7
+ * (`resize`), the rail's fold (`railOpen`, a render input) and the user's own
8
+ * drag (`resizePanel`) — so no caller has to re-derive it. The measurement is
9
+ * the only thing this hook keeps to itself: the numbers land in the shell-fit
10
+ * atoms keyed by `shellId`, so anything under `ExegiaProvider` can read the
11
+ * verdict (`useShellFitState`) or move the panel (`useShellFitActions`)
12
+ * without holding this controller.
13
+ */
14
+ export declare function useShellFit({ shellId: explicitId, hostRef, railOpen, defaultPanelWidth, onUnfit, }: UseShellFitOptions): ShellFitController;
@@ -5,5 +5,16 @@ import { ShellPanelControls, UseShellPanelsOptions } from './type';
5
5
  * callback. Spread the returned `providerProps` onto ShellLayout; the
6
6
  * setters and `toggle` are for UI that lives outside the shell (title-bar
7
7
  * buttons, command palette, shortcuts).
8
+ *
9
+ * The way back down is the store: `providerProps` carries a `shellId`, the
10
+ * shell files its measurement under it, and this hook reads `isNarrow` and
11
+ * `panelWidth` straight out of those atoms — so outside UI stands down with
12
+ * the panel instead of measuring `--sidebar-width` a second time. Name the
13
+ * `shellId` and any component below `ExegiaProvider` can do the same with
14
+ * `useShellFitState(shellId)`.
15
+ *
16
+ * Call it under the same `ExegiaProvider` as the shell it drives (or under
17
+ * none at all, on both sides): mounted above the provider it would read
18
+ * Jotai's default store while the shell writes to the provider's.
8
19
  */
9
- export declare function useShellPanels({ defaultOpen, defaultOpenMobile, onPanelChange, }?: UseShellPanelsOptions): ShellPanelControls;
20
+ export declare function useShellPanels({ shellId: explicitId, defaultOpen, defaultOpenMobile, defaultPanelWidth, onPanelChange, }?: UseShellPanelsOptions): ShellPanelControls;
@@ -29,6 +29,38 @@ export declare const SUBMENU_ITEM_VARIANTS: Variants;
29
29
  export declare const FOCUSABLE_SELECTOR: string;
30
30
  export declare const MOBILE_QUERY = "(max-width: 767px)";
31
31
  export declare const SIDEBAR_KEYBOARD_SHORTCUT = "b";
32
+ /**
33
+ * The shell's layout contract, in px. Every entry lands on the provider's
34
+ * wrapper as a CSS variable, so a consumer overrides a column by restyling it
35
+ * rather than by passing a prop:
36
+ *
37
+ * | variable | column |
38
+ * | ---------------------- | ----------------------------------------- |
39
+ * | `--sidebar-width` | left rail, expanded |
40
+ * | `--sidebar-width-icon` | left rail, folded to icons |
41
+ * | `--panel-width` | secondary panel: its floor AND its default |
42
+ * | `--inset-min-width` | the body's floor |
43
+ *
44
+ * These are only the defaults. Every measurement resolves the live variable,
45
+ * so an override wins over the value written here.
46
+ */
47
+ export declare const SHELL_WIDTHS: {
48
+ readonly "--sidebar-width": "256px";
49
+ readonly "--sidebar-width-icon": "56px";
50
+ readonly "--sidebar-width-mobile": "18rem";
51
+ readonly "--panel-width": "320px";
52
+ readonly "--inset-min-width": "360px";
53
+ };
54
+ /** The expanded width of a panel docked to `side`: the secondary panel opens
55
+ * at `--panel-width`, the primary rail at `--sidebar-width`. */
56
+ export declare function expandedWidthVar(side: "left" | "right"): "var(--panel-width)" | "var(--sidebar-width)";
57
+ /** Resolve a CSS length — `var()` included — to px inside `host`'s cascade.
58
+ * Custom properties inherit, so a throwaway probe mounted in `host` reads the
59
+ * very `--sidebar-width` the shell lays out with, including a value a
60
+ * consumer overrode on the provider. Returns 0 when `host` has no layout
61
+ * (server render, `display: none`), so callers must fail open on 0 rather
62
+ * than treat it as a real measurement. */
63
+ export declare function resolveLength(host: HTMLElement, value: string): number;
32
64
  export declare const AnimatedSidebarContext: import('react').Context<AnimatedSidebarContextValue | null>;
33
65
  export declare const AnimatedSidebarPanelContext: import('react').Context<AnimatedSidebarPanelContextValue | null>;
34
66
  export declare function useAnimatedSidebar(): AnimatedSidebarContextValue;
@@ -0,0 +1,34 @@
1
+ import * as React from "react";
2
+ /** `full` shows mark + wordmark; `mark` folds the wordmark away — an icon
3
+ * rail, a favicon-sized corner. */
4
+ export type LogoVariant = "full" | "mark";
5
+ export interface LogoProps extends Omit<React.HTMLAttributes<HTMLElement>, "children"> {
6
+ /**
7
+ * Brand name. Labels the logo for AT (and the link, when `href` renders
8
+ * one), drives the default wordmark, and the monogram tile when no mark
9
+ * is given.
10
+ */
11
+ name: string;
12
+ /** Custom mark — an inline SVG sized to fill its box. Wins over `src`. */
13
+ mark?: React.ReactNode;
14
+ /** Image URL for the mark. Decorative — `name` labels the logo. */
15
+ src?: string;
16
+ /** Wordmark content. Defaults to `name`. */
17
+ wordmark?: React.ReactNode;
18
+ /** `full` (default) or `mark` — the wordmark folds away, animated. */
19
+ variant?: LogoVariant;
20
+ /** Renders the logo as a link — the usual "mark goes home" affordance. */
21
+ href?: string;
22
+ }
23
+ /**
24
+ * Brand lockup: a mark beside a wordmark. The mark is whatever the brand
25
+ * has — an SVG (`mark`), an image (`src`), or, given neither, a monogram
26
+ * tile derived from `name`. `variant="mark"` folds the wordmark away with
27
+ * the same motion a collapsing rail uses, so the lockup can sit in one and
28
+ * follow its fold. With `href` the whole lockup is a link named by `name`.
29
+ *
30
+ * Sizing rides on the mark: it defaults to `size-8`; restyle via
31
+ * `[&_[data-slot=logo-mark]]:size-*` or wrap in a text-size context for the
32
+ * wordmark, which inherits.
33
+ */
34
+ export declare function Logo({ name, mark, src, wordmark, variant, href, className, ...props }: LogoProps): React.ReactElement;
@@ -9,10 +9,10 @@ export declare const TREE_CLOSE_DURATION = 0.15;
9
9
  export declare const TREE_MICRO_DURATION = 0.15;
10
10
  /** Sidebar rail label reveal/hide when `collapsed` flips. */
11
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;
12
+ /** The collapsed rail's width in px — one `h-10 rounded-xl` icon row, edge
13
+ * to edge. Kept in sync with the `w-10` resting class the rail falls back to
14
+ * before its expanded width has been measured. */
15
+ export declare const RAIL_COLLAPSED_WIDTH = 40;
16
16
  /** Rail label text fade — starts as the width growth is finishing, so the
17
17
  * label never reads as clipped mid-grow. */
18
18
  export declare const TREE_LABEL_REVEAL_DELAY = 0.01;
@@ -1,4 +1,6 @@
1
1
  export { Tree } from './tree';
2
2
  export { useTree } from './use-tree';
3
+ export { useTreeActions, useTreeState } from './use-tree-state';
3
4
  export { moveNode, renameNode } from './utils';
4
- export type { TreeController, TreeControllerProps, TreeDataProps, TreeDropPosition, TreeNode, TreeProps, TreeVariant, UseTreeOptions, } from './type';
5
+ export { cancelTreeRenameAtom, collapseAllTreeNodesAtom, collapseTreeNodeAtom, expandAllTreeNodesAtom, expandTreeNodeAtom, moveTreeNodeAtom, removeTreeInstance, renameTreeNodeAtom, resetTreeAtom, revealTreeNodeAtom, selectTreeNodeAtom, setTreeCollapsedAtom, setTreeItemsAtom, startTreeRenameAtom, toggleTreeCollapsedAtom, toggleTreeNodeAtom, treeActiveIdAtom, treeCanMoveAtom, treeCanRenameAtom, treeCollapsedAtom, treeDraggedIdAtom, treeDropTargetAtom, treeExpandedIdsAtom, treeItemsAtom, treeRenamingIdAtom, treeSectionedAtom, treeSectionIdsAtom, treeStateAtom, } from './tree-atom';
6
+ export type { TreeActions, TreeController, TreeControllerProps, TreeDataProps, TreeDropPosition, TreeDropTarget, TreeInstanceId, TreeNode, TreeProps, TreeState, TreeVariant, UseTreeOptions, } from './type';
@@ -0,0 +1,160 @@
1
+ import { Atom } from 'jotai';
2
+ import { TreeConfig, TreeDropPosition, TreeDropTarget, TreeHandlers, TreeInstanceId, TreeNode, TreeSeed, TreeState, TreeVariant } from './type';
3
+ /** What a tree reads as before `useTree` publishes its options. */
4
+ export declare const DEFAULT_TREE_CONFIG: TreeConfig;
5
+ /**
6
+ * A string-keyed atom family.
7
+ *
8
+ * `jotai/utils`' `atomFamily` is deprecated for Jotai v3, and we need only
9
+ * the string-keyed case with a `remove` — so this stays in-house rather than
10
+ * adding `jotai-family` as a second Jotai package to keep version-aligned.
11
+ * Dropping a key lets the store's WeakMap release that instance's state.
12
+ */
13
+ type Family<AtomType> = ((id: TreeInstanceId) => AtomType) & {
14
+ remove: (id: TreeInstanceId) => void;
15
+ };
16
+ /** @internal */
17
+ export declare const treeConfigAtom: Family<import('jotai').PrimitiveAtom<TreeConfig> & {
18
+ init: TreeConfig;
19
+ }>;
20
+ /** @internal Refreshed every commit; nothing subscribes, so it is free. */
21
+ export declare const treeHandlersAtom: Family<import('jotai').PrimitiveAtom<TreeHandlers> & {
22
+ init: TreeHandlers;
23
+ }>;
24
+ /** The tree's current data — hook-owned, or the projection of a controlled
25
+ * `items` prop. */
26
+ export declare const treeItemsAtom: Family<import('jotai').PrimitiveAtom<TreeNode[]> & {
27
+ init: TreeNode[];
28
+ }>;
29
+ export declare const treeActiveIdAtom: Family<import('jotai').PrimitiveAtom<string | undefined> & {
30
+ init: string | undefined;
31
+ }>;
32
+ export declare const treeExpandedIdsAtom: Family<import('jotai').PrimitiveAtom<ReadonlySet<string>> & {
33
+ init: ReadonlySet<string>;
34
+ }>;
35
+ /** Raw rail fold. Read `treeCollapsedAtom` for the variant-masked value. */
36
+ export declare const treeRailCollapsedAtom: Family<import('jotai').PrimitiveAtom<boolean> & {
37
+ init: boolean;
38
+ }>;
39
+ export declare const treeRenamingIdAtom: Family<import('jotai').PrimitiveAtom<string | null> & {
40
+ init: string | null;
41
+ }>;
42
+ export declare const treeDraggedIdAtom: Family<import('jotai').PrimitiveAtom<string | null> & {
43
+ init: string | null;
44
+ }>;
45
+ export declare const treeDropTargetAtom: Family<import('jotai').PrimitiveAtom<TreeDropTarget | null> & {
46
+ init: TreeDropTarget | null;
47
+ }>;
48
+ /** @internal The only items atom `useTree` may subscribe to: empty while a
49
+ * controlled `items` prop owns the data, so the projection write never feeds
50
+ * back into the render that produced it. An inline `items={[…]}` array is a
51
+ * new reference every render, and a subscribed round-trip would loop. */
52
+ export declare const treeOwnedItemsAtom: Family<Atom<TreeNode[]>>;
53
+ /** Rail folded to icons — always `false` outside `sidebar`. */
54
+ export declare const treeCollapsedAtom: Family<Atom<boolean>>;
55
+ export declare const treeSectionedAtom: Family<Atom<boolean>>;
56
+ export declare const treeSectionIdsAtom: Family<Atom<string[]>>;
57
+ export declare const treeCanRenameAtom: Family<Atom<boolean>>;
58
+ export declare const treeCanMoveAtom: Family<Atom<boolean>>;
59
+ export declare const treeVariantAtom: Family<Atom<TreeVariant>>;
60
+ export declare const treeSoundAtom: Family<Atom<boolean>>;
61
+ /** Whether this one node's branch is open. */
62
+ export declare const treeNodeExpandedAtom: (id: TreeInstanceId, nodeId: string) => Atom<boolean>;
63
+ /** Whether this one node is the current entry. */
64
+ export declare const treeNodeActiveAtom: (id: TreeInstanceId, nodeId: string) => Atom<boolean>;
65
+ /** Whether this one node is in rename mode. */
66
+ export declare const treeNodeRenamingAtom: (id: TreeInstanceId, nodeId: string) => Atom<boolean>;
67
+ /** Whether this one node is the row in flight. */
68
+ export declare const treeNodeDraggingAtom: (id: TreeInstanceId, nodeId: string) => Atom<boolean>;
69
+ /** Where a drop on this one node would land, `null` when it is not hovered. */
70
+ export declare const treeNodeDropAtom: (id: TreeInstanceId, nodeId: string) => Atom<TreeDropPosition | null>;
71
+ /** The whole state of one tree. Components reading a single field should
72
+ * subscribe to that field's atom instead — this one changes on every edit. */
73
+ export declare const treeStateAtom: Family<Atom<TreeState>>;
74
+ export declare const expandTreeNodeAtom: Family<import('jotai').WritableAtom<null, [nodeId: string], void> & {
75
+ init: null;
76
+ }>;
77
+ export declare const collapseTreeNodeAtom: Family<import('jotai').WritableAtom<null, [nodeId: string], void> & {
78
+ init: null;
79
+ }>;
80
+ export declare const toggleTreeNodeAtom: Family<import('jotai').WritableAtom<null, [nodeId: string], void> & {
81
+ init: null;
82
+ }>;
83
+ export declare const expandAllTreeNodesAtom: Family<import('jotai').WritableAtom<null, [], void> & {
84
+ init: null;
85
+ }>;
86
+ export declare const collapseAllTreeNodesAtom: Family<import('jotai').WritableAtom<null, [], void> & {
87
+ init: null;
88
+ }>;
89
+ /** Open every ancestor of `nodeId` without touching the rest of the tree. */
90
+ export declare const revealTreeNodeAtom: Family<import('jotai').WritableAtom<null, [nodeId: string], void> & {
91
+ init: null;
92
+ }>;
93
+ /** @internal The same, silent. `useTree` runs it whenever the active id
94
+ * changes so a nested entry is never folded away — the hook has never
95
+ * reported that implicit reveal through `onExpandedChange`. */
96
+ export declare const revealTreeAncestorsAtom: Family<import('jotai').WritableAtom<null, [nodeId: string], void> & {
97
+ init: null;
98
+ }>;
99
+ export declare const setTreeCollapsedAtom: Family<import('jotai').WritableAtom<null, [collapsed: boolean], void> & {
100
+ init: null;
101
+ }>;
102
+ export declare const toggleTreeCollapsedAtom: Family<import('jotai').WritableAtom<null, [], void> & {
103
+ init: null;
104
+ }>;
105
+ /** Runs the node's own `onSelect` first, then `onNavigate` — the order a row
106
+ * press follows. Disabled and unknown nodes are inert. */
107
+ export declare const selectTreeNodeAtom: Family<import('jotai').WritableAtom<null, [nodeId: string], void> & {
108
+ init: null;
109
+ }>;
110
+ export declare const startTreeRenameAtom: Family<import('jotai').WritableAtom<null, [nodeId: string], void> & {
111
+ init: null;
112
+ }>;
113
+ export declare const cancelTreeRenameAtom: Family<import('jotai').WritableAtom<null, [], void> & {
114
+ init: null;
115
+ }>;
116
+ /** Commit a rename and leave rename mode. Blank or unchanged labels are
117
+ * dropped. */
118
+ export declare const renameTreeNodeAtom: Family<import('jotai').WritableAtom<null, [nodeId: string, label: string], void> & {
119
+ init: null;
120
+ }>;
121
+ export declare const moveTreeNodeAtom: Family<import('jotai').WritableAtom<null, [nodeId: string, parentId: string | null, index: number], void> & {
122
+ init: null;
123
+ }>;
124
+ /** Replace the data. Reports through `onItemsChange`; writes the atom only
125
+ * when no controlled `items` prop owns it. */
126
+ export declare const setTreeItemsAtom: Family<import('jotai').WritableAtom<null, [items: TreeNode[]], void> & {
127
+ init: null;
128
+ }>;
129
+ export declare const endTreeDragAtom: Family<import('jotai').WritableAtom<null, [], void> & {
130
+ init: null;
131
+ }>;
132
+ /** @internal Publish the latest options; seed the instance once. */
133
+ export declare const mountTreeAtom: Family<import('jotai').WritableAtom<null, [config: TreeConfig, seed: TreeSeed], void> & {
134
+ init: null;
135
+ }>;
136
+ /** @internal */
137
+ export declare const setTreeHandlersAtom: Family<import('jotai').WritableAtom<null, [handlers: TreeHandlers], void> & {
138
+ init: null;
139
+ }>;
140
+ /** @internal One-way projections of controlled props — no callbacks fire. */
141
+ export declare const projectTreeItemsAtom: Family<import('jotai').WritableAtom<null, [items: TreeNode[]], void> & {
142
+ init: null;
143
+ }>;
144
+ /** @internal */
145
+ export declare const projectTreeActiveIdAtom: Family<import('jotai').WritableAtom<null, [activeId: string | undefined], void> & {
146
+ init: null;
147
+ }>;
148
+ /** @internal */
149
+ export declare const projectTreeCollapsedAtom: Family<import('jotai').WritableAtom<null, [collapsed: boolean], void> & {
150
+ init: null;
151
+ }>;
152
+ /** Back to the values the instance mounted with. */
153
+ export declare const resetTreeAtom: Family<import('jotai').WritableAtom<null, [], void> & {
154
+ init: null;
155
+ }>;
156
+ /** Drop every atom for `id`. `useTree` calls this on unmount for trees it
157
+ * keyed itself; an explicit `treeId` outlives its component, so a rail's fold
158
+ * survives a route change. */
159
+ export declare function removeTreeInstance(id: TreeInstanceId): void;
160
+ export {};
@@ -4,4 +4,9 @@ export interface TreeRowProps {
4
4
  node: TreeNode;
5
5
  depth: number;
6
6
  }
7
- export declare function TreeRow({ node, depth }: TreeRowProps): React.ReactElement;
7
+ declare function TreeRowImpl({ node, depth }: TreeRowProps): React.ReactElement;
8
+ /** Rows are memoized so a re-render of the root — a rail measurement, a new
9
+ * `renderTrailing` — does not walk the whole tree. Each row's own state
10
+ * reaches it through its atoms instead. */
11
+ export declare const TreeRow: React.MemoExoticComponent<typeof TreeRowImpl>;
12
+ export {};