@fragments-sdk/ui 0.2.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/fragments.json +1 -1
  2. package/package.json +9 -4
  3. package/src/components/Accordion/Accordion.fragment.tsx +186 -0
  4. package/src/components/Accordion/Accordion.module.scss +111 -0
  5. package/src/components/Accordion/index.tsx +271 -0
  6. package/src/components/Alert/Alert.fragment.tsx +66 -41
  7. package/src/components/Alert/Alert.module.scss +31 -21
  8. package/src/components/Alert/index.tsx +202 -73
  9. package/src/components/AppShell/AppShell.fragment.tsx +315 -0
  10. package/src/components/AppShell/AppShell.module.scss +213 -0
  11. package/src/components/AppShell/index.tsx +398 -0
  12. package/src/components/Avatar/index.tsx +8 -9
  13. package/src/components/Badge/Badge.module.scss +16 -10
  14. package/src/components/Badge/index.tsx +20 -6
  15. package/src/components/Box/Box.fragment.tsx +168 -0
  16. package/src/components/Box/Box.module.scss +84 -0
  17. package/src/components/Box/index.tsx +78 -0
  18. package/src/components/Button/Button.module.scss +42 -0
  19. package/src/components/Button/index.tsx +67 -33
  20. package/src/components/ButtonGroup/ButtonGroup.module.scss +37 -0
  21. package/src/components/ButtonGroup/index.tsx +40 -0
  22. package/src/components/Card/Card.fragment.tsx +51 -25
  23. package/src/components/Card/Card.module.scss +52 -5
  24. package/src/components/Card/index.tsx +154 -53
  25. package/src/components/Checkbox/Checkbox.module.scss +4 -4
  26. package/src/components/Checkbox/index.tsx +3 -4
  27. package/src/components/CodeBlock/CodeBlock.fragment.tsx +201 -0
  28. package/src/components/CodeBlock/CodeBlock.module.scss +224 -0
  29. package/src/components/CodeBlock/index.tsx +385 -0
  30. package/src/components/ColorChip/ColorChip.module.scss +165 -0
  31. package/src/components/ColorChip/index.tsx +157 -0
  32. package/src/components/ColorPicker/ColorPicker.module.scss +109 -0
  33. package/src/components/ColorPicker/index.tsx +107 -0
  34. package/src/components/Dialog/Dialog.fragment.tsx +9 -0
  35. package/src/components/Dialog/Dialog.module.scss +26 -7
  36. package/src/components/Dialog/index.tsx +12 -15
  37. package/src/components/EmptyState/EmptyState.fragment.tsx +54 -71
  38. package/src/components/EmptyState/EmptyState.module.scss +9 -9
  39. package/src/components/EmptyState/index.tsx +104 -69
  40. package/src/components/Field/Field.fragment.tsx +165 -0
  41. package/src/components/Field/Field.module.scss +31 -0
  42. package/src/components/Field/index.tsx +143 -0
  43. package/src/components/Fieldset/Fieldset.fragment.tsx +166 -0
  44. package/src/components/Fieldset/Fieldset.module.scss +22 -0
  45. package/src/components/Fieldset/index.tsx +47 -0
  46. package/src/components/Form/Form.fragment.tsx +286 -0
  47. package/src/components/Form/Form.module.scss +8 -0
  48. package/src/components/Form/index.tsx +53 -0
  49. package/src/components/Grid/Grid.fragment.tsx +17 -17
  50. package/src/components/Grid/index.tsx +6 -1
  51. package/src/components/Header/Header.fragment.tsx +192 -0
  52. package/src/components/Header/Header.module.scss +209 -0
  53. package/src/components/Header/index.tsx +363 -0
  54. package/src/components/Icon/Icon.fragment.tsx +138 -0
  55. package/src/components/Icon/Icon.module.scss +38 -0
  56. package/src/components/Icon/index.tsx +58 -0
  57. package/src/components/Image/Image.fragment.tsx +195 -0
  58. package/src/components/Image/Image.module.scss +77 -0
  59. package/src/components/Image/index.tsx +95 -0
  60. package/src/components/Input/Input.module.scss +75 -2
  61. package/src/components/Input/index.tsx +60 -21
  62. package/src/components/Link/Link.fragment.tsx +132 -0
  63. package/src/components/Link/Link.module.scss +67 -0
  64. package/src/components/Link/index.tsx +57 -0
  65. package/src/components/List/List.fragment.tsx +152 -0
  66. package/src/components/List/List.module.scss +71 -0
  67. package/src/components/List/index.tsx +106 -0
  68. package/src/components/Listbox/Listbox.fragment.tsx +191 -0
  69. package/src/components/Listbox/Listbox.module.scss +97 -0
  70. package/src/components/Listbox/index.tsx +121 -0
  71. package/src/components/Menu/Menu.fragment.tsx +9 -0
  72. package/src/components/Menu/Menu.module.scss +17 -1
  73. package/src/components/Menu/index.tsx +3 -3
  74. package/src/components/Popover/Popover.fragment.tsx +9 -0
  75. package/src/components/Popover/Popover.module.scss +33 -10
  76. package/src/components/Popover/index.tsx +9 -11
  77. package/src/components/Progress/Progress.module.scss +11 -11
  78. package/src/components/Progress/index.tsx +34 -7
  79. package/src/components/Prompt/Prompt.fragment.tsx +231 -0
  80. package/src/components/Prompt/Prompt.module.scss +243 -0
  81. package/src/components/Prompt/index.tsx +439 -0
  82. package/src/components/RadioGroup/RadioGroup.module.scss +3 -3
  83. package/src/components/RadioGroup/index.tsx +3 -4
  84. package/src/components/Select/Select.fragment.tsx +9 -0
  85. package/src/components/Select/index.tsx +6 -7
  86. package/src/components/Separator/index.tsx +7 -3
  87. package/src/components/Sidebar/Sidebar.fragment.tsx +783 -0
  88. package/src/components/Sidebar/Sidebar.module.scss +586 -0
  89. package/src/components/Sidebar/index.tsx +1013 -0
  90. package/src/components/Skeleton/Skeleton.fragment.tsx +5 -5
  91. package/src/components/Skeleton/Skeleton.module.scss +11 -0
  92. package/src/components/Slider/Slider.module.scss +87 -0
  93. package/src/components/Slider/index.tsx +88 -0
  94. package/src/components/Stack/Stack.module.scss +120 -0
  95. package/src/components/Stack/index.tsx +148 -0
  96. package/src/components/Table/Table.fragment.tsx +7 -0
  97. package/src/components/Table/Table.module.scss +57 -0
  98. package/src/components/Table/index.tsx +44 -6
  99. package/src/components/Tabs/Tabs.fragment.tsx +9 -0
  100. package/src/components/Tabs/Tabs.module.scss +25 -10
  101. package/src/components/Tabs/index.tsx +11 -8
  102. package/src/components/Text/Text.module.scss +82 -0
  103. package/src/components/Text/index.tsx +58 -0
  104. package/src/components/Textarea/index.tsx +3 -7
  105. package/src/components/Theme/Theme.fragment.tsx +128 -0
  106. package/src/components/Theme/ThemeToggle.module.scss +82 -0
  107. package/src/components/Theme/index.tsx +343 -0
  108. package/src/components/Toast/Toast.fragment.tsx +5 -5
  109. package/src/components/Toast/Toast.module.scss +16 -1
  110. package/src/components/Toast/index.tsx +27 -11
  111. package/src/components/Toggle/Toggle.module.scss +25 -10
  112. package/src/components/Toggle/index.tsx +12 -0
  113. package/src/components/ToggleGroup/ToggleGroup.module.scss +134 -0
  114. package/src/components/ToggleGroup/index.tsx +144 -0
  115. package/src/components/Tooltip/Tooltip.module.scss +4 -4
  116. package/src/components/Tooltip/index.tsx +4 -2
  117. package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +134 -0
  118. package/src/components/VisuallyHidden/VisuallyHidden.module.scss +13 -0
  119. package/src/components/VisuallyHidden/index.tsx +29 -0
  120. package/src/index.ts +241 -3
  121. package/src/recipes/AppShell.recipe.ts +175 -0
  122. package/src/recipes/CardGrid.recipe.ts +6 -2
  123. package/src/recipes/ChatInterface.recipe.ts +87 -0
  124. package/src/recipes/CodeExamples.recipe.ts +66 -0
  125. package/src/recipes/DashboardLayout.recipe.ts +46 -12
  126. package/src/recipes/DashboardNav.recipe.ts +183 -0
  127. package/src/recipes/LoginForm.recipe.ts +8 -1
  128. package/src/recipes/SettingsPage.recipe.ts +37 -20
  129. package/src/styles/globals.scss +31 -0
  130. package/src/tokens/_index.scss +3 -0
  131. package/src/tokens/_mixins.scss +54 -1
  132. package/src/tokens/_variables.scss +429 -64
  133. package/src/utils/a11y.tsx +439 -0
@@ -0,0 +1,439 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import styles from './Prompt.module.scss';
5
+ import '../../styles/globals.scss';
6
+
7
+ // ============================================
8
+ // Types
9
+ // ============================================
10
+
11
+ export type PromptVariant = 'default' | 'fixed' | 'sticky';
12
+
13
+ export interface PromptProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'onSubmit' | 'defaultValue'> {
14
+ children: React.ReactNode;
15
+ /** Controlled input value */
16
+ value?: string;
17
+ /** Default value for uncontrolled usage */
18
+ defaultValue?: string;
19
+ /** Callback when value changes */
20
+ onChange?: (value: string) => void;
21
+ /** Callback when form is submitted */
22
+ onSubmit?: (value: string) => void;
23
+ /** Placeholder text for the textarea */
24
+ placeholder?: string;
25
+ /** Disable the entire prompt */
26
+ disabled?: boolean;
27
+ /** Show loading state (disables submit) */
28
+ loading?: boolean;
29
+ /** Minimum number of rows */
30
+ minRows?: number;
31
+ /** Maximum number of rows */
32
+ maxRows?: number;
33
+ /** Enable auto-resize based on content */
34
+ autoResize?: boolean;
35
+ /** Submit on Enter key (Shift+Enter for newline) */
36
+ submitOnEnter?: boolean;
37
+ /** Visual variant - "fixed" for bottom-fixed elevated prompt */
38
+ variant?: PromptVariant;
39
+ }
40
+
41
+ export interface PromptTextareaProps {
42
+ /** Override placeholder from context */
43
+ placeholder?: string;
44
+ className?: string;
45
+ }
46
+
47
+ export interface PromptToolbarProps {
48
+ children: React.ReactNode;
49
+ className?: string;
50
+ }
51
+
52
+ export interface PromptActionsProps {
53
+ children: React.ReactNode;
54
+ className?: string;
55
+ }
56
+
57
+ export interface PromptInfoProps {
58
+ children: React.ReactNode;
59
+ className?: string;
60
+ }
61
+
62
+ export interface PromptActionButtonProps {
63
+ children: React.ReactNode;
64
+ /** Accessible label for the button */
65
+ 'aria-label': string;
66
+ /** Click handler */
67
+ onClick?: () => void;
68
+ /** Disabled state */
69
+ disabled?: boolean;
70
+ className?: string;
71
+ }
72
+
73
+ export interface PromptModeButtonProps {
74
+ children: React.ReactNode;
75
+ /** Click handler */
76
+ onClick?: () => void;
77
+ /** Whether this mode is currently active */
78
+ active?: boolean;
79
+ /** Disabled state */
80
+ disabled?: boolean;
81
+ className?: string;
82
+ }
83
+
84
+ export interface PromptUsageProps {
85
+ children: React.ReactNode;
86
+ className?: string;
87
+ }
88
+
89
+ export interface PromptSubmitProps {
90
+ /** Custom submit icon/content */
91
+ children?: React.ReactNode;
92
+ /** Override aria-label */
93
+ 'aria-label'?: string;
94
+ className?: string;
95
+ }
96
+
97
+ // ============================================
98
+ // Icons
99
+ // ============================================
100
+
101
+ function ArrowUpIcon() {
102
+ return (
103
+ <svg
104
+ xmlns="http://www.w3.org/2000/svg"
105
+ width="16"
106
+ height="16"
107
+ viewBox="0 0 256 256"
108
+ fill="currentColor"
109
+ aria-hidden="true"
110
+ >
111
+ <path d="M205.66,117.66a8,8,0,0,1-11.32,0L136,59.31V216a8,8,0,0,1-16,0V59.31L61.66,117.66a8,8,0,0,1-11.32-11.32l72-72a8,8,0,0,1,11.32,0l72,72A8,8,0,0,1,205.66,117.66Z" />
112
+ </svg>
113
+ );
114
+ }
115
+
116
+ // ============================================
117
+ // Context
118
+ // ============================================
119
+
120
+ interface PromptContextValue {
121
+ value: string;
122
+ setValue: (value: string) => void;
123
+ placeholder: string;
124
+ disabled: boolean;
125
+ loading: boolean;
126
+ minRows: number;
127
+ maxRows: number;
128
+ autoResize: boolean;
129
+ submitOnEnter: boolean;
130
+ handleSubmit: () => void;
131
+ textareaRef: React.RefObject<HTMLTextAreaElement | null>;
132
+ }
133
+
134
+ const PromptContext = React.createContext<PromptContextValue | null>(null);
135
+
136
+ function usePromptContext() {
137
+ const context = React.useContext(PromptContext);
138
+ if (!context) {
139
+ throw new Error('Prompt compound components must be used within a Prompt');
140
+ }
141
+ return context;
142
+ }
143
+
144
+ // ============================================
145
+ // Hooks
146
+ // ============================================
147
+
148
+ function useControllableState<T>(
149
+ controlledValue: T | undefined,
150
+ defaultValue: T,
151
+ onChange?: (value: T) => void
152
+ ): [T, (value: T) => void] {
153
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
154
+ const isControlled = controlledValue !== undefined;
155
+ const value = isControlled ? controlledValue : uncontrolledValue;
156
+
157
+ const setValue = React.useCallback(
158
+ (newValue: T) => {
159
+ if (!isControlled) {
160
+ setUncontrolledValue(newValue);
161
+ }
162
+ onChange?.(newValue);
163
+ },
164
+ [isControlled, onChange]
165
+ );
166
+
167
+ return [value, setValue];
168
+ }
169
+
170
+ // ============================================
171
+ // Components
172
+ // ============================================
173
+
174
+ function PromptRoot({
175
+ children,
176
+ value: controlledValue,
177
+ defaultValue = '',
178
+ onChange,
179
+ onSubmit,
180
+ placeholder = 'Ask, Search or Chat...',
181
+ disabled = false,
182
+ loading = false,
183
+ minRows = 1,
184
+ maxRows = 8,
185
+ autoResize = true,
186
+ submitOnEnter = true,
187
+ variant = 'default',
188
+ className,
189
+ ...htmlProps
190
+ }: PromptProps) {
191
+ const textareaRef = React.useRef<HTMLTextAreaElement>(null);
192
+
193
+ const [value, setValue] = useControllableState(
194
+ controlledValue,
195
+ defaultValue,
196
+ onChange
197
+ );
198
+
199
+ const handleSubmit = React.useCallback(() => {
200
+ if (disabled || loading || !value.trim()) return;
201
+ onSubmit?.(value);
202
+ }, [disabled, loading, value, onSubmit]);
203
+
204
+ const contextValue: PromptContextValue = {
205
+ value,
206
+ setValue,
207
+ placeholder,
208
+ disabled,
209
+ loading,
210
+ minRows,
211
+ maxRows,
212
+ autoResize,
213
+ submitOnEnter,
214
+ handleSubmit,
215
+ textareaRef,
216
+ };
217
+
218
+ const classes = [
219
+ styles.prompt,
220
+ variant === 'fixed' && styles.fixed,
221
+ variant === 'sticky' && styles.sticky,
222
+ disabled && styles.disabled,
223
+ loading && styles.loading,
224
+ className,
225
+ ].filter(Boolean).join(' ');
226
+
227
+ return (
228
+ <PromptContext.Provider value={contextValue}>
229
+ <div
230
+ {...htmlProps}
231
+ className={classes}
232
+ data-disabled={disabled || undefined}
233
+ data-loading={loading || undefined}
234
+ data-variant={variant}
235
+ >
236
+ {children}
237
+ </div>
238
+ </PromptContext.Provider>
239
+ );
240
+ }
241
+
242
+ function PromptTextarea({ placeholder: overridePlaceholder, className }: PromptTextareaProps) {
243
+ const {
244
+ value,
245
+ setValue,
246
+ placeholder,
247
+ disabled,
248
+ loading,
249
+ minRows,
250
+ maxRows,
251
+ autoResize,
252
+ submitOnEnter,
253
+ handleSubmit,
254
+ textareaRef,
255
+ } = usePromptContext();
256
+
257
+ const lineHeight = 1.5;
258
+ const padding = 12; // top + bottom padding in pixels
259
+
260
+ const adjustHeight = React.useCallback(() => {
261
+ const textarea = textareaRef.current;
262
+ if (!textarea || !autoResize) return;
263
+
264
+ // Reset height to auto to get the correct scrollHeight
265
+ textarea.style.height = 'auto';
266
+
267
+ // Calculate min and max heights based on rows
268
+ const computedStyle = window.getComputedStyle(textarea);
269
+ const fontSize = parseFloat(computedStyle.fontSize);
270
+ const minHeight = minRows * fontSize * lineHeight + padding;
271
+ const maxHeight = maxRows * fontSize * lineHeight + padding;
272
+
273
+ // Set the height, clamped to min/max
274
+ const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
275
+ textarea.style.height = `${newHeight}px`;
276
+ }, [textareaRef, autoResize, minRows, maxRows]);
277
+
278
+ React.useEffect(() => {
279
+ adjustHeight();
280
+ }, [value, adjustHeight]);
281
+
282
+ const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
283
+ setValue(e.target.value);
284
+ };
285
+
286
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
287
+ if (submitOnEnter && e.key === 'Enter' && !e.shiftKey) {
288
+ e.preventDefault();
289
+ handleSubmit();
290
+ }
291
+ };
292
+
293
+ const classes = [styles.textarea, className].filter(Boolean).join(' ');
294
+
295
+ return (
296
+ <textarea
297
+ ref={textareaRef}
298
+ className={classes}
299
+ value={value}
300
+ onChange={handleChange}
301
+ onKeyDown={handleKeyDown}
302
+ placeholder={overridePlaceholder ?? placeholder}
303
+ disabled={disabled || loading}
304
+ rows={minRows}
305
+ aria-label={overridePlaceholder ?? placeholder}
306
+ />
307
+ );
308
+ }
309
+
310
+ function PromptToolbar({ children, className }: PromptToolbarProps) {
311
+ const classes = [styles.toolbar, className].filter(Boolean).join(' ');
312
+ return <div className={classes}>{children}</div>;
313
+ }
314
+
315
+ function PromptActions({ children, className }: PromptActionsProps) {
316
+ const classes = [styles.actions, className].filter(Boolean).join(' ');
317
+ return <div className={classes}>{children}</div>;
318
+ }
319
+
320
+ function PromptInfo({ children, className }: PromptInfoProps) {
321
+ const classes = [styles.info, className].filter(Boolean).join(' ');
322
+ return <div className={classes}>{children}</div>;
323
+ }
324
+
325
+ function PromptActionButton({
326
+ children,
327
+ 'aria-label': ariaLabel,
328
+ onClick,
329
+ disabled: buttonDisabled,
330
+ className,
331
+ }: PromptActionButtonProps) {
332
+ const { disabled, loading } = usePromptContext();
333
+ const isDisabled = disabled || loading || buttonDisabled;
334
+
335
+ const classes = [styles.actionButton, className].filter(Boolean).join(' ');
336
+
337
+ return (
338
+ <button
339
+ type="button"
340
+ className={classes}
341
+ onClick={onClick}
342
+ disabled={isDisabled}
343
+ aria-label={ariaLabel}
344
+ >
345
+ {children}
346
+ </button>
347
+ );
348
+ }
349
+
350
+ function PromptModeButton({
351
+ children,
352
+ onClick,
353
+ active = false,
354
+ disabled: buttonDisabled,
355
+ className,
356
+ }: PromptModeButtonProps) {
357
+ const { disabled, loading } = usePromptContext();
358
+ const isDisabled = disabled || loading || buttonDisabled;
359
+
360
+ const classes = [
361
+ styles.modeButton,
362
+ active && styles.modeButtonActive,
363
+ className,
364
+ ].filter(Boolean).join(' ');
365
+
366
+ return (
367
+ <button
368
+ type="button"
369
+ className={classes}
370
+ onClick={onClick}
371
+ disabled={isDisabled}
372
+ aria-pressed={active}
373
+ >
374
+ {children}
375
+ </button>
376
+ );
377
+ }
378
+
379
+ function PromptUsage({ children, className }: PromptUsageProps) {
380
+ const classes = [styles.usage, className].filter(Boolean).join(' ');
381
+ return <span className={classes}>{children}</span>;
382
+ }
383
+
384
+ function PromptSubmit({
385
+ children,
386
+ 'aria-label': ariaLabel = 'Submit',
387
+ className,
388
+ }: PromptSubmitProps) {
389
+ const { disabled, loading, handleSubmit, value } = usePromptContext();
390
+ const isDisabled = disabled || loading || !value.trim();
391
+
392
+ const classes = [
393
+ styles.submit,
394
+ loading && styles.submitLoading,
395
+ className,
396
+ ].filter(Boolean).join(' ');
397
+
398
+ return (
399
+ <button
400
+ type="button"
401
+ className={classes}
402
+ onClick={handleSubmit}
403
+ disabled={isDisabled}
404
+ aria-label={ariaLabel}
405
+ >
406
+ {children ?? <ArrowUpIcon />}
407
+ </button>
408
+ );
409
+ }
410
+
411
+ // ============================================
412
+ // Export compound component
413
+ // ============================================
414
+
415
+ export const Prompt = Object.assign(PromptRoot, {
416
+ Textarea: PromptTextarea,
417
+ Toolbar: PromptToolbar,
418
+ Actions: PromptActions,
419
+ Info: PromptInfo,
420
+ ActionButton: PromptActionButton,
421
+ ModeButton: PromptModeButton,
422
+ Usage: PromptUsage,
423
+ Submit: PromptSubmit,
424
+ });
425
+
426
+ export {
427
+ PromptRoot,
428
+ PromptTextarea,
429
+ PromptToolbar,
430
+ PromptActions,
431
+ PromptInfo,
432
+ PromptActionButton,
433
+ PromptModeButton,
434
+ PromptUsage,
435
+ PromptSubmit,
436
+ };
437
+
438
+ // Export hook for external use
439
+ export { usePromptContext };
@@ -52,7 +52,7 @@
52
52
  flex-shrink: 0;
53
53
  width: 1rem;
54
54
  height: 1rem;
55
- margin-top: 2px;
55
+ margin-top: var(--fui-space-0-5, $fui-space-0-5);
56
56
  background-color: var(--fui-bg-elevated, $fui-bg-elevated);
57
57
  border: 1px solid var(--fui-border-strong, $fui-border-strong);
58
58
  border-radius: var(--fui-radius-full, $fui-radius-full);
@@ -80,7 +80,7 @@
80
80
  .radioSm {
81
81
  width: 0.875rem;
82
82
  height: 0.875rem;
83
- margin-top: 3px;
83
+ margin-top: var(--fui-space-0-75, $fui-space-0-75);
84
84
  }
85
85
 
86
86
  .radioLg {
@@ -123,7 +123,7 @@
123
123
  .content {
124
124
  display: flex;
125
125
  flex-direction: column;
126
- gap: 2px;
126
+ gap: var(--fui-space-0-5, $fui-space-0-5);
127
127
  }
128
128
 
129
129
  .label {
@@ -9,7 +9,7 @@ import '../../styles/globals.scss';
9
9
  // Types
10
10
  // ============================================
11
11
 
12
- export interface RadioGroupProps {
12
+ export interface RadioGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'> {
13
13
  /** Current value (controlled) */
14
14
  value?: string;
15
15
  /** Default value (uncontrolled) */
@@ -30,8 +30,6 @@ export interface RadioGroupProps {
30
30
  size?: 'sm' | 'md' | 'lg';
31
31
  /** Children (Radio.Item components) */
32
32
  children: React.ReactNode;
33
- /** Additional class name */
34
- className?: string;
35
33
  }
36
34
 
37
35
  export interface RadioItemProps {
@@ -128,6 +126,7 @@ function RadioGroupRoot({
128
126
  size = 'md',
129
127
  children,
130
128
  className,
129
+ ...htmlProps
131
130
  }: RadioGroupProps) {
132
131
  const groupClasses = [
133
132
  styles.group,
@@ -137,7 +136,7 @@ function RadioGroupRoot({
137
136
 
138
137
  return (
139
138
  <RadioSizeContext.Provider value={size}>
140
- <div className={styles.wrapper}>
139
+ <div {...htmlProps} className={styles.wrapper}>
141
140
  {label && <span className={styles.groupLabel}>{label}</span>}
142
141
  <BaseRadioGroup
143
142
  value={value}
@@ -104,6 +104,15 @@ export default defineSegment({
104
104
  a11yRules: ['A11Y_SELECT_KEYBOARD', 'A11Y_SELECT_LABEL'],
105
105
  },
106
106
 
107
+ ai: {
108
+ compositionPattern: 'compound',
109
+ subComponents: ['Trigger', 'Content', 'Item', 'Group', 'GroupLabel'],
110
+ requiredChildren: ['Trigger', 'Content'],
111
+ commonPatterns: [
112
+ '<Select placeholder="Select option"><Select.Trigger /><Select.Content><Select.Item value="opt1">{label1}</Select.Item><Select.Item value="opt2">{label2}</Select.Item></Select.Content></Select>',
113
+ ],
114
+ },
115
+
107
116
  variants: [
108
117
  {
109
118
  name: 'Default',
@@ -30,15 +30,13 @@ export interface SelectProps {
30
30
  placeholder?: string;
31
31
  }
32
32
 
33
- export interface SelectTriggerProps {
33
+ export interface SelectTriggerProps extends React.HTMLAttributes<HTMLButtonElement> {
34
34
  children?: React.ReactNode;
35
35
  placeholder?: string;
36
- className?: string;
37
36
  }
38
37
 
39
- export interface SelectContentProps {
38
+ export interface SelectContentProps extends React.HTMLAttributes<HTMLDivElement> {
40
39
  children: React.ReactNode;
41
- className?: string;
42
40
  sideOffset?: number;
43
41
  align?: 'start' | 'center' | 'end';
44
42
  }
@@ -144,14 +142,14 @@ function SelectRoot({
144
142
  );
145
143
  }
146
144
 
147
- function SelectTrigger({ children, placeholder, className }: SelectTriggerProps) {
145
+ function SelectTrigger({ children, placeholder, className, ...htmlProps }: SelectTriggerProps) {
148
146
  const context = React.useContext(SelectContext);
149
147
  const placeholderText = placeholder ?? context.placeholder;
150
148
 
151
149
  const classes = [styles.trigger, className].filter(Boolean).join(' ');
152
150
 
153
151
  return (
154
- <BaseSelect.Trigger className={classes}>
152
+ <BaseSelect.Trigger {...htmlProps} className={classes}>
155
153
  {children ?? (
156
154
  <>
157
155
  <BaseSelect.Value placeholder={placeholderText} className={styles.value} />
@@ -169,6 +167,7 @@ function SelectContent({
169
167
  className,
170
168
  sideOffset = 4,
171
169
  align = 'start',
170
+ ...htmlProps
172
171
  }: SelectContentProps) {
173
172
  const popupClasses = [styles.popup, className].filter(Boolean).join(' ');
174
173
 
@@ -179,7 +178,7 @@ function SelectContent({
179
178
  align={align}
180
179
  className={styles.positioner}
181
180
  >
182
- <BaseSelect.Popup className={popupClasses}>
181
+ <BaseSelect.Popup {...htmlProps} className={popupClasses}>
183
182
  {children}
184
183
  </BaseSelect.Popup>
185
184
  </BaseSelect.Positioner>
@@ -8,7 +8,7 @@ import '../../styles/globals.scss';
8
8
  // Types
9
9
  // ============================================
10
10
 
11
- export interface SeparatorProps {
11
+ export interface SeparatorProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
12
12
  /** Orientation of the separator */
13
13
  orientation?: 'horizontal' | 'vertical';
14
14
  /** Spacing around the separator */
@@ -17,8 +17,6 @@ export interface SeparatorProps {
17
17
  soft?: boolean;
18
18
  /** Optional label text (creates a labeled divider) */
19
19
  label?: string;
20
- /** Additional class name */
21
- className?: string;
22
20
  }
23
21
 
24
22
  // ============================================
@@ -44,6 +42,8 @@ export const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(
44
42
  soft = false,
45
43
  label,
46
44
  className,
45
+ style,
46
+ ...htmlProps
47
47
  },
48
48
  ref
49
49
  ) {
@@ -63,6 +63,8 @@ export const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(
63
63
  role="separator"
64
64
  aria-orientation="horizontal"
65
65
  className={classes}
66
+ style={style}
67
+ {...htmlProps}
66
68
  >
67
69
  <span className={styles.label}>{label}</span>
68
70
  </div>
@@ -83,6 +85,8 @@ export const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(
83
85
  ref={ref}
84
86
  orientation={orientation}
85
87
  className={classes}
88
+ style={style}
89
+ {...htmlProps}
86
90
  />
87
91
  );
88
92
  }