@neuravision/construct 2.0.3 → 2.1.2
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/CHANGELOG.md +318 -0
- package/README.md +158 -149
- package/behaviors/combobox.js +486 -0
- package/behaviors/drawer.js +18 -0
- package/behaviors/dropdown.js +310 -0
- package/behaviors/index.d.ts +335 -0
- package/behaviors/index.js +9 -0
- package/behaviors/internal/dialog-controller.js +421 -0
- package/behaviors/internal/dom.js +408 -0
- package/behaviors/internal/overlay.js +241 -0
- package/behaviors/internal/popup-layer.js +190 -0
- package/behaviors/modal.js +17 -0
- package/behaviors/popover.js +278 -0
- package/behaviors/select-menu.js +344 -0
- package/behaviors/tabs.js +241 -0
- package/behaviors/toggle-group.js +266 -0
- package/behaviors/tooltip.js +257 -0
- package/components/README.md +319 -83
- package/components/_shared.css +22 -10
- package/components/_shell-shared.css +20 -4
- package/components/accordion.css +8 -0
- package/components/app-shell-v2.css +28 -14
- package/components/app-shell.css +30 -14
- package/components/badge.css +15 -0
- package/components/banner.css +32 -1
- package/components/button.css +103 -15
- package/components/card.css +67 -8
- package/components/chart.css +26 -0
- package/components/checkbox.css +35 -2
- package/components/chip.css +30 -2
- package/components/combobox.css +21 -0
- package/components/datepicker.css +58 -9
- package/components/datum.css +277 -0
- package/components/dropdown.css +23 -3
- package/components/field.css +6 -4
- package/components/file-upload.css +1 -1
- package/components/index.css +5 -0
- package/components/list.css +28 -2
- package/components/meter.css +182 -0
- package/components/navbar.css +102 -4
- package/components/pagination.css +18 -0
- package/components/progress-bar.css +22 -1
- package/components/select-menu.css +8 -0
- package/components/select.css +8 -0
- package/components/sidebar.css +15 -2
- package/components/skip-link.css +6 -2
- package/components/slider.css +7 -1
- package/components/status.css +107 -0
- package/components/switch.css +66 -5
- package/components/table.css +53 -4
- package/components/tabs.css +55 -3
- package/components/toast.css +10 -1
- package/components/toggle-group.css +16 -5
- package/components/toolbar.css +17 -1
- package/components/tooltip.css +4 -0
- package/components/tree.css +30 -0
- package/docs/architecture.md +79 -0
- package/docs/behaviors.md +122 -0
- package/docs/fonts.md +70 -0
- package/docs/governance.md +85 -0
- package/docs/guidelines.md +72 -25
- package/examples/react/README.md +20 -0
- package/examples/react/SettingsModal.jsx +66 -0
- package/examples/react/construct.css +5 -0
- package/fonts.css +10 -0
- package/foundations.css +14 -1
- package/package.json +76 -21
- package/schemas/components.schema.json +122 -0
- package/schemas/examples/theme.example.json +27 -0
- package/schemas/primitives.schema.json +135 -0
- package/schemas/semantic.schema.json +224 -0
- package/schemas/theme.schema.json +63 -0
- package/scripts/build-tokens.mjs +1499 -0
- package/scripts/construct-theme.mjs +43 -0
- package/scripts/validate-tokens.mjs +25 -0
- package/standalone/accordion.css +3 -0
- package/standalone/alert.css +3 -0
- package/standalone/app-shell-v2.css +4 -0
- package/standalone/app-shell.css +4 -0
- package/standalone/avatar.css +3 -0
- package/standalone/badge.css +3 -0
- package/standalone/banner.css +3 -0
- package/standalone/breadcrumbs.css +3 -0
- package/standalone/button.css +3 -0
- package/standalone/card.css +3 -0
- package/standalone/chart.css +3 -0
- package/standalone/checkbox.css +3 -0
- package/standalone/chip.css +3 -0
- package/standalone/combobox.css +3 -0
- package/standalone/core.css +4 -0
- package/standalone/data-table.css +3 -0
- package/standalone/datepicker.css +3 -0
- package/standalone/datum.css +3 -0
- package/standalone/divider.css +3 -0
- package/standalone/drawer.css +3 -0
- package/standalone/dropdown.css +3 -0
- package/standalone/empty-state.css +3 -0
- package/standalone/field.css +3 -0
- package/standalone/file-upload.css +3 -0
- package/standalone/icon.css +3 -0
- package/standalone/input.css +3 -0
- package/standalone/list.css +3 -0
- package/standalone/meter.css +3 -0
- package/standalone/modal.css +3 -0
- package/standalone/navbar.css +3 -0
- package/standalone/pagination.css +3 -0
- package/standalone/popover.css +3 -0
- package/standalone/progress-bar.css +3 -0
- package/standalone/select-menu.css +3 -0
- package/standalone/select.css +3 -0
- package/standalone/sidebar.css +3 -0
- package/standalone/skeleton.css +3 -0
- package/standalone/skip-link.css +3 -0
- package/standalone/slider.css +3 -0
- package/standalone/spinner.css +3 -0
- package/standalone/status.css +3 -0
- package/standalone/switch.css +3 -0
- package/standalone/table.css +3 -0
- package/standalone/tabs.css +3 -0
- package/standalone/textarea.css +3 -0
- package/standalone/toast.css +3 -0
- package/standalone/toggle-group.css +3 -0
- package/standalone/toolbar.css +3 -0
- package/standalone/tooltip.css +3 -0
- package/standalone/tree.css +3 -0
- package/themes/README.md +37 -0
- package/themes/walnut.theme.json +106 -0
- package/tokens/README.md +135 -123
- package/tokens/components.json +64 -0
- package/tokens/primitives.json +268 -0
- package/tokens/semantic.dark.json +94 -0
- package/tokens/semantic.high-contrast-dark.json +94 -0
- package/tokens/semantic.high-contrast.json +94 -0
- package/tokens/semantic.light.json +179 -0
- package/tokens/tokens.css +960 -58
- package/tokens/tokens.js +729 -120
- package/tokens/tokens.json +1158 -116
- package/tokens/tokens.ts +729 -120
package/components/_shared.css
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
--ct-control-bg: var(--
|
|
3
|
-
--ct-control-bg-hover: var(--
|
|
4
|
-
--ct-control-bg-disabled: var(--
|
|
5
|
-
--ct-control-border: var(--
|
|
6
|
-
--ct-control-border-hover: var(--
|
|
7
|
-
--ct-control-border-focus: var(--
|
|
8
|
-
--ct-control-text: var(--
|
|
9
|
-
--ct-control-placeholder: var(--
|
|
10
|
-
--ct-control-radius: var(--
|
|
2
|
+
--ct-control-bg: var(--component-control-background);
|
|
3
|
+
--ct-control-bg-hover: var(--component-control-background-hover);
|
|
4
|
+
--ct-control-bg-disabled: var(--component-control-background-disabled);
|
|
5
|
+
--ct-control-border: var(--component-control-border);
|
|
6
|
+
--ct-control-border-hover: var(--component-control-border-hover);
|
|
7
|
+
--ct-control-border-focus: var(--component-control-border-focus);
|
|
8
|
+
--ct-control-text: var(--component-control-text);
|
|
9
|
+
--ct-control-placeholder: var(--component-control-placeholder);
|
|
10
|
+
--ct-control-radius: var(--component-control-radius);
|
|
11
11
|
--ct-control-transition: border-color var(--duration-fast) var(--easing-standard),
|
|
12
12
|
box-shadow var(--duration-fast) var(--easing-standard),
|
|
13
13
|
background var(--duration-fast) var(--easing-standard),
|
|
@@ -93,6 +93,9 @@
|
|
|
93
93
|
background: var(--ct-control-bg-disabled);
|
|
94
94
|
border-color: var(--color-border-subtle);
|
|
95
95
|
color: var(--color-text-muted);
|
|
96
|
+
/* Disabled is fully colour-encoded above; cancel foundations' global
|
|
97
|
+
:disabled opacity so controls are not double-dimmed. */
|
|
98
|
+
opacity: 1;
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
:where(.ct-input, .ct-select, .ct-textarea, .ct-control)[aria-invalid='true'],
|
|
@@ -106,11 +109,12 @@
|
|
|
106
109
|
outline-color: var(--color-state-danger);
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
/* ──
|
|
112
|
+
/* ── Construct 44×44 target policy (WCAG 2.2 SC 2.5.5 is AAA) ─── */
|
|
110
113
|
|
|
111
114
|
@media (pointer: coarse) {
|
|
112
115
|
:root {
|
|
113
116
|
--control-height-sm: 44px;
|
|
117
|
+
--control-height-md: 44px;
|
|
114
118
|
--control-height-calendar-day: 44px;
|
|
115
119
|
}
|
|
116
120
|
}
|
|
@@ -134,3 +138,11 @@
|
|
|
134
138
|
--ct-control-font-size: var(--font-size-lg);
|
|
135
139
|
--ct-control-line-height: var(--line-height-lg);
|
|
136
140
|
}
|
|
141
|
+
|
|
142
|
+
/* Per-file kill switch: standalone/core.css imports this file without
|
|
143
|
+
foundations.css and its global reduced-motion rule. */
|
|
144
|
+
@media (prefers-reduced-motion: reduce) {
|
|
145
|
+
:where(.ct-input, .ct-select, .ct-textarea, .ct-control) {
|
|
146
|
+
transition: none;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
/* Crosses component boundary: shells orchestrate child layout. */
|
|
4
4
|
/* ─────────────────────────────────────────────────────────────── */
|
|
5
5
|
|
|
6
|
+
/* Both layouts expose the canonical `--ct-shell-*` sizing and motion
|
|
7
|
+
controls. Their historical variable names are compatibility aliases. */
|
|
8
|
+
|
|
9
|
+
:is(.ct-app-shell, .ct-app-shell-v2) {
|
|
10
|
+
min-width: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
:is(.ct-app-shell__main, .ct-app-shell-v2__main),
|
|
14
|
+
:is(.ct-app-shell__sidebar, .ct-app-shell-v2__sidebar),
|
|
15
|
+
:is(.ct-app-shell__panel, .ct-app-shell-v2__panel) {
|
|
16
|
+
min-width: 0;
|
|
17
|
+
min-height: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
|
|
7
21
|
/* ── Collapsed: Hide Labels & Badges ── */
|
|
8
22
|
|
|
@@ -16,8 +30,8 @@
|
|
|
16
30
|
white-space: nowrap;
|
|
17
31
|
/* Collapsing: labels fade out first, then width snaps */
|
|
18
32
|
transition:
|
|
19
|
-
opacity
|
|
20
|
-
width 0ms linear
|
|
33
|
+
opacity var(--ct-shell-motion-duration) var(--ct-shell-motion-easing),
|
|
34
|
+
width 0ms linear var(--ct-shell-motion-duration);
|
|
21
35
|
}
|
|
22
36
|
|
|
23
37
|
|
|
@@ -30,7 +44,9 @@
|
|
|
30
44
|
opacity: 1;
|
|
31
45
|
width: auto;
|
|
32
46
|
/* Expanding: labels fade in after sidebar width finishes growing */
|
|
33
|
-
transition:
|
|
47
|
+
transition:
|
|
48
|
+
opacity var(--ct-shell-motion-duration) var(--ct-shell-motion-easing)
|
|
49
|
+
var(--ct-shell-motion-duration);
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
|
|
@@ -44,7 +60,7 @@
|
|
|
44
60
|
|
|
45
61
|
/* ── Tablet: Auto-Collapse to Rail ── */
|
|
46
62
|
|
|
47
|
-
@media (max-width: 1199px) {
|
|
63
|
+
@media (max-width: 1199px) { /* < lg breakpoint (1200px) */
|
|
48
64
|
:is(.ct-app-shell, .ct-app-shell-v2):not([data-sidebar-state]) .ct-nav-item__label,
|
|
49
65
|
:is(.ct-app-shell, .ct-app-shell-v2):not([data-sidebar-state]) .ct-nav-item__badge,
|
|
50
66
|
:is(.ct-app-shell, .ct-app-shell-v2):not([data-sidebar-state]) .ct-sidebar__header span,
|
package/components/accordion.css
CHANGED
|
@@ -116,6 +116,14 @@
|
|
|
116
116
|
padding: 0 var(--space-4) var(--space-4);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/* ── Construct 44×44 target policy (WCAG 2.2 SC 2.5.5 is AAA) ── */
|
|
120
|
+
|
|
121
|
+
@media (pointer: coarse) {
|
|
122
|
+
.ct-accordion__trigger {
|
|
123
|
+
min-block-size: 44px;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
119
127
|
@media (prefers-reduced-motion: reduce) {
|
|
120
128
|
.ct-accordion__icon,
|
|
121
129
|
.ct-accordion__item::details-content {
|
|
@@ -17,24 +17,31 @@
|
|
|
17
17
|
/* │ ╰────────╯ ╰───────────────────┴──────────────╯ │ */
|
|
18
18
|
/* ╰─────────────────────────────────────────────────────╯ */
|
|
19
19
|
/* */
|
|
20
|
-
/* Shell breakpoints:
|
|
21
|
-
/*
|
|
22
|
-
/* Tablet:
|
|
20
|
+
/* Shell breakpoints (from the public primitive scale): */
|
|
21
|
+
/* Compact: <900px (< md) */
|
|
22
|
+
/* Tablet: 900–1199px */
|
|
23
23
|
/* Desktop: >=1200px */
|
|
24
24
|
/* ─────────────────────────────────────────────────────────────── */
|
|
25
25
|
|
|
26
26
|
.ct-app-shell-v2 {
|
|
27
|
-
/* ── Public Tokens ── */
|
|
28
|
-
--ct-
|
|
29
|
-
--ct-
|
|
30
|
-
--ct-
|
|
27
|
+
/* ── Shared Public Tokens ── */
|
|
28
|
+
--ct-shell-sidebar-width: 260px;
|
|
29
|
+
--ct-shell-sidebar-rail-width: 64px;
|
|
30
|
+
--ct-shell-panel-width: 340px;
|
|
31
|
+
--ct-shell-motion-duration: 250ms;
|
|
32
|
+
--ct-shell-motion-easing: cubic-bezier(0.32, 0.72, 0, 1);
|
|
33
|
+
|
|
34
|
+
/* V2 aliases remain public for backward compatibility. */
|
|
35
|
+
--ct-v2-sidebar-width: var(--ct-shell-sidebar-width);
|
|
36
|
+
--ct-v2-sidebar-rail-width: var(--ct-shell-sidebar-rail-width);
|
|
37
|
+
--ct-v2-panel-width: var(--ct-shell-panel-width);
|
|
31
38
|
--ct-v2-gap: var(--space-4);
|
|
32
39
|
--ct-v2-radius: var(--radius-lg);
|
|
33
40
|
--ct-v2-canvas-bg: var(--color-bg-muted);
|
|
34
41
|
--ct-v2-surface-bg: var(--color-bg-canvas);
|
|
35
42
|
--ct-v2-surface-shadow: var(--shadow-xs);
|
|
36
|
-
--ct-v2-transition-duration:
|
|
37
|
-
--ct-v2-transition-easing:
|
|
43
|
+
--ct-v2-transition-duration: var(--ct-shell-motion-duration);
|
|
44
|
+
--ct-v2-transition-easing: var(--ct-shell-motion-easing);
|
|
38
45
|
|
|
39
46
|
/* ── Internal ── */
|
|
40
47
|
--_v2-sidebar-w: var(--ct-v2-sidebar-width);
|
|
@@ -163,6 +170,13 @@
|
|
|
163
170
|
background: var(--ct-v2-surface-bg);
|
|
164
171
|
}
|
|
165
172
|
|
|
173
|
+
/* The shell owns its scroll container, so it can satisfy WCAG 2.4.11
|
|
174
|
+
itself: scrolled-to content clears the sticky toolbar. Override
|
|
175
|
+
--ct-shell-scroll-offset when the toolbar height is customized. */
|
|
176
|
+
.ct-app-shell-v2__main:has(.ct-app-shell-v2__toolbar--sticky) {
|
|
177
|
+
scroll-padding-block-start: var(--ct-shell-scroll-offset, 64px);
|
|
178
|
+
}
|
|
179
|
+
|
|
166
180
|
|
|
167
181
|
/* ── Sidebar States ── */
|
|
168
182
|
|
|
@@ -362,13 +376,13 @@
|
|
|
362
376
|
|
|
363
377
|
/* Delay grid shrink on collapse so labels fade out first */
|
|
364
378
|
.ct-app-shell-v2[data-sidebar-state='collapsed'] {
|
|
365
|
-
transition-delay:
|
|
379
|
+
transition-delay: var(--ct-shell-motion-duration);
|
|
366
380
|
}
|
|
367
381
|
|
|
368
382
|
|
|
369
|
-
/* ── Responsive: Tablet (
|
|
383
|
+
/* ── Responsive: Tablet (900–1199px) ── */
|
|
370
384
|
|
|
371
|
-
@media (max-width: 1199px) {
|
|
385
|
+
@media (max-width: 1199px) { /* < lg breakpoint (1200px) */
|
|
372
386
|
/* Auto-collapse sidebar to rail when no explicit state */
|
|
373
387
|
.ct-app-shell-v2:not([data-sidebar-state]) {
|
|
374
388
|
--_v2-sidebar-w: var(--ct-v2-sidebar-rail-width);
|
|
@@ -390,9 +404,9 @@
|
|
|
390
404
|
}
|
|
391
405
|
|
|
392
406
|
|
|
393
|
-
/* ── Responsive:
|
|
407
|
+
/* ── Responsive: Compact (< md breakpoint, 900px) ── */
|
|
394
408
|
|
|
395
|
-
@media (max-width:
|
|
409
|
+
@media (max-width: 899px) { /* < md breakpoint (900px) */
|
|
396
410
|
.ct-app-shell-v2 {
|
|
397
411
|
--ct-v2-gap: var(--space-3);
|
|
398
412
|
}
|
package/components/app-shell.css
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
1
|
-
/* ── App Shell
|
|
1
|
+
/* ── App Shell (compatibility layout) ─────────────────────────── */
|
|
2
2
|
/* CSS Grid layout shell that orchestrates Navbar, Sidebar, */
|
|
3
3
|
/* Main content, optional Panel, and optional Footer. */
|
|
4
|
+
/* New applications should prefer App Shell V2. `.ct-app-shell` */
|
|
5
|
+
/* remains supported as the non-floating compatibility contract. */
|
|
4
6
|
/* */
|
|
5
|
-
/* Shell breakpoints (
|
|
6
|
-
/*
|
|
7
|
-
/* Tablet:
|
|
7
|
+
/* Shell breakpoints (from the public primitive scale): */
|
|
8
|
+
/* Compact: <900px (< md) */
|
|
9
|
+
/* Tablet: 900–1199px */
|
|
8
10
|
/* Desktop: 1200–1439px */
|
|
9
11
|
/* Wide: >=1440px */
|
|
10
12
|
/* ────────────────────────────────────────────────────────────── */
|
|
11
13
|
|
|
12
14
|
.ct-app-shell {
|
|
13
|
-
/* ── Public Tokens ── */
|
|
14
|
-
--ct-sidebar-width: 260px;
|
|
15
|
-
--ct-sidebar-rail-width: 56px;
|
|
16
|
-
--ct-panel-width: 320px;
|
|
15
|
+
/* ── Shared Public Tokens ── */
|
|
16
|
+
--ct-shell-sidebar-width: 260px;
|
|
17
|
+
--ct-shell-sidebar-rail-width: 56px;
|
|
18
|
+
--ct-shell-panel-width: 320px;
|
|
19
|
+
--ct-shell-motion-duration: 200ms;
|
|
20
|
+
--ct-shell-motion-easing: ease-out;
|
|
21
|
+
|
|
22
|
+
/* Legacy aliases remain public for backward compatibility. */
|
|
23
|
+
--ct-sidebar-width: var(--ct-shell-sidebar-width);
|
|
24
|
+
--ct-sidebar-rail-width: var(--ct-shell-sidebar-rail-width);
|
|
25
|
+
--ct-panel-width: var(--ct-shell-panel-width);
|
|
17
26
|
--ct-header-height: auto;
|
|
18
27
|
--ct-footer-height: auto;
|
|
19
|
-
--ct-shell-transition-duration:
|
|
20
|
-
--ct-shell-transition-easing:
|
|
28
|
+
--ct-shell-transition-duration: var(--ct-shell-motion-duration);
|
|
29
|
+
--ct-shell-transition-easing: var(--ct-shell-motion-easing);
|
|
21
30
|
|
|
22
31
|
/* ── Internal Variables ── */
|
|
23
32
|
--_sidebar-width: var(--ct-sidebar-width);
|
|
@@ -221,6 +230,13 @@
|
|
|
221
230
|
background: var(--color-bg-canvas);
|
|
222
231
|
}
|
|
223
232
|
|
|
233
|
+
/* The shell owns its scroll container, so it can satisfy WCAG 2.4.11
|
|
234
|
+
itself: scrolled-to content clears the sticky page header. Override
|
|
235
|
+
--ct-shell-scroll-offset when the header height is customized. */
|
|
236
|
+
.ct-app-shell__main:has(.ct-app-shell__page-header--sticky) {
|
|
237
|
+
scroll-padding-block-start: var(--ct-shell-scroll-offset, 64px);
|
|
238
|
+
}
|
|
239
|
+
|
|
224
240
|
|
|
225
241
|
|
|
226
242
|
/* ── Backdrop (Mobile Sidebar Overlay) ── */
|
|
@@ -249,7 +265,7 @@
|
|
|
249
265
|
|
|
250
266
|
/* Collapsing: delay grid shrink so labels fade out first */
|
|
251
267
|
.ct-app-shell[data-sidebar-state='collapsed'] {
|
|
252
|
-
transition-delay:
|
|
268
|
+
transition-delay: var(--ct-shell-motion-duration);
|
|
253
269
|
}
|
|
254
270
|
|
|
255
271
|
|
|
@@ -260,7 +276,7 @@
|
|
|
260
276
|
}
|
|
261
277
|
|
|
262
278
|
|
|
263
|
-
/* ── Responsive: Tablet (
|
|
279
|
+
/* ── Responsive: Tablet (900–1199px) ── */
|
|
264
280
|
|
|
265
281
|
@media (max-width: 1199px) { /* < lg breakpoint (1200px) */
|
|
266
282
|
/* Auto-collapse sidebar to rail when no explicit state is set */
|
|
@@ -286,9 +302,9 @@
|
|
|
286
302
|
}
|
|
287
303
|
|
|
288
304
|
|
|
289
|
-
/* ── Responsive:
|
|
305
|
+
/* ── Responsive: Compact (< md breakpoint, 900px) ── */
|
|
290
306
|
|
|
291
|
-
@media (max-width:
|
|
307
|
+
@media (max-width: 899px) { /* < md breakpoint (900px) */
|
|
292
308
|
/* Auto-hide sidebar when no explicit state is set */
|
|
293
309
|
.ct-app-shell:not([data-sidebar-state]) {
|
|
294
310
|
--_sidebar-width: 0px;
|
package/components/badge.css
CHANGED
|
@@ -38,6 +38,21 @@
|
|
|
38
38
|
background: var(--ct-badge-accent);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/* Explicit neutral alias for generated/conditional variant class lists. */
|
|
42
|
+
.ct-badge--neutral {
|
|
43
|
+
--ct-badge-bg: var(--color-bg-muted);
|
|
44
|
+
--ct-badge-border: var(--color-border-subtle);
|
|
45
|
+
--ct-badge-color: var(--color-text-secondary);
|
|
46
|
+
--ct-badge-accent: var(--ct-badge-color);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ct-badge--sm {
|
|
50
|
+
gap: var(--space-1);
|
|
51
|
+
padding: 0 var(--space-2);
|
|
52
|
+
font-size: var(--font-size-2xs);
|
|
53
|
+
line-height: var(--line-height-xs);
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
.ct-badge--info {
|
|
42
57
|
--ct-badge-bg: var(--color-state-info-surface);
|
|
43
58
|
--ct-badge-border: var(--color-state-info-border);
|
package/components/banner.css
CHANGED
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
font-size: var(--font-size-sm);
|
|
27
27
|
line-height: var(--line-height-sm);
|
|
28
28
|
transition: opacity var(--duration-normal) var(--easing-standard),
|
|
29
|
-
transform var(--duration-normal) var(--easing-standard)
|
|
29
|
+
transform var(--duration-normal) var(--easing-standard),
|
|
30
|
+
visibility 0s linear 0s;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/* ---- Sub-elements ---- */
|
|
@@ -159,6 +160,8 @@
|
|
|
159
160
|
|
|
160
161
|
/* ---- Positions ---- */
|
|
161
162
|
|
|
163
|
+
/* Consumers: set --ct-scroll-offset-top on :root (see foundations.css) so
|
|
164
|
+
scrolled-to content is not obscured behind the banner (WCAG 2.4.11). */
|
|
162
165
|
.ct-banner--fixed-top {
|
|
163
166
|
position: fixed;
|
|
164
167
|
inset-block-start: 0;
|
|
@@ -167,6 +170,8 @@
|
|
|
167
170
|
border-radius: 0;
|
|
168
171
|
}
|
|
169
172
|
|
|
173
|
+
/* Consumers: set --ct-scroll-offset-bottom on :root (see foundations.css) so
|
|
174
|
+
scrolled-to content is not obscured behind the banner (WCAG 2.4.11). */
|
|
170
175
|
.ct-banner--fixed-bottom {
|
|
171
176
|
position: fixed;
|
|
172
177
|
inset-block-end: 0;
|
|
@@ -175,6 +180,8 @@
|
|
|
175
180
|
border-radius: 0;
|
|
176
181
|
}
|
|
177
182
|
|
|
183
|
+
/* Consumers: set --ct-scroll-offset-top on :root (see foundations.css) so
|
|
184
|
+
scrolled-to content is not obscured behind the banner (WCAG 2.4.11). */
|
|
178
185
|
.ct-banner--sticky {
|
|
179
186
|
position: sticky;
|
|
180
187
|
inset-block-start: 0;
|
|
@@ -211,9 +218,16 @@
|
|
|
211
218
|
|
|
212
219
|
/* ---- State ---- */
|
|
213
220
|
|
|
221
|
+
/* Delayed visibility (drawer.css pattern) removes the faded-out banner
|
|
222
|
+
from the accessibility tree and tab order once the fade completes;
|
|
223
|
+
reopening restores it instantly via the base transition. */
|
|
214
224
|
.ct-banner[data-state='closed'] {
|
|
215
225
|
opacity: 0;
|
|
226
|
+
visibility: hidden;
|
|
216
227
|
pointer-events: none;
|
|
228
|
+
transition: opacity var(--duration-normal) var(--easing-standard),
|
|
229
|
+
transform var(--duration-normal) var(--easing-standard),
|
|
230
|
+
visibility 0s linear var(--duration-normal);
|
|
217
231
|
}
|
|
218
232
|
|
|
219
233
|
.ct-banner--fixed-top[data-state='closed'],
|
|
@@ -237,6 +251,23 @@
|
|
|
237
251
|
}
|
|
238
252
|
}
|
|
239
253
|
|
|
254
|
+
/* ---- Construct 44×44 target policy (WCAG 2.2 SC 2.5.5 is AAA) ---- */
|
|
255
|
+
|
|
256
|
+
@media (pointer: coarse) {
|
|
257
|
+
/* The default close is --control-height-sm, which _shared.css remaps to
|
|
258
|
+
44px; only the fixed 24px compact close needs the checkbox.css-style
|
|
259
|
+
hit area. */
|
|
260
|
+
.ct-banner--compact .ct-banner__close {
|
|
261
|
+
position: relative;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.ct-banner--compact .ct-banner__close::before {
|
|
265
|
+
content: '';
|
|
266
|
+
position: absolute;
|
|
267
|
+
inset: calc((var(--icon-lg) - 44px) / 2);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
240
271
|
/* ---- Reduced Motion ---- */
|
|
241
272
|
|
|
242
273
|
@media (prefers-reduced-motion: reduce) {
|
package/components/button.css
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
.ct-button {
|
|
2
|
-
--ct-button-bg: var(--
|
|
3
|
-
--ct-button-bg-hover: var(--
|
|
4
|
-
--ct-button-bg-active: var(--
|
|
5
|
-
--ct-button-border:
|
|
6
|
-
--ct-button-color: var(--
|
|
7
|
-
--ct-button-height: var(--
|
|
8
|
-
--ct-button-padding-x: var(--
|
|
9
|
-
--ct-button-font-size: var(--font-size
|
|
10
|
-
--ct-button-line-height: var(--line-height
|
|
11
|
-
|
|
2
|
+
--ct-button-bg: var(--component-button-background);
|
|
3
|
+
--ct-button-bg-hover: var(--component-button-background-hover);
|
|
4
|
+
--ct-button-bg-active: var(--component-button-background-active);
|
|
5
|
+
--ct-button-border: var(--component-button-border);
|
|
6
|
+
--ct-button-color: var(--component-button-color);
|
|
7
|
+
--ct-button-height: var(--component-button-height);
|
|
8
|
+
--ct-button-padding-x: var(--component-button-padding-x);
|
|
9
|
+
--ct-button-font-size: var(--component-button-font-size);
|
|
10
|
+
--ct-button-line-height: var(--component-button-line-height);
|
|
11
|
+
--ct-button-font-weight: var(--component-button-font-weight);
|
|
12
|
+
--ct-button-radius: var(--component-button-radius);
|
|
13
|
+
--ct-button-gap: var(--component-button-gap);
|
|
14
|
+
--ct-button-loading-spinner-size: var(--component-button-loading-spinner-size);
|
|
15
|
+
|
|
16
|
+
position: relative;
|
|
12
17
|
display: inline-flex;
|
|
13
18
|
align-items: center;
|
|
14
19
|
justify-content: center;
|
|
@@ -32,12 +37,12 @@
|
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
@media (hover: hover) {
|
|
35
|
-
.ct-button:hover {
|
|
40
|
+
.ct-button:not(:disabled):not([aria-disabled='true']):not([aria-busy='true']):not([data-loading='true']):hover {
|
|
36
41
|
background: var(--ct-button-bg-hover);
|
|
37
42
|
}
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
.ct-button:active {
|
|
45
|
+
.ct-button:not(:disabled):not([aria-disabled='true']):not([aria-busy='true']):not([data-loading='true']):active {
|
|
41
46
|
background: var(--ct-button-bg-active);
|
|
42
47
|
transform: translateY(1px);
|
|
43
48
|
}
|
|
@@ -52,6 +57,56 @@
|
|
|
52
57
|
opacity: var(--opacity-disabled);
|
|
53
58
|
transform: none;
|
|
54
59
|
cursor: not-allowed;
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Loading contract:
|
|
64
|
+
- set `aria-busy="true"` and `data-loading="true"`;
|
|
65
|
+
- native buttons must also use `disabled` to block keyboard activation;
|
|
66
|
+
- wrap the stable accessible label in `.ct-button__content`;
|
|
67
|
+
- keep `.ct-button__loader` decorative (`aria-hidden="true"`). */
|
|
68
|
+
.ct-button__content {
|
|
69
|
+
display: inline-flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
gap: inherit;
|
|
73
|
+
opacity: 1;
|
|
74
|
+
transition: opacity var(--duration-fast) var(--easing-standard);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ct-button__loader {
|
|
78
|
+
position: absolute;
|
|
79
|
+
inset: 0;
|
|
80
|
+
display: grid;
|
|
81
|
+
place-items: center;
|
|
82
|
+
opacity: 0;
|
|
83
|
+
pointer-events: none;
|
|
84
|
+
transition: opacity var(--duration-fast) var(--easing-standard);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ct-button__loader::before {
|
|
88
|
+
content: '';
|
|
89
|
+
width: var(--ct-button-loading-spinner-size);
|
|
90
|
+
height: var(--ct-button-loading-spinner-size);
|
|
91
|
+
border: var(--border-medium) solid currentColor;
|
|
92
|
+
border-inline-end-color: transparent;
|
|
93
|
+
border-radius: var(--radius-round);
|
|
94
|
+
animation: ct-spin var(--ct-spinner-duration) linear infinite;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.ct-button:is([aria-busy='true'], [data-loading='true']) {
|
|
98
|
+
opacity: 1;
|
|
99
|
+
transform: none;
|
|
100
|
+
cursor: wait;
|
|
101
|
+
pointer-events: none;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.ct-button:is([aria-busy='true'], [data-loading='true']) .ct-button__content {
|
|
105
|
+
opacity: 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.ct-button:is([aria-busy='true'], [data-loading='true']) .ct-button__loader {
|
|
109
|
+
opacity: 1;
|
|
55
110
|
}
|
|
56
111
|
|
|
57
112
|
.ct-button--secondary {
|
|
@@ -88,8 +143,8 @@
|
|
|
88
143
|
|
|
89
144
|
.ct-button--danger {
|
|
90
145
|
--ct-button-bg: var(--color-state-danger);
|
|
91
|
-
--ct-button-bg-hover: color-mix(in srgb, var(--color-state-danger) 85%,
|
|
92
|
-
--ct-button-bg-active: color-mix(in srgb, var(--color-state-danger) 70%,
|
|
146
|
+
--ct-button-bg-hover: color-mix(in srgb, var(--color-state-danger) 85%, var(--color-state-danger-text));
|
|
147
|
+
--ct-button-bg-active: color-mix(in srgb, var(--color-state-danger) 70%, var(--color-state-danger-text));
|
|
93
148
|
--ct-button-border: transparent;
|
|
94
149
|
--ct-button-color: var(--color-state-on-solid);
|
|
95
150
|
}
|
|
@@ -138,8 +193,41 @@
|
|
|
138
193
|
justify-content: center;
|
|
139
194
|
}
|
|
140
195
|
|
|
196
|
+
@media (pointer: coarse) {
|
|
197
|
+
.ct-button {
|
|
198
|
+
min-width: var(--control-height-sm);
|
|
199
|
+
min-height: var(--control-height-sm);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.ct-button--link {
|
|
203
|
+
min-height: var(--control-height-sm);
|
|
204
|
+
padding-inline: var(--space-2);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
141
208
|
@media (prefers-reduced-motion: reduce) {
|
|
142
|
-
.ct-button
|
|
209
|
+
.ct-button,
|
|
210
|
+
.ct-button__content,
|
|
211
|
+
.ct-button__loader {
|
|
212
|
+
transition: none;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.ct-button:not(:disabled):not([aria-disabled='true']):not([aria-busy='true']):not([data-loading='true']):active {
|
|
143
216
|
transform: none;
|
|
144
217
|
}
|
|
218
|
+
|
|
219
|
+
.ct-button__loader::before {
|
|
220
|
+
animation: none;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@media (forced-colors: active) {
|
|
225
|
+
.ct-button {
|
|
226
|
+
border-color: currentColor;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.ct-button__loader::before {
|
|
230
|
+
border-color: currentColor;
|
|
231
|
+
border-inline-end-color: transparent;
|
|
232
|
+
}
|
|
145
233
|
}
|
package/components/card.css
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
.ct-card {
|
|
2
|
-
--ct-card-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
--ct-card-bg: var(--component-card-background);
|
|
3
|
+
--ct-card-border: var(--component-card-border);
|
|
4
|
+
--ct-card-radius: var(--component-card-radius);
|
|
5
|
+
--ct-card-padding: var(--component-card-padding);
|
|
6
|
+
--ct-card-shadow: var(--component-card-shadow);
|
|
7
|
+
--ct-card-shadow-hover: var(--component-card-shadow-hover);
|
|
8
|
+
|
|
9
|
+
background: var(--ct-card-bg);
|
|
10
|
+
border: var(--border-thin) solid var(--ct-card-border);
|
|
11
|
+
border-radius: var(--ct-card-radius);
|
|
6
12
|
padding: var(--ct-card-padding);
|
|
7
|
-
box-shadow: var(--shadow
|
|
13
|
+
box-shadow: var(--ct-card-shadow);
|
|
14
|
+
/* The datum edge slides in on activation instead of popping. */
|
|
15
|
+
transition: box-shadow var(--duration-fast) var(--easing-standard);
|
|
8
16
|
}
|
|
9
17
|
|
|
10
18
|
.ct-card--sm {
|
|
@@ -47,7 +55,7 @@
|
|
|
47
55
|
@media (hover: hover) {
|
|
48
56
|
:is(a, button, [role='button'], [role='link']).ct-card--interactive:hover {
|
|
49
57
|
border-color: var(--color-border-strong);
|
|
50
|
-
box-shadow: var(--shadow-
|
|
58
|
+
box-shadow: var(--ct-card-shadow-hover);
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
|
|
@@ -55,7 +63,7 @@
|
|
|
55
63
|
outline: 2px solid var(--color-focus-ring);
|
|
56
64
|
outline-offset: 2px;
|
|
57
65
|
border-color: var(--color-border-strong);
|
|
58
|
-
box-shadow: var(--shadow-
|
|
66
|
+
box-shadow: var(--ct-card-shadow-hover);
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
.ct-card--interactive[aria-selected='true'] {
|
|
@@ -64,9 +72,20 @@
|
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
.ct-card--interactive[aria-disabled='true'],
|
|
67
|
-
.ct-card--interactive:disabled
|
|
75
|
+
.ct-card--interactive:disabled,
|
|
76
|
+
.ct-card--interactive[data-state='disabled'] {
|
|
68
77
|
opacity: var(--opacity-disabled);
|
|
69
78
|
cursor: not-allowed;
|
|
79
|
+
pointer-events: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.ct-card[aria-busy='true'],
|
|
83
|
+
.ct-card[data-state='loading'] {
|
|
84
|
+
cursor: wait;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ct-card[data-state='error'] {
|
|
88
|
+
border-color: var(--color-state-danger-border);
|
|
70
89
|
}
|
|
71
90
|
|
|
72
91
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -74,3 +93,43 @@
|
|
|
74
93
|
transition: none;
|
|
75
94
|
}
|
|
76
95
|
}
|
|
96
|
+
|
|
97
|
+
/* ── Datum ──
|
|
98
|
+
The reference-line grammar applied to cards. `--datum` adds a corner
|
|
99
|
+
registration mark (like an engineering drawing); the active/current
|
|
100
|
+
state carries the same orange leading edge as list and sidebar items.
|
|
101
|
+
Both are opt-in and additive — they don't change default cards. */
|
|
102
|
+
.ct-card--datum {
|
|
103
|
+
position: relative;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.ct-card--datum::before {
|
|
107
|
+
content: '';
|
|
108
|
+
position: absolute;
|
|
109
|
+
inset-block-start: calc(-1 * var(--border-thin));
|
|
110
|
+
inset-inline-start: calc(-1 * var(--border-thin));
|
|
111
|
+
width: var(--component-datum-mark-size);
|
|
112
|
+
height: var(--component-datum-mark-size);
|
|
113
|
+
border-block-start: var(--border-medium) solid var(--color-brand-accent);
|
|
114
|
+
border-inline-start: var(--border-medium) solid var(--color-brand-accent);
|
|
115
|
+
border-start-start-radius: var(--radius-lg);
|
|
116
|
+
pointer-events: none;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.ct-card--active,
|
|
120
|
+
.ct-card[data-state='active'] {
|
|
121
|
+
/* The datum: an orange leading edge marks the active/current card,
|
|
122
|
+
consistent with .ct-list__item and .ct-sidebar active states. The
|
|
123
|
+
ambient shadow stays first so the list interpolates against the
|
|
124
|
+
base card shadow and the edge can animate in. */
|
|
125
|
+
box-shadow: var(--ct-card-shadow), inset var(--component-datum-edge-width) 0 0 0 var(--color-brand-accent);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@media (forced-colors: active) {
|
|
129
|
+
/* The datum edge is an inset box-shadow, which forced colors drops —
|
|
130
|
+
re-encode it as a border. */
|
|
131
|
+
.ct-card--active,
|
|
132
|
+
.ct-card[data-state='active'] {
|
|
133
|
+
border-inline-start: var(--component-datum-edge-width) solid SelectedItem;
|
|
134
|
+
}
|
|
135
|
+
}
|