@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.
- package/dist-lib/components/blocks/auth/auth-flow-atom.d.ts +77 -0
- package/dist-lib/components/blocks/auth/auth-session-atom.d.ts +28 -0
- package/dist-lib/components/blocks/auth/auth-state-type.d.ts +71 -0
- package/dist-lib/components/blocks/auth/auth-state.d.ts +9 -0
- package/dist-lib/components/blocks/auth/use-auth-state.d.ts +52 -0
- package/dist-lib/components/blocks/layout.d.ts +4 -1
- package/dist-lib/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.d.ts +1 -0
- package/dist-lib/components/blocks/nav/sidebar/ai-sidebar-atom.d.ts +217 -0
- package/dist-lib/components/blocks/nav/sidebar/index.d.ts +3 -1
- package/dist-lib/components/blocks/nav/sidebar/sidebar-context.d.ts +3 -0
- package/dist-lib/components/blocks/nav/sidebar/sidebar-row.d.ts +3 -1
- package/dist-lib/components/blocks/nav/sidebar/type.d.ts +109 -29
- package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar-state.d.ts +29 -0
- package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar.d.ts +6 -1
- package/dist-lib/components/blocks/nav/sidebar/utils.d.ts +1 -1
- package/dist-lib/components/blocks/profile/index.d.ts +7 -0
- package/dist-lib/components/blocks/profile/profile-card-atom.d.ts +68 -0
- package/dist-lib/components/blocks/profile/profile-card-block.d.ts +28 -39
- package/dist-lib/components/blocks/profile/type.d.ts +80 -0
- package/dist-lib/components/blocks/profile/use-profile-card-state.d.ts +24 -0
- package/dist-lib/components/blocks/profile/use-profile-card.d.ts +34 -0
- package/dist-lib/components/blocks/shell/__tests__/shell-fit-atom.test.d.ts +1 -0
- package/dist-lib/components/blocks/shell/__tests__/shell-metrics.test.d.ts +1 -0
- package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +1 -1
- package/dist-lib/components/blocks/shell/animated-panel.d.ts +1 -1
- package/dist-lib/components/blocks/shell/index.d.ts +4 -12
- package/dist-lib/components/blocks/shell/shell-fit-atom.d.ts +68 -0
- package/dist-lib/components/blocks/shell/shell-metrics.d.ts +54 -0
- package/dist-lib/components/blocks/shell/type.d.ts +115 -3
- package/dist-lib/components/blocks/shell/use-shell-fit-state.d.ts +25 -0
- package/dist-lib/components/blocks/shell/use-shell-fit.d.ts +14 -0
- package/dist-lib/components/blocks/shell/use-shell-panels.d.ts +12 -1
- package/dist-lib/components/blocks/shell/utils.d.ts +32 -0
- package/dist-lib/components/composed/__tests__/logo.test.d.ts +1 -0
- package/dist-lib/components/composed/logo.d.ts +34 -0
- package/dist-lib/components/composed/tree/__tests__/tree-atom.test.d.ts +1 -0
- package/dist-lib/components/composed/tree/constants.d.ts +4 -4
- package/dist-lib/components/composed/tree/index.d.ts +3 -1
- package/dist-lib/components/composed/tree/tree-atom.d.ts +160 -0
- package/dist-lib/components/composed/tree/tree-node.d.ts +6 -1
- package/dist-lib/components/composed/tree/type.d.ts +116 -18
- package/dist-lib/components/composed/tree/use-tree-dnd.d.ts +18 -7
- package/dist-lib/components/composed/tree/use-tree-state.d.ts +25 -0
- package/dist-lib/components/composed/tree/use-tree.d.ts +4 -0
- package/dist-lib/components/composed/user-avatar.d.ts +5 -28
- package/dist-lib/components/user-avatar/__tests__/user-avatar.test.d.ts +1 -0
- package/dist-lib/components/user-avatar/component.d.ts +1 -8
- package/dist-lib/components/user-avatar/index.d.ts +5 -0
- package/dist-lib/components/user-avatar/presence-badge.d.ts +16 -0
- package/dist-lib/components/user-avatar/type.d.ts +53 -0
- package/dist-lib/components/user-avatar/use-user-avatar-state.d.ts +26 -0
- package/dist-lib/components/user-avatar/use-user-avatar.d.ts +37 -0
- package/dist-lib/components/user-avatar/user-avatar-atom.d.ts +64 -0
- package/dist-lib/components/user-avatar/utils.d.ts +22 -0
- package/dist-lib/index.d.ts +6 -3
- package/dist-lib/index.js +3732 -2670
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/state/exegia-provider.d.ts +51 -0
- package/dist-lib/state/index.d.ts +4 -0
- package/dist-lib/state/store.d.ts +17 -0
- package/package.json +15 -12
- package/src/components/blocks/auth/__tests__/auth-state-atom.test.tsx +247 -0
- package/src/components/blocks/auth/auth-flow-atom.ts +238 -0
- package/src/components/blocks/auth/auth-session-atom.ts +82 -0
- package/src/components/blocks/auth/auth-state-type.ts +97 -0
- package/src/components/blocks/auth/auth-state.ts +52 -0
- package/src/components/blocks/auth/use-auth-state.ts +127 -0
- package/src/components/blocks/layout.ts +30 -2
- package/src/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.tsx +325 -0
- package/src/components/blocks/nav/sidebar/ai-sidebar-atom.ts +698 -0
- package/src/components/blocks/nav/sidebar/ai-sidebar.tsx +48 -42
- package/src/components/blocks/nav/sidebar/index.ts +53 -1
- package/src/components/blocks/nav/sidebar/sidebar-context.ts +15 -0
- package/src/components/blocks/nav/sidebar/sidebar-row.tsx +119 -50
- package/src/components/blocks/nav/sidebar/type.ts +125 -30
- package/src/components/blocks/nav/sidebar/use-ai-sidebar-state.ts +123 -0
- package/src/components/blocks/nav/sidebar/use-ai-sidebar.ts +301 -248
- package/src/components/blocks/nav/sidebar/utils.ts +1 -1
- package/src/components/blocks/profile/__tests__/profile-card-block.test.tsx +194 -0
- package/src/components/blocks/profile/index.ts +28 -0
- package/src/components/blocks/profile/profile-card-atom.ts +247 -0
- package/src/components/blocks/profile/profile-card-block.tsx +153 -74
- package/src/components/blocks/profile/type.ts +95 -0
- package/src/components/blocks/profile/use-profile-card-state.ts +67 -0
- package/src/components/blocks/profile/use-profile-card.ts +126 -0
- package/src/components/blocks/shell/__tests__/shell-fit-atom.test.tsx +360 -0
- package/src/components/blocks/shell/__tests__/shell-layout.test.tsx +192 -3
- package/src/components/blocks/shell/__tests__/shell-metrics.test.ts +108 -0
- package/src/components/blocks/shell/animated-panel-inset.tsx +5 -1
- package/src/components/blocks/shell/animated-panel-provider.tsx +77 -11
- package/src/components/blocks/shell/animated-panel-trigger.tsx +4 -0
- package/src/components/blocks/shell/animated-panel.tsx +126 -29
- package/src/components/blocks/shell/index.ts +18 -13
- package/src/components/blocks/shell/shell-fit-atom.ts +243 -0
- package/src/components/blocks/shell/shell-layout.tsx +55 -53
- package/src/components/blocks/shell/shell-metrics.ts +79 -0
- package/src/components/blocks/shell/type.ts +130 -3
- package/src/components/blocks/shell/use-shell-fit-state.ts +49 -0
- package/src/components/blocks/shell/use-shell-fit.ts +135 -0
- package/src/components/blocks/shell/use-shell-panels.ts +57 -4
- package/src/components/blocks/shell/utils.ts +44 -4
- package/src/components/composed/__tests__/logo.test.tsx +60 -0
- package/src/components/composed/logo.tsx +159 -0
- package/src/components/composed/tree/CLAUDE.md +132 -0
- package/src/components/composed/tree/__tests__/tree-atom.test.tsx +217 -0
- package/src/components/composed/tree/__tests__/tree.test.tsx +162 -37
- package/src/components/composed/tree/__tests__/use-tree.test.tsx +2 -1
- package/src/components/composed/tree/constants.ts +4 -4
- package/src/components/composed/tree/index.ts +38 -0
- package/src/components/composed/tree/tree-atom.ts +590 -0
- package/src/components/composed/tree/tree-node.tsx +117 -78
- package/src/components/composed/tree/tree.tsx +24 -16
- package/src/components/composed/tree/type.ts +125 -15
- package/src/components/composed/tree/use-tree-dnd.ts +82 -51
- package/src/components/composed/tree/use-tree-state.ts +105 -0
- package/src/components/composed/tree/use-tree.ts +185 -184
- package/src/components/composed/user-avatar.tsx +11 -99
- package/src/components/user-avatar/__tests__/user-avatar.test.tsx +286 -0
- package/src/components/user-avatar/component.tsx +151 -22
- package/src/components/user-avatar/fallback.tsx +3 -1
- package/src/components/user-avatar/index.ts +19 -0
- package/src/components/user-avatar/presence-badge.tsx +56 -0
- package/src/components/user-avatar/type.ts +58 -0
- package/src/components/user-avatar/use-user-avatar-state.ts +60 -0
- package/src/components/user-avatar/use-user-avatar.ts +180 -0
- package/src/components/user-avatar/user-avatar-atom.ts +218 -0
- package/src/components/user-avatar/utils.ts +94 -0
- package/src/index.ts +35 -3
- package/src/state/exegia-provider.tsx +79 -0
- package/src/state/index.ts +4 -0
- package/src/state/store.ts +19 -0
- package/dist-lib/components/blocks/nav/sidebar-block.d.ts +0 -14
- package/dist-lib/components/blocks/nav/sidebar-nav-row.d.ts +0 -3
- package/dist-lib/components/blocks/nav/types.d.ts +0 -60
- package/dist-lib/components/blocks/nav/utils.d.ts +0 -1
- package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +0 -3
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +0 -3
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +0 -2
- package/src/components/blocks/nav/__tests__/sidebar-block.test.tsx +0 -125
- package/src/components/blocks/nav/sidebar-block.tsx +0 -142
- package/src/components/blocks/nav/sidebar-nav-row.tsx +0 -78
- package/src/components/blocks/nav/types.ts +0 -65
- package/src/components/blocks/nav/utils.ts +0 -4
- package/src/components/blocks/shell/animated-sidebar-content.tsx +0 -19
- package/src/components/blocks/shell/animated-sidebar-footer.tsx +0 -19
- package/src/components/blocks/shell/animated-sidebar-group-content.tsx +0 -16
- package/src/components/blocks/shell/animated-sidebar-group-label.tsx +0 -29
- package/src/components/blocks/shell/animated-sidebar-group.tsx +0 -16
- package/src/components/blocks/shell/animated-sidebar-header.tsx +0 -16
- package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +0 -151
- package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +0 -20
- package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +0 -85
- package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +0 -19
- package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +0 -44
- package/src/components/blocks/shell/animated-sidebar-menu.tsx +0 -26
- /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,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-instance Jotai state for the shell's layout fit.
|
|
3
|
+
*
|
|
4
|
+
* Every atom is a module-level family keyed by a shell id, so one
|
|
5
|
+
* `ExegiaProvider` at the app root is enough: several shells coexist in one
|
|
6
|
+
* store without a provider each, and a title bar, a command palette or a
|
|
7
|
+
* layout test can drive one by id without holding its controller.
|
|
8
|
+
*
|
|
9
|
+
* Unlike the tree, this feature has no controlled props — the shell measures
|
|
10
|
+
* itself, nobody hands it a viewport. So, like `blocks/auth`'s coordination
|
|
11
|
+
* layer, there is no config atom, no projection and no owned-* loop guard.
|
|
12
|
+
* `useShellFit` is the only writer of `metrics`, and that write is silent by
|
|
13
|
+
* design: a resize is not an event an app subscribed to.
|
|
14
|
+
*
|
|
15
|
+
* The rule itself lives in `shell-metrics.ts` and is called from here, so the
|
|
16
|
+
* arithmetic has one home whether it is read through a hook or a store.
|
|
17
|
+
*/
|
|
18
|
+
import { atom } from "jotai"
|
|
19
|
+
import type { Getter, Setter } from "jotai"
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
clampPanelWidth,
|
|
23
|
+
fitsPanel,
|
|
24
|
+
metricsEqual,
|
|
25
|
+
panelBounds,
|
|
26
|
+
type ShellMetrics,
|
|
27
|
+
} from "./shell-metrics"
|
|
28
|
+
import type {
|
|
29
|
+
ShellFitInstanceId,
|
|
30
|
+
ShellFitPanelBounds,
|
|
31
|
+
ShellFitSeed,
|
|
32
|
+
ShellFitState,
|
|
33
|
+
} from "./type"
|
|
34
|
+
|
|
35
|
+
/** An unmeasured shell. Zeroes read as "no layout yet", which the rule fails
|
|
36
|
+
* open on rather than hiding a panel over a reading it never took. */
|
|
37
|
+
const NO_METRICS: ShellMetrics = {
|
|
38
|
+
rail: 0,
|
|
39
|
+
insetMin: 0,
|
|
40
|
+
panelMin: 0,
|
|
41
|
+
viewport: 0,
|
|
42
|
+
chrome: 0,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A panel that mounts at `--panel-width`. */
|
|
46
|
+
const DEFAULT_SHELL_FIT_SEED: ShellFitSeed = { panelWidth: null }
|
|
47
|
+
|
|
48
|
+
const NO_BOUNDS: ShellFitPanelBounds = { min: 0, max: 0 }
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A string-keyed atom family.
|
|
52
|
+
*
|
|
53
|
+
* `jotai/utils`' `atomFamily` is deprecated for Jotai v3, and we need only
|
|
54
|
+
* the string-keyed case with a `remove` — so this stays in-house rather than
|
|
55
|
+
* adding `jotai-family` as a second Jotai package to keep version-aligned.
|
|
56
|
+
* Dropping a key lets the store's WeakMap release that instance's state.
|
|
57
|
+
*/
|
|
58
|
+
type Family<AtomType> = ((id: ShellFitInstanceId) => AtomType) & {
|
|
59
|
+
remove: (id: ShellFitInstanceId) => void
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Every family, so `removeShellFitInstance` can drop an id from all of them. */
|
|
63
|
+
const families: { remove: (id: ShellFitInstanceId) => void }[] = []
|
|
64
|
+
|
|
65
|
+
function keyed<AtomType>(create: (id: ShellFitInstanceId) => AtomType) {
|
|
66
|
+
const cache = new Map<ShellFitInstanceId, AtomType>()
|
|
67
|
+
const family = ((id: ShellFitInstanceId) => {
|
|
68
|
+
let instance = cache.get(id)
|
|
69
|
+
if (instance === undefined) {
|
|
70
|
+
instance = create(id)
|
|
71
|
+
cache.set(id, instance)
|
|
72
|
+
}
|
|
73
|
+
return instance
|
|
74
|
+
}) as Family<AtomType>
|
|
75
|
+
family.remove = (id: ShellFitInstanceId) => {
|
|
76
|
+
cache.delete(id)
|
|
77
|
+
}
|
|
78
|
+
families.push(family)
|
|
79
|
+
return family
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function stateFamily<Value>(name: string, initialValue: Value) {
|
|
83
|
+
return keyed((id) => {
|
|
84
|
+
const instance = atom(initialValue)
|
|
85
|
+
instance.debugLabel = `shell-fit/${id}/${name}`
|
|
86
|
+
return instance
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function readFamily<Value>(
|
|
91
|
+
name: string,
|
|
92
|
+
read: (get: Getter, id: ShellFitInstanceId) => Value
|
|
93
|
+
) {
|
|
94
|
+
return keyed((id) => {
|
|
95
|
+
const instance = atom((get) => read(get, id))
|
|
96
|
+
instance.debugLabel = `shell-fit/${id}/${name}`
|
|
97
|
+
return instance
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function actionFamily<Args extends unknown[]>(
|
|
102
|
+
name: string,
|
|
103
|
+
write: (
|
|
104
|
+
get: Getter,
|
|
105
|
+
set: Setter,
|
|
106
|
+
id: ShellFitInstanceId,
|
|
107
|
+
...args: Args
|
|
108
|
+
) => void
|
|
109
|
+
) {
|
|
110
|
+
return keyed((id) => {
|
|
111
|
+
const instance = atom(null, (get, set, ...args: Args) =>
|
|
112
|
+
write(get, set, id, ...args)
|
|
113
|
+
)
|
|
114
|
+
instance.debugLabel = `shell-fit/${id}/${name}`
|
|
115
|
+
return instance
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The shell's own measurement of its columns. `useShellFit` is the only
|
|
120
|
+
* writer; read `shellFitMeasuredAtom` to get it with the "no layout yet" case
|
|
121
|
+
* already resolved. */
|
|
122
|
+
export const shellFitMetricsAtom = stateFamily<ShellMetrics>(
|
|
123
|
+
"metrics",
|
|
124
|
+
NO_METRICS
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
/** The width the user dragged to, before clamping — null while the panel sits
|
|
128
|
+
* at `--panel-width`. Read `shellFitPanelWidthAtom` for the width the panel
|
|
129
|
+
* actually renders at: clamping on read is what lets a narrowing viewport
|
|
130
|
+
* pull the panel down without forgetting the width they asked for. */
|
|
131
|
+
export const shellFitRequestedWidthAtom = stateFamily<number | null>(
|
|
132
|
+
"requestedWidth",
|
|
133
|
+
null
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const shellFitSeedAtom = stateFamily<ShellFitSeed>(
|
|
137
|
+
"seed",
|
|
138
|
+
DEFAULT_SHELL_FIT_SEED
|
|
139
|
+
)
|
|
140
|
+
const shellFitInitializedAtom = stateFamily<boolean>("initialized", false)
|
|
141
|
+
|
|
142
|
+
/** The metrics, or null when the shell has no layout to report — a server
|
|
143
|
+
* render, a `display: none` host, a test with no layout engine. A zero is not
|
|
144
|
+
* a width, and treating it as one would pin the panel to nothing. */
|
|
145
|
+
export const shellFitMeasuredAtom = readFamily<ShellMetrics | null>(
|
|
146
|
+
"measured",
|
|
147
|
+
(get, id) => {
|
|
148
|
+
const metrics = get(shellFitMetricsAtom(id))
|
|
149
|
+
return metrics.panelMin > 0 ? metrics : null
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
/** Whether the shell can hold a secondary panel at all. */
|
|
154
|
+
export const shellFitFitsAtom = readFamily("fits", (get, id) =>
|
|
155
|
+
fitsPanel(get(shellFitMetricsAtom(id)))
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
/** The range a resize may land in. */
|
|
159
|
+
export const shellFitPanelBoundsAtom = readFamily<ShellFitPanelBounds>(
|
|
160
|
+
"panelBounds",
|
|
161
|
+
(get, id) => {
|
|
162
|
+
const measured = get(shellFitMeasuredAtom(id))
|
|
163
|
+
return measured ? panelBounds(measured) : NO_BOUNDS
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
/** The width the secondary panel renders at, or null before the first
|
|
168
|
+
* measurement, where the caller falls back to `--panel-width`. */
|
|
169
|
+
export const shellFitPanelWidthAtom = readFamily<number | null>(
|
|
170
|
+
"panelWidth",
|
|
171
|
+
(get, id) => {
|
|
172
|
+
const measured = get(shellFitMeasuredAtom(id))
|
|
173
|
+
if (!measured) return null
|
|
174
|
+
const requested = get(shellFitRequestedWidthAtom(id))
|
|
175
|
+
return clampPanelWidth(requested ?? measured.panelMin, measured)
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
/** The whole fit of one shell. This changes on every measurement, so a
|
|
180
|
+
* component that reads one field should subscribe to that field's atom
|
|
181
|
+
* instead: `useAtomValue(shellFitFitsAtom("app-shell"))`. */
|
|
182
|
+
export const shellFitStateAtom = readFamily<ShellFitState>(
|
|
183
|
+
"state",
|
|
184
|
+
(get, id) => ({
|
|
185
|
+
metrics: get(shellFitMeasuredAtom(id)),
|
|
186
|
+
fits: get(shellFitFitsAtom(id)),
|
|
187
|
+
panelWidth: get(shellFitPanelWidthAtom(id)),
|
|
188
|
+
bounds: get(shellFitPanelBoundsAtom(id)),
|
|
189
|
+
})
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
/** @internal The measurement's way in. Silent, and inert when the numbers did
|
|
193
|
+
* not move — a resize event that changes nothing must not re-render a shell. */
|
|
194
|
+
export const measureShellFitAtom = actionFamily<[metrics: ShellMetrics]>(
|
|
195
|
+
"measure",
|
|
196
|
+
(get, set, id, metrics) => {
|
|
197
|
+
if (metricsEqual(get(shellFitMetricsAtom(id)), metrics)) return
|
|
198
|
+
set(shellFitMetricsAtom(id), metrics)
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
/** Resize the secondary panel. Clamped on the way in, so a drag past the
|
|
203
|
+
* gutter parks at the bound instead of banking travel it has to give back —
|
|
204
|
+
* and a caller with no idea how wide the shell is can still ask for 900. */
|
|
205
|
+
export const resizeShellPanelAtom = actionFamily<[width: number]>(
|
|
206
|
+
"resizePanel",
|
|
207
|
+
(get, set, id, width) => {
|
|
208
|
+
const measured = get(shellFitMeasuredAtom(id))
|
|
209
|
+
set(
|
|
210
|
+
shellFitRequestedWidthAtom(id),
|
|
211
|
+
measured ? clampPanelWidth(width, measured) : width
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
/** Back to the width the shell mounted with — `defaultPanelWidth`, or
|
|
217
|
+
* `--panel-width` when there was none. */
|
|
218
|
+
export const resetShellPanelWidthAtom = actionFamily<[]>(
|
|
219
|
+
"resetPanelWidth",
|
|
220
|
+
(get, set, id) => {
|
|
221
|
+
set(shellFitRequestedWidthAtom(id), get(shellFitSeedAtom(id)).panelWidth)
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
/** @internal Seed the instance once. A seed describes the mount, not every
|
|
226
|
+
* render, so a `defaultPanelWidth` that arrives later never overwrites a
|
|
227
|
+
* width the user dragged to. */
|
|
228
|
+
export const mountShellFitAtom = actionFamily<[seed: ShellFitSeed]>(
|
|
229
|
+
"mount",
|
|
230
|
+
(get, set, id, seed) => {
|
|
231
|
+
if (get(shellFitInitializedAtom(id))) return
|
|
232
|
+
set(shellFitInitializedAtom(id), true)
|
|
233
|
+
set(shellFitSeedAtom(id), seed)
|
|
234
|
+
set(shellFitRequestedWidthAtom(id), seed.panelWidth)
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
/** Drop every atom for `id`. `useShellFit` calls this on unmount for shells it
|
|
239
|
+
* keyed itself; an explicit `shellId` is the app's key and outlives its
|
|
240
|
+
* component, so a resized panel survives a route change. */
|
|
241
|
+
export function removeShellFitInstance(id: ShellFitInstanceId): void {
|
|
242
|
+
for (const family of families) family.remove(id)
|
|
243
|
+
}
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
import { MotionIcon } from "motion-icons-react"
|
|
4
4
|
import * as React from "react"
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
AnimatedPanel
|
|
8
|
-
} from "./animated-panel.tsx"
|
|
6
|
+
import { AnimatedPanel } from "./animated-panel.tsx"
|
|
9
7
|
import { cn } from "@/lib/utils"
|
|
10
8
|
import type { ShellLayoutProps, ShellPanelControlProps } from "./type"
|
|
11
9
|
import { TITLE_BAR_HEIGHT } from "./utils"
|
|
@@ -23,82 +21,86 @@ export function ShellLayout({
|
|
|
23
21
|
defaultOpen,
|
|
24
22
|
...panelControlProps
|
|
25
23
|
}: ShellLayoutProps): React.ReactElement {
|
|
26
|
-
|
|
27
24
|
const background: ClassNameValue = `bg-linear-to-tr/increasing from-neutral-200 via-neutral-100 to-stone-200 dark:from-neutral-900 dark:via-neutral-950 dark:to-stone-950`
|
|
28
25
|
|
|
29
26
|
// Each panel seeds its own side's initial state (`defaultOpen ?? open`);
|
|
30
27
|
// an explicit `defaultOpen` record — usually from useShellPanels — wins
|
|
31
28
|
// per side.
|
|
32
29
|
const initialOpen: ShellPanelControlProps["defaultOpen"] = {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
...(panels?.left && {
|
|
31
|
+
left: panels.left.defaultOpen ?? true,
|
|
32
|
+
}),
|
|
33
|
+
...(panels?.right && {
|
|
34
|
+
right: panels.right.defaultOpen ?? false,
|
|
35
|
+
}),
|
|
36
|
+
...defaultOpen,
|
|
37
|
+
}
|
|
42
38
|
|
|
43
39
|
return (
|
|
44
40
|
<AnimatedPanelProvider
|
|
45
41
|
{...panelControlProps}
|
|
46
42
|
defaultOpen={initialOpen}
|
|
47
|
-
className={cn("relative h-full min-h-0 pb-2", className, background)}
|
|
43
|
+
className={cn("relative h-full min-h-0 px-2 pb-2", className, background)}
|
|
48
44
|
style={{
|
|
49
45
|
paddingTop: variant === "desktop" ? TITLE_BAR_HEIGHT : 0,
|
|
50
46
|
}}
|
|
51
47
|
>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
{panels?.left?.component && (
|
|
49
|
+
<AnimatedPanel
|
|
50
|
+
ariaLabel="Primary navigation"
|
|
51
|
+
collapsible="icon"
|
|
52
|
+
role="navigation"
|
|
53
|
+
variant="inset"
|
|
54
|
+
>
|
|
55
|
+
{panels?.left?.component}
|
|
56
|
+
</AnimatedPanel>
|
|
57
|
+
)}
|
|
60
58
|
|
|
61
|
-
<AnimatedPanelInset
|
|
62
|
-
<header className="flex h-12 items-center justify-between gap-2 border-b px-2">
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
|
|
59
|
+
<AnimatedPanelInset>
|
|
60
|
+
<header className="flex h-12 flex-row! items-center justify-between gap-2 border-b px-2">
|
|
61
|
+
{panels?.left?.component && (
|
|
62
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
63
|
+
<AnimatedPanelTrigger side="left">
|
|
66
64
|
<MotionIcon name="PanelLeft" size={24} animation="press" />
|
|
67
|
-
|
|
68
|
-
</
|
|
69
|
-
|
|
70
|
-
{header &&
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
</AnimatedPanelTrigger>
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
{header && (
|
|
69
|
+
<div className="flex w-full flex-1 items-center">{header}</div>
|
|
70
|
+
)}
|
|
71
|
+
{panels?.right?.component && (
|
|
72
|
+
<div className="flex flex-1 items-center justify-end gap-2">
|
|
73
|
+
<AnimatedPanelTrigger aria-label="Toggle panel" side="right">
|
|
74
74
|
<MotionIcon
|
|
75
75
|
className="opacity-70"
|
|
76
76
|
name="PanelRight"
|
|
77
77
|
size={24}
|
|
78
78
|
/>
|
|
79
|
-
|
|
80
|
-
</
|
|
81
|
-
|
|
79
|
+
</AnimatedPanelTrigger>
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
82
|
</header>
|
|
83
83
|
<div className="min-h-24 flex-1 overflow-auto">{children}</div>
|
|
84
84
|
</AnimatedPanelInset>
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
86
|
+
{panels?.right?.component && (
|
|
87
|
+
<AnimatedPanel
|
|
88
|
+
ariaLabel={panels.right.name ?? "Secondary panel"}
|
|
89
|
+
// Below md the panel is portal led over the page, so it carries the
|
|
90
|
+
// surface itself; the desktop rail keeps it on the inner panel.
|
|
91
|
+
className={cn(
|
|
92
|
+
"bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900",
|
|
93
|
+
"outline-offset-0.5 border-t-3 border-white outline-neutral-100 dark:inset-ring-black",
|
|
94
|
+
"rounded-lg shadow-md shadow-neutral-200 dark:shadow-neutral-950"
|
|
95
|
+
)}
|
|
96
|
+
collapsible="offcanvas"
|
|
97
|
+
role="complementary"
|
|
98
|
+
side="right"
|
|
99
|
+
variant="inset"
|
|
100
|
+
>
|
|
101
|
+
{panels.right.component}
|
|
102
|
+
</AnimatedPanel>
|
|
103
|
+
)}
|
|
102
104
|
</AnimatedPanelProvider>
|
|
103
105
|
)
|
|
104
106
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
|
|
10
|
+
/** The px the shell lays itself out with. Every field is measured from a CSS
|
|
11
|
+
* variable (see `SHELL_WIDTHS`), never hard-coded, so a consumer's override
|
|
12
|
+
* flows straight through the rule. */
|
|
13
|
+
export interface ShellMetrics {
|
|
14
|
+
/** What the left rail occupies right now — its expanded width, its icon
|
|
15
|
+
* width, or 0 when there is no rail at all (or it is off canvas). */
|
|
16
|
+
rail: number
|
|
17
|
+
/** The floor the body refuses to go below. */
|
|
18
|
+
insetMin: number
|
|
19
|
+
/** The secondary panel's floor, which is also the width it opens at. */
|
|
20
|
+
panelMin: number
|
|
21
|
+
/** The viewport all three columns share. */
|
|
22
|
+
viewport: number
|
|
23
|
+
/** px the shell's own frame eats before any column gets a share: its
|
|
24
|
+
* padding, plus the gap between columns.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately absent from `fitsPanel` — that rule is stated against the raw
|
|
27
|
+
* viewport — but subtracted from the resize ceiling, where ignoring it lets
|
|
28
|
+
* a full-width drag push the row past the shell by exactly this much. */
|
|
29
|
+
chrome: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** px the shell needs before a secondary panel can exist: the rail as it
|
|
33
|
+
* stands, plus the body and the panel at their own floors. */
|
|
34
|
+
export function requiredWidth({ rail, insetMin, panelMin }: ShellMetrics) {
|
|
35
|
+
return rail + insetMin + panelMin
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether the shell can hold a secondary panel at all. Strictly `<`: a shell
|
|
40
|
+
* that fits its columns exactly has no room left to give one.
|
|
41
|
+
*
|
|
42
|
+
* An unmeasurable shell fails open. A server render, a `display: none` host
|
|
43
|
+
* or a test environment with no layout engine all report 0, and a panel must
|
|
44
|
+
* never disappear over a reading the layout could not produce.
|
|
45
|
+
*/
|
|
46
|
+
export function fitsPanel(metrics: ShellMetrics) {
|
|
47
|
+
if (metrics.viewport <= 0 || metrics.panelMin <= 0) return true
|
|
48
|
+
return requiredWidth(metrics) < metrics.viewport
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** How wide the secondary panel may be: never under its own floor, never past
|
|
52
|
+
* the slack the body holds above its floor. `max` never drops below `min`, so
|
|
53
|
+
* a shell that does not fit reports a degenerate range instead of an inverted
|
|
54
|
+
* one — `fitsPanel` is what hides the panel, not a negative bound. */
|
|
55
|
+
export function panelBounds(metrics: ShellMetrics) {
|
|
56
|
+
const { chrome, insetMin, panelMin, rail, viewport } = metrics
|
|
57
|
+
return {
|
|
58
|
+
min: panelMin,
|
|
59
|
+
max: Math.max(panelMin, viewport - chrome - rail - insetMin),
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function clampPanelWidth(width: number, metrics: ShellMetrics) {
|
|
64
|
+
const { min, max } = panelBounds(metrics)
|
|
65
|
+
return Math.min(Math.max(width, min), max)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Resizes fire per pointer move and per resize event, most of them landing
|
|
69
|
+
* on the same numbers — comparing fields keeps those from re-rendering the
|
|
70
|
+
* whole shell. */
|
|
71
|
+
export function metricsEqual(a: ShellMetrics, b: ShellMetrics) {
|
|
72
|
+
return (
|
|
73
|
+
a.rail === b.rail &&
|
|
74
|
+
a.insetMin === b.insetMin &&
|
|
75
|
+
a.panelMin === b.panelMin &&
|
|
76
|
+
a.viewport === b.viewport &&
|
|
77
|
+
a.chrome === b.chrome
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
1
|
+
import type { ReactNode, RefObject } from "react"
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
type ButtonHTMLAttributes,
|
|
@@ -7,6 +7,83 @@ import {
|
|
|
7
7
|
} from "react"
|
|
8
8
|
import type { HTMLMotionProps } from "motion/react"
|
|
9
9
|
|
|
10
|
+
import type { ShellMetrics } from "./shell-metrics"
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Shell fit — the shell's measurement of itself, kept in Jotai atoms keyed by
|
|
14
|
+
// `shellId` (see `shell-fit-atom.ts`). No atom imports here: this file stays
|
|
15
|
+
// dependency-free so the types can travel without the store.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/** The key a shell's fit state is filed under. Pass your own to reach it from
|
|
19
|
+
* anywhere (`useShellFitState("app-shell")`); leave it out and the provider
|
|
20
|
+
* generates one that dies with it. */
|
|
21
|
+
export type ShellFitInstanceId = string
|
|
22
|
+
|
|
23
|
+
/** The range a secondary-panel resize may land in, in px. */
|
|
24
|
+
export interface ShellFitPanelBounds {
|
|
25
|
+
min: number
|
|
26
|
+
max: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** What the shell measured of itself — readable by id through
|
|
30
|
+
* `useShellFitState`. */
|
|
31
|
+
export interface ShellFitState {
|
|
32
|
+
/** The px the shell last measured, or null before its first measurement
|
|
33
|
+
* (a server render, a host with no layout). */
|
|
34
|
+
metrics: ShellMetrics | null
|
|
35
|
+
/** Whether the shell can hold a secondary panel. True while unmeasured, so
|
|
36
|
+
* a panel never flickers out over a reading that has not happened yet. */
|
|
37
|
+
fits: boolean
|
|
38
|
+
/** The secondary panel's width in px, already clamped to `bounds` — null
|
|
39
|
+
* before the first measurement, where the panel falls back to
|
|
40
|
+
* `--panel-width`. */
|
|
41
|
+
panelWidth: number | null
|
|
42
|
+
/** The range a resize may land in. */
|
|
43
|
+
bounds: ShellFitPanelBounds
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Writes only — drive a shell's secondary panel by id through
|
|
47
|
+
* `useShellFitActions` without re-rendering when it moves. */
|
|
48
|
+
export interface ShellFitActions {
|
|
49
|
+
/** Resize the secondary panel. Clamped on the way in, so a drag past the
|
|
50
|
+
* gutter parks at the bound instead of banking travel it has to give back. */
|
|
51
|
+
resizePanel: (width: number) => void
|
|
52
|
+
/** Back to the width the shell mounted with — `defaultPanelWidth`, or
|
|
53
|
+
* `--panel-width` when there was none. */
|
|
54
|
+
resetPanelWidth: () => void
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** What `useShellFit` hands the provider: the state, the actions and the id
|
|
58
|
+
* they are filed under. */
|
|
59
|
+
export interface ShellFitController extends ShellFitState, ShellFitActions {
|
|
60
|
+
shellId: ShellFitInstanceId
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface UseShellFitOptions {
|
|
64
|
+
/** File the fit under this id so it can be read elsewhere and outlive the
|
|
65
|
+
* shell. Generated (and dropped on unmount) when omitted. */
|
|
66
|
+
shellId?: ShellFitInstanceId
|
|
67
|
+
/** The element the shell's CSS variables live on (the provider wrapper). */
|
|
68
|
+
hostRef: RefObject<HTMLElement | null>
|
|
69
|
+
/** Whether the left rail is expanded right now — a render input, so a fold
|
|
70
|
+
* counts the moment React commits it rather than a frame later when the
|
|
71
|
+
* width animation has moved. */
|
|
72
|
+
railOpen: boolean
|
|
73
|
+
/** The width the secondary panel opens at, in px, instead of
|
|
74
|
+
* `--panel-width`. Read once, on mount. */
|
|
75
|
+
defaultPanelWidth?: number
|
|
76
|
+
/** Fired whenever a measurement lands on "no room": the shell uses it to
|
|
77
|
+
* retire the panel's open state instead of parking it. */
|
|
78
|
+
onUnfit?: () => void
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @internal What an instance starts from, restored by `resetShellPanelWidthAtom`. */
|
|
82
|
+
export interface ShellFitSeed {
|
|
83
|
+
/** The requested panel width at mount — null for `--panel-width`. */
|
|
84
|
+
panelWidth: number | null
|
|
85
|
+
}
|
|
86
|
+
|
|
10
87
|
export interface ShellAction {
|
|
11
88
|
id: string
|
|
12
89
|
label: string
|
|
@@ -60,14 +137,24 @@ export type ShellPanelControlProps = Pick<
|
|
|
60
137
|
| "openMobile"
|
|
61
138
|
| "defaultOpenMobile"
|
|
62
139
|
| "onOpenMobileChange"
|
|
140
|
+
| "shellId"
|
|
141
|
+
| "defaultPanelWidth"
|
|
63
142
|
>
|
|
64
143
|
|
|
65
144
|
export interface UseShellPanelsOptions {
|
|
145
|
+
/** The id the shell's fit state is filed under. Name it to read the same
|
|
146
|
+
* shell from elsewhere (`useShellFitState("app-shell")`) and to keep a
|
|
147
|
+
* dragged panel width across a route change; otherwise the hook generates
|
|
148
|
+
* one that dies with it. */
|
|
149
|
+
shellId?: ShellFitInstanceId
|
|
66
150
|
/** Initial desktop open state per side, merged over
|
|
67
151
|
* `{ left: true, right: false }`. */
|
|
68
152
|
defaultOpen?: AnimatedSidebarProviderProps["defaultOpen"]
|
|
69
153
|
/** Initial mobile overlay state per side — every side starts closed. */
|
|
70
154
|
defaultOpenMobile?: AnimatedSidebarProviderProps["defaultOpenMobile"]
|
|
155
|
+
/** The width the secondary panel opens at, in px, instead of
|
|
156
|
+
* `--panel-width`. */
|
|
157
|
+
defaultPanelWidth?: number
|
|
71
158
|
/** Panel change event returning both the next open state and the side of
|
|
72
159
|
* the panel the change comes from. Mobile overlay changes report the same
|
|
73
160
|
* side as their desktop counterpart. */
|
|
@@ -75,14 +162,32 @@ export interface UseShellPanelsOptions {
|
|
|
75
162
|
}
|
|
76
163
|
|
|
77
164
|
export interface ShellPanelControls {
|
|
165
|
+
/** The id the shell's fit state is filed under — hand it to
|
|
166
|
+
* `useShellFitState` / `useShellFitActions` anywhere below `ExegiaProvider`. */
|
|
167
|
+
shellId: ShellFitInstanceId
|
|
168
|
+
/** The viewport cannot hold the secondary panel beside the rail and the
|
|
169
|
+
* body at their floors, so the shell has dropped the right panel and its
|
|
170
|
+
* trigger — UI outside the shell should stand down with them. Read straight
|
|
171
|
+
* out of the shell's fit atoms; it is only ever `true` once the shell is
|
|
172
|
+
* mounted and measured. */
|
|
173
|
+
isNarrow: boolean
|
|
174
|
+
/** The secondary panel's current width in px, or null until the shell has
|
|
175
|
+
* measured itself (the panel then sits at `--panel-width`). */
|
|
176
|
+
panelWidth: number | null
|
|
177
|
+
/** Resize the secondary panel from outside the shell — clamped to the room
|
|
178
|
+
* the shell has. */
|
|
179
|
+
resizePanel: (width: number) => void
|
|
78
180
|
/** Live desktop open state, keyed by side. */
|
|
79
181
|
open: Record<SidebarSide, boolean>
|
|
80
182
|
/** Live mobile overlay state, keyed by side. */
|
|
81
183
|
openMobile: Record<SidebarSide, boolean>
|
|
184
|
+
/** Refuses to OPEN the right panel while `isNarrow` — there is nothing on
|
|
185
|
+
* screen to open. Closing it always goes through. */
|
|
82
186
|
setOpen: (open: boolean, side: SidebarSide) => void
|
|
83
187
|
setOpenMobile: (open: boolean, side: SidebarSide) => void
|
|
84
188
|
/** Desktop-only convenience — the in-shell triggers already pick the
|
|
85
|
-
* mobile state themselves when the viewport is narrow.
|
|
189
|
+
* mobile state themselves when the viewport is narrow. Carries the same
|
|
190
|
+
* `isNarrow` refusal as `setOpen`. */
|
|
86
191
|
toggle: (side: SidebarSide) => void
|
|
87
192
|
/** Spread onto ShellLayout (or AnimatedPanelProvider directly). */
|
|
88
193
|
providerProps: ShellPanelControlProps
|
|
@@ -98,7 +203,6 @@ export interface ShellLayoutProps extends ShellPanelControlProps {
|
|
|
98
203
|
variant?: "web" | "desktop"
|
|
99
204
|
}
|
|
100
205
|
|
|
101
|
-
|
|
102
206
|
export type SidebarSide = "left" | "right"
|
|
103
207
|
/** Open flags keyed by the side. Sides left out stay uncontrolled / at their
|
|
104
208
|
* default — there is no per-side prop, the record IS the API. */
|
|
@@ -108,6 +212,10 @@ export type SidebarCollapsible = "offcanvas" | "icon" | "none"
|
|
|
108
212
|
|
|
109
213
|
export interface AnimatedSidebarContextValue {
|
|
110
214
|
isMobile: boolean
|
|
215
|
+
/** What the shell measured of itself: whether it can hold a secondary panel
|
|
216
|
+
* at all, how wide that panel is, and the range a resize may land in. The
|
|
217
|
+
* right panel and its trigger stand down when `fit.fits` is false. */
|
|
218
|
+
fit: ShellFitController
|
|
111
219
|
layoutId: string
|
|
112
220
|
/** Desktop open state, keyed by side. */
|
|
113
221
|
open: Record<SidebarSide, boolean>
|
|
@@ -122,6 +230,14 @@ export interface AnimatedSidebarContextValue {
|
|
|
122
230
|
}
|
|
123
231
|
|
|
124
232
|
export interface AnimatedSidebarProviderProps extends HTMLAttributes<HTMLDivElement> {
|
|
233
|
+
/** The id this shell's fit state (whether a secondary panel fits, how wide
|
|
234
|
+
* it is) is filed under in the store. Name it to read or drive the shell
|
|
235
|
+
* from elsewhere and to keep a dragged width across a route change; omit it
|
|
236
|
+
* and the provider generates one that is dropped on unmount. */
|
|
237
|
+
shellId?: ShellFitInstanceId
|
|
238
|
+
/** The width the secondary panel opens at, in px, instead of
|
|
239
|
+
* `--panel-width`. Read once, on mount. */
|
|
240
|
+
defaultPanelWidth?: number
|
|
125
241
|
/** Controlled desktop open state, keyed by the side. A side left undefined
|
|
126
242
|
* stays uncontrolled. */
|
|
127
243
|
open?: SidebarOpenState
|
|
@@ -133,13 +249,24 @@ export interface AnimatedSidebarProviderProps extends HTMLAttributes<HTMLDivElem
|
|
|
133
249
|
/** Initial mobile overlay state — every side starts closed. */
|
|
134
250
|
defaultOpenMobile?: SidebarOpenState
|
|
135
251
|
onOpenMobileChange?: (open: boolean, side: SidebarSide) => void
|
|
252
|
+
/** Fires when the shell crosses the width a secondary panel needs (rail +
|
|
253
|
+
* body + panel at their floors). An imperative escape hatch for a consumer
|
|
254
|
+
* that mounts the provider on its own; anything under `ExegiaProvider` can
|
|
255
|
+
* subscribe by id instead with `useShellFitState(shellId).fits`. */
|
|
256
|
+
onNarrowChange?: (isNarrow: boolean) => void
|
|
136
257
|
style?: SidebarProviderStyle
|
|
137
258
|
}
|
|
138
259
|
|
|
139
260
|
export type SidebarProviderStyle = CSSProperties & {
|
|
261
|
+
/** Left rail, expanded. */
|
|
140
262
|
"--sidebar-width"?: string
|
|
263
|
+
/** Left rail, folded to icons. */
|
|
141
264
|
"--sidebar-width-icon"?: string
|
|
142
265
|
"--sidebar-width-mobile"?: string
|
|
266
|
+
/** The secondary panel's floor, and the width it opens at. */
|
|
267
|
+
"--panel-width"?: string
|
|
268
|
+
/** The body's floor — the secondary panel may never squeeze it past this. */
|
|
269
|
+
"--inset-min-width"?: string
|
|
143
270
|
}
|
|
144
271
|
|
|
145
272
|
export type AnimatedSidebarInsetProps = HTMLMotionProps<"main">
|