@fragments-sdk/ui 0.4.0 → 0.5.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 (94) hide show
  1. package/README.md +98 -2
  2. package/fragments.json +1 -1
  3. package/package.json +3 -2
  4. package/src/components/Accordion/Accordion.fragment.tsx +1 -1
  5. package/src/components/Alert/Alert.fragment.tsx +1 -1
  6. package/src/components/AppShell/AppShell.fragment.tsx +4 -4
  7. package/src/components/Avatar/Avatar.fragment.tsx +2 -2
  8. package/src/components/Badge/Badge.fragment.tsx +2 -2
  9. package/src/components/Badge/Badge.module.scss +1 -1
  10. package/src/components/Box/Box.fragment.tsx +1 -1
  11. package/src/components/Button/Button.fragment.tsx +2 -2
  12. package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +153 -0
  13. package/src/components/Card/Card.fragment.tsx +1 -1
  14. package/src/components/Chart/Chart.fragment.tsx +213 -0
  15. package/src/components/Chart/Chart.module.scss +123 -0
  16. package/src/components/Chart/index.tsx +267 -0
  17. package/src/components/Checkbox/Checkbox.fragment.tsx +1 -1
  18. package/src/components/CodeBlock/CodeBlock.fragment.tsx +265 -6
  19. package/src/components/CodeBlock/CodeBlock.module.scss +141 -3
  20. package/src/components/CodeBlock/index.tsx +250 -36
  21. package/src/components/Collapsible/Collapsible.fragment.tsx +199 -0
  22. package/src/components/Collapsible/Collapsible.module.scss +117 -0
  23. package/src/components/Collapsible/index.tsx +219 -0
  24. package/src/components/ColorPicker/ColorPicker.fragment.tsx +196 -0
  25. package/src/components/ColorPicker/ColorPicker.module.scss +33 -23
  26. package/src/components/ColorPicker/index.tsx +34 -12
  27. package/src/components/ConversationList/ConversationList.fragment.tsx +202 -0
  28. package/src/components/ConversationList/ConversationList.module.scss +160 -0
  29. package/src/components/ConversationList/index.tsx +254 -0
  30. package/src/components/Dialog/Dialog.fragment.tsx +3 -3
  31. package/src/components/EmptyState/EmptyState.fragment.tsx +2 -2
  32. package/src/components/Field/Field.fragment.tsx +3 -3
  33. package/src/components/Fieldset/Fieldset.fragment.tsx +7 -7
  34. package/src/components/Form/Form.fragment.tsx +11 -11
  35. package/src/components/Grid/Grid.fragment.tsx +1 -1
  36. package/src/components/Header/Header.fragment.tsx +4 -4
  37. package/src/components/Header/Header.module.scss +9 -10
  38. package/src/components/Icon/Icon.fragment.tsx +2 -2
  39. package/src/components/Image/Image.fragment.tsx +2 -2
  40. package/src/components/Input/Input.fragment.tsx +1 -1
  41. package/src/components/Input/Input.module.scss +2 -2
  42. package/src/components/Link/Link.fragment.tsx +1 -1
  43. package/src/components/List/List.fragment.tsx +2 -2
  44. package/src/components/Listbox/Listbox.fragment.tsx +1 -1
  45. package/src/components/Loading/Loading.fragment.tsx +153 -0
  46. package/src/components/Loading/Loading.module.scss +256 -0
  47. package/src/components/Loading/index.tsx +236 -0
  48. package/src/components/Menu/Menu.fragment.tsx +3 -3
  49. package/src/components/Message/Message.fragment.tsx +200 -0
  50. package/src/components/Message/Message.module.scss +224 -0
  51. package/src/components/Message/index.tsx +278 -0
  52. package/src/components/Popover/Popover.fragment.tsx +4 -4
  53. package/src/components/Progress/Progress.fragment.tsx +1 -1
  54. package/src/components/Prompt/Prompt.fragment.tsx +2 -2
  55. package/src/components/RadioGroup/RadioGroup.fragment.tsx +1 -1
  56. package/src/components/RadioGroup/RadioGroup.module.scss +7 -4
  57. package/src/components/Select/Select.fragment.tsx +1 -1
  58. package/src/components/Select/Select.module.scss +8 -0
  59. package/src/components/Select/index.tsx +85 -5
  60. package/src/components/Separator/Separator.fragment.tsx +1 -1
  61. package/src/components/Sidebar/Sidebar.fragment.tsx +2 -2
  62. package/src/components/Sidebar/Sidebar.module.scss +19 -0
  63. package/src/components/Sidebar/index.tsx +52 -11
  64. package/src/components/Skeleton/Skeleton.fragment.tsx +1 -1
  65. package/src/components/Slider/Slider.fragment.tsx +201 -0
  66. package/src/components/Stack/Stack.fragment.tsx +194 -0
  67. package/src/components/Table/Table.fragment.tsx +3 -3
  68. package/src/components/Tabs/Tabs.fragment.tsx +1 -1
  69. package/src/components/Tabs/Tabs.module.scss +2 -2
  70. package/src/components/Text/Text.fragment.tsx +188 -0
  71. package/src/components/Textarea/Textarea.fragment.tsx +1 -1
  72. package/src/components/Theme/Theme.fragment.tsx +2 -2
  73. package/src/components/Theme/ThemeToggle.module.scss +13 -13
  74. package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +182 -0
  75. package/src/components/ThinkingIndicator/ThinkingIndicator.module.scss +226 -0
  76. package/src/components/ThinkingIndicator/index.tsx +258 -0
  77. package/src/components/Toast/Toast.fragment.tsx +1 -1
  78. package/src/components/Toggle/Toggle.fragment.tsx +1 -1
  79. package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +207 -0
  80. package/src/components/Tooltip/Tooltip.fragment.tsx +3 -3
  81. package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +2 -2
  82. package/src/index.ts +86 -3
  83. package/src/recipes/AIChat.recipe.ts +266 -0
  84. package/src/tokens/_computed.scss +212 -0
  85. package/src/tokens/_density.scss +171 -0
  86. package/src/tokens/_derive.scss +287 -0
  87. package/src/tokens/_index.scss +39 -1
  88. package/src/tokens/_mixins.scss +41 -0
  89. package/src/tokens/_palettes.scss +185 -0
  90. package/src/tokens/_radius.scss +107 -0
  91. package/src/tokens/_seeds.scss +59 -0
  92. package/src/tokens/_variables.scss +171 -130
  93. package/src/components/ColorChip/ColorChip.module.scss +0 -165
  94. package/src/components/ColorChip/index.tsx +0 -157
@@ -0,0 +1,194 @@
1
+ import React from 'react';
2
+ import { defineSegment } from '@fragments/core';
3
+ import { Stack } from '.';
4
+ import { Button } from '../Button';
5
+ import { Badge } from '../Badge';
6
+
7
+ export default defineSegment({
8
+ component: Stack,
9
+
10
+ meta: {
11
+ name: 'Stack',
12
+ description: 'Flexible layout component for arranging children in rows or columns with consistent spacing. Supports responsive direction and gap.',
13
+ category: 'layout',
14
+ status: 'stable',
15
+ tags: ['stack', 'layout', 'flex', 'spacing', 'responsive'],
16
+ since: '0.2.0',
17
+ },
18
+
19
+ usage: {
20
+ when: [
21
+ 'Arranging elements in a row or column',
22
+ 'Creating consistent spacing between items',
23
+ 'Building responsive layouts',
24
+ 'Simple flexbox-based arrangements',
25
+ ],
26
+ whenNot: [
27
+ 'Complex grid layouts (use Grid)',
28
+ 'Button-specific grouping (use ButtonGroup)',
29
+ 'Page-level layout (use AppShell)',
30
+ ],
31
+ guidelines: [
32
+ 'Use semantic elements via the "as" prop when appropriate',
33
+ 'Leverage responsive props for mobile-first layouts',
34
+ 'Keep spacing consistent within related sections',
35
+ 'Consider alignment for visual balance',
36
+ ],
37
+ accessibility: [
38
+ 'Use semantic elements (nav, section, etc.) via "as" prop',
39
+ 'Maintains source order for screen readers',
40
+ 'No accessibility concerns with visual arrangement',
41
+ ],
42
+ },
43
+
44
+ props: {
45
+ children: {
46
+ type: 'node',
47
+ description: 'Elements to arrange',
48
+ required: true,
49
+ },
50
+ direction: {
51
+ type: 'string | object',
52
+ description: 'Stack direction: "row", "column", or responsive object',
53
+ default: 'column',
54
+ },
55
+ gap: {
56
+ type: 'string | object',
57
+ description: 'Spacing between items: "none", "xs", "sm", "md", "lg", "xl", or responsive object',
58
+ default: 'md',
59
+ },
60
+ align: {
61
+ type: 'enum',
62
+ description: 'Cross-axis alignment',
63
+ values: ['start', 'center', 'end', 'stretch', 'baseline'],
64
+ },
65
+ justify: {
66
+ type: 'enum',
67
+ description: 'Main-axis alignment',
68
+ values: ['start', 'center', 'end', 'between'],
69
+ },
70
+ wrap: {
71
+ type: 'boolean',
72
+ description: 'Allow items to wrap',
73
+ default: 'false',
74
+ },
75
+ as: {
76
+ type: 'enum',
77
+ description: 'HTML element to render',
78
+ values: ['div', 'section', 'nav', 'article', 'aside', 'header', 'footer', 'main', 'ul', 'ol'],
79
+ default: 'div',
80
+ },
81
+ },
82
+
83
+ relations: [
84
+ { component: 'Grid', relationship: 'alternative', note: 'Use Grid for complex 2D layouts' },
85
+ { component: 'ButtonGroup', relationship: 'sibling', note: 'ButtonGroup is specialized for buttons' },
86
+ { component: 'Box', relationship: 'sibling', note: 'Box for single-element styling' },
87
+ ],
88
+
89
+ contract: {
90
+ propsSummary: [
91
+ 'direction: row|column|{responsive} - stack direction',
92
+ 'gap: none|xs|sm|md|lg|xl|{responsive} - spacing',
93
+ 'align: start|center|end|stretch|baseline - cross-axis',
94
+ 'justify: start|center|end|between - main-axis',
95
+ 'wrap: boolean - allow wrapping',
96
+ 'as: string - HTML element',
97
+ ],
98
+ scenarioTags: [
99
+ 'layout.flex',
100
+ 'spacing.consistent',
101
+ 'responsive.layout',
102
+ ],
103
+ a11yRules: ['A11Y_SEMANTIC_ELEMENTS'],
104
+ },
105
+
106
+ variants: [
107
+ {
108
+ name: 'Vertical Stack',
109
+ description: 'Default column layout',
110
+ render: () => (
111
+ <Stack gap="sm">
112
+ <Badge>Item 1</Badge>
113
+ <Badge>Item 2</Badge>
114
+ <Badge>Item 3</Badge>
115
+ </Stack>
116
+ ),
117
+ },
118
+ {
119
+ name: 'Horizontal Stack',
120
+ description: 'Row layout',
121
+ render: () => (
122
+ <Stack direction="row" gap="sm">
123
+ <Badge>Item 1</Badge>
124
+ <Badge>Item 2</Badge>
125
+ <Badge>Item 3</Badge>
126
+ </Stack>
127
+ ),
128
+ },
129
+ {
130
+ name: 'Gap Sizes',
131
+ description: 'Different spacing options',
132
+ render: () => (
133
+ <Stack gap="lg">
134
+ <Stack direction="row" gap="xs">
135
+ <Badge variant="info">XS</Badge>
136
+ <Badge variant="info">Gap</Badge>
137
+ </Stack>
138
+ <Stack direction="row" gap="sm">
139
+ <Badge variant="info">SM</Badge>
140
+ <Badge variant="info">Gap</Badge>
141
+ </Stack>
142
+ <Stack direction="row" gap="md">
143
+ <Badge variant="info">MD</Badge>
144
+ <Badge variant="info">Gap</Badge>
145
+ </Stack>
146
+ <Stack direction="row" gap="lg">
147
+ <Badge variant="info">LG</Badge>
148
+ <Badge variant="info">Gap</Badge>
149
+ </Stack>
150
+ </Stack>
151
+ ),
152
+ },
153
+ {
154
+ name: 'Alignment',
155
+ description: 'Cross-axis and main-axis alignment',
156
+ render: () => (
157
+ <Stack gap="md">
158
+ <Stack direction="row" gap="sm" justify="between" style={{ width: '200px', padding: '8px', background: 'var(--fui-bg-secondary)', borderRadius: '4px' }}>
159
+ <Badge>Start</Badge>
160
+ <Badge>End</Badge>
161
+ </Stack>
162
+ <Stack direction="row" gap="sm" justify="center" style={{ width: '200px', padding: '8px', background: 'var(--fui-bg-secondary)', borderRadius: '4px' }}>
163
+ <Badge>Centered</Badge>
164
+ </Stack>
165
+ </Stack>
166
+ ),
167
+ },
168
+ {
169
+ name: 'Responsive',
170
+ description: 'Direction changes at breakpoints',
171
+ render: () => (
172
+ <Stack
173
+ direction={{ base: 'column', md: 'row' }}
174
+ gap={{ base: 'sm', md: 'lg' }}
175
+ >
176
+ <Button variant="secondary">First</Button>
177
+ <Button variant="secondary">Second</Button>
178
+ <Button variant="secondary">Third</Button>
179
+ </Stack>
180
+ ),
181
+ },
182
+ {
183
+ name: 'Semantic Element',
184
+ description: 'Using nav element for navigation',
185
+ render: () => (
186
+ <Stack as="nav" direction="row" gap="md">
187
+ <Button variant="ghost" size="sm">Home</Button>
188
+ <Button variant="ghost" size="sm">About</Button>
189
+ <Button variant="ghost" size="sm">Contact</Button>
190
+ </Stack>
191
+ ),
192
+ },
193
+ ],
194
+ });
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Table, createColumns } from './index.js';
4
- import { Badge } from '../Badge/index.js';
3
+ import { Table, createColumns } from '.';
4
+ import { Badge } from '../Badge';
5
5
 
6
6
  // Sample data types
7
7
  interface User {
@@ -43,7 +43,7 @@ export default defineSegment({
43
43
  meta: {
44
44
  name: 'Table',
45
45
  description: 'Data table with sorting and row selection. Use for displaying structured data that needs to be scanned, compared, or acted upon.',
46
- category: 'data-display',
46
+ category: 'display',
47
47
  status: 'stable',
48
48
  tags: ['table', 'data', 'grid', 'list', 'sorting'],
49
49
  since: '0.1.0',
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Tabs } from './index.js';
3
+ import { Tabs } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: Tabs,
@@ -35,8 +35,8 @@
35
35
  border-bottom: none;
36
36
  background-color: var(--fui-bg-secondary, $fui-bg-secondary);
37
37
  border-radius: var(--fui-radius-lg, $fui-radius-lg);
38
- padding: 4px;
39
- gap: 4px;
38
+ padding: var(--fui-space-0-75, $fui-space-0-75);
39
+ gap: var(--fui-space-0-75, $fui-space-0-75);
40
40
  width: fit-content;
41
41
  }
42
42
 
@@ -0,0 +1,188 @@
1
+ import React from 'react';
2
+ import { defineSegment } from '@fragments/core';
3
+ import { Text } from '.';
4
+
5
+ export default defineSegment({
6
+ component: Text,
7
+
8
+ meta: {
9
+ name: 'Text',
10
+ description: 'Typography component for rendering text with consistent styling. Supports various sizes, weights, colors, and semantic elements.',
11
+ category: 'display',
12
+ status: 'stable',
13
+ tags: ['text', 'typography', 'heading', 'paragraph', 'font'],
14
+ since: '0.2.0',
15
+ },
16
+
17
+ usage: {
18
+ when: [
19
+ 'Displaying text with specific typography styles',
20
+ 'Creating headings, paragraphs, or labels',
21
+ 'Text that needs truncation or line clamping',
22
+ 'Consistent typography across the application',
23
+ ],
24
+ whenNot: [
25
+ 'Complex rich text (use a rich text editor)',
26
+ 'Code display (use CodeBlock)',
27
+ 'Interactive text (use Link or Button)',
28
+ ],
29
+ guidelines: [
30
+ 'Use semantic elements (h1-h6, p) via the "as" prop',
31
+ 'Maintain heading hierarchy for accessibility',
32
+ 'Use color variants sparingly for visual hierarchy',
33
+ 'Consider truncation for user-generated content',
34
+ ],
35
+ accessibility: [
36
+ 'Use proper heading levels (h1-h6) for document structure',
37
+ 'Semantic elements convey meaning to screen readers',
38
+ 'Truncated text should have full content in title/tooltip',
39
+ ],
40
+ },
41
+
42
+ props: {
43
+ children: {
44
+ type: 'node',
45
+ description: 'Text content',
46
+ required: true,
47
+ },
48
+ as: {
49
+ type: 'enum',
50
+ description: 'HTML element to render',
51
+ values: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'label', 'div', 'strong', 'em', 'small', 'code'],
52
+ default: 'span',
53
+ },
54
+ size: {
55
+ type: 'enum',
56
+ description: 'Font size',
57
+ values: ['2xs', 'xs', 'sm', 'base', 'lg', 'xl', '2xl'],
58
+ },
59
+ weight: {
60
+ type: 'enum',
61
+ description: 'Font weight',
62
+ values: ['normal', 'medium', 'semibold'],
63
+ },
64
+ color: {
65
+ type: 'enum',
66
+ description: 'Text color',
67
+ values: ['primary', 'secondary', 'tertiary'],
68
+ },
69
+ font: {
70
+ type: 'enum',
71
+ description: 'Font family',
72
+ values: ['sans', 'mono'],
73
+ default: 'sans',
74
+ },
75
+ truncate: {
76
+ type: 'boolean',
77
+ description: 'Truncate with ellipsis on overflow',
78
+ },
79
+ lineClamp: {
80
+ type: 'number',
81
+ description: 'Number of lines before truncating (requires truncate)',
82
+ },
83
+ },
84
+
85
+ relations: [
86
+ { component: 'Link', relationship: 'sibling', note: 'Use Link for clickable text' },
87
+ { component: 'CodeBlock', relationship: 'alternative', note: 'Use CodeBlock for code display' },
88
+ { component: 'Badge', relationship: 'sibling', note: 'Use Badge for labels/tags' },
89
+ ],
90
+
91
+ contract: {
92
+ propsSummary: [
93
+ 'as: string - HTML element',
94
+ 'size: 2xs|xs|sm|base|lg|xl|2xl - font size',
95
+ 'weight: normal|medium|semibold - font weight',
96
+ 'color: primary|secondary|tertiary - text color',
97
+ 'font: sans|mono - font family',
98
+ 'truncate: boolean - enable truncation',
99
+ 'lineClamp: number - max lines',
100
+ ],
101
+ scenarioTags: [
102
+ 'typography.text',
103
+ 'display.content',
104
+ 'semantic.elements',
105
+ ],
106
+ a11yRules: ['A11Y_HEADING_HIERARCHY', 'A11Y_SEMANTIC_ELEMENTS'],
107
+ },
108
+
109
+ variants: [
110
+ {
111
+ name: 'Sizes',
112
+ description: 'Different text sizes',
113
+ render: () => (
114
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
115
+ <Text size="2xs">Extra extra small (2xs)</Text>
116
+ <Text size="xs">Extra small (xs)</Text>
117
+ <Text size="sm">Small (sm)</Text>
118
+ <Text size="base">Base size</Text>
119
+ <Text size="lg">Large (lg)</Text>
120
+ <Text size="xl">Extra large (xl)</Text>
121
+ <Text size="2xl">Extra extra large (2xl)</Text>
122
+ </div>
123
+ ),
124
+ },
125
+ {
126
+ name: 'Weights',
127
+ description: 'Different font weights',
128
+ render: () => (
129
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
130
+ <Text weight="normal">Normal weight</Text>
131
+ <Text weight="medium">Medium weight</Text>
132
+ <Text weight="semibold">Semibold weight</Text>
133
+ </div>
134
+ ),
135
+ },
136
+ {
137
+ name: 'Colors',
138
+ description: 'Different text colors',
139
+ render: () => (
140
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
141
+ <Text color="primary">Primary color (default)</Text>
142
+ <Text color="secondary">Secondary color</Text>
143
+ <Text color="tertiary">Tertiary color</Text>
144
+ </div>
145
+ ),
146
+ },
147
+ {
148
+ name: 'Semantic Elements',
149
+ description: 'Using appropriate HTML elements',
150
+ render: () => (
151
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
152
+ <Text as="h1" size="2xl" weight="semibold">Heading 1</Text>
153
+ <Text as="h2" size="xl" weight="semibold">Heading 2</Text>
154
+ <Text as="h3" size="lg" weight="medium">Heading 3</Text>
155
+ <Text as="p" color="secondary">
156
+ This is a paragraph of text that demonstrates the Text component
157
+ with semantic paragraph element.
158
+ </Text>
159
+ </div>
160
+ ),
161
+ },
162
+ {
163
+ name: 'Monospace',
164
+ description: 'Monospace font for code-like text',
165
+ render: () => (
166
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
167
+ <Text font="mono" size="sm">const greeting = "Hello, World!";</Text>
168
+ <Text font="mono" size="sm" color="secondary">npm install @fragments-sdk/ui</Text>
169
+ </div>
170
+ ),
171
+ },
172
+ {
173
+ name: 'Truncation',
174
+ description: 'Text truncation with ellipsis',
175
+ render: () => (
176
+ <div style={{ width: '200px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
177
+ <Text truncate>
178
+ This is a very long text that will be truncated with an ellipsis when it overflows.
179
+ </Text>
180
+ <Text truncate lineClamp={2}>
181
+ This text will be clamped to two lines. Any content beyond two lines
182
+ will be hidden and replaced with an ellipsis at the end.
183
+ </Text>
184
+ </div>
185
+ ),
186
+ },
187
+ ],
188
+ });
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Textarea } from './index.js';
3
+ import { Textarea } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: Textarea,
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { ThemeProvider, ThemeToggle, useTheme } from './index.js';
3
+ import { ThemeProvider, ThemeToggle, useTheme } from '.';
4
4
 
5
5
  // Demo component to show hook usage
6
6
  function ThemeDemo() {
@@ -20,7 +20,7 @@ export default defineSegment({
20
20
  meta: {
21
21
  name: 'Theme',
22
22
  description: 'Theme management system with provider, toggle, and hook pattern. Supports light, dark, and system modes with localStorage persistence.',
23
- category: 'layout',
23
+ category: 'navigation',
24
24
  status: 'stable',
25
25
  tags: ['theme', 'dark-mode', 'light-mode', 'provider', 'toggle'],
26
26
  since: '0.5.0',
@@ -8,7 +8,7 @@
8
8
  .toggleGroup {
9
9
  display: inline-flex;
10
10
  gap: var(--fui-space-1, $fui-space-1);
11
- padding: 2px;
11
+ padding: var(--fui-space-0-5, $fui-space-0-5);
12
12
  background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
13
13
  border-radius: var(--fui-radius-md, $fui-radius-md);
14
14
  }
@@ -47,36 +47,36 @@
47
47
  // Size variants
48
48
  .sizeSm {
49
49
  .toggleButton {
50
- width: 26px;
51
- height: 22px;
50
+ width: var(--fui-theme-toggle-sm-width, $fui-theme-toggle-sm-width);
51
+ height: var(--fui-theme-toggle-sm-height, $fui-theme-toggle-sm-height);
52
52
 
53
53
  svg {
54
- width: 12px;
55
- height: 12px;
54
+ width: var(--fui-theme-toggle-sm-icon, $fui-theme-toggle-sm-icon);
55
+ height: var(--fui-theme-toggle-sm-icon, $fui-theme-toggle-sm-icon);
56
56
  }
57
57
  }
58
58
  }
59
59
 
60
60
  .sizeMd {
61
61
  .toggleButton {
62
- width: 32px;
63
- height: 28px;
62
+ width: var(--fui-theme-toggle-md-width, $fui-theme-toggle-md-width);
63
+ height: var(--fui-theme-toggle-md-height, $fui-theme-toggle-md-height);
64
64
 
65
65
  svg {
66
- width: 14px;
67
- height: 14px;
66
+ width: var(--fui-theme-toggle-md-icon, $fui-theme-toggle-md-icon);
67
+ height: var(--fui-theme-toggle-md-icon, $fui-theme-toggle-md-icon);
68
68
  }
69
69
  }
70
70
  }
71
71
 
72
72
  .sizeLg {
73
73
  .toggleButton {
74
- width: 40px;
75
- height: 34px;
74
+ width: var(--fui-theme-toggle-lg-width, $fui-theme-toggle-lg-width);
75
+ height: var(--fui-theme-toggle-lg-height, $fui-theme-toggle-lg-height);
76
76
 
77
77
  svg {
78
- width: 18px;
79
- height: 18px;
78
+ width: var(--fui-theme-toggle-lg-icon, $fui-theme-toggle-lg-icon);
79
+ height: var(--fui-theme-toggle-lg-icon, $fui-theme-toggle-lg-icon);
80
80
  }
81
81
  }
82
82
  }
@@ -0,0 +1,182 @@
1
+ import React from 'react';
2
+ import { defineSegment } from '@fragments/core';
3
+ import { ThinkingIndicator } from '.';
4
+
5
+ export default defineSegment({
6
+ component: ThinkingIndicator,
7
+
8
+ meta: {
9
+ name: 'ThinkingIndicator',
10
+ description: 'Animated indicator showing AI is processing',
11
+ category: 'ai',
12
+ status: 'stable',
13
+ tags: ['thinking', 'loading', 'ai', 'processing', 'indicator', 'animation'],
14
+ },
15
+
16
+ usage: {
17
+ when: [
18
+ 'AI is processing a request and generating a response',
19
+ 'Need visual feedback during async AI operations',
20
+ 'Want to show multi-step progress for complex AI tasks',
21
+ 'Indicating streaming is about to begin',
22
+ ],
23
+ whenNot: [
24
+ 'Simple loading states (use Progress or Skeleton)',
25
+ 'Form submission loading (use Button loading state)',
26
+ 'Page-level loading (use Progress or Skeleton)',
27
+ ],
28
+ guidelines: [
29
+ 'Use active prop to control visibility',
30
+ 'Choose variant based on context (dots for chat, spinner for actions)',
31
+ 'Enable showElapsed for longer operations',
32
+ 'Use steps for multi-step AI workflows (tool calls, research)',
33
+ ],
34
+ accessibility: [
35
+ 'Uses role="status" and aria-live="polite"',
36
+ 'Provides aria-label for screen readers',
37
+ 'Animations respect prefers-reduced-motion',
38
+ 'Elapsed time uses tabular numbers for stability',
39
+ ],
40
+ },
41
+
42
+ props: {
43
+ active: {
44
+ type: 'boolean',
45
+ default: 'true',
46
+ description: 'Whether thinking is active',
47
+ },
48
+ label: {
49
+ type: 'string',
50
+ default: '"Thinking..."',
51
+ description: 'Status text',
52
+ },
53
+ variant: {
54
+ type: '"dots" | "pulse" | "spinner"',
55
+ default: '"dots"',
56
+ description: 'Animation style',
57
+ },
58
+ showElapsed: {
59
+ type: 'boolean',
60
+ default: 'false',
61
+ description: 'Show elapsed time',
62
+ },
63
+ steps: {
64
+ type: 'ThinkingStep[]',
65
+ description: 'Multi-step progress array',
66
+ },
67
+ },
68
+
69
+ relations: [
70
+ {
71
+ component: 'Message',
72
+ relationship: 'sibling',
73
+ note: 'Show ThinkingIndicator while waiting for assistant message',
74
+ },
75
+ {
76
+ component: 'ConversationList',
77
+ relationship: 'parent',
78
+ note: 'Typically placed at bottom of ConversationList',
79
+ },
80
+ {
81
+ component: 'Progress',
82
+ relationship: 'alternative',
83
+ note: 'Use Progress for determinate progress',
84
+ },
85
+ ],
86
+
87
+ contract: {
88
+ propsSummary: [
89
+ 'active: boolean - whether indicator is visible (default: true)',
90
+ 'label: string - status text (default: "Thinking...")',
91
+ 'variant: "dots" | "pulse" | "spinner" - animation style',
92
+ 'showElapsed: boolean - show elapsed time',
93
+ 'steps: ThinkingStep[] - multi-step progress',
94
+ ],
95
+ scenarioTags: [
96
+ 'ui.loading',
97
+ 'ui.indicator',
98
+ 'ai.thinking',
99
+ 'ai.processing',
100
+ ],
101
+ a11yRules: [
102
+ 'A11Y_ARIA_LIVE',
103
+ 'A11Y_MOTION_PREFERENCE',
104
+ ],
105
+ bans: [],
106
+ },
107
+
108
+ variants: [
109
+ {
110
+ name: 'Dots (Default)',
111
+ description: 'Bouncing dots animation',
112
+ render: () => (
113
+ <ThinkingIndicator variant="dots" label="Thinking..." />
114
+ ),
115
+ },
116
+ {
117
+ name: 'Pulse',
118
+ description: 'Pulsing ring animation',
119
+ render: () => (
120
+ <ThinkingIndicator variant="pulse" label="Processing..." />
121
+ ),
122
+ },
123
+ {
124
+ name: 'Spinner',
125
+ description: 'Rotating spinner animation',
126
+ render: () => (
127
+ <ThinkingIndicator variant="spinner" label="Loading..." />
128
+ ),
129
+ },
130
+ {
131
+ name: 'With Elapsed Time',
132
+ description: 'Shows time since started',
133
+ render: () => (
134
+ <ThinkingIndicator
135
+ variant="dots"
136
+ label="Generating response..."
137
+ showElapsed
138
+ />
139
+ ),
140
+ },
141
+ {
142
+ name: 'Multi-Step Progress',
143
+ description: 'Shows progress through multiple steps',
144
+ render: () => (
145
+ <ThinkingIndicator
146
+ variant="spinner"
147
+ label="Working..."
148
+ steps={[
149
+ { id: '1', label: 'Analyzing request', status: 'complete' },
150
+ { id: '2', label: 'Searching knowledge base', status: 'active' },
151
+ { id: '3', label: 'Generating response', status: 'pending' },
152
+ ]}
153
+ />
154
+ ),
155
+ },
156
+ {
157
+ name: 'With Error Step',
158
+ description: 'Shows a step that encountered an error',
159
+ render: () => (
160
+ <ThinkingIndicator
161
+ variant="spinner"
162
+ label="Retrying..."
163
+ steps={[
164
+ { id: '1', label: 'Connecting to API', status: 'complete' },
165
+ { id: '2', label: 'Fetching data', status: 'error' },
166
+ { id: '3', label: 'Retrying with fallback', status: 'active' },
167
+ ]}
168
+ />
169
+ ),
170
+ },
171
+ {
172
+ name: 'Custom Label',
173
+ description: 'Custom status text',
174
+ render: () => (
175
+ <ThinkingIndicator
176
+ variant="dots"
177
+ label="Claude is writing code..."
178
+ />
179
+ ),
180
+ },
181
+ ],
182
+ });