@fragments-sdk/ui 0.3.0 → 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 +9 -0
- package/src/components/Sidebar/Sidebar.module.scss +72 -47
- package/src/components/Sidebar/index.tsx +5 -3
- 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 +195 -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
|
@@ -41,17 +41,17 @@
|
|
|
41
41
|
border-radius: var(--fui-radius-full, $fui-radius-full);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// Size variants for track
|
|
44
|
+
// Size variants for track (using closest 7px grid values)
|
|
45
45
|
.trackSm {
|
|
46
|
-
height:
|
|
46
|
+
height: calc(var(--fui-space-1, $fui-space-1) / 2); // ~3.5px
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
.trackMd {
|
|
50
|
-
height:
|
|
50
|
+
height: var(--fui-space-1, $fui-space-1); // 7px
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
.trackLg {
|
|
54
|
-
height:
|
|
54
|
+
height: var(--fui-space-2, $fui-space-2); // 14px
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// Indicator (filled portion)
|
|
@@ -160,10 +160,10 @@
|
|
|
160
160
|
font-variant-numeric: tabular-nums;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
// Size variants for circular
|
|
163
|
+
// Size variants for circular (using closest 7px grid values)
|
|
164
164
|
.circularSm {
|
|
165
|
-
width:
|
|
166
|
-
height:
|
|
165
|
+
width: var(--fui-space-4, $fui-space-4); // 28px
|
|
166
|
+
height: var(--fui-space-4, $fui-space-4);
|
|
167
167
|
|
|
168
168
|
.circularValue {
|
|
169
169
|
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
@@ -171,13 +171,13 @@
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
.circularMd {
|
|
174
|
-
width:
|
|
175
|
-
height:
|
|
174
|
+
width: var(--fui-space-6, $fui-space-6); // 42px
|
|
175
|
+
height: var(--fui-space-6, $fui-space-6);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
.circularLg {
|
|
179
|
-
width:
|
|
180
|
-
height:
|
|
179
|
+
width: var(--fui-space-8, $fui-space-8); // 56px
|
|
180
|
+
height: var(--fui-space-8, $fui-space-8);
|
|
181
181
|
|
|
182
182
|
.circularValue {
|
|
183
183
|
font-size: var(--fui-font-size-base, $fui-font-size-base);
|
|
@@ -8,7 +8,7 @@ import '../../styles/globals.scss';
|
|
|
8
8
|
// Types
|
|
9
9
|
// ============================================
|
|
10
10
|
|
|
11
|
-
export interface ProgressProps {
|
|
11
|
+
export interface ProgressProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'defaultValue'> {
|
|
12
12
|
/** Current progress value (0-100). Null for indeterminate. */
|
|
13
13
|
value?: number | null;
|
|
14
14
|
/** Minimum value */
|
|
@@ -25,11 +25,9 @@ export interface ProgressProps {
|
|
|
25
25
|
showValue?: boolean;
|
|
26
26
|
/** Custom value formatter */
|
|
27
27
|
formatValue?: (value: number) => string;
|
|
28
|
-
/** Additional class name */
|
|
29
|
-
className?: string;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
export interface CircularProgressProps {
|
|
30
|
+
export interface CircularProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
33
31
|
/** Current progress value (0-100). Null for indeterminate. */
|
|
34
32
|
value?: number | null;
|
|
35
33
|
/** Size of the circular progress */
|
|
@@ -40,8 +38,6 @@ export interface CircularProgressProps {
|
|
|
40
38
|
showValue?: boolean;
|
|
41
39
|
/** Stroke width */
|
|
42
40
|
strokeWidth?: number;
|
|
43
|
-
/** Additional class name */
|
|
44
|
-
className?: string;
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
// ============================================
|
|
@@ -58,6 +54,9 @@ export function Progress({
|
|
|
58
54
|
showValue = false,
|
|
59
55
|
formatValue,
|
|
60
56
|
className,
|
|
57
|
+
'aria-label': ariaLabel,
|
|
58
|
+
'aria-valuetext': ariaValueText,
|
|
59
|
+
...htmlProps
|
|
61
60
|
}: ProgressProps) {
|
|
62
61
|
const isIndeterminate = value === null;
|
|
63
62
|
const percentage = isIndeterminate ? 0 : Math.round(((value - min) / (max - min)) * 100);
|
|
@@ -83,12 +82,21 @@ export function Progress({
|
|
|
83
82
|
? formatValue(percentage)
|
|
84
83
|
: `${percentage}%`;
|
|
85
84
|
|
|
85
|
+
// Default value text for screen readers
|
|
86
|
+
const effectiveValueText = ariaValueText || (
|
|
87
|
+
isIndeterminate ? 'Loading' : `${percentage} percent`
|
|
88
|
+
);
|
|
89
|
+
|
|
86
90
|
return (
|
|
87
91
|
<BaseProgress.Root
|
|
92
|
+
{...htmlProps}
|
|
88
93
|
value={value}
|
|
89
94
|
min={min}
|
|
90
95
|
max={max}
|
|
91
96
|
className={rootClasses}
|
|
97
|
+
aria-label={ariaLabel || (label ? undefined : 'Progress')}
|
|
98
|
+
aria-valuetext={effectiveValueText}
|
|
99
|
+
aria-busy={isIndeterminate}
|
|
92
100
|
>
|
|
93
101
|
{(label || showValue) && (
|
|
94
102
|
<div className={styles.header}>
|
|
@@ -129,6 +137,10 @@ export function CircularProgress({
|
|
|
129
137
|
showValue = false,
|
|
130
138
|
strokeWidth: customStrokeWidth,
|
|
131
139
|
className,
|
|
140
|
+
'aria-label': ariaLabel,
|
|
141
|
+
'aria-labelledby': ariaLabelledBy,
|
|
142
|
+
'aria-valuetext': ariaValueText,
|
|
143
|
+
...htmlProps
|
|
132
144
|
}: CircularProgressProps) {
|
|
133
145
|
const isIndeterminate = value === null;
|
|
134
146
|
const percentage = isIndeterminate ? 0 : Math.min(100, Math.max(0, value));
|
|
@@ -156,17 +168,32 @@ export function CircularProgress({
|
|
|
156
168
|
.filter(Boolean)
|
|
157
169
|
.join(' ');
|
|
158
170
|
|
|
171
|
+
// Default value text for screen readers
|
|
172
|
+
const effectiveValueText = ariaValueText || (
|
|
173
|
+
isIndeterminate ? 'Loading' : `${Math.round(percentage)} percent`
|
|
174
|
+
);
|
|
175
|
+
|
|
159
176
|
return (
|
|
160
177
|
<BaseProgress.Root
|
|
178
|
+
{...htmlProps}
|
|
161
179
|
value={value}
|
|
180
|
+
min={0}
|
|
181
|
+
max={100}
|
|
162
182
|
className={rootClasses}
|
|
183
|
+
aria-label={ariaLabel}
|
|
184
|
+
aria-labelledby={ariaLabelledBy}
|
|
163
185
|
aria-valuenow={isIndeterminate ? undefined : percentage}
|
|
186
|
+
aria-valuemin={0}
|
|
187
|
+
aria-valuemax={100}
|
|
188
|
+
aria-valuetext={effectiveValueText}
|
|
189
|
+
aria-busy={isIndeterminate}
|
|
164
190
|
>
|
|
165
191
|
<svg
|
|
166
192
|
className={styles.circularSvg}
|
|
167
193
|
width={svgSize}
|
|
168
194
|
height={svgSize}
|
|
169
195
|
viewBox={`0 0 ${svgSize} ${svgSize}`}
|
|
196
|
+
aria-hidden="true"
|
|
170
197
|
>
|
|
171
198
|
{/* Track circle */}
|
|
172
199
|
<circle
|
|
@@ -189,7 +216,7 @@ export function CircularProgress({
|
|
|
189
216
|
/>
|
|
190
217
|
</svg>
|
|
191
218
|
{showValue && !isIndeterminate && (
|
|
192
|
-
<span className={styles.circularValue}>{Math.round(percentage)}%</span>
|
|
219
|
+
<span className={styles.circularValue} aria-hidden="true">{Math.round(percentage)}%</span>
|
|
193
220
|
)}
|
|
194
221
|
</BaseProgress.Root>
|
|
195
222
|
);
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Prompt } from './index.js';
|
|
4
|
+
|
|
5
|
+
export default defineSegment({
|
|
6
|
+
component: Prompt,
|
|
7
|
+
|
|
8
|
+
meta: {
|
|
9
|
+
name: 'Prompt',
|
|
10
|
+
description: 'Multi-line input with toolbar for AI/chat interfaces',
|
|
11
|
+
category: 'forms',
|
|
12
|
+
status: 'stable',
|
|
13
|
+
tags: ['prompt', 'chat', 'ai', 'input', 'textarea', 'form'],
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
usage: {
|
|
17
|
+
when: [
|
|
18
|
+
'Building chat or AI assistant interfaces',
|
|
19
|
+
'Need multi-line input with submit action',
|
|
20
|
+
'Require toolbar with actions like attachments or model selection',
|
|
21
|
+
],
|
|
22
|
+
whenNot: [
|
|
23
|
+
'Simple single-line text input (use Input)',
|
|
24
|
+
'Basic multi-line without toolbar (use Textarea)',
|
|
25
|
+
'Search-only interface (use Input with search variant)',
|
|
26
|
+
],
|
|
27
|
+
guidelines: [
|
|
28
|
+
'Always provide an onSubmit handler',
|
|
29
|
+
'Use loading state during API calls',
|
|
30
|
+
'Consider showing usage/token limits for AI contexts',
|
|
31
|
+
],
|
|
32
|
+
accessibility: [
|
|
33
|
+
'Enter submits, Shift+Enter for newline',
|
|
34
|
+
'Submit button is keyboard accessible',
|
|
35
|
+
'Loading state prevents duplicate submissions',
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
props: {
|
|
40
|
+
value: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
description: 'Controlled input value',
|
|
43
|
+
},
|
|
44
|
+
defaultValue: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'Uncontrolled default value',
|
|
47
|
+
},
|
|
48
|
+
onChange: {
|
|
49
|
+
type: 'function',
|
|
50
|
+
description: 'Called when value changes',
|
|
51
|
+
},
|
|
52
|
+
onSubmit: {
|
|
53
|
+
type: 'function',
|
|
54
|
+
description: 'Called on form submission',
|
|
55
|
+
},
|
|
56
|
+
placeholder: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
default: '"Ask, Search or Chat..."',
|
|
59
|
+
description: 'Placeholder text for the textarea',
|
|
60
|
+
},
|
|
61
|
+
disabled: {
|
|
62
|
+
type: 'boolean',
|
|
63
|
+
default: 'false',
|
|
64
|
+
description: 'Disable the entire prompt',
|
|
65
|
+
},
|
|
66
|
+
loading: {
|
|
67
|
+
type: 'boolean',
|
|
68
|
+
default: 'false',
|
|
69
|
+
description: 'Show loading state',
|
|
70
|
+
},
|
|
71
|
+
minRows: {
|
|
72
|
+
type: 'number',
|
|
73
|
+
default: '1',
|
|
74
|
+
description: 'Minimum number of visible rows',
|
|
75
|
+
},
|
|
76
|
+
maxRows: {
|
|
77
|
+
type: 'number',
|
|
78
|
+
default: '8',
|
|
79
|
+
description: 'Maximum number of visible rows',
|
|
80
|
+
},
|
|
81
|
+
autoResize: {
|
|
82
|
+
type: 'boolean',
|
|
83
|
+
default: 'true',
|
|
84
|
+
description: 'Enable auto-resize based on content',
|
|
85
|
+
},
|
|
86
|
+
submitOnEnter: {
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
default: 'true',
|
|
89
|
+
description: 'Submit on Enter (Shift+Enter for newline)',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
relations: [
|
|
94
|
+
{
|
|
95
|
+
component: 'Input',
|
|
96
|
+
relationship: 'alternative',
|
|
97
|
+
note: 'Use Input for simple single-line text input',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
component: 'Textarea',
|
|
101
|
+
relationship: 'alternative',
|
|
102
|
+
note: 'Use Textarea for multi-line without toolbar',
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
|
|
106
|
+
contract: {
|
|
107
|
+
propsSummary: [
|
|
108
|
+
'value: string - controlled input value',
|
|
109
|
+
'onSubmit: (value: string) => void - submission handler',
|
|
110
|
+
'placeholder: string - hint text (default: "Ask, Search or Chat...")',
|
|
111
|
+
'disabled: boolean - disables interaction',
|
|
112
|
+
'loading: boolean - shows loading state',
|
|
113
|
+
'minRows/maxRows: number - row constraints (default: 1/8)',
|
|
114
|
+
'submitOnEnter: boolean - Enter key behavior (default: true)',
|
|
115
|
+
],
|
|
116
|
+
scenarioTags: [
|
|
117
|
+
'form.prompt',
|
|
118
|
+
'form.chat',
|
|
119
|
+
'form.ai',
|
|
120
|
+
'ui.chat-input',
|
|
121
|
+
],
|
|
122
|
+
a11yRules: [
|
|
123
|
+
'A11Y_TEXTAREA_LABEL',
|
|
124
|
+
'A11Y_BUTTON_LABEL',
|
|
125
|
+
],
|
|
126
|
+
bans: [],
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
variants: [
|
|
130
|
+
{
|
|
131
|
+
name: 'Basic',
|
|
132
|
+
description: 'Simple prompt with submit button',
|
|
133
|
+
render: () => (
|
|
134
|
+
<Prompt onSubmit={(value) => console.log(value)}>
|
|
135
|
+
<Prompt.Textarea />
|
|
136
|
+
<Prompt.Toolbar>
|
|
137
|
+
<Prompt.Actions />
|
|
138
|
+
<Prompt.Info>
|
|
139
|
+
<Prompt.Submit />
|
|
140
|
+
</Prompt.Info>
|
|
141
|
+
</Prompt.Toolbar>
|
|
142
|
+
</Prompt>
|
|
143
|
+
),
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'With Actions',
|
|
147
|
+
description: 'Prompt with attachment and mode buttons',
|
|
148
|
+
render: () => (
|
|
149
|
+
<Prompt onSubmit={(value) => console.log(value)}>
|
|
150
|
+
<Prompt.Textarea />
|
|
151
|
+
<Prompt.Toolbar>
|
|
152
|
+
<Prompt.Actions>
|
|
153
|
+
<Prompt.ActionButton aria-label="Add attachment">
|
|
154
|
+
+
|
|
155
|
+
</Prompt.ActionButton>
|
|
156
|
+
<Prompt.ModeButton>Auto</Prompt.ModeButton>
|
|
157
|
+
</Prompt.Actions>
|
|
158
|
+
<Prompt.Info>
|
|
159
|
+
<Prompt.Submit />
|
|
160
|
+
</Prompt.Info>
|
|
161
|
+
</Prompt.Toolbar>
|
|
162
|
+
</Prompt>
|
|
163
|
+
),
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'With Usage',
|
|
167
|
+
description: 'Shows token usage indicator',
|
|
168
|
+
render: () => (
|
|
169
|
+
<Prompt onSubmit={(value) => console.log(value)}>
|
|
170
|
+
<Prompt.Textarea />
|
|
171
|
+
<Prompt.Toolbar>
|
|
172
|
+
<Prompt.Actions>
|
|
173
|
+
<Prompt.ActionButton aria-label="Add attachment">
|
|
174
|
+
+
|
|
175
|
+
</Prompt.ActionButton>
|
|
176
|
+
<Prompt.ModeButton active>Auto</Prompt.ModeButton>
|
|
177
|
+
</Prompt.Actions>
|
|
178
|
+
<Prompt.Info>
|
|
179
|
+
<Prompt.Usage>52% used</Prompt.Usage>
|
|
180
|
+
<Prompt.Submit />
|
|
181
|
+
</Prompt.Info>
|
|
182
|
+
</Prompt.Toolbar>
|
|
183
|
+
</Prompt>
|
|
184
|
+
),
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'Loading State',
|
|
188
|
+
description: 'During API submission',
|
|
189
|
+
render: () => (
|
|
190
|
+
<Prompt
|
|
191
|
+
onSubmit={(value) => console.log(value)}
|
|
192
|
+
loading
|
|
193
|
+
defaultValue="Tell me about the weather..."
|
|
194
|
+
>
|
|
195
|
+
<Prompt.Textarea />
|
|
196
|
+
<Prompt.Toolbar>
|
|
197
|
+
<Prompt.Actions>
|
|
198
|
+
<Prompt.ActionButton aria-label="Add attachment">
|
|
199
|
+
+
|
|
200
|
+
</Prompt.ActionButton>
|
|
201
|
+
<Prompt.ModeButton>Auto</Prompt.ModeButton>
|
|
202
|
+
</Prompt.Actions>
|
|
203
|
+
<Prompt.Info>
|
|
204
|
+
<Prompt.Usage>52% used</Prompt.Usage>
|
|
205
|
+
<Prompt.Submit />
|
|
206
|
+
</Prompt.Info>
|
|
207
|
+
</Prompt.Toolbar>
|
|
208
|
+
</Prompt>
|
|
209
|
+
),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'Disabled',
|
|
213
|
+
description: 'Non-interactive prompt',
|
|
214
|
+
render: () => (
|
|
215
|
+
<Prompt onSubmit={(value) => console.log(value)} disabled>
|
|
216
|
+
<Prompt.Textarea />
|
|
217
|
+
<Prompt.Toolbar>
|
|
218
|
+
<Prompt.Actions>
|
|
219
|
+
<Prompt.ActionButton aria-label="Add attachment">
|
|
220
|
+
+
|
|
221
|
+
</Prompt.ActionButton>
|
|
222
|
+
</Prompt.Actions>
|
|
223
|
+
<Prompt.Info>
|
|
224
|
+
<Prompt.Submit />
|
|
225
|
+
</Prompt.Info>
|
|
226
|
+
</Prompt.Toolbar>
|
|
227
|
+
</Prompt>
|
|
228
|
+
),
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
@use '../../tokens/mixins' as *;
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// Prompt Root
|
|
6
|
+
// ============================================
|
|
7
|
+
|
|
8
|
+
.prompt {
|
|
9
|
+
@include surface-elevated;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
border-radius: var(--fui-radius-xl, $fui-radius-xl);
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
|
|
15
|
+
&[data-disabled] {
|
|
16
|
+
opacity: 0.5;
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Fixed variant - for viewport-fixed prompts (centered to page)
|
|
22
|
+
.fixed {
|
|
23
|
+
position: fixed;
|
|
24
|
+
bottom: var(--fui-space-4, $fui-space-4);
|
|
25
|
+
left: 50%;
|
|
26
|
+
transform: translateX(-50%);
|
|
27
|
+
width: calc(100% - var(--fui-space-8, $fui-space-8));
|
|
28
|
+
max-width: 800px;
|
|
29
|
+
z-index: 100;
|
|
30
|
+
box-shadow:
|
|
31
|
+
0 -4px 16px rgba(0, 0, 0, 0.08),
|
|
32
|
+
0 4px 24px rgba(0, 0, 0, 0.12);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Sticky variant - for content-area fixed prompts (accounts for sidebar)
|
|
36
|
+
// Use --fui-prompt-inset-left CSS variable to offset from sidebar
|
|
37
|
+
.sticky {
|
|
38
|
+
position: fixed;
|
|
39
|
+
bottom: var(--fui-space-4, $fui-space-4);
|
|
40
|
+
left: var(--fui-prompt-inset-left, 0);
|
|
41
|
+
right: 0;
|
|
42
|
+
margin-left: auto;
|
|
43
|
+
margin-right: auto;
|
|
44
|
+
width: calc(100% - var(--fui-prompt-inset-left, 0px) - var(--fui-space-8, $fui-space-8));
|
|
45
|
+
max-width: 800px;
|
|
46
|
+
z-index: 100;
|
|
47
|
+
box-shadow:
|
|
48
|
+
0 -4px 16px rgba(0, 0, 0, 0.08),
|
|
49
|
+
0 4px 24px rgba(0, 0, 0, 0.12);
|
|
50
|
+
|
|
51
|
+
// Center within the available space (after sidebar)
|
|
52
|
+
left: calc(var(--fui-prompt-inset-left, 0px) + ((100% - var(--fui-prompt-inset-left, 0px)) / 2));
|
|
53
|
+
transform: translateX(-50%);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================
|
|
57
|
+
// Textarea
|
|
58
|
+
// ============================================
|
|
59
|
+
|
|
60
|
+
.textarea {
|
|
61
|
+
@include button-reset;
|
|
62
|
+
@include text-base;
|
|
63
|
+
|
|
64
|
+
display: block;
|
|
65
|
+
width: 100%;
|
|
66
|
+
min-height: 2.857rem; // ~40px
|
|
67
|
+
padding: var(--fui-space-2, $fui-space-2) var(--fui-space-3, $fui-space-3);
|
|
68
|
+
background: transparent;
|
|
69
|
+
border: none;
|
|
70
|
+
resize: none;
|
|
71
|
+
overflow-y: auto;
|
|
72
|
+
line-height: $fui-line-height-normal;
|
|
73
|
+
|
|
74
|
+
&::placeholder {
|
|
75
|
+
color: var(--fui-text-tertiary, $fui-text-tertiary);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&:focus {
|
|
79
|
+
outline: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&:disabled {
|
|
83
|
+
cursor: not-allowed;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ============================================
|
|
88
|
+
// Toolbar
|
|
89
|
+
// ============================================
|
|
90
|
+
|
|
91
|
+
.toolbar {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: space-between;
|
|
95
|
+
gap: var(--fui-space-2, $fui-space-2);
|
|
96
|
+
padding: var(--fui-space-1, $fui-space-1) var(--fui-space-2, $fui-space-2);
|
|
97
|
+
background-color: var(--fui-bg-secondary, $fui-bg-secondary);
|
|
98
|
+
border-top: 1px solid var(--fui-border, $fui-border);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============================================
|
|
102
|
+
// Actions & Info
|
|
103
|
+
// ============================================
|
|
104
|
+
|
|
105
|
+
.actions {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
gap: var(--fui-space-1, $fui-space-1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.info {
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: var(--fui-space-2, $fui-space-2);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================
|
|
118
|
+
// Action Button (circular, e.g. "+")
|
|
119
|
+
// ============================================
|
|
120
|
+
|
|
121
|
+
.actionButton {
|
|
122
|
+
@include button-reset;
|
|
123
|
+
@include interactive-base;
|
|
124
|
+
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
justify-content: center;
|
|
128
|
+
width: 2rem;
|
|
129
|
+
height: 2rem;
|
|
130
|
+
border-radius: var(--fui-radius-full, $fui-radius-full);
|
|
131
|
+
background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
|
|
132
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
133
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
134
|
+
|
|
135
|
+
&:hover:not(:disabled) {
|
|
136
|
+
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&:active:not(:disabled) {
|
|
140
|
+
background-color: var(--fui-bg-active, $fui-bg-active);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
svg {
|
|
144
|
+
width: 1rem;
|
|
145
|
+
height: 1rem;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============================================
|
|
150
|
+
// Mode Button (text button, e.g. "Auto")
|
|
151
|
+
// ============================================
|
|
152
|
+
|
|
153
|
+
.modeButton {
|
|
154
|
+
@include button-reset;
|
|
155
|
+
@include interactive-base;
|
|
156
|
+
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
gap: var(--fui-space-1, $fui-space-1);
|
|
160
|
+
padding: var(--fui-space-1, $fui-space-1) var(--fui-space-2, $fui-space-2);
|
|
161
|
+
border-radius: var(--fui-radius-md, $fui-radius-md);
|
|
162
|
+
background-color: transparent;
|
|
163
|
+
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
164
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
165
|
+
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
166
|
+
|
|
167
|
+
&:hover:not(:disabled) {
|
|
168
|
+
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
169
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
&:active:not(:disabled) {
|
|
173
|
+
background-color: var(--fui-bg-active, $fui-bg-active);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.modeButtonActive {
|
|
178
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
179
|
+
background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ============================================
|
|
183
|
+
// Usage indicator
|
|
184
|
+
// ============================================
|
|
185
|
+
|
|
186
|
+
.usage {
|
|
187
|
+
@include helper-text;
|
|
188
|
+
white-space: nowrap;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ============================================
|
|
192
|
+
// Submit Button
|
|
193
|
+
// ============================================
|
|
194
|
+
|
|
195
|
+
.submit {
|
|
196
|
+
@include button-reset;
|
|
197
|
+
@include interactive-base;
|
|
198
|
+
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
justify-content: center;
|
|
202
|
+
width: 2rem;
|
|
203
|
+
height: 2rem;
|
|
204
|
+
border-radius: var(--fui-radius-full, $fui-radius-full);
|
|
205
|
+
background-color: var(--fui-color-accent, $fui-color-accent);
|
|
206
|
+
color: var(--fui-text-inverse, $fui-text-inverse);
|
|
207
|
+
|
|
208
|
+
&:hover:not(:disabled) {
|
|
209
|
+
background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
&:active:not(:disabled) {
|
|
213
|
+
background-color: var(--fui-color-accent-active, $fui-color-accent-active);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
&:disabled {
|
|
217
|
+
opacity: 0.5;
|
|
218
|
+
cursor: not-allowed;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
svg {
|
|
222
|
+
width: 1rem;
|
|
223
|
+
height: 1rem;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.submitLoading {
|
|
228
|
+
position: relative;
|
|
229
|
+
|
|
230
|
+
svg {
|
|
231
|
+
animation: pulse 1s ease-in-out infinite;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@keyframes pulse {
|
|
236
|
+
0%,
|
|
237
|
+
100% {
|
|
238
|
+
opacity: 1;
|
|
239
|
+
}
|
|
240
|
+
50% {
|
|
241
|
+
opacity: 0.5;
|
|
242
|
+
}
|
|
243
|
+
}
|