@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.
- package/fragments.json +1 -1
- package/package.json +9 -4
- package/src/components/Accordion/Accordion.fragment.tsx +186 -0
- package/src/components/Accordion/Accordion.module.scss +111 -0
- package/src/components/Accordion/index.tsx +271 -0
- package/src/components/Alert/Alert.fragment.tsx +66 -41
- package/src/components/Alert/Alert.module.scss +31 -21
- package/src/components/Alert/index.tsx +202 -73
- package/src/components/AppShell/AppShell.fragment.tsx +315 -0
- package/src/components/AppShell/AppShell.module.scss +213 -0
- package/src/components/AppShell/index.tsx +398 -0
- package/src/components/Avatar/index.tsx +8 -9
- package/src/components/Badge/Badge.module.scss +16 -10
- package/src/components/Badge/index.tsx +20 -6
- package/src/components/Box/Box.fragment.tsx +168 -0
- package/src/components/Box/Box.module.scss +84 -0
- package/src/components/Box/index.tsx +78 -0
- package/src/components/Button/Button.module.scss +42 -0
- package/src/components/Button/index.tsx +67 -33
- package/src/components/ButtonGroup/ButtonGroup.module.scss +37 -0
- package/src/components/ButtonGroup/index.tsx +40 -0
- package/src/components/Card/Card.fragment.tsx +51 -25
- package/src/components/Card/Card.module.scss +52 -5
- package/src/components/Card/index.tsx +154 -53
- package/src/components/Checkbox/Checkbox.module.scss +4 -4
- package/src/components/Checkbox/index.tsx +3 -4
- package/src/components/CodeBlock/CodeBlock.fragment.tsx +201 -0
- package/src/components/CodeBlock/CodeBlock.module.scss +224 -0
- package/src/components/CodeBlock/index.tsx +385 -0
- package/src/components/ColorChip/ColorChip.module.scss +165 -0
- package/src/components/ColorChip/index.tsx +157 -0
- package/src/components/ColorPicker/ColorPicker.module.scss +109 -0
- package/src/components/ColorPicker/index.tsx +107 -0
- package/src/components/Dialog/Dialog.fragment.tsx +9 -0
- package/src/components/Dialog/Dialog.module.scss +26 -7
- package/src/components/Dialog/index.tsx +12 -15
- package/src/components/EmptyState/EmptyState.fragment.tsx +54 -71
- package/src/components/EmptyState/EmptyState.module.scss +9 -9
- package/src/components/EmptyState/index.tsx +104 -69
- package/src/components/Field/Field.fragment.tsx +165 -0
- package/src/components/Field/Field.module.scss +31 -0
- package/src/components/Field/index.tsx +143 -0
- package/src/components/Fieldset/Fieldset.fragment.tsx +166 -0
- package/src/components/Fieldset/Fieldset.module.scss +22 -0
- package/src/components/Fieldset/index.tsx +47 -0
- package/src/components/Form/Form.fragment.tsx +286 -0
- package/src/components/Form/Form.module.scss +8 -0
- package/src/components/Form/index.tsx +53 -0
- package/src/components/Grid/Grid.fragment.tsx +17 -17
- package/src/components/Grid/index.tsx +6 -1
- package/src/components/Header/Header.fragment.tsx +192 -0
- package/src/components/Header/Header.module.scss +209 -0
- package/src/components/Header/index.tsx +363 -0
- package/src/components/Icon/Icon.fragment.tsx +138 -0
- package/src/components/Icon/Icon.module.scss +38 -0
- package/src/components/Icon/index.tsx +58 -0
- package/src/components/Image/Image.fragment.tsx +195 -0
- package/src/components/Image/Image.module.scss +77 -0
- package/src/components/Image/index.tsx +95 -0
- package/src/components/Input/Input.module.scss +75 -2
- package/src/components/Input/index.tsx +60 -21
- package/src/components/Link/Link.fragment.tsx +132 -0
- package/src/components/Link/Link.module.scss +67 -0
- package/src/components/Link/index.tsx +57 -0
- package/src/components/List/List.fragment.tsx +152 -0
- package/src/components/List/List.module.scss +71 -0
- package/src/components/List/index.tsx +106 -0
- package/src/components/Listbox/Listbox.fragment.tsx +191 -0
- package/src/components/Listbox/Listbox.module.scss +97 -0
- package/src/components/Listbox/index.tsx +121 -0
- package/src/components/Menu/Menu.fragment.tsx +9 -0
- package/src/components/Menu/Menu.module.scss +17 -1
- package/src/components/Menu/index.tsx +3 -3
- package/src/components/Popover/Popover.fragment.tsx +9 -0
- package/src/components/Popover/Popover.module.scss +33 -10
- package/src/components/Popover/index.tsx +9 -11
- package/src/components/Progress/Progress.module.scss +11 -11
- package/src/components/Progress/index.tsx +34 -7
- package/src/components/Prompt/Prompt.fragment.tsx +231 -0
- package/src/components/Prompt/Prompt.module.scss +243 -0
- package/src/components/Prompt/index.tsx +439 -0
- package/src/components/RadioGroup/RadioGroup.module.scss +3 -3
- package/src/components/RadioGroup/index.tsx +3 -4
- package/src/components/Select/Select.fragment.tsx +9 -0
- package/src/components/Select/index.tsx +6 -7
- package/src/components/Separator/index.tsx +7 -3
- package/src/components/Sidebar/Sidebar.fragment.tsx +783 -0
- package/src/components/Sidebar/Sidebar.module.scss +586 -0
- package/src/components/Sidebar/index.tsx +1013 -0
- package/src/components/Skeleton/Skeleton.fragment.tsx +5 -5
- package/src/components/Skeleton/Skeleton.module.scss +11 -0
- package/src/components/Slider/Slider.module.scss +87 -0
- package/src/components/Slider/index.tsx +88 -0
- package/src/components/Stack/Stack.module.scss +120 -0
- package/src/components/Stack/index.tsx +148 -0
- package/src/components/Table/Table.fragment.tsx +7 -0
- package/src/components/Table/Table.module.scss +57 -0
- package/src/components/Table/index.tsx +44 -6
- package/src/components/Tabs/Tabs.fragment.tsx +9 -0
- package/src/components/Tabs/Tabs.module.scss +25 -10
- package/src/components/Tabs/index.tsx +11 -8
- package/src/components/Text/Text.module.scss +82 -0
- package/src/components/Text/index.tsx +58 -0
- package/src/components/Textarea/index.tsx +3 -7
- package/src/components/Theme/Theme.fragment.tsx +128 -0
- package/src/components/Theme/ThemeToggle.module.scss +82 -0
- package/src/components/Theme/index.tsx +343 -0
- package/src/components/Toast/Toast.fragment.tsx +5 -5
- package/src/components/Toast/Toast.module.scss +16 -1
- package/src/components/Toast/index.tsx +27 -11
- package/src/components/Toggle/Toggle.module.scss +25 -10
- package/src/components/Toggle/index.tsx +12 -0
- package/src/components/ToggleGroup/ToggleGroup.module.scss +134 -0
- package/src/components/ToggleGroup/index.tsx +144 -0
- package/src/components/Tooltip/Tooltip.module.scss +4 -4
- package/src/components/Tooltip/index.tsx +4 -2
- package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +134 -0
- package/src/components/VisuallyHidden/VisuallyHidden.module.scss +13 -0
- package/src/components/VisuallyHidden/index.tsx +29 -0
- package/src/index.ts +241 -3
- package/src/recipes/AppShell.recipe.ts +175 -0
- package/src/recipes/CardGrid.recipe.ts +6 -2
- package/src/recipes/ChatInterface.recipe.ts +87 -0
- package/src/recipes/CodeExamples.recipe.ts +66 -0
- package/src/recipes/DashboardLayout.recipe.ts +46 -12
- package/src/recipes/DashboardNav.recipe.ts +183 -0
- package/src/recipes/LoginForm.recipe.ts +8 -1
- package/src/recipes/SettingsPage.recipe.ts +37 -20
- package/src/styles/globals.scss +31 -0
- package/src/tokens/_index.scss +3 -0
- package/src/tokens/_mixins.scss +54 -1
- package/src/tokens/_variables.scss +429 -64
- 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:
|
|
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:
|
|
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:
|
|
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
|
}
|