@kouji-ui/components 0.9.1 → 0.9.3

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 (73) hide show
  1. package/fesm2022/kouji-ui-components.mjs +137 -65
  2. package/fesm2022/kouji-ui-components.mjs.map +1 -1
  3. package/package.json +1 -1
  4. package/src/accordion/accordion.css +68 -0
  5. package/src/action-sheet/action-sheet.css +79 -0
  6. package/src/alert/alert.css +133 -0
  7. package/src/avatar/avatar.css +93 -0
  8. package/src/badge/badge.css +59 -0
  9. package/src/breadcrumb/breadcrumb.css +145 -0
  10. package/src/button/button.css +295 -0
  11. package/src/button-group/button-group.css +114 -0
  12. package/src/calendar/calendar.css +129 -0
  13. package/src/card/card.css +120 -0
  14. package/src/carousel/carousel.css +181 -0
  15. package/src/cascade-select/cascade-select.css +120 -0
  16. package/src/chart/chart.css +26 -0
  17. package/src/chat/chat-ai.css +352 -0
  18. package/src/chat/chat.css +231 -0
  19. package/src/checkbox/checkbox.css +74 -0
  20. package/src/color-picker/color-picker.css +183 -0
  21. package/src/combobox/combobox.css +101 -0
  22. package/src/command-palette/command-palette.css +234 -0
  23. package/src/confirm-popup/confirm-popup.css +47 -0
  24. package/src/date-picker/date-picker.css +61 -0
  25. package/src/date-range-presets/date-range-presets.css +49 -0
  26. package/src/datetime-picker/datetime-picker.css +84 -0
  27. package/src/dialog/dialog.css +56 -0
  28. package/src/direction-toggle/direction-toggle.css +55 -0
  29. package/src/divider/divider.css +93 -0
  30. package/src/drawer/drawer.css +130 -0
  31. package/src/dropdown-menu/dropdown-menu.css +108 -0
  32. package/src/editor/editor.css +140 -0
  33. package/src/empty-state/empty-state.css +114 -0
  34. package/src/field/field.css +148 -0
  35. package/src/file-upload/file-upload.css +207 -0
  36. package/src/form/form.css +63 -0
  37. package/src/input/input.css +70 -0
  38. package/src/input-group/input-group.css +125 -0
  39. package/src/input-mask/input-mask.css +39 -0
  40. package/src/input-otp/input-otp.css +87 -0
  41. package/src/kbd/kbd.css +67 -0
  42. package/src/link/link.css +104 -0
  43. package/src/list/list.css +166 -0
  44. package/src/menubar/menubar.css +62 -0
  45. package/src/number-input/number-input.css +115 -0
  46. package/src/overflow/overflow.css +43 -0
  47. package/src/overlay/overlay.css +45 -0
  48. package/src/overlay-badge/overlay-badge.css +118 -0
  49. package/src/pagination/pagination.css +146 -0
  50. package/src/password-input/password-input.css +155 -0
  51. package/src/popover/popover.css +71 -0
  52. package/src/progress-bar/progress-bar.css +113 -0
  53. package/src/radio/radio.css +58 -0
  54. package/src/rich-text/rich-text-editor.css +351 -0
  55. package/src/select/select.css +91 -0
  56. package/src/sheet/sheet.css +121 -0
  57. package/src/skeleton/skeleton.css +111 -0
  58. package/src/skip-link/skip-link.css +58 -0
  59. package/src/slider/slider.css +172 -0
  60. package/src/speed-dial/speed-dial.css +137 -0
  61. package/src/spinner/spinner.css +161 -0
  62. package/src/stepper/stepper.css +147 -0
  63. package/src/table/table-filters/filters.css +53 -0
  64. package/src/table/table.css +509 -0
  65. package/src/tabs/tabs.css +162 -0
  66. package/src/tag/tag.css +183 -0
  67. package/src/textarea/textarea.css +117 -0
  68. package/src/time-picker/time-picker.css +126 -0
  69. package/src/toast/toast.css +115 -0
  70. package/src/toggle/toggle.css +123 -0
  71. package/src/tooltip/tooltip.css +61 -0
  72. package/src/tree-select/tree-select.css +220 -0
  73. package/types/kouji-ui-components.d.ts +17 -1
@@ -0,0 +1,58 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ .kj-skip-link — WCAG 2.4.1 "skip to content" link.
3
+ Visually hidden (parked above the viewport, NOT display:none so
4
+ it stays in the tab order) until focused, then slides on-screen
5
+ with a solid high-contrast surface. Component tokens default to
6
+ shared-layer values from @kouji-ui/themes.
7
+ ──────────────────────────────────────────────────────────── */
8
+
9
+ @layer kj.component {
10
+ .kj-skip-link {
11
+ /* component knobs — themes / users can override any of these */
12
+ --kj-skip-link-bg: var(--kj-bg-primary);
13
+ --kj-skip-link-fg: var(--kj-fg-on-primary);
14
+ --kj-skip-link-radius: var(--kj-radius-field, 0.25rem);
15
+ --kj-skip-link-shadow: var(--kj-shadow-lg);
16
+ --kj-skip-link-offset: var(--kj-space-sm, 0.5rem);
17
+ --kj-skip-link-z: 1000;
18
+
19
+ position: fixed;
20
+ /* Parked just above the viewport — off-screen but focusable. */
21
+ top: 0;
22
+ left: var(--kj-skip-link-offset);
23
+ z-index: var(--kj-skip-link-z);
24
+ transform: translateY(-150%);
25
+ transition: transform var(--kj-transition, 150ms ease);
26
+
27
+ display: inline-block;
28
+ padding: 0.625rem 1rem;
29
+ min-height: 44px; /* WCAG 2.5.5 comfortable target once revealed */
30
+ box-sizing: border-box;
31
+
32
+ background: var(--kj-skip-link-bg);
33
+ color: var(--kj-skip-link-fg);
34
+ border-radius: var(--kj-skip-link-radius);
35
+ box-shadow: var(--kj-skip-link-shadow);
36
+ font: inherit;
37
+ font-weight: 600;
38
+ line-height: 1.4;
39
+ text-decoration: none;
40
+ white-space: nowrap;
41
+ }
42
+
43
+ /* Revealed on focus (:focus covers browsers/AT that don't match
44
+ :focus-visible for programmatic focus, e.g. skip-to-here). */
45
+ .kj-skip-link:focus,
46
+ .kj-skip-link:focus-visible {
47
+ transform: translateY(var(--kj-skip-link-offset));
48
+ outline: 3px solid var(--kj-fg-primary);
49
+ outline-offset: 2px;
50
+ }
51
+
52
+ /* Honour reduced-motion: reveal instantly rather than sliding. */
53
+ @media (prefers-reduced-motion: reduce) {
54
+ .kj-skip-link {
55
+ transition: none;
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,172 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ .kj-slider — wrapper around the KjSlider directive family.
3
+
4
+ The directive layer is headless. This wrapper translates the
5
+ directive context into pixels: track height/width, thumb size,
6
+ filled-portion offsets via the --kj-slider-start / --kj-slider-end
7
+ custom properties published by [kjSliderRange].
8
+ ──────────────────────────────────────────────────────────── */
9
+
10
+ @layer kj.component {
11
+ .kj-slider {
12
+ --kj-slider-track-thickness: 0.375rem; /* 6px */
13
+ --kj-slider-track-bg: var(--kj-bg-field);
14
+ --kj-slider-fill-bg: var(--kj-bg-primary);
15
+ --kj-slider-thumb-size: 1.25rem; /* 20px visual */
16
+ --kj-slider-thumb-hit: 2.75rem; /* 44px touch — WCAG 2.5.5 */
17
+ --kj-slider-thumb-bg: var(--kj-bg-primary);
18
+ --kj-slider-thumb-fg: var(--kj-fg-on-primary);
19
+ --kj-slider-thumb-border: 2px solid var(--kj-bg-body);
20
+ --kj-slider-tick-size: 0.25rem; /* 4px */
21
+ --kj-slider-tick-bg: var(--kj-fg-default);
22
+ --kj-slider-radius: 999px;
23
+ --kj-slider-vertical-length: 12rem; /* 192px default */
24
+
25
+ display: inline-flex;
26
+ align-items: center;
27
+ user-select: none;
28
+ -webkit-user-select: none;
29
+ }
30
+
31
+ .kj-slider[data-orientation="horizontal"] {
32
+ width: 100%;
33
+ min-height: var(--kj-slider-thumb-hit);
34
+ padding: calc((var(--kj-slider-thumb-hit) - var(--kj-slider-track-thickness)) / 2) 0;
35
+ }
36
+
37
+ .kj-slider[data-orientation="vertical"] {
38
+ flex-direction: column;
39
+ height: var(--kj-slider-vertical-length);
40
+ min-width: var(--kj-slider-thumb-hit);
41
+ padding: 0 calc((var(--kj-slider-thumb-hit) - var(--kj-slider-track-thickness)) / 2);
42
+ }
43
+
44
+ .kj-slider[data-disabled] {
45
+ opacity: 0.55;
46
+ pointer-events: none;
47
+ }
48
+
49
+ /* ── Track ───────────────────────────────────────────────── */
50
+ .kj-slider__track {
51
+ position: relative;
52
+ flex: 1 1 auto;
53
+ background: var(--kj-slider-track-bg);
54
+ border-radius: var(--kj-slider-radius);
55
+ cursor: pointer;
56
+ }
57
+
58
+ .kj-slider[data-orientation="horizontal"] .kj-slider__track {
59
+ height: var(--kj-slider-track-thickness);
60
+ width: 100%;
61
+ }
62
+
63
+ .kj-slider[data-orientation="vertical"] .kj-slider__track {
64
+ width: var(--kj-slider-track-thickness);
65
+ height: 100%;
66
+ }
67
+
68
+ /* ── Range fill ──────────────────────────────────────────── */
69
+ .kj-slider__range {
70
+ position: absolute;
71
+ background: var(--kj-slider-fill-bg);
72
+ border-radius: inherit;
73
+ }
74
+
75
+ .kj-slider[data-orientation="horizontal"] .kj-slider__range {
76
+ top: 0;
77
+ bottom: 0;
78
+ left: calc(var(--kj-slider-start, 0) * 100%);
79
+ right: calc((1 - var(--kj-slider-end, 0)) * 100%);
80
+ }
81
+
82
+ .kj-slider[data-orientation="vertical"] .kj-slider__range {
83
+ left: 0;
84
+ right: 0;
85
+ bottom: calc(var(--kj-slider-start, 0) * 100%);
86
+ top: calc((1 - var(--kj-slider-end, 0)) * 100%);
87
+ }
88
+
89
+ /* ── Thumb ───────────────────────────────────────────────── */
90
+ .kj-slider__thumb {
91
+ position: absolute;
92
+ width: var(--kj-slider-thumb-size);
93
+ height: var(--kj-slider-thumb-size);
94
+ background: var(--kj-slider-thumb-bg);
95
+ color: var(--kj-slider-thumb-fg);
96
+ border: var(--kj-slider-thumb-border);
97
+ border-radius: 50%;
98
+ box-shadow: 0 1px 3px rgb(0 0 0 / 0.2);
99
+ cursor: grab;
100
+ padding: 0;
101
+ transition: transform var(--kj-transition, 120ms ease), box-shadow var(--kj-transition, 120ms ease);
102
+ }
103
+
104
+ .kj-slider__thumb:hover { transform: scale(1.05); }
105
+ .kj-slider__thumb[data-dragging] { cursor: grabbing; transform: scale(1.1); }
106
+
107
+ .kj-slider__thumb[data-focus-visible] {
108
+ outline: 2px solid var(--kj-bg-primary);
109
+ outline-offset: 3px;
110
+ }
111
+
112
+ .kj-slider[data-orientation="horizontal"] .kj-slider__thumb {
113
+ top: 50%;
114
+ left: calc(var(--kj-slider-thumb-fraction, 0) * 100%);
115
+ transform: translate(-50%, -50%);
116
+ }
117
+
118
+ .kj-slider[data-orientation="horizontal"] .kj-slider__thumb:hover {
119
+ transform: translate(-50%, -50%) scale(1.05);
120
+ }
121
+
122
+ .kj-slider[data-orientation="horizontal"] .kj-slider__thumb[data-dragging] {
123
+ transform: translate(-50%, -50%) scale(1.1);
124
+ }
125
+
126
+ .kj-slider[data-orientation="vertical"] .kj-slider__thumb {
127
+ left: 50%;
128
+ bottom: calc(var(--kj-slider-thumb-fraction, 0) * 100%);
129
+ transform: translate(-50%, 50%);
130
+ }
131
+
132
+ .kj-slider[data-orientation="vertical"] .kj-slider__thumb:hover {
133
+ transform: translate(-50%, 50%) scale(1.05);
134
+ }
135
+
136
+ .kj-slider[data-orientation="vertical"] .kj-slider__thumb[data-dragging] {
137
+ transform: translate(-50%, 50%) scale(1.1);
138
+ }
139
+
140
+ /* ── Tick marks (decorative) ─────────────────────────────── */
141
+ .kj-slider__ticks {
142
+ position: absolute;
143
+ inset: 0;
144
+ pointer-events: none;
145
+ }
146
+
147
+ .kj-slider__tick {
148
+ position: absolute;
149
+ width: var(--kj-slider-tick-size);
150
+ height: var(--kj-slider-tick-size);
151
+ background: var(--kj-slider-tick-bg);
152
+ opacity: 0.5;
153
+ border-radius: 50%;
154
+ }
155
+
156
+ .kj-slider[data-orientation="horizontal"] .kj-slider__tick {
157
+ top: 50%;
158
+ left: calc(var(--kj-slider-tick-fraction, 0) * 100%);
159
+ transform: translate(-50%, -50%);
160
+ }
161
+
162
+ .kj-slider[data-orientation="vertical"] .kj-slider__tick {
163
+ left: 50%;
164
+ bottom: calc(var(--kj-slider-tick-fraction, 0) * 100%);
165
+ transform: translate(-50%, 50%);
166
+ }
167
+
168
+ /* ── Reduced motion ──────────────────────────────────────── */
169
+ @media (prefers-reduced-motion: reduce) {
170
+ .kj-slider__thumb { transition: none; }
171
+ }
172
+ }
@@ -0,0 +1,137 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ .kj-speed-dial — viewport-anchored FAB + fan-out action cluster
3
+ The root component is a positioning shell (fixed when
4
+ data-position="*-*"). The trigger and actions are buttons that
5
+ theme through .kj-button — overrides target the kj-button-*
6
+ tokens so theme presets still apply.
7
+ ──────────────────────────────────────────────────────────── */
8
+
9
+ @layer kj.component {
10
+ .kj-speed-dial {
11
+ --kj-speed-dial-gap: var(--kj-space-md);
12
+ --kj-speed-dial-edge: var(--kj-space-2xl);
13
+ --kj-speed-dial-trigger-size: 3.5rem;
14
+ --kj-speed-dial-action-size: 2.75rem;
15
+ --kj-speed-dial-shadow: 0 6px 16px rgb(0 0 0 / 0.18);
16
+ --kj-speed-dial-stagger: 30ms;
17
+ --kj-speed-dial-duration: 180ms;
18
+
19
+ position: relative;
20
+ display: inline-flex;
21
+ flex-direction: column;
22
+ align-items: center;
23
+ gap: var(--kj-speed-dial-gap);
24
+ z-index: 100;
25
+ }
26
+
27
+ .kj-speed-dial[data-direction='up'] { flex-direction: column-reverse; }
28
+ .kj-speed-dial[data-direction='down'] { flex-direction: column; }
29
+ .kj-speed-dial[data-direction='left'] { flex-direction: row-reverse; }
30
+ .kj-speed-dial[data-direction='right'] { flex-direction: row; }
31
+
32
+ .kj-speed-dial[data-position='bottom-right'] {
33
+ position: fixed;
34
+ right: var(--kj-speed-dial-edge);
35
+ bottom: var(--kj-speed-dial-edge);
36
+ }
37
+ .kj-speed-dial[data-position='bottom-left'] {
38
+ position: fixed;
39
+ left: var(--kj-speed-dial-edge);
40
+ bottom: var(--kj-speed-dial-edge);
41
+ }
42
+ .kj-speed-dial[data-position='top-right'] {
43
+ position: fixed;
44
+ right: var(--kj-speed-dial-edge);
45
+ top: var(--kj-speed-dial-edge);
46
+ }
47
+ .kj-speed-dial[data-position='top-left'] {
48
+ position: fixed;
49
+ left: var(--kj-speed-dial-edge);
50
+ top: var(--kj-speed-dial-edge);
51
+ }
52
+
53
+ .kj-speed-dial-trigger {
54
+ --kj-button-radius: 9999px;
55
+ --kj-button-padding-x: 0;
56
+ --kj-button-padding-y: 0;
57
+ --kj-button-shadow: var(--kj-speed-dial-shadow);
58
+
59
+ width: var(--kj-speed-dial-trigger-size);
60
+ height: var(--kj-speed-dial-trigger-size);
61
+ min-width: var(--kj-speed-dial-trigger-size);
62
+ flex: 0 0 auto;
63
+ }
64
+
65
+ .kj-speed-dial-trigger[data-state='open'] > * {
66
+ transform: rotate(45deg);
67
+ transition: transform var(--kj-speed-dial-duration) ease;
68
+ }
69
+ .kj-speed-dial-trigger > * {
70
+ display: inline-flex;
71
+ transition: transform var(--kj-speed-dial-duration) ease;
72
+ }
73
+
74
+ .kj-speed-dial-actions {
75
+ display: inline-flex;
76
+ gap: var(--kj-speed-dial-gap);
77
+ align-items: center;
78
+ justify-content: center;
79
+ pointer-events: none;
80
+ opacity: 0;
81
+ transition: opacity var(--kj-speed-dial-duration) ease;
82
+ }
83
+ .kj-speed-dial-actions[data-direction='up'] { flex-direction: column-reverse; }
84
+ .kj-speed-dial-actions[data-direction='down'] { flex-direction: column; }
85
+ .kj-speed-dial-actions[data-direction='left'] { flex-direction: row-reverse; }
86
+ .kj-speed-dial-actions[data-direction='right'] { flex-direction: row; }
87
+
88
+ .kj-speed-dial-actions[aria-hidden='true'] {
89
+ visibility: hidden;
90
+ pointer-events: none;
91
+ }
92
+ .kj-speed-dial-actions[data-expanded] {
93
+ opacity: 1;
94
+ visibility: visible;
95
+ pointer-events: auto;
96
+ }
97
+
98
+ .kj-speed-dial-actions[data-direction='up'] > * { transform: translateY(8px); }
99
+ .kj-speed-dial-actions[data-direction='down'] > * { transform: translateY(-8px); }
100
+ .kj-speed-dial-actions[data-direction='left'] > * { transform: translateX(8px); }
101
+ .kj-speed-dial-actions[data-direction='right'] > * { transform: translateX(-8px); }
102
+ .kj-speed-dial-actions[data-expanded] > * {
103
+ transform: translate3d(0, 0, 0);
104
+ transition: transform var(--kj-speed-dial-duration) ease,
105
+ opacity var(--kj-speed-dial-duration) ease;
106
+ transition-delay: calc(var(--kj-speed-dial-stagger) * var(--kj-speed-dial-i, 0));
107
+ }
108
+ .kj-speed-dial-actions > *:nth-child(1) { --kj-speed-dial-i: 0; }
109
+ .kj-speed-dial-actions > *:nth-child(2) { --kj-speed-dial-i: 1; }
110
+ .kj-speed-dial-actions > *:nth-child(3) { --kj-speed-dial-i: 2; }
111
+ .kj-speed-dial-actions > *:nth-child(4) { --kj-speed-dial-i: 3; }
112
+ .kj-speed-dial-actions > *:nth-child(5) { --kj-speed-dial-i: 4; }
113
+ .kj-speed-dial-actions > *:nth-child(6) { --kj-speed-dial-i: 5; }
114
+
115
+ .kj-speed-dial-action {
116
+ --kj-button-radius: 9999px;
117
+ --kj-button-padding-x: 0;
118
+ --kj-button-padding-y: 0;
119
+ --kj-button-shadow: var(--kj-speed-dial-shadow);
120
+
121
+ width: var(--kj-speed-dial-action-size);
122
+ height: var(--kj-speed-dial-action-size);
123
+ min-width: var(--kj-speed-dial-action-size);
124
+ flex: 0 0 auto;
125
+ }
126
+
127
+ @media (prefers-reduced-motion: reduce) {
128
+ .kj-speed-dial-trigger > *,
129
+ .kj-speed-dial-actions,
130
+ .kj-speed-dial-actions > * {
131
+ transition: none;
132
+ }
133
+ .kj-speed-dial-actions > * {
134
+ transform: none;
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,161 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ .kj-spinner — component-layer tokens + structural CSS
3
+
4
+ The directive owns role/label/data-attribute reflection. The CSS
5
+ below renders the visual glyph via the inner `.kj-spinner__glyph`
6
+ element (kept `aria-hidden="true"` in the template so AT only sees
7
+ the host's `role="status"` + `aria-label`). Variant + size + animation
8
+ routing all happens through `data-*` attributes on the wrapper host.
9
+ ──────────────────────────────────────────────────────────── */
10
+
11
+ @layer kj.component {
12
+ .kj-spinner {
13
+ --kj-spinner-size: 1.25rem; /* md default */
14
+ --kj-spinner-color: currentColor; /* neutral resolves to currentColor */
15
+ --kj-spinner-track: color-mix(in srgb, currentColor 20%, transparent);
16
+ --kj-spinner-duration: 0.7s;
17
+
18
+ display: inline-flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ inline-size: var(--kj-spinner-size);
22
+ block-size: var(--kj-spinner-size);
23
+ color: var(--kj-spinner-color);
24
+ /* Keep the glyph centred without offering a hit-target — spinners are
25
+ not interactive. */
26
+ pointer-events: none;
27
+ }
28
+
29
+ /* Sizes — circular shapes use --kj-spinner-size for both axes. Linear
30
+ shapes (dots / bars) override inline-size on the host below. */
31
+ .kj-spinner[data-size="xs"] { --kj-spinner-size: 0.75rem; }
32
+ .kj-spinner[data-size="sm"] { --kj-spinner-size: 1rem; }
33
+ .kj-spinner[data-size="md"] { --kj-spinner-size: 1.25rem; }
34
+ .kj-spinner[data-size="lg"] { --kj-spinner-size: 1.75rem; }
35
+
36
+ /* Variant routing — neutral inherits currentColor (matches the
37
+ daisyUI / lucide convention so a spinner inside a primary button
38
+ picks up the button's text colour). */
39
+ .kj-spinner[data-variant="neutral"] { --kj-spinner-color: currentColor; }
40
+ .kj-spinner[data-variant="primary"] { --kj-spinner-color: var(--kj-fg-primary); }
41
+ .kj-spinner[data-variant="secondary"] { --kj-spinner-color: var(--kj-fg-default); }
42
+ .kj-spinner[data-variant="success"] { --kj-spinner-color: var(--kj-fg-success, #16a34a); }
43
+ .kj-spinner[data-variant="warning"] { --kj-spinner-color: var(--kj-fg-warning, #d97706); }
44
+ .kj-spinner[data-variant="error"] { --kj-spinner-color: var(--kj-fg-danger, #dc2626); }
45
+ .kj-spinner[data-variant="info"] { --kj-spinner-color: var(--kj-fg-info, #2563eb); }
46
+
47
+ /* Glyph host — themes can repaint the inner element per data-animation. */
48
+ .kj-spinner__glyph {
49
+ display: inline-block;
50
+ inline-size: 100%;
51
+ block-size: 100%;
52
+ }
53
+
54
+ /* ── Animation: spin (default) — rotating arc ─────────────────────── */
55
+ .kj-spinner[data-animation="spin"] .kj-spinner__glyph {
56
+ border: 2px solid var(--kj-spinner-track);
57
+ border-block-start-color: var(--kj-spinner-color);
58
+ border-radius: 50%;
59
+ animation: kj-spinner-spin var(--kj-spinner-duration) linear infinite;
60
+ }
61
+
62
+ /* ── Animation: pulse — single fading circle ──────────────────────── */
63
+ .kj-spinner[data-animation="pulse"] .kj-spinner__glyph {
64
+ background: var(--kj-spinner-color);
65
+ border-radius: 50%;
66
+ animation: kj-spinner-pulse calc(var(--kj-spinner-duration) * 2) ease-in-out infinite;
67
+ }
68
+
69
+ /* ── Animation: dots — three pulsing dots side by side ────────────── */
70
+ .kj-spinner[data-animation="dots"] {
71
+ /* Linear shape — width is auto, height is the size token. */
72
+ inline-size: auto;
73
+ }
74
+ .kj-spinner[data-animation="dots"] .kj-spinner__glyph {
75
+ display: inline-flex;
76
+ gap: calc(var(--kj-spinner-size) * 0.25);
77
+ inline-size: auto;
78
+ block-size: 100%;
79
+ }
80
+ .kj-spinner[data-animation="dots"] .kj-spinner__glyph::before,
81
+ .kj-spinner[data-animation="dots"] .kj-spinner__glyph::after,
82
+ .kj-spinner[data-animation="dots"] .kj-spinner__glyph {
83
+ /* Each dot via pseudo + the glyph itself acts as a flex container. */
84
+ }
85
+ .kj-spinner[data-animation="dots"] .kj-spinner__glyph::before,
86
+ .kj-spinner[data-animation="dots"] .kj-spinner__glyph::after {
87
+ content: "";
88
+ inline-size: calc(var(--kj-spinner-size) * 0.3);
89
+ block-size: calc(var(--kj-spinner-size) * 0.3);
90
+ background: var(--kj-spinner-color);
91
+ border-radius: 50%;
92
+ animation: kj-spinner-dots calc(var(--kj-spinner-duration) * 2) ease-in-out infinite;
93
+ }
94
+ .kj-spinner[data-animation="dots"] .kj-spinner__glyph {
95
+ background: transparent;
96
+ }
97
+ /* Middle dot rendered via box-shadow on ::before for a 3-dot row without
98
+ extra DOM. The two pseudos animate offset; the middle dot is the glyph
99
+ element's own background dot. */
100
+ .kj-spinner[data-animation="dots"] .kj-spinner__glyph::after {
101
+ animation-delay: calc(var(--kj-spinner-duration) * 0.4);
102
+ }
103
+
104
+ /* ── Animation: bars — vertical bars rising/falling ───────────────── */
105
+ .kj-spinner[data-animation="bars"] {
106
+ inline-size: auto;
107
+ }
108
+ .kj-spinner[data-animation="bars"] .kj-spinner__glyph {
109
+ display: inline-flex;
110
+ align-items: stretch;
111
+ gap: calc(var(--kj-spinner-size) * 0.15);
112
+ inline-size: auto;
113
+ block-size: 100%;
114
+ }
115
+ .kj-spinner[data-animation="bars"] .kj-spinner__glyph::before,
116
+ .kj-spinner[data-animation="bars"] .kj-spinner__glyph::after {
117
+ content: "";
118
+ inline-size: calc(var(--kj-spinner-size) * 0.2);
119
+ background: var(--kj-spinner-color);
120
+ border-radius: calc(var(--kj-spinner-size) * 0.05);
121
+ animation: kj-spinner-bars calc(var(--kj-spinner-duration) * 1.6) ease-in-out infinite;
122
+ transform-origin: center;
123
+ }
124
+ .kj-spinner[data-animation="bars"] .kj-spinner__glyph::after {
125
+ animation-delay: calc(var(--kj-spinner-duration) * 0.4);
126
+ }
127
+
128
+ /* ── Reduced motion — directive sets `data-reduced-motion="true"` ──── */
129
+ /* WCAG 2.3.3 (AAA): swap continuous rotation for a slow opacity pulse. */
130
+ .kj-spinner[data-reduced-motion="true"] .kj-spinner__glyph,
131
+ .kj-spinner[data-reduced-motion="true"] .kj-spinner__glyph::before,
132
+ .kj-spinner[data-reduced-motion="true"] .kj-spinner__glyph::after {
133
+ animation: kj-spinner-reduced-motion 1.6s ease-in-out infinite !important;
134
+ }
135
+ }
136
+
137
+ /* Keyframes live outside the cascade layer so consumers can't accidentally
138
+ defeat them with a higher-priority unlayered rule. */
139
+ @keyframes kj-spinner-spin {
140
+ to { transform: rotate(360deg); }
141
+ }
142
+
143
+ @keyframes kj-spinner-pulse {
144
+ 0%, 100% { opacity: 0.35; transform: scale(0.85); }
145
+ 50% { opacity: 1; transform: scale(1); }
146
+ }
147
+
148
+ @keyframes kj-spinner-dots {
149
+ 0%, 80%, 100% { transform: scale(0.4); opacity: 0.4; }
150
+ 40% { transform: scale(1); opacity: 1; }
151
+ }
152
+
153
+ @keyframes kj-spinner-bars {
154
+ 0%, 40%, 100% { transform: scaleY(0.4); }
155
+ 20% { transform: scaleY(1); }
156
+ }
157
+
158
+ @keyframes kj-spinner-reduced-motion {
159
+ 0%, 100% { opacity: 0.45; }
160
+ 50% { opacity: 1; }
161
+ }
@@ -0,0 +1,147 @@
1
+ @layer kj.component {
2
+ .kj-stepper {
3
+ --kj-stepper-gap: var(--kj-space-md);
4
+ --kj-stepper-indicator-size: 2rem;
5
+ --kj-stepper-bg: var(--kj-bg-body);
6
+ --kj-stepper-border: var(--kj-border-default);
7
+ --kj-stepper-active: var(--kj-bg-primary);
8
+ --kj-stepper-active-fg: var(--kj-fg-on-primary);
9
+ --kj-stepper-completed: var(--kj-bg-success);
10
+ --kj-stepper-error: var(--kj-bg-danger);
11
+ --kj-stepper-muted: var(--kj-fg-muted);
12
+
13
+ display: flex;
14
+ flex-direction: column;
15
+ gap: var(--kj-space-lg);
16
+ list-style: none;
17
+ margin: 0;
18
+ padding: 0;
19
+ }
20
+
21
+ .kj-stepper[data-orientation="horizontal"] {
22
+ flex-direction: row;
23
+ flex-wrap: wrap;
24
+ align-items: flex-start;
25
+ }
26
+
27
+ .kj-step {
28
+ display: flex;
29
+ flex-direction: column;
30
+ gap: var(--kj-space-sm);
31
+ flex: 1 1 auto;
32
+ }
33
+
34
+ .kj-stepper[data-orientation="vertical"] .kj-step {
35
+ flex-direction: column;
36
+ align-items: stretch;
37
+ }
38
+
39
+ .kj-step__label {
40
+ appearance: none;
41
+ background: none;
42
+ border: 0;
43
+ padding: var(--kj-space-sm) var(--kj-space-md);
44
+ display: inline-flex;
45
+ align-items: center;
46
+ gap: var(--kj-space-sm);
47
+ color: var(--kj-stepper-muted);
48
+ font: 0.875rem / 1.4 var(--kj-font-sans);
49
+ font-weight: 600;
50
+ cursor: pointer;
51
+ border-radius: var(--kj-radius-field);
52
+ transition: var(--kj-transition);
53
+ text-align: left;
54
+ }
55
+
56
+ .kj-step__label::before {
57
+ content: counter(kj-step-counter);
58
+ counter-increment: kj-step-counter;
59
+ display: inline-flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ width: var(--kj-stepper-indicator-size);
63
+ height: var(--kj-stepper-indicator-size);
64
+ border-radius: 50%;
65
+ border: var(--kj-border) solid var(--kj-stepper-border);
66
+ background: var(--kj-stepper-bg);
67
+ color: var(--kj-stepper-muted);
68
+ font-size: 0.75rem;
69
+ flex-shrink: 0;
70
+ }
71
+
72
+ .kj-stepper { counter-reset: kj-step-counter; }
73
+
74
+ .kj-step__label:hover { color: var(--kj-fg-default); }
75
+ .kj-step__label:focus-visible { outline: 2px solid var(--kj-stepper-active); outline-offset: 2px; }
76
+
77
+ .kj-step[data-active="true"] > .kj-step__label,
78
+ .kj-step[data-active="true"] .kj-step__label {
79
+ color: var(--kj-fg-default);
80
+ }
81
+ .kj-step[data-active="true"] > .kj-step__label::before,
82
+ .kj-step[data-active="true"] .kj-step__label::before {
83
+ background: var(--kj-stepper-active);
84
+ border-color: var(--kj-stepper-active);
85
+ color: var(--kj-stepper-active-fg);
86
+ }
87
+
88
+ .kj-step[data-completed="true"] > .kj-step__label::before,
89
+ .kj-step[data-completed="true"] .kj-step__label::before {
90
+ background: var(--kj-stepper-completed);
91
+ border-color: var(--kj-stepper-completed);
92
+ color: var(--kj-fg-on-success, #fff);
93
+ content: "\2713"; /* check mark */
94
+ }
95
+
96
+ .kj-step[data-error="true"] > .kj-step__label::before,
97
+ .kj-step[data-error="true"] .kj-step__label::before {
98
+ background: var(--kj-stepper-error);
99
+ border-color: var(--kj-stepper-error);
100
+ color: var(--kj-fg-on-danger, #fff);
101
+ content: "!";
102
+ }
103
+
104
+ .kj-step[data-disabled="true"] > .kj-step__label,
105
+ .kj-step[data-disabled="true"] .kj-step__label,
106
+ .kj-step__label[aria-disabled="true"],
107
+ .kj-step__label[disabled] {
108
+ opacity: 0.45;
109
+ cursor: not-allowed;
110
+ }
111
+
112
+ .kj-step__content {
113
+ display: block;
114
+ color: var(--kj-fg-default);
115
+ font: 0.875rem / 1.5 var(--kj-font-sans);
116
+ padding: var(--kj-space-md);
117
+ background: var(--kj-bg-body);
118
+ border: var(--kj-border) solid var(--kj-stepper-border);
119
+ border-radius: var(--kj-radius-box);
120
+ }
121
+ .kj-step__content[hidden] { display: none; }
122
+
123
+ .kj-stepper-action {
124
+ appearance: none;
125
+ border: var(--kj-border) solid var(--kj-stepper-border);
126
+ background: var(--kj-bg-body);
127
+ color: var(--kj-fg-default);
128
+ padding: var(--kj-space-sm) var(--kj-space-md);
129
+ border-radius: var(--kj-radius-field);
130
+ font: 0.875rem / 1 var(--kj-font-sans);
131
+ cursor: pointer;
132
+ transition: var(--kj-transition);
133
+ }
134
+ .kj-stepper-action:hover:not([disabled]):not([aria-disabled="true"]) {
135
+ border-color: var(--kj-stepper-active);
136
+ color: var(--kj-stepper-active);
137
+ }
138
+ .kj-stepper-action:focus-visible { outline: 2px solid var(--kj-stepper-active); outline-offset: 2px; }
139
+ .kj-stepper-action[disabled],
140
+ .kj-stepper-action[aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
141
+
142
+ .kj-stepper-action--next {
143
+ background: var(--kj-stepper-active);
144
+ color: var(--kj-stepper-active-fg);
145
+ border-color: var(--kj-stepper-active);
146
+ }
147
+ }