@exegia/corpora-ui 0.18.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.
- package/dist-lib/components/blocks/nav/sidebar/__tests__/use-ai-sidebar.test.d.ts +1 -0
- package/dist-lib/components/blocks/nav/sidebar/ai-sidebar.d.ts +11 -2
- package/dist-lib/components/blocks/nav/sidebar/type.d.ts +83 -1
- package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar.d.ts +22 -31
- package/dist-lib/components/blocks/nav/sidebar/utils.d.ts +5 -0
- package/dist-lib/components/blocks/scaffold/constants.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/index.d.ts +3 -1
- package/dist-lib/components/blocks/scaffold/panel-menu-button.d.ts +4 -3
- package/dist-lib/components/blocks/scaffold/scaffold-actions.d.ts +5 -4
- package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +2 -0
- package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +1 -1
- package/dist-lib/components/blocks/scaffold/scaffold-tab.d.ts +14 -0
- package/dist-lib/components/blocks/scaffold/type.d.ts +58 -28
- package/dist-lib/components/blocks/scaffold/use-panel-visibility.d.ts +19 -0
- package/dist-lib/components/blocks/scaffold/utils.d.ts +12 -0
- package/dist-lib/components/composed/tree/__tests__/tree.test.d.ts +1 -0
- package/dist-lib/components/composed/tree/__tests__/use-tree.test.d.ts +1 -0
- package/dist-lib/components/composed/tree/constants.d.ts +35 -0
- package/dist-lib/components/composed/tree/index.d.ts +4 -0
- package/dist-lib/components/composed/tree/tree-context.d.ts +4 -0
- package/dist-lib/components/composed/tree/tree-node.d.ts +7 -0
- package/dist-lib/components/composed/tree/tree.d.ts +19 -0
- package/dist-lib/components/composed/tree/type.d.ts +192 -0
- package/dist-lib/components/composed/tree/use-tree-dnd.d.ts +7 -0
- package/dist-lib/components/composed/tree/use-tree.d.ts +20 -0
- package/dist-lib/components/composed/tree/utils.d.ts +28 -0
- package/dist-lib/components/user-avatar/audio-wave.d.ts +2 -0
- package/dist-lib/components/user-avatar/component.d.ts +10 -0
- package/dist-lib/components/user-avatar/fallback.d.ts +5 -0
- package/dist-lib/components/user-avatar/index.d.ts +13 -0
- package/dist-lib/components/user-avatar/type.d.ts +26 -0
- package/dist-lib/components/user-avatar/utils.d.ts +2 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.js +2309 -1285
- package/dist-lib/index.js.map +1 -1
- package/package.json +2 -1
- package/src/components/beste/piece/browser-frame.tsx +9 -6
- package/src/components/blocks/nav/sidebar/__tests__/use-ai-sidebar.test.tsx +310 -0
- package/src/components/blocks/nav/sidebar/ai-sidebar.tsx +90 -60
- package/src/components/blocks/nav/sidebar/type.ts +105 -1
- package/src/components/blocks/nav/sidebar/use-ai-sidebar.ts +293 -116
- package/src/components/blocks/nav/sidebar/utils.ts +28 -0
- package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +268 -26
- package/src/components/blocks/scaffold/constants.ts +11 -0
- package/src/components/blocks/scaffold/index.ts +4 -1
- package/src/components/blocks/scaffold/panel-menu-button.tsx +96 -115
- package/src/components/blocks/scaffold/scaffold-actions.tsx +41 -50
- package/src/components/blocks/scaffold/scaffold-canvas.tsx +48 -5
- package/src/components/blocks/scaffold/scaffold-inspector.tsx +27 -3
- package/src/components/blocks/scaffold/scaffold-main.tsx +1 -1
- package/src/components/blocks/scaffold/scaffold-panel.tsx +56 -30
- package/src/components/blocks/scaffold/scaffold-root.tsx +18 -3
- package/src/components/blocks/scaffold/scaffold-sidebar.tsx +1 -1
- package/src/components/blocks/scaffold/scaffold-tab.tsx +104 -0
- package/src/components/blocks/scaffold/type.ts +62 -33
- package/src/components/blocks/scaffold/use-panel-visibility.ts +180 -0
- package/src/components/blocks/scaffold/utils.ts +35 -0
- package/src/components/composed/tree/__tests__/tree.test.tsx +400 -0
- package/src/components/composed/tree/__tests__/use-tree.test.tsx +332 -0
- package/src/components/composed/tree/constants.ts +60 -0
- package/src/components/composed/tree/index.ts +13 -0
- package/src/components/composed/tree/tree-context.ts +13 -0
- package/src/components/composed/tree/tree-node.tsx +451 -0
- package/src/components/composed/tree/tree.tsx +278 -0
- package/src/components/composed/tree/type.ts +212 -0
- package/src/components/composed/tree/use-tree-dnd.ts +148 -0
- package/src/components/composed/tree/use-tree.ts +306 -0
- package/src/components/composed/tree/utils.ts +172 -0
- package/src/components/composed/user-avatar.tsx +4 -1
- package/src/components/docs/component-preview.tsx +11 -11
- package/src/components/user-avatar/audio-wave.tsx +22 -0
- package/src/components/user-avatar/component.tsx +53 -0
- package/src/components/user-avatar/fallback.tsx +26 -0
- package/src/components/user-avatar/index.ts +15 -0
- package/src/components/user-avatar/type.ts +35 -0
- package/src/components/user-avatar/utils.ts +8 -0
- package/src/index.css +211 -153
- package/src/index.ts +1 -0
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
import { describe, expect, mock, test } from "bun:test"
|
|
2
|
-
import { render, screen, waitFor } from "@testing-library/react"
|
|
2
|
+
import { act, render, screen, waitFor } from "@testing-library/react"
|
|
3
3
|
import userEvent from "@testing-library/user-event"
|
|
4
|
+
import type { ReactNode } from "react"
|
|
4
5
|
|
|
5
6
|
import { Scaffold, useScaffold } from "../index"
|
|
6
7
|
import type { TScaffoldPanelChild } from "../type"
|
|
8
|
+
import { getPanelCapacity } from "../utils"
|
|
9
|
+
|
|
10
|
+
// The canvas measures itself through ResizeObserver; this stub hands the
|
|
11
|
+
// callback to the test so it can drive the width deterministically.
|
|
12
|
+
let observeCanvas: ResizeObserverCallback | undefined
|
|
13
|
+
globalThis.ResizeObserver = class {
|
|
14
|
+
constructor(callback: ResizeObserverCallback) {
|
|
15
|
+
observeCanvas = callback
|
|
16
|
+
}
|
|
17
|
+
observe() {}
|
|
18
|
+
unobserve() {}
|
|
19
|
+
disconnect() {}
|
|
20
|
+
} as unknown as typeof ResizeObserver
|
|
21
|
+
|
|
22
|
+
const resizeCanvas = (width: number) =>
|
|
23
|
+
act(() => {
|
|
24
|
+
observeCanvas?.(
|
|
25
|
+
[{ contentRect: { width } }] as unknown as ResizeObserverEntry[],
|
|
26
|
+
undefined as unknown as ResizeObserver
|
|
27
|
+
)
|
|
28
|
+
})
|
|
7
29
|
|
|
8
30
|
function Strip() {
|
|
9
31
|
return <span>Strip</span>
|
|
10
32
|
}
|
|
11
33
|
|
|
12
34
|
function Workspace({
|
|
13
|
-
onClose,
|
|
14
35
|
onSwap,
|
|
36
|
+
onCloseSecondary,
|
|
15
37
|
onAdd,
|
|
16
|
-
|
|
38
|
+
tabs,
|
|
17
39
|
SecondaryPanel = <Strip />,
|
|
18
40
|
}: {
|
|
19
|
-
onClose?: () => void
|
|
20
41
|
onSwap?: () => void
|
|
42
|
+
onCloseSecondary?: () => void
|
|
21
43
|
onAdd?: () => void
|
|
22
|
-
|
|
44
|
+
/** Rendered as Actions children — the panel tabs. */
|
|
45
|
+
tabs?: ReactNode
|
|
23
46
|
/** Pass null to render the panel without a secondary strip. */
|
|
24
47
|
SecondaryPanel?: TScaffoldPanelChild | null
|
|
25
48
|
}) {
|
|
@@ -33,12 +56,12 @@ function Workspace({
|
|
|
33
56
|
</button>
|
|
34
57
|
</Scaffold.Sidebar>
|
|
35
58
|
<Scaffold.Main>
|
|
36
|
-
<Scaffold.Actions onAdd={onAdd}
|
|
59
|
+
<Scaffold.Actions onAdd={onAdd}>{tabs}</Scaffold.Actions>
|
|
37
60
|
<Scaffold.Canvas>
|
|
38
61
|
<Scaffold.Panel
|
|
39
62
|
key="alpha"
|
|
40
63
|
name="Alpha"
|
|
41
|
-
|
|
64
|
+
onCloseSecondary={onCloseSecondary}
|
|
42
65
|
onSwap={onSwap}
|
|
43
66
|
SecondaryPanel={SecondaryPanel ?? undefined}
|
|
44
67
|
>
|
|
@@ -57,6 +80,12 @@ function Workspace({
|
|
|
57
80
|
const menuToggle = () =>
|
|
58
81
|
screen.queryByRole("button", { name: "Swap panel content" })
|
|
59
82
|
|
|
83
|
+
/** Under the id'd `#scaffold-actions` ancestor, happy-dom serves stale
|
|
84
|
+
* selector-cache results to `waitFor` queries that start while an
|
|
85
|
+
* AnimatePresence exit is still running — let the exit settle on real
|
|
86
|
+
* timers, then query once. */
|
|
87
|
+
const settleExit = () => new Promise((resolve) => setTimeout(resolve, 400))
|
|
88
|
+
|
|
60
89
|
describe("Scaffold", () => {
|
|
61
90
|
test("renders rail, panel surfaces and secondary strip", () => {
|
|
62
91
|
render(<Workspace />)
|
|
@@ -67,18 +96,38 @@ describe("Scaffold", () => {
|
|
|
67
96
|
expect(panel.textContent).toContain("Strip")
|
|
68
97
|
})
|
|
69
98
|
|
|
70
|
-
test("
|
|
71
|
-
const user = userEvent.setup()
|
|
72
|
-
const onClose = mock(() => {})
|
|
73
|
-
|
|
99
|
+
test("seam menu renders only with a callback; actions stay hidden until opened", () => {
|
|
74
100
|
const { rerender } = render(<Workspace />)
|
|
75
|
-
expect(screen.queryByRole("button", { name: "Close panel" })).toBeNull()
|
|
76
101
|
expect(menuToggle()).toBeNull()
|
|
77
102
|
|
|
78
|
-
rerender(<Workspace
|
|
79
|
-
|
|
80
|
-
|
|
103
|
+
rerender(<Workspace onSwap={() => {}} />)
|
|
104
|
+
expect(menuToggle()).toBeDefined()
|
|
105
|
+
// Collapsed: the action segment is aria-hidden and inert.
|
|
106
|
+
expect(screen.queryByRole("button", { name: "Swap" })).toBeNull()
|
|
107
|
+
|
|
108
|
+
// The Close action alone also earns the menu.
|
|
109
|
+
rerender(<Workspace onCloseSecondary={() => {}} />)
|
|
110
|
+
expect(menuToggle()).toBeDefined()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test("seam menu Close renders only with onCloseSecondary, fires and collapses", async () => {
|
|
114
|
+
const user = userEvent.setup()
|
|
115
|
+
const onCloseSecondary = mock(() => {})
|
|
116
|
+
|
|
117
|
+
const { rerender } = render(<Workspace onSwap={() => {}} />)
|
|
118
|
+
await user.click(menuToggle() as HTMLElement)
|
|
119
|
+
expect(await screen.findByRole("button", { name: "Swap" })).toBeDefined()
|
|
120
|
+
expect(screen.queryByRole("button", { name: "Close" })).toBeNull()
|
|
121
|
+
|
|
122
|
+
rerender(
|
|
123
|
+
<Workspace onCloseSecondary={onCloseSecondary} onSwap={() => {}} />
|
|
124
|
+
)
|
|
125
|
+
await user.click(screen.getByRole("button", { name: "Close" }))
|
|
126
|
+
expect(onCloseSecondary).toHaveBeenCalledTimes(1)
|
|
127
|
+
|
|
128
|
+
// Acting collapses the menu back to the compact toggle.
|
|
81
129
|
expect(menuToggle()).toBeDefined()
|
|
130
|
+
expect(screen.queryByRole("button", { name: "Close" })).toBeNull()
|
|
82
131
|
})
|
|
83
132
|
|
|
84
133
|
test("seam menu opens; Swap fires onSwap, flips the panel and collapses the menu", async () => {
|
|
@@ -140,31 +189,86 @@ describe("Scaffold", () => {
|
|
|
140
189
|
expect(bottom.getAttribute("data-expanded")).toBeNull()
|
|
141
190
|
})
|
|
142
191
|
|
|
192
|
+
test("expand chevron mirrors when the panel is swapped", async () => {
|
|
193
|
+
const user = userEvent.setup()
|
|
194
|
+
render(<Workspace onSwap={() => {}} />)
|
|
195
|
+
|
|
196
|
+
// Swap first — column-reverse puts the strip visually on top.
|
|
197
|
+
await user.click(menuToggle() as HTMLElement)
|
|
198
|
+
await user.click(await screen.findByRole("button", { name: "Swap" }))
|
|
199
|
+
|
|
200
|
+
// Primary still holds the flexible slot, but the strip that grows
|
|
201
|
+
// next now sits on top — the chevron points up.
|
|
202
|
+
await user.click(
|
|
203
|
+
await screen.findByRole("button", { name: "Swap panel content" })
|
|
204
|
+
)
|
|
205
|
+
const expandButton = await screen.findByRole("button", { name: "Expand" })
|
|
206
|
+
const chevron = () => expandButton.querySelector("svg.lucide-chevron-down")
|
|
207
|
+
expect(chevron()?.classList.contains("rotate-180")).toBe(true)
|
|
208
|
+
|
|
209
|
+
// After expanding, the primary — now visually at the bottom — grows
|
|
210
|
+
// next: the chevron points down again.
|
|
211
|
+
await user.click(expandButton)
|
|
212
|
+
await user.click(
|
|
213
|
+
await screen.findByRole("button", { name: "Swap panel content" })
|
|
214
|
+
)
|
|
215
|
+
expect(chevron()?.classList.contains("rotate-180")).toBe(false)
|
|
216
|
+
})
|
|
217
|
+
|
|
143
218
|
test("seam menu needs a secondary strip", () => {
|
|
144
219
|
render(<Workspace onSwap={() => {}} SecondaryPanel={null} />)
|
|
145
220
|
|
|
146
221
|
expect(menuToggle()).toBeNull()
|
|
147
222
|
})
|
|
148
223
|
|
|
149
|
-
test("actions cluster: Add
|
|
224
|
+
test("actions cluster: Add renders only with onAdd and fires", async () => {
|
|
150
225
|
const user = userEvent.setup()
|
|
151
226
|
const onAdd = mock(() => {})
|
|
152
227
|
|
|
153
|
-
|
|
154
|
-
|
|
228
|
+
// Consumers omit onAdd at SCAFFOLD_PANEL_CAPACITY — the segment hides.
|
|
229
|
+
const { rerender } = render(<Workspace />)
|
|
230
|
+
expect(screen.queryByRole("button", { name: "Panel" })).toBeNull()
|
|
155
231
|
|
|
232
|
+
rerender(<Workspace onAdd={onAdd} />)
|
|
156
233
|
await user.click(screen.getByRole("button", { name: "Panel" }))
|
|
157
234
|
expect(onAdd).toHaveBeenCalledTimes(1)
|
|
158
235
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
expect(
|
|
162
|
-
|
|
163
|
-
|
|
236
|
+
rerender(<Workspace />)
|
|
237
|
+
await settleExit()
|
|
238
|
+
expect(screen.queryByRole("button", { name: "Panel" })).toBeNull()
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test("panel tabs show their label; close renders only with onClose and fires", async () => {
|
|
242
|
+
const user = userEvent.setup()
|
|
243
|
+
const onCloseTab = mock(() => {})
|
|
244
|
+
|
|
245
|
+
// Consumers omit onClose on the last remaining panel's tab.
|
|
246
|
+
const { rerender } = render(
|
|
247
|
+
<Workspace tabs={<Scaffold.Tab key="alpha">Alpha</Scaffold.Tab>} />
|
|
248
|
+
)
|
|
249
|
+
expect(screen.getByText("Alpha")).toBeDefined()
|
|
250
|
+
expect(screen.queryByRole("button", { name: "Close Alpha" })).toBeNull()
|
|
251
|
+
|
|
252
|
+
rerender(
|
|
253
|
+
<Workspace
|
|
254
|
+
tabs={
|
|
255
|
+
<Scaffold.Tab
|
|
256
|
+
key="alpha"
|
|
257
|
+
closeLabel="Close Alpha"
|
|
258
|
+
onClose={onCloseTab}
|
|
259
|
+
>
|
|
260
|
+
Alpha
|
|
261
|
+
</Scaffold.Tab>
|
|
262
|
+
}
|
|
263
|
+
/>
|
|
264
|
+
)
|
|
265
|
+
await user.click(screen.getByRole("button", { name: "Close Alpha" }))
|
|
266
|
+
expect(onCloseTab).toHaveBeenCalledTimes(1)
|
|
164
267
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
268
|
+
// Closing removes the tab — it animates out of the pill.
|
|
269
|
+
rerender(<Workspace tabs={null} />)
|
|
270
|
+
await settleExit()
|
|
271
|
+
expect(screen.queryByText("Alpha")).toBeNull()
|
|
168
272
|
})
|
|
169
273
|
|
|
170
274
|
test("inspector opens from outside state and hides again", async () => {
|
|
@@ -185,6 +289,16 @@ describe("Scaffold", () => {
|
|
|
185
289
|
expect(inspector().getAttribute("aria-hidden")).toBe("true")
|
|
186
290
|
})
|
|
187
291
|
|
|
292
|
+
test("capacity grants each panel 320px plus the gaps between them", () => {
|
|
293
|
+
expect(getPanelCapacity(null)).toBe(3)
|
|
294
|
+
expect(getPanelCapacity(976)).toBe(3) // 3×320 + 2×8
|
|
295
|
+
expect(getPanelCapacity(975)).toBe(2)
|
|
296
|
+
expect(getPanelCapacity(648)).toBe(2) // 2×320 + 8
|
|
297
|
+
expect(getPanelCapacity(647)).toBe(1)
|
|
298
|
+
expect(getPanelCapacity(100)).toBe(1) // never below one panel
|
|
299
|
+
expect(getPanelCapacity(5000)).toBe(3) // never above the cap
|
|
300
|
+
})
|
|
301
|
+
|
|
188
302
|
test("root manages inspector state itself when uncontrolled", () => {
|
|
189
303
|
render(
|
|
190
304
|
<Scaffold.Root defaultInspectorOpen>
|
|
@@ -201,3 +315,131 @@ describe("Scaffold", () => {
|
|
|
201
315
|
).toBeDefined()
|
|
202
316
|
})
|
|
203
317
|
})
|
|
318
|
+
|
|
319
|
+
const PANEL_IDS = ["one", "two", "three"] as const
|
|
320
|
+
|
|
321
|
+
function ResponsiveWorkspace() {
|
|
322
|
+
return (
|
|
323
|
+
<Scaffold.Root>
|
|
324
|
+
<Scaffold.Main>
|
|
325
|
+
<Scaffold.Actions>
|
|
326
|
+
{PANEL_IDS.map((id) => (
|
|
327
|
+
<Scaffold.Tab key={id} panelId={id}>
|
|
328
|
+
{`Tab ${id}`}
|
|
329
|
+
</Scaffold.Tab>
|
|
330
|
+
))}
|
|
331
|
+
</Scaffold.Actions>
|
|
332
|
+
<Scaffold.Canvas>
|
|
333
|
+
{PANEL_IDS.map((id) => (
|
|
334
|
+
<Scaffold.Panel key={id} id={id} name={`Panel ${id}`}>
|
|
335
|
+
<span>{`Body ${id}`}</span>
|
|
336
|
+
</Scaffold.Panel>
|
|
337
|
+
))}
|
|
338
|
+
</Scaffold.Canvas>
|
|
339
|
+
</Scaffold.Main>
|
|
340
|
+
</Scaffold.Root>
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const panel = (id: string) =>
|
|
345
|
+
screen.queryByRole("region", { name: `Panel ${id}` })
|
|
346
|
+
|
|
347
|
+
const tab = (id: string) => screen.getByRole("button", { name: `Tab ${id}` })
|
|
348
|
+
|
|
349
|
+
describe("Scaffold responsive panels", () => {
|
|
350
|
+
test("narrowing hides the oldest panel; widening restores it", async () => {
|
|
351
|
+
render(<ResponsiveWorkspace />)
|
|
352
|
+
|
|
353
|
+
resizeCanvas(976)
|
|
354
|
+
for (const id of PANEL_IDS) expect(panel(id)).not.toBeNull()
|
|
355
|
+
|
|
356
|
+
// Two panels' worth of room — the least-recently-activated hides.
|
|
357
|
+
resizeCanvas(700)
|
|
358
|
+
await settleExit()
|
|
359
|
+
expect(panel("one")).toBeNull()
|
|
360
|
+
expect(panel("two")).not.toBeNull()
|
|
361
|
+
expect(panel("three")).not.toBeNull()
|
|
362
|
+
expect(
|
|
363
|
+
tab("one")
|
|
364
|
+
.closest('[data-slot="scaffold-tab"]')
|
|
365
|
+
?.getAttribute("data-panel-hidden")
|
|
366
|
+
).toBe("")
|
|
367
|
+
expect(tab("one").getAttribute("aria-pressed")).toBe("false")
|
|
368
|
+
|
|
369
|
+
// Room returns — the auto-hidden panel comes back by itself.
|
|
370
|
+
resizeCanvas(1200)
|
|
371
|
+
expect(await screen.findByRole("region", { name: "Panel one" })).toBeDefined()
|
|
372
|
+
expect(tab("one").getAttribute("aria-pressed")).toBe("true")
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
test("pressing a hidden tab shows its panel, hiding the oldest visible one", async () => {
|
|
376
|
+
const user = userEvent.setup()
|
|
377
|
+
render(<ResponsiveWorkspace />)
|
|
378
|
+
|
|
379
|
+
resizeCanvas(700)
|
|
380
|
+
await settleExit()
|
|
381
|
+
expect(panel("one")).toBeNull()
|
|
382
|
+
|
|
383
|
+
await user.click(tab("one"))
|
|
384
|
+
await settleExit()
|
|
385
|
+
expect(panel("one")).not.toBeNull()
|
|
386
|
+
expect(panel("two")).toBeNull()
|
|
387
|
+
expect(panel("three")).not.toBeNull()
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
test("pressing a visible tab hides its panel; the last one never hides", async () => {
|
|
391
|
+
const user = userEvent.setup()
|
|
392
|
+
render(<ResponsiveWorkspace />)
|
|
393
|
+
|
|
394
|
+
resizeCanvas(400)
|
|
395
|
+
await settleExit()
|
|
396
|
+
// One panel's worth of room — only the newest activation stays.
|
|
397
|
+
expect(panel("one")).toBeNull()
|
|
398
|
+
expect(panel("two")).toBeNull()
|
|
399
|
+
expect(panel("three")).not.toBeNull()
|
|
400
|
+
|
|
401
|
+
// The sole visible panel refuses to hide.
|
|
402
|
+
await user.click(tab("three"))
|
|
403
|
+
await settleExit()
|
|
404
|
+
expect(panel("three")).not.toBeNull()
|
|
405
|
+
|
|
406
|
+
// Activating another trades places with it.
|
|
407
|
+
await user.click(tab("one"))
|
|
408
|
+
await settleExit()
|
|
409
|
+
expect(panel("one")).not.toBeNull()
|
|
410
|
+
expect(panel("three")).toBeNull()
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
test("hovering a tab spotlights its panel and dims the others", async () => {
|
|
414
|
+
const user = userEvent.setup()
|
|
415
|
+
render(<ResponsiveWorkspace />)
|
|
416
|
+
|
|
417
|
+
resizeCanvas(1200)
|
|
418
|
+
await user.hover(tab("two"))
|
|
419
|
+
expect(panel("two")?.getAttribute("data-dimmed")).toBeNull()
|
|
420
|
+
expect(panel("one")?.getAttribute("data-dimmed")).toBe("")
|
|
421
|
+
expect(panel("three")?.getAttribute("data-dimmed")).toBe("")
|
|
422
|
+
|
|
423
|
+
await user.unhover(tab("two"))
|
|
424
|
+
expect(panel("one")?.getAttribute("data-dimmed")).toBeNull()
|
|
425
|
+
expect(panel("three")?.getAttribute("data-dimmed")).toBeNull()
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
test("user-hidden panels stay hidden when the canvas widens", async () => {
|
|
429
|
+
const user = userEvent.setup()
|
|
430
|
+
render(<ResponsiveWorkspace />)
|
|
431
|
+
|
|
432
|
+
resizeCanvas(1200)
|
|
433
|
+
await user.click(tab("two"))
|
|
434
|
+
await settleExit()
|
|
435
|
+
expect(panel("two")).toBeNull()
|
|
436
|
+
|
|
437
|
+
resizeCanvas(1400)
|
|
438
|
+
await settleExit()
|
|
439
|
+
expect(panel("two")).toBeNull()
|
|
440
|
+
|
|
441
|
+
// Its tab presses back to visible.
|
|
442
|
+
await user.click(tab("two"))
|
|
443
|
+
expect(await screen.findByRole("region", { name: "Panel two" })).toBeDefined()
|
|
444
|
+
})
|
|
445
|
+
})
|
|
@@ -4,6 +4,17 @@ export const SCAFFOLD_SIDEBAR_WIDTH = 64
|
|
|
4
4
|
/** Inspector drawer width in px. */
|
|
5
5
|
export const SCAFFOLD_INSPECTOR_WIDTH = 272
|
|
6
6
|
|
|
7
|
+
/** Most panels the canvas holds at once — omit Actions' `onAdd` when the
|
|
8
|
+
* count reaches it so the Add segment hides. */
|
|
9
|
+
export const SCAFFOLD_PANEL_CAPACITY = 3
|
|
10
|
+
|
|
11
|
+
/** Narrowest a panel may render, in px. When the canvas can't grant every
|
|
12
|
+
* panel this width, the scaffold hides panels until the rest fit. */
|
|
13
|
+
export const SCAFFOLD_PANEL_MIN_WIDTH = 320
|
|
14
|
+
|
|
15
|
+
/** Gap in px between side-by-side panels on the canvas (`gap-2`). */
|
|
16
|
+
export const SCAFFOLD_CANVAS_GAP = 8
|
|
17
|
+
|
|
7
18
|
/** Gap in px between the drawer/actions and the viewport edges. */
|
|
8
19
|
export const SCAFFOLD_EDGE_GUTTER = 10
|
|
9
20
|
|
|
@@ -7,6 +7,7 @@ import { ScaffoldMain } from "./scaffold-main"
|
|
|
7
7
|
import { ScaffoldPanel } from "./scaffold-panel"
|
|
8
8
|
import { ScaffoldRoot } from "./scaffold-root"
|
|
9
9
|
import { ScaffoldSidebar } from "./scaffold-sidebar"
|
|
10
|
+
import { ScaffoldTab } from "./scaffold-tab"
|
|
10
11
|
import { ScaffoldSubPanel } from "@/components/blocks/scaffold/scaffold-sub-panel.tsx"
|
|
11
12
|
|
|
12
13
|
export { useScaffoldContext } from "./scaffold-context"
|
|
@@ -24,6 +25,7 @@ export {
|
|
|
24
25
|
ScaffoldPanel,
|
|
25
26
|
ScaffoldRoot,
|
|
26
27
|
ScaffoldSidebar,
|
|
28
|
+
ScaffoldTab,
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export const Scaffold = {
|
|
@@ -33,6 +35,7 @@ export const Scaffold = {
|
|
|
33
35
|
Actions: ScaffoldActions,
|
|
34
36
|
Canvas: ScaffoldCanvas,
|
|
35
37
|
Panel: ScaffoldPanel,
|
|
38
|
+
Tab: ScaffoldTab,
|
|
36
39
|
Inspector: ScaffoldInspector,
|
|
37
|
-
SubPanel: ScaffoldSubPanel
|
|
40
|
+
SubPanel: ScaffoldSubPanel,
|
|
38
41
|
}
|