@vvedantb/ui 0.17.0 → 0.18.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 (130) hide show
  1. package/README.md +253 -3
  2. package/package.json +13 -5
  3. package/shell/src/SidebarResize.tsx +1 -4
  4. package/src/ai-elements/__snapshots__/queue.test.tsx.snap +11 -0
  5. package/src/ai-elements/activity-question-utils.ts +26 -0
  6. package/src/ai-elements/activity-question.tsx +159 -0
  7. package/src/ai-elements/activity-shared.tsx +459 -0
  8. package/src/ai-elements/activity-step-detail.tsx +115 -0
  9. package/src/ai-elements/activity-step-label.ts +981 -0
  10. package/src/ai-elements/activity-tasks-utils.ts +199 -0
  11. package/src/ai-elements/activity-tasks.tsx +770 -0
  12. package/src/ai-elements/artifact.tsx +162 -52
  13. package/src/ai-elements/attachments.tsx +62 -32
  14. package/src/ai-elements/code-block.tsx +76 -22
  15. package/src/ai-elements/context.tsx +370 -0
  16. package/src/ai-elements/conversation.tsx +186 -62
  17. package/src/ai-elements/file-chip.tsx +53 -0
  18. package/src/ai-elements/message.tsx +161 -50
  19. package/src/ai-elements/model-picker-content.tsx +505 -0
  20. package/src/ai-elements/model-picker-types.ts +56 -0
  21. package/src/ai-elements/model-picker.tsx +301 -0
  22. package/src/ai-elements/model-selector.tsx +102 -21
  23. package/src/ai-elements/plan.tsx +130 -0
  24. package/src/ai-elements/prompt-input-settings.tsx +144 -0
  25. package/src/ai-elements/prompt-input-speech.tsx +189 -0
  26. package/src/ai-elements/prompt-input-traits.tsx +281 -0
  27. package/src/ai-elements/prompt-input.tsx +320 -134
  28. package/src/ai-elements/provider-icon.tsx +133 -0
  29. package/src/ai-elements/queue.tsx +293 -9
  30. package/src/ai-elements/shimmer.tsx +72 -1
  31. package/src/ai-elements/simple-model-ladder.tsx +108 -0
  32. package/src/ai-elements/test-results.tsx +510 -0
  33. package/src/ai-elements/trace.tsx +243 -0
  34. package/src/ai-elements/use-streaming-disclosure.ts +50 -0
  35. package/src/ai-elements/web-preview.tsx +349 -0
  36. package/src/composer/input-area/ComposerSlashMenu.tsx +1 -1
  37. package/src/index.ts +66 -12
  38. package/src/kibo/avatar-stack.tsx +40 -0
  39. package/src/kibo/contribution-graph.tsx +539 -0
  40. package/src/kibo/cursor.tsx +72 -0
  41. package/src/kibo/gantt-features.tsx +439 -0
  42. package/src/kibo/gantt-header.tsx +181 -0
  43. package/src/kibo/gantt-provider.tsx +657 -0
  44. package/src/kibo/gantt-sidebar.tsx +157 -0
  45. package/src/kibo/gantt-timeline.tsx +154 -0
  46. package/src/kibo/index.ts +151 -0
  47. package/src/kibo/kanban.tsx +338 -0
  48. package/src/kibo/kanbanDropTarget.ts +25 -0
  49. package/src/kibo/list.tsx +162 -0
  50. package/src/kibo/theme-switcher.tsx +109 -0
  51. package/src/kibo/video-player.tsx +237 -0
  52. package/src/markdown/Markdown.tsx +112 -0
  53. package/src/markdown/MarkdownBlockquote.tsx +45 -0
  54. package/src/markdown/MarkdownCodeBlock.tsx +197 -0
  55. package/src/markdown/MarkdownInlineCode.tsx +40 -0
  56. package/src/markdown/MarkdownLink.tsx +92 -0
  57. package/src/markdown/codeFence.ts +57 -0
  58. package/src/markdown/codeHighlight.ts +138 -0
  59. package/src/markdown/incrementalBlocks.ts +182 -0
  60. package/src/markdown/index.ts +3 -0
  61. package/src/markdown/markdownClipboard.ts +178 -0
  62. package/src/markdown/mermaid.ts +155 -0
  63. package/src/markdown/rehypeGithubAlerts.ts +80 -0
  64. package/src/motion/presets.ts +24 -0
  65. package/src/ui/__snapshots__/alt-wrappers.test.tsx.snap +19 -0
  66. package/src/ui/__snapshots__/sonner-dom.test.tsx.snap +5 -0
  67. package/src/ui/_menu-parts.tsx +29 -0
  68. package/src/ui/accordion.alt.tsx +46 -53
  69. package/src/ui/accordion.tsx +59 -10
  70. package/src/ui/avatar.tsx +56 -0
  71. package/src/ui/badge.tsx +45 -21
  72. package/src/ui/border-beam.tsx +97 -0
  73. package/src/ui/button-group.tsx +50 -21
  74. package/src/ui/button.tsx +67 -45
  75. package/src/ui/calendar.tsx +48 -5
  76. package/src/ui/card.tsx +77 -8
  77. package/src/ui/carousel.tsx +60 -0
  78. package/src/ui/centered-spinner.tsx +30 -0
  79. package/src/ui/checkbox.tsx +34 -16
  80. package/src/ui/clear-input.alt.tsx +33 -75
  81. package/src/ui/clear-input.tsx +140 -15
  82. package/src/ui/collapsible.tsx +40 -2
  83. package/src/ui/command.tsx +143 -70
  84. package/src/ui/context-menu.tsx +159 -93
  85. package/src/ui/crossfade-icon.tsx +123 -0
  86. package/src/ui/dialog.tsx +154 -70
  87. package/src/ui/dropdown-menu.tsx +177 -117
  88. package/src/ui/hover-card.tsx +59 -17
  89. package/src/ui/input-group.alt.tsx +83 -83
  90. package/src/ui/input-group.tsx +129 -58
  91. package/src/ui/input.tsx +13 -4
  92. package/src/ui/label.tsx +11 -8
  93. package/src/ui/list-row.tsx +232 -0
  94. package/src/ui/loading-state.tsx +215 -0
  95. package/src/ui/popover.tsx +56 -23
  96. package/src/ui/progress.tsx +28 -16
  97. package/src/ui/refresh-spin-icon.tsx +41 -0
  98. package/src/ui/search-input.tsx +66 -15
  99. package/src/ui/select.tsx +171 -100
  100. package/src/ui/separator.tsx +23 -14
  101. package/src/ui/slider.tsx +21 -7
  102. package/src/ui/sonner.tsx +68 -30
  103. package/src/ui/spinner.tsx +229 -11
  104. package/src/ui/status-dot.tsx +84 -0
  105. package/src/ui/surface.tsx +49 -0
  106. package/src/ui/switch.tsx +29 -15
  107. package/src/ui/table.tsx +38 -16
  108. package/src/ui/tabs.tsx +123 -35
  109. package/src/ui/tabsSliding.ts +33 -1
  110. package/src/ui/textarea.tsx +13 -4
  111. package/src/ui/tooltip.tsx +50 -14
  112. package/src/ui/ui-config.tsx +149 -0
  113. package/src/utils/cat-color.ts +123 -0
  114. package/src/utils/dayjs.ts +6 -0
  115. package/src/utils/gesture.ts +59 -0
  116. package/src/utils/runtimeVisibility.ts +167 -0
  117. package/src/utils/sharedClock.ts +96 -0
  118. package/src/utils/surface-radius.ts +16 -0
  119. package/src/utils/useDragSensors.ts +58 -0
  120. package/tokens/examples/tokens-moved/Fixture.tsx +283 -0
  121. package/tokens/examples/tokens-moved/README.md +20 -0
  122. package/tokens/examples/tokens-moved/index.html +12 -0
  123. package/tokens/examples/tokens-moved/main.tsx +6 -0
  124. package/tokens/examples/tokens-moved/package.json +14 -0
  125. package/tokens/examples/tokens-moved/styles.css +12 -0
  126. package/tokens/examples/tokens-moved/tsconfig.json +14 -0
  127. package/tokens/examples/tokens-moved/vite.config.ts +11 -0
  128. package/tokens/src/css/border-beam.css +116 -0
  129. package/tokens/src/css/compat-shadcn-rgb.css +6 -0
  130. package/tokens/src/css/markdown.css +318 -0
@@ -2,9 +2,9 @@
2
2
 
3
3
  import * as React from "react";
4
4
  import { IconSearch } from "@tabler/icons-react";
5
- import { cn } from "../utils/cn";
6
5
  import { ClearInput, type ClearInputProps } from "./clear-input";
7
6
  import { Kbd } from "./kbd";
7
+ import { useCn, useUiConfig } from "./ui-config";
8
8
 
9
9
  const sizeClasses = {
10
10
  sm: { input: "h-8 pl-8 text-sm", icon: "left-2" },
@@ -19,6 +19,26 @@ const variantClasses = {
19
19
  },
20
20
  };
21
21
 
22
+ type EnterKeyHint = React.InputHTMLAttributes<HTMLInputElement>["enterKeyHint"];
23
+
24
+ /**
25
+ * `UiConfig.SearchInput`. `classes.input` and `classes.icon` replace the
26
+ * size classes of the input and the whole icon class; `classes.wrapper` is a
27
+ * base for the wrapper. Variant classes still apply after them. The other keys
28
+ * are prop defaults; `false` leaves the attribute off.
29
+ */
30
+ export interface SearchInputConfig {
31
+ classes?: { wrapper?: string; input?: string; icon?: string };
32
+ /** Search icon size in px. Default `16`. */
33
+ iconSize?: number;
34
+ label?: string | false;
35
+ placeholder?: string;
36
+ clearLabel?: string;
37
+ enterKeyHint?: EnterKeyHint | false;
38
+ /** Element that receives `className`: `"input"` (default) or `"wrapper"`. */
39
+ classNameTarget?: "input" | "wrapper";
40
+ }
41
+
22
42
  export interface SearchInputProps extends Omit<ClearInputProps, "leading" | "size"> {
23
43
  /** `aria-label`, and the placeholder when `placeholder` is not set. Default `"Search"`. */
24
44
  label?: string;
@@ -28,46 +48,77 @@ export interface SearchInputProps extends Omit<ClearInputProps, "leading" | "siz
28
48
  size?: keyof typeof sizeClasses;
29
49
  /** `toolbar`: transparent until hover or focus, fixed width from `sm`. Default `"default"`. */
30
50
  variant?: keyof typeof variantClasses;
51
+ /** Called after `onChange` whenever the value becomes empty (typed away or cleared). */
52
+ onClear?: () => void;
53
+ /** Classes for the input, merged after `className` when that also targets the input. */
54
+ inputClassName?: string;
31
55
  }
32
56
 
33
57
  const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(
34
58
  (
35
59
  {
36
- label = "Search",
60
+ label,
37
61
  placeholder,
38
- clearLabel = "Clear search",
62
+ clearLabel,
39
63
  shortcut,
40
64
  size = "sm",
41
65
  variant = "default",
42
66
  className,
43
67
  wrapperClassName,
68
+ inputClassName,
69
+ onClear,
70
+ onChange,
44
71
  ...props
45
72
  },
46
73
  ref,
47
74
  ) => {
75
+ const config = useUiConfig("SearchInput");
76
+ const cn = useCn();
77
+ const name = label ?? config?.label ?? "Search";
78
+ const hint = config?.enterKeyHint ?? "search";
79
+ const onWrapper = config?.classNameTarget === "wrapper";
48
80
  const showShortcut = Boolean(shortcut) && props.value.length === 0;
49
81
  return (
50
82
  <ClearInput
51
83
  ref={ref}
52
- aria-label={label}
53
- placeholder={placeholder ?? label}
54
- clearLabel={clearLabel}
55
- enterKeyHint="search"
56
- wrapperClassName={cn(variantClasses[variant].wrapper, wrapperClassName)}
84
+ aria-label={name === false ? undefined : name}
85
+ placeholder={placeholder ?? config?.placeholder ?? (name === false ? undefined : name)}
86
+ clearLabel={clearLabel ?? config?.clearLabel ?? "Clear search"}
87
+ enterKeyHint={hint === false ? undefined : hint}
88
+ onChange={
89
+ onClear
90
+ ? (next) => {
91
+ onChange(next);
92
+ if (next.length === 0) {
93
+ onClear();
94
+ }
95
+ }
96
+ : onChange
97
+ }
98
+ wrapperClassName={cn(
99
+ config?.classes?.wrapper,
100
+ variantClasses[variant].wrapper,
101
+ wrapperClassName,
102
+ onWrapper && className,
103
+ )}
57
104
  className={cn(
58
- sizeClasses[size].input,
105
+ config?.classes?.input ?? sizeClasses[size].input,
59
106
  variantClasses[variant].input,
60
107
  showShortcut && "pr-12",
61
- className,
108
+ !onWrapper && className,
109
+ inputClassName,
62
110
  )}
63
111
  leading={
64
112
  <>
65
113
  <IconSearch
66
- size={16}
67
- className={cn(
68
- "pointer-events-none absolute top-1/2 -translate-y-1/2 shrink-0 text-muted-foreground",
69
- sizeClasses[size].icon,
70
- )}
114
+ size={config?.iconSize ?? 16}
115
+ className={
116
+ config?.classes?.icon ??
117
+ cn(
118
+ "pointer-events-none absolute top-1/2 -translate-y-1/2 shrink-0 text-muted-foreground",
119
+ sizeClasses[size].icon,
120
+ )
121
+ }
71
122
  />
72
123
  {showShortcut ? (
73
124
  <Kbd className="absolute right-2 top-1/2 -translate-y-1/2">{shortcut}</Kbd>
package/src/ui/select.tsx CHANGED
@@ -7,6 +7,37 @@ import {
7
7
  floatingSurfaceAnimateClass,
8
8
  floatingSurfaceClass,
9
9
  } from "./_menu-classes";
10
+ import { useCn, useUiConfig } from "./ui-config";
11
+
12
+ /** App defaults for `Select*` (see `UiConfigProvider`). */
13
+ export interface SelectConfig {
14
+ /** Replaces the built-in classes of a part. */
15
+ classes?: {
16
+ trigger?: string;
17
+ /** Both scroll buttons. */
18
+ scrollButton?: string;
19
+ content?: string;
20
+ /** Viewport classes for every `position`. */
21
+ viewport?: string;
22
+ /** Extra viewport classes for `position="popper"`. */
23
+ popperViewport?: string;
24
+ label?: string;
25
+ item?: string;
26
+ separator?: string;
27
+ };
28
+ /** Replace the built-in icons. */
29
+ icons?: {
30
+ trigger?: React.ReactNode;
31
+ scrollUp?: React.ReactNode;
32
+ scrollDown?: React.ReactNode;
33
+ /** `SelectItem` indicator. */
34
+ check?: React.ReactNode;
35
+ };
36
+ /** Adds `data-slot="select-trigger"` to the trigger, for parent selectors such as `ButtonGroup`'s. */
37
+ triggerSlot?: boolean;
38
+ /** Default for `SelectContent` `collisionPadding`. */
39
+ collisionPadding?: number | Partial<Record<"top" | "right" | "bottom" | "left", number>>;
40
+ }
10
41
 
11
42
  const Select = SelectPrimitive.Root;
12
43
  const SelectGroup = SelectPrimitive.Group;
@@ -15,140 +46,180 @@ const SelectValue = SelectPrimitive.Value;
15
46
  const SelectTrigger = React.forwardRef<
16
47
  React.ComponentRef<typeof SelectPrimitive.Trigger>,
17
48
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
- >(({ className, children, ...props }, ref) => (
19
- <SelectPrimitive.Trigger
20
- ref={ref}
21
- className={cn(
22
- "flex h-10 w-full max-sm:min-w-0 items-center justify-between whitespace-nowrap rounded-field border border-border bg-field-background px-3.5 py-2 text-base sm:text-sm ring-offset-background placeholder:text-field-placeholder transition-[border-color,box-shadow,background-color] duration-200 ease-smooth disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
23
- fieldFocusClass,
24
- className,
25
- )}
26
- {...props}
27
- >
28
- {children}
29
- <SelectPrimitive.Icon asChild>
30
- <IconChevronDown className="h-4 w-4 text-muted-foreground/85" />
31
- </SelectPrimitive.Icon>
32
- </SelectPrimitive.Trigger>
33
- ));
49
+ >(({ className, children, ...props }, ref) => {
50
+ const config = useUiConfig("Select");
51
+ const merge = useCn();
52
+ return (
53
+ <SelectPrimitive.Trigger
54
+ ref={ref}
55
+ data-slot={config?.triggerSlot ? "select-trigger" : undefined}
56
+ className={merge(
57
+ config?.classes?.trigger ?? [
58
+ "flex h-10 w-full max-sm:min-w-0 items-center justify-between whitespace-nowrap rounded-field border border-border bg-field-background px-3.5 py-2 text-base sm:text-sm ring-offset-background placeholder:text-field-placeholder transition-[border-color,box-shadow,background-color] duration-200 ease-smooth disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
59
+ fieldFocusClass,
60
+ ],
61
+ className,
62
+ )}
63
+ {...props}
64
+ >
65
+ {children}
66
+ <SelectPrimitive.Icon asChild>
67
+ {config?.icons?.trigger ?? <IconChevronDown className="h-4 w-4 text-muted-foreground/85" />}
68
+ </SelectPrimitive.Icon>
69
+ </SelectPrimitive.Trigger>
70
+ );
71
+ });
34
72
  SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
35
73
 
36
74
  const SelectScrollUpButton = React.forwardRef<
37
75
  React.ComponentRef<typeof SelectPrimitive.ScrollUpButton>,
38
76
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
39
- >(({ className, ...props }, ref) => (
40
- <SelectPrimitive.ScrollUpButton
41
- ref={ref}
42
- className={cn(
43
- "flex cursor-default items-center justify-center py-1 text-muted-foreground",
44
- className,
45
- )}
46
- {...props}
47
- >
48
- <IconChevronUp className="h-4 w-4" />
49
- </SelectPrimitive.ScrollUpButton>
50
- ));
77
+ >(({ className, ...props }, ref) => {
78
+ const config = useUiConfig("Select");
79
+ const merge = useCn();
80
+ return (
81
+ <SelectPrimitive.ScrollUpButton
82
+ ref={ref}
83
+ className={merge(
84
+ config?.classes?.scrollButton ??
85
+ "flex cursor-default items-center justify-center py-1 text-muted-foreground",
86
+ className,
87
+ )}
88
+ {...props}
89
+ >
90
+ {config?.icons?.scrollUp ?? <IconChevronUp className="h-4 w-4" />}
91
+ </SelectPrimitive.ScrollUpButton>
92
+ );
93
+ });
51
94
  SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
52
95
 
53
96
  const SelectScrollDownButton = React.forwardRef<
54
97
  React.ComponentRef<typeof SelectPrimitive.ScrollDownButton>,
55
98
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
56
- >(({ className, ...props }, ref) => (
57
- <SelectPrimitive.ScrollDownButton
58
- ref={ref}
59
- className={cn(
60
- "flex cursor-default items-center justify-center py-1 text-muted-foreground",
61
- className,
62
- )}
63
- {...props}
64
- >
65
- <IconChevronDown className="h-4 w-4" />
66
- </SelectPrimitive.ScrollDownButton>
67
- ));
99
+ >(({ className, ...props }, ref) => {
100
+ const config = useUiConfig("Select");
101
+ const merge = useCn();
102
+ return (
103
+ <SelectPrimitive.ScrollDownButton
104
+ ref={ref}
105
+ className={merge(
106
+ config?.classes?.scrollButton ??
107
+ "flex cursor-default items-center justify-center py-1 text-muted-foreground",
108
+ className,
109
+ )}
110
+ {...props}
111
+ >
112
+ {config?.icons?.scrollDown ?? <IconChevronDown className="h-4 w-4" />}
113
+ </SelectPrimitive.ScrollDownButton>
114
+ );
115
+ });
68
116
  SelectScrollDownButton.displayName =
69
117
  SelectPrimitive.ScrollDownButton.displayName;
70
118
 
71
119
  const SelectContent = React.forwardRef<
72
120
  React.ComponentRef<typeof SelectPrimitive.Content>,
73
121
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
74
- >(({ className, children, position = "popper", ...props }, ref) => (
75
- <SelectPrimitive.Portal>
76
- <SelectPrimitive.Content
77
- ref={ref}
78
- className={cn(
79
- "relative z-50 max-h-96 min-w-32 overflow-hidden outline-none",
80
- floatingSurfaceClass,
81
- floatingSurfaceAnimateClass,
82
- position === "popper" &&
83
- "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
84
- className,
85
- )}
86
- position={position}
87
- {...props}
88
- >
89
- <SelectScrollUpButton />
90
- <SelectPrimitive.Viewport
91
- className={cn(
92
- "p-1.5",
122
+ >(({ className, children, position = "popper", collisionPadding, ...props }, ref) => {
123
+ const config = useUiConfig("Select");
124
+ const merge = useCn();
125
+ const classes = config?.classes;
126
+ return (
127
+ <SelectPrimitive.Portal>
128
+ <SelectPrimitive.Content
129
+ ref={ref}
130
+ collisionPadding={collisionPadding ?? config?.collisionPadding}
131
+ className={merge(
132
+ classes?.content ?? [
133
+ "relative z-50 max-h-96 min-w-32 overflow-hidden outline-none",
134
+ floatingSurfaceClass,
135
+ floatingSurfaceAnimateClass,
136
+ ],
93
137
  position === "popper" &&
94
- "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
138
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
139
+ className,
95
140
  )}
141
+ position={position}
142
+ {...props}
96
143
  >
97
- {children}
98
- </SelectPrimitive.Viewport>
99
- <SelectScrollDownButton />
100
- </SelectPrimitive.Content>
101
- </SelectPrimitive.Portal>
102
- ));
144
+ <SelectScrollUpButton />
145
+ <SelectPrimitive.Viewport
146
+ className={cn(
147
+ classes?.viewport ?? "p-1.5",
148
+ position === "popper" &&
149
+ (classes?.popperViewport ??
150
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"),
151
+ )}
152
+ >
153
+ {children}
154
+ </SelectPrimitive.Viewport>
155
+ <SelectScrollDownButton />
156
+ </SelectPrimitive.Content>
157
+ </SelectPrimitive.Portal>
158
+ );
159
+ });
103
160
  SelectContent.displayName = SelectPrimitive.Content.displayName;
104
161
 
105
162
  const SelectLabel = React.forwardRef<
106
163
  React.ComponentRef<typeof SelectPrimitive.Label>,
107
164
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
108
- >(({ className, ...props }, ref) => (
109
- <SelectPrimitive.Label
110
- ref={ref}
111
- className={cn(
112
- "px-2.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-muted-foreground",
113
- className,
114
- )}
115
- {...props}
116
- />
117
- ));
165
+ >(({ className, ...props }, ref) => {
166
+ const label = useUiConfig("Select")?.classes?.label;
167
+ const merge = useCn();
168
+ return (
169
+ <SelectPrimitive.Label
170
+ ref={ref}
171
+ className={merge(
172
+ label ??
173
+ "px-2.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-muted-foreground",
174
+ className,
175
+ )}
176
+ {...props}
177
+ />
178
+ );
179
+ });
118
180
  SelectLabel.displayName = SelectPrimitive.Label.displayName;
119
181
 
120
182
  const SelectItem = React.forwardRef<
121
183
  React.ComponentRef<typeof SelectPrimitive.Item>,
122
184
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
123
- >(({ className, children, ...props }, ref) => (
124
- <SelectPrimitive.Item
125
- ref={ref}
126
- className={cn(
127
- "relative flex w-full cursor-pointer select-none items-center rounded-menu-item py-2 max-sm:min-h-10 pl-2.5 pr-8 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",
128
- className,
129
- )}
130
- {...props}
131
- >
132
- <span className="absolute right-2.5 flex h-3.5 w-3.5 items-center justify-center">
133
- <SelectPrimitive.ItemIndicator>
134
- <IconCheck className="h-4 w-4" />
135
- </SelectPrimitive.ItemIndicator>
136
- </span>
137
- <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
138
- </SelectPrimitive.Item>
139
- ));
185
+ >(({ className, children, ...props }, ref) => {
186
+ const config = useUiConfig("Select");
187
+ const merge = useCn();
188
+ return (
189
+ <SelectPrimitive.Item
190
+ ref={ref}
191
+ className={merge(
192
+ config?.classes?.item ??
193
+ "relative flex w-full cursor-pointer select-none items-center rounded-menu-item py-2 max-sm:min-h-10 pl-2.5 pr-8 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",
194
+ className,
195
+ )}
196
+ {...props}
197
+ >
198
+ <span className="absolute right-2.5 flex h-3.5 w-3.5 items-center justify-center">
199
+ <SelectPrimitive.ItemIndicator>
200
+ {config?.icons?.check ?? <IconCheck className="h-4 w-4" />}
201
+ </SelectPrimitive.ItemIndicator>
202
+ </span>
203
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
204
+ </SelectPrimitive.Item>
205
+ );
206
+ });
140
207
  SelectItem.displayName = SelectPrimitive.Item.displayName;
141
208
 
142
209
  const SelectSeparator = React.forwardRef<
143
210
  React.ComponentRef<typeof SelectPrimitive.Separator>,
144
211
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
145
- >(({ className, ...props }, ref) => (
146
- <SelectPrimitive.Separator
147
- ref={ref}
148
- className={cn("-mx-1.5 my-1 h-px bg-separator", className)}
149
- {...props}
150
- />
151
- ));
212
+ >(({ className, ...props }, ref) => {
213
+ const separator = useUiConfig("Select")?.classes?.separator;
214
+ const merge = useCn();
215
+ return (
216
+ <SelectPrimitive.Separator
217
+ ref={ref}
218
+ className={merge(separator ?? "-mx-1.5 my-1 h-px bg-separator", className)}
219
+ {...props}
220
+ />
221
+ );
222
+ });
152
223
  SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
153
224
 
154
225
  export {
@@ -1,6 +1,11 @@
1
1
  import * as React from "react";
2
2
  import * as SeparatorPrimitive from "@radix-ui/react-separator";
3
- import { cn } from "../utils/cn";
3
+ import { useCn, useUiConfig } from "./ui-config";
4
+
5
+ export interface SeparatorConfig {
6
+ /** Replaces the built-in classes of a part. `root` excludes the orientation size classes. */
7
+ classes?: { root?: string };
8
+ }
4
9
 
5
10
  const Separator = React.forwardRef<
6
11
  React.ComponentRef<typeof SeparatorPrimitive.Root>,
@@ -9,19 +14,23 @@ const Separator = React.forwardRef<
9
14
  (
10
15
  { className, orientation = "horizontal", decorative = true, ...props },
11
16
  ref,
12
- ) => (
13
- <SeparatorPrimitive.Root
14
- ref={ref}
15
- decorative={decorative}
16
- orientation={orientation}
17
- className={cn(
18
- "shrink-0 bg-separator",
19
- orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
20
- className,
21
- )}
22
- {...props}
23
- />
24
- ),
17
+ ) => {
18
+ const root = useUiConfig("Separator")?.classes?.root;
19
+ const merge = useCn();
20
+ return (
21
+ <SeparatorPrimitive.Root
22
+ ref={ref}
23
+ decorative={decorative}
24
+ orientation={orientation}
25
+ className={merge(
26
+ root ?? "shrink-0 bg-separator",
27
+ orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
28
+ className,
29
+ )}
30
+ {...props}
31
+ />
32
+ );
33
+ },
25
34
  );
26
35
  Separator.displayName = SeparatorPrimitive.Root.displayName;
27
36
 
package/src/ui/slider.tsx CHANGED
@@ -1,21 +1,27 @@
1
1
  import * as React from "react";
2
2
  import * as SliderPrimitive from "@radix-ui/react-slider";
3
- import { cn } from "../utils/cn";
3
+ import { useCn, useUiConfig } from "./ui-config";
4
+
5
+ /** `UiConfig.Slider`: `classes` replace the built-in part classes; the `*ClassName` props still merge after. */
6
+ export interface SliderConfig {
7
+ classes?: { root?: string; track?: string; range?: string; thumb?: string };
8
+ }
4
9
 
5
10
  type SliderProps = React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> & {
6
11
  /** Accessible name per thumb, by index. Wins over `aria-label` on the first thumb. */
7
12
  thumbLabels?: string[];
8
- /** Classes for the track, merged after the defaults so they win (`bg-default dark:bg-muted/40`). */
13
+ /** Classes for the track, merged after the defaults so they win (`bg-default dark:bg-muted/40`). check-tokens-allow: muted */
9
14
  trackClassName?: string;
10
15
  /** Classes for the filled range, merged after the defaults so they win (`bg-primary`). */
11
16
  rangeClassName?: string;
12
- /** Classes for every thumb, merged after the defaults so they win (`bg-background dark:bg-foreground dark:border-muted`). */
17
+ /** Classes for every thumb, merged after the defaults so they win (`bg-background dark:bg-foreground dark:border-muted`). check-tokens-allow: muted */
13
18
  thumbClassName?: string;
14
19
  };
15
20
 
16
21
  // `id`, `aria-label`, `aria-labelledby` and `aria-valuetext` go to the first
17
22
  // Thumb, not Root: the thumb is the focusable role="slider" element, so
18
23
  // `<Label htmlFor={id}>` and the accessible name land on it. The ref stays on Root.
24
+ // `children` render inside the track, after the range (for example tick marks).
19
25
  const Slider = React.forwardRef<React.ComponentRef<typeof SliderPrimitive.Root>, SliderProps>(
20
26
  (
21
27
  {
@@ -28,15 +34,19 @@ const Slider = React.forwardRef<React.ComponentRef<typeof SliderPrimitive.Root>,
28
34
  trackClassName,
29
35
  rangeClassName,
30
36
  thumbClassName,
37
+ children,
31
38
  ...props
32
39
  },
33
40
  ref,
34
41
  ) => {
42
+ const classes = useUiConfig("Slider")?.classes;
43
+ const cn = useCn();
35
44
  const count = (props.value ?? props.defaultValue ?? [props.min ?? 0]).length;
36
45
  return (
37
46
  <SliderPrimitive.Root
38
47
  className={cn(
39
- "relative flex w-full touch-none select-none items-center data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
48
+ classes?.root ??
49
+ "relative flex w-full touch-none select-none items-center data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
40
50
  className,
41
51
  )}
42
52
  {...props}
@@ -44,16 +54,19 @@ const Slider = React.forwardRef<React.ComponentRef<typeof SliderPrimitive.Root>,
44
54
  >
45
55
  <SliderPrimitive.Track
46
56
  className={cn(
47
- "relative grow overflow-hidden rounded-full bg-default dark:bg-muted/40 data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5",
57
+ classes?.track ??
58
+ "relative grow overflow-hidden rounded-full bg-default dark:bg-muted/40 data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5", // check-tokens-allow: muted
48
59
  trackClassName,
49
60
  )}
50
61
  >
51
62
  <SliderPrimitive.Range
52
63
  className={cn(
53
- "absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full",
64
+ classes?.range ??
65
+ "absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full",
54
66
  rangeClassName,
55
67
  )}
56
68
  />
69
+ {children}
57
70
  </SliderPrimitive.Track>
58
71
  {Array.from({ length: count }, (_, i) => (
59
72
  <SliderPrimitive.Thumb
@@ -63,7 +76,8 @@ const Slider = React.forwardRef<React.ComponentRef<typeof SliderPrimitive.Root>,
63
76
  aria-labelledby={i === 0 ? ariaLabelledBy : undefined}
64
77
  aria-valuetext={i === 0 ? ariaValueText : undefined}
65
78
  className={cn(
66
- "block size-4 shrink-0 rounded-full border border-border bg-background shadow-sm transition-[box-shadow] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring data-[disabled]:cursor-not-allowed dark:border-muted dark:bg-foreground",
79
+ classes?.thumb ??
80
+ "block size-4 shrink-0 rounded-full border border-border bg-background shadow-sm transition-[box-shadow] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring data-[disabled]:cursor-not-allowed dark:border-muted dark:bg-foreground", // check-tokens-allow: muted
67
81
  thumbClassName,
68
82
  )}
69
83
  />