@neuravision/construct 1.1.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/LICENSE +21 -0
- package/README.md +227 -0
- package/components/README.md +566 -0
- package/components/_keyframes.css +23 -0
- package/components/_shared.css +120 -0
- package/components/accordion.css +124 -0
- package/components/alert.css +67 -0
- package/components/avatar.css +127 -0
- package/components/badge.css +67 -0
- package/components/banner.css +247 -0
- package/components/breadcrumbs.css +152 -0
- package/components/button.css +145 -0
- package/components/card.css +76 -0
- package/components/checkbox.css +120 -0
- package/components/chip.css +361 -0
- package/components/combobox.css +385 -0
- package/components/components.css +2 -0
- package/components/data-table.css +93 -0
- package/components/datepicker.css +268 -0
- package/components/divider.css +73 -0
- package/components/drawer.css +167 -0
- package/components/dropdown.css +401 -0
- package/components/empty-state.css +97 -0
- package/components/field.css +42 -0
- package/components/file-upload.css +111 -0
- package/components/icon.css +31 -0
- package/components/index.css +49 -0
- package/components/input.css +64 -0
- package/components/list.css +474 -0
- package/components/modal.css +164 -0
- package/components/navbar.css +587 -0
- package/components/pagination.css +131 -0
- package/components/popover.css +231 -0
- package/components/progress-bar.css +56 -0
- package/components/select-menu.css +267 -0
- package/components/select.css +30 -0
- package/components/sidebar.css +183 -0
- package/components/skeleton.css +38 -0
- package/components/skip-link.css +38 -0
- package/components/slider.css +305 -0
- package/components/spinner.css +72 -0
- package/components/switch.css +82 -0
- package/components/table.css +139 -0
- package/components/tabs.css +147 -0
- package/components/textarea.css +16 -0
- package/components/toast.css +71 -0
- package/components/toggle-group.css +196 -0
- package/components/toolbar.css +222 -0
- package/components/tooltip.css +124 -0
- package/docs/guidelines.md +141 -0
- package/foundations.css +299 -0
- package/package.json +66 -0
- package/tokens/README.md +179 -0
- package/tokens/tokens.css +434 -0
- package/tokens/tokens.js +1188 -0
- package/tokens/tokens.json +810 -0
- package/tokens/tokens.ts +1188 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/* ── Toolbar ── */
|
|
2
|
+
|
|
3
|
+
.ct-toolbar {
|
|
4
|
+
/* ── Control Variables ── */
|
|
5
|
+
--ct-toolbar-height: var(--control-height-lg);
|
|
6
|
+
--ct-toolbar-padding-x: var(--space-6);
|
|
7
|
+
--ct-toolbar-gap: var(--space-4);
|
|
8
|
+
--ct-toolbar-brand-img-height: var(--space-9);
|
|
9
|
+
--ct-toolbar-nav-gap: var(--space-1);
|
|
10
|
+
--ct-toolbar-actions-gap: var(--space-3);
|
|
11
|
+
--ct-toolbar-nav-font-size: var(--font-size-sm);
|
|
12
|
+
--ct-toolbar-nav-padding-y: var(--space-3);
|
|
13
|
+
--ct-toolbar-nav-padding-x: var(--space-4);
|
|
14
|
+
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: var(--ct-toolbar-gap);
|
|
18
|
+
height: var(--ct-toolbar-height);
|
|
19
|
+
padding: 0 var(--ct-toolbar-padding-x);
|
|
20
|
+
background: var(--color-bg-elevated);
|
|
21
|
+
border-bottom: var(--border-thin) solid var(--color-border-subtle);
|
|
22
|
+
color: var(--color-text-primary);
|
|
23
|
+
z-index: var(--z-sticky);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* ── Brand ── */
|
|
27
|
+
|
|
28
|
+
.ct-toolbar__brand {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: var(--space-3);
|
|
32
|
+
text-decoration: none;
|
|
33
|
+
color: inherit;
|
|
34
|
+
font-weight: var(--font-weight-semibold);
|
|
35
|
+
flex-shrink: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.ct-toolbar__brand img {
|
|
39
|
+
height: var(--ct-toolbar-brand-img-height);
|
|
40
|
+
width: auto;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ── Navigation ── */
|
|
44
|
+
|
|
45
|
+
.ct-toolbar__nav {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: var(--ct-toolbar-nav-gap);
|
|
49
|
+
margin: 0;
|
|
50
|
+
padding: 0;
|
|
51
|
+
list-style: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.ct-toolbar__nav-link {
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: var(--space-2);
|
|
58
|
+
padding: var(--ct-toolbar-nav-padding-y) var(--ct-toolbar-nav-padding-x);
|
|
59
|
+
border-radius: var(--radius-sm);
|
|
60
|
+
color: var(--color-text-secondary);
|
|
61
|
+
text-decoration: none;
|
|
62
|
+
font-size: var(--ct-toolbar-nav-font-size);
|
|
63
|
+
font-weight: var(--font-weight-medium);
|
|
64
|
+
white-space: nowrap;
|
|
65
|
+
transition: background var(--duration-fast) var(--easing-standard),
|
|
66
|
+
color var(--duration-fast) var(--easing-standard);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@media (hover: hover) {
|
|
70
|
+
.ct-toolbar__nav-link:hover {
|
|
71
|
+
background: var(--color-bg-muted);
|
|
72
|
+
color: var(--color-text-primary);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.ct-toolbar__nav-link:focus-visible {
|
|
77
|
+
outline: var(--border-medium) solid var(--color-focus-ring);
|
|
78
|
+
outline-offset: var(--border-medium);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.ct-toolbar__nav-link--active,
|
|
82
|
+
.ct-toolbar__nav-link[aria-current='page'] {
|
|
83
|
+
background: var(--color-bg-muted);
|
|
84
|
+
color: var(--color-brand-primary);
|
|
85
|
+
font-weight: var(--font-weight-semibold);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* ── Spacer ── */
|
|
89
|
+
|
|
90
|
+
.ct-toolbar__spacer {
|
|
91
|
+
flex: 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ── Actions ── */
|
|
95
|
+
|
|
96
|
+
.ct-toolbar__actions {
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
gap: var(--ct-toolbar-actions-gap);
|
|
100
|
+
margin-inline-start: auto;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ── Groups (role="group" within role="toolbar") ── */
|
|
104
|
+
|
|
105
|
+
.ct-toolbar__group {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
gap: var(--space-2);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* ── Separator (visual divider between groups) ── */
|
|
112
|
+
|
|
113
|
+
.ct-toolbar__separator {
|
|
114
|
+
width: var(--border-thin);
|
|
115
|
+
height: var(--space-6);
|
|
116
|
+
background: var(--color-border-subtle);
|
|
117
|
+
flex-shrink: 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ── Size: Small ── */
|
|
121
|
+
|
|
122
|
+
.ct-toolbar--sm {
|
|
123
|
+
--ct-toolbar-height: var(--control-height-md);
|
|
124
|
+
--ct-toolbar-padding-x: var(--space-5);
|
|
125
|
+
--ct-toolbar-brand-img-height: var(--space-8);
|
|
126
|
+
--ct-toolbar-nav-font-size: var(--font-size-xs);
|
|
127
|
+
--ct-toolbar-nav-padding-y: var(--space-2);
|
|
128
|
+
--ct-toolbar-nav-padding-x: var(--space-3);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ── Size: Large ── */
|
|
132
|
+
|
|
133
|
+
.ct-toolbar--lg {
|
|
134
|
+
--ct-toolbar-height: var(--space-12);
|
|
135
|
+
--ct-toolbar-padding-x: var(--space-8);
|
|
136
|
+
--ct-toolbar-brand-img-height: var(--space-10);
|
|
137
|
+
--ct-toolbar-nav-font-size: var(--font-size-md);
|
|
138
|
+
--ct-toolbar-nav-padding-y: var(--space-4);
|
|
139
|
+
--ct-toolbar-nav-padding-x: var(--space-5);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* ── Dense ── */
|
|
143
|
+
|
|
144
|
+
.ct-toolbar--dense {
|
|
145
|
+
--ct-toolbar-height: var(--control-height-sm);
|
|
146
|
+
--ct-toolbar-padding-x: var(--space-4);
|
|
147
|
+
--ct-toolbar-brand-img-height: var(--space-7);
|
|
148
|
+
--ct-toolbar-nav-font-size: var(--font-size-xs);
|
|
149
|
+
--ct-toolbar-nav-padding-y: var(--space-1);
|
|
150
|
+
--ct-toolbar-nav-padding-x: var(--space-3);
|
|
151
|
+
--ct-toolbar-gap: var(--space-3);
|
|
152
|
+
--ct-toolbar-nav-gap: var(--space-0);
|
|
153
|
+
--ct-toolbar-actions-gap: var(--space-2);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* ── Sticky ── */
|
|
157
|
+
|
|
158
|
+
.ct-toolbar--sticky {
|
|
159
|
+
position: sticky;
|
|
160
|
+
inset-block-start: 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ── Borderless ── */
|
|
164
|
+
|
|
165
|
+
.ct-toolbar--borderless {
|
|
166
|
+
border-bottom: none;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* ── Secondary ── */
|
|
170
|
+
|
|
171
|
+
.ct-toolbar--secondary {
|
|
172
|
+
background: var(--color-bg-surface);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.ct-toolbar--secondary .ct-toolbar__nav-link--active,
|
|
176
|
+
.ct-toolbar--secondary .ct-toolbar__nav-link[aria-current='page'] {
|
|
177
|
+
background: var(--color-bg-elevated);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* ── Mobile Trigger ── */
|
|
181
|
+
|
|
182
|
+
.ct-toolbar__mobile-trigger {
|
|
183
|
+
display: none;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@media (max-width: 899px) { /* < md breakpoint (900px) */
|
|
187
|
+
.ct-toolbar {
|
|
188
|
+
position: relative;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.ct-toolbar__nav {
|
|
192
|
+
display: none;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.ct-toolbar__mobile-trigger {
|
|
196
|
+
display: inline-flex;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.ct-toolbar[data-mobile-nav='open'] .ct-toolbar__nav {
|
|
200
|
+
display: flex;
|
|
201
|
+
flex-direction: column;
|
|
202
|
+
align-items: stretch;
|
|
203
|
+
position: absolute;
|
|
204
|
+
inset-block-start: 100%;
|
|
205
|
+
inset-inline-start: 0;
|
|
206
|
+
inset-inline-end: 0;
|
|
207
|
+
background: var(--color-bg-elevated);
|
|
208
|
+
border-bottom: var(--border-thin) solid var(--color-border-subtle);
|
|
209
|
+
padding: var(--space-4);
|
|
210
|
+
gap: var(--space-2);
|
|
211
|
+
z-index: var(--z-dropdown);
|
|
212
|
+
box-shadow: var(--shadow-md);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* ── Reduced Motion ── */
|
|
217
|
+
|
|
218
|
+
@media (prefers-reduced-motion: reduce) {
|
|
219
|
+
.ct-toolbar__nav-link {
|
|
220
|
+
transition: none;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
.ct-tooltip {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.ct-tooltip__content {
|
|
7
|
+
--_tt-offset: calc(100% + var(--space-2));
|
|
8
|
+
--_tt-origin-x: 0px;
|
|
9
|
+
--_tt-origin-y: 0px;
|
|
10
|
+
--_tt-slide-dir-x: 0;
|
|
11
|
+
--_tt-slide-dir-y: 0;
|
|
12
|
+
--_tt-open: 0;
|
|
13
|
+
|
|
14
|
+
position: absolute;
|
|
15
|
+
z-index: var(--z-tooltip);
|
|
16
|
+
width: max-content;
|
|
17
|
+
max-width: min(var(--ct-tooltip-max-width), 90vw);
|
|
18
|
+
padding: var(--space-2) var(--space-3);
|
|
19
|
+
background: var(--ct-tooltip-bg);
|
|
20
|
+
color: var(--ct-tooltip-color);
|
|
21
|
+
border-radius: var(--ct-tooltip-radius);
|
|
22
|
+
box-shadow: var(--ct-tooltip-shadow);
|
|
23
|
+
font-size: var(--font-size-xs);
|
|
24
|
+
line-height: var(--line-height-xs);
|
|
25
|
+
white-space: normal;
|
|
26
|
+
overflow-wrap: anywhere;
|
|
27
|
+
opacity: 0;
|
|
28
|
+
visibility: hidden;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
transform: translate(
|
|
31
|
+
calc(var(--_tt-origin-x) + var(--_tt-slide-dir-x) * 4px * (1 - var(--_tt-open))),
|
|
32
|
+
calc(var(--_tt-origin-y) + var(--_tt-slide-dir-y) * 4px * (1 - var(--_tt-open)))
|
|
33
|
+
);
|
|
34
|
+
transition: opacity var(--duration-fast) var(--easing-standard),
|
|
35
|
+
transform var(--duration-fast) var(--easing-standard),
|
|
36
|
+
visibility 0s linear var(--duration-fast);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.ct-tooltip[data-state='open'] .ct-tooltip__content,
|
|
40
|
+
.ct-tooltip:focus-within .ct-tooltip__content {
|
|
41
|
+
--_tt-open: 1;
|
|
42
|
+
opacity: 1;
|
|
43
|
+
visibility: visible;
|
|
44
|
+
transition: opacity var(--duration-fast) var(--easing-standard),
|
|
45
|
+
transform var(--duration-fast) var(--easing-standard),
|
|
46
|
+
visibility 0s linear 0s;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@media (hover: hover) {
|
|
50
|
+
.ct-tooltip:hover .ct-tooltip__content {
|
|
51
|
+
--_tt-open: 1;
|
|
52
|
+
opacity: 1;
|
|
53
|
+
visibility: visible;
|
|
54
|
+
transition: opacity var(--duration-fast) var(--easing-standard),
|
|
55
|
+
transform var(--duration-fast) var(--easing-standard),
|
|
56
|
+
visibility 0s linear 0s;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Side positioning (default center alignment) */
|
|
61
|
+
|
|
62
|
+
.ct-tooltip[data-side='top'] .ct-tooltip__content {
|
|
63
|
+
inset-block-end: var(--_tt-offset);
|
|
64
|
+
inset-inline-start: 50%;
|
|
65
|
+
--_tt-origin-x: -50%;
|
|
66
|
+
--_tt-slide-dir-y: -1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.ct-tooltip[data-side='bottom'] .ct-tooltip__content {
|
|
70
|
+
inset-block-start: var(--_tt-offset);
|
|
71
|
+
inset-inline-start: 50%;
|
|
72
|
+
--_tt-origin-x: -50%;
|
|
73
|
+
--_tt-slide-dir-y: 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.ct-tooltip[data-side='left'] .ct-tooltip__content {
|
|
77
|
+
inset-inline-end: var(--_tt-offset);
|
|
78
|
+
inset-block-start: 50%;
|
|
79
|
+
--_tt-origin-y: -50%;
|
|
80
|
+
--_tt-slide-dir-x: -1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ct-tooltip[data-side='right'] .ct-tooltip__content {
|
|
84
|
+
inset-inline-start: var(--_tt-offset);
|
|
85
|
+
inset-block-start: 50%;
|
|
86
|
+
--_tt-origin-y: -50%;
|
|
87
|
+
--_tt-slide-dir-x: 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Cross-axis alignment: start */
|
|
91
|
+
|
|
92
|
+
.ct-tooltip[data-align='start'][data-side='top'] .ct-tooltip__content,
|
|
93
|
+
.ct-tooltip[data-align='start'][data-side='bottom'] .ct-tooltip__content {
|
|
94
|
+
inset-inline-start: 0;
|
|
95
|
+
--_tt-origin-x: 0px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ct-tooltip[data-align='start'][data-side='left'] .ct-tooltip__content,
|
|
99
|
+
.ct-tooltip[data-align='start'][data-side='right'] .ct-tooltip__content {
|
|
100
|
+
inset-block-start: 0;
|
|
101
|
+
--_tt-origin-y: 0px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Cross-axis alignment: end */
|
|
105
|
+
|
|
106
|
+
.ct-tooltip[data-align='end'][data-side='top'] .ct-tooltip__content,
|
|
107
|
+
.ct-tooltip[data-align='end'][data-side='bottom'] .ct-tooltip__content {
|
|
108
|
+
inset-inline-start: auto;
|
|
109
|
+
inset-inline-end: 0;
|
|
110
|
+
--_tt-origin-x: 0px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.ct-tooltip[data-align='end'][data-side='left'] .ct-tooltip__content,
|
|
114
|
+
.ct-tooltip[data-align='end'][data-side='right'] .ct-tooltip__content {
|
|
115
|
+
inset-block-start: auto;
|
|
116
|
+
inset-block-end: 0;
|
|
117
|
+
--_tt-origin-y: 0px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@media (prefers-reduced-motion: reduce) {
|
|
121
|
+
.ct-tooltip__content {
|
|
122
|
+
transition: none;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Construct Design Guidelines
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
A consistent, accessible, professional design system for modern web applications built with Angular, React, Svelte, or vanilla HTML/CSS.
|
|
6
|
+
|
|
7
|
+
**Accessibility is Priority #1.**
|
|
8
|
+
|
|
9
|
+
## Do / Don't
|
|
10
|
+
|
|
11
|
+
### Do
|
|
12
|
+
|
|
13
|
+
- Use semantic HTML (`button`, `input`, `label`, `table`, `nav`)
|
|
14
|
+
- Use design tokens for colors, spacing, typography, and border radius
|
|
15
|
+
- Maintain clear state logic (hover, focus, active, disabled, error)
|
|
16
|
+
- Use consistent sizes (sm, md, lg) for form controls and buttons
|
|
17
|
+
- Establish clear hierarchies (Primary vs. Secondary actions)
|
|
18
|
+
- Test all components with keyboard navigation (Tab, Shift+Tab, Enter, Space)
|
|
19
|
+
|
|
20
|
+
### Don't
|
|
21
|
+
|
|
22
|
+
- Don't rely on color alone for status information without text or icons
|
|
23
|
+
- Don't create custom controls without ARIA roles and keyboard support
|
|
24
|
+
- Don't use fixed pixel layouts for content that can grow
|
|
25
|
+
- Don't use low-contrast text or disabled states that are unreadable
|
|
26
|
+
- Don't add unnecessary animations for critical actions
|
|
27
|
+
|
|
28
|
+
## Accessibility Rules (Minimum)
|
|
29
|
+
|
|
30
|
+
- **Visible Focus**: All interactive elements must have visible focus indication
|
|
31
|
+
- **Labels**: Every form input must have a label (explicit `<label>` or `aria-label`)
|
|
32
|
+
- **Keyboard Navigation**: Full support for dropdowns, tabs, modals, tooltips, datepickers
|
|
33
|
+
- **ARIA**: Use ARIA attributes only where semantic HTML isn't sufficient
|
|
34
|
+
- **Links**: Links must be distinguishable without color alone (e.g., underline)
|
|
35
|
+
- **Contrast**: Text, icons, and focus rings must meet WCAG AA standards
|
|
36
|
+
- **Themes**: Support light, dark, and high-contrast modes; respect user preferences
|
|
37
|
+
- **Disabled States**: Must be recognizable, but content should remain readable
|
|
38
|
+
- **Motion**: Respect `prefers-reduced-motion` for animations
|
|
39
|
+
- **Live Regions**: Use `aria-live` for toasts and status messages
|
|
40
|
+
- **Modal**: Requires `aria-modal`, `role="dialog"`, and focus trap
|
|
41
|
+
|
|
42
|
+
## Keyboard Patterns for Composite Components
|
|
43
|
+
|
|
44
|
+
### Tabs
|
|
45
|
+
- **Arrow keys**: Move focus between tabs
|
|
46
|
+
- **Home/End**: Jump to first/last tab
|
|
47
|
+
- **Enter/Space**: Activate tab
|
|
48
|
+
- **Implementation**: Use roving tabindex (active tab `tabindex="0"`, others `tabindex="-1"`)
|
|
49
|
+
|
|
50
|
+
### Dropdown (Action-List)
|
|
51
|
+
- **Trigger**: Uses `aria-expanded` and `aria-controls`
|
|
52
|
+
- **Open**: Focus moves to first item
|
|
53
|
+
- **Esc**: Closes and returns focus to trigger
|
|
54
|
+
- **Items**: Normal buttons/links in tab order
|
|
55
|
+
|
|
56
|
+
### Dropdown (Role=menu)
|
|
57
|
+
- **Only use if implementing**: Arrow-key navigation, Home/End, typeahead, and roving tabindex
|
|
58
|
+
- Most dropdowns should use Action-List pattern instead
|
|
59
|
+
|
|
60
|
+
### Datepicker
|
|
61
|
+
- **Arrow keys**: Move by day in calendar grid
|
|
62
|
+
- **PageUp/PageDown**: Switch months
|
|
63
|
+
- **Home/End**: Jump to start/end of week
|
|
64
|
+
- **Enter/Space**: Select date
|
|
65
|
+
- **Esc**: Close and return focus to trigger
|
|
66
|
+
|
|
67
|
+
### Modal
|
|
68
|
+
- **Focus trap**: Focus stays within modal
|
|
69
|
+
- **Initial focus**: Set to first focusable element or designated element
|
|
70
|
+
- **Esc**: Close modal
|
|
71
|
+
- **Close**: Return focus to trigger element
|
|
72
|
+
|
|
73
|
+
### Drawer
|
|
74
|
+
- **Focus trap**: Focus stays within drawer panel
|
|
75
|
+
- **Initial focus**: First focusable element or designated element
|
|
76
|
+
- **Esc**: Close drawer
|
|
77
|
+
- **Close**: Return focus to trigger element
|
|
78
|
+
- **Backdrop**: Click closes drawer
|
|
79
|
+
|
|
80
|
+
### Tooltip
|
|
81
|
+
- **Open**: On hover and focus
|
|
82
|
+
- **Close**: On blur and Esc
|
|
83
|
+
- **Role**: `role="tooltip"` with `aria-describedby`
|
|
84
|
+
|
|
85
|
+
## Component States
|
|
86
|
+
|
|
87
|
+
Use these attributes for state management:
|
|
88
|
+
|
|
89
|
+
- **error**: `aria-invalid="true"` + visible error text
|
|
90
|
+
- **disabled**: `disabled` or `aria-disabled="true"`
|
|
91
|
+
- **selected**: `aria-selected="true"`
|
|
92
|
+
- **current**: `aria-current="page"`
|
|
93
|
+
- **expanded**: `aria-expanded="true|false"`
|
|
94
|
+
|
|
95
|
+
## Breakpoints & Media Queries
|
|
96
|
+
|
|
97
|
+
Construct defines breakpoint tokens in `tokens/primitives.json`:
|
|
98
|
+
|
|
99
|
+
| Token | Value | Typical use |
|
|
100
|
+
|-------|-------|-------------|
|
|
101
|
+
| `xs` | 360px | Small phones |
|
|
102
|
+
| `sm` | 600px | Large phones / small tablets |
|
|
103
|
+
| `md` | 900px | Tablets / small laptops |
|
|
104
|
+
| `lg` | 1200px | Desktops |
|
|
105
|
+
| `xl` | 1536px | Large screens |
|
|
106
|
+
|
|
107
|
+
### Convention
|
|
108
|
+
|
|
109
|
+
CSS custom properties cannot be used inside `@media` queries. Use the raw pixel values with a reference comment:
|
|
110
|
+
|
|
111
|
+
```css
|
|
112
|
+
@media (max-width: 599px) { /* < sm breakpoint (600px) */
|
|
113
|
+
/* Mobile styles */
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@media (max-width: 899px) { /* < md breakpoint (900px) */
|
|
117
|
+
/* Tablet styles */
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Rules:**
|
|
122
|
+
- Always use `max-width: <token - 1>px` to target viewports *below* a breakpoint
|
|
123
|
+
- Always add a reference comment noting the breakpoint name and token value
|
|
124
|
+
- Only use values derived from the token system — never arbitrary pixel values
|
|
125
|
+
- Current components use two breakpoints: `< sm` (599px) and `< md` (899px)
|
|
126
|
+
|
|
127
|
+
## Fonts
|
|
128
|
+
|
|
129
|
+
- **Default**: Fonts are loaded via Google Fonts in `foundations.css` (Sora, Source Sans 3, JetBrains Mono)
|
|
130
|
+
- **Self-hosting**: For CSP or privacy requirements, replace Google Fonts import with local `@font-face` rules
|
|
131
|
+
- **Weights**: Ensure 400, 500, 600, and 700 are available
|
|
132
|
+
|
|
133
|
+
## Governance
|
|
134
|
+
|
|
135
|
+
- **New components require**: Design tokens, state definitions, accessibility notes, Storybook story
|
|
136
|
+
- **Before merge**: Visual QA + keyboard testing + contrast check
|
|
137
|
+
- **Review process**: At least one accessibility review for new interactive components
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
**Construct** - Build accessible design constructs
|