@nebutra/ui 2.0.0 → 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 (43) hide show
  1. package/dist/{canonical-Bf4czR2u.d.ts → canonical-DEhT4VfS.d.ts} +71 -120
  2. package/dist/card-DunB1gKE.d.ts +105 -0
  3. package/dist/components/index.d.ts +33 -4
  4. package/dist/components/index.js +225 -89
  5. package/dist/components/index.js.map +1 -1
  6. package/dist/editorial/index.d.ts +2 -2
  7. package/dist/editorial/index.js +75 -1
  8. package/dist/editorial/index.js.map +1 -1
  9. package/dist/empty-state-bmfHCOuN.d.ts +86 -0
  10. package/dist/hooks/index.d.ts +18 -1
  11. package/dist/hooks/index.js +120 -5
  12. package/dist/hooks/index.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/layout/index.d.ts +17 -58
  15. package/dist/layout/index.js +366 -126
  16. package/dist/layout/index.js.map +1 -1
  17. package/dist/layouts/index.d.ts +1 -1
  18. package/dist/layouts/index.js +89 -15
  19. package/dist/layouts/index.js.map +1 -1
  20. package/dist/patterns/index.d.ts +38 -44
  21. package/dist/patterns/index.js +470 -278
  22. package/dist/patterns/index.js.map +1 -1
  23. package/dist/primitives/canonical.d.ts +3 -1
  24. package/dist/primitives/canonical.js +495 -231
  25. package/dist/primitives/canonical.js.map +1 -1
  26. package/dist/primitives/index.d.ts +90 -62
  27. package/dist/primitives/index.js +1129 -1185
  28. package/dist/primitives/index.js.map +1 -1
  29. package/dist/tailwind.preset.d.ts +32 -32
  30. package/dist/tailwind.preset.js +24 -20
  31. package/dist/tailwind.preset.js.map +1 -1
  32. package/dist/theme/index.d.ts +1 -110
  33. package/dist/typography/fonts.css +8 -16
  34. package/dist/typography/index.d.ts +29 -45
  35. package/dist/typography/index.js +26 -14
  36. package/dist/typography/index.js.map +1 -1
  37. package/dist/utils/index.d.ts +18 -12
  38. package/dist/utils/index.js +94 -18
  39. package/dist/utils/index.js.map +1 -1
  40. package/package.json +6 -10
  41. package/src/typography/fonts.css +8 -16
  42. package/dist/styles/brand-override.css +0 -61
  43. 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,6 +101,18 @@ 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
@@ -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-brand)]",
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-brand)]",
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,11 @@ 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-[color,background-color,border-color,transform] duration-flow ease-[var(--ease-brand)]",
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)]",
802
1001
  "active:scale-[0.97] motion-reduce:active:scale-100 motion-reduce:transition-none",
803
1002
  "disabled:pointer-events-none disabled:opacity-50",
804
1003
  "aria-busy:cursor-wait"
@@ -808,7 +1007,7 @@ var buttonVariants = cva(
808
1007
  variant: {
809
1008
  // Recipe-driven via packages/design/tokens/recipe.css (.btn-brand-default)
810
1009
  default: "btn-brand-default",
811
- 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",
812
1011
  destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
813
1012
  outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
814
1013
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
@@ -931,10 +1130,10 @@ function Spinner({ className }) {
931
1130
  }
932
1131
  );
933
1132
  }
934
- function getIconSizeClass(size, iconSize2) {
935
- if (iconSize2 === "sm") return "[&>svg]:size-3.5";
936
- if (iconSize2 === "md") return "[&>svg]:size-3.5";
937
- 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";
938
1137
  switch (size) {
939
1138
  case "tiny":
940
1139
  return "[&>svg]:size-3";
@@ -949,10 +1148,10 @@ function getIconSizeClass(size, iconSize2) {
949
1148
  return "[&>svg]:size-4";
950
1149
  }
951
1150
  }
952
- function getSpinnerSizeClass(size, iconSize2) {
953
- if (iconSize2 === "sm") return "size-3.5";
954
- if (iconSize2 === "md") return "size-3.5";
955
- 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";
956
1155
  switch (size) {
957
1156
  case "tiny":
958
1157
  return "size-3";
@@ -977,10 +1176,10 @@ function resolveShadowClass(shadow) {
977
1176
  const level = shadow === true ? "md" : shadow;
978
1177
  return SHADOW_CLASSES[level];
979
1178
  }
980
- function ButtonContent({ loading, prefix, suffix, size, iconSize: iconSize2, children }) {
981
- const iconSizeClass = getIconSizeClass(size, iconSize2);
1179
+ function ButtonContent({ loading, prefix, suffix, size, iconSize: iconSize3, children }) {
1180
+ const iconSizeClass = getIconSizeClass(size, iconSize3);
982
1181
  return /* @__PURE__ */ jsxs(Fragment, { children: [
983
- loading && /* @__PURE__ */ jsx(Spinner, { className: getSpinnerSizeClass(size, iconSize2) }),
1182
+ loading && /* @__PURE__ */ jsx(Spinner, { className: getSpinnerSizeClass(size, iconSize3) }),
984
1183
  prefix != null && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("shrink-0", iconSizeClass), children: prefix }),
985
1184
  children,
986
1185
  suffix != null && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("shrink-0", iconSizeClass), children: suffix })
@@ -991,7 +1190,7 @@ var Button = ({
991
1190
  variant,
992
1191
  size,
993
1192
  shape,
994
- iconSize: iconSize2,
1193
+ iconSize: iconSize3,
995
1194
  asChild = false,
996
1195
  loading = false,
997
1196
  disabled,
@@ -1009,7 +1208,7 @@ var Button = ({
1009
1208
  Comp,
1010
1209
  {
1011
1210
  ref,
1012
- className: cn(buttonVariants({ variant, size, shape, iconSize: iconSize2 }), shadowClass, className),
1211
+ className: cn(buttonVariants({ variant, size, shape, iconSize: iconSize3 }), shadowClass, className),
1013
1212
  disabled: isDisabled,
1014
1213
  "aria-busy": loading || void 0,
1015
1214
  ...props,
@@ -1020,7 +1219,7 @@ var Button = ({
1020
1219
  prefix,
1021
1220
  suffix,
1022
1221
  size,
1023
- iconSize: iconSize2,
1222
+ iconSize: iconSize3,
1024
1223
  children
1025
1224
  }
1026
1225
  )
@@ -1033,7 +1232,7 @@ var ButtonLink = ({
1033
1232
  variant,
1034
1233
  size,
1035
1234
  shape,
1036
- iconSize: iconSize2,
1235
+ iconSize: iconSize3,
1037
1236
  prefix,
1038
1237
  suffix,
1039
1238
  shadow,
@@ -1053,7 +1252,7 @@ var ButtonLink = ({
1053
1252
  {
1054
1253
  ref,
1055
1254
  className: cn(
1056
- buttonVariants({ variant, size, shape, iconSize: iconSize2 }),
1255
+ buttonVariants({ variant, size, shape, iconSize: iconSize3 }),
1057
1256
  shadowClass,
1058
1257
  loading && "pointer-events-none opacity-50",
1059
1258
  className
@@ -1067,7 +1266,7 @@ var ButtonLink = ({
1067
1266
  prefix,
1068
1267
  suffix,
1069
1268
  size,
1070
- iconSize: iconSize2,
1269
+ iconSize: iconSize3,
1071
1270
  children
1072
1271
  }
1073
1272
  )
@@ -1134,12 +1333,12 @@ var ErrorSurface = ({
1134
1333
  "span",
1135
1334
  {
1136
1335
  "aria-hidden": "true",
1137
- 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",
1138
1337
  children: /* @__PURE__ */ jsx(Warning, { size: surfaceIconSize[size] })
1139
1338
  }
1140
1339
  ),
1141
1340
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1142
- 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,
1143
1342
  resolvedTitle ? /* @__PURE__ */ jsx("p", { className: "font-semibold leading-6 text-foreground", children: resolvedTitle }) : null,
1144
1343
  message ? /* @__PURE__ */ jsx("p", { className: cn("leading-5 text-muted-foreground", resolvedTitle && "mt-1"), children: message }) : null,
1145
1344
  resolvedErrorId ? /* @__PURE__ */ jsxs("details", { className: "mt-3 text-xs text-muted-foreground", children: [
@@ -1239,26 +1438,67 @@ function ActionButton({
1239
1438
  FullPageStatus.Accent = function Accent({ children }) {
1240
1439
  return /* @__PURE__ */ jsx("span", { className: "text-neutral-12", children });
1241
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
+ }
1242
1472
  var spinnerSize = {
1243
1473
  small: "h-4 w-4 border-2",
1244
1474
  medium: "h-6 w-6 border-2",
1245
1475
  large: "h-8 w-8 border-4"
1246
1476
  };
1247
1477
  function LoadingState({ message, size = "large" }) {
1248
- return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col items-center justify-center py-10", children: [
1249
- /* @__PURE__ */ jsx(
1250
- "span",
1251
- {
1252
- className: cn(
1253
- "inline-block animate-spin rounded-full border-solid border-primary border-r-transparent align-[-0.125em]",
1254
- 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
+ }
1255
1497
  ),
1256
- role: "status",
1257
- "aria-label": message ?? "Loading"
1258
- }
1259
- ),
1260
- message && /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-muted-foreground", children: message })
1261
- ] });
1498
+ message && /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-muted-foreground", children: message })
1499
+ ]
1500
+ }
1501
+ );
1262
1502
  }
1263
1503
  function PageHeader({ title, description, actions, className }) {
1264
1504
  return /* @__PURE__ */ jsxs(
@@ -1282,6 +1522,6 @@ function Section({ children, label, className }) {
1282
1522
  return /* @__PURE__ */ jsx("section", { "aria-label": label, className: cn("py-8", className), children });
1283
1523
  }
1284
1524
 
1285
- 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 };
1286
1526
  //# sourceMappingURL=index.js.map
1287
1527
  //# sourceMappingURL=index.js.map