@exegia/corpora-ui 0.16.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exegia/corpora-ui",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "shadcn-ready React UI library for the corpora manuscript-research apps.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -92,12 +92,12 @@ describe("ShellLayout", () => {
92
92
  expect(rightDrawer().getAttribute("data-state")).toBe("expanded")
93
93
  })
94
94
 
95
- test("a panel's own open flag seeds its side's initial state", () => {
95
+ test("a panel's own defaultOpen flag seeds its side's initial state", () => {
96
96
  render(
97
97
  <ShellLayout
98
98
  panels={{
99
99
  ...PANELS,
100
- right: { ...PANELS.right!, open: true },
100
+ right: { ...PANELS.right!, defaultOpen: true },
101
101
  }}
102
102
  variant="web"
103
103
  />
@@ -112,7 +112,7 @@ describe("ShellLayout", () => {
112
112
  defaultOpen={{ left: false, right: false }}
113
113
  panels={{
114
114
  ...PANELS,
115
- right: { ...PANELS.right!, open: true },
115
+ right: { ...PANELS.right!, defaultOpen: true },
116
116
  }}
117
117
  variant="web"
118
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
- ...(panels?.left && {
33
- left: panels.left.defaultOpen ?? panels.left.open,
34
- }),
35
- ...(panels?.right && {
36
- right: panels.right.defaultOpen ?? panels.right.open,
37
- }),
38
- ...(defaultOpen)
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("block-full", className, background, "relative pr-2")}
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,24 +62,20 @@ export function ShellLayout({
56
62
  {panels?.left?.component}
57
63
  </AnimatedSidebar>
58
64
 
59
- <AnimatedSidebarInset
60
- className={cn(
61
- "min-w-24 overflow-hidden border-t border-neutral-200 dark:border-neutral-700 inset-ring-1 inset-ring-neutral-50 dark:inset-ring-black bg-white dark:bg-card rounded-lg!",
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 ?? (
69
70
  <MotionIcon name="PanelLeft" size={24} animation="press" />
70
71
  )}
71
72
  </AnimatedSidebarTrigger>
72
73
  </div>
73
- <div className="flex shrink-0 items-center gap-3">
74
+ {header && <div className="flex flex-1 items-center">{header}</div>}
75
+ <div className="flex shrink-0 items-center gap-2">
74
76
  <AnimatedSidebarTrigger aria-label="Toggle panel" side="right">
75
77
  {panels?.right?.trigger ?? (
76
- <MotionIcon name="PanelRight" size={24} animation="press" />
78
+ <MotionIcon className="opacity-70" name="PanelRight" size={24} />
77
79
  )}
78
80
  </AnimatedSidebarTrigger>
79
81
  </div>
@@ -87,16 +89,10 @@ export function ShellLayout({
87
89
  ariaLabel={panels?.right?.name ?? "Secondary panel"}
88
90
  // Below md the panel is portal led over the page, so it carries the
89
91
  // surface itself; the desktop rail keeps it on the inner panel.
90
- className={cn("max-md:border-l max-md:border-neutral-200 max-md:bg-white dark:max-md:border-neutral-700 dark:max-md:bg-card")}
91
- collapsible="offcanvas"
92
- panelClassName={cn(
93
- // The off canvas panel keeps a fixed width so it can slide out of the
94
- // zero-width rail, so the inset gutter has to come out of that width
95
- // rather than from a left margin.
96
- "top-2 ml-0 h-[calc(100%-1rem)] min-w-[calc(var(--sidebar-width))]",
97
- "rounded-lg border-t border-neutral-200 bg-white shadow-md inset-ring-1 inset-ring-neutral-50",
98
- "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"
99
94
  )}
95
+ collapsible="offcanvas"
100
96
  role="complementary"
101
97
  side="right"
102
98
  variant="inset"
@@ -109,16 +105,8 @@ export function ShellLayout({
109
105
 
110
106
  function ShellLayoutPlaceholder() {
111
107
  return (
112
- <div className="grid gap-5">
113
- <div className="grid gap-5 lg:grid-cols-3">
114
- {Array.from({ length: 3 }).map((_, index) => (
115
- <div
116
- className="h-32 border border-border bg-background/60"
117
- key={index}
118
- />
119
- ))}
120
- </div>
121
- <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" />
122
110
  </div>
123
111
  )
124
112
  }
@@ -91,6 +91,7 @@ export interface ShellLayoutProps extends ShellPanelControlProps {
91
91
  /** Content for the shell's panels, keyed by the side. The shell renders
92
92
  * the `left` rail and the `right` drawer; other sides are reserved. */
93
93
  panels?: TPanelMap
94
+ header?: ReactNode
94
95
  className?: string
95
96
  variant?: "web" | "desktop"
96
97
  }
@@ -29,6 +29,7 @@ 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
35
  /** Open flags keyed by the side. Sides left out stay uncontrolled / at their
@@ -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 p-0.5 flex flex-1 bg-radial/decreasing from-amber-400 to-0% hover:to-50% ease-smooth-out to-amber-500/0 cursor-col-resize transition-colors duration-1000 animate-in" />
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",
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",
597
603
  collapsible === "offcanvas" && "w-(--sidebar-width)",
598
604
 
599
605
  variant === "floating" && "m-2 h-[calc(100svh-1rem)]",
600
- variant === "inset" && "m-2 h-[calc(100svh-1rem)]",
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
- { className, onClick, side = "left", type = "button", ...props },
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
- <button
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-background",
744
- "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:mr-1.5 md:peer-data-[variant=inset]:rounded-md md:peer-data-[variant=inset]:shadow-md",
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-10 sm:size-9",
50
- "icon-sm": "size-8 sm:size-7",
49
+ "icon-lg": "size-8 sm:size-8",
50
+ "icon-sm": "size-7 sm:size-6",
51
51
  "icon-xl":
52
- "size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
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",