@fragments-sdk/ui 0.6.5 → 0.7.1

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 (66) 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/Box/Box.fragment.tsx +110 -0
  47. package/src/components/Box/Box.module.scss +39 -0
  48. package/src/components/Box/index.tsx +68 -1
  49. package/src/components/Breadcrumbs/Breadcrumbs.fragment.tsx +162 -0
  50. package/src/components/Breadcrumbs/Breadcrumbs.module.scss +120 -0
  51. package/src/components/Breadcrumbs/index.tsx +202 -0
  52. package/src/components/Chip/Chip.fragment.tsx +175 -0
  53. package/src/components/Chip/Chip.module.scss +174 -0
  54. package/src/components/Chip/index.tsx +151 -0
  55. package/src/components/Markdown/Markdown.fragment.tsx +226 -0
  56. package/src/components/Markdown/Markdown.module.scss +219 -0
  57. package/src/components/Markdown/index.tsx +106 -0
  58. package/src/components/Message/index.tsx +9 -2
  59. package/src/components/Prompt/index.tsx +2 -1
  60. package/src/components/Stack/Stack.fragment.tsx +16 -0
  61. package/src/components/Stack/Stack.module.scss +16 -0
  62. package/src/components/Stack/index.tsx +35 -1
  63. package/src/components/Table/Table.fragment.tsx +34 -0
  64. package/src/components/Table/Table.module.scss +35 -5
  65. package/src/components/Table/index.tsx +8 -2
  66. package/src/index.ts +17 -0
@@ -0,0 +1,219 @@
1
+ @use '../../tokens/variables' as *;
2
+ @use '../../tokens/mixins' as *;
3
+
4
+ // ============================================
5
+ // Markdown Prose Container
6
+ // ============================================
7
+
8
+ .markdown {
9
+ @include text-base;
10
+ line-height: var(--fui-line-height-normal, $fui-line-height-normal);
11
+ word-wrap: break-word;
12
+ color: var(--fui-text-primary, $fui-text-primary);
13
+
14
+ // ----------------------------------------
15
+ // Headings
16
+ // ----------------------------------------
17
+
18
+ h1,
19
+ h2,
20
+ h3,
21
+ h4,
22
+ h5,
23
+ h6 {
24
+ margin-top: var(--fui-space-4, $fui-space-4);
25
+ margin-bottom: var(--fui-space-2, $fui-space-2);
26
+ font-weight: var(--fui-font-weight-semibold, $fui-font-weight-semibold);
27
+ line-height: var(--fui-line-height-tight, $fui-line-height-tight);
28
+ color: var(--fui-text-primary, $fui-text-primary);
29
+
30
+ &:first-child {
31
+ margin-top: 0;
32
+ }
33
+ }
34
+
35
+ h1 {
36
+ font-size: var(--fui-font-size-2xl, $fui-font-size-2xl);
37
+ }
38
+
39
+ h2 {
40
+ font-size: var(--fui-font-size-xl, $fui-font-size-xl);
41
+ }
42
+
43
+ h3 {
44
+ font-size: var(--fui-font-size-lg, $fui-font-size-lg);
45
+ }
46
+
47
+ h4 {
48
+ font-size: var(--fui-font-size-base, $fui-font-size-base);
49
+ }
50
+
51
+ h5,
52
+ h6 {
53
+ font-size: var(--fui-font-size-sm, $fui-font-size-sm);
54
+ }
55
+
56
+ // ----------------------------------------
57
+ // Paragraphs
58
+ // ----------------------------------------
59
+
60
+ p {
61
+ margin: 0;
62
+
63
+ & + p {
64
+ margin-top: var(--fui-space-2, $fui-space-2);
65
+ }
66
+ }
67
+
68
+ // ----------------------------------------
69
+ // Links
70
+ // ----------------------------------------
71
+
72
+ a {
73
+ color: var(--fui-color-accent, $fui-color-accent);
74
+ text-decoration: underline;
75
+ text-underline-offset: 2px;
76
+
77
+ &:hover {
78
+ opacity: 0.8;
79
+ }
80
+ }
81
+
82
+ // ----------------------------------------
83
+ // Lists
84
+ // ----------------------------------------
85
+
86
+ ul,
87
+ ol {
88
+ margin: var(--fui-space-2, $fui-space-2) 0;
89
+ padding-left: var(--fui-space-4, $fui-space-4);
90
+ }
91
+
92
+ li {
93
+ margin-bottom: var(--fui-space-1, $fui-space-1);
94
+
95
+ &:last-child {
96
+ margin-bottom: 0;
97
+ }
98
+ }
99
+
100
+ // Nested lists
101
+ li > ul,
102
+ li > ol {
103
+ margin-top: var(--fui-space-1, $fui-space-1);
104
+ margin-bottom: 0;
105
+ }
106
+
107
+ // ----------------------------------------
108
+ // Inline Code
109
+ // ----------------------------------------
110
+
111
+ code {
112
+ font-family: var(--fui-font-mono, $fui-font-mono);
113
+ font-size: 0.9em;
114
+ padding: 0.125em 0.375em;
115
+ border-radius: var(--fui-radius-sm, $fui-radius-sm);
116
+ background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
117
+ }
118
+
119
+ // ----------------------------------------
120
+ // Code Blocks
121
+ // ----------------------------------------
122
+
123
+ pre {
124
+ margin: var(--fui-space-2, $fui-space-2) 0;
125
+ padding: var(--fui-space-2, $fui-space-2);
126
+ border-radius: var(--fui-radius-md, $fui-radius-md);
127
+ background-color: var(--fui-code-bg, $fui-code-bg);
128
+ overflow-x: auto;
129
+
130
+ code {
131
+ padding: 0;
132
+ background: none;
133
+ font-size: var(--fui-font-size-xs, $fui-font-size-xs);
134
+ }
135
+ }
136
+
137
+ // ----------------------------------------
138
+ // Blockquotes
139
+ // ----------------------------------------
140
+
141
+ blockquote {
142
+ margin: var(--fui-space-2, $fui-space-2) 0;
143
+ padding: var(--fui-space-1, $fui-space-1) var(--fui-space-3, $fui-space-3);
144
+ border-left: 3px solid var(--fui-border, $fui-border);
145
+ color: var(--fui-text-secondary, $fui-text-secondary);
146
+
147
+ p {
148
+ margin: 0;
149
+ }
150
+ }
151
+
152
+ // ----------------------------------------
153
+ // Horizontal Rule
154
+ // ----------------------------------------
155
+
156
+ hr {
157
+ margin: var(--fui-space-4, $fui-space-4) 0;
158
+ border: none;
159
+ border-top: 1px solid var(--fui-border, $fui-border);
160
+ }
161
+
162
+ // ----------------------------------------
163
+ // Tables (GFM)
164
+ // ----------------------------------------
165
+
166
+ table {
167
+ width: 100%;
168
+ margin: var(--fui-space-2, $fui-space-2) 0;
169
+ border-collapse: collapse;
170
+ font-size: var(--fui-font-size-sm, $fui-font-size-sm);
171
+ }
172
+
173
+ th,
174
+ td {
175
+ padding: var(--fui-space-1, $fui-space-1) var(--fui-space-2, $fui-space-2);
176
+ border: 1px solid var(--fui-border, $fui-border);
177
+ text-align: left;
178
+ }
179
+
180
+ th {
181
+ font-weight: var(--fui-font-weight-semibold, $fui-font-weight-semibold);
182
+ background-color: var(--fui-bg-secondary, $fui-bg-secondary);
183
+ }
184
+
185
+ // ----------------------------------------
186
+ // Images
187
+ // ----------------------------------------
188
+
189
+ img {
190
+ max-width: 100%;
191
+ height: auto;
192
+ border-radius: var(--fui-radius-md, $fui-radius-md);
193
+ }
194
+
195
+ // ----------------------------------------
196
+ // Task Lists (GFM)
197
+ // ----------------------------------------
198
+
199
+ input[type='checkbox'] {
200
+ margin-right: var(--fui-space-1, $fui-space-1);
201
+ }
202
+
203
+ // ----------------------------------------
204
+ // Strong & Emphasis
205
+ // ----------------------------------------
206
+
207
+ strong {
208
+ font-weight: var(--fui-font-weight-semibold, $fui-font-weight-semibold);
209
+ }
210
+
211
+ // Remove top margin from first child and bottom margin from last child
212
+ > *:first-child {
213
+ margin-top: 0;
214
+ }
215
+
216
+ > *:last-child {
217
+ margin-bottom: 0;
218
+ }
219
+ }
@@ -0,0 +1,106 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import styles from './Markdown.module.scss';
5
+ import '../../styles/globals.scss';
6
+
7
+ // ============================================
8
+ // Types
9
+ // ============================================
10
+
11
+ export interface MarkdownProps {
12
+ /** Markdown string to render */
13
+ content: string;
14
+ /** Override map for markdown element components */
15
+ components?: Record<string, React.ComponentType<React.HTMLAttributes<HTMLElement>>>;
16
+ /** Additional class name */
17
+ className?: string;
18
+ }
19
+
20
+ // ============================================
21
+ // Lazy-loaded react-markdown
22
+ // ============================================
23
+
24
+ type ReactMarkdownType = React.ComponentType<{
25
+ children: string;
26
+ remarkPlugins?: unknown[];
27
+ components?: Record<string, React.ComponentType<unknown>>;
28
+ }>;
29
+
30
+ let ReactMarkdown: ReactMarkdownType | null = null;
31
+ let remarkGfm: unknown = null;
32
+ let loadAttempted = false;
33
+ let loadFailed = false;
34
+
35
+ function loadDeps() {
36
+ if (loadAttempted) return;
37
+ loadAttempted = true;
38
+
39
+ try {
40
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
41
+ ReactMarkdown = require('react-markdown').default || require('react-markdown');
42
+ } catch {
43
+ loadFailed = true;
44
+ }
45
+
46
+ try {
47
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
48
+ remarkGfm = require('remark-gfm').default || require('remark-gfm');
49
+ } catch {
50
+ // remark-gfm is optional; markdown still works without it
51
+ }
52
+ }
53
+
54
+ // ============================================
55
+ // Fallback renderer (plain text with paragraphs)
56
+ // ============================================
57
+
58
+ function FallbackRenderer({ content, className }: { content: string; className?: string }) {
59
+ const paragraphs = content.split(/\n{2,}/);
60
+ return (
61
+ <div className={className}>
62
+ {paragraphs.map((p, i) => (
63
+ <p key={i}>{p}</p>
64
+ ))}
65
+ </div>
66
+ );
67
+ }
68
+
69
+ // ============================================
70
+ // Component
71
+ // ============================================
72
+
73
+ export const Markdown = React.forwardRef<HTMLDivElement, MarkdownProps>(
74
+ function Markdown({ content, components: componentOverrides, className }, ref) {
75
+ loadDeps();
76
+
77
+ const classes = [styles.markdown, className].filter(Boolean).join(' ');
78
+
79
+ if (loadFailed || !ReactMarkdown) {
80
+ if (loadFailed && process.env.NODE_ENV === 'development') {
81
+ console.warn(
82
+ '[@fragments-sdk/ui] Markdown: react-markdown is not installed. ' +
83
+ 'Install it with: npm install react-markdown remark-gfm'
84
+ );
85
+ }
86
+ return (
87
+ <div ref={ref} className={classes}>
88
+ <FallbackRenderer content={content} />
89
+ </div>
90
+ );
91
+ }
92
+
93
+ const plugins = remarkGfm ? [remarkGfm] : [];
94
+
95
+ return (
96
+ <div ref={ref} className={classes}>
97
+ <ReactMarkdown
98
+ remarkPlugins={plugins}
99
+ components={componentOverrides as Record<string, React.ComponentType<unknown>>}
100
+ >
101
+ {content}
102
+ </ReactMarkdown>
103
+ </div>
104
+ );
105
+ }
106
+ );
@@ -3,6 +3,7 @@
3
3
  import * as React from 'react';
4
4
  import styles from './Message.module.scss';
5
5
  import '../../styles/globals.scss';
6
+ import { Markdown } from '../Markdown';
6
7
 
7
8
  // ============================================
8
9
  // Types
@@ -28,6 +29,8 @@ export interface MessageProps extends React.HTMLAttributes<HTMLDivElement> {
28
29
 
29
30
  export interface MessageContentProps extends React.HTMLAttributes<HTMLDivElement> {
30
31
  children: React.ReactNode;
32
+ /** When true, renders string children as markdown */
33
+ markdown?: boolean;
31
34
  }
32
35
 
33
36
  export interface MessageActionsProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -140,7 +143,7 @@ function SystemIcon() {
140
143
  // Sub-components
141
144
  // ============================================
142
145
 
143
- function MessageContent({ children, className, ...htmlProps }: MessageContentProps) {
146
+ function MessageContent({ children, markdown, className, ...htmlProps }: MessageContentProps) {
144
147
  const { status } = useMessageContext();
145
148
 
146
149
  const classes = [
@@ -149,9 +152,13 @@ function MessageContent({ children, className, ...htmlProps }: MessageContentPro
149
152
  className,
150
153
  ].filter(Boolean).join(' ');
151
154
 
155
+ const content = markdown && typeof children === 'string'
156
+ ? <Markdown content={children} />
157
+ : children;
158
+
152
159
  return (
153
160
  <div {...htmlProps} className={classes}>
154
- {children}
161
+ {content}
155
162
  </div>
156
163
  );
157
164
  }
@@ -50,7 +50,7 @@ export interface PromptToolbarProps {
50
50
  }
51
51
 
52
52
  export interface PromptActionsProps {
53
- children: React.ReactNode;
53
+ children?: React.ReactNode;
54
54
  className?: string;
55
55
  }
56
56
 
@@ -313,6 +313,7 @@ function PromptToolbar({ children, className }: PromptToolbarProps) {
313
313
  }
314
314
 
315
315
  function PromptActions({ children, className }: PromptActionsProps) {
316
+ if (!children) return null;
316
317
  const classes = [styles.actions, className].filter(Boolean).join(' ');
317
318
  return <div className={classes}>{children}</div>;
318
319
  }
@@ -72,6 +72,10 @@ export default defineSegment({
72
72
  description: 'Allow items to wrap',
73
73
  default: 'false',
74
74
  },
75
+ separator: {
76
+ type: 'custom',
77
+ description: 'Render a separator between children. true = default 1px line, or pass a ReactNode for custom separators.',
78
+ },
75
79
  as: {
76
80
  type: 'enum',
77
81
  description: 'HTML element to render',
@@ -93,6 +97,7 @@ export default defineSegment({
93
97
  'align: start|center|end|stretch|baseline - cross-axis',
94
98
  'justify: start|center|end|between - main-axis',
95
99
  'wrap: boolean - allow wrapping',
100
+ 'separator: boolean|ReactNode - divider between children',
96
101
  'as: string - HTML element',
97
102
  ],
98
103
  scenarioTags: [
@@ -179,6 +184,17 @@ export default defineSegment({
179
184
  </Stack>
180
185
  ),
181
186
  },
187
+ {
188
+ name: 'With Separator',
189
+ description: 'Default line separator between items',
190
+ render: () => (
191
+ <Stack gap="md" separator>
192
+ <div>Section One</div>
193
+ <div>Section Two</div>
194
+ <div>Section Three</div>
195
+ </Stack>
196
+ ),
197
+ },
182
198
  {
183
199
  name: 'Semantic Element',
184
200
  description: 'Using nav element for navigation',
@@ -118,3 +118,19 @@
118
118
  .wrap {
119
119
  flex-wrap: wrap;
120
120
  }
121
+
122
+ // Separator
123
+ .separator {
124
+ flex-shrink: 0;
125
+ background-color: var(--fui-border, $fui-border);
126
+
127
+ &[data-orientation="horizontal"] {
128
+ height: 1px;
129
+ width: 100%;
130
+ }
131
+
132
+ &[data-orientation="vertical"] {
133
+ width: 1px;
134
+ align-self: stretch;
135
+ }
136
+ }
@@ -50,6 +50,12 @@ export interface StackProps {
50
50
  align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline';
51
51
  justify?: 'start' | 'center' | 'end' | 'between';
52
52
  wrap?: boolean;
53
+ /**
54
+ * Render a separator between each child.
55
+ * - `true` renders a default 1px border line
56
+ * - A ReactNode renders custom content between children
57
+ */
58
+ separator?: boolean | React.ReactNode;
53
59
  as?: 'div' | 'section' | 'nav' | 'article' | 'aside' | 'header' | 'footer' | 'main' | 'ul' | 'ol';
54
60
  className?: string;
55
61
  style?: React.CSSProperties;
@@ -74,6 +80,7 @@ export const Stack = React.forwardRef<HTMLElement, StackProps>(
74
80
  align,
75
81
  justify,
76
82
  wrap = false,
83
+ separator,
77
84
  as: Component = 'div',
78
85
  className,
79
86
  style,
@@ -126,9 +133,36 @@ export const Stack = React.forwardRef<HTMLElement, StackProps>(
126
133
 
127
134
  const mergedStyle = inlineStyle ? { ...inlineStyle, ...style } : style;
128
135
 
136
+ // Interleave separator between children when provided
137
+ let content: React.ReactNode = children;
138
+ if (separator) {
139
+ const validChildren = React.Children.toArray(children).filter(Boolean);
140
+ if (validChildren.length > 1) {
141
+ const resolvedDir = isResponsiveDirection(direction) ? (direction.base ?? 'column') : direction;
142
+ const separatorEl = separator === true ? (
143
+ <div
144
+ className={styles.separator}
145
+ data-orientation={resolvedDir === 'row' ? 'vertical' : 'horizontal'}
146
+ role="separator"
147
+ />
148
+ ) : separator;
149
+
150
+ const items: React.ReactNode[] = [];
151
+ validChildren.forEach((child, i) => {
152
+ items.push(child);
153
+ if (i < validChildren.length - 1) {
154
+ items.push(
155
+ <React.Fragment key={`sep-${i}`}>{separatorEl}</React.Fragment>
156
+ );
157
+ }
158
+ });
159
+ content = items;
160
+ }
161
+ }
162
+
129
163
  return (
130
164
  <Component ref={ref as React.Ref<never>} className={classes} style={mergedStyle}>
131
- {children}
165
+ {content}
132
166
  </Component>
133
167
  );
134
168
  }
@@ -111,6 +111,16 @@ export default defineSegment({
111
111
  values: ['sm', 'md'],
112
112
  default: 'md',
113
113
  },
114
+ striped: {
115
+ type: 'boolean',
116
+ description: 'Show alternating row backgrounds',
117
+ default: 'false',
118
+ },
119
+ bordered: {
120
+ type: 'boolean',
121
+ description: 'Wrap table in a bordered container',
122
+ default: 'false',
123
+ },
114
124
  },
115
125
 
116
126
  relations: [
@@ -125,6 +135,8 @@ export default defineSegment({
125
135
  'sortable: boolean - enable sorting',
126
136
  'selectable: boolean - enable row selection',
127
137
  'size: sm|md - table density',
138
+ 'striped: boolean - alternating row backgrounds',
139
+ 'bordered: boolean - bordered container',
128
140
  ],
129
141
  scenarioTags: [
130
142
  'data.table',
@@ -185,6 +197,28 @@ export default defineSegment({
185
197
  />
186
198
  ),
187
199
  },
200
+ {
201
+ name: 'Striped',
202
+ description: 'Table with alternating row backgrounds',
203
+ render: () => (
204
+ <Table
205
+ columns={columns}
206
+ data={sampleUsers}
207
+ striped
208
+ />
209
+ ),
210
+ },
211
+ {
212
+ name: 'Bordered',
213
+ description: 'Table with bordered container',
214
+ render: () => (
215
+ <Table
216
+ columns={columns}
217
+ data={sampleUsers}
218
+ bordered
219
+ />
220
+ ),
221
+ },
188
222
  {
189
223
  name: 'Empty State',
190
224
  description: 'Table with no data',
@@ -33,7 +33,7 @@
33
33
  .sm {
34
34
  .th,
35
35
  .td {
36
- padding: var(--fui-space-2, $fui-space-2) var(--fui-space-3, $fui-space-3);
36
+ padding: var(--fui-space-1, $fui-space-1) var(--fui-space-3, $fui-space-3);
37
37
  font-size: var(--fui-font-size-xs, $fui-font-size-xs);
38
38
  }
39
39
  }
@@ -41,7 +41,7 @@
41
41
  .md {
42
42
  .th,
43
43
  .td {
44
- padding: var(--fui-space-3, $fui-space-3) var(--fui-space-4, $fui-space-4);
44
+ padding: var(--fui-space-2, $fui-space-2) var(--fui-space-3, $fui-space-3);
45
45
  font-size: var(--fui-font-size-sm, $fui-font-size-sm);
46
46
  }
47
47
  }
@@ -51,7 +51,7 @@
51
51
  position: sticky;
52
52
  top: 0;
53
53
  z-index: 1;
54
- background-color: var(--fui-bg-secondary, $fui-bg-secondary);
54
+ background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
55
55
  }
56
56
 
57
57
  .headerRow {
@@ -138,7 +138,6 @@
138
138
  }
139
139
 
140
140
  .selected {
141
- background-color: var(--fui-color-accent, $fui-color-accent);
142
141
  background-color: rgba($fui-color-accent, 0.08);
143
142
 
144
143
  &:hover {
@@ -152,6 +151,37 @@
152
151
  line-height: var(--fui-line-height-normal, $fui-line-height-normal);
153
152
  }
154
153
 
154
+ // Striped rows
155
+ .striped {
156
+ .row:nth-child(even) {
157
+ background-color: var(--fui-bg-subtle, $fui-bg-subtle);
158
+ }
159
+
160
+ // Hover and selected override stripe
161
+ .clickable:hover {
162
+ background-color: var(--fui-bg-hover, $fui-bg-hover);
163
+ }
164
+
165
+ .clickable:active {
166
+ background-color: var(--fui-bg-active, $fui-bg-active);
167
+ }
168
+
169
+ .selected {
170
+ background-color: rgba($fui-color-accent, 0.08);
171
+
172
+ &:hover {
173
+ background-color: rgba($fui-color-accent, 0.12);
174
+ }
175
+ }
176
+ }
177
+
178
+ // Bordered
179
+ .bordered {
180
+ border: 1px solid var(--fui-border, $fui-border);
181
+ border-radius: var(--fui-radius-md, $fui-radius-md);
182
+ overflow: hidden;
183
+ }
184
+
155
185
  // Empty state
156
186
  .emptyState {
157
187
  display: flex;
@@ -167,7 +197,7 @@
167
197
  }
168
198
 
169
199
  // Responsive: allow horizontal scroll on small screens
170
- @media (max-width: 640px) {
200
+ @include below-sm {
171
201
  .wrapper {
172
202
  margin-left: calc(-1 * var(--fui-space-4, $fui-space-4));
173
203
  margin-right: calc(-1 * var(--fui-space-4, $fui-space-4));
@@ -45,6 +45,10 @@ export interface TableProps<T> extends Omit<React.HTMLAttributes<HTMLTableElemen
45
45
  caption?: string;
46
46
  /** Hide the caption visually but keep it for screen readers */
47
47
  captionHidden?: boolean;
48
+ /** Show alternating row backgrounds */
49
+ striped?: boolean;
50
+ /** Wrap table in a bordered container */
51
+ bordered?: boolean;
48
52
  }
49
53
 
50
54
  export function Table<T>({
@@ -63,6 +67,8 @@ export function Table<T>({
63
67
  className,
64
68
  caption,
65
69
  captionHidden = false,
70
+ striped = false,
71
+ bordered = false,
66
72
  'aria-label': ariaLabel,
67
73
  'aria-describedby': ariaDescribedBy,
68
74
  ...htmlProps
@@ -95,7 +101,7 @@ export function Table<T>({
95
101
 
96
102
  const isEmpty = data.length === 0;
97
103
 
98
- const rootClasses = [styles.table, styles[size], className]
104
+ const rootClasses = [styles.table, styles[size], striped && styles.striped, className]
99
105
  .filter(Boolean)
100
106
  .join(' ');
101
107
 
@@ -119,7 +125,7 @@ export function Table<T>({
119
125
  };
120
126
 
121
127
  return (
122
- <div className={styles.wrapper}>
128
+ <div className={[styles.wrapper, bordered && styles.bordered].filter(Boolean).join(' ')}>
123
129
  <table
124
130
  {...htmlProps}
125
131
  className={rootClasses}
package/src/index.ts CHANGED
@@ -406,15 +406,32 @@ export {
406
406
  type ListboxEmptyProps,
407
407
  } from './components/Listbox';
408
408
 
409
+ // Breadcrumbs
410
+ export {
411
+ Breadcrumbs,
412
+ BreadcrumbsRoot,
413
+ BreadcrumbsItem,
414
+ BreadcrumbsSeparator,
415
+ type BreadcrumbsProps,
416
+ type BreadcrumbsItemProps,
417
+ type BreadcrumbsSeparatorProps,
418
+ } from './components/Breadcrumbs';
419
+
409
420
  // Box
410
421
  export { Box, type BoxProps } from './components/Box';
411
422
 
423
+ // Chip
424
+ export { Chip, type ChipProps, type ChipGroupProps } from './components/Chip';
425
+
412
426
  // VisuallyHidden
413
427
  export { VisuallyHidden, type VisuallyHiddenProps } from './components/VisuallyHidden';
414
428
 
415
429
  // Brand
416
430
  export { BRAND, type Brand } from './brand';
417
431
 
432
+ // Markdown (AI Chat)
433
+ export { Markdown, type MarkdownProps } from './components/Markdown';
434
+
418
435
  // Message (AI Chat)
419
436
  export {
420
437
  Message,