@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,39 @@
1
+ import { defineBlock } from '@fragments/core';
2
+
3
+ export default defineBlock({
4
+ name: 'Search Results',
5
+ description: 'List of search results with categories and badges',
6
+ category: 'dashboard',
7
+ components: ['Card', 'Stack', 'Text', 'Badge', 'Button'],
8
+ tags: ['search', 'results', 'list', 'dashboard'],
9
+ code: `
10
+ const results = [
11
+ { title: 'Getting Started Guide', category: 'Documentation', description: 'Learn how to set up and configure your project...' },
12
+ { title: 'Button Component', category: 'Components', description: 'A versatile button component with multiple variants...' },
13
+ { title: 'Theming System', category: 'Guides', description: 'Customize colors, typography, and spacing...' },
14
+ ];
15
+
16
+ <Card>
17
+ <Card.Header>
18
+ <Stack direction="row" justify="between" align="center">
19
+ <Card.Title>Search Results</Card.Title>
20
+ <Badge>3 results</Badge>
21
+ </Stack>
22
+ </Card.Header>
23
+ <Card.Body>
24
+ <Stack gap="md">
25
+ {results.map((result, index) => (
26
+ <Stack key={index} gap="sm" style={{ paddingBottom: index < results.length - 1 ? 'var(--fui-space-4)' : 0, borderBottom: index < results.length - 1 ? '1px solid var(--fui-border-default)' : 'none' }}>
27
+ <Stack direction="row" gap="sm" align="center">
28
+ <Text weight="semibold">{result.title}</Text>
29
+ <Badge variant="outline">{result.category}</Badge>
30
+ </Stack>
31
+ <Text size="sm" color="tertiary">{result.description}</Text>
32
+ <Button variant="ghost" size="sm">View details</Button>
33
+ </Stack>
34
+ ))}
35
+ </Stack>
36
+ </Card.Body>
37
+ </Card>
38
+ `.trim(),
39
+ });
@@ -0,0 +1,58 @@
1
+ import { defineBlock } from '@fragments/core';
2
+
3
+ export default defineBlock({
4
+ name: 'Settings Page',
5
+ description: 'Settings page with labeled sections using cards, toggles, and a save action',
6
+ category: 'forms',
7
+ components: ['Grid', 'Card', 'Toggle', 'Input', 'Select', 'Separator', 'Button'],
8
+ tags: ['settings', 'preferences', 'form', 'toggle', 'layout'],
9
+ code: `
10
+ <Grid columns={1} gap="lg">
11
+ <Card>
12
+ <Card.Header>
13
+ <Card.Title>Profile</Card.Title>
14
+ <Card.Description>Your public profile information</Card.Description>
15
+ </Card.Header>
16
+ <Card.Body>
17
+ <Grid columns={2} gap="md">
18
+ <Input label="Display Name" defaultValue={user.name} />
19
+ <Input label="Email" type="email" defaultValue={user.email} />
20
+ <Grid.Item colSpan="full">
21
+ <Input label="Website" type="url" defaultValue={user.website} />
22
+ </Grid.Item>
23
+ </Grid>
24
+ </Card.Body>
25
+ </Card>
26
+
27
+ <Card>
28
+ <Card.Header>
29
+ <Card.Title>Notifications</Card.Title>
30
+ <Card.Description>Choose what you get notified about</Card.Description>
31
+ </Card.Header>
32
+ <Card.Body>
33
+ <Grid columns={1} gap="sm">
34
+ <Toggle label="Email notifications" checked={prefs.emailNotifs} onChange={onToggle('emailNotifs')} />
35
+ <Toggle label="Push notifications" checked={prefs.pushNotifs} onChange={onToggle('pushNotifs')} />
36
+ <Toggle label="Weekly digest" checked={prefs.digest} onChange={onToggle('digest')} />
37
+ </Grid>
38
+ </Card.Body>
39
+ </Card>
40
+
41
+ <Card>
42
+ <Card.Header>
43
+ <Card.Title>Appearance</Card.Title>
44
+ </Card.Header>
45
+ <Card.Body>
46
+ <Select label="Theme" value={prefs.theme} onChange={onThemeChange}>
47
+ <Select.Item value="light">Light</Select.Item>
48
+ <Select.Item value="dark">Dark</Select.Item>
49
+ <Select.Item value="system">System</Select.Item>
50
+ </Select>
51
+ </Card.Body>
52
+ </Card>
53
+
54
+ <Separator />
55
+ <Button variant="primary" type="submit">Save Changes</Button>
56
+ </Grid>
57
+ `.trim(),
58
+ });
@@ -0,0 +1,35 @@
1
+ import { defineBlock } from '@fragments/core';
2
+
3
+ export default defineBlock({
4
+ name: 'Settings Panel',
5
+ description: 'Settings panel with toggles and descriptions',
6
+ category: 'settings',
7
+ components: ['Card', 'Stack', 'Text', 'Toggle'],
8
+ tags: ['settings', 'toggles', 'preferences', 'panel'],
9
+ code: `
10
+ const settings = [
11
+ { label: 'Email Notifications', description: 'Receive updates via email', defaultChecked: true },
12
+ { label: 'Push Notifications', description: 'Receive push notifications on your device', defaultChecked: false },
13
+ { label: 'Marketing Emails', description: 'Receive product updates and tips', defaultChecked: false },
14
+ ];
15
+
16
+ <Card>
17
+ <Card.Header>
18
+ <Card.Title>Settings</Card.Title>
19
+ </Card.Header>
20
+ <Card.Body>
21
+ <Stack gap="lg">
22
+ {settings.map((setting) => (
23
+ <Stack key={setting.label} direction="row" justify="between" align="center">
24
+ <Stack gap="xs">
25
+ <Text weight="medium">{setting.label}</Text>
26
+ <Text size="sm" color="tertiary">{setting.description}</Text>
27
+ </Stack>
28
+ <Toggle defaultChecked={setting.defaultChecked} />
29
+ </Stack>
30
+ ))}
31
+ </Stack>
32
+ </Card.Body>
33
+ </Card>
34
+ `.trim(),
35
+ });
@@ -0,0 +1,46 @@
1
+ import { defineBlock } from '@fragments/core';
2
+
3
+ export default defineBlock({
4
+ name: 'Shopping Cart',
5
+ description: 'Cart with item images, quantities, totals and checkout button',
6
+ category: 'ecommerce',
7
+ components: ['Card', 'Stack', 'Text', 'Image', 'Badge', 'Separator', 'Button'],
8
+ tags: ['cart', 'shopping', 'ecommerce', 'checkout'],
9
+ code: `
10
+ const items = [
11
+ { name: 'Wireless Headphones', price: 199, quantity: 1, image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=100&h=100&fit=crop' },
12
+ { name: 'Smart Watch', price: 299, quantity: 1, image: 'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=100&h=100&fit=crop' },
13
+ ];
14
+
15
+ <Card>
16
+ <Card.Header>
17
+ <Stack direction="row" justify="between" align="center">
18
+ <Card.Title>Shopping Cart</Card.Title>
19
+ <Badge>2 items</Badge>
20
+ </Stack>
21
+ </Card.Header>
22
+ <Card.Body>
23
+ <Stack gap="md">
24
+ {items.map((item, index) => (
25
+ <Stack key={index} direction="row" gap="md" align="center">
26
+ <Image src={item.image} alt={item.name} width={64} height={64} rounded="md" />
27
+ <Stack gap="xs" style={{ flex: 1 }}>
28
+ <Text weight="semibold">{item.name}</Text>
29
+ <Text size="sm" color="tertiary">Qty: {item.quantity}</Text>
30
+ </Stack>
31
+ <Text weight="semibold">\${item.price}</Text>
32
+ </Stack>
33
+ ))}
34
+ <Separator />
35
+ <Stack direction="row" justify="between">
36
+ <Text weight="semibold">Total</Text>
37
+ <Text weight="semibold">$498</Text>
38
+ </Stack>
39
+ </Stack>
40
+ </Card.Body>
41
+ <Card.Footer>
42
+ <Button variant="primary" fullWidth>Checkout</Button>
43
+ </Card.Footer>
44
+ </Card>
45
+ `.trim(),
46
+ });
@@ -0,0 +1,26 @@
1
+ import { defineBlock } from '@fragments/core';
2
+
3
+ export default defineBlock({
4
+ name: 'Stats Card',
5
+ description: 'Metric card with value, change indicator and icon',
6
+ category: 'dashboard',
7
+ components: ['Card', 'Stack', 'Text', 'Badge', 'Icon'],
8
+ tags: ['stats', 'metrics', 'kpi', 'dashboard', 'card'],
9
+ code: `
10
+ <Card>
11
+ <Card.Body>
12
+ <Stack direction="row" justify="between" align="start">
13
+ <Stack gap="xs">
14
+ <Text size="sm" color="tertiary">Total Revenue</Text>
15
+ <Text size="2xl" weight="semibold">$45,231</Text>
16
+ <Stack direction="row" gap="xs" align="center">
17
+ <Badge variant="success">+12.5%</Badge>
18
+ <Text size="sm" color="tertiary">from last month</Text>
19
+ </Stack>
20
+ </Stack>
21
+ <Icon icon={TrendUp} size="lg" color="success" />
22
+ </Stack>
23
+ </Card.Body>
24
+ </Card>
25
+ `.trim(),
26
+ });
@@ -0,0 +1,24 @@
1
+ import { defineBlock } from '@fragments/core';
2
+
3
+ export default defineBlock({
4
+ name: 'Streaming Message',
5
+ description: 'AI message with streaming cursor animation showing real-time response',
6
+ category: 'ai',
7
+ components: ['Stack', 'Message'],
8
+ tags: ['streaming', 'message', 'ai', 'cursor', 'real-time'],
9
+ code: `
10
+ <Stack gap="md" style={{ maxWidth: '600px' }}>
11
+ <Message role="user">
12
+ <Message.Content>
13
+ Write a haiku about programming
14
+ </Message.Content>
15
+ </Message>
16
+ <Message role="assistant" status="streaming">
17
+ <Message.Content>
18
+ Lines of code compile,
19
+ Bugs emerge from the shadows
20
+ </Message.Content>
21
+ </Message>
22
+ </Stack>
23
+ `.trim(),
24
+ });
@@ -0,0 +1,27 @@
1
+ import { defineBlock } from '@fragments/core';
2
+
3
+ export default defineBlock({
4
+ name: 'Testimonial Card',
5
+ description: 'Customer quote with avatar and attribution',
6
+ category: 'marketing',
7
+ components: ['Card', 'Stack', 'Text', 'Avatar'],
8
+ tags: ['testimonial', 'quote', 'review', 'marketing'],
9
+ code: `
10
+ <Card>
11
+ <Card.Body>
12
+ <Stack gap="md">
13
+ <Text color="secondary" style={{ fontStyle: 'italic' }}>
14
+ "This component library has completely transformed how we build UIs. The design is clean, the API is intuitive, and the documentation is excellent."
15
+ </Text>
16
+ <Stack direction="row" gap="md" align="center">
17
+ <Avatar initials="SK" />
18
+ <Stack gap="xs">
19
+ <Text weight="semibold">Sarah Kim</Text>
20
+ <Text size="sm" color="tertiary">Engineering Lead at TechCorp</Text>
21
+ </Stack>
22
+ </Stack>
23
+ </Stack>
24
+ </Card.Body>
25
+ </Card>
26
+ `.trim(),
27
+ });
@@ -0,0 +1,48 @@
1
+ import { defineBlock } from '@fragments/core';
2
+
3
+ export default defineBlock({
4
+ name: 'Thinking States',
5
+ description: 'Various AI thinking indicator states: dots, pulse, spinner with steps',
6
+ category: 'ai',
7
+ components: ['Stack', 'Card', 'ThinkingIndicator'],
8
+ tags: ['thinking', 'loading', 'ai', 'indicator', 'spinner'],
9
+ code: `
10
+ <Stack gap="lg">
11
+ <Card>
12
+ <Card.Header>
13
+ <Card.Title>Dots Animation</Card.Title>
14
+ </Card.Header>
15
+ <Card.Body>
16
+ <ThinkingIndicator variant="dots" label="Thinking..." />
17
+ </Card.Body>
18
+ </Card>
19
+
20
+ <Card>
21
+ <Card.Header>
22
+ <Card.Title>Pulse Animation</Card.Title>
23
+ </Card.Header>
24
+ <Card.Body>
25
+ <ThinkingIndicator variant="pulse" label="Processing request..." />
26
+ </Card.Body>
27
+ </Card>
28
+
29
+ <Card>
30
+ <Card.Header>
31
+ <Card.Title>Spinner with Steps</Card.Title>
32
+ </Card.Header>
33
+ <Card.Body>
34
+ <ThinkingIndicator
35
+ variant="spinner"
36
+ label="Working on your request..."
37
+ showElapsed
38
+ steps={[
39
+ { id: '1', label: 'Understanding your question', status: 'complete' },
40
+ { id: '2', label: 'Searching knowledge base', status: 'active' },
41
+ { id: '3', label: 'Generating response', status: 'pending' },
42
+ ]}
43
+ />
44
+ </Card.Body>
45
+ </Card>
46
+ </Stack>
47
+ `.trim(),
48
+ });
@@ -0,0 +1,29 @@
1
+ import { defineBlock } from '@fragments/core';
2
+
3
+ export default defineBlock({
4
+ name: 'User Profile Card',
5
+ description: 'Card displaying user info with avatar, role and actions',
6
+ category: 'dashboard',
7
+ components: ['Card', 'Stack', 'Text', 'Avatar', 'Badge', 'Button'],
8
+ tags: ['profile', 'user', 'card', 'avatar', 'dashboard'],
9
+ code: `
10
+ <Card>
11
+ <Card.Body>
12
+ <Stack align="center" gap="md">
13
+ <Avatar size="xl" initials="JD" />
14
+ <Stack align="center" gap="xs">
15
+ <Text size="lg" weight="semibold">Jane Doe</Text>
16
+ <Text color="tertiary">Product Designer</Text>
17
+ <Badge variant="success">Pro Member</Badge>
18
+ </Stack>
19
+ </Stack>
20
+ </Card.Body>
21
+ <Card.Footer>
22
+ <Stack direction="row" gap="sm" justify="center">
23
+ <Button variant="secondary" size="sm">Message</Button>
24
+ <Button variant="primary" size="sm">Follow</Button>
25
+ </Stack>
26
+ </Card.Footer>
27
+ </Card>
28
+ `.trim(),
29
+ });
@@ -205,9 +205,10 @@
205
205
  height: calc(100vh - var(--appshell-header-height, 56px));
206
206
  height: calc(100dvh - var(--appshell-header-height, 56px));
207
207
  width: var(--aside-width, 280px);
208
+ min-height: 0; // Allow shrinking in grid
208
209
  z-index: 30;
209
210
  background-color: var(--fui-bg-primary, $fui-bg-primary);
210
- overflow-y: auto;
211
+ overflow: hidden;
211
212
 
212
213
  // Stack below main on mobile
213
214
  @include below-md {
@@ -96,11 +96,75 @@ export default defineSegment({
96
96
  description: 'Show border',
97
97
  default: 'false',
98
98
  },
99
+ borderTop: {
100
+ type: 'boolean',
101
+ description: 'Show top border only',
102
+ default: 'false',
103
+ },
104
+ borderBottom: {
105
+ type: 'boolean',
106
+ description: 'Show bottom border only',
107
+ default: 'false',
108
+ },
109
+ borderLeft: {
110
+ type: 'boolean',
111
+ description: 'Show left border only',
112
+ default: 'false',
113
+ },
114
+ borderRight: {
115
+ type: 'boolean',
116
+ description: 'Show right border only',
117
+ default: 'false',
118
+ },
119
+ borderColor: {
120
+ type: 'enum',
121
+ description: 'Border color variant (requires border or directional border)',
122
+ values: ['default', 'strong', 'accent', 'danger'],
123
+ },
124
+ shadow: {
125
+ type: 'enum',
126
+ description: 'Box shadow',
127
+ values: ['sm', 'md', 'lg', 'none'],
128
+ },
129
+ overflow: {
130
+ type: 'enum',
131
+ description: 'Overflow behavior',
132
+ values: ['hidden', 'auto', 'scroll', 'visible'],
133
+ },
134
+ color: {
135
+ type: 'enum',
136
+ description: 'Text color',
137
+ values: ['primary', 'secondary', 'tertiary', 'accent', 'inverse'],
138
+ },
99
139
  display: {
100
140
  type: 'enum',
101
141
  description: 'Display type',
102
142
  values: ['block', 'inline', 'inline-block', 'flex', 'inline-flex', 'grid', 'none'],
103
143
  },
144
+ width: {
145
+ type: 'custom',
146
+ description: 'Width (CSS value, e.g. "100%", "300px", or number for px)',
147
+ },
148
+ minWidth: {
149
+ type: 'custom',
150
+ description: 'Min width',
151
+ },
152
+ maxWidth: {
153
+ type: 'custom',
154
+ description: 'Max width',
155
+ },
156
+ height: {
157
+ type: 'custom',
158
+ description: 'Height (CSS value)',
159
+ },
160
+ minHeight: {
161
+ type: 'custom',
162
+ description: 'Min height',
163
+ },
164
+ maxHeight: {
165
+ type: 'custom',
166
+ description: 'Max height',
167
+ },
104
168
  },
105
169
 
106
170
  relations: [
@@ -118,6 +182,13 @@ export default defineSegment({
118
182
  'background: none|primary|secondary|tertiary|elevated',
119
183
  'rounded: none|sm|md|lg|full - border radius',
120
184
  'border: boolean - show border',
185
+ 'borderTop/borderBottom/borderLeft/borderRight: boolean - directional borders',
186
+ 'borderColor: default|strong|accent|danger - border color variant',
187
+ 'shadow: sm|md|lg|none - box shadow',
188
+ 'overflow: hidden|auto|scroll|visible - overflow behavior',
189
+ 'color: primary|secondary|tertiary|accent|inverse - text color',
190
+ 'width/minWidth/maxWidth: string|number - sizing',
191
+ 'height/minHeight/maxHeight: string|number - sizing',
121
192
  ],
122
193
  scenarioTags: [
123
194
  'layout.container',
@@ -164,5 +235,44 @@ export default defineSegment({
164
235
  </Box>
165
236
  ),
166
237
  },
238
+ {
239
+ name: 'Directional Borders',
240
+ description: 'Individual border sides',
241
+ render: () => (
242
+ <Box padding="md" borderTop borderBottom>
243
+ Top and bottom borders only
244
+ </Box>
245
+ ),
246
+ },
247
+ {
248
+ name: 'With Shadow',
249
+ description: 'Box with shadow elevation',
250
+ render: () => (
251
+ <Box padding="lg" rounded="md" shadow="md" background="primary">
252
+ Elevated content with shadow
253
+ </Box>
254
+ ),
255
+ },
256
+ {
257
+ name: 'Overflow Hidden',
258
+ description: 'Content overflow clipped',
259
+ render: () => (
260
+ <Box padding="md" overflow="hidden" border rounded="md" style={{ maxHeight: '60px' }}>
261
+ This box clips overflowing content. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
262
+ </Box>
263
+ ),
264
+ },
265
+ {
266
+ name: 'Text Colors',
267
+ description: 'Text color variants',
268
+ render: () => (
269
+ <Box padding="md" display="flex" style={{ gap: '16px' }}>
270
+ <Box color="primary">Primary</Box>
271
+ <Box color="secondary">Secondary</Box>
272
+ <Box color="tertiary">Tertiary</Box>
273
+ <Box color="accent">Accent</Box>
274
+ </Box>
275
+ ),
276
+ },
167
277
  ],
168
278
  });
@@ -74,6 +74,45 @@
74
74
  border: 1px solid var(--fui-border, $fui-border);
75
75
  }
76
76
 
77
+ // Directional borders
78
+ .borderTop {
79
+ border-top: 1px solid var(--fui-border, $fui-border);
80
+ }
81
+ .borderBottom {
82
+ border-bottom: 1px solid var(--fui-border, $fui-border);
83
+ }
84
+ .borderLeft {
85
+ border-left: 1px solid var(--fui-border, $fui-border);
86
+ }
87
+ .borderRight {
88
+ border-right: 1px solid var(--fui-border, $fui-border);
89
+ }
90
+
91
+ // Border color
92
+ .borderColor-default { border-color: var(--fui-border-default, $fui-border-default); }
93
+ .borderColor-strong { border-color: var(--fui-border-strong, $fui-border-strong); }
94
+ .borderColor-accent { border-color: var(--fui-color-accent, $fui-color-accent); }
95
+ .borderColor-danger { border-color: var(--fui-color-danger, $fui-color-danger); }
96
+
97
+ // Shadow
98
+ .shadow-sm { box-shadow: var(--fui-shadow-sm, $fui-shadow-sm); }
99
+ .shadow-md { box-shadow: var(--fui-shadow-md, $fui-shadow-md); }
100
+ .shadow-lg { box-shadow: var(--fui-shadow-lg, $fui-shadow-lg); }
101
+ .shadow-none { box-shadow: none; }
102
+
103
+ // Overflow
104
+ .overflow-hidden { overflow: hidden; }
105
+ .overflow-auto { overflow: auto; }
106
+ .overflow-scroll { overflow: scroll; }
107
+ .overflow-visible { overflow: visible; }
108
+
109
+ // Text color
110
+ .color-primary { color: var(--fui-text-primary, $fui-text-primary); }
111
+ .color-secondary { color: var(--fui-text-secondary, $fui-text-secondary); }
112
+ .color-tertiary { color: var(--fui-text-tertiary, $fui-text-tertiary); }
113
+ .color-accent { color: var(--fui-color-accent, $fui-color-accent); }
114
+ .color-inverse { color: var(--fui-text-inverse, $fui-text-inverse); }
115
+
77
116
  // Display
78
117
  .display-block { display: block; }
79
118
  .display-inline { display: inline; }
@@ -24,14 +24,47 @@ export interface BoxProps {
24
24
  rounded?: 'none' | 'sm' | 'md' | 'lg' | 'full';
25
25
  /** Border */
26
26
  border?: boolean;
27
+ /** Top border */
28
+ borderTop?: boolean;
29
+ /** Bottom border */
30
+ borderBottom?: boolean;
31
+ /** Left border */
32
+ borderLeft?: boolean;
33
+ /** Right border */
34
+ borderRight?: boolean;
35
+ /** Border color variant */
36
+ borderColor?: 'default' | 'strong' | 'accent' | 'danger';
37
+ /** Box shadow */
38
+ shadow?: 'sm' | 'md' | 'lg' | 'none';
39
+ /** Overflow behavior */
40
+ overflow?: 'hidden' | 'auto' | 'scroll' | 'visible';
41
+ /** Text color */
42
+ color?: 'primary' | 'secondary' | 'tertiary' | 'accent' | 'inverse';
27
43
  /** Display type */
28
44
  display?: 'block' | 'inline' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none';
45
+ /** Width (CSS value, e.g. "100%", "300px", 200) */
46
+ width?: string | number;
47
+ /** Min width */
48
+ minWidth?: string | number;
49
+ /** Max width */
50
+ maxWidth?: string | number;
51
+ /** Height (CSS value, e.g. "100%", "300px", 200) */
52
+ height?: string | number;
53
+ /** Min height */
54
+ minHeight?: string | number;
55
+ /** Max height */
56
+ maxHeight?: string | number;
29
57
  /** Additional class name */
30
58
  className?: string;
31
59
  /** Inline styles */
32
60
  style?: React.CSSProperties;
33
61
  }
34
62
 
63
+ /** Convert string | number to CSS value */
64
+ function toCss(value: string | number): string {
65
+ return typeof value === 'number' ? `${value}px` : value;
66
+ }
67
+
35
68
  export const Box = React.forwardRef<HTMLElement, BoxProps>(
36
69
  function Box(
37
70
  {
@@ -46,7 +79,21 @@ export const Box = React.forwardRef<HTMLElement, BoxProps>(
46
79
  background,
47
80
  rounded,
48
81
  border,
82
+ borderTop,
83
+ borderBottom,
84
+ borderLeft,
85
+ borderRight,
86
+ borderColor,
87
+ shadow,
88
+ overflow,
89
+ color,
49
90
  display,
91
+ width,
92
+ minWidth,
93
+ maxWidth,
94
+ height,
95
+ minHeight,
96
+ maxHeight,
50
97
  className,
51
98
  style,
52
99
  },
@@ -63,14 +110,34 @@ export const Box = React.forwardRef<HTMLElement, BoxProps>(
63
110
  background && styles[`bg-${background}`],
64
111
  rounded && styles[`rounded-${rounded}`],
65
112
  border && styles.border,
113
+ borderTop && styles.borderTop,
114
+ borderBottom && styles.borderBottom,
115
+ borderLeft && styles.borderLeft,
116
+ borderRight && styles.borderRight,
117
+ borderColor && styles[`borderColor-${borderColor}`],
118
+ shadow && styles[`shadow-${shadow}`],
119
+ overflow && styles[`overflow-${overflow}`],
120
+ color && styles[`color-${color}`],
66
121
  display && styles[`display-${display}`],
67
122
  className,
68
123
  ]
69
124
  .filter(Boolean)
70
125
  .join(' ');
71
126
 
127
+ // Build sizing styles only when props are provided
128
+ const sizingStyle: React.CSSProperties = {};
129
+ if (width != null) sizingStyle.width = toCss(width);
130
+ if (minWidth != null) sizingStyle.minWidth = toCss(minWidth);
131
+ if (maxWidth != null) sizingStyle.maxWidth = toCss(maxWidth);
132
+ if (height != null) sizingStyle.height = toCss(height);
133
+ if (minHeight != null) sizingStyle.minHeight = toCss(minHeight);
134
+ if (maxHeight != null) sizingStyle.maxHeight = toCss(maxHeight);
135
+
136
+ const hasSizing = Object.keys(sizingStyle).length > 0;
137
+ const mergedStyle = hasSizing ? { ...sizingStyle, ...style } : style;
138
+
72
139
  return (
73
- <Component ref={ref as React.Ref<never>} className={classes} style={style}>
140
+ <Component ref={ref as React.Ref<never>} className={classes} style={mergedStyle}>
74
141
  {children}
75
142
  </Component>
76
143
  );