@stasho/ds 0.12.0 → 0.13.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": "@stasho/ds",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
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
+ };
@@ -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
  }