@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
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Headless Behaviors
|
|
2
|
+
|
|
3
|
+
Construct's optional behavior package enhances existing DOM with accessible state machines. It has no framework or runtime dependency and does not render markup or styles.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import {
|
|
7
|
+
createModalController,
|
|
8
|
+
createTabsController,
|
|
9
|
+
} from '@neuravision/construct/behaviors';
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Every factory validates its DOM contract, returns the existing controller when initialized twice for the same root/trigger, and exposes an idempotent `destroy()` method. Call `destroy()` before the owning view unmounts.
|
|
13
|
+
|
|
14
|
+
## Controllers
|
|
15
|
+
|
|
16
|
+
| Factory | Behavior contract |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `createModalController` | Dialog semantics, initial focus, focus trap, Escape/backdrop dismissal, nested overlay ordering, scroll lock, focus return, optional `inertBackground` |
|
|
19
|
+
| `createDrawerController` | Modal drawer behavior with the same focus/dismissal invariants and toggle trigger support |
|
|
20
|
+
| `createTabsController` | Roving tabindex, horizontal/vertical arrows, Home/End, automatic or manual activation |
|
|
21
|
+
| `createToggleGroupController` | Single/multiple pressed state (`aria-pressed`, or `aria-checked` radio semantics for single groups authored with `role="radiogroup"`/`role="radio"`), roving tabindex, orientation-aware arrows, controlled programmatic value |
|
|
22
|
+
| `createDropdownController` | ARIA menu button, first/last focus, arrows, Home/End, typeahead (open and closed), outside/Escape dismissal, menu item selection |
|
|
23
|
+
| `createSelectMenuController` | Button/listbox selection, active option, typeahead, PageUp/PageDown, Tab commits the active option, keyboard opening and dismissal, value synchronization |
|
|
24
|
+
| `createComboboxController` | Editable combobox, virtual listbox focus, optional local filtering, active descendant, Alt+Arrow open/close, IME-safe key handling, announcements, selection |
|
|
25
|
+
| `createTooltipController` | Focus and hover timing, touch toggle, Escape/outside dismissal, described-by wiring |
|
|
26
|
+
| `createPopoverController` | Non-modal popup disclosure, optional focus entry, hover/focus modes, outside/Escape dismissal, focus return |
|
|
27
|
+
|
|
28
|
+
The package includes TypeScript declarations for options, callbacks, controller methods, and event-detail types.
|
|
29
|
+
|
|
30
|
+
### What the caller still owns (accessible names and live regions)
|
|
31
|
+
|
|
32
|
+
Controllers wire state attributes, but accessible names and announcement targets come from your markup. Missing ones are reported as console warnings in the browser:
|
|
33
|
+
|
|
34
|
+
| Controller | You must provide |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| Modal / Drawer | `aria-labelledby` or `aria-label` on the dialog element |
|
|
37
|
+
| Dropdown | `aria-label` or `aria-labelledby` on the `role="menu"` element |
|
|
38
|
+
| Toggle group | `aria-label` or `aria-labelledby` on the group/radiogroup/toolbar root |
|
|
39
|
+
| Select menu | A visible label or `aria-labelledby` for the trigger, and labels on options |
|
|
40
|
+
| Combobox | A `<label>` for the input, an `aria-label` on the listbox, and a visually hidden live-region element passed as `options.status` — without it filter results are never announced |
|
|
41
|
+
| Tooltip | A keyboard-focusable trigger (natively focusable or `tabindex="0"`) and text content in the tooltip element |
|
|
42
|
+
| Popover | An accessible name when using `role="dialog"` content |
|
|
43
|
+
|
|
44
|
+
### Documented deviations from APG
|
|
45
|
+
|
|
46
|
+
- Select menu and dropdown arrow navigation wraps at the ends (APG select-only combobox specifies no wrap); wrapping matches Construct's menu behavior and is intentional.
|
|
47
|
+
- Typing on a closed select menu commits the matching value immediately, mirroring native `<select>` on Windows, instead of opening the listbox first.
|
|
48
|
+
|
|
49
|
+
## Modal example
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<button id="open-settings" class="ct-button" type="button">Settings</button>
|
|
53
|
+
|
|
54
|
+
<div id="settings-modal" class="ct-modal" data-state="closed" hidden>
|
|
55
|
+
<section class="ct-modal__dialog" aria-labelledby="settings-title">
|
|
56
|
+
<header class="ct-modal__header">
|
|
57
|
+
<h2 id="settings-title">Settings</h2>
|
|
58
|
+
<button class="ct-button ct-button--ghost" type="button" data-ct-dismiss>Close</button>
|
|
59
|
+
</header>
|
|
60
|
+
<div class="ct-modal__body">
|
|
61
|
+
<label class="ct-field">
|
|
62
|
+
<span class="ct-field__label">Display name</span>
|
|
63
|
+
<input class="ct-input" type="text" autofocus />
|
|
64
|
+
</label>
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
</div>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import { createModalController } from '@neuravision/construct/behaviors';
|
|
72
|
+
|
|
73
|
+
const modal = document.querySelector('#settings-modal');
|
|
74
|
+
const controller = createModalController({
|
|
75
|
+
container: modal,
|
|
76
|
+
dialog: modal.querySelector('.ct-modal__dialog'),
|
|
77
|
+
trigger: document.querySelector('#open-settings'),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// When the owning view is removed:
|
|
81
|
+
controller.destroy();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Buttons with `data-ct-dismiss` are discovered automatically. The caller still owns the accessible name through `aria-labelledby` or `aria-label` and owns the action performed after confirmation.
|
|
85
|
+
|
|
86
|
+
Pass `inertBackground: true` to additionally set `inert` on the container's DOM siblings while the dialog is open — a belt-and-suspenders layer on top of `aria-modal` for legacy screen-reader virtual cursors. Content portaled into the DOM after opening (for example popup layers) is unaffected.
|
|
87
|
+
|
|
88
|
+
## Tabs example
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
import { createTabsController } from '@neuravision/construct/behaviors';
|
|
92
|
+
|
|
93
|
+
const tablist = document.querySelector('[role="tablist"]');
|
|
94
|
+
const tabs = createTabsController({
|
|
95
|
+
tablist,
|
|
96
|
+
activation: 'automatic',
|
|
97
|
+
onSelect({ index }) {
|
|
98
|
+
analytics.track('settings_tab_selected', { index });
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The controller can discover tabs from `[role="tab"]` or `[data-ct-tab]`. Panels can be linked with `aria-controls` or passed explicitly. Disabled tabs use native `disabled` or `aria-disabled="true"` and are skipped by keyboard movement.
|
|
104
|
+
|
|
105
|
+
## State and events
|
|
106
|
+
|
|
107
|
+
Controllers synchronize the attributes consumed by Construct CSS, including `data-state`, `aria-expanded`, `aria-selected`, `aria-pressed`, `aria-activedescendant`, `tabindex`, and `hidden` as applicable.
|
|
108
|
+
|
|
109
|
+
State changes dispatch cancellable `ct:beforeopen`, `ct:beforeclose`, `ct:beforeselect`, or `ct:beforevaluechange` events where relevant. Preventing the event prevents the transition. Successful transitions dispatch the corresponding `ct:open`, `ct:close`, `ct:select`, or `ct:valuechange` event and invoke the typed callback option.
|
|
110
|
+
|
|
111
|
+
Programmatic methods accept a `reason` string. Use stable product-specific reasons when analytics or application policy needs to distinguish pointer, keyboard, routing, and remote-data transitions.
|
|
112
|
+
|
|
113
|
+
## Lifecycle rules
|
|
114
|
+
|
|
115
|
+
- Initialize after all managed elements exist.
|
|
116
|
+
- Keep one controller per mounted widget; repeated factory calls for the same owner return it.
|
|
117
|
+
- Do not update controller-owned ARIA attributes from a second state machine.
|
|
118
|
+
- Call `destroy()` during framework cleanup. It removes listeners, overlay registrations, timers, scroll locks, and owned temporary attributes.
|
|
119
|
+
- Recreate the controller when the managed item collection changes. `ComboboxController.refresh()` is the exception for filter/result refresh within its fixed managed collection.
|
|
120
|
+
- Prefer native `<details>`, `<select>`, and `<dialog>` where their platform behavior fully meets the product requirement.
|
|
121
|
+
|
|
122
|
+
See the [React example](../examples/react/README.md) for framework lifecycle integration and [Component usage](../components/README.md) for the corresponding markup and CSS classes.
|
package/docs/fonts.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Fonts
|
|
2
|
+
|
|
3
|
+
Construct foundations are network-free. They declare font-family tokens with resilient system fallbacks, but they do not download font files.
|
|
4
|
+
|
|
5
|
+
## System-font fallback
|
|
6
|
+
|
|
7
|
+
No extra step is required. Import the normal foundation stylesheet and the browser uses a locally installed Lato or JetBrains Mono face when available, then the generated system fallback stack.
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
@import '@neuravision/construct/foundations.css';
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This is the default for strict Content Security Policy, privacy-sensitive, offline, and embedded applications.
|
|
14
|
+
|
|
15
|
+
## Optional hosted preset
|
|
16
|
+
|
|
17
|
+
Projects that explicitly accept Google-hosted font requests can import the opt-in preset before foundations:
|
|
18
|
+
|
|
19
|
+
```css
|
|
20
|
+
@import '@neuravision/construct/fonts.css';
|
|
21
|
+
@import '@neuravision/construct/foundations.css';
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The preset requests Lato 400/700/900 and JetBrains Mono 400/500/600/700. Do not use it when the product's privacy or CSP policy forbids third-party font requests.
|
|
25
|
+
|
|
26
|
+
## Self-hosting
|
|
27
|
+
|
|
28
|
+
Host the licensed font files in the product and define the faces in application CSS. The URLs below are examples; Construct intentionally does not publish third-party font binaries.
|
|
29
|
+
|
|
30
|
+
```css
|
|
31
|
+
@font-face {
|
|
32
|
+
font-family: 'Lato';
|
|
33
|
+
src: url('/fonts/lato-regular.woff2') format('woff2');
|
|
34
|
+
font-style: normal;
|
|
35
|
+
font-weight: 400;
|
|
36
|
+
font-display: swap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@font-face {
|
|
40
|
+
font-family: 'Lato';
|
|
41
|
+
src: url('/fonts/lato-bold.woff2') format('woff2');
|
|
42
|
+
font-style: normal;
|
|
43
|
+
font-weight: 700;
|
|
44
|
+
font-display: swap;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@font-face {
|
|
48
|
+
font-family: 'Lato';
|
|
49
|
+
src: url('/fonts/lato-black.woff2') format('woff2');
|
|
50
|
+
font-style: normal;
|
|
51
|
+
font-weight: 900;
|
|
52
|
+
font-display: swap;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Add the JetBrains Mono faces only if the application renders code or tabular data with the mono token. Preload only the files needed above the fold; indiscriminate preloading delays other critical resources.
|
|
57
|
+
|
|
58
|
+
## Product typefaces
|
|
59
|
+
|
|
60
|
+
A product can keep Construct's sizing and hierarchy while replacing the families. Override both primitive family variables after the Construct imports; the semantic aliases continue to follow them.
|
|
61
|
+
|
|
62
|
+
```css
|
|
63
|
+
:root {
|
|
64
|
+
--font-family-brand: 'Company Sans', 'Segoe UI', sans-serif;
|
|
65
|
+
--font-family-text: 'Company Sans', 'Segoe UI', sans-serif;
|
|
66
|
+
--font-family-mono: 'Company Mono', 'SFMono-Regular', Menlo, monospace;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Verify every available weight, text reflow at 200% zoom, fallback metrics during loading, and contrast in all supported themes before release.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Construct Governance and Definition of Done
|
|
2
|
+
|
|
3
|
+
Construct is a reusable product contract, not a collection of project-specific styles. Changes must keep the token, CSS, behavior, accessibility, documentation, and package surfaces aligned.
|
|
4
|
+
|
|
5
|
+
## Ownership boundaries
|
|
6
|
+
|
|
7
|
+
| Layer | Owns | Must not own |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Primitive tokens | Raw scales and values | Product meaning or component selectors |
|
|
10
|
+
| Semantic tokens | Theme-aware intent | Component anatomy |
|
|
11
|
+
| Component tokens | Stable component decisions | DOM event handling |
|
|
12
|
+
| Component CSS | Appearance and attribute-driven states | Focus management or keyboard state machines |
|
|
13
|
+
| Headless behaviors | ARIA synchronization, keyboard interaction, focus, open/close state | Product data or visual styling |
|
|
14
|
+
| Framework adapters | Rendering, lifecycle integration, application data | Private forks of Construct behavior |
|
|
15
|
+
|
|
16
|
+
Product-specific rules belong in the consuming application. A pattern that is useful across products should first be expressed as a general Construct contract and covered by the quality gates below.
|
|
17
|
+
|
|
18
|
+
## Definition of Done
|
|
19
|
+
|
|
20
|
+
A change is complete only when every applicable item is satisfied.
|
|
21
|
+
|
|
22
|
+
### Tokens and themes
|
|
23
|
+
|
|
24
|
+
- Source JSON is changed instead of generated `tokens/tokens.*` files.
|
|
25
|
+
- Primitive, semantic, and component-token responsibilities remain distinct.
|
|
26
|
+
- References resolve without cycles or missing targets.
|
|
27
|
+
- Built-in theme structures stay compatible with the light-theme contract.
|
|
28
|
+
- Custom-theme input is schema-valid, only overrides known tokens, and passes its declared contrast checks.
|
|
29
|
+
- Generated CSS, JSON, JavaScript, and TypeScript outputs are rebuilt together before checks and packaging. They are intentionally ignored in this repository and produced in clean CI/prepack runs; source JSON and schemas are the reviewed files.
|
|
30
|
+
- Token additions, removals, or value changes follow the documented versioning policy.
|
|
31
|
+
|
|
32
|
+
### Components and CSS
|
|
33
|
+
|
|
34
|
+
- The public anatomy, modifiers, sizes, and supported ARIA/data attributes are documented.
|
|
35
|
+
- Default, hover, focus-visible, active, selected/open, disabled, invalid, loading, and empty states are handled where they apply.
|
|
36
|
+
- Covered compact controls follow Construct's 44×44 CSS-pixel coarse-pointer policy; custom controls are tested separately.
|
|
37
|
+
- Motion has a `prefers-reduced-motion` path and essential meaning does not depend on animation.
|
|
38
|
+
- Essential controls and states remain perceivable with `forced-colors: active`.
|
|
39
|
+
- CSS uses Construct tokens, logical properties where direction matters, and no unresolved custom properties.
|
|
40
|
+
- The component works through both the full bundle and its standalone package import.
|
|
41
|
+
- A Storybook story covers representative states and the accessibility addon reports no serious violation.
|
|
42
|
+
|
|
43
|
+
### Interactive behavior
|
|
44
|
+
|
|
45
|
+
- Native HTML is preferred where it already provides the required semantics.
|
|
46
|
+
- ARIA state and visible state share one source of truth.
|
|
47
|
+
- Keyboard behavior follows the relevant WAI-ARIA Authoring Practices pattern.
|
|
48
|
+
- Focus entry, containment where required, Escape behavior, outside interaction, and focus restoration are explicit.
|
|
49
|
+
- Disabled items are not activated, and composite widgets maintain a valid focus model.
|
|
50
|
+
- Initialization is safe, teardown removes every listener and temporary attribute, and repeated initialization does not leak state.
|
|
51
|
+
- Behavior modules have no framework dependency and expose a documented controller contract.
|
|
52
|
+
- Unit tests cover the keyboard and lifecycle invariants; Storybook verifies the integrated markup and styles.
|
|
53
|
+
|
|
54
|
+
### Documentation and release
|
|
55
|
+
|
|
56
|
+
- README, component usage, token guidance, and Storybook prose do not overstate what CSS alone provides.
|
|
57
|
+
- A framework example is updated when a public behavior or markup contract changes.
|
|
58
|
+
- The changelog explains consumer-visible additions, migrations, and accessibility implications.
|
|
59
|
+
- `package.json`, its lockfile, examples, issue templates, and visible version labels agree on the release version.
|
|
60
|
+
- Package exports and the packed file list are verified before release.
|
|
61
|
+
|
|
62
|
+
## Required quality gates
|
|
63
|
+
|
|
64
|
+
Run these from the Construct package root:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm run audit
|
|
68
|
+
npm run build
|
|
69
|
+
npm run check
|
|
70
|
+
npm test
|
|
71
|
+
npm run storybook:build
|
|
72
|
+
npm pack --dry-run
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The audit gate includes development tooling and rejects high or critical dependency advisories. Run `npm run build` before the non-mutating `npm run check`, including in a clean checkout where ignored token outputs do not exist. The check verifies generated token and standalone outputs, token/theme validity, contrast declarations, CSS-variable closure, and package export targets. `npm test` verifies headless behavior, Storybook interactions/accessibility, and the real coarse-pointer browser contract.
|
|
76
|
+
|
|
77
|
+
Manual review is still required for visual hierarchy, zoom and reflow, screen-reader announcements, keyboard order, and product-context language. Automated checks support that review; they do not replace it.
|
|
78
|
+
|
|
79
|
+
## Versioning
|
|
80
|
+
|
|
81
|
+
- **Major:** remove or rename a public token, selector, behavior export, or required markup/attribute contract.
|
|
82
|
+
- **Minor:** add a token, component, variant, optional behavior, theme capability, or backward-compatible state.
|
|
83
|
+
- **Patch:** fix an implementation without changing the documented consumer contract.
|
|
84
|
+
|
|
85
|
+
Deprecations must identify the replacement and removal release. Compatibility files may forward to a canonical implementation, but must not maintain a second divergent copy.
|
package/docs/guidelines.md
CHANGED
|
@@ -15,11 +15,12 @@ A consistent, accessible, professional design system for modern web applications
|
|
|
15
15
|
- Maintain clear state logic (hover, focus, active, disabled, error)
|
|
16
16
|
- Use consistent sizes (sm, md, lg) for form controls and buttons
|
|
17
17
|
- Establish clear hierarchies (Primary vs. Secondary actions)
|
|
18
|
-
- Test all
|
|
18
|
+
- Test all interactive functionality with keyboard navigation (Tab, Shift+Tab, Enter, Space)
|
|
19
19
|
|
|
20
20
|
### Don't
|
|
21
21
|
|
|
22
22
|
- Don't rely on color alone for status information without text or icons
|
|
23
|
+
- Don't rely on hue alone to distinguish chart series — use direct labels, ordered legends, and the always-present data-table fallback; several series pairs are near-identical in luminance for color-blind users
|
|
23
24
|
- Don't create custom controls without ARIA roles and keyboard support
|
|
24
25
|
- Don't use fixed pixel layouts for content that can grow
|
|
25
26
|
- Don't use low-contrast text or disabled states that are unreadable
|
|
@@ -28,34 +29,47 @@ A consistent, accessible, professional design system for modern web applications
|
|
|
28
29
|
## Accessibility Rules (Minimum)
|
|
29
30
|
|
|
30
31
|
- **Visible Focus**: All interactive elements must have visible focus indication
|
|
31
|
-
- **Labels**: Every form input must have a label
|
|
32
|
-
- **
|
|
32
|
+
- **Labels**: Every form input must have a label. Prefer a visible `<label>`; use `aria-label` only when a visible label is impossible, and never let it differ from visible text (2.5.3 Label in Name — note that `aria-label` overrides an associated `<label>` when both are present)
|
|
33
|
+
- **Headings**: Keep `h1`–`h6` in order without skipping levels, including inside cards, modals, and shells
|
|
34
|
+
- **Landmarks**: Wrap navigation lists in `<nav>` (also inside `<aside>` sidebars), label repeated landmarks (`aria-label`), and provide a skip link to `main` (see `skip-link.css`)
|
|
35
|
+
- **Truncation**: `nowrap` + ellipsis on essential text (titles, values, labels) needs a way to reach the full text — a tooltip, a wrapping variant, or full text elsewhere on the page
|
|
36
|
+
- **Status colors**: Semantic color (toast, banner, alert, badge) must be paired with an icon or explicit wording; the tinted border alone is decorative and intentionally not contrast-gated
|
|
37
|
+
- **Sticky chrome**: Products using `--sticky`/`--fixed` navbar, toolbar, or banner variants must set `--ct-scroll-offset-top`/`--ct-scroll-offset-bottom` on `:root` so focused elements are never scrolled underneath (2.4.11); the app shells set their own scroll padding automatically
|
|
38
|
+
- **Keyboard Navigation**: Use the tested headless controller for supported composite widgets; framework adapters must implement the documented pattern for others such as Datepicker and Tree
|
|
33
39
|
- **ARIA**: Use ARIA attributes only where semantic HTML isn't sufficient
|
|
34
40
|
- **Links**: Links must be distinguishable without color alone (e.g., underline)
|
|
35
|
-
- **Contrast**: Text, icons, and focus rings must meet WCAG AA
|
|
41
|
+
- **Contrast**: Text, icons, component boundaries, and focus rings must meet applicable WCAG 2.2 AA requirements
|
|
36
42
|
- **Themes**: Support light, dark, and high-contrast modes; respect user preferences
|
|
37
43
|
- **Disabled States**: Must be recognizable, but content should remain readable
|
|
38
44
|
- **Motion**: Respect `prefers-reduced-motion` for animations
|
|
39
|
-
- **Live Regions**: Use
|
|
45
|
+
- **Live Regions**: Use a polite live region only when a dynamic update must be announced; do not make static status labels noisy
|
|
40
46
|
- **Modal**: Requires `aria-modal`, `role="dialog"`, and focus trap
|
|
47
|
+
- **Targets**: Covered compact controls follow Construct's 44×44 CSS-pixel coarse-pointer policy; custom controls require their own checks
|
|
48
|
+
- **Forced colors**: Essential focus, boundary, selected, and status meaning must remain perceivable
|
|
41
49
|
|
|
42
50
|
## Keyboard Patterns for Composite Components
|
|
43
51
|
|
|
52
|
+
Construct provides typed controllers for Modal, Drawer, Tabs, Toggle Group, Dropdown, Select Menu,
|
|
53
|
+
Combobox, Tooltip, and Popover through `@neuravision/construct/behaviors`. Use those controllers or a
|
|
54
|
+
framework adapter with equivalent tested semantics; CSS state selectors alone do not implement behavior.
|
|
55
|
+
|
|
44
56
|
### Tabs
|
|
45
57
|
- **Arrow keys**: Move focus between tabs
|
|
46
58
|
- **Home/End**: Jump to first/last tab
|
|
47
59
|
- **Enter/Space**: Activate tab
|
|
48
60
|
- **Implementation**: Use roving tabindex (active tab `tabindex="0"`, others `tabindex="-1"`)
|
|
49
61
|
|
|
50
|
-
###
|
|
62
|
+
### Generic disclosure / action list (Popover)
|
|
63
|
+
- **Use for**: Ordinary links, buttons, form controls, or mixed content that should retain native semantics
|
|
51
64
|
- **Trigger**: Uses `aria-expanded` and `aria-controls`
|
|
52
|
-
- **
|
|
53
|
-
- **
|
|
54
|
-
- **
|
|
65
|
+
- **Focus**: Content remains in its natural Tab order; optional focus entry and focus return depend on the product flow
|
|
66
|
+
- **Behavior**: Use `createPopoverController` or an equivalent adapter for Escape, outside dismissal, and state synchronization
|
|
67
|
+
- **Roles**: Do not add `menu` / `menuitem` roles to generic disclosure content
|
|
55
68
|
|
|
56
|
-
### Dropdown (
|
|
57
|
-
- **
|
|
58
|
-
-
|
|
69
|
+
### Dropdown action menu (`role="menu"`)
|
|
70
|
+
- **Use for**: A menu button whose popup contains application commands
|
|
71
|
+
- **Behavior**: Use `createDropdownController` for first/last focus, Arrow keys, Home/End, typeahead, roving tabindex, Tab dismissal, Escape, and focus return
|
|
72
|
+
- **Naming**: Give the menu an accessible name with `aria-label` or `aria-labelledby`
|
|
59
73
|
|
|
60
74
|
### Datepicker
|
|
61
75
|
- **Arrow keys**: Move by day in calendar grid
|
|
@@ -78,9 +92,10 @@ A consistent, accessible, professional design system for modern web applications
|
|
|
78
92
|
- **Backdrop**: Click closes drawer
|
|
79
93
|
|
|
80
94
|
### Tooltip
|
|
81
|
-
- **Open**: On hover and focus
|
|
95
|
+
- **Open**: On hover and keyboard focus (the trigger must be focusable)
|
|
82
96
|
- **Close**: On blur and Esc
|
|
83
97
|
- **Role**: `role="tooltip"` with `aria-describedby`
|
|
98
|
+
- **Implementation**: The CSS `:hover`/`:focus-within` styles alone do not satisfy WCAG 1.4.13 — Esc dismissal and hover persistence require `createTooltipController` or an equivalent adapter
|
|
84
99
|
|
|
85
100
|
### Tree (`ct-tree`)
|
|
86
101
|
|
|
@@ -93,7 +108,7 @@ Hierarchical disclosure following the [WAI-ARIA Tree View pattern](https://www.w
|
|
|
93
108
|
- Expandable nodes carry `aria-expanded="true|false"`. Leaf nodes omit the attribute.
|
|
94
109
|
|
|
95
110
|
**Indent**
|
|
96
|
-
Set `--ct-level` inline on each `.ct-tree__row` (matches the node's `aria-level`). The
|
|
111
|
+
Set `--ct-level` inline on each `.ct-tree__row` (matches the node's `aria-level`). The Storybook reference implementation fills it in automatically; framework wrappers should generate it during render.
|
|
97
112
|
|
|
98
113
|
**Keyboard**
|
|
99
114
|
- `↑` / `↓`: focus previous / next visible row
|
|
@@ -103,6 +118,7 @@ Set `--ct-level` inline on each `.ct-tree__row` (matches the node's `aria-level`
|
|
|
103
118
|
- `Enter`: activate (consumer-defined; emit `ct-tree:activate`)
|
|
104
119
|
- `Space`: toggle selection (multi/single) or activate (no selection)
|
|
105
120
|
- `*`: expand all siblings on the same level
|
|
121
|
+
- `F2`: move focus into the current row's action buttons; Arrow keys move between actions and `Escape` returns to the treeitem
|
|
106
122
|
- Type-ahead (`A`–`Z`): focus next row whose label starts with the typed prefix; buffer resets after 500 ms
|
|
107
123
|
|
|
108
124
|
**Roving tabindex**
|
|
@@ -112,24 +128,24 @@ Exactly one `<li role="treeitem">` carries `tabindex="0"`. All others carry `tab
|
|
|
112
128
|
The `.ct-tree__toggle` is a non-focusable `<span aria-hidden="true">`. Expand/collapse is reachable via keyboard through `←`/`→` on the row and via mouse through clicking the chevron. We deliberately avoid making the toggle a `<button>`: a focusable button inside a focusable row violates `aria-hidden-focus` / `nested-interactive`, and the row already provides the keyboard affordance.
|
|
113
129
|
|
|
114
130
|
**Row actions**
|
|
115
|
-
Buttons in the `.ct-tree__actions` slot live inside the focusable treeitem. Give them `tabindex="-1"` so the tree exposes a single Tab stop, as required by the WAI-ARIA Tree View pattern.
|
|
131
|
+
Buttons in the `.ct-tree__actions` slot live inside the focusable treeitem. Give them `tabindex="-1"` so the tree exposes a single Tab stop, as required by the WAI-ARIA Tree View pattern. Storybook's reference controller uses `F2` to focus the first enabled action, Arrow keys to traverse the action set, and `Escape` to restore focus to the treeitem. Consumer implementations that expose row actions must provide the same reachable keyboard path or an explicitly documented equivalent.
|
|
116
132
|
|
|
117
133
|
**Selection**
|
|
118
134
|
- `aria-selected="true|false"` on the `<li role="treeitem">` (only when selection is active).
|
|
119
|
-
- For multi-selection, the container needs `aria-multiselectable="true"`.
|
|
135
|
+
- For multi-selection, the container needs `aria-multiselectable="true"`. Storybook's `attachTree` reference implementation sets and tears this down when invoked with `selection: 'multi'`; it is not exported from `@neuravision/construct/behaviors`.
|
|
120
136
|
- Construct only styles selection — the consumer decides whether to clear other rows (`single`) or keep them (`multi`).
|
|
121
137
|
|
|
122
138
|
**Async children**
|
|
123
139
|
Set `aria-busy="true"` on the `<li role="treeitem">` while its children are loading. The chevron switches to a spinner via the existing `ct-spin` keyframe and the toggle becomes non-interactive while busy.
|
|
124
140
|
|
|
125
141
|
**Disabled nodes**
|
|
126
|
-
Use `aria-disabled="true"` on the `<li>`. Do **not** use the HTML `disabled` attribute — `treeitem` is not a form control.
|
|
142
|
+
Use `aria-disabled="true"` on the `<li>`. Do **not** use the HTML `disabled` attribute — `treeitem` is not a form control. Storybook's reference implementation skips activation, selection and toggle for disabled nodes; arrow-key navigation still passes through them so screen readers can announce them.
|
|
127
143
|
|
|
128
144
|
**Orphan state**
|
|
129
145
|
For sub-nodes whose parent reference is missing in the data set, render them on the root level with the `.ct-tree__node--orphan` modifier. They get a warning-tinted surface and a dashed border so the data inconsistency is visible without breaking the tree.
|
|
130
146
|
|
|
131
|
-
**
|
|
132
|
-
|
|
147
|
+
**Reference events**
|
|
148
|
+
Storybook's non-public `attachTree` reference implementation emits four bubbling `CustomEvent`s on the focused treeitem. Consumer wrappers may adopt the same event shape, but these events and `attachTree` are examples rather than a published behavior API:
|
|
133
149
|
|
|
134
150
|
| Event | Detail | Fires on |
|
|
135
151
|
|---|---|---|
|
|
@@ -150,6 +166,31 @@ Use these attributes for state management:
|
|
|
150
166
|
- **selected**: `aria-selected="true"`
|
|
151
167
|
- **current**: `aria-current="page"`
|
|
152
168
|
- **expanded**: `aria-expanded="true|false"`
|
|
169
|
+
- **loading**: `aria-busy="true"`; loading buttons also use `data-loading="true"` and native `disabled`
|
|
170
|
+
|
|
171
|
+
Never use `aria-disabled="true"` alone to block a native button: it announces state but does not suppress
|
|
172
|
+
activation. Use the native `disabled` attribute whenever the HTML element supports it.
|
|
173
|
+
|
|
174
|
+
## The Datum
|
|
175
|
+
|
|
176
|
+
Construct's signature is one orange reference line — "the datum" — that marks what is focused, active,
|
|
177
|
+
current, or the reference value. The grammar lives in `components/datum.css` (eyebrow, scale, frame,
|
|
178
|
+
metrics), in active leading edges (list, sidebar, card, table rows), in the tab indicator and sorted
|
|
179
|
+
column, and in the chart reference line. Its dimensions are component tokens (`--component-datum-*`).
|
|
180
|
+
|
|
181
|
+
Rules that keep the signature strong and accessible:
|
|
182
|
+
|
|
183
|
+
- **One datum per surface.** Orange marks exactly one thing per view region — the current, active, or
|
|
184
|
+
reference element. Never let two orange edges compete; if everything is highlighted, nothing is.
|
|
185
|
+
- **Orange is never text.** Labels and values stay on `--color-text-*` neutrals; orange is carried by
|
|
186
|
+
non-text ticks, edges, rules, and lines.
|
|
187
|
+
- **Edges are `--color-brand-accent`; meaning-bearing fills are `--color-brand-accent-strong`**
|
|
188
|
+
(validated ≥3:1 against muted/canvas/surface tracks per theme). A decorative rule may use plain
|
|
189
|
+
accent; anything that encodes a value (progress, chart reference) uses strong.
|
|
190
|
+
- **The datum enters, it never pops.** State changes draw the edge in from its origin
|
|
191
|
+
(`--duration-fast` / `--easing-standard`); `prefers-reduced-motion` disables this globally.
|
|
192
|
+
- **Forced colors re-encode edges as borders.** Inset box-shadow edges are dropped by forced colors —
|
|
193
|
+
every new edge needs a `SelectedItem` border fallback.
|
|
153
194
|
|
|
154
195
|
## Breakpoints & Media Queries
|
|
155
196
|
|
|
@@ -181,20 +222,26 @@ CSS custom properties cannot be used inside `@media` queries. Use the raw pixel
|
|
|
181
222
|
- Always use `max-width: <token - 1>px` to target viewports *below* a breakpoint
|
|
182
223
|
- Always add a reference comment noting the breakpoint name and token value
|
|
183
224
|
- Only use values derived from the token system — never arbitrary pixel values
|
|
184
|
-
- Current components use
|
|
225
|
+
- Current components use the `sm`, `md`, and `lg` contracts (599px, 899px, and 1199px maximums). App Shell V1 and V2 use `< md` for compact overlay navigation and `< lg` for rail/panel behavior.
|
|
185
226
|
|
|
186
227
|
## Fonts
|
|
187
228
|
|
|
188
|
-
- **Default**:
|
|
189
|
-
- **
|
|
190
|
-
- **
|
|
229
|
+
- **Default**: `foundations.css` makes no network request and uses resilient local/system fallback stacks.
|
|
230
|
+
- **Hosted opt-in**: Import `fonts.css` before foundations only when Google-hosted requests satisfy product privacy and CSP policy.
|
|
231
|
+
- **Self-hosting**: Host licensed files in the application; Construct intentionally does not publish third-party binaries.
|
|
232
|
+
- **Lato weights**: Provide 400, 700, and 900. JetBrains Mono uses 400, 500, 600, and 700.
|
|
233
|
+
|
|
234
|
+
See [Fonts](fonts.md) for complete hosted, self-hosted, and product-family examples.
|
|
191
235
|
|
|
192
236
|
## Governance
|
|
193
237
|
|
|
194
|
-
- **New components require**:
|
|
195
|
-
- **
|
|
238
|
+
- **New components require**: owning token layer, state definitions, accessibility notes, Storybook coverage, and public import verification
|
|
239
|
+
- **Interactive changes require**: keyboard/focus lifecycle tests and complete controller cleanup
|
|
240
|
+
- **Before merge**: `npm run check`, `npm test`, Storybook build, visual/keyboard/screen-reader review
|
|
196
241
|
- **Review process**: At least one accessibility review for new interactive components
|
|
197
242
|
|
|
243
|
+
The normative checklist is [Governance and Definition of Done](governance.md).
|
|
244
|
+
|
|
198
245
|
---
|
|
199
246
|
|
|
200
247
|
**Construct** - Build accessible design constructs
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# React integration example
|
|
2
|
+
|
|
3
|
+
This example keeps React responsible for rendering and lifecycle while Construct owns visual and interaction contracts.
|
|
4
|
+
|
|
5
|
+
1. Install `@neuravision/construct` alongside the application's existing React dependencies.
|
|
6
|
+
2. Import `construct.css` once from the application entry.
|
|
7
|
+
3. Render `SettingsModal`.
|
|
8
|
+
|
|
9
|
+
The effect creates the controller after the DOM refs exist and returns `controller.destroy` through its cleanup. This is safe with React Strict Mode's development setup/cleanup cycle and prevents listeners, focus state, or scroll locks from surviving an unmount.
|
|
10
|
+
|
|
11
|
+
The controller owns open/closed state, dialog semantics, focus trapping, Escape/backdrop dismissal, and focus return. React still owns form values, validation, persistence, routing, and product feedback. Wire the Save action to application state; do not create a second open/close state machine around the same DOM.
|
|
12
|
+
|
|
13
|
+
For a full component bundle, replace the selective component imports with:
|
|
14
|
+
|
|
15
|
+
```css
|
|
16
|
+
@import '@neuravision/construct/foundations.css';
|
|
17
|
+
@import '@neuravision/construct/components/components.css';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
See [Headless behaviors](../../docs/behaviors.md) for the complete controller API and lifecycle rules.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { createModalController } from '@neuravision/construct/behaviors';
|
|
4
|
+
|
|
5
|
+
export function SettingsModal() {
|
|
6
|
+
const triggerRef = useRef(null);
|
|
7
|
+
const containerRef = useRef(null);
|
|
8
|
+
const dialogRef = useRef(null);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const controller = createModalController({
|
|
12
|
+
container: containerRef.current,
|
|
13
|
+
dialog: dialogRef.current,
|
|
14
|
+
trigger: triggerRef.current,
|
|
15
|
+
initialFocus: '#display-name',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return () => controller.destroy();
|
|
19
|
+
}, []);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<button ref={triggerRef} className="ct-button" type="button">
|
|
24
|
+
Edit settings
|
|
25
|
+
</button>
|
|
26
|
+
|
|
27
|
+
<div ref={containerRef} className="ct-modal" data-state="closed" hidden>
|
|
28
|
+
<section
|
|
29
|
+
ref={dialogRef}
|
|
30
|
+
className="ct-modal__dialog"
|
|
31
|
+
aria-labelledby="settings-title"
|
|
32
|
+
>
|
|
33
|
+
<header className="ct-modal__header">
|
|
34
|
+
<h2 id="settings-title">Settings</h2>
|
|
35
|
+
<button
|
|
36
|
+
className="ct-button ct-button--ghost ct-button--icon"
|
|
37
|
+
type="button"
|
|
38
|
+
aria-label="Close settings"
|
|
39
|
+
data-ct-dismiss
|
|
40
|
+
>
|
|
41
|
+
<span aria-hidden="true">×</span>
|
|
42
|
+
</button>
|
|
43
|
+
</header>
|
|
44
|
+
|
|
45
|
+
<div className="ct-modal__body">
|
|
46
|
+
<div className="ct-field">
|
|
47
|
+
<label className="ct-field__label" htmlFor="display-name">
|
|
48
|
+
Display name
|
|
49
|
+
</label>
|
|
50
|
+
<input id="display-name" className="ct-input" autoComplete="name" />
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<footer className="ct-modal__footer">
|
|
55
|
+
<button className="ct-button ct-button--secondary" type="button" data-ct-dismiss>
|
|
56
|
+
Cancel
|
|
57
|
+
</button>
|
|
58
|
+
<button className="ct-button" type="button">
|
|
59
|
+
Save
|
|
60
|
+
</button>
|
|
61
|
+
</footer>
|
|
62
|
+
</section>
|
|
63
|
+
</div>
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
@import '@neuravision/construct/foundations.css';
|
|
2
|
+
@import '@neuravision/construct/components/button.css';
|
|
3
|
+
@import '@neuravision/construct/components/field.css';
|
|
4
|
+
@import '@neuravision/construct/components/input.css';
|
|
5
|
+
@import '@neuravision/construct/components/modal.css';
|
package/fonts.css
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Optional Construct web-font preset.
|
|
3
|
+
*
|
|
4
|
+
* Foundations never make a network request. Import this file before
|
|
5
|
+
* foundations.css only when Google-hosted fonts fit the product's privacy,
|
|
6
|
+
* CSP, and offline requirements. Self-hosted applications can omit it and
|
|
7
|
+
* provide @font-face rules for "Lato" and "JetBrains Mono" themselves, or
|
|
8
|
+
* override the generated --font-family-* custom properties.
|
|
9
|
+
*/
|
|
10
|
+
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700;900&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
|
package/foundations.css
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700;900&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
|
|
2
1
|
@import './tokens/tokens.css';
|
|
3
2
|
|
|
4
3
|
:root {
|
|
@@ -19,6 +18,12 @@
|
|
|
19
18
|
html {
|
|
20
19
|
height: 100%;
|
|
21
20
|
text-size-adjust: 100%;
|
|
21
|
+
/* WCAG 2.4.11 Focus Not Obscured contract: sticky/fixed chrome (navbar,
|
|
22
|
+
toolbar, banner) does not know the document, so products publish its
|
|
23
|
+
height by setting --ct-scroll-offset-top/-bottom on :root/html. Scroll
|
|
24
|
+
operations then keep focused or anchored content clear of the bars. */
|
|
25
|
+
scroll-padding-block-start: var(--ct-scroll-offset-top, 0px);
|
|
26
|
+
scroll-padding-block-end: var(--ct-scroll-offset-bottom, 0px);
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
body {
|
|
@@ -231,6 +236,14 @@ button {
|
|
|
231
236
|
color: var(--color-text-muted);
|
|
232
237
|
}
|
|
233
238
|
|
|
239
|
+
.ct-truncate {
|
|
240
|
+
display: block;
|
|
241
|
+
min-width: 0;
|
|
242
|
+
overflow: hidden;
|
|
243
|
+
text-overflow: ellipsis;
|
|
244
|
+
white-space: nowrap;
|
|
245
|
+
}
|
|
246
|
+
|
|
234
247
|
.ct-text-center {
|
|
235
248
|
text-align: center;
|
|
236
249
|
}
|