@fragments-sdk/ui 0.8.0 → 0.8.2
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/LICENSE +1 -1
- package/README.md +2 -0
- package/fragments.json +1 -1
- package/package.json +21 -2
- package/src/assets/fragments-logo.tsx +37 -0
- package/src/assets/fragments_logo.svg +1 -0
- package/src/assets/fragments_logo_text.svg +1 -0
- package/src/blocks/AccountSettings.block.ts +1 -1
- package/src/blocks/ActivityFeed.block.ts +7 -7
- package/src/blocks/ChatInterface.block.ts +36 -80
- package/src/blocks/DashboardLayout.block.ts +85 -66
- package/src/blocks/DashboardPage.block.ts +297 -0
- package/src/blocks/EmptyState.block.ts +5 -3
- package/src/blocks/FeatureGrid.block.ts +1 -1
- package/src/blocks/LoginForm.block.ts +21 -26
- package/src/blocks/PricingComparison.block.ts +1 -1
- package/src/blocks/ShoppingCart.block.ts +2 -2
- package/src/components/Accordion/Accordion.fragment.tsx +2 -2
- package/src/components/Alert/Alert.fragment.tsx +2 -2
- package/src/components/AppShell/AppShell.fragment.tsx +2 -2
- package/src/components/Avatar/Avatar.fragment.tsx +2 -2
- package/src/components/Badge/Badge.fragment.tsx +2 -2
- package/src/components/Box/Box.fragment.tsx +2 -2
- package/src/components/Breadcrumbs/Breadcrumbs.fragment.tsx +2 -2
- package/src/components/Button/Button.fragment.tsx +2 -2
- package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +2 -2
- package/src/components/Card/Card.fragment.tsx +2 -2
- package/src/components/Chart/Chart.fragment.tsx +2 -2
- package/src/components/Checkbox/Checkbox.fragment.tsx +2 -2
- package/src/components/Chip/Chip.fragment.tsx +2 -2
- package/src/components/CodeBlock/CodeBlock.fragment.tsx +2 -2
- package/src/components/CodeBlock/CodeBlock.module.scss +10 -52
- package/src/components/CodeBlock/index.tsx +13 -24
- package/src/components/Collapsible/Collapsible.fragment.tsx +2 -2
- package/src/components/ColorPicker/ColorPicker.fragment.tsx +2 -2
- package/src/components/Combobox/Combobox.fragment.tsx +2 -2
- package/src/components/ConversationList/ConversationList.fragment.tsx +2 -2
- package/src/components/DatePicker/DatePicker.fragment.tsx +2 -2
- package/src/components/Dialog/Dialog.fragment.tsx +2 -2
- package/src/components/EmptyState/EmptyState.fragment.tsx +2 -2
- package/src/components/Field/Field.fragment.tsx +2 -2
- package/src/components/Fieldset/Fieldset.fragment.tsx +2 -2
- package/src/components/Form/Form.fragment.tsx +2 -2
- package/src/components/Grid/Grid.fragment.tsx +2 -2
- package/src/components/Header/Header.fragment.tsx +2 -2
- package/src/components/Icon/Icon.fragment.tsx +2 -2
- package/src/components/Image/Image.fragment.tsx +2 -2
- package/src/components/Input/Input.fragment.tsx +2 -2
- package/src/components/Input/Input.test.tsx +35 -0
- package/src/components/Input/index.tsx +47 -2
- package/src/components/Link/Link.fragment.tsx +2 -2
- package/src/components/List/List.fragment.tsx +2 -2
- package/src/components/Listbox/Listbox.fragment.tsx +2 -2
- package/src/components/Loading/Loading.fragment.tsx +2 -2
- package/src/components/Markdown/Markdown.fragment.tsx +2 -2
- package/src/components/Markdown/Markdown.module.scss +5 -0
- package/src/components/Menu/Menu.fragment.tsx +2 -2
- package/src/components/Menu/Menu.module.scss +2 -0
- package/src/components/Message/Message.fragment.tsx +2 -2
- package/src/components/Popover/Popover.fragment.tsx +2 -2
- package/src/components/Progress/Progress.fragment.tsx +2 -2
- package/src/components/Prompt/Prompt.fragment.tsx +2 -2
- package/src/components/RadioGroup/RadioGroup.fragment.tsx +2 -2
- package/src/components/ScrollArea/ScrollArea.fragment.tsx +2 -2
- package/src/components/Select/Select.fragment.tsx +2 -2
- package/src/components/Separator/Separator.fragment.tsx +2 -2
- package/src/components/Sidebar/Sidebar.fragment.tsx +2 -2
- package/src/components/Sidebar/Sidebar.module.scss +1 -5
- package/src/components/Skeleton/Skeleton.fragment.tsx +2 -2
- package/src/components/Slider/Slider.fragment.tsx +2 -2
- package/src/components/Stack/Stack.fragment.tsx +2 -2
- package/src/components/Table/Table.fragment.tsx +3 -3
- package/src/components/Table/index.tsx +43 -5
- package/src/components/TableOfContents/TableOfContents.fragment.tsx +2 -2
- package/src/components/TableOfContents/TableOfContents.module.scss +0 -5
- package/src/components/TableOfContents/index.tsx +6 -1
- package/src/components/Tabs/Tabs.fragment.tsx +2 -2
- package/src/components/Text/Text.fragment.tsx +2 -2
- package/src/components/Text/Text.module.scss +6 -0
- package/src/components/Text/Text.test.tsx +5 -0
- package/src/components/Text/index.tsx +3 -0
- package/src/components/Textarea/Textarea.fragment.tsx +2 -2
- package/src/components/Theme/Theme.fragment.tsx +2 -2
- package/src/components/Theme/ThemeToggle.module.scss +1 -1
- package/src/components/Theme/index.tsx +1 -1
- package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +2 -2
- package/src/components/Toast/Toast.fragment.tsx +2 -2
- package/src/components/Toggle/Toggle.fragment.tsx +2 -2
- package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +37 -5
- package/src/components/Tooltip/Tooltip.fragment.tsx +2 -2
- package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +2 -2
- package/src/index.ts +3 -0
- package/src/tokens/_derive.scss +32 -8
- package/src/tokens/_mixins.scss +9 -0
- package/src/tokens/_variables.scss +4 -4
- package/src/blocks/AIChat.block.ts +0 -266
- package/src/blocks/AppShell.block.ts +0 -175
- package/src/blocks/CTABanner.block.ts +0 -24
- package/src/blocks/CardGrid.block.ts +0 -22
- package/src/blocks/CodeExamples.block.ts +0 -66
- package/src/blocks/ConfirmDialog.block.ts +0 -19
- package/src/blocks/ConversationWithHistory.block.ts +0 -45
- package/src/blocks/DashboardNav.block.ts +0 -183
- package/src/blocks/ForgotPassword.block.ts +0 -26
- package/src/blocks/FormLayout.block.ts +0 -31
- package/src/blocks/InsetDashboardLayout.block.ts +0 -79
- package/src/blocks/MetricDashboard.block.ts +0 -38
- package/src/blocks/NewsletterSignup.block.ts +0 -26
- package/src/blocks/NotificationList.block.ts +0 -39
- package/src/blocks/NotificationPreferences.block.ts +0 -40
- package/src/blocks/OrderSummary.block.ts +0 -52
- package/src/blocks/ProfileEditForm.block.ts +0 -51
- package/src/blocks/SearchResults.block.ts +0 -39
- package/src/blocks/SettingsPage.block.ts +0 -58
- package/src/blocks/StreamingMessage.block.ts +0 -24
- package/src/blocks/TestimonialCard.block.ts +0 -27
- package/src/blocks/UserProfileCard.block.ts +0 -29
- package/src/recipes/AIChat.recipe.ts +0 -266
- package/src/recipes/AppShell.recipe.ts +0 -175
- package/src/recipes/CardGrid.recipe.ts +0 -22
- package/src/recipes/ChatInterface.recipe.ts +0 -87
- package/src/recipes/CodeExamples.recipe.ts +0 -66
- package/src/recipes/ConfirmDialog.recipe.ts +0 -19
- package/src/recipes/DashboardLayout.recipe.ts +0 -73
- package/src/recipes/DashboardNav.recipe.ts +0 -183
- package/src/recipes/FormLayout.recipe.ts +0 -31
- package/src/recipes/LoginForm.recipe.ts +0 -33
- package/src/recipes/SettingsPage.recipe.ts +0 -58
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { defineFragment } from '@fragments/core';
|
|
3
3
|
import { ToggleGroup } from '.';
|
|
4
4
|
|
|
5
5
|
function DefaultExample() {
|
|
@@ -92,7 +92,7 @@ function DisabledItemExample() {
|
|
|
92
92
|
);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
export default
|
|
95
|
+
export default defineFragment({
|
|
96
96
|
component: ToggleGroup,
|
|
97
97
|
|
|
98
98
|
meta: {
|
|
@@ -100,7 +100,7 @@ export default defineSegment({
|
|
|
100
100
|
description: 'A group of toggle buttons where only one can be selected at a time. Useful for switching between views, modes, or options.',
|
|
101
101
|
category: 'forms',
|
|
102
102
|
status: 'stable',
|
|
103
|
-
tags: ['toggle', 'group', '
|
|
103
|
+
tags: ['toggle', 'group', 'fragmented', 'control', 'tabs', 'switch'],
|
|
104
104
|
since: '0.2.0',
|
|
105
105
|
},
|
|
106
106
|
|
|
@@ -108,7 +108,7 @@ export default defineSegment({
|
|
|
108
108
|
when: [
|
|
109
109
|
'Switching between mutually exclusive views or modes',
|
|
110
110
|
'Selecting one option from a small set (2-5 options)',
|
|
111
|
-
'
|
|
111
|
+
'Fragmented controls like view switchers',
|
|
112
112
|
'Filter or sort options',
|
|
113
113
|
],
|
|
114
114
|
whenNot: [
|
|
@@ -185,7 +185,7 @@ export default defineSegment({
|
|
|
185
185
|
scenarioTags: [
|
|
186
186
|
'forms.selection',
|
|
187
187
|
'input.toggle',
|
|
188
|
-
'control.
|
|
188
|
+
'control.fragmented',
|
|
189
189
|
],
|
|
190
190
|
a11yRules: ['A11Y_GROUP_ROLE', 'A11Y_KEYBOARD_ACCESSIBLE'],
|
|
191
191
|
},
|
|
@@ -194,31 +194,63 @@ export default defineSegment({
|
|
|
194
194
|
{
|
|
195
195
|
name: 'Default',
|
|
196
196
|
description: 'Basic toggle group',
|
|
197
|
+
code: `<ToggleGroup value={value} onChange={setValue}>
|
|
198
|
+
<ToggleGroup.Item value="left">Left</ToggleGroup.Item>
|
|
199
|
+
<ToggleGroup.Item value="center">Center</ToggleGroup.Item>
|
|
200
|
+
<ToggleGroup.Item value="right">Right</ToggleGroup.Item>
|
|
201
|
+
</ToggleGroup>`,
|
|
197
202
|
render: () => <DefaultExample />,
|
|
198
203
|
},
|
|
199
204
|
{
|
|
200
205
|
name: 'Pills Variant',
|
|
201
206
|
description: 'Pill-shaped toggle buttons',
|
|
207
|
+
code: `<ToggleGroup value={value} onChange={setValue} variant="pills">
|
|
208
|
+
<ToggleGroup.Item value="all">All</ToggleGroup.Item>
|
|
209
|
+
<ToggleGroup.Item value="active">Active</ToggleGroup.Item>
|
|
210
|
+
<ToggleGroup.Item value="completed">Completed</ToggleGroup.Item>
|
|
211
|
+
</ToggleGroup>`,
|
|
202
212
|
render: () => <PillsExample />,
|
|
203
213
|
},
|
|
204
214
|
{
|
|
205
215
|
name: 'Outline Variant',
|
|
206
216
|
description: 'Outlined toggle buttons',
|
|
217
|
+
code: `<ToggleGroup value={value} onChange={setValue} variant="outline">
|
|
218
|
+
<ToggleGroup.Item value="day">Day</ToggleGroup.Item>
|
|
219
|
+
<ToggleGroup.Item value="week">Week</ToggleGroup.Item>
|
|
220
|
+
<ToggleGroup.Item value="month">Month</ToggleGroup.Item>
|
|
221
|
+
</ToggleGroup>`,
|
|
207
222
|
render: () => <OutlineExample />,
|
|
208
223
|
},
|
|
209
224
|
{
|
|
210
225
|
name: 'Sizes',
|
|
211
226
|
description: 'Different size variants',
|
|
227
|
+
code: `<ToggleGroup value={value} onChange={setValue} size="sm">
|
|
228
|
+
<ToggleGroup.Item value="a">Small</ToggleGroup.Item>
|
|
229
|
+
<ToggleGroup.Item value="b">Size</ToggleGroup.Item>
|
|
230
|
+
</ToggleGroup>
|
|
231
|
+
<ToggleGroup value={value} onChange={setValue} size="md">
|
|
232
|
+
<ToggleGroup.Item value="a">Medium</ToggleGroup.Item>
|
|
233
|
+
<ToggleGroup.Item value="b">Size</ToggleGroup.Item>
|
|
234
|
+
</ToggleGroup>`,
|
|
212
235
|
render: () => <SizesExample />,
|
|
213
236
|
},
|
|
214
237
|
{
|
|
215
238
|
name: 'View Switcher',
|
|
216
239
|
description: 'Common pattern for switching between views',
|
|
240
|
+
code: `<ToggleGroup value={view} onChange={setView} size="sm">
|
|
241
|
+
<ToggleGroup.Item value="grid"><GridIcon /></ToggleGroup.Item>
|
|
242
|
+
<ToggleGroup.Item value="list"><ListIcon /></ToggleGroup.Item>
|
|
243
|
+
</ToggleGroup>`,
|
|
217
244
|
render: () => <ViewSwitcherExample />,
|
|
218
245
|
},
|
|
219
246
|
{
|
|
220
247
|
name: 'With Disabled Item',
|
|
221
248
|
description: 'Toggle group with a disabled option',
|
|
249
|
+
code: `<ToggleGroup value={value} onChange={setValue}>
|
|
250
|
+
<ToggleGroup.Item value="basic">Basic</ToggleGroup.Item>
|
|
251
|
+
<ToggleGroup.Item value="pro">Pro</ToggleGroup.Item>
|
|
252
|
+
<ToggleGroup.Item value="enterprise" disabled>Enterprise</ToggleGroup.Item>
|
|
253
|
+
</ToggleGroup>`,
|
|
222
254
|
render: () => <DisabledItemExample />,
|
|
223
255
|
},
|
|
224
256
|
],
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { defineFragment } from '@fragments/core';
|
|
3
3
|
import { Tooltip } from '.';
|
|
4
4
|
import { Button } from '../Button';
|
|
5
5
|
|
|
6
|
-
export default
|
|
6
|
+
export default defineFragment({
|
|
7
7
|
component: Tooltip,
|
|
8
8
|
|
|
9
9
|
meta: {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { defineFragment } from '@fragments/core';
|
|
3
3
|
import { VisuallyHidden } from '.';
|
|
4
4
|
|
|
5
|
-
export default
|
|
5
|
+
export default defineFragment({
|
|
6
6
|
component: VisuallyHidden,
|
|
7
7
|
|
|
8
8
|
meta: {
|
package/src/index.ts
CHANGED
package/src/tokens/_derive.scss
CHANGED
|
@@ -46,30 +46,54 @@
|
|
|
46
46
|
// --------------------------------------------
|
|
47
47
|
|
|
48
48
|
/// Derive hover state for accent color
|
|
49
|
+
/// Handles extreme lightness values (near-black/near-white) by reversing
|
|
50
|
+
/// the adjustment direction to ensure a visible hover state.
|
|
49
51
|
/// @param {Color} $base - Base accent color
|
|
50
52
|
/// @param {Boolean} $is-dark - Whether in dark mode
|
|
51
53
|
/// @return {Color} Hover state color
|
|
52
54
|
@function derive-accent-hover($base, $is-dark: false) {
|
|
55
|
+
$l: color.channel($base, 'lightness', $space: hsl);
|
|
56
|
+
|
|
53
57
|
@if $is-dark {
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
@if $l > 85% {
|
|
59
|
+
// Very light accent on dark bg: darken for visible hover
|
|
60
|
+
@return color.scale($base, $lightness: -10%);
|
|
61
|
+
}
|
|
62
|
+
// Standard dark mode: lighten for hover
|
|
63
|
+
@return color.scale($base, $lightness: 8%);
|
|
56
64
|
} @else {
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
@if $l < 15% {
|
|
66
|
+
// Very dark accent on light bg: lighten for visible hover
|
|
67
|
+
@return color.scale($base, $lightness: 18%);
|
|
68
|
+
}
|
|
69
|
+
// Standard light mode: darken for hover
|
|
70
|
+
@return color.scale($base, $lightness: -10%);
|
|
59
71
|
}
|
|
60
72
|
}
|
|
61
73
|
|
|
62
74
|
/// Derive active state for accent color
|
|
75
|
+
/// Handles extreme lightness values (near-black/near-white) by reversing
|
|
76
|
+
/// the adjustment direction to ensure a visible active/pressed state.
|
|
63
77
|
/// @param {Color} $base - Base accent color
|
|
64
78
|
/// @param {Boolean} $is-dark - Whether in dark mode
|
|
65
79
|
/// @return {Color} Active state color
|
|
66
80
|
@function derive-accent-active($base, $is-dark: false) {
|
|
81
|
+
$l: color.channel($base, 'lightness', $space: hsl);
|
|
82
|
+
|
|
67
83
|
@if $is-dark {
|
|
68
|
-
|
|
69
|
-
|
|
84
|
+
@if $l > 85% {
|
|
85
|
+
// Very light accent on dark bg: darken more for active
|
|
86
|
+
@return color.scale($base, $lightness: -16%);
|
|
87
|
+
}
|
|
88
|
+
// Standard dark mode: lighten more for active
|
|
89
|
+
@return color.scale($base, $lightness: 14%);
|
|
70
90
|
} @else {
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
@if $l < 15% {
|
|
92
|
+
// Very dark accent on light bg: lighten more for active
|
|
93
|
+
@return color.scale($base, $lightness: 28%);
|
|
94
|
+
}
|
|
95
|
+
// Standard light mode: darken more for active
|
|
96
|
+
@return color.scale($base, $lightness: -18%);
|
|
73
97
|
}
|
|
74
98
|
}
|
|
75
99
|
|
package/src/tokens/_mixins.scss
CHANGED
|
@@ -72,6 +72,15 @@
|
|
|
72
72
|
color: var(--fui-text-secondary, #{$fui-text-secondary});
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// Section label text (e.g. sidebar headings, TOC label)
|
|
76
|
+
@mixin section-label-text {
|
|
77
|
+
@include helper-text;
|
|
78
|
+
font-size: var(--fui-font-size-2xs, #{$fui-font-size-2xs});
|
|
79
|
+
font-weight: var(--fui-font-weight-medium, #{$fui-font-weight-medium});
|
|
80
|
+
text-transform: uppercase;
|
|
81
|
+
letter-spacing: 0.05em;
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
// Card surface
|
|
76
85
|
@mixin surface-elevated {
|
|
77
86
|
background-color: var(--fui-bg-elevated, #{$fui-bg-elevated});
|
|
@@ -346,8 +346,8 @@ $fui-theme-toggle-lg-height: 34px !default;
|
|
|
346
346
|
$fui-theme-toggle-lg-icon: 18px !default;
|
|
347
347
|
|
|
348
348
|
// CodeBlock (light mode)
|
|
349
|
-
$fui-code-bg:
|
|
350
|
-
$fui-code-header-bg:
|
|
349
|
+
$fui-code-bg: var(--fui-bg-elevated) !default;
|
|
350
|
+
$fui-code-header-bg: var(--fui-bg-elevated) !default;
|
|
351
351
|
$fui-code-text: #d4d4d4 !default;
|
|
352
352
|
$fui-code-text-muted: #6b7280 !default;
|
|
353
353
|
$fui-code-border: rgba(255, 255, 255, 0.1) !default;
|
|
@@ -372,8 +372,8 @@ $fui-tooltip-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -2px rgba(0, 0
|
|
|
372
372
|
$fui-hero-gradient-color: rgba(120, 119, 198, 0.15) !default;
|
|
373
373
|
|
|
374
374
|
// CodeBlock (dark mode) - same as light, code blocks stay dark
|
|
375
|
-
$fui-dark-code-bg:
|
|
376
|
-
$fui-dark-code-header-bg:
|
|
375
|
+
$fui-dark-code-bg: var(--fui-bg-elevated) !default;
|
|
376
|
+
$fui-dark-code-header-bg: var(--fui-bg-elevated) !default;
|
|
377
377
|
$fui-dark-code-text: #d4d4d4 !default;
|
|
378
378
|
$fui-dark-code-text-muted: #6b7280 !default;
|
|
379
379
|
$fui-dark-code-border: rgba(255, 255, 255, 0.1) !default;
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
import { defineBlock } from '@fragments/core';
|
|
2
|
-
|
|
3
|
-
export default defineBlock({
|
|
4
|
-
name: 'AI Chat',
|
|
5
|
-
description: 'Complete AI chat interface with Message, ConversationList, Prompt, and ThinkingIndicator',
|
|
6
|
-
category: 'ai',
|
|
7
|
-
components: ['Message', 'ConversationList', 'ThinkingIndicator', 'Prompt', 'Stack'],
|
|
8
|
-
tags: ['chat', 'ai', 'assistant', 'conversation', 'llm', 'chatbot'],
|
|
9
|
-
code: `
|
|
10
|
-
import { useState, useCallback } from 'react';
|
|
11
|
-
import {
|
|
12
|
-
Message,
|
|
13
|
-
ConversationList,
|
|
14
|
-
ThinkingIndicator,
|
|
15
|
-
Prompt,
|
|
16
|
-
Stack,
|
|
17
|
-
} from '@fragments/ui';
|
|
18
|
-
|
|
19
|
-
interface ChatMessage {
|
|
20
|
-
id: string;
|
|
21
|
-
role: 'user' | 'assistant' | 'system';
|
|
22
|
-
content: string;
|
|
23
|
-
timestamp: Date;
|
|
24
|
-
status?: 'sending' | 'streaming' | 'complete' | 'error';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface ThinkingStep {
|
|
28
|
-
id: string;
|
|
29
|
-
label: string;
|
|
30
|
-
status: 'pending' | 'active' | 'complete' | 'error';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function AIChat() {
|
|
34
|
-
const [messages, setMessages] = useState<ChatMessage[]>([
|
|
35
|
-
{
|
|
36
|
-
id: 'system-1',
|
|
37
|
-
role: 'system',
|
|
38
|
-
content: 'Conversation started. Model: Claude 3.5 Sonnet',
|
|
39
|
-
timestamp: new Date(),
|
|
40
|
-
status: 'complete',
|
|
41
|
-
},
|
|
42
|
-
]);
|
|
43
|
-
const [isThinking, setIsThinking] = useState(false);
|
|
44
|
-
const [thinkingSteps, setThinkingSteps] = useState<ThinkingStep[]>([]);
|
|
45
|
-
const [isLoadingHistory, setIsLoadingHistory] = useState(false);
|
|
46
|
-
|
|
47
|
-
const handleSubmit = useCallback(async (value: string) => {
|
|
48
|
-
// Add user message
|
|
49
|
-
const userMessage: ChatMessage = {
|
|
50
|
-
id: crypto.randomUUID(),
|
|
51
|
-
role: 'user',
|
|
52
|
-
content: value,
|
|
53
|
-
timestamp: new Date(),
|
|
54
|
-
status: 'complete',
|
|
55
|
-
};
|
|
56
|
-
setMessages((prev) => [...prev, userMessage]);
|
|
57
|
-
|
|
58
|
-
// Start thinking state with steps
|
|
59
|
-
setIsThinking(true);
|
|
60
|
-
setThinkingSteps([
|
|
61
|
-
{ id: '1', label: 'Understanding your request', status: 'active' },
|
|
62
|
-
{ id: '2', label: 'Searching knowledge base', status: 'pending' },
|
|
63
|
-
{ id: '3', label: 'Generating response', status: 'pending' },
|
|
64
|
-
]);
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
// Simulate step 1 completion
|
|
68
|
-
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
69
|
-
setThinkingSteps((prev) =>
|
|
70
|
-
prev.map((s) =>
|
|
71
|
-
s.id === '1'
|
|
72
|
-
? { ...s, status: 'complete' }
|
|
73
|
-
: s.id === '2'
|
|
74
|
-
? { ...s, status: 'active' }
|
|
75
|
-
: s
|
|
76
|
-
)
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
// Simulate step 2 completion
|
|
80
|
-
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
81
|
-
setThinkingSteps((prev) =>
|
|
82
|
-
prev.map((s) =>
|
|
83
|
-
s.id === '2'
|
|
84
|
-
? { ...s, status: 'complete' }
|
|
85
|
-
: s.id === '3'
|
|
86
|
-
? { ...s, status: 'active' }
|
|
87
|
-
: s
|
|
88
|
-
)
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
// Simulate step 3 (response generation)
|
|
92
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
93
|
-
|
|
94
|
-
// Add assistant response
|
|
95
|
-
const assistantMessage: ChatMessage = {
|
|
96
|
-
id: crypto.randomUUID(),
|
|
97
|
-
role: 'assistant',
|
|
98
|
-
content: generateMockResponse(value),
|
|
99
|
-
timestamp: new Date(),
|
|
100
|
-
status: 'complete',
|
|
101
|
-
};
|
|
102
|
-
setMessages((prev) => [...prev, assistantMessage]);
|
|
103
|
-
} catch (error) {
|
|
104
|
-
// Handle error
|
|
105
|
-
const errorMessage: ChatMessage = {
|
|
106
|
-
id: crypto.randomUUID(),
|
|
107
|
-
role: 'assistant',
|
|
108
|
-
content: 'Sorry, I encountered an error. Please try again.',
|
|
109
|
-
timestamp: new Date(),
|
|
110
|
-
status: 'error',
|
|
111
|
-
};
|
|
112
|
-
setMessages((prev) => [...prev, errorMessage]);
|
|
113
|
-
} finally {
|
|
114
|
-
setIsThinking(false);
|
|
115
|
-
setThinkingSteps([]);
|
|
116
|
-
}
|
|
117
|
-
}, []);
|
|
118
|
-
|
|
119
|
-
const handleLoadHistory = useCallback(async () => {
|
|
120
|
-
if (isLoadingHistory) return;
|
|
121
|
-
|
|
122
|
-
setIsLoadingHistory(true);
|
|
123
|
-
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
124
|
-
|
|
125
|
-
// Simulate loading older messages
|
|
126
|
-
const olderMessages: ChatMessage[] = [
|
|
127
|
-
{
|
|
128
|
-
id: crypto.randomUUID(),
|
|
129
|
-
role: 'user',
|
|
130
|
-
content: 'What can you help me with?',
|
|
131
|
-
timestamp: new Date(Date.now() - 86400000), // Yesterday
|
|
132
|
-
status: 'complete',
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
id: crypto.randomUUID(),
|
|
136
|
-
role: 'assistant',
|
|
137
|
-
content: 'I can help with coding, writing, analysis, and more!',
|
|
138
|
-
timestamp: new Date(Date.now() - 86400000 + 1000),
|
|
139
|
-
status: 'complete',
|
|
140
|
-
},
|
|
141
|
-
];
|
|
142
|
-
|
|
143
|
-
setMessages((prev) => [...olderMessages, ...prev]);
|
|
144
|
-
setIsLoadingHistory(false);
|
|
145
|
-
}, [isLoadingHistory]);
|
|
146
|
-
|
|
147
|
-
const handleCopy = useCallback((content: string) => {
|
|
148
|
-
navigator.clipboard.writeText(content);
|
|
149
|
-
// You might want to show a toast here
|
|
150
|
-
}, []);
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<Stack
|
|
154
|
-
style={{
|
|
155
|
-
height: '100vh',
|
|
156
|
-
maxWidth: '800px',
|
|
157
|
-
margin: '0 auto',
|
|
158
|
-
background: 'var(--fui-bg-primary)',
|
|
159
|
-
}}
|
|
160
|
-
>
|
|
161
|
-
<ConversationList
|
|
162
|
-
autoScroll="smart"
|
|
163
|
-
onScrollTop={handleLoadHistory}
|
|
164
|
-
loadingHistory={isLoadingHistory}
|
|
165
|
-
emptyState={
|
|
166
|
-
<Stack align="center" justify="center" style={{ flex: 1, padding: '2rem' }}>
|
|
167
|
-
<p style={{ color: 'var(--fui-text-secondary)' }}>
|
|
168
|
-
Start a conversation with the AI assistant
|
|
169
|
-
</p>
|
|
170
|
-
</Stack>
|
|
171
|
-
}
|
|
172
|
-
>
|
|
173
|
-
{messages.map((msg, index) => {
|
|
174
|
-
// Add date separator if day changed
|
|
175
|
-
const prevMsg = messages[index - 1];
|
|
176
|
-
const showDateSeparator =
|
|
177
|
-
!prevMsg ||
|
|
178
|
-
new Date(msg.timestamp).toDateString() !==
|
|
179
|
-
new Date(prevMsg.timestamp).toDateString();
|
|
180
|
-
|
|
181
|
-
return (
|
|
182
|
-
<React.Fragment key={msg.id}>
|
|
183
|
-
{showDateSeparator && (
|
|
184
|
-
<ConversationList.DateSeparator date={msg.timestamp} />
|
|
185
|
-
)}
|
|
186
|
-
<Message
|
|
187
|
-
role={msg.role}
|
|
188
|
-
status={msg.status}
|
|
189
|
-
timestamp={msg.timestamp}
|
|
190
|
-
actions={
|
|
191
|
-
msg.role === 'assistant' && msg.status === 'complete' ? (
|
|
192
|
-
<>
|
|
193
|
-
<button
|
|
194
|
-
onClick={() => handleCopy(msg.content)}
|
|
195
|
-
style={{
|
|
196
|
-
padding: '4px 8px',
|
|
197
|
-
fontSize: '12px',
|
|
198
|
-
background: 'var(--fui-bg-tertiary)',
|
|
199
|
-
border: 'none',
|
|
200
|
-
borderRadius: '4px',
|
|
201
|
-
cursor: 'pointer',
|
|
202
|
-
}}
|
|
203
|
-
>
|
|
204
|
-
Copy
|
|
205
|
-
</button>
|
|
206
|
-
</>
|
|
207
|
-
) : undefined
|
|
208
|
-
}
|
|
209
|
-
>
|
|
210
|
-
<Message.Content>{msg.content}</Message.Content>
|
|
211
|
-
{msg.timestamp && <Message.Timestamp />}
|
|
212
|
-
</Message>
|
|
213
|
-
</React.Fragment>
|
|
214
|
-
);
|
|
215
|
-
})}
|
|
216
|
-
|
|
217
|
-
{isThinking && (
|
|
218
|
-
<ThinkingIndicator
|
|
219
|
-
variant="dots"
|
|
220
|
-
label="Claude is thinking..."
|
|
221
|
-
showElapsed
|
|
222
|
-
steps={thinkingSteps}
|
|
223
|
-
/>
|
|
224
|
-
)}
|
|
225
|
-
</ConversationList>
|
|
226
|
-
|
|
227
|
-
<div style={{ padding: '1rem', borderTop: '1px solid var(--fui-border)' }}>
|
|
228
|
-
<Prompt
|
|
229
|
-
onSubmit={handleSubmit}
|
|
230
|
-
loading={isThinking}
|
|
231
|
-
placeholder="Message Claude..."
|
|
232
|
-
>
|
|
233
|
-
<Prompt.Textarea />
|
|
234
|
-
<Prompt.Toolbar>
|
|
235
|
-
<Prompt.Actions>
|
|
236
|
-
<Prompt.ActionButton aria-label="Attach file">
|
|
237
|
-
+
|
|
238
|
-
</Prompt.ActionButton>
|
|
239
|
-
<Prompt.ModeButton active>Auto</Prompt.ModeButton>
|
|
240
|
-
</Prompt.Actions>
|
|
241
|
-
<Prompt.Info>
|
|
242
|
-
<Prompt.Submit />
|
|
243
|
-
</Prompt.Info>
|
|
244
|
-
</Prompt.Toolbar>
|
|
245
|
-
</Prompt>
|
|
246
|
-
</div>
|
|
247
|
-
</Stack>
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Mock response generator
|
|
252
|
-
function generateMockResponse(input: string): string {
|
|
253
|
-
const responses = [
|
|
254
|
-
"That's a great question! Let me help you with that.",
|
|
255
|
-
"I understand what you're looking for. Here's what I can tell you...",
|
|
256
|
-
"Based on my knowledge, I can provide some insights on this topic.",
|
|
257
|
-
"Let me break this down for you step by step.",
|
|
258
|
-
];
|
|
259
|
-
return responses[Math.floor(Math.random() * responses.length)] +
|
|
260
|
-
" This is a simulated response. In a real implementation, this would be replaced with actual AI-generated content based on your query about: " +
|
|
261
|
-
input.substring(0, 50) + (input.length > 50 ? '...' : '');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export default AIChat;
|
|
265
|
-
`.trim(),
|
|
266
|
-
});
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { defineBlock } from '@fragments/core';
|
|
2
|
-
|
|
3
|
-
export default defineBlock({
|
|
4
|
-
name: 'App Shell',
|
|
5
|
-
description: 'Full application layout with sidebar, header, and main content. Supports two layout modes: stacked (header full-width) and sidebar-inset (sidebar full-height).',
|
|
6
|
-
category: 'layout',
|
|
7
|
-
components: ['AppShell', 'Header', 'Sidebar', 'Theme'],
|
|
8
|
-
tags: ['layout', 'app-shell', 'sidebar', 'navigation', 'dashboard'],
|
|
9
|
-
code: `
|
|
10
|
-
// App Shell - Stacked Layout (header spans full width)
|
|
11
|
-
// Best for apps where the brand should be prominent in the header
|
|
12
|
-
|
|
13
|
-
import { AppShell, Header, Input, Sidebar, ThemeToggle } from '@fragments-sdk/ui';
|
|
14
|
-
|
|
15
|
-
function StackedLayout({ children }) {
|
|
16
|
-
return (
|
|
17
|
-
<AppShell layout="stacked">
|
|
18
|
-
<AppShell.Header>
|
|
19
|
-
<Header>
|
|
20
|
-
<Header.SkipLink />
|
|
21
|
-
<Header.Trigger />
|
|
22
|
-
<Header.Brand href="/">MyApp</Header.Brand>
|
|
23
|
-
<Header.Nav>
|
|
24
|
-
<Header.NavItem href="/" active>Dashboard</Header.NavItem>
|
|
25
|
-
<Header.NavItem href="/settings">Settings</Header.NavItem>
|
|
26
|
-
</Header.Nav>
|
|
27
|
-
<Header.Spacer />
|
|
28
|
-
<Header.Actions>
|
|
29
|
-
<ThemeToggle />
|
|
30
|
-
</Header.Actions>
|
|
31
|
-
</Header>
|
|
32
|
-
</AppShell.Header>
|
|
33
|
-
|
|
34
|
-
<AppShell.Sidebar width="240px" collapsible="offcanvas">
|
|
35
|
-
<Sidebar.Nav>
|
|
36
|
-
<Sidebar.Section label="Menu">
|
|
37
|
-
<Sidebar.Item icon={<HomeIcon />} href="/" active>
|
|
38
|
-
Home
|
|
39
|
-
</Sidebar.Item>
|
|
40
|
-
<Sidebar.Item icon={<ChartIcon />} href="/analytics">
|
|
41
|
-
Analytics
|
|
42
|
-
</Sidebar.Item>
|
|
43
|
-
<Sidebar.Item icon={<GearIcon />} href="/settings">
|
|
44
|
-
Settings
|
|
45
|
-
</Sidebar.Item>
|
|
46
|
-
</Sidebar.Section>
|
|
47
|
-
</Sidebar.Nav>
|
|
48
|
-
</AppShell.Sidebar>
|
|
49
|
-
|
|
50
|
-
<AppShell.Main padding="lg">
|
|
51
|
-
{children}
|
|
52
|
-
</AppShell.Main>
|
|
53
|
-
</AppShell>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// App Shell - Sidebar Inset Layout (sidebar is full height)
|
|
58
|
-
// Best for documentation sites or when sidebar branding is preferred
|
|
59
|
-
|
|
60
|
-
function SidebarInsetLayout({ children }) {
|
|
61
|
-
return (
|
|
62
|
-
<AppShell layout="sidebar-inset">
|
|
63
|
-
<AppShell.Header>
|
|
64
|
-
<Header>
|
|
65
|
-
<Header.SkipLink />
|
|
66
|
-
<Header.Trigger />
|
|
67
|
-
<Header.Search>
|
|
68
|
-
<Input placeholder="Search..." />
|
|
69
|
-
</Header.Search>
|
|
70
|
-
<Header.Spacer />
|
|
71
|
-
<Header.Actions>
|
|
72
|
-
<ThemeToggle />
|
|
73
|
-
</Header.Actions>
|
|
74
|
-
</Header>
|
|
75
|
-
</AppShell.Header>
|
|
76
|
-
|
|
77
|
-
<AppShell.Sidebar width="260px" collapsible="offcanvas">
|
|
78
|
-
<Sidebar.Header>
|
|
79
|
-
<a href="/">MyApp</a>
|
|
80
|
-
</Sidebar.Header>
|
|
81
|
-
<Sidebar.Nav>
|
|
82
|
-
<Sidebar.Section label="Getting Started">
|
|
83
|
-
<Sidebar.Item href="/docs" active>Introduction</Sidebar.Item>
|
|
84
|
-
<Sidebar.Item href="/docs/install">Installation</Sidebar.Item>
|
|
85
|
-
</Sidebar.Section>
|
|
86
|
-
<Sidebar.Section label="Components">
|
|
87
|
-
<Sidebar.Item href="/components">Overview</Sidebar.Item>
|
|
88
|
-
<Sidebar.Item href="/components/button">Button</Sidebar.Item>
|
|
89
|
-
</Sidebar.Section>
|
|
90
|
-
</Sidebar.Nav>
|
|
91
|
-
<Sidebar.Footer>v1.0.0</Sidebar.Footer>
|
|
92
|
-
</AppShell.Sidebar>
|
|
93
|
-
|
|
94
|
-
<AppShell.Main padding="lg">
|
|
95
|
-
{children}
|
|
96
|
-
</AppShell.Main>
|
|
97
|
-
</AppShell>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// App Shell with Collapsible Icon Sidebar
|
|
102
|
-
// Sidebar collapses to icons only - great for dashboards
|
|
103
|
-
|
|
104
|
-
function CollapsibleLayout({ children }) {
|
|
105
|
-
return (
|
|
106
|
-
<AppShell layout="sidebar-inset">
|
|
107
|
-
<AppShell.Header>
|
|
108
|
-
<Header>
|
|
109
|
-
<Header.Trigger />
|
|
110
|
-
<Header.Spacer />
|
|
111
|
-
<Header.Actions>
|
|
112
|
-
<ThemeToggle />
|
|
113
|
-
</Header.Actions>
|
|
114
|
-
</Header>
|
|
115
|
-
</AppShell.Header>
|
|
116
|
-
|
|
117
|
-
<AppShell.Sidebar collapsible="icon" width="240px" collapsedWidth="64px">
|
|
118
|
-
<Sidebar.Header collapsedContent={<Logo />}>
|
|
119
|
-
<Logo /> <span>MyApp</span>
|
|
120
|
-
</Sidebar.Header>
|
|
121
|
-
<Sidebar.Nav>
|
|
122
|
-
<Sidebar.Section>
|
|
123
|
-
<Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
|
|
124
|
-
<Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
|
|
125
|
-
<Sidebar.Item icon={<GearIcon />}>Settings</Sidebar.Item>
|
|
126
|
-
</Sidebar.Section>
|
|
127
|
-
</Sidebar.Nav>
|
|
128
|
-
<Sidebar.Footer>
|
|
129
|
-
<Sidebar.CollapseToggle />
|
|
130
|
-
</Sidebar.Footer>
|
|
131
|
-
</AppShell.Sidebar>
|
|
132
|
-
|
|
133
|
-
<AppShell.Main padding="lg">
|
|
134
|
-
{children}
|
|
135
|
-
</AppShell.Main>
|
|
136
|
-
</AppShell>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// App Shell with Aside Panel
|
|
141
|
-
// Optional right panel for additional context
|
|
142
|
-
|
|
143
|
-
function LayoutWithAside({ children, aside }) {
|
|
144
|
-
return (
|
|
145
|
-
<AppShell layout="stacked">
|
|
146
|
-
<AppShell.Header>
|
|
147
|
-
<Header>
|
|
148
|
-
<Header.Brand>MyApp</Header.Brand>
|
|
149
|
-
<Header.Spacer />
|
|
150
|
-
<Header.Actions>
|
|
151
|
-
<ThemeToggle />
|
|
152
|
-
</Header.Actions>
|
|
153
|
-
</Header>
|
|
154
|
-
</AppShell.Header>
|
|
155
|
-
|
|
156
|
-
<AppShell.Sidebar width="200px" collapsible="offcanvas">
|
|
157
|
-
<Sidebar.Nav>
|
|
158
|
-
<Sidebar.Section>
|
|
159
|
-
<Sidebar.Item icon={<HomeIcon />} active>Home</Sidebar.Item>
|
|
160
|
-
</Sidebar.Section>
|
|
161
|
-
</Sidebar.Nav>
|
|
162
|
-
</AppShell.Sidebar>
|
|
163
|
-
|
|
164
|
-
<AppShell.Main padding="lg">
|
|
165
|
-
{children}
|
|
166
|
-
</AppShell.Main>
|
|
167
|
-
|
|
168
|
-
<AppShell.Aside width="280px">
|
|
169
|
-
{aside}
|
|
170
|
-
</AppShell.Aside>
|
|
171
|
-
</AppShell>
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
`.trim(),
|
|
175
|
-
});
|