@fragments-sdk/ui 0.2.3 → 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 +783 -0
- package/src/components/Sidebar/Sidebar.module.scss +586 -0
- package/src/components/Sidebar/index.tsx +1013 -0
- 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 +241 -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,168 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Box } from './index.js';
|
|
4
|
+
|
|
5
|
+
export default defineSegment({
|
|
6
|
+
component: Box,
|
|
7
|
+
|
|
8
|
+
meta: {
|
|
9
|
+
name: 'Box',
|
|
10
|
+
description: 'Primitive layout component for applying spacing, backgrounds, and borders. A flexible container for building custom layouts.',
|
|
11
|
+
category: 'layout',
|
|
12
|
+
status: 'stable',
|
|
13
|
+
tags: ['layout', 'container', 'spacing', 'primitive', 'box'],
|
|
14
|
+
since: '0.1.0',
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
usage: {
|
|
18
|
+
when: [
|
|
19
|
+
'Applying consistent padding or margin to content sections',
|
|
20
|
+
'Creating bordered or elevated containers',
|
|
21
|
+
'Wrapping content with semantic HTML elements',
|
|
22
|
+
'Building custom layouts not covered by Stack or Grid',
|
|
23
|
+
],
|
|
24
|
+
whenNot: [
|
|
25
|
+
'Horizontal or vertical stacking (use Stack)',
|
|
26
|
+
'Grid-based layouts (use Grid)',
|
|
27
|
+
'Card-like containers with header/body/footer (use Card)',
|
|
28
|
+
'Simple text styling (use Text)',
|
|
29
|
+
],
|
|
30
|
+
guidelines: [
|
|
31
|
+
'Use padding props instead of inline styles for consistency',
|
|
32
|
+
'Choose semantic HTML elements (section, article) where appropriate',
|
|
33
|
+
'Combine with Stack or Grid for complex layouts',
|
|
34
|
+
'Use background variants from the design system, not custom colors',
|
|
35
|
+
],
|
|
36
|
+
accessibility: [
|
|
37
|
+
'Choose semantic as prop values for proper document structure',
|
|
38
|
+
'Avoid div-soup; use meaningful elements like section, article',
|
|
39
|
+
'Ensure proper heading hierarchy within Box containers',
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
props: {
|
|
44
|
+
children: {
|
|
45
|
+
type: 'node',
|
|
46
|
+
description: 'Content to render inside the box',
|
|
47
|
+
},
|
|
48
|
+
as: {
|
|
49
|
+
type: 'enum',
|
|
50
|
+
description: 'HTML element to render',
|
|
51
|
+
values: ['div', 'section', 'article', 'aside', 'main', 'header', 'footer', 'nav', 'span'],
|
|
52
|
+
default: 'div',
|
|
53
|
+
},
|
|
54
|
+
padding: {
|
|
55
|
+
type: 'enum',
|
|
56
|
+
description: 'Padding on all sides',
|
|
57
|
+
values: ['none', 'xs', 'sm', 'md', 'lg', 'xl'],
|
|
58
|
+
},
|
|
59
|
+
paddingX: {
|
|
60
|
+
type: 'enum',
|
|
61
|
+
description: 'Horizontal padding',
|
|
62
|
+
values: ['none', 'xs', 'sm', 'md', 'lg', 'xl'],
|
|
63
|
+
},
|
|
64
|
+
paddingY: {
|
|
65
|
+
type: 'enum',
|
|
66
|
+
description: 'Vertical padding',
|
|
67
|
+
values: ['none', 'xs', 'sm', 'md', 'lg', 'xl'],
|
|
68
|
+
},
|
|
69
|
+
margin: {
|
|
70
|
+
type: 'enum',
|
|
71
|
+
description: 'Margin on all sides',
|
|
72
|
+
values: ['none', 'xs', 'sm', 'md', 'lg', 'xl', 'auto'],
|
|
73
|
+
},
|
|
74
|
+
marginX: {
|
|
75
|
+
type: 'enum',
|
|
76
|
+
description: 'Horizontal margin',
|
|
77
|
+
values: ['none', 'xs', 'sm', 'md', 'lg', 'xl', 'auto'],
|
|
78
|
+
},
|
|
79
|
+
marginY: {
|
|
80
|
+
type: 'enum',
|
|
81
|
+
description: 'Vertical margin',
|
|
82
|
+
values: ['none', 'xs', 'sm', 'md', 'lg', 'xl', 'auto'],
|
|
83
|
+
},
|
|
84
|
+
background: {
|
|
85
|
+
type: 'enum',
|
|
86
|
+
description: 'Background color',
|
|
87
|
+
values: ['none', 'primary', 'secondary', 'tertiary', 'elevated'],
|
|
88
|
+
},
|
|
89
|
+
rounded: {
|
|
90
|
+
type: 'enum',
|
|
91
|
+
description: 'Border radius',
|
|
92
|
+
values: ['none', 'sm', 'md', 'lg', 'full'],
|
|
93
|
+
},
|
|
94
|
+
border: {
|
|
95
|
+
type: 'boolean',
|
|
96
|
+
description: 'Show border',
|
|
97
|
+
default: 'false',
|
|
98
|
+
},
|
|
99
|
+
display: {
|
|
100
|
+
type: 'enum',
|
|
101
|
+
description: 'Display type',
|
|
102
|
+
values: ['block', 'inline', 'inline-block', 'flex', 'inline-flex', 'grid', 'none'],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
relations: [
|
|
107
|
+
{ component: 'Stack', relationship: 'alternative', note: 'Use Stack for directional layouts with gap' },
|
|
108
|
+
{ component: 'Grid', relationship: 'alternative', note: 'Use Grid for column-based layouts' },
|
|
109
|
+
{ component: 'Card', relationship: 'alternative', note: 'Use Card for content containers with structure' },
|
|
110
|
+
],
|
|
111
|
+
|
|
112
|
+
contract: {
|
|
113
|
+
propsSummary: [
|
|
114
|
+
'as: div|section|article|... - HTML element',
|
|
115
|
+
'padding: none|xs|sm|md|lg|xl - all-sides padding',
|
|
116
|
+
'paddingX/paddingY: directional padding overrides',
|
|
117
|
+
'margin: none|xs|sm|md|lg|xl|auto - margin',
|
|
118
|
+
'background: none|primary|secondary|tertiary|elevated',
|
|
119
|
+
'rounded: none|sm|md|lg|full - border radius',
|
|
120
|
+
'border: boolean - show border',
|
|
121
|
+
],
|
|
122
|
+
scenarioTags: [
|
|
123
|
+
'layout.container',
|
|
124
|
+
'spacing.wrapper',
|
|
125
|
+
'structure.section',
|
|
126
|
+
],
|
|
127
|
+
a11yRules: ['A11Y_SEMANTIC_HTML'],
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
variants: [
|
|
131
|
+
{
|
|
132
|
+
name: 'Default',
|
|
133
|
+
description: 'Basic box with padding',
|
|
134
|
+
render: () => (
|
|
135
|
+
<Box padding="md" background="secondary" rounded="md">
|
|
136
|
+
Content with padding and background
|
|
137
|
+
</Box>
|
|
138
|
+
),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'With Border',
|
|
142
|
+
description: 'Bordered container',
|
|
143
|
+
render: () => (
|
|
144
|
+
<Box padding="lg" border rounded="md">
|
|
145
|
+
Bordered content area
|
|
146
|
+
</Box>
|
|
147
|
+
),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'Directional Padding',
|
|
151
|
+
description: 'Different horizontal and vertical padding',
|
|
152
|
+
render: () => (
|
|
153
|
+
<Box paddingX="xl" paddingY="sm" background="tertiary" rounded="sm">
|
|
154
|
+
Wide horizontal padding, short vertical
|
|
155
|
+
</Box>
|
|
156
|
+
),
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'Centered with Auto Margin',
|
|
160
|
+
description: 'Centered content using margin auto',
|
|
161
|
+
render: () => (
|
|
162
|
+
<Box padding="md" marginX="auto" background="elevated" rounded="lg" style={{ maxWidth: '300px' }}>
|
|
163
|
+
Centered content
|
|
164
|
+
</Box>
|
|
165
|
+
),
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
|
|
3
|
+
.box {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Padding
|
|
8
|
+
.p-none { padding: 0; }
|
|
9
|
+
.p-xs { padding: var(--fui-space-1, $fui-space-1); }
|
|
10
|
+
.p-sm { padding: var(--fui-space-2, $fui-space-2); }
|
|
11
|
+
.p-md { padding: var(--fui-space-4, $fui-space-4); }
|
|
12
|
+
.p-lg { padding: var(--fui-space-6, $fui-space-6); }
|
|
13
|
+
.p-xl { padding: var(--fui-space-8, $fui-space-8); }
|
|
14
|
+
|
|
15
|
+
// Padding X
|
|
16
|
+
.px-none { padding-left: 0; padding-right: 0; }
|
|
17
|
+
.px-xs { padding-left: var(--fui-space-1, $fui-space-1); padding-right: var(--fui-space-1, $fui-space-1); }
|
|
18
|
+
.px-sm { padding-left: var(--fui-space-2, $fui-space-2); padding-right: var(--fui-space-2, $fui-space-2); }
|
|
19
|
+
.px-md { padding-left: var(--fui-space-4, $fui-space-4); padding-right: var(--fui-space-4, $fui-space-4); }
|
|
20
|
+
.px-lg { padding-left: var(--fui-space-6, $fui-space-6); padding-right: var(--fui-space-6, $fui-space-6); }
|
|
21
|
+
.px-xl { padding-left: var(--fui-space-8, $fui-space-8); padding-right: var(--fui-space-8, $fui-space-8); }
|
|
22
|
+
|
|
23
|
+
// Padding Y
|
|
24
|
+
.py-none { padding-top: 0; padding-bottom: 0; }
|
|
25
|
+
.py-xs { padding-top: var(--fui-space-1, $fui-space-1); padding-bottom: var(--fui-space-1, $fui-space-1); }
|
|
26
|
+
.py-sm { padding-top: var(--fui-space-2, $fui-space-2); padding-bottom: var(--fui-space-2, $fui-space-2); }
|
|
27
|
+
.py-md { padding-top: var(--fui-space-4, $fui-space-4); padding-bottom: var(--fui-space-4, $fui-space-4); }
|
|
28
|
+
.py-lg { padding-top: var(--fui-space-6, $fui-space-6); padding-bottom: var(--fui-space-6, $fui-space-6); }
|
|
29
|
+
.py-xl { padding-top: var(--fui-space-8, $fui-space-8); padding-bottom: var(--fui-space-8, $fui-space-8); }
|
|
30
|
+
|
|
31
|
+
// Margin
|
|
32
|
+
.m-none { margin: 0; }
|
|
33
|
+
.m-xs { margin: var(--fui-space-1, $fui-space-1); }
|
|
34
|
+
.m-sm { margin: var(--fui-space-2, $fui-space-2); }
|
|
35
|
+
.m-md { margin: var(--fui-space-4, $fui-space-4); }
|
|
36
|
+
.m-lg { margin: var(--fui-space-6, $fui-space-6); }
|
|
37
|
+
.m-xl { margin: var(--fui-space-8, $fui-space-8); }
|
|
38
|
+
.m-auto { margin: auto; }
|
|
39
|
+
|
|
40
|
+
// Margin X
|
|
41
|
+
.mx-none { margin-left: 0; margin-right: 0; }
|
|
42
|
+
.mx-xs { margin-left: var(--fui-space-1, $fui-space-1); margin-right: var(--fui-space-1, $fui-space-1); }
|
|
43
|
+
.mx-sm { margin-left: var(--fui-space-2, $fui-space-2); margin-right: var(--fui-space-2, $fui-space-2); }
|
|
44
|
+
.mx-md { margin-left: var(--fui-space-4, $fui-space-4); margin-right: var(--fui-space-4, $fui-space-4); }
|
|
45
|
+
.mx-lg { margin-left: var(--fui-space-6, $fui-space-6); margin-right: var(--fui-space-6, $fui-space-6); }
|
|
46
|
+
.mx-xl { margin-left: var(--fui-space-8, $fui-space-8); margin-right: var(--fui-space-8, $fui-space-8); }
|
|
47
|
+
.mx-auto { margin-left: auto; margin-right: auto; }
|
|
48
|
+
|
|
49
|
+
// Margin Y
|
|
50
|
+
.my-none { margin-top: 0; margin-bottom: 0; }
|
|
51
|
+
.my-xs { margin-top: var(--fui-space-1, $fui-space-1); margin-bottom: var(--fui-space-1, $fui-space-1); }
|
|
52
|
+
.my-sm { margin-top: var(--fui-space-2, $fui-space-2); margin-bottom: var(--fui-space-2, $fui-space-2); }
|
|
53
|
+
.my-md { margin-top: var(--fui-space-4, $fui-space-4); margin-bottom: var(--fui-space-4, $fui-space-4); }
|
|
54
|
+
.my-lg { margin-top: var(--fui-space-6, $fui-space-6); margin-bottom: var(--fui-space-6, $fui-space-6); }
|
|
55
|
+
.my-xl { margin-top: var(--fui-space-8, $fui-space-8); margin-bottom: var(--fui-space-8, $fui-space-8); }
|
|
56
|
+
.my-auto { margin-top: auto; margin-bottom: auto; }
|
|
57
|
+
|
|
58
|
+
// Background
|
|
59
|
+
.bg-none { background-color: transparent; }
|
|
60
|
+
.bg-primary { background-color: var(--fui-bg-primary, $fui-bg-primary); }
|
|
61
|
+
.bg-secondary { background-color: var(--fui-bg-secondary, $fui-bg-secondary); }
|
|
62
|
+
.bg-tertiary { background-color: var(--fui-bg-tertiary, $fui-bg-tertiary); }
|
|
63
|
+
.bg-elevated { background-color: var(--fui-bg-elevated, $fui-bg-elevated); }
|
|
64
|
+
|
|
65
|
+
// Border radius
|
|
66
|
+
.rounded-none { border-radius: 0; }
|
|
67
|
+
.rounded-sm { border-radius: var(--fui-radius-sm, $fui-radius-sm); }
|
|
68
|
+
.rounded-md { border-radius: var(--fui-radius-md, $fui-radius-md); }
|
|
69
|
+
.rounded-lg { border-radius: var(--fui-radius-lg, $fui-radius-lg); }
|
|
70
|
+
.rounded-full { border-radius: var(--fui-radius-full, $fui-radius-full); }
|
|
71
|
+
|
|
72
|
+
// Border
|
|
73
|
+
.border {
|
|
74
|
+
border: 1px solid var(--fui-border, $fui-border);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Display
|
|
78
|
+
.display-block { display: block; }
|
|
79
|
+
.display-inline { display: inline; }
|
|
80
|
+
.display-inline-block { display: inline-block; }
|
|
81
|
+
.display-flex { display: flex; }
|
|
82
|
+
.display-inline-flex { display: inline-flex; }
|
|
83
|
+
.display-grid { display: grid; }
|
|
84
|
+
.display-none { display: none; }
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styles from './Box.module.scss';
|
|
3
|
+
import '../../styles/globals.scss';
|
|
4
|
+
|
|
5
|
+
export interface BoxProps {
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
/** HTML element to render */
|
|
8
|
+
as?: 'div' | 'section' | 'article' | 'aside' | 'main' | 'header' | 'footer' | 'nav' | 'span';
|
|
9
|
+
/** Padding on all sides */
|
|
10
|
+
padding?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
11
|
+
/** Horizontal padding (overrides padding) */
|
|
12
|
+
paddingX?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
13
|
+
/** Vertical padding (overrides padding) */
|
|
14
|
+
paddingY?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
15
|
+
/** Margin on all sides */
|
|
16
|
+
margin?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'auto';
|
|
17
|
+
/** Horizontal margin (overrides margin) */
|
|
18
|
+
marginX?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'auto';
|
|
19
|
+
/** Vertical margin (overrides margin) */
|
|
20
|
+
marginY?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'auto';
|
|
21
|
+
/** Background color */
|
|
22
|
+
background?: 'none' | 'primary' | 'secondary' | 'tertiary' | 'elevated';
|
|
23
|
+
/** Border radius */
|
|
24
|
+
rounded?: 'none' | 'sm' | 'md' | 'lg' | 'full';
|
|
25
|
+
/** Border */
|
|
26
|
+
border?: boolean;
|
|
27
|
+
/** Display type */
|
|
28
|
+
display?: 'block' | 'inline' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none';
|
|
29
|
+
/** Additional class name */
|
|
30
|
+
className?: string;
|
|
31
|
+
/** Inline styles */
|
|
32
|
+
style?: React.CSSProperties;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const Box = React.forwardRef<HTMLElement, BoxProps>(
|
|
36
|
+
function Box(
|
|
37
|
+
{
|
|
38
|
+
children,
|
|
39
|
+
as: Component = 'div',
|
|
40
|
+
padding,
|
|
41
|
+
paddingX,
|
|
42
|
+
paddingY,
|
|
43
|
+
margin,
|
|
44
|
+
marginX,
|
|
45
|
+
marginY,
|
|
46
|
+
background,
|
|
47
|
+
rounded,
|
|
48
|
+
border,
|
|
49
|
+
display,
|
|
50
|
+
className,
|
|
51
|
+
style,
|
|
52
|
+
},
|
|
53
|
+
ref
|
|
54
|
+
) {
|
|
55
|
+
const classes = [
|
|
56
|
+
styles.box,
|
|
57
|
+
padding && styles[`p-${padding}`],
|
|
58
|
+
paddingX && styles[`px-${paddingX}`],
|
|
59
|
+
paddingY && styles[`py-${paddingY}`],
|
|
60
|
+
margin && styles[`m-${margin}`],
|
|
61
|
+
marginX && styles[`mx-${marginX}`],
|
|
62
|
+
marginY && styles[`my-${marginY}`],
|
|
63
|
+
background && styles[`bg-${background}`],
|
|
64
|
+
rounded && styles[`rounded-${rounded}`],
|
|
65
|
+
border && styles.border,
|
|
66
|
+
display && styles[`display-${display}`],
|
|
67
|
+
className,
|
|
68
|
+
]
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.join(' ');
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Component ref={ref as React.Ref<never>} className={classes} style={style}>
|
|
74
|
+
{children}
|
|
75
|
+
</Component>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
);
|
|
@@ -95,3 +95,45 @@
|
|
|
95
95
|
background-color: var(--fui-color-danger-hover, $fui-color-danger-hover);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
// Icon-only button (square aspect ratio)
|
|
100
|
+
.icon {
|
|
101
|
+
aspect-ratio: 1;
|
|
102
|
+
padding: 0;
|
|
103
|
+
|
|
104
|
+
&.sm {
|
|
105
|
+
width: var(--fui-button-height-sm, $fui-button-height-sm);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
&.md {
|
|
109
|
+
width: var(--fui-button-height-md, $fui-button-height-md);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
&.lg {
|
|
113
|
+
width: var(--fui-button-height-lg, $fui-button-height-lg);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Full width
|
|
118
|
+
.fullWidth {
|
|
119
|
+
width: 100%;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================
|
|
123
|
+
// Accessibility: High Contrast Mode
|
|
124
|
+
// ============================================
|
|
125
|
+
|
|
126
|
+
@media (prefers-contrast: more) {
|
|
127
|
+
.secondary {
|
|
128
|
+
border-width: 2px;
|
|
129
|
+
border-color: var(--fui-text-primary, $fui-text-primary);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.ghost {
|
|
133
|
+
border-width: 2px;
|
|
134
|
+
|
|
135
|
+
&:hover:not(:disabled) {
|
|
136
|
+
border-color: var(--fui-text-primary, $fui-text-primary);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -4,48 +4,82 @@ import styles from './Button.module.scss';
|
|
|
4
4
|
// Import globals to ensure CSS variables are defined
|
|
5
5
|
import '../../styles/globals.scss';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
type ButtonBaseProps = {
|
|
8
8
|
children: React.ReactNode;
|
|
9
9
|
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
|
|
10
10
|
size?: 'sm' | 'md' | 'lg';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
/** Render as icon-only button (square aspect ratio) */
|
|
12
|
+
icon?: boolean;
|
|
13
|
+
/** Make button full width of container */
|
|
14
|
+
fullWidth?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Button as native button element
|
|
18
|
+
export interface ButtonAsButtonProps
|
|
19
|
+
extends ButtonBaseProps,
|
|
20
|
+
Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
|
21
|
+
as?: 'button';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Button as anchor element
|
|
25
|
+
export interface ButtonAsAnchorProps
|
|
26
|
+
extends ButtonBaseProps,
|
|
27
|
+
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> {
|
|
28
|
+
as: 'a';
|
|
15
29
|
}
|
|
16
30
|
|
|
17
|
-
export
|
|
18
|
-
function Button(
|
|
19
|
-
{
|
|
20
|
-
children,
|
|
21
|
-
variant = 'primary',
|
|
22
|
-
size = 'md',
|
|
23
|
-
disabled = false,
|
|
24
|
-
onClick,
|
|
25
|
-
type = 'button',
|
|
26
|
-
className,
|
|
27
|
-
},
|
|
28
|
-
ref
|
|
29
|
-
) {
|
|
30
|
-
const classNames = [
|
|
31
|
-
styles.button,
|
|
32
|
-
styles[size],
|
|
33
|
-
styles[variant],
|
|
34
|
-
className,
|
|
35
|
-
]
|
|
36
|
-
.filter(Boolean)
|
|
37
|
-
.join(' ');
|
|
31
|
+
export type ButtonProps = ButtonAsButtonProps | ButtonAsAnchorProps;
|
|
38
32
|
|
|
33
|
+
export const Button = React.forwardRef<
|
|
34
|
+
HTMLButtonElement | HTMLAnchorElement,
|
|
35
|
+
ButtonProps
|
|
36
|
+
>(function Button(props, ref) {
|
|
37
|
+
const {
|
|
38
|
+
children,
|
|
39
|
+
variant = 'primary',
|
|
40
|
+
size = 'md',
|
|
41
|
+
icon = false,
|
|
42
|
+
fullWidth = false,
|
|
43
|
+
className,
|
|
44
|
+
...rest
|
|
45
|
+
} = props;
|
|
46
|
+
|
|
47
|
+
const classNames = [
|
|
48
|
+
styles.button,
|
|
49
|
+
styles[size],
|
|
50
|
+
styles[variant],
|
|
51
|
+
icon && styles.icon,
|
|
52
|
+
fullWidth && styles.fullWidth,
|
|
53
|
+
className,
|
|
54
|
+
]
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.join(' ');
|
|
57
|
+
|
|
58
|
+
// Render as anchor
|
|
59
|
+
if (props.as === 'a') {
|
|
60
|
+
const { as: _as, ...anchorProps } = rest as ButtonAsAnchorProps & { as?: 'a' };
|
|
39
61
|
return (
|
|
40
|
-
<
|
|
41
|
-
ref={ref}
|
|
42
|
-
type={type}
|
|
43
|
-
disabled={disabled}
|
|
44
|
-
onClick={onClick}
|
|
62
|
+
<a
|
|
63
|
+
ref={ref as React.Ref<HTMLAnchorElement>}
|
|
45
64
|
className={classNames}
|
|
65
|
+
{...anchorProps}
|
|
46
66
|
>
|
|
47
67
|
{children}
|
|
48
|
-
</
|
|
68
|
+
</a>
|
|
49
69
|
);
|
|
50
70
|
}
|
|
51
|
-
|
|
71
|
+
|
|
72
|
+
// Render as button (default)
|
|
73
|
+
const { as: _as, ...buttonProps } = rest as ButtonAsButtonProps;
|
|
74
|
+
return (
|
|
75
|
+
<BaseButton
|
|
76
|
+
ref={ref as React.Ref<HTMLButtonElement>}
|
|
77
|
+
type={(buttonProps as ButtonAsButtonProps).type || 'button'}
|
|
78
|
+
disabled={(buttonProps as ButtonAsButtonProps).disabled || false}
|
|
79
|
+
className={classNames}
|
|
80
|
+
{...buttonProps}
|
|
81
|
+
>
|
|
82
|
+
{children}
|
|
83
|
+
</BaseButton>
|
|
84
|
+
);
|
|
85
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
|
|
3
|
+
.group {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: row;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Gap sizes
|
|
9
|
+
.gap-xs {
|
|
10
|
+
gap: var(--fui-space-1, $fui-space-1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.gap-sm {
|
|
14
|
+
gap: var(--fui-space-2, $fui-space-2);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.gap-md {
|
|
18
|
+
gap: var(--fui-space-3, $fui-space-3);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Wrap
|
|
22
|
+
.wrap {
|
|
23
|
+
flex-wrap: wrap;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Alignment
|
|
27
|
+
.align-start {
|
|
28
|
+
justify-content: flex-start;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.align-center {
|
|
32
|
+
justify-content: center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.align-end {
|
|
36
|
+
justify-content: flex-end;
|
|
37
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styles from './ButtonGroup.module.scss';
|
|
3
|
+
import '../../styles/globals.scss';
|
|
4
|
+
|
|
5
|
+
export interface ButtonGroupProps {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
gap?: 'none' | 'xs' | 'sm' | 'md';
|
|
8
|
+
wrap?: boolean;
|
|
9
|
+
align?: 'start' | 'center' | 'end';
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
|
|
14
|
+
function ButtonGroup(
|
|
15
|
+
{
|
|
16
|
+
children,
|
|
17
|
+
gap = 'sm',
|
|
18
|
+
wrap = false,
|
|
19
|
+
align,
|
|
20
|
+
className,
|
|
21
|
+
},
|
|
22
|
+
ref
|
|
23
|
+
) {
|
|
24
|
+
const classes = [
|
|
25
|
+
styles.group,
|
|
26
|
+
gap !== 'none' && styles[`gap-${gap}`],
|
|
27
|
+
wrap && styles.wrap,
|
|
28
|
+
align && styles[`align-${align}`],
|
|
29
|
+
className,
|
|
30
|
+
]
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.join(' ');
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div ref={ref} className={classes}>
|
|
36
|
+
{children}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
);
|