@vvedantb/ui 0.2.0 → 0.2.2

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 (78) hide show
  1. package/README.md +89 -3
  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 +16 -10
  9. package/shell/src/PillTabs.tsx +1 -1
  10. package/shell/src/SessionsHoverCard.tsx +148 -0
  11. package/shell/src/index.ts +5 -0
  12. package/src/ai-elements/artifact.tsx +1 -1
  13. package/src/ai-elements/attachments.tsx +3 -3
  14. package/src/ai-elements/chain-of-thought.tsx +5 -5
  15. package/src/ai-elements/code-block.tsx +2 -2
  16. package/src/ai-elements/conversation.tsx +2 -2
  17. package/src/ai-elements/inline-citation.tsx +6 -6
  18. package/src/ai-elements/message.tsx +2 -2
  19. package/src/ai-elements/prompt-input.tsx +3 -3
  20. package/src/ai-elements/queue.tsx +2 -2
  21. package/src/ai-elements/reasoning.tsx +1 -1
  22. package/src/ai-elements/shimmer.tsx +5 -5
  23. package/src/ai-elements/sources.tsx +2 -2
  24. package/src/ai-elements/speech-input.tsx +1 -1
  25. package/src/ai-elements/suggestion.tsx +1 -1
  26. package/src/audio/index.ts +3 -0
  27. package/src/audio/playChime.ts +76 -0
  28. package/src/audio/useReplyChime.ts +62 -0
  29. package/src/index.ts +2 -0
  30. package/src/ui/_menu-classes.alt.ts +2 -2
  31. package/src/ui/_menu-classes.ts +2 -2
  32. package/src/ui/accordion.alt.tsx +2 -2
  33. package/src/ui/badge.tsx +1 -1
  34. package/src/ui/breadcrumb.tsx +2 -2
  35. package/src/ui/button.tsx +3 -3
  36. package/src/ui/checkbox.tsx +1 -1
  37. package/src/ui/clear-input.alt.tsx +1 -1
  38. package/src/ui/clear-input.tsx +1 -1
  39. package/src/ui/command.tsx +5 -5
  40. package/src/ui/dialog.tsx +1 -1
  41. package/src/ui/input-group.alt.tsx +1 -1
  42. package/src/ui/input-group.tsx +2 -2
  43. package/src/ui/kbd.tsx +1 -1
  44. package/src/ui/labeled-switch-row.tsx +2 -2
  45. package/src/ui/progress.tsx +1 -1
  46. package/src/ui/radix-select.tsx +3 -3
  47. package/src/ui/search-input.tsx +1 -1
  48. package/src/ui/select.tsx +4 -4
  49. package/src/ui/sonner-icons.tsx +94 -0
  50. package/src/ui/sonner.tsx +167 -34
  51. package/src/ui/spinner.tsx +1 -1
  52. package/src/ui/switch.tsx +2 -2
  53. package/src/ui/table.tsx +2 -2
  54. package/src/ui/time-picker.tsx +3 -3
  55. package/tokens/README.md +98 -0
  56. package/tokens/examples/compat-shadcn-rgb/Fixture.tsx +45 -0
  57. package/tokens/examples/compat-shadcn-rgb/README.md +11 -0
  58. package/tokens/examples/compat-shadcn-rgb/index.html +12 -0
  59. package/tokens/examples/compat-shadcn-rgb/main.tsx +5 -0
  60. package/tokens/examples/compat-shadcn-rgb/package.json +14 -0
  61. package/tokens/examples/compat-shadcn-rgb/styles.css +67 -0
  62. package/tokens/examples/compat-shadcn-rgb/tsconfig.json +14 -0
  63. package/tokens/examples/compat-shadcn-rgb/vite.config.ts +11 -0
  64. package/tokens/examples/toaster/Fixture.tsx +53 -0
  65. package/tokens/examples/toaster/README.md +18 -0
  66. package/tokens/examples/toaster/compat.html +12 -0
  67. package/tokens/examples/toaster/main-compat.tsx +5 -0
  68. package/tokens/examples/toaster/main-tokens.tsx +5 -0
  69. package/tokens/examples/toaster/package.json +14 -0
  70. package/tokens/examples/toaster/styles-compat.css +82 -0
  71. package/tokens/examples/toaster/styles-tokens.css +16 -0
  72. package/tokens/examples/toaster/tokens.html +12 -0
  73. package/tokens/examples/toaster/tsconfig.json +14 -0
  74. package/tokens/examples/toaster/vite.config.ts +12 -0
  75. package/tokens/src/css/compat-shadcn-rgb.css +82 -0
  76. package/tokens/src/css/utilities.css +5 -0
  77. package/tokens/src/tailwind-preset.mjs +6 -0
  78. package/tokens/src/tokens.css +15 -0
@@ -774,7 +774,7 @@ export const PromptInputButton = ({
774
774
  <TooltipTrigger asChild>{button}</TooltipTrigger>
775
775
  <TooltipContent side={side}>
776
776
  {tooltipContent}
777
- {shortcut && <span className="ml-2 text-muted">{shortcut}</span>}
777
+ {shortcut && <span className="ml-2 text-muted-foreground">{shortcut}</span>}
778
778
  </TooltipContent>
779
779
  </Tooltip>
780
780
  );
@@ -871,7 +871,7 @@ export const PromptInputModelSelectTrigger = ({
871
871
  }: PromptInputModelSelectTriggerProps) => (
872
872
  <SelectTrigger
873
873
  className={cn(
874
- "border-none bg-transparent font-medium text-muted shadow-none transition-[color,transform] duration-150 ease-out active:scale-[0.97]",
874
+ "border-none bg-transparent font-medium text-muted-foreground shadow-none transition-[color,transform] duration-150 ease-out active:scale-[0.97]",
875
875
  "hover:bg-surface-secondary hover:text-foreground",
876
876
  segmentExpandedStateClass,
877
877
  className,
@@ -943,7 +943,7 @@ export const PromptInputTabLabel = ({
943
943
  ...props
944
944
  }: PromptInputTabLabelProps) => (
945
945
  <h3
946
- className={cn("mb-2 px-3 font-medium text-muted text-xs", className)}
946
+ className={cn("mb-2 px-3 font-medium text-muted-foreground text-xs", className)}
947
947
  {...props}
948
948
  />
949
949
  );
@@ -73,7 +73,7 @@ export function QueueHeader({
73
73
  )}
74
74
  >
75
75
  <div className="flex items-center gap-2">
76
- <IconStack2 size={16} className="text-muted" />
76
+ <IconStack2 size={16} className="text-muted-foreground" />
77
77
  <span className="text-sm font-medium text-foreground">
78
78
  Queued Messages
79
79
  </span>
@@ -84,7 +84,7 @@ export function QueueHeader({
84
84
  <IconChevronDown
85
85
  size={16}
86
86
  className={cn(
87
- "text-muted transition-transform duration-200 ease-out",
87
+ "text-muted-foreground transition-transform duration-200 ease-out",
88
88
  isOpen && "rotate-180",
89
89
  )}
90
90
  />
@@ -137,7 +137,7 @@ export function ReasoningTrigger({
137
137
  return (
138
138
  <CollapsibleTrigger
139
139
  className={cn(
140
- "flex w-full items-center gap-2 py-1 text-sm text-muted transition-[color,transform] duration-150 ease-out active:scale-[0.99] hover:text-foreground",
140
+ "flex w-full items-center gap-2 py-1 text-sm text-muted-foreground transition-[color,transform] duration-150 ease-out active:scale-[0.99] hover:text-foreground",
141
141
  className,
142
142
  )}
143
143
  {...props}
@@ -26,11 +26,11 @@ function Shimmer({
26
26
  "--spread": `${totalSpread}px`,
27
27
  "--bg": `linear-gradient(
28
28
  90deg,
29
- var(--muted) 0%,
30
- var(--foreground) calc(50% - var(--spread)),
31
- var(--muted) 50%,
32
- var(--foreground) calc(50% + var(--spread)),
33
- var(--muted) 100%
29
+ var(--color-muted-foreground, var(--muted)) 0%,
30
+ var(--color-foreground, var(--foreground)) calc(50% - var(--spread)),
31
+ var(--color-muted-foreground, var(--muted)) 50%,
32
+ var(--color-foreground, var(--foreground)) calc(50% + var(--spread)),
33
+ var(--color-muted-foreground, var(--muted)) 100%
34
34
  )`,
35
35
  backgroundImage: "var(--bg)",
36
36
  backgroundSize: "200% 100%",
@@ -28,7 +28,7 @@ export const SourcesTrigger = ({
28
28
  }: SourcesTriggerProps) => (
29
29
  <CollapsibleTrigger
30
30
  className={cn(
31
- "group inline-flex items-center gap-1.5 text-xs text-muted transition-[color,transform] duration-150 ease-out active:scale-[0.97] hover:text-foreground",
31
+ "group inline-flex items-center gap-1.5 text-xs text-muted-foreground transition-[color,transform] duration-150 ease-out active:scale-[0.97] hover:text-foreground",
32
32
  className,
33
33
  )}
34
34
  {...props}
@@ -63,7 +63,7 @@ export const Source = ({
63
63
  }: SourceProps) => (
64
64
  <a
65
65
  className={cn(
66
- "flex items-center justify-between gap-2 rounded-md px-3 py-2 text-xs text-muted transition-[background-color,color,transform] duration-150 ease-out active:scale-[0.99] hover:bg-surface-secondary hover:text-foreground",
66
+ "flex items-center justify-between gap-2 rounded-md px-3 py-2 text-xs text-muted-foreground transition-[background-color,color,transform] duration-150 ease-out active:scale-[0.99] hover:bg-surface-secondary hover:text-foreground",
67
67
  className,
68
68
  )}
69
69
  href={href}
@@ -251,7 +251,7 @@ export const SpeechInput = ({
251
251
  "relative z-10 rounded-full transition-[background-color,transform] duration-200 ease-smooth active:duration-100 active:ease-out",
252
252
  isListening
253
253
  ? "bg-danger text-danger-foreground hover:bg-danger/80 hover:text-danger-foreground"
254
- : "bg-accent text-accent-foreground hover:bg-accent/80 hover:text-accent-foreground",
254
+ : "bg-primary text-primary-foreground hover:bg-primary/80 hover:text-primary-foreground",
255
255
  className,
256
256
  )}
257
257
  disabled={isDisabled}
@@ -176,7 +176,7 @@ export const Suggestion = ({
176
176
  return (
177
177
  <Button
178
178
  className={cn(
179
- "h-auto cursor-pointer rounded-full bg-surface-secondary px-3 py-1.5 text-sm text-muted hover:bg-surface-tertiary hover:text-foreground",
179
+ "h-auto cursor-pointer rounded-full bg-surface-secondary px-3 py-1.5 text-sm text-muted-foreground hover:bg-surface-tertiary hover:text-foreground",
180
180
  className,
181
181
  )}
182
182
  onClick={handleClick}
@@ -0,0 +1,3 @@
1
+ export { playChime } from "./playChime";
2
+ export { useReplyChime, nextReplyChimeState } from "./useReplyChime";
3
+ export type { ReplyChimeInput, ReplyChimeWatch } from "./useReplyChime";
@@ -0,0 +1,76 @@
1
+ type Tone = {
2
+ frequency: number;
3
+ startOffsetS: number;
4
+ durationS: number;
5
+ peakGain: number;
6
+ };
7
+
8
+ /** A soft rising interval (A5 then D6). The second note overlaps the first. */
9
+ const REPLY_CHIME: readonly Tone[] = [
10
+ { frequency: 880, startOffsetS: 0, durationS: 0.28, peakGain: 0.14 },
11
+ { frequency: 1174.66, startOffsetS: 0.09, durationS: 0.28, peakGain: 0.14 },
12
+ ];
13
+
14
+ /** Long enough to avoid a click on attack. */
15
+ const ATTACK_S = 0.012;
16
+
17
+ /** Calls closer together than this are dropped, so overlapping events sound once. */
18
+ const MIN_INTERVAL_S = 1;
19
+
20
+ let sharedContext: AudioContext | null = null;
21
+ let lastPlayedAtS: number | null = null;
22
+
23
+ function getAudioContext(): AudioContext | null {
24
+ if (sharedContext) return sharedContext;
25
+ if (typeof window === "undefined" || !window.AudioContext) return null;
26
+ try {
27
+ sharedContext = new window.AudioContext();
28
+ } catch {
29
+ return null;
30
+ }
31
+ return sharedContext;
32
+ }
33
+
34
+ /**
35
+ * Plays a soft rising two-note chime with WebAudio.
36
+ *
37
+ * The context is created on first use and shared. The call is a no-op on the
38
+ * server, without WebAudio, or while autoplay policy blocks audio.
39
+ */
40
+ export function playChime(): void {
41
+ const context = getAudioContext();
42
+ if (!context) return;
43
+
44
+ if (context.state === "suspended") {
45
+ try {
46
+ context.resume().catch(() => undefined);
47
+ } catch {
48
+ return;
49
+ }
50
+ }
51
+
52
+ const start = context.currentTime;
53
+ if (lastPlayedAtS !== null && start - lastPlayedAtS < MIN_INTERVAL_S) return;
54
+ lastPlayedAtS = start;
55
+
56
+ try {
57
+ for (const tone of REPLY_CHIME) {
58
+ const oscillator = context.createOscillator();
59
+ const envelope = context.createGain();
60
+ oscillator.type = "sine";
61
+ oscillator.frequency.value = tone.frequency;
62
+
63
+ const toneStart = start + tone.startOffsetS;
64
+ const toneEnd = toneStart + tone.durationS;
65
+ envelope.gain.setValueAtTime(0, toneStart);
66
+ envelope.gain.linearRampToValueAtTime(tone.peakGain, toneStart + ATTACK_S);
67
+ envelope.gain.exponentialRampToValueAtTime(0.0001, toneEnd);
68
+
69
+ oscillator.connect(envelope).connect(context.destination);
70
+ oscillator.start(toneStart);
71
+ oscillator.stop(toneEnd);
72
+ }
73
+ } catch {
74
+ // Audio is optional. Stay silent on any failure.
75
+ }
76
+ }
@@ -0,0 +1,62 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { playChime } from "./playChime";
3
+
4
+ export type ReplyChimeWatch = {
5
+ threadId: string;
6
+ wasStreaming: boolean;
7
+ };
8
+
9
+ export type ReplyChimeInput = {
10
+ enabled: boolean;
11
+ isStreaming: boolean;
12
+ threadId: string | null | undefined;
13
+ };
14
+
15
+ /**
16
+ * Pure transition for the reply chime. Returns the state to remember and
17
+ * whether to chime.
18
+ *
19
+ * - No thread id: reset tracking and stay silent.
20
+ * - First observation of a thread (or a new thread): silent.
21
+ * - Chime only on streaming to finished for the same thread, while enabled.
22
+ * - The edge is tracked while disabled, so enabling mid-turn does not replay
23
+ * a finish that already happened.
24
+ */
25
+ export function nextReplyChimeState(
26
+ previous: ReplyChimeWatch | null,
27
+ { enabled, isStreaming, threadId }: ReplyChimeInput,
28
+ ): { next: ReplyChimeWatch | null; chime: boolean } {
29
+ if (threadId == null || threadId === "") {
30
+ return { next: null, chime: false };
31
+ }
32
+ const next = { threadId, wasStreaming: isStreaming };
33
+ if (!previous || previous.threadId !== threadId) {
34
+ return { next, chime: false };
35
+ }
36
+ const finished = previous.wasStreaming && !isStreaming;
37
+ return { next, chime: finished && enabled };
38
+ }
39
+
40
+ /**
41
+ * Plays the reply chime once when a thread finishes streaming.
42
+ *
43
+ * Silent on the first observation of a thread, so opening a settled thread
44
+ * does not chime for an earlier turn.
45
+ */
46
+ export function useReplyChime({
47
+ enabled,
48
+ isStreaming,
49
+ threadId,
50
+ }: ReplyChimeInput): void {
51
+ const watchedRef = useRef<ReplyChimeWatch | null>(null);
52
+
53
+ useEffect(() => {
54
+ const { next, chime } = nextReplyChimeState(watchedRef.current, {
55
+ enabled,
56
+ isStreaming,
57
+ threadId,
58
+ });
59
+ watchedRef.current = next;
60
+ if (chime) playChime();
61
+ }, [enabled, isStreaming, threadId]);
62
+ }
package/src/index.ts CHANGED
@@ -96,3 +96,5 @@ export * from "./ai-elements/shimmer";
96
96
  export * from "./ai-elements/sources";
97
97
  export * from "./ai-elements/speech-input";
98
98
  export * from "./ai-elements/suggestion";
99
+
100
+ export * from "./audio";
@@ -26,8 +26,8 @@ export const menuCheckboxRadioItemClass =
26
26
  "relative flex cursor-pointer select-none items-center rounded-md py-2 pl-8 pr-2.5 text-sm outline-hidden transition-[background-color,color,transform] duration-150 ease-out active:scale-[0.99] focus:bg-default focus:text-foreground data-highlighted:bg-default data-highlighted:text-foreground data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50";
27
27
 
28
28
  export const menuLabelClass =
29
- "px-2.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-muted";
29
+ "px-2.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-muted-foreground";
30
30
 
31
31
  export const menuSeparatorClass = "-mx-1.5 my-1 h-px bg-separator";
32
32
 
33
- export const menuShortcutClass = "ml-auto text-xs tracking-wide text-muted/90";
33
+ export const menuShortcutClass = "ml-auto text-xs tracking-wide text-muted-foreground/90";
@@ -23,8 +23,8 @@ export const menuCheckboxRadioItemClass =
23
23
  "relative flex cursor-pointer select-none items-center rounded-md py-2 max-sm:min-h-10 pl-8 pr-2.5 text-sm outline-none transition-[background-color,color] focus:bg-default focus:text-foreground data-[highlighted]:bg-default data-[highlighted]:text-foreground data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50";
24
24
 
25
25
  export const menuLabelClass =
26
- "px-2.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-muted";
26
+ "px-2.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-muted-foreground";
27
27
 
28
28
  export const menuSeparatorClass = "-mx-1.5 my-1 h-px bg-separator";
29
29
 
30
- export const menuShortcutClass = "ml-auto text-xs tracking-wide text-muted/90";
30
+ export const menuShortcutClass = "ml-auto text-xs tracking-wide text-muted-foreground/90";
@@ -31,7 +31,7 @@ const AccordionTrigger = React.forwardRef<
31
31
  {...props}
32
32
  >
33
33
  {children}
34
- <IconChevronDown className="h-4 w-4 shrink-0 text-muted transition-transform duration-200" />
34
+ <IconChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
35
35
  </AccordionPrimitive.Trigger>
36
36
  </AccordionPrimitive.Header>
37
37
  ));
@@ -46,7 +46,7 @@ const AccordionContent = React.forwardRef<
46
46
  className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
47
47
  {...props}
48
48
  >
49
- <div className={cn("pb-4 pt-0 text-muted", className)}>
49
+ <div className={cn("pb-4 pt-0 text-muted-foreground", className)}>
50
50
  {children}
51
51
  </div>
52
52
  </AccordionPrimitive.Content>
package/src/ui/badge.tsx CHANGED
@@ -9,7 +9,7 @@ const badgeVariants = cva(
9
9
  default: "border-border bg-default text-foreground",
10
10
  secondary: "border-separator bg-default text-default-foreground",
11
11
  destructive: "border-danger/25 bg-danger/12 text-danger",
12
- outline: "border-separator bg-transparent text-muted",
12
+ outline: "border-separator bg-transparent text-muted-foreground",
13
13
  success: "border-success/25 bg-success/12 text-success",
14
14
  warning: "border-warning/25 bg-warning/12 text-warning",
15
15
  },
@@ -48,7 +48,7 @@ const BreadcrumbLink = React.forwardRef<
48
48
  <Comp
49
49
  ref={ref}
50
50
  className={cn(
51
- "min-w-0 truncate font-medium text-muted transition-colors hover:text-foreground",
51
+ "min-w-0 truncate font-medium text-muted-foreground transition-colors hover:text-foreground",
52
52
  className,
53
53
  )}
54
54
  {...props}
@@ -74,7 +74,7 @@ BreadcrumbPage.displayName = "BreadcrumbPage";
74
74
 
75
75
  // separator rendered automatically between breadcrumb children
76
76
  const BreadcrumbSeparator = () => (
77
- <span aria-hidden className="flex-shrink-0 text-muted/40">
77
+ <span aria-hidden className="flex-shrink-0 text-muted-foreground/40">
78
78
  /
79
79
  </span>
80
80
  );
package/src/ui/button.tsx CHANGED
@@ -9,14 +9,14 @@ const buttonVariants = cva(
9
9
  variants: {
10
10
  variant: {
11
11
  default:
12
- "bg-accent text-accent-foreground hover:-translate-y-0.5 hover:opacity-90",
12
+ "bg-primary text-primary-foreground hover:-translate-y-0.5 hover:opacity-90",
13
13
  destructive:
14
14
  "bg-[oklch(47.2%_0.195_25.7)] text-white hover:bg-[oklch(42.5%_0.185_25.7)]",
15
15
  outline:
16
16
  "border border-border bg-transparent text-foreground hover:bg-default",
17
17
  secondary: "bg-default text-default-foreground hover:bg-default/78",
18
- ghost: "text-muted hover:bg-surface-tertiary hover:text-foreground",
19
- link: "text-accent underline-offset-4 hover:underline active:scale-100",
18
+ ghost: "text-muted-foreground hover:bg-surface-tertiary hover:text-foreground",
19
+ link: "text-primary underline-offset-4 hover:underline active:scale-100",
20
20
  },
21
21
  size: {
22
22
  default: "h-10 px-4 py-2",
@@ -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}