@fragments-sdk/ui 0.3.0 → 0.5.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 (175) hide show
  1. package/README.md +98 -2
  2. package/fragments.json +1 -1
  3. package/package.json +11 -5
  4. package/src/components/Accordion/Accordion.fragment.tsx +186 -0
  5. package/src/components/Accordion/Accordion.module.scss +111 -0
  6. package/src/components/Accordion/index.tsx +271 -0
  7. package/src/components/Alert/Alert.fragment.tsx +67 -42
  8. package/src/components/Alert/Alert.module.scss +31 -21
  9. package/src/components/Alert/index.tsx +202 -73
  10. package/src/components/AppShell/AppShell.fragment.tsx +315 -0
  11. package/src/components/AppShell/AppShell.module.scss +213 -0
  12. package/src/components/AppShell/index.tsx +398 -0
  13. package/src/components/Avatar/Avatar.fragment.tsx +2 -2
  14. package/src/components/Avatar/index.tsx +8 -9
  15. package/src/components/Badge/Badge.fragment.tsx +2 -2
  16. package/src/components/Badge/Badge.module.scss +16 -10
  17. package/src/components/Badge/index.tsx +20 -6
  18. package/src/components/Box/Box.fragment.tsx +168 -0
  19. package/src/components/Box/Box.module.scss +84 -0
  20. package/src/components/Box/index.tsx +78 -0
  21. package/src/components/Button/Button.fragment.tsx +2 -2
  22. package/src/components/Button/Button.module.scss +42 -0
  23. package/src/components/Button/index.tsx +67 -33
  24. package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +153 -0
  25. package/src/components/ButtonGroup/ButtonGroup.module.scss +37 -0
  26. package/src/components/ButtonGroup/index.tsx +40 -0
  27. package/src/components/Card/Card.fragment.tsx +52 -26
  28. package/src/components/Card/Card.module.scss +52 -5
  29. package/src/components/Card/index.tsx +154 -53
  30. package/src/components/Chart/Chart.fragment.tsx +213 -0
  31. package/src/components/Chart/Chart.module.scss +123 -0
  32. package/src/components/Chart/index.tsx +267 -0
  33. package/src/components/Checkbox/Checkbox.fragment.tsx +1 -1
  34. package/src/components/Checkbox/Checkbox.module.scss +4 -4
  35. package/src/components/Checkbox/index.tsx +3 -4
  36. package/src/components/CodeBlock/CodeBlock.fragment.tsx +460 -0
  37. package/src/components/CodeBlock/CodeBlock.module.scss +362 -0
  38. package/src/components/CodeBlock/index.tsx +599 -0
  39. package/src/components/Collapsible/Collapsible.fragment.tsx +199 -0
  40. package/src/components/Collapsible/Collapsible.module.scss +117 -0
  41. package/src/components/Collapsible/index.tsx +219 -0
  42. package/src/components/ColorPicker/ColorPicker.fragment.tsx +196 -0
  43. package/src/components/ColorPicker/ColorPicker.module.scss +119 -0
  44. package/src/components/ColorPicker/index.tsx +129 -0
  45. package/src/components/ConversationList/ConversationList.fragment.tsx +202 -0
  46. package/src/components/ConversationList/ConversationList.module.scss +160 -0
  47. package/src/components/ConversationList/index.tsx +254 -0
  48. package/src/components/Dialog/Dialog.fragment.tsx +12 -3
  49. package/src/components/Dialog/Dialog.module.scss +26 -7
  50. package/src/components/Dialog/index.tsx +12 -15
  51. package/src/components/EmptyState/EmptyState.fragment.tsx +55 -72
  52. package/src/components/EmptyState/EmptyState.module.scss +9 -9
  53. package/src/components/EmptyState/index.tsx +104 -69
  54. package/src/components/Field/Field.fragment.tsx +165 -0
  55. package/src/components/Field/Field.module.scss +31 -0
  56. package/src/components/Field/index.tsx +143 -0
  57. package/src/components/Fieldset/Fieldset.fragment.tsx +166 -0
  58. package/src/components/Fieldset/Fieldset.module.scss +22 -0
  59. package/src/components/Fieldset/index.tsx +47 -0
  60. package/src/components/Form/Form.fragment.tsx +286 -0
  61. package/src/components/Form/Form.module.scss +8 -0
  62. package/src/components/Form/index.tsx +53 -0
  63. package/src/components/Grid/Grid.fragment.tsx +18 -18
  64. package/src/components/Grid/index.tsx +6 -1
  65. package/src/components/Header/Header.fragment.tsx +192 -0
  66. package/src/components/Header/Header.module.scss +208 -0
  67. package/src/components/Header/index.tsx +363 -0
  68. package/src/components/Icon/Icon.fragment.tsx +138 -0
  69. package/src/components/Icon/Icon.module.scss +38 -0
  70. package/src/components/Icon/index.tsx +58 -0
  71. package/src/components/Image/Image.fragment.tsx +195 -0
  72. package/src/components/Image/Image.module.scss +77 -0
  73. package/src/components/Image/index.tsx +95 -0
  74. package/src/components/Input/Input.fragment.tsx +1 -1
  75. package/src/components/Input/Input.module.scss +75 -2
  76. package/src/components/Input/index.tsx +60 -21
  77. package/src/components/Link/Link.fragment.tsx +132 -0
  78. package/src/components/Link/Link.module.scss +67 -0
  79. package/src/components/Link/index.tsx +57 -0
  80. package/src/components/List/List.fragment.tsx +152 -0
  81. package/src/components/List/List.module.scss +71 -0
  82. package/src/components/List/index.tsx +106 -0
  83. package/src/components/Listbox/Listbox.fragment.tsx +191 -0
  84. package/src/components/Listbox/Listbox.module.scss +97 -0
  85. package/src/components/Listbox/index.tsx +121 -0
  86. package/src/components/Loading/Loading.fragment.tsx +153 -0
  87. package/src/components/Loading/Loading.module.scss +256 -0
  88. package/src/components/Loading/index.tsx +236 -0
  89. package/src/components/Menu/Menu.fragment.tsx +12 -3
  90. package/src/components/Menu/Menu.module.scss +17 -1
  91. package/src/components/Menu/index.tsx +3 -3
  92. package/src/components/Message/Message.fragment.tsx +200 -0
  93. package/src/components/Message/Message.module.scss +224 -0
  94. package/src/components/Message/index.tsx +278 -0
  95. package/src/components/Popover/Popover.fragment.tsx +13 -4
  96. package/src/components/Popover/Popover.module.scss +33 -10
  97. package/src/components/Popover/index.tsx +9 -11
  98. package/src/components/Progress/Progress.fragment.tsx +1 -1
  99. package/src/components/Progress/Progress.module.scss +11 -11
  100. package/src/components/Progress/index.tsx +34 -7
  101. package/src/components/Prompt/Prompt.fragment.tsx +231 -0
  102. package/src/components/Prompt/Prompt.module.scss +243 -0
  103. package/src/components/Prompt/index.tsx +439 -0
  104. package/src/components/RadioGroup/RadioGroup.fragment.tsx +1 -1
  105. package/src/components/RadioGroup/RadioGroup.module.scss +10 -7
  106. package/src/components/RadioGroup/index.tsx +3 -4
  107. package/src/components/Select/Select.fragment.tsx +10 -1
  108. package/src/components/Select/Select.module.scss +8 -0
  109. package/src/components/Select/index.tsx +91 -12
  110. package/src/components/Separator/Separator.fragment.tsx +1 -1
  111. package/src/components/Separator/index.tsx +7 -3
  112. package/src/components/Sidebar/Sidebar.fragment.tsx +11 -2
  113. package/src/components/Sidebar/Sidebar.module.scss +91 -47
  114. package/src/components/Sidebar/index.tsx +57 -14
  115. package/src/components/Skeleton/Skeleton.fragment.tsx +6 -6
  116. package/src/components/Skeleton/Skeleton.module.scss +11 -0
  117. package/src/components/Slider/Slider.fragment.tsx +201 -0
  118. package/src/components/Slider/Slider.module.scss +87 -0
  119. package/src/components/Slider/index.tsx +88 -0
  120. package/src/components/Stack/Stack.fragment.tsx +194 -0
  121. package/src/components/Stack/Stack.module.scss +120 -0
  122. package/src/components/Stack/index.tsx +148 -0
  123. package/src/components/Table/Table.fragment.tsx +10 -3
  124. package/src/components/Table/Table.module.scss +57 -0
  125. package/src/components/Table/index.tsx +44 -6
  126. package/src/components/Tabs/Tabs.fragment.tsx +10 -1
  127. package/src/components/Tabs/Tabs.module.scss +25 -10
  128. package/src/components/Tabs/index.tsx +11 -8
  129. package/src/components/Text/Text.fragment.tsx +188 -0
  130. package/src/components/Text/Text.module.scss +82 -0
  131. package/src/components/Text/index.tsx +58 -0
  132. package/src/components/Textarea/Textarea.fragment.tsx +1 -1
  133. package/src/components/Textarea/index.tsx +3 -7
  134. package/src/components/Theme/Theme.fragment.tsx +128 -0
  135. package/src/components/Theme/ThemeToggle.module.scss +82 -0
  136. package/src/components/Theme/index.tsx +343 -0
  137. package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +182 -0
  138. package/src/components/ThinkingIndicator/ThinkingIndicator.module.scss +226 -0
  139. package/src/components/ThinkingIndicator/index.tsx +258 -0
  140. package/src/components/Toast/Toast.fragment.tsx +6 -6
  141. package/src/components/Toast/Toast.module.scss +16 -1
  142. package/src/components/Toast/index.tsx +27 -11
  143. package/src/components/Toggle/Toggle.fragment.tsx +1 -1
  144. package/src/components/Toggle/Toggle.module.scss +25 -10
  145. package/src/components/Toggle/index.tsx +12 -0
  146. package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +207 -0
  147. package/src/components/ToggleGroup/ToggleGroup.module.scss +134 -0
  148. package/src/components/ToggleGroup/index.tsx +144 -0
  149. package/src/components/Tooltip/Tooltip.fragment.tsx +3 -3
  150. package/src/components/Tooltip/Tooltip.module.scss +4 -4
  151. package/src/components/Tooltip/index.tsx +4 -2
  152. package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +134 -0
  153. package/src/components/VisuallyHidden/VisuallyHidden.module.scss +13 -0
  154. package/src/components/VisuallyHidden/index.tsx +29 -0
  155. package/src/index.ts +278 -3
  156. package/src/recipes/AIChat.recipe.ts +266 -0
  157. package/src/recipes/AppShell.recipe.ts +175 -0
  158. package/src/recipes/CardGrid.recipe.ts +6 -2
  159. package/src/recipes/ChatInterface.recipe.ts +87 -0
  160. package/src/recipes/CodeExamples.recipe.ts +66 -0
  161. package/src/recipes/DashboardLayout.recipe.ts +46 -12
  162. package/src/recipes/DashboardNav.recipe.ts +183 -0
  163. package/src/recipes/LoginForm.recipe.ts +8 -1
  164. package/src/recipes/SettingsPage.recipe.ts +37 -20
  165. package/src/styles/globals.scss +31 -0
  166. package/src/tokens/_computed.scss +212 -0
  167. package/src/tokens/_density.scss +171 -0
  168. package/src/tokens/_derive.scss +287 -0
  169. package/src/tokens/_index.scss +41 -0
  170. package/src/tokens/_mixins.scss +95 -1
  171. package/src/tokens/_palettes.scss +185 -0
  172. package/src/tokens/_radius.scss +107 -0
  173. package/src/tokens/_seeds.scss +59 -0
  174. package/src/tokens/_variables.scss +507 -101
  175. package/src/utils/a11y.tsx +439 -0
@@ -0,0 +1,183 @@
1
+ import { defineRecipe } from '@fragments/core';
2
+
3
+ export default defineRecipe({
4
+ name: 'Dashboard Navigation',
5
+ description: 'Sidebar navigation pattern for dashboard applications with user profile, sections, and nested menus',
6
+ category: 'navigation',
7
+ components: ['Sidebar', 'Avatar'],
8
+ tags: ['navigation', 'sidebar', 'dashboard', 'admin', 'menu'],
9
+ code: `
10
+ // Dashboard Navigation with User Profile
11
+ // A complete sidebar navigation for admin/dashboard interfaces
12
+
13
+ function DashboardNav({ user, currentPath }) {
14
+ const [collapsed, setCollapsed] = React.useState(false);
15
+ const [projectsExpanded, setProjectsExpanded] = React.useState(false);
16
+
17
+ return (
18
+ <Sidebar
19
+ collapsed={collapsed}
20
+ onCollapsedChange={setCollapsed}
21
+ >
22
+ {/* Brand header */}
23
+ <Sidebar.Header>
24
+ <svg width="32" height="32" viewBox="0 0 256 256" fill="var(--fui-color-accent)">
25
+ <path d="M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32Zm0,176H48V48H208V208Z" />
26
+ </svg>
27
+ {!collapsed && (
28
+ <span style={{ fontWeight: 600, fontSize: '16px' }}>Dashboard</span>
29
+ )}
30
+ <Sidebar.CollapseToggle />
31
+ </Sidebar.Header>
32
+
33
+ {/* Main navigation */}
34
+ <Sidebar.Nav aria-label="Dashboard navigation">
35
+ {/* Primary section */}
36
+ <Sidebar.Section>
37
+ <Sidebar.Item
38
+ icon={<HomeIcon />}
39
+ href="/dashboard"
40
+ active={currentPath === '/dashboard'}
41
+ >
42
+ Overview
43
+ </Sidebar.Item>
44
+ <Sidebar.Item
45
+ icon={<ChartIcon />}
46
+ href="/analytics"
47
+ active={currentPath === '/analytics'}
48
+ badge="New"
49
+ >
50
+ Analytics
51
+ </Sidebar.Item>
52
+ <Sidebar.Item
53
+ icon={<InboxIcon />}
54
+ href="/inbox"
55
+ active={currentPath === '/inbox'}
56
+ badge="5"
57
+ >
58
+ Inbox
59
+ </Sidebar.Item>
60
+ </Sidebar.Section>
61
+
62
+ {/* Projects section with nested items */}
63
+ <Sidebar.Section label="Workspace">
64
+ <Sidebar.Item
65
+ icon={<FolderIcon />}
66
+ hasSubmenu
67
+ expanded={projectsExpanded}
68
+ onExpandedChange={setProjectsExpanded}
69
+ >
70
+ Projects
71
+ </Sidebar.Item>
72
+ <Sidebar.Submenu>
73
+ <Sidebar.SubItem
74
+ href="/projects/website"
75
+ active={currentPath === '/projects/website'}
76
+ >
77
+ Website Redesign
78
+ </Sidebar.SubItem>
79
+ <Sidebar.SubItem
80
+ href="/projects/mobile"
81
+ active={currentPath === '/projects/mobile'}
82
+ >
83
+ Mobile App
84
+ </Sidebar.SubItem>
85
+ <Sidebar.SubItem
86
+ href="/projects/api"
87
+ active={currentPath === '/projects/api'}
88
+ >
89
+ API Integration
90
+ </Sidebar.SubItem>
91
+ </Sidebar.Submenu>
92
+ <Sidebar.Item
93
+ icon={<UsersIcon />}
94
+ href="/team"
95
+ active={currentPath === '/team'}
96
+ >
97
+ Team Members
98
+ </Sidebar.Item>
99
+ <Sidebar.Item
100
+ icon={<CalendarIcon />}
101
+ href="/calendar"
102
+ active={currentPath === '/calendar'}
103
+ >
104
+ Calendar
105
+ </Sidebar.Item>
106
+ </Sidebar.Section>
107
+
108
+ {/* Settings section */}
109
+ <Sidebar.Section label="Account">
110
+ <Sidebar.Item
111
+ icon={<GearIcon />}
112
+ href="/settings"
113
+ active={currentPath === '/settings'}
114
+ >
115
+ Settings
116
+ </Sidebar.Item>
117
+ <Sidebar.Item
118
+ icon={<HelpIcon />}
119
+ href="/help"
120
+ active={currentPath === '/help'}
121
+ >
122
+ Help & Support
123
+ </Sidebar.Item>
124
+ </Sidebar.Section>
125
+ </Sidebar.Nav>
126
+
127
+ {/* Footer with user profile */}
128
+ <Sidebar.Footer>
129
+ <div style={{
130
+ display: 'flex',
131
+ alignItems: 'center',
132
+ gap: '12px',
133
+ padding: collapsed ? '0' : '8px',
134
+ borderRadius: '8px',
135
+ cursor: 'pointer',
136
+ }}>
137
+ <Avatar
138
+ src={user.avatar}
139
+ name={user.name}
140
+ size="sm"
141
+ />
142
+ {!collapsed && (
143
+ <div style={{ flex: 1, minWidth: 0 }}>
144
+ <div style={{
145
+ fontWeight: 500,
146
+ fontSize: '14px',
147
+ whiteSpace: 'nowrap',
148
+ overflow: 'hidden',
149
+ textOverflow: 'ellipsis',
150
+ }}>
151
+ {user.name}
152
+ </div>
153
+ <div style={{
154
+ fontSize: '12px',
155
+ color: 'var(--fui-text-secondary)',
156
+ whiteSpace: 'nowrap',
157
+ overflow: 'hidden',
158
+ textOverflow: 'ellipsis',
159
+ }}>
160
+ {user.email}
161
+ </div>
162
+ </div>
163
+ )}
164
+ </div>
165
+ </Sidebar.Footer>
166
+ </Sidebar>
167
+ );
168
+ }
169
+
170
+ // Usage example:
171
+ // <DashboardNav
172
+ // user={{ name: 'Jane Doe', email: 'jane@example.com', avatar: '/avatar.jpg' }}
173
+ // currentPath="/dashboard"
174
+ // />
175
+
176
+ // Icon components (use your preferred icon library)
177
+ const HomeIcon = () => (
178
+ <svg width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
179
+ <path d="M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8H96a8,8,0,0,0,8-8V160h48v56a8,8,0,0,0,8,8h56a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68Z" />
180
+ </svg>
181
+ );
182
+ `.trim(),
183
+ });
@@ -14,6 +14,13 @@ export default defineRecipe({
14
14
  <Input type="password" />
15
15
  </FormField>
16
16
  <Button type="submit" variant="primary">Sign in</Button>
17
- {error && <Alert variant="danger">{error}</Alert>}
17
+ {error && (
18
+ <Alert severity="error">
19
+ <Alert.Icon />
20
+ <Alert.Body>
21
+ <Alert.Content>{error}</Alert.Content>
22
+ </Alert.Body>
23
+ </Alert>
24
+ )}
18
25
  `.trim(),
19
26
  });
@@ -8,30 +8,47 @@ export default defineRecipe({
8
8
  tags: ['settings', 'preferences', 'form', 'toggle', 'layout'],
9
9
  code: `
10
10
  <Grid columns={1} gap="lg">
11
- <Card title="Profile" description="Your public profile information">
12
- <Grid columns={2} gap="md">
13
- <Input label="Display Name" defaultValue={user.name} />
14
- <Input label="Email" type="email" defaultValue={user.email} />
15
- <Grid.Item colSpan="full">
16
- <Input label="Website" type="url" defaultValue={user.website} />
17
- </Grid.Item>
18
- </Grid>
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>
19
25
  </Card>
20
26
 
21
- <Card title="Notifications" description="Choose what you get notified about">
22
- <Grid columns={1} gap="sm">
23
- <Toggle label="Email notifications" checked={prefs.emailNotifs} onChange={onToggle('emailNotifs')} />
24
- <Toggle label="Push notifications" checked={prefs.pushNotifs} onChange={onToggle('pushNotifs')} />
25
- <Toggle label="Weekly digest" checked={prefs.digest} onChange={onToggle('digest')} />
26
- </Grid>
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>
27
39
  </Card>
28
40
 
29
- <Card title="Appearance">
30
- <Select label="Theme" value={prefs.theme} onChange={onThemeChange}>
31
- <Select.Item value="light">Light</Select.Item>
32
- <Select.Item value="dark">Dark</Select.Item>
33
- <Select.Item value="system">System</Select.Item>
34
- </Select>
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>
35
52
  </Card>
36
53
 
37
54
  <Separator />
@@ -15,3 +15,34 @@
15
15
 
16
16
  // Output all CSS custom properties
17
17
  @include fui-css-variables;
18
+
19
+ // ============================================
20
+ // Accessibility: Reduced Motion
21
+ // ============================================
22
+ // Respects user's system preference for reduced motion.
23
+ // Disables animations and transitions globally when enabled.
24
+
25
+ @media (prefers-reduced-motion: reduce) {
26
+ *,
27
+ *::before,
28
+ *::after {
29
+ animation-duration: 0.01ms !important;
30
+ animation-iteration-count: 1 !important;
31
+ transition-duration: 0.01ms !important;
32
+ scroll-behavior: auto !important;
33
+ }
34
+ }
35
+
36
+ // ============================================
37
+ // Accessibility: Focus Visible
38
+ // ============================================
39
+ // Ensures focus indicators are only shown for keyboard navigation
40
+
41
+ :focus:not(:focus-visible) {
42
+ outline: none;
43
+ }
44
+
45
+ :focus-visible {
46
+ outline: var(--fui-focus-ring-width, 2px) solid var(--fui-focus-ring-color, #18181b);
47
+ outline-offset: var(--fui-focus-ring-offset, 2px);
48
+ }
@@ -0,0 +1,212 @@
1
+ // ============================================
2
+ // @fragments/ui Computed Tokens
3
+ // ============================================
4
+ //
5
+ // This module imports seed values and derivation functions,
6
+ // then computes all token values. These computed values are
7
+ // used as fallbacks in _variables.scss for backward compatibility.
8
+ //
9
+ // ============================================
10
+
11
+ @use 'sass:map';
12
+ @use 'seeds' as seeds;
13
+ @use 'palettes' as palettes;
14
+ @use 'density' as density;
15
+ @use 'radius' as radius;
16
+ @use 'derive' as derive;
17
+
18
+ // --------------------------------------------
19
+ // Resolve Seed Values
20
+ // --------------------------------------------
21
+
22
+ // Get the selected configurations
23
+ $_palette: palettes.get-palette(seeds.$fui-neutral);
24
+ $_density: density.get-density(seeds.$fui-density);
25
+ $_radius: radius.get-radius-style(seeds.$fui-radius-style);
26
+
27
+ // Derive spacing and padding from density
28
+ $_spacing: density.derive-spacing($_density);
29
+ $_padding: density.derive-padding($_density);
30
+
31
+ // --------------------------------------------
32
+ // Computed: Base Configuration
33
+ // --------------------------------------------
34
+
35
+ $computed-base-font-size: map.get($_density, base-font-size);
36
+ $computed-base-unit: map.get($_density, base-unit);
37
+
38
+ // --------------------------------------------
39
+ // Computed: Spacing
40
+ // --------------------------------------------
41
+
42
+ $computed-space-px: map.get($_spacing, px);
43
+ $computed-space-0-5: map.get($_spacing, 0-5);
44
+ $computed-space-0-75: map.get($_spacing, 0-75);
45
+ $computed-space-1: map.get($_spacing, 1);
46
+ $computed-space-2: map.get($_spacing, 2);
47
+ $computed-space-3: map.get($_spacing, 3);
48
+ $computed-space-4: map.get($_spacing, 4);
49
+ $computed-space-5: map.get($_spacing, 5);
50
+ $computed-space-6: map.get($_spacing, 6);
51
+ $computed-space-8: map.get($_spacing, 8);
52
+ $computed-space-10: map.get($_spacing, 10);
53
+ $computed-space-12: map.get($_spacing, 12);
54
+
55
+ // --------------------------------------------
56
+ // Computed: Density Padding
57
+ // --------------------------------------------
58
+
59
+ $computed-padding-container-sm: map.get($_padding, container-sm);
60
+ $computed-padding-container-md: map.get($_padding, container-md);
61
+ $computed-padding-container-lg: map.get($_padding, container-lg);
62
+ $computed-padding-inline-sm: map.get($_padding, inline-sm);
63
+ $computed-padding-inline-md: map.get($_padding, inline-md);
64
+ $computed-padding-inline-lg: map.get($_padding, inline-lg);
65
+ $computed-padding-item-sm: map.get($_padding, item-sm);
66
+ $computed-padding-item-md: map.get($_padding, item-md);
67
+ $computed-padding-item-lg: map.get($_padding, item-lg);
68
+
69
+ // --------------------------------------------
70
+ // Computed: Border Radius
71
+ // --------------------------------------------
72
+
73
+ $computed-radius-sm: radius.radius-value($_radius, sm);
74
+ $computed-radius-md: radius.radius-value($_radius, md);
75
+ $computed-radius-lg: radius.radius-value($_radius, lg);
76
+ $computed-radius-xl: radius.radius-value($_radius, xl);
77
+ $computed-radius-full: radius.radius-full();
78
+
79
+ // --------------------------------------------
80
+ // Computed: Component Heights (from density)
81
+ // --------------------------------------------
82
+
83
+ $computed-button-height-sm: density.px-to-rem($_density, map.get($_density, button-height-sm));
84
+ $computed-button-height-md: density.px-to-rem($_density, map.get($_density, button-height-md));
85
+ $computed-button-height-lg: density.px-to-rem($_density, map.get($_density, button-height-lg));
86
+
87
+ $computed-input-height: density.px-to-rem($_density, map.get($_density, input-height));
88
+ $computed-input-height-sm: density.px-to-rem($_density, map.get($_density, input-height-sm));
89
+ $computed-input-height-lg: density.px-to-rem($_density, map.get($_density, input-height-lg));
90
+
91
+ $computed-touch-sm: density.px-to-rem($_density, map.get($_density, touch-sm));
92
+ $computed-touch-md: density.px-to-rem($_density, map.get($_density, touch-md));
93
+ $computed-touch-lg: density.px-to-rem($_density, map.get($_density, touch-lg));
94
+
95
+ // --------------------------------------------
96
+ // Computed: Colors - Light Mode
97
+ // --------------------------------------------
98
+
99
+ // Accent colors (from brand seed)
100
+ $computed-color-accent: seeds.$fui-brand;
101
+ $computed-color-accent-hover: derive.derive-accent-hover(seeds.$fui-brand, false);
102
+ $computed-color-accent-active: derive.derive-accent-active(seeds.$fui-brand, false);
103
+
104
+ // Semantic colors (from seeds or defaults)
105
+ $computed-color-danger: seeds.$fui-danger;
106
+ $computed-color-danger-hover: derive.derive-semantic-hover(seeds.$fui-danger);
107
+ $computed-color-success: seeds.$fui-success;
108
+ $computed-color-warning: seeds.$fui-warning;
109
+ $computed-color-info: seeds.$fui-info;
110
+
111
+ // Semantic backgrounds
112
+ $computed-color-danger-bg: derive.derive-semantic-bg(seeds.$fui-danger, false);
113
+ $computed-color-success-bg: derive.derive-semantic-bg(seeds.$fui-success, false);
114
+ $computed-color-warning-bg: derive.derive-semantic-bg(seeds.$fui-warning, false);
115
+ $computed-color-info-bg: derive.derive-semantic-bg(seeds.$fui-info, false);
116
+
117
+ // Surfaces (from neutral palette)
118
+ $_surfaces-light: derive.derive-surfaces($_palette, false);
119
+ $computed-bg-primary: map.get($_surfaces-light, primary);
120
+ $computed-bg-secondary: map.get($_surfaces-light, secondary);
121
+ $computed-bg-tertiary: map.get($_surfaces-light, tertiary);
122
+ $computed-bg-elevated: map.get($_surfaces-light, elevated);
123
+ $computed-bg-subtle: map.get($_surfaces-light, subtle);
124
+ $computed-bg-hover: map.get($_surfaces-light, hover);
125
+ $computed-bg-active: map.get($_surfaces-light, active);
126
+
127
+ // Text (from neutral palette)
128
+ $_text-light: derive.derive-text($_palette, false);
129
+ $computed-text-primary: map.get($_text-light, primary);
130
+ $computed-text-secondary: map.get($_text-light, secondary);
131
+ $computed-text-tertiary: map.get($_text-light, tertiary);
132
+ $computed-text-inverse: map.get($_text-light, inverse);
133
+
134
+ // Borders (from neutral palette)
135
+ $_borders-light: derive.derive-borders($_palette, false);
136
+ $computed-border: map.get($_borders-light, default);
137
+ $computed-border-default: map.get($_borders-light, default);
138
+ $computed-border-strong: map.get($_borders-light, strong);
139
+
140
+ // Shadows
141
+ $_shadows-light: derive.derive-shadows(false);
142
+ $computed-shadow-sm: map.get($_shadows-light, sm);
143
+ $computed-shadow-md: map.get($_shadows-light, md);
144
+ $computed-shadow-lg: map.get($_shadows-light, lg);
145
+
146
+ // Focus ring
147
+ $computed-focus-ring-color: derive.derive-focus-ring(seeds.$fui-brand, false);
148
+
149
+ // Scrollbar
150
+ $_scrollbar-light: derive.derive-scrollbar($_palette, false);
151
+ $computed-scrollbar-track: map.get($_scrollbar-light, track);
152
+ $computed-scrollbar-thumb: map.get($_scrollbar-light, thumb);
153
+ $computed-scrollbar-thumb-hover: map.get($_scrollbar-light, thumb-hover);
154
+
155
+ // Backdrop
156
+ $computed-backdrop: derive.derive-backdrop(false);
157
+
158
+ // --------------------------------------------
159
+ // Computed: Colors - Dark Mode
160
+ // --------------------------------------------
161
+
162
+ // Accent colors (derive light version for dark mode)
163
+ $computed-dark-color-accent: derive.derive-dark-accent(seeds.$fui-brand);
164
+ $computed-dark-color-accent-hover: derive.derive-accent-hover($computed-dark-color-accent, true);
165
+ $computed-dark-color-accent-active: derive.derive-accent-active($computed-dark-color-accent, true);
166
+
167
+ // Semantic backgrounds (dark mode)
168
+ $computed-dark-color-danger-bg: derive.derive-semantic-bg(seeds.$fui-danger, true);
169
+ $computed-dark-color-success-bg: derive.derive-semantic-bg(seeds.$fui-success, true);
170
+ $computed-dark-color-warning-bg: derive.derive-semantic-bg(seeds.$fui-warning, true);
171
+ $computed-dark-color-info-bg: derive.derive-semantic-bg(seeds.$fui-info, true);
172
+
173
+ // Surfaces (dark mode)
174
+ $_surfaces-dark: derive.derive-surfaces($_palette, true);
175
+ $computed-dark-bg-primary: map.get($_surfaces-dark, primary);
176
+ $computed-dark-bg-secondary: map.get($_surfaces-dark, secondary);
177
+ $computed-dark-bg-tertiary: map.get($_surfaces-dark, tertiary);
178
+ $computed-dark-bg-elevated: map.get($_surfaces-dark, elevated);
179
+ $computed-dark-bg-subtle: map.get($_surfaces-dark, subtle);
180
+ $computed-dark-bg-hover: map.get($_surfaces-dark, hover);
181
+ $computed-dark-bg-active: map.get($_surfaces-dark, active);
182
+
183
+ // Text (dark mode)
184
+ $_text-dark: derive.derive-text($_palette, true);
185
+ $computed-dark-text-primary: map.get($_text-dark, primary);
186
+ $computed-dark-text-secondary: map.get($_text-dark, secondary);
187
+ $computed-dark-text-tertiary: map.get($_text-dark, tertiary);
188
+ $computed-dark-text-inverse: map.get($_text-dark, inverse);
189
+
190
+ // Borders (dark mode)
191
+ $_borders-dark: derive.derive-borders($_palette, true);
192
+ $computed-dark-border: map.get($_borders-dark, default);
193
+ $computed-dark-border-default: map.get($_borders-dark, default);
194
+ $computed-dark-border-strong: map.get($_borders-dark, strong);
195
+
196
+ // Shadows (dark mode)
197
+ $_shadows-dark: derive.derive-shadows(true);
198
+ $computed-dark-shadow-sm: map.get($_shadows-dark, sm);
199
+ $computed-dark-shadow-md: map.get($_shadows-dark, md);
200
+ $computed-dark-shadow-lg: map.get($_shadows-dark, lg);
201
+
202
+ // Focus ring (dark mode)
203
+ $computed-dark-focus-ring-color: derive.derive-focus-ring(seeds.$fui-brand, true);
204
+
205
+ // Scrollbar (dark mode)
206
+ $_scrollbar-dark: derive.derive-scrollbar($_palette, true);
207
+ $computed-dark-scrollbar-track: map.get($_scrollbar-dark, track);
208
+ $computed-dark-scrollbar-thumb: map.get($_scrollbar-dark, thumb);
209
+ $computed-dark-scrollbar-thumb-hover: map.get($_scrollbar-dark, thumb-hover);
210
+
211
+ // Backdrop (dark mode)
212
+ $computed-dark-backdrop: derive.derive-backdrop(true);
@@ -0,0 +1,171 @@
1
+ // ============================================
2
+ // @fragments/ui Density Scales
3
+ // ============================================
4
+ //
5
+ // Defines 3 density presets that control spacing and sizing.
6
+ // Each preset uses a different base unit to scale all spacing.
7
+ //
8
+ // ============================================
9
+
10
+ @use 'sass:math';
11
+ @use 'sass:map';
12
+
13
+ // --------------------------------------------
14
+ // Density Definitions
15
+ // --------------------------------------------
16
+ // Each density includes:
17
+ // - base-unit: The fundamental spacing unit
18
+ // - multipliers: Scale factors for spacing tokens
19
+
20
+ $density-compact: (
21
+ base-unit: 6px,
22
+ base-font-size: 14px,
23
+ // Button heights (more compact)
24
+ button-height-sm: 24px, // ~4 units
25
+ button-height-md: 30px, // 5 units
26
+ button-height-lg: 36px, // 6 units
27
+ // Input height
28
+ input-height: 32px, // ~5.3 units
29
+ input-height-sm: 24px, // 4 units
30
+ input-height-lg: 36px, // 6 units
31
+ // Touch targets (minimum for accessibility)
32
+ touch-sm: 24px,
33
+ touch-md: 30px,
34
+ touch-lg: 36px
35
+ );
36
+
37
+ $density-default: (
38
+ base-unit: 7px,
39
+ base-font-size: 14px,
40
+ // Button heights (current values)
41
+ button-height-sm: 28px, // 4 units
42
+ button-height-md: 36px, // ~5 units
43
+ button-height-lg: 44px, // ~6 units
44
+ // Input height
45
+ input-height: 40px, // ~6 units
46
+ input-height-sm: 28px, // 4 units
47
+ input-height-lg: 44px, // ~6 units
48
+ // Touch targets
49
+ touch-sm: 24px,
50
+ touch-md: 32px,
51
+ touch-lg: 44px
52
+ );
53
+
54
+ $density-relaxed: (
55
+ base-unit: 8px,
56
+ base-font-size: 14px,
57
+ // Button heights (more spacious)
58
+ button-height-sm: 32px, // 4 units
59
+ button-height-md: 40px, // 5 units
60
+ button-height-lg: 48px, // 6 units
61
+ // Input height
62
+ input-height: 44px, // ~5.5 units
63
+ input-height-sm: 32px, // 4 units
64
+ input-height-lg: 48px, // 6 units
65
+ // Touch targets
66
+ touch-sm: 32px,
67
+ touch-md: 40px,
68
+ touch-lg: 48px
69
+ );
70
+
71
+ // --------------------------------------------
72
+ // Density Map for Dynamic Access
73
+ // --------------------------------------------
74
+ $densities: (
75
+ "compact": $density-compact,
76
+ "default": $density-default,
77
+ "relaxed": $density-relaxed
78
+ );
79
+
80
+ // --------------------------------------------
81
+ // Helper Functions
82
+ // --------------------------------------------
83
+
84
+ /// Get a density configuration by name
85
+ /// @param {String} $name - Density name (compact, default, relaxed)
86
+ /// @return {Map} The density configuration map
87
+ @function get-density($name) {
88
+ @if not map.has-key($densities, $name) {
89
+ @warn "Unknown density '#{$name}'. Using 'default' as fallback.";
90
+ @return $density-default;
91
+ }
92
+ @return map.get($densities, $name);
93
+ }
94
+
95
+ /// Get a specific value from a density configuration
96
+ /// @param {Map} $density - A density configuration map
97
+ /// @param {String} $key - The key to retrieve
98
+ /// @return {*} The value
99
+ @function density-value($density, $key) {
100
+ @if not map.has-key($density, $key) {
101
+ @warn "Unknown density key '#{$key}'.";
102
+ @return null;
103
+ }
104
+ @return map.get($density, $key);
105
+ }
106
+
107
+ /// Derive spacing scale from base unit
108
+ /// Returns a map with spacing tokens
109
+ /// @param {Map} $density - A density configuration map
110
+ /// @return {Map} Spacing tokens
111
+ @function derive-spacing($density) {
112
+ $base: map.get($density, base-unit);
113
+ $font-size: map.get($density, base-font-size);
114
+
115
+ // Convert to rem based on font size
116
+ // 1 unit = base-unit / font-size * 1rem
117
+ $unit-rem: math.div($base, $font-size) * 1rem;
118
+
119
+ @return (
120
+ // Micro spacing (pixel-precise for tiny alignments)
121
+ px: 1px,
122
+ 0-5: $unit-rem * 0.3, // ~2px for 7px base
123
+ 0-75: $unit-rem * 0.43, // ~3px for 7px base
124
+
125
+ // Standard spacing scale
126
+ 1: $unit-rem, // 1 unit
127
+ 2: $unit-rem * 2, // 2 units
128
+ 3: $unit-rem * 3, // 3 units
129
+ 4: $unit-rem * 4, // 4 units
130
+ 5: $unit-rem * 5, // 5 units
131
+ 6: $unit-rem * 6, // 6 units
132
+ 8: $unit-rem * 8, // 8 units
133
+ 10: $unit-rem * 10, // 10 units
134
+ 12: $unit-rem * 12 // 12 units
135
+ );
136
+ }
137
+
138
+ /// Derive container padding from density
139
+ /// @param {Map} $density - A density configuration map
140
+ /// @return {Map} Padding tokens
141
+ @function derive-padding($density) {
142
+ $base: map.get($density, base-unit);
143
+ $font-size: map.get($density, base-font-size);
144
+ $unit-rem: math.div($base, $font-size) * 1rem;
145
+
146
+ @return (
147
+ // Container padding: Cards, Dialogs, Popovers
148
+ container-sm: $unit-rem * 3, // 3 units
149
+ container-md: $unit-rem * 4, // 4 units
150
+ container-lg: $unit-rem * 6, // 6 units
151
+
152
+ // Inline padding: Alerts, Toasts
153
+ inline-sm: $unit-rem * 2, // 2 units
154
+ inline-md: $unit-rem * 3, // 3 units
155
+ inline-lg: $unit-rem * 4, // 4 units
156
+
157
+ // Item padding: Accordion, Tabs, Menu items
158
+ item-sm: $unit-rem * 2, // 2 units
159
+ item-md: $unit-rem * 3, // 3 units
160
+ item-lg: $unit-rem * 4 // 4 units
161
+ );
162
+ }
163
+
164
+ /// Convert pixel value to rem based on density
165
+ /// @param {Map} $density - A density configuration map
166
+ /// @param {Number} $px - Pixel value to convert
167
+ /// @return {Number} Value in rem
168
+ @function px-to-rem($density, $px) {
169
+ $font-size: map.get($density, base-font-size);
170
+ @return math.div($px, $font-size) * 1rem;
171
+ }