@nebutra/ui 0.2.3 → 3.0.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.
Files changed (51) hide show
  1. package/dist/animate-in-BlvjVh2S.d.ts +250 -0
  2. package/dist/{canonical-Drca5UMj.d.ts → canonical-DEhT4VfS.d.ts} +83 -369
  3. package/dist/card-DunB1gKE.d.ts +105 -0
  4. package/dist/chat/index.d.ts +1 -0
  5. package/dist/chat/index.js +3 -0
  6. package/dist/chat/index.js.map +1 -0
  7. package/dist/components/index.d.ts +90 -126
  8. package/dist/components/index.js +433 -182
  9. package/dist/components/index.js.map +1 -1
  10. package/dist/editorial/index.d.ts +404 -0
  11. package/dist/editorial/index.js +942 -0
  12. package/dist/editorial/index.js.map +1 -0
  13. package/dist/empty-state-bmfHCOuN.d.ts +86 -0
  14. package/dist/hooks/index.d.ts +18 -1
  15. package/dist/hooks/index.js +120 -5
  16. package/dist/hooks/index.js.map +1 -1
  17. package/dist/icons/index.d.ts +1 -0
  18. package/dist/index.d.ts +1 -1
  19. package/dist/layout/index.d.ts +17 -58
  20. package/dist/layout/index.js +368 -127
  21. package/dist/layout/index.js.map +1 -1
  22. package/dist/layouts/index.d.ts +1 -1
  23. package/dist/layouts/index.js +93 -28
  24. package/dist/layouts/index.js.map +1 -1
  25. package/dist/patterns/index.d.ts +39 -46
  26. package/dist/patterns/index.js +534 -323
  27. package/dist/patterns/index.js.map +1 -1
  28. package/dist/primitives/canonical.d.ts +5 -2
  29. package/dist/primitives/canonical.js +598 -288
  30. package/dist/primitives/canonical.js.map +1 -1
  31. package/dist/primitives/index.d.ts +165 -63
  32. package/dist/primitives/index.js +1251 -983
  33. package/dist/primitives/index.js.map +1 -1
  34. package/dist/tailwind.preset.d.ts +32 -32
  35. package/dist/tailwind.preset.js +24 -20
  36. package/dist/tailwind.preset.js.map +1 -1
  37. package/dist/theme/index.d.ts +1 -110
  38. package/dist/typography/fonts.css +8 -16
  39. package/dist/typography/index.d.ts +29 -45
  40. package/dist/typography/index.js +26 -14
  41. package/dist/typography/index.js.map +1 -1
  42. package/dist/utils/auth-surfaces.d.ts +33 -0
  43. package/dist/utils/auth-surfaces.js +9 -0
  44. package/dist/utils/auth-surfaces.js.map +1 -0
  45. package/dist/utils/index.d.ts +20 -16
  46. package/dist/utils/index.js +99 -20
  47. package/dist/utils/index.js.map +1 -1
  48. package/package.json +25 -11
  49. package/src/typography/fonts.css +8 -16
  50. package/dist/styles/brand-override.css +0 -61
  51. package/src/styles/brand-override.css +0 -61
@@ -1,18 +1,19 @@
1
1
  "use client";
2
2
  import { Menu, Cross, Warning } from '@nebutra/icons';
3
- import * as React4 from 'react';
3
+ import * as React3 from 'react';
4
4
  import { createContext, useState, useEffect, useMemo, useCallback } from 'react';
5
5
  import { Dialog } from '@base-ui/react/dialog';
6
6
  import { cva } from 'class-variance-authority';
7
7
  import { colors } from '@nebutra/brand';
8
+ import { tokenColor, fontStack } from '@nebutra/tokens/values';
8
9
  import { clsx } from 'clsx';
9
- import { twMerge } from 'tailwind-merge';
10
+ import { extendTailwindMerge } from 'tailwind-merge';
10
11
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
12
  import Link from 'next/link';
12
13
 
13
14
  // src/layout/app-shell.tsx
14
15
  ({
15
- // Nebutra Blue (云毓蓝) — technology & trust — VI: #0033FE
16
+ // Nebutra Blue (云毓蓝) — technology & trust
16
17
  blue50: colors.primary[50],
17
18
  blue100: colors.primary[100],
18
19
  blue200: colors.primary[200],
@@ -25,7 +26,7 @@ import Link from 'next/link';
25
26
  blue800: colors.primary[800],
26
27
  blue900: colors.primary[900],
27
28
  blue950: colors.primary[950],
28
- // Nebutra Cyan (云毓青) — data flow & intelligence — VI: #0BF1C3
29
+ // Nebutra Cyan (云毓青) — data flow & intelligence
29
30
  cyan50: colors.accent[50],
30
31
  cyan100: colors.accent[100],
31
32
  cyan200: colors.accent[200],
@@ -50,6 +51,13 @@ import Link from 'next/link';
50
51
  neutral800: colors.neutral[800],
51
52
  neutral900: colors.neutral[900],
52
53
  neutral950: colors.neutral[950],
54
+ // deepest — used as dark background
55
+ // Semantic raw colors — VI §Color Specifications. The 500s are the status
56
+ // tokens; the 600 hover steps have no token of their own.
57
+ // NOTE: info = brand blue, not sky/teal
58
+ red500: tokenColor("--status-danger"),
59
+ green500: tokenColor("--status-success"),
60
+ amber500: tokenColor("--status-warning"),
53
61
  white: colors.white,
54
62
  black: colors.black
55
63
  });
@@ -93,12 +101,24 @@ var primitiveFontSize = {
93
101
  lg: 18};
94
102
  var primitiveFontWeight = {
95
103
  semibold: 600};
104
+ ({
105
+ /** Default body/UI stack — Geist with CJK falling through to MiSans */
106
+ sans: fontStack("--font-sans"),
107
+ /** CJK-locale stack (same order as sans, minus Latin-only system faces) */
108
+ cnSans: fontStack("--font-cn"),
109
+ /** Display / hero headlines */
110
+ display: fontStack("--font-display"),
111
+ /** Headings */
112
+ heading: fontStack("--font-heading"),
113
+ /** Code / monospace */
114
+ mono: fontStack("--font-mono")
115
+ });
96
116
  var primitiveTransition = {
97
117
  duration: {
98
118
  // hover, focus, small state changes
99
119
  flow: 200},
100
120
  easing: {
101
- default: "ease-out"}
121
+ default: "var(--ease-brand)"}
102
122
  };
103
123
 
104
124
  // src/tokens/components/overlay.ts
@@ -114,13 +134,13 @@ var overlayClassNames = {
114
134
  focusRing: overlayFocusRingClassName,
115
135
  sheetBackdrop: [
116
136
  "fixed inset-0 bg-[var(--sheet-overlay-background)] backdrop-blur-[var(--sheet-overlay-blur)]",
117
- "transition-[opacity,backdrop-filter,display] duration-[var(--motion-duration-flow)] ease-[var(--ease-out)]",
118
- "data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none"
137
+ "transition-[opacity] duration-[var(--motion-duration-flow)] ease-[var(--ease-brand)]",
138
+ "data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:duration-[var(--motion-duration-micro)] motion-reduce:transition-none"
119
139
  ].join(" "),
120
140
  sheetSurface: [
121
141
  "fixed flex flex-col overflow-hidden border border-border bg-[var(--sheet-background)] text-foreground shadow-[var(--sheet-shadow)]",
122
- "outline-none transition-[opacity,transform,display] duration-[var(--motion-duration-flow)] ease-[var(--ease-out)]",
123
- "data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none"
142
+ "outline-none transition-[opacity,transform] duration-[var(--motion-duration-flow)] ease-[var(--ease-brand)]",
143
+ "data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:duration-[var(--motion-duration-micro)] motion-reduce:transition-none"
124
144
  ].join(" ")};
125
145
 
126
146
  // src/tokens/components/sheet.ts
@@ -151,6 +171,80 @@ var sheetTokens = {
151
171
  background: "hsl(var(--popover))",
152
172
  shadow: "var(--elevation-xl)"
153
173
  }};
174
+
175
+ // src/utils/tw-merge-theme.generated.ts
176
+ var TW_MERGE_THEME = {
177
+ breakpoint: ["3xl", "shell", "tight", "xs"],
178
+ container: ["content", "text", "wide"],
179
+ ease: ["brand", "in", "in-out", "out", "spring"],
180
+ font: ["cn", "display", "heading", "mono", "sans"],
181
+ leading: ["display", "heading"],
182
+ radius: ["2xl", "3xl", "button", "card", "full", "lg", "md", "none", "panel", "sm", "xl", "xs"],
183
+ shadow: [
184
+ "2xl",
185
+ "ambient-glow",
186
+ "ambient-lg",
187
+ "ambient-md",
188
+ "ambient-sm",
189
+ "brand",
190
+ "brand-lg",
191
+ "glass-lg",
192
+ "glass-md",
193
+ "glass-sm",
194
+ "glow-accent",
195
+ "glow-accent-lg",
196
+ "glow-accent-sm",
197
+ "glow-primary",
198
+ "lg",
199
+ "md",
200
+ "sheen",
201
+ "sm",
202
+ "xl",
203
+ "xs"
204
+ ],
205
+ text: [
206
+ "2xl",
207
+ "2xs",
208
+ "3xl",
209
+ "4xl",
210
+ "5xl",
211
+ "6xl",
212
+ "7xl",
213
+ "8xl",
214
+ "9xl",
215
+ "base",
216
+ "body",
217
+ "display",
218
+ "label",
219
+ "lg",
220
+ "meta",
221
+ "sm",
222
+ "ui",
223
+ "xl",
224
+ "xs"
225
+ ],
226
+ tracking: ["display", "heading", "label", "tight"]
227
+ };
228
+ var TW_MERGE_CLASS_GROUPS = {
229
+ duration: [{ duration: ["cinematic", "flow", "micro", "reveal"] }]
230
+ };
231
+
232
+ // src/utils/cn.ts
233
+ var twMerge = extendTailwindMerge({
234
+ extend: {
235
+ theme: Object.fromEntries(
236
+ Object.entries(TW_MERGE_THEME).map(([key, values]) => [key, [...values]])
237
+ ),
238
+ classGroups: Object.fromEntries(
239
+ Object.entries(TW_MERGE_CLASS_GROUPS).map(([key, groups]) => [
240
+ key,
241
+ groups.map(
242
+ (group) => Object.fromEntries(Object.entries(group).map(([k, v]) => [k, [...v]]))
243
+ )
244
+ ])
245
+ )
246
+ }
247
+ });
154
248
  function cn(...inputs) {
155
249
  return twMerge(clsx(inputs));
156
250
  }
@@ -203,7 +297,7 @@ var SheetClose = ({
203
297
  ref,
204
298
  ...props
205
299
  }) => {
206
- if (asChild && React4.isValidElement(children)) {
300
+ if (asChild && React3.isValidElement(children)) {
207
301
  return /* @__PURE__ */ jsx(
208
302
  Dialog.Close,
209
303
  {
@@ -258,6 +352,7 @@ var SheetContent = ({
258
352
  noOverlay = false,
259
353
  showClose,
260
354
  close,
355
+ closeLabel = "Close",
261
356
  style,
262
357
  ref,
263
358
  ...props
@@ -279,7 +374,7 @@ var SheetContent = ({
279
374
  shouldRenderClose ? /* @__PURE__ */ jsxs(
280
375
  SheetClose,
281
376
  {
282
- "aria-label": "Close",
377
+ "aria-label": closeLabel,
283
378
  className: cn(
284
379
  "absolute right-[var(--sheet-close-offset)] top-[var(--sheet-close-offset)] inline-flex size-[var(--sheet-close-size)] items-center justify-center rounded-[var(--sheet-close-radius)] text-muted-foreground",
285
380
  "transition-[background-color,color,box-shadow] duration-[var(--motion-duration-micro)] ease-[var(--ease-out)] hover:bg-muted hover:text-foreground",
@@ -288,7 +383,7 @@ var SheetContent = ({
288
383
  ),
289
384
  children: [
290
385
  /* @__PURE__ */ jsx(Cross, { "aria-hidden": "true", className: "size-[var(--sheet-close-icon-size)]" }),
291
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
386
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: closeLabel })
292
387
  ]
293
388
  }
294
389
  ) : null
@@ -313,28 +408,31 @@ function AppShell({
313
408
  headerHeight = DEFAULT_HEADER_HEIGHT,
314
409
  contentClassName,
315
410
  className,
316
- overlay = false
411
+ overlay = false,
412
+ primaryNavLabel = "Primary",
413
+ collapseNavigationLabel = "Collapse navigation",
414
+ openNavigationMenuLabel = "Open navigation menu"
317
415
  }) {
318
416
  const isControlled = collapsed !== void 0;
319
- const [internalCollapsed] = React4.useState(defaultCollapsed);
417
+ const [internalCollapsed] = React3.useState(defaultCollapsed);
320
418
  const isCollapsed = isControlled ? collapsed : internalCollapsed;
321
- const [mobileOpen, setMobileOpen] = React4.useState(false);
419
+ const [mobileOpen, setMobileOpen] = React3.useState(false);
322
420
  const railWidth = isCollapsed ? sidebarCollapsedWidth : sidebarWidth;
323
421
  return /* @__PURE__ */ jsxs(
324
422
  "div",
325
423
  {
326
424
  "data-ui": "nebutra-app-shell",
327
425
  className: cn(
328
- "relative flex h-screen w-full overflow-hidden bg-background text-foreground",
426
+ "relative flex h-dvh w-full overflow-hidden bg-background text-foreground",
329
427
  className
330
428
  ),
331
429
  children: [
332
430
  /* @__PURE__ */ jsx(
333
431
  "aside",
334
432
  {
335
- "aria-label": "Primary",
433
+ "aria-label": primaryNavLabel,
336
434
  className: cn(
337
- "hidden h-screen overflow-hidden bg-sidebar text-sidebar-foreground md:block",
435
+ "hidden h-dvh overflow-hidden bg-sidebar text-sidebar-foreground md:block",
338
436
  "transition-[width] duration-flow ease-out",
339
437
  overlay ? "absolute inset-y-0 left-0 z-40" : "shrink-0",
340
438
  railWidth > 0 && "border-r border-sidebar-border",
@@ -355,9 +453,9 @@ function AppShell({
355
453
  "button",
356
454
  {
357
455
  type: "button",
358
- "aria-label": "Collapse navigation",
456
+ "aria-label": collapseNavigationLabel,
359
457
  onClick: () => onCollapsedChange?.(true),
360
- className: "absolute inset-0 z-30 hidden cursor-default bg-foreground/20 backdrop-blur-[1px] md:block"
458
+ className: "absolute inset-0 z-30 hidden cursor-default bg-foreground/25 backdrop-blur-xs md:block"
361
459
  }
362
460
  ) : null,
363
461
  /* @__PURE__ */ jsx(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: /* @__PURE__ */ jsx(SheetContent, { side: "left", className: "w-72 border-r border-sidebar-border bg-sidebar p-0", children: /* @__PURE__ */ jsx("div", { className: "h-full overflow-y-auto", children: sidebar }) }) }),
@@ -375,12 +473,11 @@ function AppShell({
375
473
  "button",
376
474
  {
377
475
  type: "button",
378
- "aria-label": "Open navigation menu",
476
+ "aria-label": openNavigationMenuLabel,
379
477
  onClick: () => setMobileOpen(true),
380
478
  className: cn(
381
479
  "relative inline-flex size-8 items-center justify-center rounded-[var(--radius-md)] text-foreground/80 after:absolute after:-inset-1.5 after:content-['']",
382
- "hover:bg-muted hover:text-foreground md:hidden",
383
- "focus-visible:outline-none"
480
+ "transition-colors duration-micro ease-brand hover:bg-muted hover:text-foreground md:hidden"
384
481
  ),
385
482
  children: /* @__PURE__ */ jsx(Menu, { className: "size-4", "aria-hidden": "true" })
386
483
  }
@@ -393,12 +490,11 @@ function AppShell({
393
490
  "button",
394
491
  {
395
492
  type: "button",
396
- "aria-label": "Open navigation menu",
493
+ "aria-label": openNavigationMenuLabel,
397
494
  onClick: () => setMobileOpen(true),
398
495
  className: cn(
399
496
  "relative inline-flex size-8 items-center justify-center rounded-[var(--radius-md)] text-foreground/80 after:absolute after:-inset-1.5 after:content-['']",
400
- "hover:bg-muted hover:text-foreground",
401
- "focus-visible:outline-none"
497
+ "transition-colors duration-micro ease-brand hover:bg-muted hover:text-foreground"
402
498
  ),
403
499
  children: /* @__PURE__ */ jsx(Menu, { className: "size-4", "aria-hidden": "true" })
404
500
  }
@@ -418,19 +514,118 @@ function AppShell({
418
514
  }
419
515
  );
420
516
  }
421
- function Card({ children, isInteractive = false, className }) {
517
+ var cardVariants = cva(
518
+ "rounded-[var(--radius-card,var(--radius-lg))] text-card-foreground transition-[box-shadow,background-color] duration-micro",
519
+ {
520
+ variants: {
521
+ variant: {
522
+ default: "bg-card shadow-[var(--elevation-card)] ring-1 ring-[var(--edge-soft)]",
523
+ elevated: "bg-card shadow-ambient-md ring-1 ring-[var(--edge-soft)]",
524
+ outline: "bg-transparent ring-1 ring-border",
525
+ ghost: "bg-transparent"
526
+ },
527
+ padding: {
528
+ none: "",
529
+ sm: "p-4",
530
+ md: "p-6",
531
+ lg: "p-8"
532
+ },
533
+ interactive: {
534
+ true: "cursor-pointer hover:shadow-ambient-md",
535
+ false: ""
536
+ }
537
+ },
538
+ defaultVariants: { variant: "default", padding: "none", interactive: false }
539
+ }
540
+ );
541
+ var CardPaddedContext = React3.createContext(false);
542
+ var CardRoot = ({
543
+ className,
544
+ variant,
545
+ padding = "none",
546
+ interactive,
547
+ ref,
548
+ ...props
549
+ }) => /* @__PURE__ */ jsx(CardPaddedContext.Provider, { value: padding !== "none", children: /* @__PURE__ */ jsx(
550
+ "div",
551
+ {
552
+ ref,
553
+ className: cn(cardVariants({ variant, padding, interactive }), className),
554
+ ...props
555
+ }
556
+ ) });
557
+ CardRoot.displayName = "Card";
558
+ var CardHeader = ({ className, ref, ...props }) => {
559
+ const padded = React3.use(CardPaddedContext);
422
560
  return /* @__PURE__ */ jsx(
423
561
  "div",
424
562
  {
563
+ ref,
564
+ className: cn("flex flex-col gap-1.5", !padded && "p-6", className),
565
+ ...props
566
+ }
567
+ );
568
+ };
569
+ CardHeader.displayName = "CardHeader";
570
+ var CardTitle = ({ className, as: Tag = "h3", ref, ...props }) => /* @__PURE__ */ jsx(
571
+ Tag,
572
+ {
573
+ ref,
574
+ className: cn("text-lg font-semibold leading-tight tracking-tight text-foreground", className),
575
+ ...props
576
+ }
577
+ );
578
+ CardTitle.displayName = "CardTitle";
579
+ var CardDescription = ({ className, as: Tag = "p", ref, ...props }) => React3.createElement(Tag, {
580
+ ref,
581
+ className: cn("text-sm leading-relaxed text-muted-foreground", className),
582
+ ...props
583
+ });
584
+ CardDescription.displayName = "CardDescription";
585
+ var CardContent = ({ className, ref, ...props }) => {
586
+ const padded = React3.use(CardPaddedContext);
587
+ return /* @__PURE__ */ jsx("div", { ref, className: cn(padded ? "mt-4 first:mt-0" : "p-6 pt-0", className), ...props });
588
+ };
589
+ CardContent.displayName = "CardContent";
590
+ var CardFooter = ({ className, ref, ...props }) => {
591
+ const padded = React3.use(CardPaddedContext);
592
+ return /* @__PURE__ */ jsx(
593
+ "div",
594
+ {
595
+ ref,
425
596
  className: cn(
426
- "rounded-[var(--radius-lg)] border border-border bg-background p-4 text-foreground shadow-sm dark:bg-black/40",
427
- isInteractive && "cursor-pointer transition-shadow hover:shadow-md",
597
+ "flex items-center",
598
+ padded ? "mt-6 border-t border-border/50 pt-4 first:mt-0" : "p-6 pt-0",
428
599
  className
429
600
  ),
430
- children
601
+ ...props
431
602
  }
432
603
  );
433
- }
604
+ };
605
+ CardFooter.displayName = "CardFooter";
606
+ var iconSize = { sm: "size-8", md: "size-10", lg: "size-12" };
607
+ var CardIcon = ({ className, size = "md", ref, ...props }) => /* @__PURE__ */ jsx(
608
+ "div",
609
+ {
610
+ ref,
611
+ className: cn(
612
+ "flex shrink-0 items-center justify-center rounded-[var(--radius-lg)] bg-muted/50",
613
+ iconSize[size],
614
+ className
615
+ ),
616
+ ...props
617
+ }
618
+ );
619
+ CardIcon.displayName = "CardIcon";
620
+ var Card = Object.assign(CardRoot, {
621
+ Header: CardHeader,
622
+ Title: CardTitle,
623
+ Description: CardDescription,
624
+ Content: CardContent,
625
+ Body: CardContent,
626
+ Footer: CardFooter,
627
+ Icon: CardIcon
628
+ });
434
629
  var sizeClasses = {
435
630
  sm: "max-w-screen-sm",
436
631
  md: "max-w-screen-md",
@@ -496,6 +691,54 @@ function DesignSystemProvider({
496
691
  );
497
692
  return /* @__PURE__ */ jsx(DesignSystemContext.Provider, { value: contextValue, children });
498
693
  }
694
+
695
+ // src/tokens/components/empty-state.ts
696
+ var emptyStateTokens = {
697
+ root: {
698
+ minHeight: {
699
+ sm: primitiveSpacing[16] * 2,
700
+ md: primitiveSpacing[24] * 2,
701
+ lg: primitiveSpacing[16] * 4
702
+ },
703
+ paddingX: {
704
+ sm: primitiveSpacing[4],
705
+ md: primitiveSpacing[6],
706
+ lg: primitiveSpacing[8]
707
+ },
708
+ paddingY: {
709
+ sm: primitiveSpacing[8],
710
+ md: primitiveSpacing[10],
711
+ lg: primitiveSpacing[14]
712
+ },
713
+ radius: primitiveRadius.lg
714
+ },
715
+ content: {
716
+ maxWidth: primitiveSpacing[14] * 8,
717
+ descriptionMaxWidth: primitiveSpacing[12] * 8,
718
+ stackGap: primitiveSpacing[4],
719
+ copyGap: primitiveSpacing[2],
720
+ actionsGap: primitiveSpacing[2]
721
+ },
722
+ icon: {
723
+ size: primitiveSizing.lg,
724
+ radius: primitiveRadius.lg
725
+ },
726
+ typography: {
727
+ title: {
728
+ sm: primitiveFontSize.sm,
729
+ md: primitiveFontSize.base,
730
+ lg: primitiveFontSize.lg
731
+ },
732
+ description: {
733
+ sm: primitiveFontSize.xs,
734
+ md: primitiveFontSize.sm,
735
+ lg: primitiveFontSize.sm
736
+ },
737
+ titleWeight: primitiveFontWeight.semibold,
738
+ titleLineHeight: primitiveSpacing[6],
739
+ descriptionLineHeight: primitiveSpacing[6]
740
+ }
741
+ };
499
742
  var SIZE_MAP = {
500
743
  sm: { box: "h-8 w-8", halo: "h-16 w-16 -inset-4", glyph: "h-3.5 w-3.5" },
501
744
  md: { box: "h-10 w-10", halo: "h-20 w-20 -inset-5", glyph: "h-4 w-4" },
@@ -560,54 +803,6 @@ function DefaultGlyph({ className }) {
560
803
  }
561
804
  );
562
805
  }
563
-
564
- // src/tokens/components/empty-state.ts
565
- var emptyStateTokens = {
566
- root: {
567
- minHeight: {
568
- sm: primitiveSpacing[16] * 2,
569
- md: primitiveSpacing[24] * 2,
570
- lg: primitiveSpacing[16] * 4
571
- },
572
- paddingX: {
573
- sm: primitiveSpacing[4],
574
- md: primitiveSpacing[6],
575
- lg: primitiveSpacing[8]
576
- },
577
- paddingY: {
578
- sm: primitiveSpacing[8],
579
- md: primitiveSpacing[10],
580
- lg: primitiveSpacing[14]
581
- },
582
- radius: primitiveRadius.lg
583
- },
584
- content: {
585
- maxWidth: primitiveSpacing[14] * 8,
586
- descriptionMaxWidth: primitiveSpacing[12] * 8,
587
- stackGap: primitiveSpacing[4],
588
- copyGap: primitiveSpacing[2],
589
- actionsGap: primitiveSpacing[2]
590
- },
591
- icon: {
592
- size: primitiveSizing.lg,
593
- radius: primitiveRadius.lg
594
- },
595
- typography: {
596
- title: {
597
- sm: primitiveFontSize.sm,
598
- md: primitiveFontSize.base,
599
- lg: primitiveFontSize.lg
600
- },
601
- description: {
602
- sm: primitiveFontSize.xs,
603
- md: primitiveFontSize.sm,
604
- lg: primitiveFontSize.sm
605
- },
606
- titleWeight: primitiveFontWeight.semibold,
607
- titleLineHeight: primitiveSpacing[6],
608
- descriptionLineHeight: primitiveSpacing[6]
609
- }
610
- };
611
806
  var variantClassName = {
612
807
  "blank-slate": "border border-dashed border-border bg-background",
613
808
  informational: "border border-border bg-card",
@@ -626,7 +821,7 @@ var iconToneClassName = {
626
821
  "no-results": "text-muted-foreground",
627
822
  cleared: "text-muted-foreground",
628
823
  permission: "text-muted-foreground",
629
- error: "text-destructive"
824
+ error: "text-destructive-strong"
630
825
  };
631
826
  var emptyStateCssVars = (size) => ({
632
827
  "--empty-state-min-height": `${emptyStateTokens.root.minHeight[size]}px`,
@@ -682,7 +877,7 @@ var EmptyStateRoot = ({
682
877
  ...props
683
878
  }) => {
684
879
  const actions = children ?? [action, link].filter(Boolean);
685
- const hasActions = React4.Children.toArray(actions).length > 0;
880
+ const hasActions = React3.Children.toArray(actions).length > 0;
686
881
  return /* @__PURE__ */ jsx(
687
882
  "section",
688
883
  {
@@ -726,16 +921,12 @@ var EmptyStateRoot = ({
726
921
  );
727
922
  };
728
923
  EmptyStateRoot.displayName = "EmptyState.Root";
729
- var EmptyState = {
730
- Root: EmptyStateRoot,
731
- Icon: EmptyStateIcon
732
- };
733
924
  var toneVariant = {
734
925
  default: "blank-slate",
735
926
  branded: "informational",
736
927
  subtle: "guide"
737
928
  };
738
- function EmptyState2({
929
+ function EmptyStateSimple({
739
930
  title,
740
931
  description,
741
932
  mascot,
@@ -748,12 +939,12 @@ function EmptyState2({
748
939
  }) {
749
940
  const visual = mascot ?? icon ?? (tone === "branded" ? /* @__PURE__ */ jsx(BrandMark, { size: size === "lg" ? "lg" : "md" }) : null);
750
941
  return /* @__PURE__ */ jsx(
751
- EmptyState.Root,
942
+ EmptyStateRoot,
752
943
  {
753
944
  action,
754
945
  className: cn(tone === "subtle" && "border-0 bg-transparent", className),
755
946
  description,
756
- icon: visual ? /* @__PURE__ */ jsx(EmptyState.Icon, { icon: visual }) : void 0,
947
+ icon: visual ? /* @__PURE__ */ jsx(EmptyStateIcon, { icon: visual }) : void 0,
757
948
  link: secondaryAction,
758
949
  size,
759
950
  title,
@@ -761,11 +952,15 @@ function EmptyState2({
761
952
  }
762
953
  );
763
954
  }
764
- var Slot = React4.forwardRef(
955
+ var EmptyState = Object.assign(EmptyStateSimple, {
956
+ Root: EmptyStateRoot,
957
+ Icon: EmptyStateIcon
958
+ });
959
+ var Slot = React3.forwardRef(
765
960
  ({ children, className, style, ...props }, ref) => {
766
- const childArray = React4.Children.toArray(children);
961
+ const childArray = React3.Children.toArray(children);
767
962
  const child = childArray.length === 1 ? childArray[0] : null;
768
- if (child == null || !React4.isValidElement(child)) {
963
+ if (child == null || !React3.isValidElement(child)) {
769
964
  return /* @__PURE__ */ jsx("span", { ref, className, style, ...props, children });
770
965
  }
771
966
  const childProps = child.props;
@@ -786,7 +981,7 @@ var Slot = React4.forwardRef(
786
981
  className: mergedClass,
787
982
  style: { ...childProps.style, ...style }
788
983
  };
789
- return React4.cloneElement(
984
+ return React3.cloneElement(
790
985
  child,
791
986
  merged
792
987
  );
@@ -798,7 +993,12 @@ var buttonVariants = cva(
798
993
  "inline-flex items-center justify-center gap-2 whitespace-nowrap",
799
994
  "rounded-[var(--btn-default-radius,var(--radius-md))]",
800
995
  "text-[length:var(--control-font-size-md,0.875rem)] font-[number:var(--font-weight-medium,500)]",
801
- "transition-colors duration-micro ease-out",
996
+ // `transform` is in the property list on purpose: without it the
997
+ // active:scale-[0.97] press snapped instantly and the button felt dead
998
+ // under the finger. Duration/ease stay on the flow rail — motion
999
+ // governance pins them (primitives/__tests__/motion-governance.test.ts).
1000
+ "transition-[color,background-color,border-color,box-shadow,transform] duration-flow ease-[var(--ease-brand)]",
1001
+ "active:scale-[0.97] motion-reduce:active:scale-100 motion-reduce:transition-none",
802
1002
  "disabled:pointer-events-none disabled:opacity-50",
803
1003
  "aria-busy:cursor-wait"
804
1004
  ].join(" "),
@@ -807,7 +1007,7 @@ var buttonVariants = cva(
807
1007
  variant: {
808
1008
  // Recipe-driven via packages/design/tokens/recipe.css (.btn-brand-default)
809
1009
  default: "btn-brand-default",
810
- ink: "bg-[var(--neutral-12)] text-[var(--neutral-1)] ring-1 ring-inset ring-[color:var(--neutral-1)]/5 hover:bg-[var(--neutral-11)] hover:-translate-y-px active:translate-y-0 transition-[transform,background-color] duration-micro",
1010
+ ink: "bg-neutral-12 text-neutral-1 ring-1 ring-inset ring-neutral-1/5 hover:bg-neutral-11 hover:-translate-y-px active:translate-y-0 transition-[transform,background-color] duration-micro",
811
1011
  destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
812
1012
  outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
813
1013
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
@@ -930,10 +1130,10 @@ function Spinner({ className }) {
930
1130
  }
931
1131
  );
932
1132
  }
933
- function getIconSizeClass(size, iconSize2) {
934
- if (iconSize2 === "sm") return "[&>svg]:size-3.5";
935
- if (iconSize2 === "md") return "[&>svg]:size-3.5";
936
- if (iconSize2 === "lg") return "[&>svg]:size-4";
1133
+ function getIconSizeClass(size, iconSize3) {
1134
+ if (iconSize3 === "sm") return "[&>svg]:size-3.5";
1135
+ if (iconSize3 === "md") return "[&>svg]:size-3.5";
1136
+ if (iconSize3 === "lg") return "[&>svg]:size-4";
937
1137
  switch (size) {
938
1138
  case "tiny":
939
1139
  return "[&>svg]:size-3";
@@ -948,10 +1148,10 @@ function getIconSizeClass(size, iconSize2) {
948
1148
  return "[&>svg]:size-4";
949
1149
  }
950
1150
  }
951
- function getSpinnerSizeClass(size, iconSize2) {
952
- if (iconSize2 === "sm") return "size-3.5";
953
- if (iconSize2 === "md") return "size-3.5";
954
- if (iconSize2 === "lg") return "size-4";
1151
+ function getSpinnerSizeClass(size, iconSize3) {
1152
+ if (iconSize3 === "sm") return "size-3.5";
1153
+ if (iconSize3 === "md") return "size-3.5";
1154
+ if (iconSize3 === "lg") return "size-4";
955
1155
  switch (size) {
956
1156
  case "tiny":
957
1157
  return "size-3";
@@ -976,10 +1176,10 @@ function resolveShadowClass(shadow) {
976
1176
  const level = shadow === true ? "md" : shadow;
977
1177
  return SHADOW_CLASSES[level];
978
1178
  }
979
- function ButtonContent({ loading, prefix, suffix, size, iconSize: iconSize2, children }) {
980
- const iconSizeClass = getIconSizeClass(size, iconSize2);
1179
+ function ButtonContent({ loading, prefix, suffix, size, iconSize: iconSize3, children }) {
1180
+ const iconSizeClass = getIconSizeClass(size, iconSize3);
981
1181
  return /* @__PURE__ */ jsxs(Fragment, { children: [
982
- loading && /* @__PURE__ */ jsx(Spinner, { className: getSpinnerSizeClass(size, iconSize2) }),
1182
+ loading && /* @__PURE__ */ jsx(Spinner, { className: getSpinnerSizeClass(size, iconSize3) }),
983
1183
  prefix != null && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("shrink-0", iconSizeClass), children: prefix }),
984
1184
  children,
985
1185
  suffix != null && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("shrink-0", iconSizeClass), children: suffix })
@@ -990,7 +1190,7 @@ var Button = ({
990
1190
  variant,
991
1191
  size,
992
1192
  shape,
993
- iconSize: iconSize2,
1193
+ iconSize: iconSize3,
994
1194
  asChild = false,
995
1195
  loading = false,
996
1196
  disabled,
@@ -1008,7 +1208,7 @@ var Button = ({
1008
1208
  Comp,
1009
1209
  {
1010
1210
  ref,
1011
- className: cn(buttonVariants({ variant, size, shape, iconSize: iconSize2 }), shadowClass, className),
1211
+ className: cn(buttonVariants({ variant, size, shape, iconSize: iconSize3 }), shadowClass, className),
1012
1212
  disabled: isDisabled,
1013
1213
  "aria-busy": loading || void 0,
1014
1214
  ...props,
@@ -1019,7 +1219,7 @@ var Button = ({
1019
1219
  prefix,
1020
1220
  suffix,
1021
1221
  size,
1022
- iconSize: iconSize2,
1222
+ iconSize: iconSize3,
1023
1223
  children
1024
1224
  }
1025
1225
  )
@@ -1032,7 +1232,7 @@ var ButtonLink = ({
1032
1232
  variant,
1033
1233
  size,
1034
1234
  shape,
1035
- iconSize: iconSize2,
1235
+ iconSize: iconSize3,
1036
1236
  prefix,
1037
1237
  suffix,
1038
1238
  shadow,
@@ -1052,7 +1252,7 @@ var ButtonLink = ({
1052
1252
  {
1053
1253
  ref,
1054
1254
  className: cn(
1055
- buttonVariants({ variant, size, shape, iconSize: iconSize2 }),
1255
+ buttonVariants({ variant, size, shape, iconSize: iconSize3 }),
1056
1256
  shadowClass,
1057
1257
  loading && "pointer-events-none opacity-50",
1058
1258
  className
@@ -1066,7 +1266,7 @@ var ButtonLink = ({
1066
1266
  prefix,
1067
1267
  suffix,
1068
1268
  size,
1069
- iconSize: iconSize2,
1269
+ iconSize: iconSize3,
1070
1270
  children
1071
1271
  }
1072
1272
  )
@@ -1133,12 +1333,12 @@ var ErrorSurface = ({
1133
1333
  "span",
1134
1334
  {
1135
1335
  "aria-hidden": "true",
1136
- className: "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-[var(--radius-sm)] border border-destructive/20 bg-background text-destructive",
1336
+ className: "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-[var(--radius-sm)] border border-destructive/20 bg-background text-destructive-strong",
1137
1337
  children: /* @__PURE__ */ jsx(Warning, { size: surfaceIconSize[size] })
1138
1338
  }
1139
1339
  ),
1140
1340
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1141
- label && showLabel ? /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium uppercase tracking-normal text-destructive", children: label }) : null,
1341
+ label && showLabel ? /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium uppercase tracking-normal text-destructive-strong", children: label }) : null,
1142
1342
  resolvedTitle ? /* @__PURE__ */ jsx("p", { className: "font-semibold leading-6 text-foreground", children: resolvedTitle }) : null,
1143
1343
  message ? /* @__PURE__ */ jsx("p", { className: cn("leading-5 text-muted-foreground", resolvedTitle && "mt-1"), children: message }) : null,
1144
1344
  resolvedErrorId ? /* @__PURE__ */ jsxs("details", { className: "mt-3 text-xs text-muted-foreground", children: [
@@ -1238,26 +1438,67 @@ function ActionButton({
1238
1438
  FullPageStatus.Accent = function Accent({ children }) {
1239
1439
  return /* @__PURE__ */ jsx("span", { className: "text-neutral-12", children });
1240
1440
  };
1441
+
1442
+ // src/tokens/components/interaction.ts
1443
+ var interaction = {
1444
+ /**
1445
+ * Pending states. A spinner that flashes for 80ms reads as a glitch; one
1446
+ * that appears and vanishes in 200ms reads as jank. Show only after the
1447
+ * wait is noticeable, then keep it long enough to be read.
1448
+ * Vercel Web Interface Guidelines: show-delay ~150–300ms, minimum visible
1449
+ * ~300–500ms.
1450
+ */
1451
+ pending: { showDelayMs: 200, minVisibleMs: 400 }};
1452
+
1453
+ // src/hooks/use-pending-visible.ts
1454
+ function usePendingVisible(pending, {
1455
+ showDelayMs = interaction.pending.showDelayMs,
1456
+ minVisibleMs = interaction.pending.minVisibleMs
1457
+ } = {}) {
1458
+ const [visible, setVisible] = React3.useState(false);
1459
+ const shownAt = React3.useRef(null);
1460
+ React3.useEffect(() => {
1461
+ {
1462
+ if (visible) return;
1463
+ const timer2 = setTimeout(() => {
1464
+ shownAt.current = Date.now();
1465
+ setVisible(true);
1466
+ }, showDelayMs);
1467
+ return () => clearTimeout(timer2);
1468
+ }
1469
+ }, [pending, visible, showDelayMs, minVisibleMs]);
1470
+ return visible;
1471
+ }
1241
1472
  var spinnerSize = {
1242
1473
  small: "h-4 w-4 border-2",
1243
1474
  medium: "h-6 w-6 border-2",
1244
1475
  large: "h-8 w-8 border-4"
1245
1476
  };
1246
1477
  function LoadingState({ message, size = "large" }) {
1247
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-10", children: [
1248
- /* @__PURE__ */ jsx(
1249
- "span",
1250
- {
1251
- className: cn(
1252
- "inline-block animate-spin rounded-full border-solid border-primary border-r-transparent align-[-0.125em]",
1253
- spinnerSize[size]
1478
+ const visible = usePendingVisible(true);
1479
+ return /* @__PURE__ */ jsxs(
1480
+ "div",
1481
+ {
1482
+ className: cn(
1483
+ "flex w-full flex-col items-center justify-center py-10 transition-opacity duration-flow ease-out",
1484
+ visible ? "opacity-100" : "opacity-0"
1485
+ ),
1486
+ children: [
1487
+ /* @__PURE__ */ jsx(
1488
+ "span",
1489
+ {
1490
+ className: cn(
1491
+ "inline-block animate-spin rounded-full border-solid border-primary border-r-transparent align-[-0.125em]",
1492
+ spinnerSize[size]
1493
+ ),
1494
+ role: "status",
1495
+ "aria-label": message ?? "Loading"
1496
+ }
1254
1497
  ),
1255
- role: "status",
1256
- "aria-label": message ?? "Loading"
1257
- }
1258
- ),
1259
- message && /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-muted-foreground", children: message })
1260
- ] });
1498
+ message && /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-muted-foreground", children: message })
1499
+ ]
1500
+ }
1501
+ );
1261
1502
  }
1262
1503
  function PageHeader({ title, description, actions, className }) {
1263
1504
  return /* @__PURE__ */ jsxs(
@@ -1281,6 +1522,6 @@ function Section({ children, label, className }) {
1281
1522
  return /* @__PURE__ */ jsx("section", { "aria-label": label, className: cn("py-8", className), children });
1282
1523
  }
1283
1524
 
1284
- export { AppShell, Card, Container, DesignSystemProvider, EmptyState2 as EmptyState, ErrorState, FullPageStatus, LoadingState, PageHeader, Section };
1525
+ export { AppShell, Card, Container, DesignSystemProvider, EmptyState, ErrorState, FullPageStatus, LoadingState, PageHeader, Section };
1285
1526
  //# sourceMappingURL=index.js.map
1286
1527
  //# sourceMappingURL=index.js.map