@ponti-studios/ui 0.1.2 → 0.4.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 (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -7
  3. package/THIRD_PARTY_NOTICES.md +17 -0
  4. package/dist/index.js +988 -640
  5. package/dist/index.js.map +1 -1
  6. package/package.json +41 -41
  7. package/src/animations.css +42 -383
  8. package/src/components/data-display/accordion.tsx +22 -10
  9. package/src/components/data-display/filter-chip.stories.tsx +38 -0
  10. package/src/components/data-display/filter-chip.tsx +10 -4
  11. package/src/components/data-display/index.ts +5 -0
  12. package/src/components/data-display/metric-card.stories.tsx +48 -0
  13. package/src/components/data-display/metric-card.tsx +20 -0
  14. package/src/components/data-display/sort-controls.stories.tsx +45 -0
  15. package/src/components/data-display/sort-controls.tsx +99 -0
  16. package/src/components/data-display/sort-row.tsx +74 -0
  17. package/src/components/data-display/table.tsx +5 -9
  18. package/src/components/feedback/alert.tsx +4 -4
  19. package/src/components/feedback/empty-state.tsx +16 -6
  20. package/src/components/feedback/file-upload-status-badge.stories.tsx +28 -0
  21. package/src/components/feedback/file-upload-status-badge.tsx +33 -0
  22. package/src/components/feedback/file-upload-status.stories.tsx +57 -0
  23. package/src/components/feedback/file-upload-status.tsx +152 -0
  24. package/src/components/feedback/index.ts +9 -0
  25. package/src/components/feedback/progress-bar.stories.tsx +39 -0
  26. package/src/components/feedback/progress-bar.tsx +21 -0
  27. package/src/components/feedback/progress.stories.tsx +8 -6
  28. package/src/components/feedback/progress.tsx +3 -3
  29. package/src/components/feedback/skeleton.stories.tsx +36 -0
  30. package/src/components/feedback/skeleton.tsx +7 -0
  31. package/src/components/feedback/spinner.tsx +2 -2
  32. package/src/components/feedback/update-guard.stories.tsx +32 -0
  33. package/src/components/feedback/update-guard.tsx +280 -0
  34. package/src/components/forms/calendar.tsx +14 -11
  35. package/src/components/forms/date-month-select.stories.tsx +26 -0
  36. package/src/components/forms/date-month-select.tsx +70 -0
  37. package/src/components/forms/date-picker.tsx +3 -3
  38. package/src/components/forms/drop-zone.stories.tsx +56 -0
  39. package/src/components/forms/drop-zone.tsx +146 -0
  40. package/src/components/forms/entity-select.stories.tsx +65 -0
  41. package/src/components/forms/entity-select.tsx +79 -0
  42. package/src/components/forms/field.tsx +3 -3
  43. package/src/components/forms/group-by-select.stories.tsx +26 -0
  44. package/src/components/forms/group-by-select.tsx +47 -0
  45. package/src/components/forms/index.ts +10 -0
  46. package/src/components/forms/input.stories.tsx +4 -4
  47. package/src/components/forms/input.tsx +1 -1
  48. package/src/components/forms/passkey-management.stories.tsx +81 -0
  49. package/src/components/forms/passkey-management.tsx +138 -0
  50. package/src/components/forms/radio-group.tsx +14 -13
  51. package/src/components/forms/select.stories.tsx +4 -4
  52. package/src/components/forms/select.tsx +47 -48
  53. package/src/components/forms/slider.stories.tsx +5 -2
  54. package/src/components/forms/slider.tsx +39 -17
  55. package/src/components/forms/stepper.tsx +4 -4
  56. package/src/components/forms/switch.stories.tsx +1 -1
  57. package/src/components/forms/switch.tsx +2 -2
  58. package/src/components/forms/textarea.stories.tsx +2 -2
  59. package/src/components/forms/textarea.tsx +1 -1
  60. package/src/components/layout/index.ts +3 -0
  61. package/src/components/layout/scroll-area.stories.tsx +4 -4
  62. package/src/components/layout/scroll-area.tsx +23 -21
  63. package/src/components/layout/section-intro.stories.tsx +29 -0
  64. package/src/components/layout/section-intro.tsx +31 -0
  65. package/src/components/layout/surface-panel.stories.tsx +32 -0
  66. package/src/components/layout/surface-panel.tsx +15 -0
  67. package/src/components/navigation/app-navigation.stories.tsx +2 -2
  68. package/src/components/navigation/app-navigation.tsx +13 -13
  69. package/src/components/navigation/index.ts +4 -0
  70. package/src/components/navigation/pagination-controls.stories.tsx +41 -0
  71. package/src/components/navigation/pagination-controls.tsx +50 -0
  72. package/src/components/navigation/route-link.stories.tsx +30 -0
  73. package/src/components/navigation/route-link.tsx +42 -0
  74. package/src/components/navigation/tabs.stories.tsx +5 -5
  75. package/src/components/navigation/tabs.tsx +8 -8
  76. package/src/components/overlays/alert-dialog.tsx +22 -25
  77. package/src/components/overlays/command.tsx +98 -26
  78. package/src/components/overlays/dialog.tsx +5 -5
  79. package/src/components/overlays/dropdown-menu.stories.tsx +0 -5
  80. package/src/components/overlays/dropdown-menu.tsx +49 -31
  81. package/src/components/overlays/popover.stories.tsx +2 -2
  82. package/src/components/overlays/popover.tsx +43 -14
  83. package/src/components/overlays/sheet.stories.tsx +2 -2
  84. package/src/components/overlays/sheet.tsx +51 -17
  85. package/src/components/primitives/avatar.tsx +9 -5
  86. package/src/components/primitives/badge.tsx +11 -12
  87. package/src/components/primitives/button.tsx +49 -23
  88. package/src/components/primitives/card.stories.tsx +2 -2
  89. package/src/components/primitives/card.tsx +2 -5
  90. package/src/components/primitives/index.ts +2 -0
  91. package/src/components/primitives/label.stories.tsx +1 -1
  92. package/src/components/primitives/label.tsx +5 -6
  93. package/src/components/primitives/status-badge.stories.tsx +52 -0
  94. package/src/components/primitives/status-badge.tsx +33 -0
  95. package/src/constants/chart-colors.ts +7 -7
  96. package/src/hooks/index.ts +1 -0
  97. package/src/hooks/use-sort.ts +20 -0
  98. package/src/index.ts +30 -11
  99. package/src/styles.css +100 -974
  100. package/src/tokens/chart-colors.stories.tsx +192 -0
  101. package/src/tokens/color-systems.stories.tsx +20 -112
  102. package/src/tokens/foundations.stories.tsx +58 -100
  103. package/src/tokens/generated/dark.colors.generated.ts +29 -0
  104. package/src/tokens/generated/dark.css +29 -0
  105. package/src/tokens/generated/foundations.css +148 -0
  106. package/src/tokens/generated/foundations.generated.ts +451 -0
  107. package/src/tokens/generated/light.colors.generated.ts +29 -0
  108. package/src/tokens/generated/light.css +29 -0
  109. package/src/tokens/index.ts +79 -16
  110. package/src/tokens/layout.stories.tsx +23 -91
  111. package/src/tokens/colors.ts +0 -156
  112. package/src/tokens/motion.ts +0 -51
  113. package/src/tokens/radii.ts +0 -10
  114. package/src/tokens/shadows.ts +0 -34
  115. package/src/tokens/spacing.ts +0 -30
  116. package/src/tokens/typography.ts +0 -42
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import * as SheetPrimitive from "@radix-ui/react-dialog";
3
+ import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
4
4
  import { X } from "lucide-react";
5
5
  import * as React from "react";
6
6
 
@@ -8,48 +8,78 @@ import { cn } from "../../lib/utils";
8
8
 
9
9
  const Sheet = SheetPrimitive.Root;
10
10
 
11
- const SheetTrigger = SheetPrimitive.Trigger;
11
+ function SheetTrigger({
12
+ asChild,
13
+ children,
14
+ ...props
15
+ }: React.ComponentProps<typeof SheetPrimitive.Trigger> & { asChild?: boolean }) {
16
+ return (
17
+ <SheetPrimitive.Trigger
18
+ {...(asChild && React.isValidElement(children)
19
+ ? { render: children as React.ReactElement }
20
+ : {})}
21
+ {...props}
22
+ >
23
+ {!asChild && children}
24
+ </SheetPrimitive.Trigger>
25
+ );
26
+ }
12
27
 
13
- const SheetClose = SheetPrimitive.Close;
28
+ function SheetClose({
29
+ asChild,
30
+ children,
31
+ ...props
32
+ }: React.ComponentProps<typeof SheetPrimitive.Close> & { asChild?: boolean }) {
33
+ return (
34
+ <SheetPrimitive.Close
35
+ {...(asChild && React.isValidElement(children)
36
+ ? { render: children as React.ReactElement }
37
+ : {})}
38
+ {...props}
39
+ >
40
+ {!asChild && children}
41
+ </SheetPrimitive.Close>
42
+ );
43
+ }
14
44
 
15
45
  const SheetPortal = SheetPrimitive.Portal;
16
46
 
17
47
  const SheetOverlay = React.forwardRef<
18
- React.ComponentRef<typeof SheetPrimitive.Overlay>,
19
- React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
48
+ React.ComponentRef<typeof SheetPrimitive.Backdrop>,
49
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Backdrop>
20
50
  >(({ className, ...props }, ref) => (
21
- <SheetPrimitive.Overlay
51
+ <SheetPrimitive.Backdrop
22
52
  className={cn(
23
- "overlay-backdrop data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 transition-opacity",
53
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/80 transition-opacity",
24
54
  className,
25
55
  )}
26
56
  {...props}
27
57
  ref={ref}
28
58
  />
29
59
  ));
30
- SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
60
+ SheetOverlay.displayName = "SheetOverlay";
31
61
 
32
62
  const sheetClassName =
33
- "sheet-responsive sheet-edge-responsive bg-canvas text-primary border-subtle fixed inset-x-0 bottom-0 z-50 flex max-h-[90dvh] min-h-[30dvh] flex-col gap-6 rounded-t-xl border border-r-0 border-b-0 border-l-0 p-4 pb-[max(1rem,env(safe-area-inset-bottom))] md:inset-y-0 md:right-0 md:bottom-auto md:left-auto md:h-full md:w-3/4 md:max-w-sm md:min-h-0 md:rounded-t-none md:rounded-l-xl md:border-t-0 md:border-r-0 md:border-b-0 md:border-l md:p-6 md:pb-6";
63
+ "bg-background text-foreground data-open:animate-in data-closed:animate-out fixed inset-x-0 bottom-0 z-50 flex max-h-[90dvh] min-h-[30dvh] flex-col gap-6 rounded-t-xl border border-b-0 border-l-0 border-r-0 p-4 pb-[max(1rem,env(safe-area-inset-bottom))] md:inset-y-0 md:right-0 md:bottom-auto md:left-auto md:h-full md:w-3/4 md:max-w-sm md:min-h-0 md:rounded-t-none md:rounded-l-xl md:border-t-0 md:border-r-0 md:border-b-0 md:border-l md:p-6 md:pb-6";
34
64
 
35
- interface SheetContentProps extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content> {}
65
+ interface SheetContentProps extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Popup> {}
36
66
 
37
67
  const SheetContent = React.forwardRef<
38
- React.ComponentRef<typeof SheetPrimitive.Content>,
68
+ React.ComponentRef<typeof SheetPrimitive.Popup>,
39
69
  SheetContentProps
40
70
  >(({ className, children, ...props }, ref) => (
41
71
  <SheetPortal>
42
72
  <SheetOverlay />
43
- <SheetPrimitive.Content ref={ref} className={cn(sheetClassName, className)} {...props}>
44
- <SheetPrimitive.Close className="sheet-glass-control text-secondary hover:bg-inset/70 hover:text-primary focus-visible:outline-ring absolute top-3 right-3 inline-flex size-11 items-center justify-center rounded-md border border-transparent transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none md:top-4 md:right-4">
73
+ <SheetPrimitive.Popup ref={ref} className={cn(sheetClassName, className)} {...props}>
74
+ <SheetPrimitive.Close className="text-muted-foreground hover:bg-muted hover:text-muted-foreground focus-visible:outline-ring absolute top-3 right-3 inline-flex size-9 items-center justify-center rounded-md border border-transparent transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none md:top-4 md:right-4">
45
75
  <X className="size-4.5" />
46
76
  <span className="sr-only">Close</span>
47
77
  </SheetPrimitive.Close>
48
78
  {children}
49
- </SheetPrimitive.Content>
79
+ </SheetPrimitive.Popup>
50
80
  </SheetPortal>
51
81
  ));
52
- SheetContent.displayName = SheetPrimitive.Content.displayName;
82
+ SheetContent.displayName = "SheetContent";
53
83
 
54
84
  const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
55
85
  <div className={cn("flex flex-col gap-2 text-left", className)} {...props} />
@@ -68,7 +98,11 @@ const SheetTitle = React.forwardRef<
68
98
  React.ComponentRef<typeof SheetPrimitive.Title>,
69
99
  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
70
100
  >(({ className, ...props }, ref) => (
71
- <SheetPrimitive.Title ref={ref} className={cn("heading-2 text-primary", className)} {...props} />
101
+ <SheetPrimitive.Title
102
+ ref={ref}
103
+ className={cn("text-lg font-semibold tracking-tight", className)}
104
+ {...props}
105
+ />
72
106
  ));
73
107
  SheetTitle.displayName = SheetPrimitive.Title.displayName;
74
108
 
@@ -78,7 +112,7 @@ const SheetDescription = React.forwardRef<
78
112
  >(({ className, ...props }, ref) => (
79
113
  <SheetPrimitive.Description
80
114
  ref={ref}
81
- className={cn("body-2 text-secondary max-w-prose", className)}
115
+ className={cn("text-muted-foreground max-w-prose text-sm", className)}
82
116
  {...props}
83
117
  />
84
118
  ));
@@ -1,4 +1,4 @@
1
- import * as AvatarPrimitive from "@radix-ui/react-avatar";
1
+ import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
2
2
  import type { ClassValue } from "clsx";
3
3
  import * as React from "react";
4
4
 
@@ -46,7 +46,11 @@ function Avatar({
46
46
  <AvatarContext.Provider value={{ size }}>
47
47
  <AvatarPrimitive.Root
48
48
  data-slot="avatar"
49
- className={cn("relative shrink-0 rounded-full select-none", avatarSize[size], className)}
49
+ className={cn(
50
+ "relative inline-flex shrink-0 rounded-full select-none",
51
+ avatarSize[size],
52
+ className,
53
+ )}
50
54
  {...props}
51
55
  >
52
56
  {children}
@@ -54,7 +58,7 @@ function Avatar({
54
58
  <span
55
59
  data-slot="avatar-badge"
56
60
  className={cn(
57
- "bg-accent ring-background absolute right-0 bottom-0 z-10 rounded-full ring-2",
61
+ "bg-primary ring-background absolute right-0 bottom-0 z-10 rounded-full ring-2",
58
62
  badgeDotSize[size],
59
63
  )}
60
64
  />
@@ -84,7 +88,7 @@ function AvatarFallback({
84
88
  <AvatarPrimitive.Fallback
85
89
  data-slot="avatar-fallback"
86
90
  className={cn(
87
- "bg-inset text-secondary absolute inset-0 flex items-center justify-center rounded-full",
91
+ "bg-muted text-muted-foreground absolute inset-0 flex items-center justify-center rounded-full",
88
92
  fallbackText[size],
89
93
  className,
90
94
  )}
@@ -119,7 +123,7 @@ function AvatarGroupCount({ className, ...props }: React.ComponentProps<"div">)
119
123
  <div
120
124
  data-slot="avatar-group-count"
121
125
  className={cn(
122
- "bg-inset text-secondary ring-background relative flex shrink-0 items-center justify-center rounded-full text-sm ring-2",
126
+ "bg-muted text-muted-foreground ring-background relative flex shrink-0 items-center justify-center rounded-full text-sm ring-2",
123
127
  avatarSize[size],
124
128
  groupCountSvg[size],
125
129
  className,
@@ -1,20 +1,22 @@
1
- import { Slot } from "@radix-ui/react-slot";
2
1
  import { cva, type VariantProps } from "class-variance-authority";
3
2
  import * as React from "react";
4
3
 
5
4
  import { cn } from "../../lib/utils";
6
5
 
7
6
  const badgeVariants = cva(
8
- "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded border border-transparent px-1.5 py-px text-xs font-medium [&>svg]:size-3 [&>svg]:pointer-events-none aria-invalid:border-destructive aria-invalid:ring-destructive/20",
7
+ "state-settle inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded border border-transparent px-1.5 py-px text-xs font-medium [&>svg]:size-3 [&>svg]:pointer-events-none aria-invalid:border-destructive aria-invalid:ring-destructive/20",
9
8
  {
10
9
  variants: {
11
10
  variant: {
12
- default: "bg-accent text-on-accent [a&]:[a&]: [a&]: [a&]:",
13
- secondary: "bg-panel text-primary [a&]:[a&]: [a&]: [a&]:",
14
- destructive: "bg-destructive text-on-destructive [a&]:[a&]: [a&]: [a&]:",
15
- outline: "border-default text-primary [a&]:[a&]: [a&]: [a&]:",
11
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
12
+ secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/80",
13
+ destructive: "bg-destructive text-destructive-foreground [a&]:hover:bg-destructive/90",
14
+ outline: "border-border text-foreground [a&]:hover:bg-muted",
16
15
  ghost: "[a&]:[a&]: [a&]: [a&]:",
17
- link: "text-primary underline-offset-4 [a&]:[a&]:hover:underline [a&]: [a&]: [a&]:",
16
+ link: "text-foreground underline-offset-4 [a&]:hover:underline",
17
+ /** Quiet metadata marker for real reference IDs (case/appointment/feed
18
+ * numbers) — never for decorative sequence markers. */
19
+ ref: "ref-tag border-transparent bg-transparent px-0 py-0 font-normal",
18
20
  },
19
21
  },
20
22
  defaultVariants: {
@@ -26,13 +28,10 @@ const badgeVariants = cva(
26
28
  function Badge({
27
29
  className,
28
30
  variant = "default",
29
- asChild = false,
30
31
  ...props
31
- }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
32
- const Comp = asChild ? Slot : "span";
33
-
32
+ }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
34
33
  return (
35
- <Comp
34
+ <span
36
35
  data-slot="badge"
37
36
  data-variant={variant}
38
37
  className={cn(badgeVariants({ variant }), className)}
@@ -1,4 +1,4 @@
1
- import { Slot } from "@radix-ui/react-slot";
1
+ import { Button as BaseButton } from "@base-ui/react/button";
2
2
  import { cva, type VariantProps } from "class-variance-authority";
3
3
  import * as React from "react";
4
4
 
@@ -6,28 +6,22 @@ import { cn } from "../../lib/utils";
6
6
  import { Spinner } from "../feedback/spinner";
7
7
 
8
8
  const buttonVariants = cva(
9
- "inline-flex min-h-11 !cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-md border px-3 py-1.5 text-sm font-medium leading-5 transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:!cursor-default [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md border border-transparent font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
10
10
  {
11
11
  variants: {
12
12
  variant: {
13
- default:
14
- "border-transparent bg-accent text-on-accent hover:bg-accent-hover active:bg-accent-pressed disabled:border-subtle disabled:bg-panel disabled:text-disabled",
15
- destructive:
16
- "border-transparent bg-destructive text-on-destructive hover:bg-destructive-hover active:bg-destructive-pressed disabled:border-subtle disabled:bg-panel disabled:text-disabled",
17
- outline:
18
- "border-default bg-transparent text-primary hover:bg-surface-hover active:bg-surface-pressed disabled:border-subtle disabled:text-disabled",
19
- secondary:
20
- "border-default bg-panel text-primary hover:bg-surface-hover active:bg-surface-pressed disabled:border-subtle disabled:bg-panel disabled:text-disabled",
21
- ghost:
22
- "border-transparent bg-transparent text-secondary hover:bg-surface-hover hover:text-primary active:bg-surface-pressed disabled:text-disabled",
23
- link: "border-transparent bg-transparent px-0 text-accent hover:text-accent-hover disabled:text-disabled underline-offset-4 hover:underline",
13
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
14
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline: "border-input bg-background hover:bg-muted hover:text-muted-foreground",
16
+ secondary: "border-input bg-secondary text-secondary-foreground hover:bg-secondary/80",
17
+ ghost: "border-transparent bg-transparent hover:bg-muted hover:text-muted-foreground",
18
+ link: "border-transparent bg-transparent px-0 text-primary underline-offset-4 hover:underline",
24
19
  },
25
20
  size: {
26
- sm: "min-h-11 px-2.5 text-sm",
27
- md: "min-h-11 px-3 text-sm",
28
- lg: "min-h-12 px-4 text-base",
29
- default: "",
30
- icon: "size-11 rounded-md p-0",
21
+ sm: "min-h-8 px-2.5 text-xs",
22
+ md: "min-h-9 px-3 text-sm",
23
+ lg: "min-h-10 px-4 text-base",
24
+ icon: "size-9 rounded-md p-0",
31
25
  },
32
26
  },
33
27
  defaultVariants: {
@@ -60,9 +54,40 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
60
54
  },
61
55
  ref,
62
56
  ) => {
63
- const Comp = asChild ? Slot : "button";
57
+ if (asChild && React.isValidElement(children)) {
58
+ const child = children as React.ReactElement<{ children?: React.ReactNode }>;
59
+ const renderedChild = React.cloneElement(child, {
60
+ children: isLoading ? (
61
+ <>
62
+ <span className="opacity-0" aria-hidden="true">
63
+ {child.props.children}
64
+ </span>
65
+ <span className="absolute inset-0 inline-flex items-center justify-center">
66
+ <Spinner size="sm" aria-hidden="true" />
67
+ </span>
68
+ <span className="sr-only">{loadingLabel}</span>
69
+ </>
70
+ ) : (
71
+ child.props.children
72
+ ),
73
+ });
74
+
75
+ return (
76
+ <BaseButton
77
+ {...props}
78
+ render={renderedChild}
79
+ nativeButton={false}
80
+ className={cn(buttonVariants({ variant, size, className }), isLoading && "relative")}
81
+ disabled={disabled || isLoading}
82
+ aria-busy={isLoading}
83
+ aria-label={isLoading ? loadingLabel : ariaLabel}
84
+ ref={ref}
85
+ />
86
+ );
87
+ }
64
88
  return (
65
- <Comp
89
+ <button
90
+ {...props}
66
91
  className={cn(buttonVariants({ variant, size, className }), isLoading && "relative")}
67
92
  style={{
68
93
  ...props.style,
@@ -73,11 +98,12 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
73
98
  disabled={disabled || isLoading}
74
99
  aria-busy={isLoading}
75
100
  aria-label={isLoading ? loadingLabel : ariaLabel}
76
- {...props}
77
101
  >
78
102
  {isLoading ? (
79
103
  <>
80
- <span className="opacity-0">{children}</span>
104
+ <span className="opacity-0" aria-hidden="true">
105
+ {children}
106
+ </span>
81
107
  <span className="absolute inset-0 inline-flex items-center justify-center">
82
108
  <Spinner size="sm" aria-hidden="true" />
83
109
  </span>
@@ -86,7 +112,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
86
112
  ) : (
87
113
  children
88
114
  )}
89
- </Comp>
115
+ </button>
90
116
  );
91
117
  },
92
118
  );
@@ -21,7 +21,7 @@ export const Default: Story = {
21
21
  <CardDescription>A short description of the card's content.</CardDescription>
22
22
  </CardHeader>
23
23
  <CardContent>
24
- <p className="body-2 text-secondary">
24
+ <p className="text-muted-foreground text-sm">
25
25
  Card body content goes here. It can be any React node.
26
26
  </p>
27
27
  </CardContent>
@@ -47,7 +47,7 @@ export const Minimal: Story = {
47
47
  render: () => (
48
48
  <Card className="w-80">
49
49
  <CardContent>
50
- <p className="body-2 text-secondary">Minimal card with only content.</p>
50
+ <p className="text-muted-foreground text-sm">Minimal card with only content.</p>
51
51
  </CardContent>
52
52
  </Card>
53
53
  ),
@@ -6,10 +6,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
6
  return (
7
7
  <div
8
8
  data-slot="card"
9
- className={cn(
10
- "bg-panel text-primary border-default flex flex-col rounded-md border",
11
- className,
12
- )}
9
+ className={cn("bg-card text-card-foreground flex flex-col rounded-xl border", className)}
13
10
  {...props}
14
11
  />
15
12
  );
@@ -36,7 +33,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
36
33
  return (
37
34
  <div
38
35
  data-slot="card-description"
39
- className={cn("body-4 text-secondary", className)}
36
+ className={cn("text-muted-foreground text-sm", className)}
40
37
  {...props}
41
38
  />
42
39
  );
@@ -11,3 +11,5 @@ export {
11
11
  CardTitle,
12
12
  } from "./card";
13
13
  export { Label } from "./label";
14
+ export { StatusBadge } from "./status-badge";
15
+ export type { StatusBadgeConfig, StatusBadgeProps } from "./status-badge";
@@ -38,7 +38,7 @@ export const Disabled: Story = {
38
38
  (locked)
39
39
  </Label>
40
40
  </div>
41
- <p className="text-secondary text-xs">
41
+ <p className="text-muted-foreground text-xs">
42
42
  The <code className="font-mono">peer-disabled</code> variant mutes the label when its paired
43
43
  input is disabled.
44
44
  </p>
@@ -1,21 +1,20 @@
1
1
  "use client";
2
2
 
3
- import * as LabelPrimitive from "@radix-ui/react-label";
4
3
  import { cva, type VariantProps } from "class-variance-authority";
5
4
  import * as React from "react";
6
5
 
7
6
  import { cn } from "../../lib/utils";
8
7
 
9
8
  const labelVariants = cva(
10
- "text-sm font-medium leading-none text-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
9
+ "text-sm font-medium leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
11
10
  );
12
11
 
13
12
  const Label = React.forwardRef<
14
- React.ComponentRef<typeof LabelPrimitive.Root>,
15
- React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
13
+ HTMLLabelElement,
14
+ React.LabelHTMLAttributes<HTMLLabelElement> & VariantProps<typeof labelVariants>
16
15
  >(({ className, ...props }, ref) => (
17
- <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
16
+ <label ref={ref} className={cn(labelVariants(), className)} {...props} />
18
17
  ));
19
- Label.displayName = LabelPrimitive.Root.displayName;
18
+ Label.displayName = "Label";
20
19
 
21
20
  export { Label };
@@ -0,0 +1,52 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { CheckCircle, Clock, XCircle } from "lucide-react";
3
+
4
+ import { StatusBadge, type StatusBadgeConfig } from "./status-badge";
5
+
6
+ type ConnectionStatus = "active" | "error" | "pending" | "revoked";
7
+
8
+ const connectionStatusConfig: Record<ConnectionStatus, StatusBadgeConfig> = {
9
+ active: { label: "Active", variant: "default", icon: <CheckCircle className="mr-1 size-3" /> },
10
+ error: { label: "Error", variant: "destructive", icon: <XCircle className="mr-1 size-3" /> },
11
+ pending: { label: "Pending", variant: "secondary", icon: <Clock className="mr-1 size-3" /> },
12
+ revoked: { label: "Revoked", variant: "outline" },
13
+ };
14
+
15
+ const meta = {
16
+ title: "Primitives/StatusBadge",
17
+ component: StatusBadge<ConnectionStatus>,
18
+ tags: ["autodocs"],
19
+ } satisfies Meta<typeof StatusBadge<ConnectionStatus>>;
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof meta>;
23
+
24
+ export const Default: Story = {
25
+ args: {
26
+ status: "active",
27
+ config: connectionStatusConfig,
28
+ },
29
+ };
30
+
31
+ export const AllStatuses: Story = {
32
+ args: {
33
+ status: "active",
34
+ config: connectionStatusConfig,
35
+ },
36
+ render: () => (
37
+ <div className="flex flex-wrap gap-2">
38
+ <StatusBadge status="active" config={connectionStatusConfig} />
39
+ <StatusBadge status="error" config={connectionStatusConfig} />
40
+ <StatusBadge status="pending" config={connectionStatusConfig} />
41
+ <StatusBadge status="revoked" config={connectionStatusConfig} />
42
+ </div>
43
+ ),
44
+ };
45
+
46
+ export const WithFallback: Story = {
47
+ args: {
48
+ status: null,
49
+ config: connectionStatusConfig,
50
+ fallback: { label: "Unknown", variant: "outline" },
51
+ },
52
+ };
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ import { Badge, type badgeVariants } from "./badge";
4
+ import type { VariantProps } from "class-variance-authority";
5
+
6
+ export interface StatusBadgeConfig {
7
+ label: string;
8
+ variant?: VariantProps<typeof badgeVariants>["variant"];
9
+ className?: string;
10
+ icon?: ReactNode;
11
+ }
12
+
13
+ export interface StatusBadgeProps<T extends string> {
14
+ status: T | null | undefined;
15
+ config: Record<T, StatusBadgeConfig>;
16
+ fallback?: StatusBadgeConfig;
17
+ }
18
+
19
+ /** Renders a `Badge` from a status → { label, variant, icon } config map. */
20
+ export function StatusBadge<T extends string>({ status, config, fallback }: StatusBadgeProps<T>) {
21
+ const entry = (status != null ? config[status] : undefined) ?? fallback;
22
+
23
+ if (!entry) {
24
+ return null;
25
+ }
26
+
27
+ return (
28
+ <Badge variant={entry.variant ?? "outline"} className={entry.className}>
29
+ {entry.icon}
30
+ {entry.label}
31
+ </Badge>
32
+ );
33
+ }
@@ -14,16 +14,16 @@ export const CHART_COLORS = {
14
14
  negative: "var(--color-chart-negative)",
15
15
  neutral: "var(--color-chart-neutral)",
16
16
 
17
- background: "var(--color-surface-canvas)",
18
- grid: "var(--color-border-subtle)",
17
+ background: "var(--color-background)",
18
+ grid: "var(--color-border)",
19
19
 
20
- axis: "var(--color-text-tertiary)",
21
- label: "var(--color-text-secondary)",
20
+ axis: "var(--color-muted-foreground)",
21
+ label: "var(--color-muted-foreground)",
22
22
 
23
23
  tooltip: {
24
- background: "var(--color-surface-panel)",
25
- text: "var(--color-text-primary)",
26
- border: "var(--color-border-default)",
24
+ background: "var(--color-card)",
25
+ text: "var(--color-foreground)",
26
+ border: "var(--color-border)",
27
27
  },
28
28
  } as const;
29
29
 
@@ -3,4 +3,5 @@ export { useDebouncedValue } from "./use-debounced-value";
3
3
  export { useFilterState } from "./use-filter-state";
4
4
  export { useMediaQuery } from "./use-media-query";
5
5
  export { useIsMobile } from "./use-mobile";
6
+ export { useSort } from "./use-sort";
6
7
  export type { SortField, SortDirection, SortOption } from "./sort.types";
@@ -0,0 +1,20 @@
1
+ import { useState } from "react";
2
+
3
+ import type { SortOption } from "./sort.types";
4
+
5
+ interface UseSortOptions {
6
+ initialSortOptions?: SortOption[];
7
+ }
8
+
9
+ export function useSort(options: UseSortOptions = {}) {
10
+ const [sortOptions, setSortOptions] = useState<SortOption[]>(options.initialSortOptions ?? []);
11
+
12
+ return {
13
+ sortOptions,
14
+ addSortOption: (opt: SortOption) => setSortOptions((prev) => [...prev, opt]),
15
+ updateSortOption: (index: number, opt: SortOption) =>
16
+ setSortOptions((prev) => prev.map((o, i) => (i === index ? opt : o))),
17
+ removeSortOption: (index: number) =>
18
+ setSortOptions((prev) => prev.filter((_, i) => i !== index)),
19
+ };
20
+ }
package/src/index.ts CHANGED
@@ -10,16 +10,35 @@ export { copyToClipboard } from "./lib/clipboard";
10
10
  export { createMemoryStorage } from "./lib/create-memory-storage";
11
11
  export { cn } from "./lib/utils";
12
12
  export {
13
- COLOR_MODE_ATTRIBUTE,
14
- COLOR_SYSTEM_ATTRIBUTE,
13
+ animations,
15
14
  colors,
16
- colorSystems,
15
+ colorThemes,
17
16
  colorTokenNames,
18
- } from "./tokens/colors";
19
- export type { ColorMode, ColorSystem, ColorToken } from "./tokens/colors";
20
- export { durations, easingWeb, translateDistances } from "./tokens/motion";
21
- export { radii } from "./tokens/radii";
22
- export type { RadiusToken } from "./tokens/radii";
23
- export { shadowsNative, shadowsWeb } from "./tokens/shadows";
24
- export { contentWidths, spacing } from "./tokens/spacing";
25
- export type { ContentWidthToken, SpacingToken } from "./tokens/spacing";
17
+ containers,
18
+ easing,
19
+ fontFamilies,
20
+ fontFamiliesNative,
21
+ fontWeights,
22
+ leading,
23
+ radii,
24
+ shadows,
25
+ spacing,
26
+ textLineHeights,
27
+ textSizes,
28
+ tracking,
29
+ transitionDurations,
30
+ breakpoints,
31
+ zIndices,
32
+ } from "./tokens/index";
33
+ export type {
34
+ ColorMode,
35
+ ColorTheme,
36
+ ColorToken,
37
+ FontWeightToken,
38
+ LeadingToken,
39
+ RadiusToken,
40
+ ShadowToken,
41
+ SpacingToken,
42
+ TextSizeToken,
43
+ TrackingToken,
44
+ } from "./tokens/index";