@exegia/corpora-ui 0.18.0 → 0.19.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/scaffold/constants.d.ts +3 -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-panel.d.ts +1 -1
- package/dist-lib/components/blocks/scaffold/scaffold-tab.d.ts +9 -0
- package/dist-lib/components/blocks/scaffold/type.d.ts +30 -28
- package/dist-lib/components/blocks/scaffold/utils.d.ts +6 -0
- package/dist-lib/index.js +870 -848
- package/dist-lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +108 -25
- package/src/components/blocks/scaffold/constants.ts +4 -0
- package/src/components/blocks/scaffold/index.ts +4 -1
- package/src/components/blocks/scaffold/panel-menu-button.tsx +93 -115
- package/src/components/blocks/scaffold/scaffold-actions.tsx +41 -50
- package/src/components/blocks/scaffold/scaffold-inspector.tsx +26 -3
- package/src/components/blocks/scaffold/scaffold-panel.tsx +15 -25
- package/src/components/blocks/scaffold/scaffold-sidebar.tsx +1 -1
- package/src/components/blocks/scaffold/scaffold-tab.tsx +66 -0
- package/src/components/blocks/scaffold/type.ts +34 -33
- package/src/components/blocks/scaffold/utils.ts +16 -0
|
@@ -8,6 +8,8 @@ import { SCAFFOLD_EASE, SCAFFOLD_EDGE_GUTTER, SCAFFOLD_MORPH_DURATION } from "./
|
|
|
8
8
|
import { useScaffoldContext } from "./scaffold-context"
|
|
9
9
|
import type { ScaffoldInspectorProps } from "./type"
|
|
10
10
|
import { GlassContainer } from "@/components/ui/glasscn/glass-container.tsx"
|
|
11
|
+
import { Button } from "@/components/ui/button"
|
|
12
|
+
import { LucideX } from "lucide-react"
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* The right-hand drawer: a brighter card that slides in over the canvas,
|
|
@@ -21,7 +23,8 @@ export function ScaffoldInspector({
|
|
|
21
23
|
className,
|
|
22
24
|
...rest
|
|
23
25
|
}: ScaffoldInspectorProps): React.ReactElement {
|
|
24
|
-
const { inspectorOpen, inspectorWidth } =
|
|
26
|
+
const { inspectorOpen, inspectorWidth, toggleInspector } =
|
|
27
|
+
useScaffoldContext()
|
|
25
28
|
const reducedMotion = useReducedMotion()
|
|
26
29
|
|
|
27
30
|
// Slide fully past the edge gutter and the drop shadow's blur radius.
|
|
@@ -34,7 +37,7 @@ export function ScaffoldInspector({
|
|
|
34
37
|
aria-hidden={inspectorOpen ? undefined : true}
|
|
35
38
|
aria-label={name}
|
|
36
39
|
className={cn(
|
|
37
|
-
"absolute right-0
|
|
40
|
+
"absolute right-0 z-20 my-2 flex h-full max-h-[calc(100%-0.5rem)] w-96 flex-col",
|
|
38
41
|
!inspectorOpen && "pointer-events-none",
|
|
39
42
|
className
|
|
40
43
|
)}
|
|
@@ -49,7 +52,27 @@ export function ScaffoldInspector({
|
|
|
49
52
|
}}
|
|
50
53
|
{...rest}
|
|
51
54
|
>
|
|
52
|
-
<GlassContainer
|
|
55
|
+
<GlassContainer
|
|
56
|
+
className="flex flex-1 flex-col rounded-md border-t border-white bg-neutral-50/10 shadow-inner shadow-md dark:border-neutral-800 dark:bg-black/5"
|
|
57
|
+
glassVariant="subtle"
|
|
58
|
+
>
|
|
59
|
+
<div
|
|
60
|
+
id="scaffold-inspector-header"
|
|
61
|
+
className="flex h-12 items-center justify-between gap-2 border-b pr-3 pl-4"
|
|
62
|
+
>
|
|
63
|
+
<span className="text-base font-normal text-card-foreground/70">
|
|
64
|
+
{name}
|
|
65
|
+
</span>
|
|
66
|
+
<Button
|
|
67
|
+
size="icon-xs"
|
|
68
|
+
variant="secondary"
|
|
69
|
+
className="scale-85"
|
|
70
|
+
aria-label="Close inspector"
|
|
71
|
+
onClick={toggleInspector}
|
|
72
|
+
>
|
|
73
|
+
<LucideX className="stroke-3" />
|
|
74
|
+
</Button>
|
|
75
|
+
</div>
|
|
53
76
|
{children}
|
|
54
77
|
</GlassContainer>
|
|
55
78
|
</motion.aside>
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { motion, useReducedMotion } from "motion/react"
|
|
4
4
|
import * as React from "react"
|
|
5
|
+
import { useState } from "react"
|
|
5
6
|
|
|
6
7
|
import { cn } from "@/lib/utils"
|
|
7
8
|
import { SCAFFOLD_EASE, SCAFFOLD_MORPH_DURATION } from "./constants"
|
|
8
|
-
import { PanelCloseButton } from "./panel-close-button"
|
|
9
9
|
import { PanelMenuButton } from "./panel-menu-button.tsx"
|
|
10
10
|
import type { ScaffoldPanelProps, TSubPanelPosition } from "./type"
|
|
11
11
|
import { ScaffoldSubPanel } from "@/components/blocks/scaffold/scaffold-sub-panel.tsx"
|
|
12
|
-
import { useState } from "react"
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* A canvas panel: a primary card plus an optional secondary strip below it,
|
|
@@ -21,15 +20,13 @@ import { useState } from "react"
|
|
|
21
20
|
export function ScaffoldPanel({
|
|
22
21
|
children,
|
|
23
22
|
SecondaryPanel,
|
|
24
|
-
onClose,
|
|
25
23
|
onSwap,
|
|
24
|
+
onCloseSecondary,
|
|
26
25
|
name,
|
|
27
|
-
closeLabel = "Close panel",
|
|
28
26
|
swapLabel = "Swap panel content",
|
|
29
27
|
sound = true,
|
|
30
28
|
className,
|
|
31
29
|
}: ScaffoldPanelProps): React.ReactElement {
|
|
32
|
-
|
|
33
30
|
const reducedMotion = useReducedMotion()
|
|
34
31
|
|
|
35
32
|
const transition = {
|
|
@@ -37,18 +34,19 @@ export function ScaffoldPanel({
|
|
|
37
34
|
ease: SCAFFOLD_EASE,
|
|
38
35
|
}
|
|
39
36
|
|
|
40
|
-
const [subPanelPosition, setSubPanelPosition] =
|
|
37
|
+
const [subPanelPosition, setSubPanelPosition] =
|
|
38
|
+
useState<TSubPanelPosition>("top")
|
|
41
39
|
const [isSwapped, setIsSwapped] = useState<boolean>(false)
|
|
42
40
|
const handleSwap = () => {
|
|
43
|
-
setIsSwapped((prev) => !prev)
|
|
44
|
-
onSwap?.()
|
|
45
|
-
}
|
|
41
|
+
setIsSwapped((prev) => !prev)
|
|
42
|
+
onSwap?.()
|
|
43
|
+
}
|
|
46
44
|
|
|
47
45
|
// Trades which sub-panel holds the flexible slot — the collapsed card grows
|
|
48
46
|
// into `flex-1` while the expanded one shrinks to the `min-h-14` strip.
|
|
49
47
|
const handleExpand = () => {
|
|
50
|
-
setSubPanelPosition((prev) => (prev === "top" ? "bottom" : "top"))
|
|
51
|
-
}
|
|
48
|
+
setSubPanelPosition((prev) => (prev === "top" ? "bottom" : "top"))
|
|
49
|
+
}
|
|
52
50
|
|
|
53
51
|
return (
|
|
54
52
|
<motion.section
|
|
@@ -64,35 +62,27 @@ export function ScaffoldPanel({
|
|
|
64
62
|
data-swapped={isSwapped ? "" : undefined}
|
|
65
63
|
transition={transition}
|
|
66
64
|
>
|
|
67
|
-
<ScaffoldSubPanel
|
|
68
|
-
id="top"
|
|
69
|
-
expanded={subPanelPosition === "top"}
|
|
70
|
-
primary
|
|
71
|
-
>
|
|
65
|
+
<ScaffoldSubPanel id="top" expanded={subPanelPosition === "top"} primary>
|
|
72
66
|
{children}
|
|
73
67
|
</ScaffoldSubPanel>
|
|
74
|
-
{onSwap && SecondaryPanel && (
|
|
75
|
-
<div className="relative flex items-center justify-center
|
|
68
|
+
{(onSwap || onCloseSecondary) && SecondaryPanel && (
|
|
69
|
+
<div className="relative flex h-2 items-center justify-center">
|
|
76
70
|
<PanelMenuButton
|
|
77
71
|
label={swapLabel}
|
|
78
72
|
onClick={handleSwap}
|
|
79
73
|
onExpand={handleExpand}
|
|
74
|
+
onCloseSecondary={onCloseSecondary}
|
|
80
75
|
secondaryExpanded={subPanelPosition === "bottom"}
|
|
76
|
+
swapped={isSwapped}
|
|
81
77
|
sound={sound}
|
|
82
78
|
/>
|
|
83
79
|
</div>
|
|
84
80
|
)}
|
|
85
81
|
{SecondaryPanel && (
|
|
86
|
-
<ScaffoldSubPanel
|
|
87
|
-
id="bottom"
|
|
88
|
-
expanded={subPanelPosition === "bottom"}
|
|
89
|
-
>
|
|
82
|
+
<ScaffoldSubPanel id="bottom" expanded={subPanelPosition === "bottom"}>
|
|
90
83
|
{SecondaryPanel}
|
|
91
84
|
</ScaffoldSubPanel>
|
|
92
85
|
)}
|
|
93
|
-
{onClose && (
|
|
94
|
-
<PanelCloseButton label={closeLabel} onClick={onClose} sound={sound} />
|
|
95
|
-
)}
|
|
96
86
|
</motion.section>
|
|
97
87
|
)
|
|
98
88
|
}
|
|
@@ -19,7 +19,7 @@ export function ScaffoldSidebar({
|
|
|
19
19
|
id="scaffold-sidebar"
|
|
20
20
|
aria-label="Primary"
|
|
21
21
|
className={cn(
|
|
22
|
-
"flex min-w-
|
|
22
|
+
"flex min-w-12 shrink-0 flex-col items-center gap-1 pt-10 pb-2",
|
|
23
23
|
className
|
|
24
24
|
)}
|
|
25
25
|
data-slot="scaffold-sidebar"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useReducedMotion } from "motion/react"
|
|
4
|
+
import * as React from "react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { SPRING_LAYOUT } from "@/lib/ease.ts"
|
|
8
|
+
import type { ScaffoldTabProps } from "./type"
|
|
9
|
+
import { segmentVariants } from "./utils"
|
|
10
|
+
import { Button } from "@/components/ui/button"
|
|
11
|
+
import { LucideAppWindow, LucideX } from "lucide-react"
|
|
12
|
+
import { GlassButtonGroup } from "@/components/ui/glasscn/glass-button-group.tsx"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A panel's tab inside the action cluster: its label plus a close
|
|
16
|
+
* affordance. Render one per open panel as Actions children, keyed —
|
|
17
|
+
* closes animate out of the pill. Omit `onClose` on the last remaining
|
|
18
|
+
* panel's tab; the canvas keeps at least one panel open.
|
|
19
|
+
*/
|
|
20
|
+
export function ScaffoldTab({
|
|
21
|
+
children,
|
|
22
|
+
onClose,
|
|
23
|
+
closeLabel = "Close panel",
|
|
24
|
+
sound = true,
|
|
25
|
+
className,
|
|
26
|
+
...rest
|
|
27
|
+
}: ScaffoldTabProps): React.ReactElement {
|
|
28
|
+
const reduce = useReducedMotion()
|
|
29
|
+
const transition = reduce ? { duration: 0 } : SPRING_LAYOUT
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<GlassButtonGroup
|
|
33
|
+
layout
|
|
34
|
+
animate={reduce ? { opacity: 1 } : "visible"}
|
|
35
|
+
data-slot="scaffold-tab"
|
|
36
|
+
exit={reduce ? { opacity: 0 } : "exit"}
|
|
37
|
+
initial={reduce ? { opacity: 0 } : "hidden"}
|
|
38
|
+
transition={transition}
|
|
39
|
+
variants={segmentVariants}
|
|
40
|
+
glassVariant="subtle"
|
|
41
|
+
className={cn("h-full rounded-sm", className)}
|
|
42
|
+
{...rest}
|
|
43
|
+
>
|
|
44
|
+
<Button
|
|
45
|
+
size="sm"
|
|
46
|
+
variant="ghost"
|
|
47
|
+
className={cn("gap-2.5 rounded-sm pl-2", onClose && "pr-2")}
|
|
48
|
+
>
|
|
49
|
+
<LucideAppWindow size={16} />
|
|
50
|
+
{children}
|
|
51
|
+
</Button>
|
|
52
|
+
{onClose && (
|
|
53
|
+
<Button
|
|
54
|
+
aria-label={closeLabel}
|
|
55
|
+
onClick={onClose}
|
|
56
|
+
size="icon-sm"
|
|
57
|
+
sound={sound}
|
|
58
|
+
variant="ghost"
|
|
59
|
+
className="rounded-none!"
|
|
60
|
+
>
|
|
61
|
+
<LucideX size={16} />
|
|
62
|
+
</Button>
|
|
63
|
+
)}
|
|
64
|
+
</GlassButtonGroup>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
type ComponentProps,
|
|
3
|
-
type ReactElement,
|
|
4
|
-
type ReactNode,
|
|
5
|
-
} from "react"
|
|
1
|
+
import React, { type ComponentProps, type ReactElement, type ReactNode, } from "react"
|
|
6
2
|
|
|
7
3
|
/** Inspector state shared by every scaffold part through ScaffoldContext. */
|
|
8
4
|
export interface ScaffoldContextValue {
|
|
@@ -46,23 +42,16 @@ type MotionSafe<T> = Omit<
|
|
|
46
42
|
export interface ScaffoldActionsProps extends MotionSafe<
|
|
47
43
|
Omit<ComponentProps<"div">, "children">
|
|
48
44
|
> {
|
|
49
|
-
/** Called when the Add segment is pressed.
|
|
45
|
+
/** Called when the Add segment is pressed. The segment renders only
|
|
46
|
+
* while this is present — omit it to hide Add, e.g. once the canvas
|
|
47
|
+
* holds `SCAFFOLD_PANEL_CAPACITY` panels. */
|
|
50
48
|
onAdd?: () => void
|
|
51
49
|
/** Label of the Add segment. */
|
|
52
50
|
addLabel?: ReactNode
|
|
53
51
|
/** Replaces the default add icon. */
|
|
54
52
|
addIcon?: ReactNode
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
57
|
-
* `onBrowse` is present; the badge only when > 0. Per the design
|
|
58
|
-
* comments the dropdown menu itself is a later iteration; for now the
|
|
59
|
-
* segment just keeps the count. */
|
|
60
|
-
overflowCount?: number
|
|
61
|
-
/** Called when the overflow (stacked-panels) segment is pressed. */
|
|
62
|
-
onBrowse?: () => void
|
|
63
|
-
/** Accessible name of the overflow segment. */
|
|
64
|
-
browseLabel?: string
|
|
65
|
-
/** Extra segments appended after the built-in ones. */
|
|
53
|
+
/** The panel tabs (`Scaffold.Tab`, one per open panel) and any extra
|
|
54
|
+
* segments, rendered before Add. Key each tab — closes animate out. */
|
|
66
55
|
children?: ReactNode
|
|
67
56
|
/** Emit interaction-sound attributes (inert until an app binds cuelume). */
|
|
68
57
|
sound?: boolean
|
|
@@ -77,32 +66,38 @@ export interface ScaffoldPanelProps {
|
|
|
77
66
|
children: ReactNode
|
|
78
67
|
/** The panel's content, rendered in a card with a drop shadow. */
|
|
79
68
|
SecondaryPanel?: TScaffoldPanelChild
|
|
80
|
-
/**
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
* Only shown when `secondary` is present. Clicking it trades the two
|
|
84
|
-
* cards with a layout morph — flip the slot content here so each card
|
|
85
|
-
* carries its subject to its new home. */
|
|
69
|
+
/** Reveals the Swap action in the seam menu (needs `SecondaryPanel`).
|
|
70
|
+
* Swapping trades the two cards with a layout morph — flip the slot
|
|
71
|
+
* content here so each card carries its subject to its new home. */
|
|
86
72
|
onSwap?: () => void
|
|
87
|
-
/**
|
|
88
|
-
*
|
|
89
|
-
|
|
90
|
-
/** Initial swap state when uncontrolled. */
|
|
91
|
-
defaultSwapped?: boolean
|
|
92
|
-
/** Fixed width in px; omitted, the panel flexes to share the canvas. */
|
|
93
|
-
width?: number
|
|
73
|
+
/** Reveals the Close action in the seam menu — remove `SecondaryPanel`
|
|
74
|
+
* here; the strip and the menu leave together. */
|
|
75
|
+
onCloseSecondary?: () => void
|
|
94
76
|
/** Accessible name of the panel region. */
|
|
95
77
|
name?: string
|
|
96
78
|
/** Emit interaction-sound attributes on the floating buttons. */
|
|
97
79
|
sound?: boolean
|
|
98
|
-
/** Accessible name of the
|
|
99
|
-
closeLabel?: string
|
|
100
|
-
/** Accessible name of the swap button. */
|
|
80
|
+
/** Accessible name of the seam-menu toggle. */
|
|
101
81
|
swapLabel?: string
|
|
102
82
|
/** Extra class names for the panel's root element. */
|
|
103
83
|
className?: string
|
|
104
84
|
}
|
|
105
85
|
|
|
86
|
+
export interface ScaffoldTabProps extends MotionSafe<
|
|
87
|
+
Omit<ComponentProps<"div">, "children">
|
|
88
|
+
> {
|
|
89
|
+
/** The tab's label. */
|
|
90
|
+
children?: ReactNode
|
|
91
|
+
/** Renders the tab's close button. The button renders only while this
|
|
92
|
+
* is present — omit it to pin the tab, e.g. on the last remaining
|
|
93
|
+
* panel (the canvas keeps at least one open). */
|
|
94
|
+
onClose?: () => void
|
|
95
|
+
/** Accessible name of the close button. */
|
|
96
|
+
closeLabel?: string
|
|
97
|
+
/** Emit interaction-sound attributes on the close button. */
|
|
98
|
+
sound?: boolean
|
|
99
|
+
}
|
|
100
|
+
|
|
106
101
|
export type TSubPanelPosition = "bottom" | "top"
|
|
107
102
|
export type TSubPanelVariant = "card" | "subtle" | "inset"
|
|
108
103
|
|
|
@@ -147,9 +142,15 @@ export interface PanelMenuButtonProps extends PanelFloatingButtonProps {
|
|
|
147
142
|
/** Called when the Expand action is pressed — the panel trades which
|
|
148
143
|
* sub-panel holds the flexible slot. */
|
|
149
144
|
onExpand?: () => void
|
|
145
|
+
/** Renders the Close action; called when it is pressed — the consumer
|
|
146
|
+
* removes the secondary strip. */
|
|
147
|
+
onCloseSecondary?: () => void
|
|
150
148
|
/** Whether the secondary strip currently holds the flexible slot —
|
|
151
149
|
* flips the Expand chevron between down and up. */
|
|
152
150
|
secondaryExpanded?: boolean
|
|
151
|
+
/** Whether the panel is swapped (column-reverse). Mirrors the Expand
|
|
152
|
+
* chevron so it keeps pointing at the sub-panel that grows next. */
|
|
153
|
+
swapped?: boolean
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
export interface UseScaffoldOptions {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { Variants } from "motion/react"
|
|
1
2
|
import type { ClassNameValue } from "tailwind-merge"
|
|
2
3
|
import type { TSubPanelVariant } from "@/components/blocks/scaffold/type.ts"
|
|
4
|
+
import { EASE_OUT } from "@/lib/ease.ts"
|
|
3
5
|
|
|
4
6
|
/** Desktop backdrop the whole scaffold sits on — a soft warm-gray wash. */
|
|
5
7
|
export const scaffoldBackgroundClass: ClassNameValue =
|
|
@@ -22,6 +24,20 @@ export const revealOnPanelHoverClass: ClassNameValue =
|
|
|
22
24
|
export const actionSegmentClass: ClassNameValue =
|
|
23
25
|
"flex h-[30px] items-center rounded-full text-neutral-700 transition-[background-color,color,scale,box-shadow] duration-150 ease-smooth-out hover:bg-white hover:text-neutral-900 hover:shadow-xs active:scale-97 dark:text-neutral-300 dark:hover:bg-neutral-700 dark:hover:text-white motion-reduce:transition-none motion-reduce:active:scale-100"
|
|
24
26
|
|
|
27
|
+
/** Pill segments (seam-menu actions, tabs, Add) blur/fade in place while
|
|
28
|
+
* the track morphs around them. The exit is a fast tween, not the layout
|
|
29
|
+
* spring — the track stays overflow-visible, so popped-out segments must be
|
|
30
|
+
* gone before the shrinking pill leaves them behind as a ghost. */
|
|
31
|
+
export const segmentVariants: Variants = {
|
|
32
|
+
hidden: { opacity: 0, filter: "blur(4px)" },
|
|
33
|
+
visible: { opacity: 1, filter: "blur(0px)" },
|
|
34
|
+
exit: {
|
|
35
|
+
opacity: 0,
|
|
36
|
+
filter: "blur(4px)",
|
|
37
|
+
transition: { duration: 0.18, ease: EASE_OUT },
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
/** Map of sub-panel variants to their corresponding side. */
|
|
26
42
|
export const subPanelVariant: Record<TSubPanelVariant, ClassNameValue> = {
|
|
27
43
|
card: "bg-neutral-50 dark:bg-neutral-900 rounded-md border-t border-b border-t-white dark:border-t-neutral-700/50 border-b-neutral-900/10 dark:border-b-black/70 shadow-sm shadow-neutral-900/10 dark:shadow-black/50 inset-shadow-sm inset-shadow-white dark:inset-shadow-neutral-800",
|