@fragments-sdk/ui 0.6.4 → 0.7.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 (65) hide show
  1. package/README.md +3 -3
  2. package/fragments.json +1 -1
  3. package/package.json +16 -3
  4. package/src/blocks/AIChat.block.ts +266 -0
  5. package/src/blocks/AccountSettings.block.ts +47 -0
  6. package/src/blocks/ActivityFeed.block.ts +38 -0
  7. package/src/blocks/AppShell.block.ts +175 -0
  8. package/src/blocks/CTABanner.block.ts +24 -0
  9. package/src/blocks/CardGrid.block.ts +22 -0
  10. package/src/blocks/ChatInterface.block.ts +87 -0
  11. package/src/blocks/ChatMessages.block.ts +35 -0
  12. package/src/blocks/CheckoutForm.block.ts +62 -0
  13. package/src/blocks/CodeExamples.block.ts +66 -0
  14. package/src/blocks/ConfirmDialog.block.ts +19 -0
  15. package/src/blocks/ContactForm.block.ts +28 -0
  16. package/src/blocks/ConversationWithHistory.block.ts +45 -0
  17. package/src/blocks/DashboardLayout.block.ts +73 -0
  18. package/src/blocks/DashboardNav.block.ts +183 -0
  19. package/src/blocks/DataTable.block.ts +29 -0
  20. package/src/blocks/EmptyState.block.ts +21 -0
  21. package/src/blocks/FAQSection.block.ts +35 -0
  22. package/src/blocks/FeatureGrid.block.ts +33 -0
  23. package/src/blocks/ForgotPassword.block.ts +26 -0
  24. package/src/blocks/FormLayout.block.ts +31 -0
  25. package/src/blocks/HeroSection.block.ts +31 -0
  26. package/src/blocks/InsetDashboardLayout.block.ts +79 -0
  27. package/src/blocks/LoginForm.block.ts +26 -0
  28. package/src/blocks/MetricDashboard.block.ts +38 -0
  29. package/src/blocks/NewsletterSignup.block.ts +26 -0
  30. package/src/blocks/NotificationList.block.ts +39 -0
  31. package/src/blocks/NotificationPreferences.block.ts +40 -0
  32. package/src/blocks/OrderSummary.block.ts +52 -0
  33. package/src/blocks/PricingComparison.block.ts +44 -0
  34. package/src/blocks/ProductCard.block.ts +33 -0
  35. package/src/blocks/ProfileEditForm.block.ts +51 -0
  36. package/src/blocks/RegistrationForm.block.ts +38 -0
  37. package/src/blocks/SearchResults.block.ts +39 -0
  38. package/src/blocks/SettingsPage.block.ts +58 -0
  39. package/src/blocks/SettingsPanel.block.ts +35 -0
  40. package/src/blocks/ShoppingCart.block.ts +46 -0
  41. package/src/blocks/StatsCard.block.ts +26 -0
  42. package/src/blocks/StreamingMessage.block.ts +24 -0
  43. package/src/blocks/TestimonialCard.block.ts +27 -0
  44. package/src/blocks/ThinkingStates.block.ts +48 -0
  45. package/src/blocks/UserProfileCard.block.ts +29 -0
  46. package/src/components/AppShell/AppShell.module.scss +2 -1
  47. package/src/components/Box/Box.fragment.tsx +110 -0
  48. package/src/components/Box/Box.module.scss +39 -0
  49. package/src/components/Box/index.tsx +68 -1
  50. package/src/components/Breadcrumbs/Breadcrumbs.fragment.tsx +162 -0
  51. package/src/components/Breadcrumbs/Breadcrumbs.module.scss +120 -0
  52. package/src/components/Breadcrumbs/index.tsx +202 -0
  53. package/src/components/Chip/Chip.fragment.tsx +175 -0
  54. package/src/components/Chip/Chip.module.scss +174 -0
  55. package/src/components/Chip/index.tsx +151 -0
  56. package/src/components/Markdown/Markdown.fragment.tsx +226 -0
  57. package/src/components/Markdown/Markdown.module.scss +219 -0
  58. package/src/components/Markdown/index.tsx +106 -0
  59. package/src/components/Message/Message.module.scss +0 -4
  60. package/src/components/Message/index.tsx +9 -2
  61. package/src/components/Prompt/index.tsx +2 -1
  62. package/src/components/Stack/Stack.fragment.tsx +16 -0
  63. package/src/components/Stack/Stack.module.scss +16 -0
  64. package/src/components/Stack/index.tsx +35 -1
  65. package/src/index.ts +17 -0
@@ -0,0 +1,174 @@
1
+ @use '../../tokens/variables' as *;
2
+ @use '../../tokens/mixins' as *;
3
+
4
+ .chip {
5
+ @include button-reset;
6
+ @include interactive-base;
7
+
8
+ display: inline-flex;
9
+ align-items: center;
10
+ gap: var(--fui-space-1, $fui-space-1);
11
+ border-radius: var(--fui-radius-full, $fui-radius-full);
12
+ font-family: var(--fui-font-sans, $fui-font-sans);
13
+ font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
14
+ line-height: 1;
15
+ white-space: nowrap;
16
+ user-select: none;
17
+ }
18
+
19
+ // Sizes
20
+ .sm {
21
+ height: var(--fui-button-height-sm, $fui-button-height-sm);
22
+ padding: 0 var(--fui-space-2, $fui-space-2);
23
+ font-size: var(--fui-font-size-xs, $fui-font-size-xs);
24
+ }
25
+
26
+ .md {
27
+ height: var(--fui-button-height-md, $fui-button-height-md);
28
+ padding: 0 var(--fui-space-3, $fui-space-3);
29
+ font-size: var(--fui-font-size-sm, $fui-font-size-sm);
30
+ }
31
+
32
+ // Variants
33
+ .filled {
34
+ background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
35
+ color: var(--fui-text-primary, $fui-text-primary);
36
+
37
+ &:hover:not(:disabled) {
38
+ background-color: var(--fui-bg-hover, $fui-bg-hover);
39
+ }
40
+
41
+ &:active:not(:disabled) {
42
+ background-color: var(--fui-bg-active, $fui-bg-active);
43
+ }
44
+
45
+ &.selected {
46
+ background-color: var(--fui-color-accent, $fui-color-accent);
47
+ color: var(--fui-text-inverse, $fui-text-inverse);
48
+
49
+ &:hover:not(:disabled) {
50
+ background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
51
+ }
52
+
53
+ &:active:not(:disabled) {
54
+ background-color: var(--fui-color-accent-active, $fui-color-accent-active);
55
+ }
56
+ }
57
+ }
58
+
59
+ .outlined {
60
+ background-color: transparent;
61
+ color: var(--fui-text-primary, $fui-text-primary);
62
+ border: 1px solid var(--fui-border, $fui-border);
63
+
64
+ &:hover:not(:disabled) {
65
+ background-color: var(--fui-bg-hover, $fui-bg-hover);
66
+ border-color: var(--fui-border-strong, $fui-border-strong);
67
+ }
68
+
69
+ &:active:not(:disabled) {
70
+ background-color: var(--fui-bg-active, $fui-bg-active);
71
+ }
72
+
73
+ &.selected {
74
+ background-color: var(--fui-color-accent, $fui-color-accent);
75
+ color: var(--fui-text-inverse, $fui-text-inverse);
76
+ border-color: var(--fui-color-accent, $fui-color-accent);
77
+
78
+ &:hover:not(:disabled) {
79
+ background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
80
+ border-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
81
+ }
82
+
83
+ &:active:not(:disabled) {
84
+ background-color: var(--fui-color-accent-active, $fui-color-accent-active);
85
+ border-color: var(--fui-color-accent-active, $fui-color-accent-active);
86
+ }
87
+ }
88
+ }
89
+
90
+ .soft {
91
+ background-color: var(--fui-color-info-bg, $fui-color-info-bg);
92
+ color: var(--fui-color-info, $fui-color-info);
93
+
94
+ &:hover:not(:disabled) {
95
+ background-color: var(--fui-bg-hover, $fui-bg-hover);
96
+ }
97
+
98
+ &:active:not(:disabled) {
99
+ background-color: var(--fui-bg-active, $fui-bg-active);
100
+ }
101
+
102
+ &.selected {
103
+ background-color: var(--fui-color-accent, $fui-color-accent);
104
+ color: var(--fui-text-inverse, $fui-text-inverse);
105
+
106
+ &:hover:not(:disabled) {
107
+ background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
108
+ }
109
+
110
+ &:active:not(:disabled) {
111
+ background-color: var(--fui-color-accent-active, $fui-color-accent-active);
112
+ }
113
+ }
114
+ }
115
+
116
+ // Avatar slot
117
+ .avatar {
118
+ display: flex;
119
+ align-items: center;
120
+ margin-left: calc(-1 * var(--fui-space-1, $fui-space-1));
121
+
122
+ img {
123
+ width: var(--fui-icon-md, $fui-icon-md);
124
+ height: var(--fui-icon-md, $fui-icon-md);
125
+ border-radius: var(--fui-radius-full, $fui-radius-full);
126
+ object-fit: cover;
127
+ }
128
+
129
+ .sm & {
130
+ img {
131
+ width: var(--fui-icon-sm, $fui-icon-sm);
132
+ height: var(--fui-icon-sm, $fui-icon-sm);
133
+ }
134
+ }
135
+ }
136
+
137
+ // Icon slot
138
+ .icon {
139
+ display: flex;
140
+ align-items: center;
141
+ }
142
+
143
+ // Remove button
144
+ .remove {
145
+ @include button-reset;
146
+ @include interactive-base;
147
+
148
+ display: flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ margin-right: calc(-1 * var(--fui-space-1, $fui-space-1));
152
+ padding: var(--fui-space-0-5, $fui-space-0-5);
153
+ font-size: var(--fui-font-size-sm, $fui-font-size-sm);
154
+ color: inherit;
155
+ opacity: 0.6;
156
+ line-height: 1;
157
+ border-radius: var(--fui-radius-full, $fui-radius-full);
158
+
159
+ .sm & {
160
+ font-size: var(--fui-font-size-xs, $fui-font-size-xs);
161
+ }
162
+
163
+ &:hover {
164
+ opacity: 1;
165
+ background-color: var(--fui-bg-hover, $fui-bg-hover);
166
+ }
167
+ }
168
+
169
+ // Chip.Group
170
+ .group {
171
+ display: flex;
172
+ flex-wrap: wrap;
173
+ gap: var(--fui-space-2, $fui-space-2);
174
+ }
@@ -0,0 +1,151 @@
1
+ import * as React from 'react';
2
+ import styles from './Chip.module.scss';
3
+ import '../../styles/globals.scss';
4
+
5
+ export interface ChipProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
6
+ children: React.ReactNode;
7
+ /** Visual style variant */
8
+ variant?: 'filled' | 'outlined' | 'soft';
9
+ /** Size of the chip */
10
+ size?: 'sm' | 'md';
11
+ /** Whether the chip is selected */
12
+ selected?: boolean;
13
+ /** Icon element rendered before the label */
14
+ icon?: React.ReactNode;
15
+ /** Avatar element rendered before the label */
16
+ avatar?: React.ReactNode;
17
+ /** Makes chip removable. Called when X is clicked. */
18
+ onRemove?: () => void;
19
+ /** Value identifier used by Chip.Group */
20
+ value?: string;
21
+ }
22
+
23
+ export interface ChipGroupProps {
24
+ children: React.ReactNode;
25
+ /** Controlled selected values */
26
+ value?: string[];
27
+ /** Default selected values (uncontrolled) */
28
+ defaultValue?: string[];
29
+ /** Called when selection changes */
30
+ onChange?: (value: string[]) => void;
31
+ className?: string;
32
+ }
33
+
34
+ const ChipBase = React.forwardRef<HTMLButtonElement, ChipProps>(
35
+ function Chip(
36
+ {
37
+ children,
38
+ variant = 'filled',
39
+ size = 'md',
40
+ selected = false,
41
+ disabled = false,
42
+ icon,
43
+ avatar,
44
+ onRemove,
45
+ className,
46
+ onClick,
47
+ value: _value,
48
+ ...htmlProps
49
+ },
50
+ ref
51
+ ) {
52
+ const classes = [
53
+ styles.chip,
54
+ styles[size],
55
+ styles[variant],
56
+ selected && styles.selected,
57
+ className,
58
+ ]
59
+ .filter(Boolean)
60
+ .join(' ');
61
+
62
+ return (
63
+ <button
64
+ ref={ref}
65
+ type="button"
66
+ role="option"
67
+ aria-selected={selected}
68
+ disabled={disabled}
69
+ className={classes}
70
+ onClick={onClick}
71
+ {...htmlProps}
72
+ >
73
+ {avatar && (
74
+ <span className={styles.avatar} aria-hidden="true">
75
+ {avatar}
76
+ </span>
77
+ )}
78
+ {icon && (
79
+ <span className={styles.icon} aria-hidden="true">
80
+ {icon}
81
+ </span>
82
+ )}
83
+ <span>{children}</span>
84
+ {onRemove && (
85
+ <span
86
+ role="button"
87
+ tabIndex={0}
88
+ aria-label={`Remove ${typeof children === 'string' ? children : 'chip'}`}
89
+ className={styles.remove}
90
+ onClick={(e) => {
91
+ e.stopPropagation();
92
+ onRemove();
93
+ }}
94
+ onKeyDown={(e) => {
95
+ if (e.key === 'Enter' || e.key === ' ') {
96
+ e.preventDefault();
97
+ e.stopPropagation();
98
+ onRemove();
99
+ }
100
+ }}
101
+ >
102
+ &times;
103
+ </span>
104
+ )}
105
+ </button>
106
+ );
107
+ }
108
+ );
109
+
110
+ function ChipGroupInner(
111
+ { children, value: controlledValue, defaultValue = [], onChange, className }: ChipGroupProps,
112
+ ref: React.Ref<HTMLDivElement>
113
+ ) {
114
+ const [internalValue, setInternalValue] = React.useState<string[]>(defaultValue);
115
+ const isControlled = controlledValue !== undefined;
116
+ const currentValue = isControlled ? controlledValue : internalValue;
117
+
118
+ const toggle = React.useCallback(
119
+ (chipValue: string) => {
120
+ const next = currentValue.includes(chipValue)
121
+ ? currentValue.filter((v) => v !== chipValue)
122
+ : [...currentValue, chipValue];
123
+
124
+ if (!isControlled) {
125
+ setInternalValue(next);
126
+ }
127
+ onChange?.(next);
128
+ },
129
+ [currentValue, isControlled, onChange]
130
+ );
131
+
132
+ const classes = [styles.group, className].filter(Boolean).join(' ');
133
+
134
+ return (
135
+ <div ref={ref} role="listbox" aria-multiselectable="true" className={classes}>
136
+ {React.Children.map(children, (child) => {
137
+ if (!React.isValidElement<ChipProps>(child)) return child;
138
+ const chipValue = child.props.value ?? (typeof child.props.children === 'string' ? child.props.children : '');
139
+ return React.cloneElement(child, {
140
+ selected: currentValue.includes(chipValue),
141
+ onClick: () => toggle(chipValue),
142
+ } as Partial<ChipProps>);
143
+ })}
144
+ </div>
145
+ );
146
+ }
147
+
148
+ const ChipGroup = React.forwardRef<HTMLDivElement, ChipGroupProps>(ChipGroupInner);
149
+
150
+ // Compose Chip with static Group property
151
+ export const Chip = Object.assign(ChipBase, { Group: ChipGroup });
@@ -0,0 +1,226 @@
1
+ import React from 'react';
2
+ import { defineSegment } from '@fragments/core';
3
+ import { Markdown } from '.';
4
+
5
+ const defaultContent = `# Hello World
6
+
7
+ This is a paragraph with **bold text** and *italic text*.
8
+
9
+ Here is some \`inline code\` within a sentence.
10
+
11
+ - First item
12
+ - Second item
13
+ - Third item
14
+
15
+ > A blockquote for emphasis.
16
+ `;
17
+
18
+ const gfmTableContent = `## Data Overview
19
+
20
+ | Feature | Status | Priority |
21
+ |------------|-----------|----------|
22
+ | Markdown | Done | High |
23
+ | Tables | Done | Medium |
24
+ | Task Lists | Planned | Low |
25
+
26
+ Notes:
27
+ - [x] Support GFM tables
28
+ - [x] Support task lists
29
+ - [ ] Syntax highlighting
30
+ `;
31
+
32
+ const codeBlockContent = `## Code Example
33
+
34
+ Here is a JavaScript function:
35
+
36
+ \`\`\`js
37
+ function greet(name) {
38
+ return \\\`Hello, \\\${name}!\\\`;
39
+ }
40
+
41
+ console.log(greet('World'));
42
+ \`\`\`
43
+
44
+ And some inline code: \`const x = 42;\`
45
+ `;
46
+
47
+ const mixedContent = `# Project Update
48
+
49
+ ## Summary
50
+
51
+ The project is progressing well. Here are the **key highlights**:
52
+
53
+ 1. Completed the *design system* components
54
+ 2. Added markdown rendering support
55
+ 3. Integrated with the documentation site
56
+
57
+ ### Performance Metrics
58
+
59
+ | Metric | Before | After |
60
+ |-------------|--------|--------|
61
+ | Bundle Size | 142kb | 98kb |
62
+ | Load Time | 1.2s | 0.8s |
63
+ | Lighthouse | 72 | 95 |
64
+
65
+ > These improvements were achieved through tree-shaking and code splitting.
66
+
67
+ ### Next Steps
68
+
69
+ - [ ] Add syntax highlighting
70
+ - [ ] Support custom themes
71
+ - [x] GFM table support
72
+
73
+ ---
74
+
75
+ For more details, see the [documentation](#).
76
+ `;
77
+
78
+ export default defineSegment({
79
+ component: Markdown,
80
+
81
+ meta: {
82
+ name: 'Markdown',
83
+ description: 'Renders markdown strings as styled prose using react-markdown and remark-gfm. Supports headings, lists, tables, code blocks, blockquotes, and more.',
84
+ category: 'display',
85
+ status: 'stable',
86
+ tags: ['markdown', 'prose', 'content', 'text', 'ai', 'chat'],
87
+ since: '0.7.0',
88
+ },
89
+
90
+ usage: {
91
+ when: [
92
+ 'Rendering AI/LLM response content',
93
+ 'Displaying user-authored markdown text',
94
+ 'Showing documentation or readme content',
95
+ 'Rich text display without a WYSIWYG editor',
96
+ ],
97
+ whenNot: [
98
+ 'Plain text without formatting (use Text)',
99
+ 'Editing markdown (use a markdown editor component)',
100
+ 'Rendering trusted HTML directly (use dangerouslySetInnerHTML)',
101
+ ],
102
+ guidelines: [
103
+ 'Install react-markdown and remark-gfm as peer dependencies',
104
+ 'Use the components prop to override default element rendering',
105
+ 'Content is sanitized by react-markdown by default',
106
+ 'Falls back to plain text paragraphs if react-markdown is not installed',
107
+ ],
108
+ accessibility: [
109
+ 'Rendered HTML follows semantic structure (headings, lists, tables)',
110
+ 'Links are rendered as proper anchor elements',
111
+ 'Images include alt text from markdown syntax',
112
+ 'Tables use proper th/td structure for screen readers',
113
+ ],
114
+ },
115
+
116
+ props: {
117
+ content: {
118
+ type: 'string',
119
+ description: 'Markdown string to render',
120
+ required: true,
121
+ },
122
+ components: {
123
+ type: 'object',
124
+ description: 'Override map for markdown element components (e.g., { h1: MyHeading })',
125
+ },
126
+ className: {
127
+ type: 'string',
128
+ description: 'Additional CSS class name',
129
+ },
130
+ },
131
+
132
+ relations: [
133
+ { component: 'Text', relationship: 'alternative', note: 'Use Text for plain, non-markdown text' },
134
+ { component: 'CodeBlock', relationship: 'complementary', note: 'CodeBlock can be used via components prop for syntax highlighting' },
135
+ { component: 'Message', relationship: 'parent', note: 'Message wraps Markdown when markdown prop is true' },
136
+ ],
137
+
138
+ contract: {
139
+ propsSummary: [
140
+ 'content: string - Markdown string to render',
141
+ 'components: object - Override map for element components',
142
+ 'className: string - Additional CSS class',
143
+ ],
144
+ scenarioTags: [
145
+ 'content.markdown',
146
+ 'content.prose',
147
+ 'ai.response',
148
+ ],
149
+ a11yRules: ['A11Y_SEMANTIC_HTML'],
150
+ },
151
+
152
+ variants: [
153
+ {
154
+ name: 'Default',
155
+ description: 'Basic markdown with headings, paragraphs, inline code, lists, and blockquote',
156
+ code: `<Markdown content={\`# Hello World
157
+
158
+ This is a paragraph with **bold text** and *italic text*.
159
+
160
+ Here is some \\\`inline code\\\` within a sentence.
161
+
162
+ - First item
163
+ - Second item
164
+ - Third item
165
+
166
+ > A blockquote for emphasis.
167
+ \`} />`,
168
+ render: () => <Markdown content={defaultContent} />,
169
+ },
170
+ {
171
+ name: 'GFM Table',
172
+ description: 'GitHub Flavored Markdown with tables and task lists',
173
+ code: `<Markdown content={\`## Data Overview
174
+
175
+ | Feature | Status | Priority |
176
+ |------------|-----------|----------|
177
+ | Markdown | Done | High |
178
+ | Tables | Done | Medium |
179
+ | Task Lists | Planned | Low |
180
+
181
+ Notes:
182
+ - [x] Support GFM tables
183
+ - [x] Support task lists
184
+ - [ ] Syntax highlighting
185
+ \`} />`,
186
+ render: () => <Markdown content={gfmTableContent} />,
187
+ },
188
+ {
189
+ name: 'Code Block',
190
+ description: 'Markdown with fenced code blocks and inline code',
191
+ code: '<Markdown content={`## Code Example\\n\\nHere is a JavaScript function:\\n\\n```js\\nfunction greet(name) {\\n return \\`Hello, ${name}!\\`;\\n}\\n```\\n\\nAnd some inline code: \\`const x = 42;\\``} />',
192
+ render: () => <Markdown content={codeBlockContent} />,
193
+ },
194
+ {
195
+ name: 'Mixed Content',
196
+ description: 'Complex markdown mixing headings, lists, tables, blockquotes, and task lists',
197
+ code: `<Markdown content={\`# Project Update
198
+
199
+ ## Summary
200
+
201
+ The project is progressing well. Here are the **key highlights**:
202
+
203
+ 1. Completed the *design system* components
204
+ 2. Added markdown rendering support
205
+ 3. Integrated with the documentation site
206
+
207
+ ### Performance Metrics
208
+
209
+ | Metric | Before | After |
210
+ |-------------|--------|-------|
211
+ | Bundle Size | 142kb | 98kb |
212
+ | Load Time | 1.2s | 0.8s |
213
+ | Lighthouse | 72 | 95 |
214
+
215
+ > These improvements were achieved through tree-shaking and code splitting.
216
+
217
+ ### Next Steps
218
+
219
+ - [ ] Add syntax highlighting
220
+ - [ ] Support custom themes
221
+ - [x] GFM table support
222
+ \`} />`,
223
+ render: () => <Markdown content={mixedContent} />,
224
+ },
225
+ ],
226
+ });