@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.
Files changed (128) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -0
  3. package/fragments.json +1 -1
  4. package/package.json +21 -2
  5. package/src/assets/fragments-logo.tsx +37 -0
  6. package/src/assets/fragments_logo.svg +1 -0
  7. package/src/assets/fragments_logo_text.svg +1 -0
  8. package/src/blocks/AccountSettings.block.ts +1 -1
  9. package/src/blocks/ActivityFeed.block.ts +7 -7
  10. package/src/blocks/ChatInterface.block.ts +36 -80
  11. package/src/blocks/DashboardLayout.block.ts +85 -66
  12. package/src/blocks/DashboardPage.block.ts +297 -0
  13. package/src/blocks/EmptyState.block.ts +5 -3
  14. package/src/blocks/FeatureGrid.block.ts +1 -1
  15. package/src/blocks/LoginForm.block.ts +21 -26
  16. package/src/blocks/PricingComparison.block.ts +1 -1
  17. package/src/blocks/ShoppingCart.block.ts +2 -2
  18. package/src/components/Accordion/Accordion.fragment.tsx +2 -2
  19. package/src/components/Alert/Alert.fragment.tsx +2 -2
  20. package/src/components/AppShell/AppShell.fragment.tsx +2 -2
  21. package/src/components/Avatar/Avatar.fragment.tsx +2 -2
  22. package/src/components/Badge/Badge.fragment.tsx +2 -2
  23. package/src/components/Box/Box.fragment.tsx +2 -2
  24. package/src/components/Breadcrumbs/Breadcrumbs.fragment.tsx +2 -2
  25. package/src/components/Button/Button.fragment.tsx +2 -2
  26. package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +2 -2
  27. package/src/components/Card/Card.fragment.tsx +2 -2
  28. package/src/components/Chart/Chart.fragment.tsx +2 -2
  29. package/src/components/Checkbox/Checkbox.fragment.tsx +2 -2
  30. package/src/components/Chip/Chip.fragment.tsx +2 -2
  31. package/src/components/CodeBlock/CodeBlock.fragment.tsx +2 -2
  32. package/src/components/CodeBlock/CodeBlock.module.scss +10 -52
  33. package/src/components/CodeBlock/index.tsx +13 -24
  34. package/src/components/Collapsible/Collapsible.fragment.tsx +2 -2
  35. package/src/components/ColorPicker/ColorPicker.fragment.tsx +2 -2
  36. package/src/components/Combobox/Combobox.fragment.tsx +2 -2
  37. package/src/components/ConversationList/ConversationList.fragment.tsx +2 -2
  38. package/src/components/DatePicker/DatePicker.fragment.tsx +2 -2
  39. package/src/components/Dialog/Dialog.fragment.tsx +2 -2
  40. package/src/components/EmptyState/EmptyState.fragment.tsx +2 -2
  41. package/src/components/Field/Field.fragment.tsx +2 -2
  42. package/src/components/Fieldset/Fieldset.fragment.tsx +2 -2
  43. package/src/components/Form/Form.fragment.tsx +2 -2
  44. package/src/components/Grid/Grid.fragment.tsx +2 -2
  45. package/src/components/Header/Header.fragment.tsx +2 -2
  46. package/src/components/Icon/Icon.fragment.tsx +2 -2
  47. package/src/components/Image/Image.fragment.tsx +2 -2
  48. package/src/components/Input/Input.fragment.tsx +2 -2
  49. package/src/components/Input/Input.test.tsx +35 -0
  50. package/src/components/Input/index.tsx +47 -2
  51. package/src/components/Link/Link.fragment.tsx +2 -2
  52. package/src/components/List/List.fragment.tsx +2 -2
  53. package/src/components/Listbox/Listbox.fragment.tsx +2 -2
  54. package/src/components/Loading/Loading.fragment.tsx +2 -2
  55. package/src/components/Markdown/Markdown.fragment.tsx +2 -2
  56. package/src/components/Markdown/Markdown.module.scss +5 -0
  57. package/src/components/Menu/Menu.fragment.tsx +2 -2
  58. package/src/components/Menu/Menu.module.scss +2 -0
  59. package/src/components/Message/Message.fragment.tsx +2 -2
  60. package/src/components/Popover/Popover.fragment.tsx +2 -2
  61. package/src/components/Progress/Progress.fragment.tsx +2 -2
  62. package/src/components/Prompt/Prompt.fragment.tsx +2 -2
  63. package/src/components/RadioGroup/RadioGroup.fragment.tsx +2 -2
  64. package/src/components/ScrollArea/ScrollArea.fragment.tsx +2 -2
  65. package/src/components/Select/Select.fragment.tsx +2 -2
  66. package/src/components/Separator/Separator.fragment.tsx +2 -2
  67. package/src/components/Sidebar/Sidebar.fragment.tsx +2 -2
  68. package/src/components/Sidebar/Sidebar.module.scss +1 -5
  69. package/src/components/Skeleton/Skeleton.fragment.tsx +2 -2
  70. package/src/components/Slider/Slider.fragment.tsx +2 -2
  71. package/src/components/Stack/Stack.fragment.tsx +2 -2
  72. package/src/components/Table/Table.fragment.tsx +3 -3
  73. package/src/components/Table/index.tsx +43 -5
  74. package/src/components/TableOfContents/TableOfContents.fragment.tsx +2 -2
  75. package/src/components/TableOfContents/TableOfContents.module.scss +0 -5
  76. package/src/components/TableOfContents/index.tsx +6 -1
  77. package/src/components/Tabs/Tabs.fragment.tsx +2 -2
  78. package/src/components/Text/Text.fragment.tsx +2 -2
  79. package/src/components/Text/Text.module.scss +6 -0
  80. package/src/components/Text/Text.test.tsx +5 -0
  81. package/src/components/Text/index.tsx +3 -0
  82. package/src/components/Textarea/Textarea.fragment.tsx +2 -2
  83. package/src/components/Theme/Theme.fragment.tsx +2 -2
  84. package/src/components/Theme/ThemeToggle.module.scss +1 -1
  85. package/src/components/Theme/index.tsx +1 -1
  86. package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +2 -2
  87. package/src/components/Toast/Toast.fragment.tsx +2 -2
  88. package/src/components/Toggle/Toggle.fragment.tsx +2 -2
  89. package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +37 -5
  90. package/src/components/Tooltip/Tooltip.fragment.tsx +2 -2
  91. package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +2 -2
  92. package/src/index.ts +3 -0
  93. package/src/tokens/_derive.scss +32 -8
  94. package/src/tokens/_mixins.scss +9 -0
  95. package/src/tokens/_variables.scss +4 -4
  96. package/src/blocks/AIChat.block.ts +0 -266
  97. package/src/blocks/AppShell.block.ts +0 -175
  98. package/src/blocks/CTABanner.block.ts +0 -24
  99. package/src/blocks/CardGrid.block.ts +0 -22
  100. package/src/blocks/CodeExamples.block.ts +0 -66
  101. package/src/blocks/ConfirmDialog.block.ts +0 -19
  102. package/src/blocks/ConversationWithHistory.block.ts +0 -45
  103. package/src/blocks/DashboardNav.block.ts +0 -183
  104. package/src/blocks/ForgotPassword.block.ts +0 -26
  105. package/src/blocks/FormLayout.block.ts +0 -31
  106. package/src/blocks/InsetDashboardLayout.block.ts +0 -79
  107. package/src/blocks/MetricDashboard.block.ts +0 -38
  108. package/src/blocks/NewsletterSignup.block.ts +0 -26
  109. package/src/blocks/NotificationList.block.ts +0 -39
  110. package/src/blocks/NotificationPreferences.block.ts +0 -40
  111. package/src/blocks/OrderSummary.block.ts +0 -52
  112. package/src/blocks/ProfileEditForm.block.ts +0 -51
  113. package/src/blocks/SearchResults.block.ts +0 -39
  114. package/src/blocks/SettingsPage.block.ts +0 -58
  115. package/src/blocks/StreamingMessage.block.ts +0 -24
  116. package/src/blocks/TestimonialCard.block.ts +0 -27
  117. package/src/blocks/UserProfileCard.block.ts +0 -29
  118. package/src/recipes/AIChat.recipe.ts +0 -266
  119. package/src/recipes/AppShell.recipe.ts +0 -175
  120. package/src/recipes/CardGrid.recipe.ts +0 -22
  121. package/src/recipes/ChatInterface.recipe.ts +0 -87
  122. package/src/recipes/CodeExamples.recipe.ts +0 -66
  123. package/src/recipes/ConfirmDialog.recipe.ts +0 -19
  124. package/src/recipes/DashboardLayout.recipe.ts +0 -73
  125. package/src/recipes/DashboardNav.recipe.ts +0 -183
  126. package/src/recipes/FormLayout.recipe.ts +0 -31
  127. package/src/recipes/LoginForm.recipe.ts +0 -33
  128. package/src/recipes/SettingsPage.recipe.ts +0 -58
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineSegment } from '@fragments/core';
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 defineSegment({
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', 'segmented', 'control', 'tabs', 'switch'],
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
- 'Segmented controls like view switchers',
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.segmented',
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 { defineSegment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments/core';
3
3
  import { Tooltip } from '.';
4
4
  import { Button } from '../Button';
5
5
 
6
- export default defineSegment({
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 { defineSegment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments/core';
3
3
  import { VisuallyHidden } from '.';
4
4
 
5
- export default defineSegment({
5
+ export default defineFragment({
6
6
  component: VisuallyHidden,
7
7
 
8
8
  meta: {
package/src/index.ts CHANGED
@@ -521,6 +521,9 @@ export {
521
521
  type ChartLegendContentProps,
522
522
  } from './components/Chart';
523
523
 
524
+ // Assets
525
+ export { FragmentsLogo, type FragmentsLogoProps } from './assets/fragments-logo';
526
+
524
527
  // Accessibility Utilities
525
528
  export {
526
529
  useId,
@@ -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
- // In dark mode, lighten for hover
55
- @return color.scale($base, $lightness: 5%);
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
- // In light mode, darken for hover
58
- @return color.scale($base, $lightness: -8%);
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
- // In dark mode, lighten more for active
69
- @return color.scale($base, $lightness: 10%);
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
- // In light mode, darken more for active
72
- @return color.scale($base, $lightness: -15%);
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
 
@@ -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: #1e1e1e !default;
350
- $fui-code-header-bg: #252526 !default;
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: #1e1e1e !default;
376
- $fui-dark-code-header-bg: #252526 !default;
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
- });