@stasho/ds 0.1.1 → 0.3.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.
@@ -6,6 +6,7 @@ import {
6
6
  useContext,
7
7
  type HTMLAttributes,
8
8
  } from "react";
9
+ import { Check } from "@phosphor-icons/react";
9
10
  import { cn } from "@ac/lib/cn";
10
11
 
11
12
  /* ── Stepper context (orientation) ─────────────── */
@@ -114,23 +115,22 @@ const StepperIndicator = forwardRef<HTMLDivElement, StepperIndicatorProps>(
114
115
  ref={ref}
115
116
  data-state={state}
116
117
  className={cn(
117
- "relative flex size-8 items-center justify-center rounded-full",
118
- "font-heading text-sm font-bold",
119
- "border-2 border-edge text-muted-foreground",
120
- "data-[state=active]:border-primary-500 data-[state=active]:bg-primary-500 data-[state=active]:text-white",
121
- "data-[state=completed]:border-primary-500 data-[state=completed]:bg-primary-500 data-[state=completed]:text-white",
122
- "transition-all duration-300 motion-reduce:transition-colors",
118
+ "relative flex size-8 items-center justify-center rounded-[2px]",
119
+ "font-sans text-sm font-semibold",
120
+ "border border-edge text-foreground/45 bg-transparent",
121
+ "data-[state=active]:border-accent data-[state=active]:text-accent-500 dark:data-[state=active]:text-accent",
122
+ "data-[state=active]:shadow-[0_0_6px_rgba(0,225,250,0.5),0_0_14px_rgba(0,225,250,0.3)]",
123
+ "data-[state=completed]:border-accent data-[state=completed]:bg-accent data-[state=completed]:text-neutral-950",
124
+ "transition-all duration-200 motion-reduce:transition-colors",
123
125
  className,
124
126
  )}
125
127
  {...rest}
126
128
  >
127
- {state === "active" && (
128
- <>
129
- <span className="absolute -inset-1 rounded-full border-2 border-primary-400/35 animate-[ring-wave_2.4s_ease-in-out_infinite] motion-reduce:animate-none" />
130
- <span className="absolute -inset-1.5 rounded-full border border-primary-300/25 animate-[ring-wave_2.4s_ease-in-out_-1.2s_infinite] motion-reduce:animate-none" />
131
- </>
129
+ {state === "completed" ? (
130
+ <Check weight="bold" className="size-4" aria-hidden="true" />
131
+ ) : (
132
+ children
132
133
  )}
133
- {children}
134
134
  </div>
135
135
  );
136
136
  },
@@ -186,19 +186,23 @@ StepperDescription.displayName = "StepperDescription";
186
186
 
187
187
  /* ── StepperConnector (li, visual line) ────────── */
188
188
 
189
- type StepperConnectorProps = HTMLAttributes<HTMLLIElement>;
189
+ type StepperConnectorProps = HTMLAttributes<HTMLLIElement> & {
190
+ /** Mark the connector as filled (cyan) — used between two consecutive completed steps. */
191
+ completed?: boolean;
192
+ };
190
193
 
191
194
  const StepperConnector = forwardRef<HTMLLIElement, StepperConnectorProps>(
192
- ({ className, ...rest }, ref) => {
195
+ ({ className, completed, ...rest }, ref) => {
193
196
  const { orientation } = useStepperContext();
194
197
  return (
195
198
  <li
196
199
  ref={ref}
197
200
  aria-hidden="true"
198
201
  data-orientation={orientation}
202
+ data-completed={completed ? "" : undefined}
199
203
  className={cn(
200
- "relative overflow-hidden rounded-full bg-edge/50 flex-1",
201
- orientation === "horizontal" ? "h-1" : "w-1",
204
+ "relative overflow-hidden flex-1 bg-edge data-[completed]:bg-accent transition-colors",
205
+ orientation === "horizontal" ? "h-px" : "w-px",
202
206
  className,
203
207
  )}
204
208
  {...rest}
@@ -6,21 +6,23 @@ import { cn } from "@ac/lib/cn";
6
6
  const switchVariants = cva(
7
7
  [
8
8
  "peer inline-flex shrink-0 cursor-pointer",
9
- "items-center rounded-full",
10
- "border-3 border-edge bg-muted",
11
- "hover:border-edge-hover",
12
- "focus-visible:outline-none focus-visible:ring-3",
13
- "focus-visible:ring-primary-500",
14
- "disabled:opacity-50 disabled:pointer-events-none",
15
- "data-[state=checked]:bg-primary data-[state=checked]:border-primary",
9
+ "items-center rounded-[2px]",
10
+ "border border-edge bg-muted dark:bg-neutral-900",
11
+ "shadow-[inset_0_1px_0_rgba(255,255,255,0.06),inset_0_-1px_0_rgba(0,0,0,0.4)]",
12
+ "focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2",
13
+ "disabled:bg-muted dark:disabled:bg-background",
14
+ "disabled:border-edge/50 disabled:shadow-none",
15
+ "disabled:cursor-not-allowed",
16
+ "disabled:data-[state=checked]:border-edge/50",
17
+ "data-[state=checked]:border-accent/30",
16
18
  "transition-colors",
17
19
  ].join(" "),
18
20
  {
19
21
  variants: {
20
22
  size: {
21
- xs: "h-5 w-9",
22
- sm: "h-[26px] w-12",
23
- md: "h-8 w-[60px]",
23
+ xs: "h-4 w-7",
24
+ sm: "h-5 w-9",
25
+ md: "h-6 w-11",
24
26
  },
25
27
  },
26
28
  defaultVariants: {
@@ -31,16 +33,21 @@ const switchVariants = cva(
31
33
 
32
34
  const thumbVariants = cva(
33
35
  [
34
- "pointer-events-none block rounded-full bg-white",
35
- "shadow-sm transition-transform motion-reduce:transition-none",
36
+ "pointer-events-none block rounded-[2px]",
37
+ "bg-edge data-[state=checked]:bg-accent",
38
+ "transition-all motion-reduce:transition-none",
36
39
  "data-[state=unchecked]:translate-x-0.5",
40
+ "group-hover/sw:data-[state=checked]:shadow-[0_0_5px_var(--accent),0_0_10px_rgba(0,225,250,0.6)]",
41
+ "group-focus-visible/sw:data-[state=checked]:shadow-[0_0_5px_var(--accent),0_0_10px_rgba(0,225,250,0.6)]",
42
+ "group-disabled/sw:bg-foreground/30 group-disabled/sw:data-[state=checked]:bg-foreground/30",
43
+ "group-disabled/sw:shadow-none",
37
44
  ].join(" "),
38
45
  {
39
46
  variants: {
40
47
  size: {
41
- xs: "size-3 data-[state=checked]:translate-x-[18px]",
42
- sm: "size-[18px] data-[state=checked]:translate-x-[24px]",
43
- md: "size-6 data-[state=checked]:translate-x-[30px]",
48
+ xs: "size-3 data-[state=checked]:translate-x-[12px]",
49
+ sm: "size-4 data-[state=checked]:translate-x-[16px]",
50
+ md: "size-5 data-[state=checked]:translate-x-[20px]",
44
51
  },
45
52
  },
46
53
  defaultVariants: {
@@ -60,7 +67,7 @@ const Switch = forwardRef<HTMLButtonElement, SwitchProps>(
60
67
  return (
61
68
  <SwitchPrimitive.Root
62
69
  ref={ref}
63
- className={cn(switchVariants({ size }), className)}
70
+ className={cn("group/sw", switchVariants({ size }), className)}
64
71
  {...rest}
65
72
  >
66
73
  <SwitchPrimitive.Thumb className={thumbVariants({ size })} />
@@ -165,7 +165,7 @@ export function Table<T>({
165
165
  <th
166
166
  key={col.header}
167
167
  className={cn(
168
- "px-4 py-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground",
168
+ "px-4 py-3 font-mono font-normal uppercase tracking-widest text-[11px] text-muted-foreground",
169
169
  alignClass[col.align ?? "left"],
170
170
  col.sortable && "cursor-pointer select-none",
171
171
  )}
@@ -221,11 +221,11 @@ export function Table<T>({
221
221
  className={cn(
222
222
  "border-b border-edge transition-all",
223
223
  activeKey === keyExtractor(row)
224
- ? "bg-primary-600/10 shadow-[inset_3px_0_0_var(--color-primary-500)]"
224
+ ? "bg-accent/15 shadow-[inset_3px_0_0_var(--accent)]"
225
225
  : "even:bg-muted/30",
226
226
  "hover:bg-muted/50",
227
227
  onRowClick &&
228
- "cursor-pointer hover:shadow-[inset_3px_0_0_var(--color-primary-500)]",
228
+ "cursor-pointer hover:shadow-[inset_3px_0_0_var(--accent)]",
229
229
  )}
230
230
  aria-current={
231
231
  activeKey === keyExtractor(row) ? "true" : undefined
@@ -201,25 +201,22 @@ const OverflowTrigger = forwardRef<HTMLButtonElement, OverflowTriggerProps>(
201
201
  aria-label="More tabs"
202
202
  className={cn(
203
203
  "inline-flex items-center justify-center shrink-0",
204
- "font-heading font-bold",
205
- hasActiveHidden && isPill
206
- ? "text-white"
207
- : hasActiveHidden
208
- ? "text-primary-600 dark:text-primary-400"
209
- : "text-muted-foreground",
204
+ "font-sans font-semibold",
205
+ hasActiveHidden
206
+ ? "text-accent-500 dark:text-accent"
207
+ : "text-muted-foreground",
210
208
  "transition-colors duration-200",
211
- "hover:text-primary-600 dark:hover:text-primary-400",
212
- "focus-visible:outline-none focus-visible:ring-2",
213
- "focus-visible:ring-primary-400 focus-visible:ring-offset-2",
209
+ "hover:text-accent-500 dark:hover:text-accent",
210
+ "focus-visible:outline-2 focus-visible:outline-accent-500 dark:focus-visible:outline-accent focus-visible:outline-offset-2",
214
211
  "motion-reduce:transition-none",
215
212
  isPill
216
213
  ? cn(
217
- "relative z-10 rounded-full",
214
+ "relative z-10 rounded-[2px]",
218
215
  isSmall ? "px-2 py-0.5 text-xs" : "px-3 py-1.5 text-sm",
219
216
  )
220
217
  : isSmall
221
218
  ? "px-3 py-1.5 text-sm"
222
- : "px-4 py-3 text-lg",
219
+ : "px-4 py-3 text-sm",
223
220
  !visible && "invisible",
224
221
  )}
225
222
  >
@@ -230,7 +227,7 @@ const OverflowTrigger = forwardRef<HTMLButtonElement, OverflowTriggerProps>(
230
227
  <DropdownMenu.Content
231
228
  className={cn(
232
229
  "z-50 min-w-[8rem]",
233
- "rounded-md bg-surface border border-edge shadow-brand",
230
+ "rounded-none bg-popover-bg border border-popover-border shadow",
234
231
  "p-1",
235
232
  "motion-reduce:transition-none",
236
233
  )}
@@ -255,13 +252,13 @@ const OverflowTrigger = forwardRef<HTMLButtonElement, OverflowTriggerProps>(
255
252
  });
256
253
  }}
257
254
  className={cn(
258
- "flex w-full items-center rounded-sm px-3 py-2",
255
+ "flex w-full items-center rounded-none px-3 py-2",
259
256
  "text-sm text-foreground cursor-pointer select-none",
260
257
  "outline-none",
261
258
  "hover:bg-muted focus-visible:bg-muted",
262
- "data-[disabled]:opacity-50 data-[disabled]:pointer-events-none",
259
+ "data-[disabled]:text-foreground/30 data-[disabled]:cursor-not-allowed",
263
260
  tab.triggerEl.dataset["state"] === "active" &&
264
- "text-primary-600 dark:text-primary-400 font-bold",
261
+ "text-accent-500 dark:text-accent font-semibold",
265
262
  )}
266
263
  >
267
264
  {tab.label}
@@ -363,13 +360,11 @@ const TabsList = forwardRef<HTMLDivElement, TabsListProps>(
363
360
  "group relative flex",
364
361
  isPill
365
362
  ? [
366
- "rounded-full bg-muted",
363
+ "rounded-[2px] bg-muted border border-edge",
367
364
  isSmall ? "p-0.5" : "p-1",
368
365
  !isCollapse && "inline-flex",
369
366
  ]
370
- : isSmall
371
- ? "border-b-2 border-edge/40"
372
- : "border-b-4 border-edge/40",
367
+ : "border-b border-edge/40",
373
368
  className,
374
369
  )}
375
370
  {...rest}
@@ -392,15 +387,14 @@ const TabsList = forwardRef<HTMLDivElement, TabsListProps>(
392
387
  isPill
393
388
  ? [
394
389
  isSmall ? "inset-y-0.5" : "inset-y-1",
395
- "rounded-full bg-primary-600 dark:bg-primary-500",
390
+ "rounded-[2px] bg-accent/15",
396
391
  ready ? "opacity-100" : "opacity-0",
397
392
  ready
398
393
  ? "transition-[transform,width,opacity] duration-200 ease-out"
399
394
  : "",
400
395
  ]
401
396
  : [
402
- isSmall ? "-bottom-0.5 h-0.5" : "-bottom-1 h-1",
403
- "bg-primary-600 dark:bg-primary-400",
397
+ "-bottom-px h-px bg-accent-500 dark:bg-accent",
404
398
  ready
405
399
  ? "transition-[transform,width] duration-200 ease-out"
406
400
  : "",
@@ -427,31 +421,28 @@ const TabsTrigger = forwardRef<
427
421
  className={cn(
428
422
  [
429
423
  "inline-flex items-center gap-2 px-4 py-3",
430
- "font-heading font-bold text-lg",
424
+ "font-sans font-semibold text-sm",
431
425
  "text-foreground",
432
426
  "transition-[color,transform] duration-200 ease-out",
433
- "hover:text-primary-600 dark:hover:text-primary-400",
434
- "data-[state=active]:text-primary-600",
435
- "dark:data-[state=active]:text-primary-400",
427
+ "hover:text-accent-500 dark:hover:text-accent",
428
+ "data-[state=active]:text-accent-500 dark:data-[state=active]:text-accent",
436
429
  "data-[state=active]:-translate-y-0.5",
437
- "disabled:opacity-20 disabled:pointer-events-none",
438
- "focus-visible:outline-none focus-visible:ring-2",
439
- "focus-visible:ring-primary-400 focus-visible:ring-offset-2",
430
+ "disabled:text-foreground/30 disabled:cursor-not-allowed",
431
+ "focus-visible:outline-2 focus-visible:outline-accent-500 dark:focus-visible:outline-accent focus-visible:outline-offset-2",
440
432
  "motion-reduce:transition-none",
441
433
  // Small size overrides (underline)
442
434
  "group-data-[size=sm]:px-3 group-data-[size=sm]:py-1.5",
443
435
  "group-data-[size=sm]:text-sm group-data-[size=sm]:gap-1.5",
444
436
  // Pill variant overrides (via group data attribute on TabsList)
445
437
  "group-data-[variant=pill]:relative group-data-[variant=pill]:z-10",
446
- "group-data-[variant=pill]:rounded-full",
438
+ "group-data-[variant=pill]:rounded-[2px]",
447
439
  "group-data-[variant=pill]:px-5 group-data-[variant=pill]:py-1.5",
448
440
  "group-data-[variant=pill]:text-sm",
449
441
  "group-data-[variant=pill]:text-muted-foreground",
450
442
  "group-data-[variant=pill]:translate-y-0",
451
- "group-data-[variant=pill]:hover:text-foreground",
452
- "group-data-[variant=pill]:data-[state=active]:text-white",
443
+ "group-data-[variant=pill]:hover:text-accent-500 dark:group-data-[variant=pill]:hover:text-accent",
444
+ "group-data-[variant=pill]:data-[state=active]:text-accent-500 dark:group-data-[variant=pill]:data-[state=active]:text-accent",
453
445
  "group-data-[variant=pill]:data-[state=active]:translate-y-0",
454
- "group-data-[variant=pill]:focus-visible:ring-offset-0",
455
446
  // Small pill overrides (compound group selector)
456
447
  "group-[[data-variant=pill][data-size=sm]]:px-3",
457
448
  "group-[[data-variant=pill][data-size=sm]]:py-1",
@@ -4,13 +4,18 @@ import { cn } from "@ac/lib/cn";
4
4
 
5
5
  const textareaVariants = cva(
6
6
  [
7
- "w-full font-sans text-foreground bg-primary-100 dark:bg-base-700",
8
- "border-0 rounded-2xl",
7
+ "w-full font-sans text-foreground",
8
+ "bg-background dark:bg-surface",
9
+ "border border-edge rounded-none",
9
10
  "placeholder:text-muted-foreground",
10
- "focus-visible:outline-none focus-visible:ring-3",
11
- "focus-visible:ring-primary-500",
12
- "disabled:opacity-50 disabled:pointer-events-none",
13
- "ring-0 resize-y transition-colors",
11
+ "focus-visible:outline-none",
12
+ "focus-visible:border-accent-700 dark:focus-visible:border-accent",
13
+ "disabled:bg-muted dark:disabled:bg-background",
14
+ "disabled:border-edge/50",
15
+ "disabled:text-foreground/30",
16
+ "disabled:placeholder:text-muted-foreground/50",
17
+ "disabled:cursor-not-allowed",
18
+ "resize-y transition-colors",
14
19
  ].join(" "),
15
20
  {
16
21
  variants: {
@@ -41,7 +46,8 @@ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
41
46
  rows={rows}
42
47
  className={cn(
43
48
  textareaVariants({ size }),
44
- error && "border-3 border-error-400 hover:border-error-500",
49
+ error &&
50
+ "border-error hover:border-error focus-visible:border-error dark:focus-visible:border-error",
45
51
  className,
46
52
  )}
47
53
  aria-invalid={error || undefined}
@@ -16,8 +16,8 @@ const TooltipContent = forwardRef<
16
16
  sideOffset={sideOffset}
17
17
  className={cn(
18
18
  [
19
- "z-50 rounded-lg bg-neutral-900 dark:bg-base-800 px-3 py-1.5",
20
- "text-sm text-white shadow-brand-sm",
19
+ "z-50 rounded-none bg-popover-bg border border-popover-border px-3 py-1.5",
20
+ "text-sm text-foreground shadow-sm",
21
21
  "animate-in fade-in-0 zoom-in-95",
22
22
  "data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
23
23
  "data-[state=closed]:zoom-out-95",
@@ -9,7 +9,7 @@ const Skeleton = forwardRef<HTMLDivElement, SkeletonProps>(
9
9
  <div
10
10
  ref={ref}
11
11
  aria-hidden="true"
12
- className={cn("animate-pulse motion-reduce:animate-none rounded-md bg-muted", className)}
12
+ className={cn("animate-pulse motion-reduce:animate-none rounded-none bg-muted", className)}
13
13
  {...rest}
14
14
  />
15
15
  );