@exegia/corpora-ui 0.14.0 → 0.15.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/__tests__/shell-layout.test.d.ts +1 -0
- package/dist-lib/components/blocks/shell/index.d.ts +1 -0
- package/dist-lib/components/blocks/shell/shell-layout.d.ts +1 -1
- package/dist-lib/components/blocks/shell/type.d.ts +58 -16
- package/dist-lib/components/blocks/shell/use-shell-panels.d.ts +9 -0
- package/dist-lib/components/motion/animated-sidebar.d.ts +25 -25
- package/dist-lib/index.js +970 -889
- package/dist-lib/index.js.map +1 -1
- package/package.json +3 -2
- package/src/components/blocks/nav/sidebar-block.tsx +15 -7
- package/src/components/blocks/shell/__tests__/shell-layout.test.tsx +178 -0
- package/src/components/blocks/shell/index.ts +1 -0
- package/src/components/blocks/shell/shell-layout.tsx +29 -24
- package/src/components/blocks/shell/type.ts +78 -16
- package/src/components/blocks/shell/use-shell-panels.ts +78 -0
- package/src/components/motion/animated-sidebar.tsx +113 -90
- package/src/index.css +2 -1
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
useContext,
|
|
21
21
|
useEffect,
|
|
22
22
|
useId,
|
|
23
|
+
useMemo,
|
|
23
24
|
useRef,
|
|
24
25
|
useState,
|
|
25
26
|
useSyncExternalStore,
|
|
@@ -29,8 +30,10 @@ import { SharedLayoutBg } from "@/components/motion/shared-layout-bg"
|
|
|
29
30
|
import { EASE_DRAWER, EASE_OUT, SPRING_LAYOUT, SPRING_PRESS } from "@/lib/ease"
|
|
30
31
|
import { cn } from "@/lib/utils"
|
|
31
32
|
|
|
32
|
-
type
|
|
33
|
-
|
|
33
|
+
export type SidebarSide = "left" | "right"
|
|
34
|
+
/** Open flags keyed by side. Sides left out stay uncontrolled / at their
|
|
35
|
+
* default — there is no per-side prop, the record IS the API. */
|
|
36
|
+
export type SidebarOpenState = Partial<Record<SidebarSide, boolean>>
|
|
34
37
|
type SidebarVariant = "sidebar" | "floating" | "inset"
|
|
35
38
|
type SidebarCollapsible = "offcanvas" | "icon" | "none"
|
|
36
39
|
|
|
@@ -144,20 +147,16 @@ function useIsMobile() {
|
|
|
144
147
|
interface AnimatedSidebarContextValue {
|
|
145
148
|
isMobile: boolean
|
|
146
149
|
layoutId: string
|
|
147
|
-
open
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
|
|
151
|
-
openRight: boolean
|
|
150
|
+
/** Desktop open state, keyed by side. */
|
|
151
|
+
open: Record<SidebarSide, boolean>
|
|
152
|
+
/** Mobile overlay open state, keyed by side. */
|
|
153
|
+
openMobile: Record<SidebarSide, boolean>
|
|
152
154
|
reduce: boolean
|
|
153
|
-
setOpen: (open: boolean) => void
|
|
154
|
-
setOpenMobile: (open: boolean) => void
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
toggleRightSidebar: () => void
|
|
159
|
-
triggerRef: React.RefObject<HTMLButtonElement | null>
|
|
160
|
-
rightTriggerRef: React.RefObject<HTMLButtonElement | null>
|
|
155
|
+
setOpen: (open: boolean, side: SidebarSide) => void
|
|
156
|
+
setOpenMobile: (open: boolean, side: SidebarSide) => void
|
|
157
|
+
/** Mobile-aware: toggles the overlay below md, the docked panel above. */
|
|
158
|
+
toggleSidebar: (side: SidebarSide) => void
|
|
159
|
+
triggerRefs: Record<SidebarSide, React.RefObject<HTMLButtonElement | null>>
|
|
161
160
|
}
|
|
162
161
|
|
|
163
162
|
const AnimatedSidebarContext =
|
|
@@ -199,78 +198,111 @@ type SidebarProviderStyle = CSSProperties & {
|
|
|
199
198
|
}
|
|
200
199
|
|
|
201
200
|
export interface AnimatedSidebarProviderProps extends HTMLAttributes<HTMLDivElement> {
|
|
202
|
-
open
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
201
|
+
/** Controlled desktop open state, keyed by side. A side left undefined
|
|
202
|
+
* stays uncontrolled. */
|
|
203
|
+
open?: SidebarOpenState
|
|
204
|
+
/** Initial desktop open state, merged over `{ left: true, right: false }`. */
|
|
205
|
+
defaultOpen?: SidebarOpenState
|
|
206
|
+
onOpenChange?: (open: boolean, side: SidebarSide) => void
|
|
207
|
+
/** Controlled mobile overlay state, keyed by side. */
|
|
208
|
+
openMobile?: SidebarOpenState
|
|
209
|
+
/** Initial mobile overlay state — every side starts closed. */
|
|
210
|
+
defaultOpenMobile?: SidebarOpenState
|
|
211
|
+
onOpenMobileChange?: (open: boolean, side: SidebarSide) => void
|
|
211
212
|
style?: SidebarProviderStyle
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
export function AnimatedSidebarProvider({
|
|
215
216
|
children,
|
|
216
217
|
open,
|
|
217
|
-
defaultOpen
|
|
218
|
+
defaultOpen,
|
|
218
219
|
onOpenChange,
|
|
219
220
|
openMobile,
|
|
220
|
-
defaultOpenMobile
|
|
221
|
+
defaultOpenMobile,
|
|
221
222
|
onOpenMobileChange,
|
|
222
|
-
openRight,
|
|
223
|
-
defaultOpenRight = false,
|
|
224
|
-
onOpenRightChange,
|
|
225
223
|
className,
|
|
226
224
|
style,
|
|
227
225
|
...props
|
|
228
226
|
}: AnimatedSidebarProviderProps) {
|
|
229
|
-
const [internalOpen, setInternalOpen] = useState
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
227
|
+
const [internalOpen, setInternalOpen] = useState<
|
|
228
|
+
Record<SidebarSide, boolean>
|
|
229
|
+
>(() => ({
|
|
230
|
+
left: defaultOpen?.left ?? true,
|
|
231
|
+
right: defaultOpen?.right ?? false,
|
|
232
|
+
}))
|
|
233
|
+
const [internalOpenMobile, setInternalOpenMobile] = useState<
|
|
234
|
+
Record<SidebarSide, boolean>
|
|
235
|
+
>(() => ({
|
|
236
|
+
left: defaultOpenMobile?.left ?? false,
|
|
237
|
+
right: defaultOpenMobile?.right ?? false,
|
|
238
|
+
}))
|
|
233
239
|
const isMobile = useIsMobile()
|
|
234
240
|
const reduce = useReducedMotion() ?? false
|
|
235
241
|
const generatedId = useId()
|
|
236
|
-
const
|
|
242
|
+
const leftTriggerRef = useRef<HTMLButtonElement>(null)
|
|
237
243
|
const rightTriggerRef = useRef<HTMLButtonElement>(null)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
244
|
+
|
|
245
|
+
// Per-side controlled/uncontrolled merge — a side is controlled exactly
|
|
246
|
+
// when its key is present on the controlled record.
|
|
247
|
+
const controlledLeft = open?.left
|
|
248
|
+
const controlledRight = open?.right
|
|
249
|
+
const controlledMobileLeft = openMobile?.left
|
|
250
|
+
const controlledMobileRight = openMobile?.right
|
|
251
|
+
|
|
252
|
+
const openState = useMemo<Record<SidebarSide, boolean>>(
|
|
253
|
+
() => ({
|
|
254
|
+
left: controlledLeft ?? internalOpen.left,
|
|
255
|
+
right: controlledRight ?? internalOpen.right,
|
|
256
|
+
}),
|
|
257
|
+
[controlledLeft, controlledRight, internalOpen]
|
|
258
|
+
)
|
|
259
|
+
const openMobileState = useMemo<Record<SidebarSide, boolean>>(
|
|
260
|
+
() => ({
|
|
261
|
+
left: controlledMobileLeft ?? internalOpenMobile.left,
|
|
262
|
+
right: controlledMobileRight ?? internalOpenMobile.right,
|
|
263
|
+
}),
|
|
264
|
+
[controlledMobileLeft, controlledMobileRight, internalOpenMobile]
|
|
265
|
+
)
|
|
241
266
|
|
|
242
267
|
const setOpen = useCallback(
|
|
243
|
-
(nextOpen: boolean) => {
|
|
244
|
-
|
|
245
|
-
|
|
268
|
+
(nextOpen: boolean, side: SidebarSide) => {
|
|
269
|
+
const controlled = side === "left" ? controlledLeft : controlledRight
|
|
270
|
+
if (controlled === undefined) {
|
|
271
|
+
setInternalOpen((prev) =>
|
|
272
|
+
prev[side] === nextOpen ? prev : { ...prev, [side]: nextOpen }
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
onOpenChange?.(nextOpen, side)
|
|
246
276
|
},
|
|
247
|
-
[
|
|
277
|
+
[controlledLeft, controlledRight, onOpenChange]
|
|
248
278
|
)
|
|
249
279
|
|
|
250
280
|
const setOpenMobile = useCallback(
|
|
251
|
-
(nextOpen: boolean) => {
|
|
252
|
-
|
|
253
|
-
|
|
281
|
+
(nextOpen: boolean, side: SidebarSide) => {
|
|
282
|
+
const controlled =
|
|
283
|
+
side === "left" ? controlledMobileLeft : controlledMobileRight
|
|
284
|
+
if (controlled === undefined) {
|
|
285
|
+
setInternalOpenMobile((prev) =>
|
|
286
|
+
prev[side] === nextOpen ? prev : { ...prev, [side]: nextOpen }
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
onOpenMobileChange?.(nextOpen, side)
|
|
254
290
|
},
|
|
255
|
-
[
|
|
291
|
+
[controlledMobileLeft, controlledMobileRight, onOpenMobileChange]
|
|
256
292
|
)
|
|
257
293
|
|
|
258
|
-
const
|
|
259
|
-
(
|
|
260
|
-
if (
|
|
261
|
-
|
|
294
|
+
const toggleSidebar = useCallback(
|
|
295
|
+
(side: SidebarSide) => {
|
|
296
|
+
if (isMobile) setOpenMobile(!openMobileState[side], side)
|
|
297
|
+
else setOpen(!openState[side], side)
|
|
262
298
|
},
|
|
263
|
-
[
|
|
299
|
+
[isMobile, openMobileState, openState, setOpen, setOpenMobile]
|
|
264
300
|
)
|
|
265
301
|
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const toggleRightSidebar = useCallback(() => {
|
|
272
|
-
setOpenRight(!rightOpen)
|
|
273
|
-
}, [rightOpen, setOpenRight])
|
|
302
|
+
const triggerRefs = useMemo(
|
|
303
|
+
() => ({ left: leftTriggerRef, right: rightTriggerRef }),
|
|
304
|
+
[]
|
|
305
|
+
)
|
|
274
306
|
|
|
275
307
|
useEffect(() => {
|
|
276
308
|
const handleShortcut = (event: KeyboardEvent) => {
|
|
@@ -279,7 +311,7 @@ export function AnimatedSidebarProvider({
|
|
|
279
311
|
(event.metaKey || event.ctrlKey)
|
|
280
312
|
) {
|
|
281
313
|
event.preventDefault()
|
|
282
|
-
toggleSidebar()
|
|
314
|
+
toggleSidebar("left")
|
|
283
315
|
}
|
|
284
316
|
}
|
|
285
317
|
|
|
@@ -292,25 +324,20 @@ export function AnimatedSidebarProvider({
|
|
|
292
324
|
value={{
|
|
293
325
|
isMobile,
|
|
294
326
|
layoutId: `${generatedId}-active`,
|
|
295
|
-
open:
|
|
296
|
-
openMobile:
|
|
297
|
-
openRight: rightOpen,
|
|
327
|
+
open: openState,
|
|
328
|
+
openMobile: openMobileState,
|
|
298
329
|
reduce,
|
|
299
330
|
setOpen,
|
|
300
331
|
setOpenMobile,
|
|
301
|
-
setOpenRight,
|
|
302
|
-
state: desktopOpen ? "expanded" : "collapsed",
|
|
303
332
|
toggleSidebar,
|
|
304
|
-
|
|
305
|
-
triggerRef,
|
|
306
|
-
rightTriggerRef,
|
|
333
|
+
triggerRefs,
|
|
307
334
|
}}
|
|
308
335
|
>
|
|
309
336
|
<div
|
|
310
337
|
{...props}
|
|
311
338
|
data-slot="sidebar-wrapper"
|
|
312
|
-
data-state={
|
|
313
|
-
data-right
|
|
339
|
+
data-state-left={openState.left ? "expanded" : "collapsed"}
|
|
340
|
+
data-state-right={openState.right ? "expanded" : "collapsed"}
|
|
314
341
|
style={{
|
|
315
342
|
"--sidebar-width": "19rem",
|
|
316
343
|
"--sidebar-width-icon": "4.25rem",
|
|
@@ -340,10 +367,9 @@ function MobileSidebar({
|
|
|
340
367
|
side: SidebarSide
|
|
341
368
|
}) {
|
|
342
369
|
const context = useAnimatedSidebar()
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
const
|
|
346
|
-
const triggerRef = isRight ? context.rightTriggerRef : context.triggerRef
|
|
370
|
+
const open = context.openMobile[side]
|
|
371
|
+
const setOpen = (nextOpen: boolean) => context.setOpenMobile(nextOpen, side)
|
|
372
|
+
const triggerRef = context.triggerRefs[side]
|
|
347
373
|
const panelRef = useRef<HTMLDivElement>(null)
|
|
348
374
|
const [mounted, setMounted] = useState(false)
|
|
349
375
|
|
|
@@ -512,7 +538,7 @@ export const AnimatedSidebar = forwardRef<HTMLElement, AnimatedSidebarProps>(
|
|
|
512
538
|
forwardedRef
|
|
513
539
|
) {
|
|
514
540
|
const context = useAnimatedSidebar()
|
|
515
|
-
const sideOpen =
|
|
541
|
+
const sideOpen = context.open[side]
|
|
516
542
|
const collapsed = collapsible !== "none" && !sideOpen
|
|
517
543
|
const offcanvas = collapsed && collapsible === "offcanvas"
|
|
518
544
|
const width = offcanvas
|
|
@@ -598,13 +624,10 @@ export const AnimatedSidebarTrigger = forwardRef<
|
|
|
598
624
|
forwardedRef
|
|
599
625
|
) {
|
|
600
626
|
const context = useAnimatedSidebar()
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
? context.openMobile
|
|
606
|
-
: context.open
|
|
607
|
-
const triggerRef = isRight ? context.rightTriggerRef : context.triggerRef
|
|
627
|
+
const expanded = context.isMobile
|
|
628
|
+
? context.openMobile[side]
|
|
629
|
+
: context.open[side]
|
|
630
|
+
const triggerRef = context.triggerRefs[side]
|
|
608
631
|
|
|
609
632
|
return (
|
|
610
633
|
<button
|
|
@@ -623,8 +646,7 @@ export const AnimatedSidebarTrigger = forwardRef<
|
|
|
623
646
|
onClick={(event) => {
|
|
624
647
|
onClick?.(event)
|
|
625
648
|
if (event.defaultPrevented) return
|
|
626
|
-
|
|
627
|
-
else context.toggleSidebar()
|
|
649
|
+
context.toggleSidebar(side)
|
|
628
650
|
}}
|
|
629
651
|
className={cn(
|
|
630
652
|
"inline-flex size-10 shrink-0 items-center justify-center rounded-xl outline-none",
|
|
@@ -658,9 +680,8 @@ export const AnimatedSidebarClose = forwardRef<
|
|
|
658
680
|
onClick={(event) => {
|
|
659
681
|
onClick?.(event)
|
|
660
682
|
if (event.defaultPrevented) return
|
|
661
|
-
if (
|
|
662
|
-
else
|
|
663
|
-
else context.setOpen(false)
|
|
683
|
+
if (context.isMobile) context.setOpenMobile(false, side)
|
|
684
|
+
else context.setOpen(false, side)
|
|
664
685
|
}}
|
|
665
686
|
className={cn(
|
|
666
687
|
"inline-flex size-10 shrink-0 items-center justify-center rounded-xl outline-none",
|
|
@@ -695,8 +716,7 @@ export const AnimatedSidebarRail = forwardRef<
|
|
|
695
716
|
onClick={(event) => {
|
|
696
717
|
onClick?.(event)
|
|
697
718
|
if (event.defaultPrevented) return
|
|
698
|
-
|
|
699
|
-
else context.toggleSidebar()
|
|
719
|
+
context.toggleSidebar(panel.side)
|
|
700
720
|
}}
|
|
701
721
|
className={cn(
|
|
702
722
|
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 outline-none md:block",
|
|
@@ -956,6 +976,7 @@ export function AnimatedSidebarMenuSubButton({
|
|
|
956
976
|
className,
|
|
957
977
|
}: AnimatedSidebarMenuSubButtonProps) {
|
|
958
978
|
const context = useAnimatedSidebar()
|
|
979
|
+
const panel = useAnimatedSidebarPanel()
|
|
959
980
|
|
|
960
981
|
const select = (
|
|
961
982
|
event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>
|
|
@@ -965,7 +986,9 @@ export function AnimatedSidebarMenuSubButton({
|
|
|
965
986
|
return
|
|
966
987
|
}
|
|
967
988
|
onSelect?.()
|
|
968
|
-
if (context.isMobile && closeOnSelect)
|
|
989
|
+
if (context.isMobile && closeOnSelect) {
|
|
990
|
+
context.setOpenMobile(false, panel.side)
|
|
991
|
+
}
|
|
969
992
|
}
|
|
970
993
|
|
|
971
994
|
const content = (
|
|
@@ -1062,7 +1085,7 @@ export function AnimatedSidebarMenuButton({
|
|
|
1062
1085
|
onSelect?.()
|
|
1063
1086
|
const shouldCloseOnSelect = closeOnSelect ?? ariaExpanded === undefined
|
|
1064
1087
|
if (context.isMobile && shouldCloseOnSelect) {
|
|
1065
|
-
context.setOpenMobile(false)
|
|
1088
|
+
context.setOpenMobile(false, panel.side)
|
|
1066
1089
|
}
|
|
1067
1090
|
}
|
|
1068
1091
|
|
package/src/index.css
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
@import "shadcn/tailwind.css";
|
|
4
4
|
@import "@fontsource-variable/inter";
|
|
5
5
|
@import "@fontsource-variable/figtree";
|
|
6
|
+
@import "motion-icons-react/style.css";
|
|
6
7
|
|
|
7
8
|
@custom-variant dark (&:is(.dark *));
|
|
8
9
|
|
|
@@ -190,4 +191,4 @@
|
|
|
190
191
|
html {
|
|
191
192
|
@apply font-sans;
|
|
192
193
|
}
|
|
193
|
-
}
|
|
194
|
+
}
|