@vvedantb/ui 0.2.0 → 0.2.1

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 (59) hide show
  1. package/README.md +26 -1
  2. package/app/README.md +100 -0
  3. package/app/src/confirm/ConfirmSkipHint.tsx +1 -1
  4. package/app/src/settings/SettingsEmptyState.tsx +2 -2
  5. package/app/src/settings/SettingsField.tsx +1 -1
  6. package/app/src/settings/SettingsSection.tsx +1 -1
  7. package/app/src/settings/SettingsToggleRow.tsx +1 -1
  8. package/package.json +13 -9
  9. package/shell/src/PillTabs.tsx +1 -1
  10. package/src/ai-elements/artifact.tsx +1 -1
  11. package/src/ai-elements/attachments.tsx +3 -3
  12. package/src/ai-elements/chain-of-thought.tsx +5 -5
  13. package/src/ai-elements/code-block.tsx +2 -2
  14. package/src/ai-elements/conversation.tsx +2 -2
  15. package/src/ai-elements/inline-citation.tsx +6 -6
  16. package/src/ai-elements/message.tsx +2 -2
  17. package/src/ai-elements/prompt-input.tsx +3 -3
  18. package/src/ai-elements/queue.tsx +2 -2
  19. package/src/ai-elements/reasoning.tsx +1 -1
  20. package/src/ai-elements/shimmer.tsx +5 -5
  21. package/src/ai-elements/sources.tsx +2 -2
  22. package/src/ai-elements/speech-input.tsx +1 -1
  23. package/src/ai-elements/suggestion.tsx +1 -1
  24. package/src/ui/_menu-classes.alt.ts +2 -2
  25. package/src/ui/_menu-classes.ts +2 -2
  26. package/src/ui/accordion.alt.tsx +2 -2
  27. package/src/ui/badge.tsx +1 -1
  28. package/src/ui/breadcrumb.tsx +2 -2
  29. package/src/ui/button.tsx +3 -3
  30. package/src/ui/checkbox.tsx +1 -1
  31. package/src/ui/clear-input.alt.tsx +1 -1
  32. package/src/ui/clear-input.tsx +1 -1
  33. package/src/ui/command.tsx +5 -5
  34. package/src/ui/dialog.tsx +1 -1
  35. package/src/ui/input-group.alt.tsx +1 -1
  36. package/src/ui/input-group.tsx +2 -2
  37. package/src/ui/kbd.tsx +1 -1
  38. package/src/ui/labeled-switch-row.tsx +2 -2
  39. package/src/ui/progress.tsx +1 -1
  40. package/src/ui/radix-select.tsx +3 -3
  41. package/src/ui/search-input.tsx +1 -1
  42. package/src/ui/select.tsx +4 -4
  43. package/src/ui/sonner.tsx +5 -5
  44. package/src/ui/spinner.tsx +1 -1
  45. package/src/ui/switch.tsx +2 -2
  46. package/src/ui/table.tsx +2 -2
  47. package/src/ui/time-picker.tsx +3 -3
  48. package/tokens/README.md +98 -0
  49. package/tokens/examples/compat-shadcn-rgb/Fixture.tsx +45 -0
  50. package/tokens/examples/compat-shadcn-rgb/README.md +11 -0
  51. package/tokens/examples/compat-shadcn-rgb/index.html +12 -0
  52. package/tokens/examples/compat-shadcn-rgb/main.tsx +5 -0
  53. package/tokens/examples/compat-shadcn-rgb/package.json +14 -0
  54. package/tokens/examples/compat-shadcn-rgb/styles.css +67 -0
  55. package/tokens/examples/compat-shadcn-rgb/tsconfig.json +14 -0
  56. package/tokens/examples/compat-shadcn-rgb/vite.config.ts +11 -0
  57. package/tokens/src/css/compat-shadcn-rgb.css +75 -0
  58. package/tokens/src/tailwind-preset.mjs +6 -0
  59. package/tokens/src/tokens.css +15 -0
@@ -10,7 +10,7 @@ const Checkbox = React.forwardRef<
10
10
  <CheckboxPrimitive.Root
11
11
  ref={ref}
12
12
  className={cn(
13
- "peer relative h-4 w-4 shrink-0 rounded-[4px] border border-border bg-field-background transition-[background-color,border-color] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-accent data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground before:absolute before:inset-[-12px] before:content-['']",
13
+ "peer relative h-4 w-4 shrink-0 rounded-[4px] border border-border bg-field-background transition-[background-color,border-color] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground before:absolute before:inset-[-12px] before:content-['']",
14
14
  className,
15
15
  )}
16
16
  {...props}
@@ -57,7 +57,7 @@ const ClearInput = React.forwardRef<HTMLInputElement, ClearInputProps>(
57
57
  type="button"
58
58
  tabIndex={hasValue ? 0 : -1}
59
59
  className={cn(
60
- "absolute right-2 top-1/2 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted transition-[opacity,background-color,color,transform] duration-150 ease-out active:duration-100 active:ease-out active:scale-[0.97] hover:bg-surface-tertiary hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring before:absolute before:inset-[-8px] before:content-['']",
60
+ "absolute right-2 top-1/2 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-[opacity,background-color,color,transform] duration-150 ease-out active:duration-100 active:ease-out active:scale-[0.97] hover:bg-surface-tertiary hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring before:absolute before:inset-[-8px] before:content-['']",
61
61
  hasValue ? "opacity-100" : "pointer-events-none opacity-0",
62
62
  )}
63
63
  aria-hidden={!hasValue}
@@ -152,7 +152,7 @@ const ClearInput = React.forwardRef<HTMLInputElement, ClearInputProps>(
152
152
  {value.length > 0 && !clearing ? (
153
153
  <button
154
154
  type="button"
155
- className="t-clear-btn absolute right-2 top-1/2 z-4 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted transition-[background-color,color,transform] duration-100 ease-out active:scale-[0.92] hover:bg-surface-tertiary/50 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring"
155
+ className="t-clear-btn absolute right-2 top-1/2 z-4 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-[background-color,color,transform] duration-100 ease-out active:scale-[0.92] hover:bg-surface-tertiary/50 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring"
156
156
  aria-label={clearLabel}
157
157
  onPointerDown={preventFocusSteal}
158
158
  onMouseDown={preventFocusSteal}
@@ -28,7 +28,7 @@ function CommandDialog({
28
28
  <DialogContent className="overflow-hidden p-0 sm:max-w-xl">
29
29
  {/* The list is pinned rather than `max-h`: a palette that resizes on
30
30
  every keystroke shifts the whole dialog under the cursor. */}
31
- <Command className="[&_[cmdk-list]]:h-80 [&_[cmdk-list]]:max-h-[60dvh] [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-widest [&_[cmdk-group-heading]]:text-muted [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-1.5 [&_[cmdk-input-wrapper]_svg]:h-4 [&_[cmdk-input-wrapper]_svg]:w-4 [&_[cmdk-input]]:h-11 [&_[cmdk-item]]:rounded-md [&_[cmdk-item]]:px-2.5 [&_[cmdk-item]]:py-2 [&_[cmdk-item]_svg]:h-4 [&_[cmdk-item]_svg]:w-4">
31
+ <Command className="[&_[cmdk-list]]:h-80 [&_[cmdk-list]]:max-h-[60dvh] [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-widest [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-1.5 [&_[cmdk-input-wrapper]_svg]:h-4 [&_[cmdk-input-wrapper]_svg]:w-4 [&_[cmdk-input]]:h-11 [&_[cmdk-item]]:rounded-md [&_[cmdk-item]]:px-2.5 [&_[cmdk-item]]:py-2 [&_[cmdk-item]_svg]:h-4 [&_[cmdk-item]_svg]:w-4">
32
32
  {children}
33
33
  </Command>
34
34
  </DialogContent>
@@ -48,7 +48,7 @@ const CommandInput = React.forwardRef<
48
48
  <CommandPrimitive.Input
49
49
  ref={ref}
50
50
  className={cn(
51
- "flex h-11 w-full max-sm:min-w-0 bg-transparent text-base sm:text-sm outline-none placeholder:text-muted disabled:cursor-not-allowed disabled:opacity-50",
51
+ "flex h-11 w-full max-sm:min-w-0 bg-transparent text-base sm:text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
52
52
  className,
53
53
  )}
54
54
  {...props}
@@ -75,7 +75,7 @@ const CommandEmpty = React.forwardRef<
75
75
  >(({ className, ...props }, ref) => (
76
76
  <CommandPrimitive.Empty
77
77
  ref={ref}
78
- className={cn("py-8 text-center text-sm text-muted", className)}
78
+ className={cn("py-8 text-center text-sm text-muted-foreground", className)}
79
79
  {...props}
80
80
  />
81
81
  ));
@@ -88,7 +88,7 @@ const CommandGroup = React.forwardRef<
88
88
  <CommandPrimitive.Group
89
89
  ref={ref}
90
90
  className={cn(
91
- "overflow-hidden p-1.5 text-foreground [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-widest [&_[cmdk-group-heading]]:text-muted",
91
+ "overflow-hidden p-1.5 text-foreground [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-widest [&_[cmdk-group-heading]]:text-muted-foreground",
92
92
  className,
93
93
  )}
94
94
  {...props}
@@ -129,7 +129,7 @@ function CommandShortcut({
129
129
  }: React.HTMLAttributes<HTMLSpanElement>) {
130
130
  return (
131
131
  <span
132
- className={cn("ml-auto text-xs tracking-wide text-muted/90", className)}
132
+ className={cn("ml-auto text-xs tracking-wide text-muted-foreground/90", className)}
133
133
  {...props}
134
134
  />
135
135
  );
package/src/ui/dialog.tsx CHANGED
@@ -251,7 +251,7 @@ const DialogDescription = React.forwardRef<
251
251
  ref={ref}
252
252
  className={cn(
253
253
  // `--muted` is ~3.5:1 on overlay; mix toward overlay-foreground for 4.5:1.
254
- "text-sm leading-relaxed text-[color-mix(in_oklch,var(--overlay-foreground)_72%,var(--overlay))]",
254
+ "text-sm leading-relaxed text-[color-mix(in_oklch,var(--color-popover-foreground,var(--overlay-foreground))_72%,var(--color-popover,var(--overlay)))]",
255
255
  className,
256
256
  )}
257
257
  {...props}
@@ -102,7 +102,7 @@ function InputGroupText({
102
102
  return (
103
103
  <span
104
104
  className={cn(
105
- "flex items-center text-sm text-muted/95",
105
+ "flex items-center text-sm text-muted-foreground/95",
106
106
  className,
107
107
  )}
108
108
  {...props}
@@ -30,7 +30,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
30
30
  }
31
31
 
32
32
  const inputGroupAddonVariants = cva(
33
- "text-muted flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-md group-data-[disabled=true]/input-group:opacity-50",
33
+ "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-md group-data-[disabled=true]/input-group:opacity-50",
34
34
  {
35
35
  variants: {
36
36
  align: {
@@ -112,7 +112,7 @@ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
112
112
  return (
113
113
  <span
114
114
  className={cn(
115
- "text-muted flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
115
+ "text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
116
116
  className,
117
117
  )}
118
118
  {...props}
package/src/ui/kbd.tsx CHANGED
@@ -6,7 +6,7 @@ const Kbd = React.forwardRef<HTMLElement, React.HTMLAttributes<HTMLElement>>(
6
6
  <kbd
7
7
  ref={ref}
8
8
  className={cn(
9
- "pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded-md bg-surface-secondary px-1.5 font-mono text-[10px] font-medium text-muted",
9
+ "pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded-md bg-surface-secondary px-1.5 font-mono text-[10px] font-medium text-muted-foreground",
10
10
  className,
11
11
  )}
12
12
  {...props}
@@ -27,14 +27,14 @@ export function LabeledSwitchRow({
27
27
  <div className="flex items-center justify-between gap-3 max-sm:flex-wrap">
28
28
  <div className="flex min-w-0 items-start gap-2">
29
29
  {icon ? (
30
- <span className="mt-0.5 shrink-0 text-muted">{icon}</span>
30
+ <span className="mt-0.5 shrink-0 text-muted-foreground">{icon}</span>
31
31
  ) : null}
32
32
  <div className="min-w-0">
33
33
  <Label htmlFor={id} className="text-sm font-medium">
34
34
  {label}
35
35
  </Label>
36
36
  {description ? (
37
- <p className="mt-1 text-xs text-muted text-pretty">{description}</p>
37
+ <p className="mt-1 text-xs text-muted-foreground text-pretty">{description}</p>
38
38
  ) : null}
39
39
  </div>
40
40
  </div>
@@ -15,7 +15,7 @@ const Progress = React.forwardRef<
15
15
  {...props}
16
16
  >
17
17
  <ProgressPrimitive.Indicator
18
- className="h-full w-full flex-1 rounded-full bg-accent transition-transform duration-300 ease-smooth"
18
+ className="h-full w-full flex-1 rounded-full bg-primary transition-transform duration-300 ease-smooth"
19
19
  style={{ transform: `translateX(-${100 - (value ?? 0)}%)` }}
20
20
  />
21
21
  </ProgressPrimitive.Root>
@@ -40,7 +40,7 @@ function SelectTrigger({
40
40
  data-slot="select-trigger"
41
41
  data-size={size}
42
42
  className={cn(
43
- "flex w-fit items-center justify-between gap-2 rounded-field border border-border bg-field-background px-3.5 py-2 text-sm whitespace-nowrap ring-offset-background transition-[border-color,box-shadow,background-color,transform] duration-200 ease-smooth active:duration-100 active:ease-out active:scale-[0.98] placeholder:text-field-placeholder focus-visible:border-focus-border focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-danger data-placeholder:text-muted [&_svg:not([class*='text-'])]:text-muted data-[size=default]:h-10 data-[size=sm]:h-8 data-[slot=select-value]:*:line-clamp-1 data-[slot=select-value]:*:flex data-[slot=select-value]:*:items-center data-[slot=select-value]:*:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
43
+ "flex w-fit items-center justify-between gap-2 rounded-field border border-border bg-field-background px-3.5 py-2 text-sm whitespace-nowrap ring-offset-background transition-[border-color,box-shadow,background-color,transform] duration-200 ease-smooth active:duration-100 active:ease-out active:scale-[0.98] placeholder:text-field-placeholder focus-visible:border-focus-border focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-danger data-placeholder:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground data-[size=default]:h-10 data-[size=sm]:h-8 data-[slot=select-value]:*:line-clamp-1 data-[slot=select-value]:*:flex data-[slot=select-value]:*:items-center data-[slot=select-value]:*:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
44
44
  className,
45
45
  )}
46
46
  {...props}
@@ -100,7 +100,7 @@ function SelectLabel({
100
100
  <SelectPrimitive.Label
101
101
  data-slot="select-label"
102
102
  className={cn(
103
- "text-muted px-2.5 py-1.5 text-xs font-semibold uppercase tracking-widest",
103
+ "text-muted-foreground px-2.5 py-1.5 text-xs font-semibold uppercase tracking-widest",
104
104
  className,
105
105
  )}
106
106
  {...props}
@@ -117,7 +117,7 @@ function SelectItem({
117
117
  <SelectPrimitive.Item
118
118
  data-slot="select-item"
119
119
  className={cn(
120
- "[&_svg:not([class*='text-'])]:text-muted relative flex w-full cursor-pointer select-none items-center gap-2 rounded-lg py-2 pl-2.5 pr-8 text-sm outline-hidden [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 last:[span]:*:flex last:[span]:*:items-center last:[span]:*:gap-2",
120
+ "[&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-pointer select-none items-center gap-2 rounded-lg py-2 pl-2.5 pr-8 text-sm outline-hidden [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 last:[span]:*:flex last:[span]:*:items-center last:[span]:*:gap-2",
121
121
  menuItemClass,
122
122
  className,
123
123
  )}
@@ -16,7 +16,7 @@ const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(
16
16
  leading={
17
17
  <IconSearch
18
18
  size={16}
19
- className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 shrink-0 text-muted"
19
+ className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 shrink-0 text-muted-foreground"
20
20
  />
21
21
  }
22
22
  {...props}
package/src/ui/select.tsx CHANGED
@@ -25,7 +25,7 @@ const SelectTrigger = React.forwardRef<
25
25
  >
26
26
  {children}
27
27
  <SelectPrimitive.Icon asChild>
28
- <IconChevronDown className="h-4 w-4 text-muted/85" />
28
+ <IconChevronDown className="h-4 w-4 text-muted-foreground/85" />
29
29
  </SelectPrimitive.Icon>
30
30
  </SelectPrimitive.Trigger>
31
31
  ));
@@ -38,7 +38,7 @@ const SelectScrollUpButton = React.forwardRef<
38
38
  <SelectPrimitive.ScrollUpButton
39
39
  ref={ref}
40
40
  className={cn(
41
- "flex cursor-default items-center justify-center py-1 text-muted",
41
+ "flex cursor-default items-center justify-center py-1 text-muted-foreground",
42
42
  className,
43
43
  )}
44
44
  {...props}
@@ -55,7 +55,7 @@ const SelectScrollDownButton = React.forwardRef<
55
55
  <SelectPrimitive.ScrollDownButton
56
56
  ref={ref}
57
57
  className={cn(
58
- "flex cursor-default items-center justify-center py-1 text-muted",
58
+ "flex cursor-default items-center justify-center py-1 text-muted-foreground",
59
59
  className,
60
60
  )}
61
61
  {...props}
@@ -107,7 +107,7 @@ const SelectLabel = React.forwardRef<
107
107
  <SelectPrimitive.Label
108
108
  ref={ref}
109
109
  className={cn(
110
- "px-2.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-muted",
110
+ "px-2.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-muted-foreground",
111
111
  className,
112
112
  )}
113
113
  {...props}
package/src/ui/sonner.tsx CHANGED
@@ -25,18 +25,18 @@ function SonnerToaster(props: ToasterProps) {
25
25
  toast:
26
26
  "group toast glass-panel-strong flex w-full items-center gap-3 rounded-lg px-4 py-3 text-sm text-foreground smooth-shadow-ring-md",
27
27
  title: "font-medium leading-snug",
28
- description: "text-[13px] leading-snug text-muted",
28
+ description: "text-[13px] leading-snug text-muted-foreground",
29
29
  actionButton:
30
- "rounded-lg bg-accent px-2.5 py-1 text-xs font-medium text-accent-foreground transition-opacity hover:opacity-90",
30
+ "rounded-lg bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground transition-opacity hover:opacity-90",
31
31
  cancelButton:
32
- "rounded-lg bg-surface-secondary px-2.5 py-1 text-xs font-medium text-muted transition-colors hover:bg-surface-tertiary",
32
+ "rounded-lg bg-surface-secondary px-2.5 py-1 text-xs font-medium text-muted-foreground transition-colors hover:bg-surface-tertiary",
33
33
  closeButton:
34
34
  "rounded-lg bg-default px-2.5 py-1 text-xs font-medium text-foreground transition-colors hover:bg-default/78",
35
- loader: "text-muted",
35
+ loader: "text-muted-foreground",
36
36
  success: "[&_[data-icon]]:text-success",
37
37
  error: "[&_[data-icon]]:text-danger",
38
38
  warning: "[&_[data-icon]]:text-warning",
39
- info: "[&_[data-icon]]:text-accent",
39
+ info: "[&_[data-icon]]:text-primary",
40
40
  },
41
41
  }}
42
42
  {...props}
@@ -18,7 +18,7 @@ function Spinner({
18
18
  <IconLoader2
19
19
  role="status"
20
20
  aria-label="Loading"
21
- className={cn("animate-spin text-muted", sizeClasses[size], className)}
21
+ className={cn("animate-spin text-muted-foreground", sizeClasses[size], className)}
22
22
  {...props}
23
23
  />
24
24
  );
package/src/ui/switch.tsx CHANGED
@@ -8,7 +8,7 @@ const Switch = React.forwardRef<
8
8
  >(({ className, ...props }, ref) => (
9
9
  <SwitchPrimitive.Root
10
10
  className={cn(
11
- "peer relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-border bg-default transition-[background-color,border-color] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-accent data-[state=checked]:bg-accent data-[state=unchecked]:bg-default before:absolute before:inset-[-8px] before:content-['']",
11
+ "peer relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-border bg-default transition-[background-color,border-color] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=unchecked]:bg-default before:absolute before:inset-[-8px] before:content-['']",
12
12
  className,
13
13
  )}
14
14
  {...props}
@@ -16,7 +16,7 @@ const Switch = React.forwardRef<
16
16
  >
17
17
  <SwitchPrimitive.Thumb
18
18
  className={cn(
19
- "pointer-events-none block size-4 rounded-full bg-background ring-0 transition-[transform,background-color] duration-200 ease-smooth data-[state=checked]:translate-x-4 data-[state=checked]:bg-accent-foreground data-[state=unchecked]:translate-x-0",
19
+ "pointer-events-none block size-4 rounded-full bg-background ring-0 transition-[transform,background-color] duration-200 ease-smooth data-[state=checked]:translate-x-4 data-[state=checked]:bg-primary-foreground data-[state=unchecked]:translate-x-0",
20
20
  )}
21
21
  />
22
22
  </SwitchPrimitive.Root>
package/src/ui/table.tsx CHANGED
@@ -76,7 +76,7 @@ const TableHead = React.forwardRef<
76
76
  <th
77
77
  ref={ref}
78
78
  className={cn(
79
- "h-11 px-4 text-left align-middle text-xs font-semibold uppercase tracking-widest text-muted [&:has([role=checkbox])]:pr-0",
79
+ "h-11 px-4 text-left align-middle text-xs font-semibold uppercase tracking-widest text-muted-foreground [&:has([role=checkbox])]:pr-0",
80
80
  className,
81
81
  )}
82
82
  {...props}
@@ -105,7 +105,7 @@ const TableCaption = React.forwardRef<
105
105
  >(({ className, ...props }, ref) => (
106
106
  <caption
107
107
  ref={ref}
108
- className={cn("mt-4 text-sm text-muted", className)}
108
+ className={cn("mt-4 text-sm text-muted-foreground", className)}
109
109
  {...props}
110
110
  />
111
111
  ));
@@ -94,7 +94,7 @@ const TimePicker = React.forwardRef<HTMLButtonElement, TimePickerProps>(
94
94
  )}
95
95
  >
96
96
  <span>{`${hour}:${minute}`}</span>
97
- <IconClock className="h-3.5 w-3.5 text-muted" />
97
+ <IconClock className="h-3.5 w-3.5 text-muted-foreground" />
98
98
  </button>
99
99
  </PopoverTrigger>
100
100
  <PopoverContent
@@ -134,7 +134,7 @@ const TimeColumn = React.forwardRef<HTMLDivElement, TimeColumnProps>(
134
134
  ({ label, values, selected, onSelect }, ref) => {
135
135
  return (
136
136
  <div className="flex w-14 flex-col">
137
- <div className="px-2 pb-1 text-[10px] uppercase tracking-wider text-muted">
137
+ <div className="px-2 pb-1 text-[10px] uppercase tracking-wider text-muted-foreground">
138
138
  {label}
139
139
  </div>
140
140
  <div
@@ -152,7 +152,7 @@ const TimeColumn = React.forwardRef<HTMLDivElement, TimeColumnProps>(
152
152
  className={cn(
153
153
  "flex h-7 w-full items-center justify-center text-xs tabular-nums transition-colors",
154
154
  isSelected
155
- ? "bg-accent text-accent-foreground"
155
+ ? "bg-primary text-primary-foreground"
156
156
  : "text-foreground/80 hover:bg-surface-tertiary/50",
157
157
  )}
158
158
  >
@@ -0,0 +1,98 @@
1
+ # `@vvedantb/ui/tokens`
2
+
3
+ Design tokens and shared Tailwind v4 CSS for the vvedantb web apps, plus JS tokens for React Native. These are subpaths of `@vvedantb/ui`, source-exported: no build step. Paths in the `File` column are relative to `packages/ui/tokens/`.
4
+
5
+ ## Exports
6
+
7
+ | Export | File | What it is |
8
+ | --- | --- | --- |
9
+ | `@vvedantb/ui/tokens`, `@vvedantb/ui/tokens/tokens.css` | `src/tokens.css` | Colour, radius, shadow, ease and font tokens (`@theme`), light/dark variables, `dark` variant, `*` border and `body` defaults. In 0.2.1 it also defines shadcn-style aliases (`primary`, `muted-foreground`, `secondary`, `destructive`, `card`, `popover`, `input`, `ring`) that point at the same tokens. |
10
+ | `@vvedantb/ui/tokens/native` | `src/native.ts` | JS colours, radii and spacing for React Native. `@vvedantb/native-ui` keeps its own copy in `src/tokens.ts`; a test keeps the two equal. |
11
+ | `@vvedantb/ui/tokens/css/base.css` | `src/css/base.css` | `html` touch/accent, `::selection`, control transitions, pointer cursor, range accent (`@layer base`). |
12
+ | `@vvedantb/ui/tokens/css/dialog.css` | `src/css/dialog.css` | Unlayered `max-width: none` for `@vvedantb/ui` compact (bottom-sheet) dialogs. |
13
+ | `@vvedantb/ui/tokens/css/modal.css` | `src/css/modal.css` | `.t-modal`, `.is-open`, `.is-closing` and `t-modal-presence-wait` (`@layer components`). |
14
+ | `@vvedantb/ui/tokens/css/tabs.css` | `src/css/tabs.css` | `.t-tab`, active colour, `.t-tabs-pill`, small-screen `.t-tabs` scrolling. |
15
+ | `@vvedantb/ui/tokens/css/tabs-pills.css` | `src/css/tabs-pills.css` | `--tabs-*` colour variables, `.t-tabs--segmented`, `.t-tabs--pills` and pill hover / active fills. |
16
+ | `@vvedantb/ui/tokens/css/utilities.css` | `src/css/utilities.css` | `@utility hit-target` and `@utility reveal-on-hover`. |
17
+ | `@vvedantb/ui/tokens/css/scrollbar-thin.css` | `src/css/scrollbar-thin.css` | `.scrollbar-thin` (`@layer utilities`, always emitted). |
18
+ | `@vvedantb/ui/tokens/css/animations.css` | `src/css/animations.css` | `animate-aurora`, `animate-fade-in-up`, `animate-indeterminate` as `@theme` (CSS-first apps). |
19
+ | `@vvedantb/ui/tokens/css/compat-shadcn-rgb.css`, `@vvedantb/ui/tokens/compat/shadcn-rgb.css` | `src/css/compat-shadcn-rgb.css` | Replaces `tokens.css` for apps with shadcn-style `R G B` channel variables. Defines only the UI-only `--color-*` names as `rgb(var(--x) / alpha)` references, `--radius-field`, `--scrollbar`, `--field-radius` and a `glass-panel-strong` fallback. Import it **or** `tokens.css`, never both. |
20
+ | `@vvedantb/ui/tokens/css/index.css` | `src/css/index.css` | base + dialog + utilities + scrollbar-thin + animations, for new CSS-first apps. |
21
+ | `@vvedantb/ui/tokens/tailwind-preset` | `src/tailwind-preset.mjs` (+ `.d.mts`) | Tailwind preset for apps that load a JS config with `@config`. |
22
+
23
+ Every file was extracted because the same rule (same at-rule context, selector and body) sits in 3 or more of the five apps. The measured duplication is in `verify/ANALYSIS.md`, and the proof that each app compiles to the same CSS after migrating is in `verify/RESULTS.md`. Blocks that only 2 apps share, or that drift in a way that would change an app's CSS, stay in the apps; `verify/ANALYSIS.md` lists them.
24
+
25
+ ## Use
26
+
27
+ CSS-first app (no `tailwind.config`):
28
+
29
+ ```css
30
+ @import "tailwindcss";
31
+ @import "@vvedantb/ui/tokens";
32
+ @import "@vvedantb/ui/tokens/css/animations.css";
33
+ @import "@vvedantb/ui/tokens/css/base.css";
34
+ /* ...app @layer base rules... */
35
+ @import "@vvedantb/ui/tokens/css/modal.css";
36
+ @import "@vvedantb/ui/tokens/css/tabs.css";
37
+ /* ...app @layer components rules... */
38
+ ```
39
+
40
+ App with a JS config (`@config "../tailwind.config.ts"`):
41
+
42
+ ```ts
43
+ import { tokensPreset } from "@vvedantb/ui/tokens/tailwind-preset";
44
+
45
+ export default {
46
+ presets: [tokensPreset()],
47
+ // or tokensPreset({ color: (name) => `color-mix(in oklch, var(--${name}) calc(<alpha-value> * 100%), transparent)` })
48
+ content: ["./src/**/*.{ts,tsx}"],
49
+ theme: { extend: { /* app-only colours, fonts, animations */ } },
50
+ plugins: [tailwindcssAnimate],
51
+ };
52
+ ```
53
+
54
+ The preset holds `darkMode: "class"`, the shared colour map (`border`, `surface.*`, `danger.*`, `field.*`, shadcn aliases `primary`/`secondary`/`destructive`/`card`/`popover`/`input` ...), `borderRadius` lg/md/sm/field, `boxShadow` soft/panel/insetSoft, `ease-smooth`, `screens` xs to 2xl in px, and the aurora / fade-in-up / indeterminate / accordion keyframes. It does not hold plugins, `content`, fonts, `accent`/`muted`/`ring`/`sidebar` (they differ per app) or accordion timings.
55
+
56
+ Do not import `css/animations.css` in an app that uses the preset: the preset already defines the same keyframes and animations.
57
+
58
+ ### Where to put the imports
59
+
60
+ Tailwind inlines an `@import` where it sits, even in the middle of a file (it must be at top level, not inside an `@layer` block). Put each import where the app's own copy of the rules was. Within one layer, later rules win at equal specificity, so moving shared rules above or below app rules can flip the result for elements matched by both. `examples/migration.md` gives the exact position for each app, and `pnpm --filter @vvedantb/ui verify` checks it.
61
+
62
+ ### Variables each file needs
63
+
64
+ | File | Needs | Provided by |
65
+ | --- | --- | --- |
66
+ | `base.css` | `--accent`, `--foreground` | tokens.css or the app theme |
67
+ | `dialog.css` | none | |
68
+ | `modal.css` | `--modal-open-dur`, `--modal-close-dur`, `--modal-scale`, `--modal-scale-close`, `--modal-ease` | app `:root` |
69
+ | `tabs.css` | `--tabs-dur`, `--tabs-ease` (app `:root`); `--tabs-text-muted`, `--tabs-text-active`, `--tabs-pill-bg` | colours: tabs-pills.css or the app theme |
70
+ | `tabs-pills.css` | `--muted`, `--segment`, `--segment-foreground`, `--default` | tokens.css or the app theme |
71
+ | `utilities.css` | `--breakpoint-sm` (Tailwind default or app theme) | Tailwind |
72
+ | `scrollbar-thin.css` | `--scrollbar` | tokens.css or the app theme |
73
+ | `animations.css` | none | |
74
+ | `tailwind-preset` | the CSS variables named in its colour map, `--radius`, `--field-radius` | tokens.css or the app theme |
75
+
76
+ ### Using an existing shadcn RGB theme
77
+
78
+ See the package `README.md`. With the compat file, `base.css`, `scrollbar-thin.css`, `utilities.css`, `dialog.css`, `modal.css` and `tabs.css` need no `tokens.css`. `base.css` reads `--accent` and `--foreground` and `tabs-pills.css` reads `--muted`, `--segment` and `--default`. Those are not channel-compatible, so skip `tabs-pills.css` and `index.css` (which includes `base.css`) unless the app defines those variables as full colours.
79
+
80
+ ### Parameters
81
+
82
+ Where apps share a rule but drifted in one value, the value is a custom property whose fallback is the most common value. Set it on `:root` to keep a different value.
83
+
84
+ | File | Property | Default | Set by |
85
+ | --- | --- | --- | --- |
86
+ | `base.css` | `--tokens-control-transition-property` | `background-color, border-color, color, box-shadow, transform, opacity` | app-c: `background-color, color, transform, opacity` |
87
+ | `base.css` | `--tokens-control-transition-duration` | `240ms` | app-c: `100ms` |
88
+
89
+ ## Develop
90
+
91
+ ```sh
92
+ pnpm --filter @vvedantb/ui build # export targets exist + tsc (primitives, shell, app)
93
+ pnpm --filter @vvedantb/ui test # vitest (app helpers) + offline node --test: exports, CSS structure, Tailwind compile, preset
94
+ pnpm --filter @vvedantb/ui analyze # rule-level duplication across the app clones -> verify/ANALYSIS.tables.md
95
+ pnpm --filter @vvedantb/ui verify # before/after compile of every app -> verify/RESULTS.md, examples/migration.md
96
+ ```
97
+
98
+ `analyze` and `verify` need the app clones (`APPS_DIR`, default `/workspace/cvx`; name or symlink the clones `app-a` to `app-e` inside it) and network access the first time: they install Tailwind 4.3.3, the plugins, postcss and jiti into a scratch directory (`VERIFY_DIR`, default `/workspace/pk-tokens-verify`, outside the repo). The migration for each app is data in `verify/migrations/<app>.json`. Each removed block must equal the shared file's block with the same context and selector, or the run fails.
@@ -0,0 +1,45 @@
1
+ import { useState } from "react";
2
+ import { toast } from "sonner";
3
+ import {
4
+ Button,
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogTitle,
9
+ DialogTrigger,
10
+ Input,
11
+ SonnerToaster,
12
+ Switch,
13
+ Textarea,
14
+ } from "../../../src";
15
+
16
+ export function Fixture() {
17
+ const [dark, setDark] = useState(false);
18
+ return (
19
+ <div className={dark ? "dark" : ""}>
20
+ <main className="min-h-screen space-y-6 bg-background p-8 text-foreground">
21
+ <section className="flex flex-wrap items-center gap-3">
22
+ <Button onClick={() => setDark((d) => !d)}>Toggle theme</Button>
23
+ <Button onClick={() => toast("Saved", { description: "Fixture toast" })}>
24
+ Show toast
25
+ </Button>
26
+ <Dialog>
27
+ <DialogTrigger asChild>
28
+ <Button>Open dialog</Button>
29
+ </DialogTrigger>
30
+ <DialogContent>
31
+ <DialogTitle>Dialog</DialogTitle>
32
+ <DialogDescription>Body text on the popover colour.</DialogDescription>
33
+ </DialogContent>
34
+ </Dialog>
35
+ </section>
36
+ <section className="grid max-w-md gap-3">
37
+ <Input placeholder="Input placeholder" />
38
+ <Textarea placeholder="Textarea placeholder" />
39
+ <Switch defaultChecked aria-label="Sample switch" />
40
+ </section>
41
+ <SonnerToaster />
42
+ </main>
43
+ </div>
44
+ );
45
+ }
@@ -0,0 +1,11 @@
1
+ # compat-shadcn-rgb fixture
2
+
3
+ Renders Button, toast, Dialog, Input, Textarea and Switch against sample shadcn-style `R G B` variables for light and dark. `styles.css` imports `compat-shadcn-rgb.css` and not `tokens.css`.
4
+
5
+ Run it from this folder (it is not a workspace member, and React resolves from the parent `packages/ui` install):
6
+
7
+ ```sh
8
+ pnpm install --ignore-workspace && pnpm dev
9
+ ```
10
+
11
+ Open the printed URL and use "Toggle theme" to switch between light and dark. `Fixture.tsx` is not part of the package typecheck.
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>compat-shadcn-rgb fixture</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,5 @@
1
+ import { createRoot } from "react-dom/client";
2
+ import { Fixture } from "./Fixture";
3
+ import "./styles.css";
4
+
5
+ createRoot(document.getElementById("root")!).render(<Fixture />);
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "compat-shadcn-rgb-fixture",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build"
8
+ },
9
+ "devDependencies": {
10
+ "@tailwindcss/vite": "4.3.3",
11
+ "tailwindcss": "4.3.3",
12
+ "vite": "^7.0.0"
13
+ }
14
+ }
@@ -0,0 +1,67 @@
1
+ @import "tailwindcss";
2
+ @import "../../src/css/compat-shadcn-rgb.css";
3
+ @source "../../../src/**/*.{ts,tsx}";
4
+ @source "./*.tsx";
5
+
6
+ @custom-variant dark (&:where(.dark, .dark *));
7
+
8
+ /* Sample shadcn-style RGB channel variables owned by the app. */
9
+ :root {
10
+ --background: 244 245 246;
11
+ --foreground: 24 24 27;
12
+ --card: 255 255 255;
13
+ --card-foreground: 24 24 27;
14
+ --popover: 255 255 255;
15
+ --popover-foreground: 24 24 27;
16
+ --primary: 37 99 235;
17
+ --primary-foreground: 255 255 255;
18
+ --secondary: 235 236 238;
19
+ --secondary-foreground: 24 24 27;
20
+ --muted: 235 236 238;
21
+ --muted-foreground: 100 100 110;
22
+ --accent: 235 236 238;
23
+ --accent-foreground: 24 24 27;
24
+ --destructive: 220 38 38;
25
+ --destructive-foreground: 255 255 255;
26
+ --border: 224 225 228;
27
+ --input: 224 225 228;
28
+ --ring: 37 99 235;
29
+ --radius: 0.75rem;
30
+ }
31
+
32
+ .dark {
33
+ --background: 15 15 17;
34
+ --foreground: 244 244 245;
35
+ --card: 28 28 31;
36
+ --card-foreground: 244 244 245;
37
+ --popover: 28 28 31;
38
+ --popover-foreground: 244 244 245;
39
+ --primary: 96 165 250;
40
+ --primary-foreground: 15 15 17;
41
+ --secondary: 39 39 43;
42
+ --secondary-foreground: 244 244 245;
43
+ --muted: 39 39 43;
44
+ --muted-foreground: 161 161 170;
45
+ --accent: 39 39 43;
46
+ --accent-foreground: 244 244 245;
47
+ --destructive: 248 113 113;
48
+ --destructive-foreground: 15 15 17;
49
+ --border: 46 46 51;
50
+ --input: 46 46 51;
51
+ --ring: 96 165 250;
52
+ }
53
+
54
+ @theme inline {
55
+ --color-background: rgb(var(--background));
56
+ --color-foreground: rgb(var(--foreground));
57
+ --color-border: rgb(var(--border));
58
+ --color-primary: rgb(var(--primary));
59
+ --color-primary-foreground: rgb(var(--primary-foreground));
60
+ --color-muted-foreground: rgb(var(--muted-foreground));
61
+ }
62
+
63
+ body {
64
+ background: rgb(var(--background));
65
+ color: rgb(var(--foreground));
66
+ font-family: system-ui, sans-serif;
67
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "skipLibCheck": true,
11
+ "isolatedModules": true
12
+ },
13
+ "include": ["*.ts", "*.tsx"]
14
+ }
@@ -0,0 +1,11 @@
1
+ import tailwindcss from "@tailwindcss/vite";
2
+ import { defineConfig } from "vite";
3
+
4
+ // React, react-dom and the @vvedantb/ui peers resolve from packages/ui/node_modules
5
+ // (this folder sits inside that package), so the fixture installs only the build tools.
6
+ export default defineConfig({
7
+ plugins: [tailwindcss()],
8
+ esbuild: { jsx: "automatic" },
9
+ resolve: { dedupe: ["react", "react-dom"] },
10
+ server: { fs: { allow: ["../../.."] } },
11
+ });