@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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/components/README.md +566 -0
  4. package/components/_keyframes.css +23 -0
  5. package/components/_shared.css +120 -0
  6. package/components/accordion.css +124 -0
  7. package/components/alert.css +67 -0
  8. package/components/avatar.css +127 -0
  9. package/components/badge.css +67 -0
  10. package/components/banner.css +247 -0
  11. package/components/breadcrumbs.css +152 -0
  12. package/components/button.css +145 -0
  13. package/components/card.css +76 -0
  14. package/components/checkbox.css +120 -0
  15. package/components/chip.css +361 -0
  16. package/components/combobox.css +385 -0
  17. package/components/components.css +2 -0
  18. package/components/data-table.css +93 -0
  19. package/components/datepicker.css +268 -0
  20. package/components/divider.css +73 -0
  21. package/components/drawer.css +167 -0
  22. package/components/dropdown.css +401 -0
  23. package/components/empty-state.css +97 -0
  24. package/components/field.css +42 -0
  25. package/components/file-upload.css +111 -0
  26. package/components/icon.css +31 -0
  27. package/components/index.css +49 -0
  28. package/components/input.css +64 -0
  29. package/components/list.css +474 -0
  30. package/components/modal.css +164 -0
  31. package/components/navbar.css +587 -0
  32. package/components/pagination.css +131 -0
  33. package/components/popover.css +231 -0
  34. package/components/progress-bar.css +56 -0
  35. package/components/select-menu.css +267 -0
  36. package/components/select.css +30 -0
  37. package/components/sidebar.css +183 -0
  38. package/components/skeleton.css +38 -0
  39. package/components/skip-link.css +38 -0
  40. package/components/slider.css +305 -0
  41. package/components/spinner.css +72 -0
  42. package/components/switch.css +82 -0
  43. package/components/table.css +139 -0
  44. package/components/tabs.css +147 -0
  45. package/components/textarea.css +16 -0
  46. package/components/toast.css +71 -0
  47. package/components/toggle-group.css +196 -0
  48. package/components/toolbar.css +222 -0
  49. package/components/tooltip.css +124 -0
  50. package/docs/guidelines.md +141 -0
  51. package/foundations.css +299 -0
  52. package/package.json +66 -0
  53. package/tokens/README.md +179 -0
  54. package/tokens/tokens.css +434 -0
  55. package/tokens/tokens.js +1188 -0
  56. package/tokens/tokens.json +810 -0
  57. 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