@fragments-sdk/ui 0.3.0 → 0.4.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.
- package/fragments.json +1 -1
- package/package.json +9 -4
- package/src/components/Accordion/Accordion.fragment.tsx +186 -0
- package/src/components/Accordion/Accordion.module.scss +111 -0
- package/src/components/Accordion/index.tsx +271 -0
- package/src/components/Alert/Alert.fragment.tsx +66 -41
- package/src/components/Alert/Alert.module.scss +31 -21
- package/src/components/Alert/index.tsx +202 -73
- package/src/components/AppShell/AppShell.fragment.tsx +315 -0
- package/src/components/AppShell/AppShell.module.scss +213 -0
- package/src/components/AppShell/index.tsx +398 -0
- package/src/components/Avatar/index.tsx +8 -9
- package/src/components/Badge/Badge.module.scss +16 -10
- package/src/components/Badge/index.tsx +20 -6
- package/src/components/Box/Box.fragment.tsx +168 -0
- package/src/components/Box/Box.module.scss +84 -0
- package/src/components/Box/index.tsx +78 -0
- package/src/components/Button/Button.module.scss +42 -0
- package/src/components/Button/index.tsx +67 -33
- package/src/components/ButtonGroup/ButtonGroup.module.scss +37 -0
- package/src/components/ButtonGroup/index.tsx +40 -0
- package/src/components/Card/Card.fragment.tsx +51 -25
- package/src/components/Card/Card.module.scss +52 -5
- package/src/components/Card/index.tsx +154 -53
- package/src/components/Checkbox/Checkbox.module.scss +4 -4
- package/src/components/Checkbox/index.tsx +3 -4
- package/src/components/CodeBlock/CodeBlock.fragment.tsx +201 -0
- package/src/components/CodeBlock/CodeBlock.module.scss +224 -0
- package/src/components/CodeBlock/index.tsx +385 -0
- package/src/components/ColorChip/ColorChip.module.scss +165 -0
- package/src/components/ColorChip/index.tsx +157 -0
- package/src/components/ColorPicker/ColorPicker.module.scss +109 -0
- package/src/components/ColorPicker/index.tsx +107 -0
- package/src/components/Dialog/Dialog.fragment.tsx +9 -0
- package/src/components/Dialog/Dialog.module.scss +26 -7
- package/src/components/Dialog/index.tsx +12 -15
- package/src/components/EmptyState/EmptyState.fragment.tsx +54 -71
- package/src/components/EmptyState/EmptyState.module.scss +9 -9
- package/src/components/EmptyState/index.tsx +104 -69
- package/src/components/Field/Field.fragment.tsx +165 -0
- package/src/components/Field/Field.module.scss +31 -0
- package/src/components/Field/index.tsx +143 -0
- package/src/components/Fieldset/Fieldset.fragment.tsx +166 -0
- package/src/components/Fieldset/Fieldset.module.scss +22 -0
- package/src/components/Fieldset/index.tsx +47 -0
- package/src/components/Form/Form.fragment.tsx +286 -0
- package/src/components/Form/Form.module.scss +8 -0
- package/src/components/Form/index.tsx +53 -0
- package/src/components/Grid/Grid.fragment.tsx +17 -17
- package/src/components/Grid/index.tsx +6 -1
- package/src/components/Header/Header.fragment.tsx +192 -0
- package/src/components/Header/Header.module.scss +209 -0
- package/src/components/Header/index.tsx +363 -0
- package/src/components/Icon/Icon.fragment.tsx +138 -0
- package/src/components/Icon/Icon.module.scss +38 -0
- package/src/components/Icon/index.tsx +58 -0
- package/src/components/Image/Image.fragment.tsx +195 -0
- package/src/components/Image/Image.module.scss +77 -0
- package/src/components/Image/index.tsx +95 -0
- package/src/components/Input/Input.module.scss +75 -2
- package/src/components/Input/index.tsx +60 -21
- package/src/components/Link/Link.fragment.tsx +132 -0
- package/src/components/Link/Link.module.scss +67 -0
- package/src/components/Link/index.tsx +57 -0
- package/src/components/List/List.fragment.tsx +152 -0
- package/src/components/List/List.module.scss +71 -0
- package/src/components/List/index.tsx +106 -0
- package/src/components/Listbox/Listbox.fragment.tsx +191 -0
- package/src/components/Listbox/Listbox.module.scss +97 -0
- package/src/components/Listbox/index.tsx +121 -0
- package/src/components/Menu/Menu.fragment.tsx +9 -0
- package/src/components/Menu/Menu.module.scss +17 -1
- package/src/components/Menu/index.tsx +3 -3
- package/src/components/Popover/Popover.fragment.tsx +9 -0
- package/src/components/Popover/Popover.module.scss +33 -10
- package/src/components/Popover/index.tsx +9 -11
- package/src/components/Progress/Progress.module.scss +11 -11
- package/src/components/Progress/index.tsx +34 -7
- package/src/components/Prompt/Prompt.fragment.tsx +231 -0
- package/src/components/Prompt/Prompt.module.scss +243 -0
- package/src/components/Prompt/index.tsx +439 -0
- package/src/components/RadioGroup/RadioGroup.module.scss +3 -3
- package/src/components/RadioGroup/index.tsx +3 -4
- package/src/components/Select/Select.fragment.tsx +9 -0
- package/src/components/Select/index.tsx +6 -7
- package/src/components/Separator/index.tsx +7 -3
- package/src/components/Sidebar/Sidebar.fragment.tsx +9 -0
- package/src/components/Sidebar/Sidebar.module.scss +72 -47
- package/src/components/Sidebar/index.tsx +5 -3
- package/src/components/Skeleton/Skeleton.fragment.tsx +5 -5
- package/src/components/Skeleton/Skeleton.module.scss +11 -0
- package/src/components/Slider/Slider.module.scss +87 -0
- package/src/components/Slider/index.tsx +88 -0
- package/src/components/Stack/Stack.module.scss +120 -0
- package/src/components/Stack/index.tsx +148 -0
- package/src/components/Table/Table.fragment.tsx +7 -0
- package/src/components/Table/Table.module.scss +57 -0
- package/src/components/Table/index.tsx +44 -6
- package/src/components/Tabs/Tabs.fragment.tsx +9 -0
- package/src/components/Tabs/Tabs.module.scss +25 -10
- package/src/components/Tabs/index.tsx +11 -8
- package/src/components/Text/Text.module.scss +82 -0
- package/src/components/Text/index.tsx +58 -0
- package/src/components/Textarea/index.tsx +3 -7
- package/src/components/Theme/Theme.fragment.tsx +128 -0
- package/src/components/Theme/ThemeToggle.module.scss +82 -0
- package/src/components/Theme/index.tsx +343 -0
- package/src/components/Toast/Toast.fragment.tsx +5 -5
- package/src/components/Toast/Toast.module.scss +16 -1
- package/src/components/Toast/index.tsx +27 -11
- package/src/components/Toggle/Toggle.module.scss +25 -10
- package/src/components/Toggle/index.tsx +12 -0
- package/src/components/ToggleGroup/ToggleGroup.module.scss +134 -0
- package/src/components/ToggleGroup/index.tsx +144 -0
- package/src/components/Tooltip/Tooltip.module.scss +4 -4
- package/src/components/Tooltip/index.tsx +4 -2
- package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +134 -0
- package/src/components/VisuallyHidden/VisuallyHidden.module.scss +13 -0
- package/src/components/VisuallyHidden/index.tsx +29 -0
- package/src/index.ts +195 -3
- package/src/recipes/AppShell.recipe.ts +175 -0
- package/src/recipes/CardGrid.recipe.ts +6 -2
- package/src/recipes/ChatInterface.recipe.ts +87 -0
- package/src/recipes/CodeExamples.recipe.ts +66 -0
- package/src/recipes/DashboardLayout.recipe.ts +46 -12
- package/src/recipes/DashboardNav.recipe.ts +183 -0
- package/src/recipes/LoginForm.recipe.ts +8 -1
- package/src/recipes/SettingsPage.recipe.ts +37 -20
- package/src/styles/globals.scss +31 -0
- package/src/tokens/_index.scss +3 -0
- package/src/tokens/_mixins.scss +54 -1
- package/src/tokens/_variables.scss +429 -64
- 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 &&
|
|
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
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
|
|
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>
|
|
27
39
|
</Card>
|
|
28
40
|
|
|
29
|
-
<Card
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 />
|
package/src/styles/globals.scss
CHANGED
|
@@ -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
|
+
}
|
package/src/tokens/_mixins.scss
CHANGED
|
@@ -79,12 +79,19 @@
|
|
|
79
79
|
border-radius: var(--fui-radius-lg, #{$fui-radius-lg});
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
// Responsive breakpoint mixins (mobile-first)
|
|
82
|
+
// Responsive breakpoint mixins (mobile-first, min-width)
|
|
83
83
|
@mixin breakpoint-sm { @media (min-width: $fui-breakpoint-sm) { @content; } }
|
|
84
84
|
@mixin breakpoint-md { @media (min-width: $fui-breakpoint-md) { @content; } }
|
|
85
85
|
@mixin breakpoint-lg { @media (min-width: $fui-breakpoint-lg) { @content; } }
|
|
86
86
|
@mixin breakpoint-xl { @media (min-width: $fui-breakpoint-xl) { @content; } }
|
|
87
87
|
|
|
88
|
+
// Responsive breakpoint mixins (desktop-first, max-width)
|
|
89
|
+
// Use for hiding/changing elements below a breakpoint
|
|
90
|
+
@mixin below-sm { @media (max-width: #{$fui-breakpoint-sm - 1px}) { @content; } }
|
|
91
|
+
@mixin below-md { @media (max-width: #{$fui-breakpoint-md - 1px}) { @content; } }
|
|
92
|
+
@mixin below-lg { @media (max-width: #{$fui-breakpoint-lg - 1px}) { @content; } }
|
|
93
|
+
@mixin below-xl { @media (max-width: #{$fui-breakpoint-xl - 1px}) { @content; } }
|
|
94
|
+
|
|
88
95
|
// Visually hidden (for screen readers)
|
|
89
96
|
@mixin visually-hidden {
|
|
90
97
|
position: absolute;
|
|
@@ -97,3 +104,49 @@
|
|
|
97
104
|
white-space: nowrap;
|
|
98
105
|
border: 0;
|
|
99
106
|
}
|
|
107
|
+
|
|
108
|
+
// ============================================
|
|
109
|
+
// Accessibility Mixins
|
|
110
|
+
// ============================================
|
|
111
|
+
|
|
112
|
+
// Reduced motion preference - respects user's system setting
|
|
113
|
+
@mixin prefers-reduced-motion {
|
|
114
|
+
@media (prefers-reduced-motion: reduce) {
|
|
115
|
+
@content;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// High contrast mode support - for users who need more visual contrast
|
|
120
|
+
@mixin prefers-contrast {
|
|
121
|
+
@media (prefers-contrast: more) {
|
|
122
|
+
@content;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Safe transition - respects reduced motion preference
|
|
127
|
+
// Usage: @include safe-transition(background-color 150ms ease, border-color 150ms ease);
|
|
128
|
+
@mixin safe-transition($transitions...) {
|
|
129
|
+
transition: $transitions;
|
|
130
|
+
|
|
131
|
+
@media (prefers-reduced-motion: reduce) {
|
|
132
|
+
transition: none;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Safe animation - respects reduced motion preference
|
|
137
|
+
// Usage: @include safe-animation(fadeIn 200ms ease);
|
|
138
|
+
@mixin safe-animation($animation) {
|
|
139
|
+
animation: $animation;
|
|
140
|
+
|
|
141
|
+
@media (prefers-reduced-motion: reduce) {
|
|
142
|
+
animation: none;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Focus ring for error states (e.g., invalid form fields)
|
|
147
|
+
@mixin focus-ring-error {
|
|
148
|
+
outline: none;
|
|
149
|
+
box-shadow:
|
|
150
|
+
0 0 0 var(--fui-focus-ring-offset, #{$fui-focus-ring-offset}) var(--fui-bg-primary, #{$fui-bg-primary}),
|
|
151
|
+
0 0 0 calc(var(--fui-focus-ring-offset, #{$fui-focus-ring-offset}) + var(--fui-focus-ring-width, #{$fui-focus-ring-width})) var(--fui-color-danger, #{$fui-color-danger});
|
|
152
|
+
}
|