@stasho/ds 0.12.0 → 0.13.1

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": "@stasho/ds",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
4
4
  "description": "stasho design system",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -48,6 +48,7 @@
48
48
  "./pagination": "./src/components/pagination/pagination.tsx",
49
49
  "./copyable-text": "./src/components/copyable-text/copyable-text.tsx",
50
50
  "./dialog": "./src/components/dialog/dialog.tsx",
51
+ "./drawer": "./src/components/drawer/drawer.tsx",
51
52
  "./logo": "./src/components/logo/logo.tsx",
52
53
  "./progress-bar": "./src/components/progress-bar/progress-bar.tsx",
53
54
  "./stepper": "./src/components/stepper/stepper.tsx",
@@ -0,0 +1,127 @@
1
+ "use client";
2
+
3
+ import {
4
+ forwardRef,
5
+ type ComponentPropsWithoutRef,
6
+ } from "react";
7
+ import { Dialog as DialogPrimitive } from "radix-ui";
8
+ import { X } from "@phosphor-icons/react";
9
+ import { cn } from "@ac/lib/cn";
10
+
11
+ /* ── Direct re-exports ─────────────────────────── */
12
+
13
+ const Drawer = DialogPrimitive.Root;
14
+ const DrawerTrigger = DialogPrimitive.Trigger;
15
+ const DrawerClose = DialogPrimitive.Close;
16
+
17
+ /* ── DrawerContent (Portal + Overlay + Content) ── */
18
+
19
+ type DrawerSide = "bottom" | "left" | "right";
20
+
21
+ const sideClasses: Record<DrawerSide, string> = {
22
+ bottom: cn(
23
+ "inset-x-0 bottom-0 max-h-[85dvh] w-full rounded-t-xl border-t",
24
+ "motion-safe:data-[state=open]:animate-drawer-in-bottom",
25
+ "motion-safe:data-[state=closed]:animate-drawer-out-bottom",
26
+ ),
27
+ left: cn(
28
+ "inset-y-0 left-0 h-dvh w-80 max-w-[85vw] border-r",
29
+ "motion-safe:data-[state=open]:animate-drawer-in-left",
30
+ "motion-safe:data-[state=closed]:animate-drawer-out-left",
31
+ ),
32
+ right: cn(
33
+ "inset-y-0 right-0 h-dvh w-80 max-w-[85vw] border-l",
34
+ "motion-safe:data-[state=open]:animate-drawer-in-right",
35
+ "motion-safe:data-[state=closed]:animate-drawer-out-right",
36
+ ),
37
+ };
38
+
39
+ type DrawerContentProps = ComponentPropsWithoutRef<
40
+ typeof DialogPrimitive.Content
41
+ > & {
42
+ side?: DrawerSide;
43
+ };
44
+
45
+ const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
46
+ ({ className, children, side = "bottom", ...rest }, ref) => (
47
+ <DialogPrimitive.Portal>
48
+ <DialogPrimitive.Overlay
49
+ className={cn(
50
+ "fixed inset-0 z-50 bg-black/60 backdrop-blur-sm",
51
+ "data-[state=open]:animate-in data-[state=open]:fade-in-0",
52
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
53
+ "motion-reduce:animate-none",
54
+ )}
55
+ />
56
+ <DialogPrimitive.Content
57
+ ref={ref}
58
+ className={cn(
59
+ "fixed z-50 border-edge bg-surface p-6",
60
+ "overflow-y-auto overscroll-contain",
61
+ sideClasses[side],
62
+ className,
63
+ )}
64
+ {...rest}
65
+ >
66
+ {children}
67
+ <DialogPrimitive.Close
68
+ className={cn(
69
+ "absolute top-4 right-4 rounded-sm",
70
+ "text-muted-foreground transition-colors hover:text-foreground",
71
+ "focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2",
72
+ )}
73
+ aria-label="Close"
74
+ >
75
+ <X weight="bold" className="size-4" />
76
+ </DialogPrimitive.Close>
77
+ </DialogPrimitive.Content>
78
+ </DialogPrimitive.Portal>
79
+ ),
80
+ );
81
+
82
+ DrawerContent.displayName = "DrawerContent";
83
+
84
+ /* ── DrawerTitle ───────────────────────────────── */
85
+
86
+ const DrawerTitle = forwardRef<
87
+ HTMLHeadingElement,
88
+ ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
89
+ >(({ className, ...rest }, ref) => (
90
+ <DialogPrimitive.Title
91
+ ref={ref}
92
+ className={cn(
93
+ "font-heading font-bold text-lg text-foreground",
94
+ className,
95
+ )}
96
+ {...rest}
97
+ />
98
+ ));
99
+
100
+ DrawerTitle.displayName = "DrawerTitle";
101
+
102
+ /* ── DrawerDescription ─────────────────────────── */
103
+
104
+ const DrawerDescription = forwardRef<
105
+ HTMLParagraphElement,
106
+ ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
107
+ >(({ className, ...rest }, ref) => (
108
+ <DialogPrimitive.Description
109
+ ref={ref}
110
+ className={cn("text-sm text-muted-foreground", className)}
111
+ {...rest}
112
+ />
113
+ ));
114
+
115
+ DrawerDescription.displayName = "DrawerDescription";
116
+
117
+ /* ── Exports ───────────────────────────────────── */
118
+
119
+ export {
120
+ Drawer,
121
+ DrawerClose,
122
+ DrawerContent,
123
+ DrawerDescription,
124
+ DrawerTitle,
125
+ DrawerTrigger,
126
+ type DrawerContentProps,
127
+ };
@@ -193,7 +193,9 @@ type OverflowTriggerProps = {
193
193
 
194
194
  const OverflowTrigger = forwardRef<HTMLButtonElement, OverflowTriggerProps>(
195
195
  ({ isPill, isSmall, hiddenTabs, hasActiveHidden, visible }, ref) => (
196
- <DropdownMenu.Root>
196
+ // Non-modal: Radix's modal scroll-lock pads <body> for the missing
197
+ // scrollbar, visibly shifting/squeezing the page on mobile viewports.
198
+ <DropdownMenu.Root modal={false}>
197
199
  <DropdownMenu.Trigger asChild>
198
200
  <button
199
201
  ref={ref}
@@ -398,4 +398,49 @@
398
398
  @keyframes accordion-up {
399
399
  from { height: var(--radix-accordion-content-height); }
400
400
  to { height: 0; }
401
+ }
402
+
403
+ /* ── Drawer slide in/out (per side) ────────────── */
404
+
405
+ /* Same mechanism as the Accordion utilities above: registered in @theme so
406
+ the `data-[state=...]:` variant composes, and applied in the component as
407
+ `motion-safe:data-[state=…]:animate-drawer-*` so reduced motion is honored
408
+ by gating the animation INTO motion-safe. */
409
+ @theme {
410
+ --animate-drawer-in-bottom: drawer-in-bottom 300ms ease-out;
411
+ --animate-drawer-out-bottom: drawer-out-bottom 300ms ease-in;
412
+ --animate-drawer-in-left: drawer-in-left 300ms ease-out;
413
+ --animate-drawer-out-left: drawer-out-left 300ms ease-in;
414
+ --animate-drawer-in-right: drawer-in-right 300ms ease-out;
415
+ --animate-drawer-out-right: drawer-out-right 300ms ease-in;
416
+ }
417
+
418
+ @keyframes drawer-in-bottom {
419
+ from { transform: translateY(100%); }
420
+ to { transform: translateY(0); }
421
+ }
422
+
423
+ @keyframes drawer-out-bottom {
424
+ from { transform: translateY(0); }
425
+ to { transform: translateY(100%); }
426
+ }
427
+
428
+ @keyframes drawer-in-left {
429
+ from { transform: translateX(-100%); }
430
+ to { transform: translateX(0); }
431
+ }
432
+
433
+ @keyframes drawer-out-left {
434
+ from { transform: translateX(0); }
435
+ to { transform: translateX(-100%); }
436
+ }
437
+
438
+ @keyframes drawer-in-right {
439
+ from { transform: translateX(100%); }
440
+ to { transform: translateX(0); }
441
+ }
442
+
443
+ @keyframes drawer-out-right {
444
+ from { transform: translateX(0); }
445
+ to { transform: translateX(100%); }
401
446
  }