@exegia/corpora-ui 0.15.0 → 0.17.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/shell/shell-layout.d.ts +1 -1
- package/dist-lib/components/blocks/shell/type.d.ts +5 -0
- package/dist-lib/components/motion/animated-sidebar.d.ts +2 -5
- package/dist-lib/index.js +869 -877
- package/dist-lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/blocks/shell/__tests__/shell-layout.test.tsx +22 -3
- package/src/components/blocks/shell/shell-layout.tsx +32 -40
- package/src/components/blocks/shell/type.ts +5 -0
- package/src/components/motion/animated-sidebar.tsx +25 -50
- package/src/components/ui/button.tsx +3 -3
package/package.json
CHANGED
|
@@ -73,12 +73,31 @@ describe("ShellLayout", () => {
|
|
|
73
73
|
expect(leftRail().getAttribute("data-state")).toBe("collapsed")
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
test("a panel's
|
|
76
|
+
test("a panel's trigger node replaces the default icon, keeping the toggle", async () => {
|
|
77
|
+
const user = userEvent.setup()
|
|
78
|
+
render(
|
|
79
|
+
<ShellLayout
|
|
80
|
+
panels={{
|
|
81
|
+
...PANELS,
|
|
82
|
+
right: { ...PANELS.right!, trigger: <span>Custom trigger</span> },
|
|
83
|
+
}}
|
|
84
|
+
variant="web"
|
|
85
|
+
/>
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const rightTrigger = screen.getByRole("button", { name: "Toggle panel" })
|
|
89
|
+
expect(rightTrigger.textContent).toContain("Custom trigger")
|
|
90
|
+
|
|
91
|
+
await user.click(rightTrigger)
|
|
92
|
+
expect(rightDrawer().getAttribute("data-state")).toBe("expanded")
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test("a panel's own defaultOpen flag seeds its side's initial state", () => {
|
|
77
96
|
render(
|
|
78
97
|
<ShellLayout
|
|
79
98
|
panels={{
|
|
80
99
|
...PANELS,
|
|
81
|
-
right: { ...PANELS.right!,
|
|
100
|
+
right: { ...PANELS.right!, defaultOpen: true },
|
|
82
101
|
}}
|
|
83
102
|
variant="web"
|
|
84
103
|
/>
|
|
@@ -93,7 +112,7 @@ describe("ShellLayout", () => {
|
|
|
93
112
|
defaultOpen={{ left: false, right: false }}
|
|
94
113
|
panels={{
|
|
95
114
|
...PANELS,
|
|
96
|
-
right: { ...PANELS.right!,
|
|
115
|
+
right: { ...PANELS.right!, defaultOpen: true },
|
|
97
116
|
}}
|
|
98
117
|
variant="web"
|
|
99
118
|
/>
|
|
@@ -19,6 +19,7 @@ export function ShellLayout({
|
|
|
19
19
|
variant = "desktop",
|
|
20
20
|
panels,
|
|
21
21
|
className,
|
|
22
|
+
header,
|
|
22
23
|
defaultOpen,
|
|
23
24
|
...panelControlProps
|
|
24
25
|
}: ShellLayoutProps): React.ReactElement {
|
|
@@ -29,22 +30,27 @@ export function ShellLayout({
|
|
|
29
30
|
// an explicit `defaultOpen` record — usually from useShellPanels — wins
|
|
30
31
|
// per side.
|
|
31
32
|
const initialOpen: ShellPanelControlProps["defaultOpen"] = {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
...(panels?.left && {
|
|
34
|
+
left: panels.left.defaultOpen ?? true,
|
|
35
|
+
}),
|
|
36
|
+
...(panels?.right && {
|
|
37
|
+
right: panels.right.defaultOpen ?? false,
|
|
38
|
+
}),
|
|
39
|
+
...defaultOpen,
|
|
40
|
+
}
|
|
41
|
+
|
|
40
42
|
|
|
41
43
|
return (
|
|
42
44
|
<AnimatedSidebarProvider
|
|
43
45
|
{...panelControlProps}
|
|
44
46
|
defaultOpen={initialOpen}
|
|
45
|
-
className={cn(
|
|
47
|
+
className={cn(
|
|
48
|
+
"relative block-full pr-2",
|
|
49
|
+
className,
|
|
50
|
+
background
|
|
51
|
+
)}
|
|
46
52
|
style={{
|
|
47
|
-
paddingTop: variant === "desktop" ? TITLE_BAR_HEIGHT : 0
|
|
53
|
+
paddingTop: variant === "desktop" ? TITLE_BAR_HEIGHT : 0,
|
|
48
54
|
}}
|
|
49
55
|
>
|
|
50
56
|
<AnimatedSidebar
|
|
@@ -56,21 +62,21 @@ export function ShellLayout({
|
|
|
56
62
|
{panels?.left?.component}
|
|
57
63
|
</AnimatedSidebar>
|
|
58
64
|
|
|
59
|
-
<AnimatedSidebarInset
|
|
60
|
-
className=
|
|
61
|
-
"min-w-
|
|
62
|
-
`mx-0!`
|
|
63
|
-
)}
|
|
64
|
-
>
|
|
65
|
-
<header className="flex h-16 items-center justify-between gap-3 px-4 sm:px-6">
|
|
66
|
-
<div className="flex min-w-0 items-center gap-3">
|
|
65
|
+
<AnimatedSidebarInset className="min-w-24">
|
|
66
|
+
<header className="flex h-12 items-center justify-between gap-2 px-2">
|
|
67
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
67
68
|
<AnimatedSidebarTrigger>
|
|
68
|
-
|
|
69
|
+
{panels?.left?.trigger ?? (
|
|
70
|
+
<MotionIcon name="PanelLeft" size={24} animation="press" />
|
|
71
|
+
)}
|
|
69
72
|
</AnimatedSidebarTrigger>
|
|
70
73
|
</div>
|
|
71
|
-
<div className="flex
|
|
74
|
+
{header && <div className="flex flex-1 items-center">{header}</div>}
|
|
75
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
72
76
|
<AnimatedSidebarTrigger aria-label="Toggle panel" side="right">
|
|
73
|
-
|
|
77
|
+
{panels?.right?.trigger ?? (
|
|
78
|
+
<MotionIcon className="opacity-70" name="PanelRight" size={24} />
|
|
79
|
+
)}
|
|
74
80
|
</AnimatedSidebarTrigger>
|
|
75
81
|
</div>
|
|
76
82
|
</header>
|
|
@@ -83,16 +89,10 @@ export function ShellLayout({
|
|
|
83
89
|
ariaLabel={panels?.right?.name ?? "Secondary panel"}
|
|
84
90
|
// Below md the panel is portal led over the page, so it carries the
|
|
85
91
|
// surface itself; the desktop rail keeps it on the inner panel.
|
|
86
|
-
className={cn(
|
|
87
|
-
|
|
88
|
-
panelClassName={cn(
|
|
89
|
-
// The off canvas panel keeps a fixed width so it can slide out of the
|
|
90
|
-
// zero-width rail, so the inset gutter has to come out of that width
|
|
91
|
-
// rather than from a left margin.
|
|
92
|
-
"top-2 ml-0 h-[calc(100%-1rem)] min-w-[calc(var(--sidebar-width))]",
|
|
93
|
-
"rounded-lg border-t border-neutral-200 bg-white shadow-md inset-ring-1 inset-ring-neutral-50",
|
|
94
|
-
"dark:border-neutral-700 dark:bg-card dark:inset-ring-black"
|
|
92
|
+
className={cn(
|
|
93
|
+
"max-md:border-l max-md:border-neutral-200 max-md:bg-white dark:max-md:border-neutral-700 dark:max-md:bg-card"
|
|
95
94
|
)}
|
|
95
|
+
collapsible="offcanvas"
|
|
96
96
|
role="complementary"
|
|
97
97
|
side="right"
|
|
98
98
|
variant="inset"
|
|
@@ -105,16 +105,8 @@ export function ShellLayout({
|
|
|
105
105
|
|
|
106
106
|
function ShellLayoutPlaceholder() {
|
|
107
107
|
return (
|
|
108
|
-
<div className="
|
|
109
|
-
<div className="
|
|
110
|
-
{Array.from({ length: 3 }).map((_, index) => (
|
|
111
|
-
<div
|
|
112
|
-
className="h-32 border border-border bg-background/60"
|
|
113
|
-
key={index}
|
|
114
|
-
/>
|
|
115
|
-
))}
|
|
116
|
-
</div>
|
|
117
|
-
<div className="h-72 border border-border bg-background/60" />
|
|
108
|
+
<div className="flex flex-col flex-1 p-4">
|
|
109
|
+
<div className="h-96 border border-border bg-muted-foreground/10 rounded-lg" />
|
|
118
110
|
</div>
|
|
119
111
|
)
|
|
120
112
|
}
|
|
@@ -23,6 +23,10 @@ export interface IPanel {
|
|
|
23
23
|
id: string
|
|
24
24
|
name: string
|
|
25
25
|
component: ReactNode
|
|
26
|
+
/** Replaces the default icon inside the panel's header trigger. The shell
|
|
27
|
+
* keeps the trigger button itself (toggle wiring, aria-label) — this only
|
|
28
|
+
* swaps what renders inside it. */
|
|
29
|
+
trigger?: ReactNode
|
|
26
30
|
open: boolean
|
|
27
31
|
defaultOpen?: boolean
|
|
28
32
|
side: "left" | "bottom" | "right" | "sidebar"
|
|
@@ -87,6 +91,7 @@ export interface ShellLayoutProps extends ShellPanelControlProps {
|
|
|
87
91
|
/** Content for the shell's panels, keyed by the side. The shell renders
|
|
88
92
|
* the `left` rail and the `right` drawer; other sides are reserved. */
|
|
89
93
|
panels?: TPanelMap
|
|
94
|
+
header?: ReactNode
|
|
90
95
|
className?: string
|
|
91
96
|
variant?: "web" | "desktop"
|
|
92
97
|
}
|
|
@@ -29,9 +29,10 @@ import { createPortal } from "react-dom"
|
|
|
29
29
|
import { SharedLayoutBg } from "@/components/motion/shared-layout-bg"
|
|
30
30
|
import { EASE_DRAWER, EASE_OUT, SPRING_LAYOUT, SPRING_PRESS } from "@/lib/ease"
|
|
31
31
|
import { cn } from "@/lib/utils"
|
|
32
|
+
import { Button } from "@/components/ui/button";
|
|
32
33
|
|
|
33
34
|
export type SidebarSide = "left" | "right"
|
|
34
|
-
/** Open flags keyed by side. Sides left out stay uncontrolled / at their
|
|
35
|
+
/** Open flags keyed by the side. Sides left out stay uncontrolled / at their
|
|
35
36
|
* default — there is no per-side prop, the record IS the API. */
|
|
36
37
|
export type SidebarOpenState = Partial<Record<SidebarSide, boolean>>
|
|
37
38
|
type SidebarVariant = "sidebar" | "floating" | "inset"
|
|
@@ -198,7 +199,7 @@ type SidebarProviderStyle = CSSProperties & {
|
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
export interface AnimatedSidebarProviderProps extends HTMLAttributes<HTMLDivElement> {
|
|
201
|
-
/** Controlled desktop open state, keyed by side. A side left undefined
|
|
202
|
+
/** Controlled desktop open state, keyed by the side. A side left undefined
|
|
202
203
|
* stays uncontrolled. */
|
|
203
204
|
open?: SidebarOpenState
|
|
204
205
|
/** Initial desktop open state, merged over `{ left: true, right: false }`. */
|
|
@@ -572,7 +573,7 @@ export const AnimatedSidebar = forwardRef<HTMLElement, AnimatedSidebarProps>(
|
|
|
572
573
|
className={cn(
|
|
573
574
|
"group/sidebar relative hidden h-auto shrink-0 will-change-[width] md:block",
|
|
574
575
|
"peer flex!",
|
|
575
|
-
// The
|
|
576
|
+
// The off canvas panel keeps its full width and slides out of the
|
|
576
577
|
// zero-width rail, so clip it rather than letting it bleed past the
|
|
577
578
|
// shell edge. The clip margin lets the panel's own shadow escape
|
|
578
579
|
// without letting the slid-out panel escape with it
|
|
@@ -583,7 +584,9 @@ export const AnimatedSidebar = forwardRef<HTMLElement, AnimatedSidebarProps>(
|
|
|
583
584
|
)}
|
|
584
585
|
>
|
|
585
586
|
{side === "right" && !collapsed && (
|
|
586
|
-
<motion.div className="h-full
|
|
587
|
+
<motion.div className="flex h-full cursor-col-resize justify-center p-1 group relative">
|
|
588
|
+
<motion.div className="h-full w-full scale-y-0 animate-in bg-radial/decreasing from-amber-300/70 to-amber-500/0 to-40% transition-[colors,scale] duration-200 ease-smooth-out group-hover:scale-y-100 absolute" />
|
|
589
|
+
</motion.div>
|
|
587
590
|
)}
|
|
588
591
|
<motion.div
|
|
589
592
|
initial={false}
|
|
@@ -594,10 +597,19 @@ export const AnimatedSidebar = forwardRef<HTMLElement, AnimatedSidebarProps>(
|
|
|
594
597
|
transition={context.reduce ? REDUCED_TRANSITION : PANEL_TRANSITION}
|
|
595
598
|
className={cn(
|
|
596
599
|
"sticky top-0 flex h-svh w-full flex-col overflow-hidden",
|
|
597
|
-
|
|
600
|
+
"bg-neutral-50 dark:bg-neutral-900",
|
|
601
|
+
"outline-offset-0.5 border-t-3 border-white outline-neutral-100 dark:border-neutral-800 dark:inset-ring-black",
|
|
602
|
+
"rounded-lg shadow-md shadow-neutral-200 dark:shadow-neutral-950",
|
|
603
|
+
collapsible === "offcanvas" && "w-(--sidebar-width)",
|
|
598
604
|
|
|
599
605
|
variant === "floating" && "m-2 h-[calc(100svh-1rem)]",
|
|
600
|
-
variant === "inset" && "
|
|
606
|
+
variant === "inset" && "my-2 h-[calc(100svh-1rem)]",
|
|
607
|
+
variant === "inset" &&
|
|
608
|
+
side === "right" &&
|
|
609
|
+
"w-[calc(var(--sidebar-width)-0.5rem)]",
|
|
610
|
+
variant === "inset" &&
|
|
611
|
+
side === "left" &&
|
|
612
|
+
"border-transparent! bg-transparent! shadow-none!",
|
|
601
613
|
panelClassName
|
|
602
614
|
)}
|
|
603
615
|
>
|
|
@@ -620,7 +632,7 @@ export const AnimatedSidebarTrigger = forwardRef<
|
|
|
620
632
|
HTMLButtonElement,
|
|
621
633
|
AnimatedSidebarTriggerProps
|
|
622
634
|
>(function AnimatedSidebarTrigger(
|
|
623
|
-
{
|
|
635
|
+
{ onClick, side = "left", type = "button", ...props },
|
|
624
636
|
forwardedRef
|
|
625
637
|
) {
|
|
626
638
|
const context = useAnimatedSidebar()
|
|
@@ -630,7 +642,7 @@ export const AnimatedSidebarTrigger = forwardRef<
|
|
|
630
642
|
const triggerRef = context.triggerRefs[side]
|
|
631
643
|
|
|
632
644
|
return (
|
|
633
|
-
<
|
|
645
|
+
<Button
|
|
634
646
|
{...props}
|
|
635
647
|
ref={(node) => {
|
|
636
648
|
triggerRef.current = node
|
|
@@ -638,6 +650,8 @@ export const AnimatedSidebarTrigger = forwardRef<
|
|
|
638
650
|
else if (forwardedRef) forwardedRef.current = node
|
|
639
651
|
}}
|
|
640
652
|
type={type}
|
|
653
|
+
variant="ghost"
|
|
654
|
+
size="icon-xl"
|
|
641
655
|
aria-label={props["aria-label"] ?? "Toggle sidebar"}
|
|
642
656
|
aria-expanded={expanded}
|
|
643
657
|
data-slot="sidebar-trigger"
|
|
@@ -648,11 +662,6 @@ export const AnimatedSidebarTrigger = forwardRef<
|
|
|
648
662
|
if (event.defaultPrevented) return
|
|
649
663
|
context.toggleSidebar(side)
|
|
650
664
|
}}
|
|
651
|
-
className={cn(
|
|
652
|
-
"inline-flex size-10 shrink-0 items-center justify-center rounded-xl outline-none",
|
|
653
|
-
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
654
|
-
className
|
|
655
|
-
)}
|
|
656
665
|
/>
|
|
657
666
|
)
|
|
658
667
|
})
|
|
@@ -692,41 +701,6 @@ export const AnimatedSidebarClose = forwardRef<
|
|
|
692
701
|
)
|
|
693
702
|
})
|
|
694
703
|
|
|
695
|
-
export interface AnimatedSidebarRailProps extends ButtonHTMLAttributes<HTMLButtonElement> {}
|
|
696
|
-
|
|
697
|
-
export const AnimatedSidebarRail = forwardRef<
|
|
698
|
-
HTMLButtonElement,
|
|
699
|
-
AnimatedSidebarRailProps
|
|
700
|
-
>(function AnimatedSidebarRail(
|
|
701
|
-
{ className, onClick, type = "button", ...props },
|
|
702
|
-
forwardedRef
|
|
703
|
-
) {
|
|
704
|
-
const context = useAnimatedSidebar()
|
|
705
|
-
const panel = useAnimatedSidebarPanel()
|
|
706
|
-
|
|
707
|
-
return (
|
|
708
|
-
<button
|
|
709
|
-
{...props}
|
|
710
|
-
ref={forwardedRef}
|
|
711
|
-
type={type}
|
|
712
|
-
data-side={panel.side}
|
|
713
|
-
aria-label={props["aria-label"] ?? "Toggle sidebar"}
|
|
714
|
-
title="Toggle sidebar"
|
|
715
|
-
tabIndex={-1}
|
|
716
|
-
onClick={(event) => {
|
|
717
|
-
onClick?.(event)
|
|
718
|
-
if (event.defaultPrevented) return
|
|
719
|
-
context.toggleSidebar(panel.side)
|
|
720
|
-
}}
|
|
721
|
-
className={cn(
|
|
722
|
-
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 outline-none md:block",
|
|
723
|
-
"after:absolute after:inset-y-0 after:left-1/2 after:w-px after:bg-transparent after:transition-colors hover:after:bg-border",
|
|
724
|
-
"data-[side=left]:left-full data-[side=right]:right-0 data-[side=right]:translate-x-1/2",
|
|
725
|
-
className
|
|
726
|
-
)}
|
|
727
|
-
/>
|
|
728
|
-
)
|
|
729
|
-
})
|
|
730
704
|
|
|
731
705
|
export interface AnimatedSidebarInsetProps extends HTMLMotionProps<"main"> {}
|
|
732
706
|
|
|
@@ -740,8 +714,9 @@ export const AnimatedSidebarInset = forwardRef<
|
|
|
740
714
|
ref={forwardedRef}
|
|
741
715
|
data-slot="sidebar-inset"
|
|
742
716
|
className={cn(
|
|
743
|
-
"relative flex min-w-0 flex-1 flex-col bg-
|
|
744
|
-
"
|
|
717
|
+
"relative flex min-w-0 flex-1 flex-col bg-neutral-50 dark:bg-neutral-900",
|
|
718
|
+
"outline-offset-0.5 overflow-hidden border-t-3 border-white outline-neutral-100 dark:border-neutral-800 dark:inset-ring-black",
|
|
719
|
+
"m-2 mx-0 rounded-lg shadow-md shadow-neutral-200 dark:shadow-neutral-950",
|
|
745
720
|
className
|
|
746
721
|
)}
|
|
747
722
|
/>
|
|
@@ -46,10 +46,10 @@ export const buttonVariants = cva(
|
|
|
46
46
|
size: {
|
|
47
47
|
default: "h-9 px-[calc(--spacing(3)-1px)] sm:h-8",
|
|
48
48
|
icon: "size-9 sm:size-8",
|
|
49
|
-
"icon-lg": "size-
|
|
50
|
-
"icon-sm": "size-
|
|
49
|
+
"icon-lg": "size-8 sm:size-8",
|
|
50
|
+
"icon-sm": "size-7 sm:size-6",
|
|
51
51
|
"icon-xl":
|
|
52
|
-
"size-
|
|
52
|
+
"size-10 sm:size-9 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
|
|
53
53
|
"icon-xs":
|
|
54
54
|
"size-7 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5",
|
|
55
55
|
lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9",
|