@kouji-ui/components 0.9.2 → 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.
- package/package.json +1 -1
- package/src/accordion/accordion.css +68 -0
- package/src/action-sheet/action-sheet.css +79 -0
- package/src/alert/alert.css +133 -0
- package/src/avatar/avatar.css +93 -0
- package/src/badge/badge.css +59 -0
- package/src/breadcrumb/breadcrumb.css +145 -0
- package/src/button/button.css +295 -0
- package/src/button-group/button-group.css +114 -0
- package/src/calendar/calendar.css +129 -0
- package/src/card/card.css +120 -0
- package/src/carousel/carousel.css +181 -0
- package/src/cascade-select/cascade-select.css +120 -0
- package/src/chart/chart.css +26 -0
- package/src/chat/chat-ai.css +352 -0
- package/src/chat/chat.css +231 -0
- package/src/checkbox/checkbox.css +74 -0
- package/src/color-picker/color-picker.css +183 -0
- package/src/combobox/combobox.css +101 -0
- package/src/command-palette/command-palette.css +234 -0
- package/src/confirm-popup/confirm-popup.css +47 -0
- package/src/date-picker/date-picker.css +61 -0
- package/src/date-range-presets/date-range-presets.css +49 -0
- package/src/datetime-picker/datetime-picker.css +84 -0
- package/src/dialog/dialog.css +56 -0
- package/src/direction-toggle/direction-toggle.css +55 -0
- package/src/divider/divider.css +93 -0
- package/src/drawer/drawer.css +130 -0
- package/src/dropdown-menu/dropdown-menu.css +108 -0
- package/src/editor/editor.css +140 -0
- package/src/empty-state/empty-state.css +114 -0
- package/src/field/field.css +148 -0
- package/src/file-upload/file-upload.css +207 -0
- package/src/form/form.css +63 -0
- package/src/input/input.css +70 -0
- package/src/input-group/input-group.css +125 -0
- package/src/input-mask/input-mask.css +39 -0
- package/src/input-otp/input-otp.css +87 -0
- package/src/kbd/kbd.css +67 -0
- package/src/link/link.css +104 -0
- package/src/list/list.css +166 -0
- package/src/menubar/menubar.css +62 -0
- package/src/number-input/number-input.css +115 -0
- package/src/overflow/overflow.css +43 -0
- package/src/overlay/overlay.css +45 -0
- package/src/overlay-badge/overlay-badge.css +118 -0
- package/src/pagination/pagination.css +146 -0
- package/src/password-input/password-input.css +155 -0
- package/src/popover/popover.css +71 -0
- package/src/progress-bar/progress-bar.css +113 -0
- package/src/radio/radio.css +58 -0
- package/src/rich-text/rich-text-editor.css +351 -0
- package/src/select/select.css +91 -0
- package/src/sheet/sheet.css +121 -0
- package/src/skeleton/skeleton.css +111 -0
- package/src/skip-link/skip-link.css +58 -0
- package/src/slider/slider.css +172 -0
- package/src/speed-dial/speed-dial.css +137 -0
- package/src/spinner/spinner.css +161 -0
- package/src/stepper/stepper.css +147 -0
- package/src/table/table-filters/filters.css +53 -0
- package/src/table/table.css +509 -0
- package/src/tabs/tabs.css +162 -0
- package/src/tag/tag.css +183 -0
- package/src/textarea/textarea.css +117 -0
- package/src/time-picker/time-picker.css +126 -0
- package/src/toast/toast.css +115 -0
- package/src/toggle/toggle.css +123 -0
- package/src/tooltip/tooltip.css +61 -0
- package/src/tree-select/tree-select.css +220 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-file-upload — themed wrapper for the headless KjFileUpload
|
|
3
|
+
directive family. The directives own state + ARIA; CSS only
|
|
4
|
+
handles drop-zone / list / row visuals, drag-active highlight,
|
|
5
|
+
and per-row status icons.
|
|
6
|
+
──────────────────────────────────────────────────────────── */
|
|
7
|
+
|
|
8
|
+
@layer kj.component {
|
|
9
|
+
.kj-file-upload {
|
|
10
|
+
--kj-file-upload-gap: var(--kj-space-md, 0.75rem);
|
|
11
|
+
--kj-file-upload-radius: var(--kj-radius-md, 0.5rem);
|
|
12
|
+
--kj-file-upload-border: 1px dashed var(--kj-border-default, #d4d4d8);
|
|
13
|
+
--kj-file-upload-border-active: 2px dashed var(--kj-bg-primary, #2563eb);
|
|
14
|
+
--kj-file-upload-bg: var(--kj-bg-field, transparent);
|
|
15
|
+
--kj-file-upload-bg-active: color-mix(in oklch, var(--kj-bg-primary) 8%, transparent);
|
|
16
|
+
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: var(--kj-file-upload-gap);
|
|
20
|
+
width: 100%;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.kj-file-upload[data-disabled] {
|
|
24
|
+
opacity: 0.55;
|
|
25
|
+
pointer-events: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ── Drop-zone ───────────────────────────────────────────── */
|
|
29
|
+
.kj-file-upload__dropzone {
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
gap: var(--kj-space-sm, 0.5rem);
|
|
35
|
+
padding: var(--kj-space-xl, 2rem) var(--kj-space-md, 1rem);
|
|
36
|
+
border: var(--kj-file-upload-border);
|
|
37
|
+
border-radius: var(--kj-file-upload-radius);
|
|
38
|
+
background: var(--kj-file-upload-bg);
|
|
39
|
+
color: var(--kj-fg-default, currentColor);
|
|
40
|
+
text-align: center;
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
transition: background-color 150ms ease-out, border-color 150ms ease-out;
|
|
43
|
+
min-height: 8rem; /* roomy enough for a thumbnail grid in the gallery example */
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.kj-file-upload__dropzone:focus-visible {
|
|
47
|
+
outline: 2px solid var(--kj-bg-primary, #2563eb);
|
|
48
|
+
outline-offset: 2px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.kj-file-upload__dropzone[data-drag-active] {
|
|
52
|
+
border: var(--kj-file-upload-border-active);
|
|
53
|
+
background: var(--kj-file-upload-bg-active);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.kj-file-upload__dropzone[data-disabled] {
|
|
57
|
+
cursor: not-allowed;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.kj-file-upload__dropzone-hint {
|
|
61
|
+
font-size: 0.875rem;
|
|
62
|
+
color: var(--kj-fg-muted, #71717a);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ── Toolbar (trigger button) ───────────────────────────── */
|
|
66
|
+
.kj-file-upload__toolbar {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-wrap: wrap;
|
|
69
|
+
gap: var(--kj-space-sm, 0.5rem);
|
|
70
|
+
align-items: center;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ── List + rows ────────────────────────────────────────── */
|
|
74
|
+
.kj-file-upload__list {
|
|
75
|
+
list-style: none;
|
|
76
|
+
margin: 0;
|
|
77
|
+
padding: 0;
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
gap: var(--kj-space-xs, 0.25rem);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.kj-file-upload__list:empty {
|
|
84
|
+
display: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.kj-file-upload__row {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: var(--kj-space-sm, 0.5rem);
|
|
91
|
+
padding: var(--kj-space-sm, 0.5rem) var(--kj-space-md, 0.75rem);
|
|
92
|
+
border: 1px solid var(--kj-border-muted, #e4e4e7);
|
|
93
|
+
border-radius: var(--kj-file-upload-radius);
|
|
94
|
+
background: var(--kj-bg-surface, transparent);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.kj-file-upload__row[data-status="error"] {
|
|
98
|
+
border-color: var(--kj-fg-danger, #ef4444);
|
|
99
|
+
background: color-mix(in oklch, var(--kj-bg-danger, #ef4444) 8%, transparent);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.kj-file-upload__row[data-status="done"] {
|
|
103
|
+
border-color: var(--kj-fg-success, #10b981);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.kj-file-upload__row[data-status="cancelled"] {
|
|
107
|
+
opacity: 0.6;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.kj-file-upload__thumb {
|
|
111
|
+
width: 2.5rem;
|
|
112
|
+
height: 2.5rem;
|
|
113
|
+
border-radius: var(--kj-radius-sm, 0.25rem);
|
|
114
|
+
object-fit: cover;
|
|
115
|
+
background: var(--kj-bg-surface, #e4e4e7);
|
|
116
|
+
flex: none;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.kj-file-upload__row-body {
|
|
120
|
+
flex: 1 1 auto;
|
|
121
|
+
min-width: 0;
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
gap: 0.25rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.kj-file-upload__row-meta {
|
|
128
|
+
display: flex;
|
|
129
|
+
justify-content: space-between;
|
|
130
|
+
gap: var(--kj-space-sm, 0.5rem);
|
|
131
|
+
font-size: 0.875rem;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.kj-file-upload__row-name {
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
text-overflow: ellipsis;
|
|
137
|
+
white-space: nowrap;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.kj-file-upload__row-size {
|
|
141
|
+
color: var(--kj-fg-muted, #71717a);
|
|
142
|
+
font-variant-numeric: tabular-nums;
|
|
143
|
+
flex: none;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.kj-file-upload__row-status {
|
|
147
|
+
font-size: 0.75rem;
|
|
148
|
+
color: var(--kj-fg-muted, #71717a);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.kj-file-upload__row[data-status="error"] .kj-file-upload__row-status {
|
|
152
|
+
color: var(--kj-fg-danger, #ef4444);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.kj-file-upload__row[data-status="done"] .kj-file-upload__row-status {
|
|
156
|
+
color: var(--kj-fg-success, #10b981);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.kj-file-upload__row-actions {
|
|
160
|
+
display: flex;
|
|
161
|
+
gap: var(--kj-space-xs, 0.25rem);
|
|
162
|
+
align-items: center;
|
|
163
|
+
flex: none;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Status icons — pure CSS, ::before content covers ✓ ✕ … */
|
|
167
|
+
.kj-file-upload__row-icon {
|
|
168
|
+
width: 1rem;
|
|
169
|
+
height: 1rem;
|
|
170
|
+
display: inline-flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
justify-content: center;
|
|
173
|
+
font-size: 0.875rem;
|
|
174
|
+
line-height: 1;
|
|
175
|
+
}
|
|
176
|
+
.kj-file-upload__row-icon[data-status="done"] { color: var(--kj-fg-success, #10b981); }
|
|
177
|
+
.kj-file-upload__row-icon[data-status="done"]::before { content: "✓"; }
|
|
178
|
+
.kj-file-upload__row-icon[data-status="error"] { color: var(--kj-fg-danger, #ef4444); }
|
|
179
|
+
.kj-file-upload__row-icon[data-status="error"]::before { content: "✕"; }
|
|
180
|
+
.kj-file-upload__row-icon[data-status="uploading"]::before { content: "…"; }
|
|
181
|
+
|
|
182
|
+
.kj-file-upload__remove {
|
|
183
|
+
background: transparent;
|
|
184
|
+
border: 0;
|
|
185
|
+
cursor: pointer;
|
|
186
|
+
color: inherit;
|
|
187
|
+
padding: 0.25rem 0.5rem;
|
|
188
|
+
border-radius: var(--kj-radius-sm, 0.25rem);
|
|
189
|
+
font-size: 0.875rem;
|
|
190
|
+
min-width: 44px;
|
|
191
|
+
min-height: 44px; /* WCAG 2.5.5 touch target floor */
|
|
192
|
+
}
|
|
193
|
+
.kj-file-upload__remove:focus-visible {
|
|
194
|
+
outline: 2px solid var(--kj-bg-primary, #2563eb);
|
|
195
|
+
outline-offset: 2px;
|
|
196
|
+
}
|
|
197
|
+
.kj-file-upload__remove:hover {
|
|
198
|
+
background: var(--kj-bg-surface, #e4e4e7);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* Reduced motion: collapse drop-zone hover transitions */
|
|
202
|
+
@media (prefers-reduced-motion: reduce) {
|
|
203
|
+
.kj-file-upload__dropzone {
|
|
204
|
+
transition: none;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
@layer kj.component {
|
|
2
|
+
.kj-form {
|
|
3
|
+
/* component knobs — themes/users override any of these */
|
|
4
|
+
--kj-form-gap: var(--kj-space-md, 0.75rem);
|
|
5
|
+
--kj-form-actions-gap: var(--kj-space-sm, 0.5rem);
|
|
6
|
+
--kj-form-summary-bg: var(--kj-bg-surface);
|
|
7
|
+
--kj-form-summary-fg: var(--kj-fg-default);
|
|
8
|
+
--kj-form-summary-accent: var(--kj-fg-danger, #dc2626);
|
|
9
|
+
--kj-form-summary-radius: var(--kj-radius-box, 0.5rem);
|
|
10
|
+
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
gap: var(--kj-form-gap);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.kj-form[data-submitting] {
|
|
17
|
+
cursor: progress;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Actions row — typically at the bottom; flex with gap, right-aligned by default. */
|
|
21
|
+
.kj-form-actions {
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: flex-end;
|
|
25
|
+
gap: var(--kj-form-actions-gap);
|
|
26
|
+
margin-top: var(--kj-space-sm, 0.5rem);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.kj-form-actions[data-align="start"] { justify-content: flex-start; }
|
|
30
|
+
.kj-form-actions[data-align="between"] { justify-content: space-between; }
|
|
31
|
+
.kj-form-actions[data-align="center"] { justify-content: center; }
|
|
32
|
+
|
|
33
|
+
/* Error summary — destructive-tinted alert chrome. Hidden when no errors. */
|
|
34
|
+
.kj-form-summary {
|
|
35
|
+
display: none;
|
|
36
|
+
background: var(--kj-form-summary-bg);
|
|
37
|
+
color: var(--kj-form-summary-fg);
|
|
38
|
+
border: var(--kj-border, 1px) solid var(--kj-form-summary-accent);
|
|
39
|
+
border-left: 4px solid var(--kj-form-summary-accent);
|
|
40
|
+
border-radius: var(--kj-form-summary-radius);
|
|
41
|
+
padding: var(--kj-space-md, 0.75rem) var(--kj-space-lg, 1rem);
|
|
42
|
+
font: var(--kj-text-sm, 0.875rem) / 1.45 var(--kj-font-sans, sans-serif);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.kj-form-summary[data-visible] {
|
|
46
|
+
display: block;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.kj-form-summary__title {
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
margin: 0 0 var(--kj-space-xs, 0.25rem) 0;
|
|
52
|
+
color: var(--kj-form-summary-accent);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.kj-form-summary__list {
|
|
56
|
+
margin: 0;
|
|
57
|
+
padding-left: var(--kj-space-lg, 1rem);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.kj-form-summary__list li {
|
|
61
|
+
margin: 0.125rem 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-input — component-layer tokens + structural CSS
|
|
3
|
+
Same pattern as button.css: tokens default to shared layer,
|
|
4
|
+
variants/states retarget tokens, structural CSS reads tokens only.
|
|
5
|
+
──────────────────────────────────────────────────────────── */
|
|
6
|
+
|
|
7
|
+
@layer kj.component {
|
|
8
|
+
.kj-input {
|
|
9
|
+
--kj-input-bg: var(--kj-bg-field);
|
|
10
|
+
--kj-input-fg: var(--kj-fg-default);
|
|
11
|
+
--kj-input-border-color: var(--kj-border-default);
|
|
12
|
+
--kj-input-border-style: solid;
|
|
13
|
+
--kj-input-border-width: var(--kj-border);
|
|
14
|
+
--kj-input-radius: var(--kj-radius-field);
|
|
15
|
+
--kj-input-padding-x: var(--kj-space-md);
|
|
16
|
+
--kj-input-padding-y: var(--kj-space-sm);
|
|
17
|
+
--kj-input-font: var(--kj-font-sans);
|
|
18
|
+
--kj-input-font-size: var(--kj-text-sm);
|
|
19
|
+
--kj-input-placeholder-fg: var(--kj-fg-muted);
|
|
20
|
+
/* Match button md (36px) so inputs and buttons line up in form rows.
|
|
21
|
+
Don't set `align-self` — in column-flex parents (form-field stacks),
|
|
22
|
+
`center` horizontally shifts the host; explicit height alone is what
|
|
23
|
+
prevents row-flex parents from stretching the input vertically. */
|
|
24
|
+
--kj-input-height: var(--kj-ctl-h-md);
|
|
25
|
+
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
width: 100%;
|
|
28
|
+
height: var(--kj-input-height);
|
|
29
|
+
background: var(--kj-input-bg);
|
|
30
|
+
color: var(--kj-input-fg);
|
|
31
|
+
border: var(--kj-input-border-width) var(--kj-input-border-style) var(--kj-input-border-color);
|
|
32
|
+
border-radius: var(--kj-input-radius);
|
|
33
|
+
padding: var(--kj-input-padding-y) var(--kj-input-padding-x);
|
|
34
|
+
font: var(--kj-input-font-size) / 1.4 var(--kj-input-font);
|
|
35
|
+
transition: var(--kj-transition);
|
|
36
|
+
}
|
|
37
|
+
/* Per-size heights mirror the button scale. */
|
|
38
|
+
.kj-input[data-size="xs"] { --kj-input-height: var(--kj-ctl-h-xs); --kj-input-font-size: 0.75rem; --kj-input-padding-x: var(--kj-space-sm); --kj-input-padding-y: 0; }
|
|
39
|
+
.kj-input[data-size="sm"] { --kj-input-height: var(--kj-ctl-h-sm); --kj-input-font-size: 0.8125rem; }
|
|
40
|
+
.kj-input[data-size="lg"] { --kj-input-height: var(--kj-ctl-h-lg); --kj-input-font-size: 1rem; }
|
|
41
|
+
|
|
42
|
+
/* Variant: sunken — inputs sit on `--kj-bg-body` so they read distinctly
|
|
43
|
+
against a card / surface parent. Used on the landing showcase where
|
|
44
|
+
cards already use `--kj-bg-surface`; mirrors the design's pattern of
|
|
45
|
+
"input on page bg, card around it on surface". */
|
|
46
|
+
.kj-input[data-variant="sunken"] {
|
|
47
|
+
--kj-input-bg: var(--kj-bg-body);
|
|
48
|
+
}
|
|
49
|
+
.kj-input::placeholder { color: var(--kj-input-placeholder-fg); }
|
|
50
|
+
|
|
51
|
+
.kj-input:focus-visible {
|
|
52
|
+
outline: 2px solid var(--kj-bg-primary);
|
|
53
|
+
outline-offset: 2px;
|
|
54
|
+
--kj-input-border-color: var(--kj-border-focus, var(--kj-bg-primary));
|
|
55
|
+
}
|
|
56
|
+
.kj-input[aria-invalid="true"] {
|
|
57
|
+
--kj-input-border-color: var(--kj-border-danger, var(--kj-fg-danger));
|
|
58
|
+
}
|
|
59
|
+
.kj-input[aria-disabled="true"], .kj-input:disabled {
|
|
60
|
+
opacity: 0.55;
|
|
61
|
+
cursor: not-allowed;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.kj-input[type="color"] {
|
|
65
|
+
inline-size: 44px;
|
|
66
|
+
block-size: 32px;
|
|
67
|
+
padding: 2px;
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-input-group — flex wrapper that joins an input with
|
|
3
|
+
prefix/suffix addons into a single visually-unified field.
|
|
4
|
+
──────────────────────────────────────────────────────────── */
|
|
5
|
+
|
|
6
|
+
@layer kj.component {
|
|
7
|
+
.kj-input-group {
|
|
8
|
+
--kj-input-group-border-color: var(--kj-border-default);
|
|
9
|
+
--kj-input-group-border-width: var(--kj-border, 1px);
|
|
10
|
+
--kj-input-group-radius: var(--kj-radius-field);
|
|
11
|
+
--kj-input-group-addon-bg: var(--kj-bg-surface);
|
|
12
|
+
--kj-input-group-addon-fg: var(--kj-fg-default);
|
|
13
|
+
--kj-input-group-addon-font: var(--kj-font-sans);
|
|
14
|
+
--kj-input-group-addon-size: var(--kj-text-sm);
|
|
15
|
+
--kj-input-group-padding-x: var(--kj-space-md);
|
|
16
|
+
--kj-input-group-padding-y: var(--kj-space-sm);
|
|
17
|
+
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
align-items: stretch;
|
|
20
|
+
border-radius: var(--kj-input-group-radius);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* ── Vertical layout ──────────────────────────────────────────────────── */
|
|
24
|
+
.kj-input-group[data-orientation='vertical'] {
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
align-items: stretch;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* ── Shared border collapse ───────────────────────────────────────────── */
|
|
30
|
+
/* Every child (input or addon) shares the same border so they appear as
|
|
31
|
+
one component. We zero out the border-radius on inner edges and use a
|
|
32
|
+
negative margin to collapse duplicate borders. */
|
|
33
|
+
|
|
34
|
+
.kj-input-group [kjInput],
|
|
35
|
+
.kj-input-group .kj-input,
|
|
36
|
+
.kj-input-group [kjInputGroupAddon] {
|
|
37
|
+
border-radius: 0;
|
|
38
|
+
flex: 0 0 auto;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Give the native input flex-grow so it fills remaining space. */
|
|
42
|
+
.kj-input-group [kjInput],
|
|
43
|
+
.kj-input-group .kj-input {
|
|
44
|
+
flex: 1 1 auto;
|
|
45
|
+
min-width: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Horizontal: collapse left border on non-first children. */
|
|
49
|
+
.kj-input-group:not([data-orientation='vertical']) > * + [kjInput],
|
|
50
|
+
.kj-input-group:not([data-orientation='vertical']) > * + [kjInputGroupAddon],
|
|
51
|
+
.kj-input-group:not([data-orientation='vertical']) > * + kj-input-group-addon,
|
|
52
|
+
.kj-input-group:not([data-orientation='vertical']) > kj-input-group-addon + * {
|
|
53
|
+
margin-left: -1px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Vertical: collapse top border on non-first children. */
|
|
57
|
+
.kj-input-group[data-orientation='vertical'] > * + [kjInput],
|
|
58
|
+
.kj-input-group[data-orientation='vertical'] > * + [kjInputGroupAddon],
|
|
59
|
+
.kj-input-group[data-orientation='vertical'] > * + kj-input-group-addon,
|
|
60
|
+
.kj-input-group[data-orientation='vertical'] > kj-input-group-addon + * {
|
|
61
|
+
margin-top: -1px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ── Outer-corner rounding ──────────────────────────────────────────────
|
|
65
|
+
The first and last child get rounded corners on their outer edges only,
|
|
66
|
+
giving the group its pill/rounded-box appearance. */
|
|
67
|
+
|
|
68
|
+
/* Horizontal — first child */
|
|
69
|
+
.kj-input-group:not([data-orientation='vertical']) > :first-child,
|
|
70
|
+
.kj-input-group:not([data-orientation='vertical']) > :first-child [kjInput],
|
|
71
|
+
.kj-input-group:not([data-orientation='vertical']) > :first-child [kjInputGroupAddon] {
|
|
72
|
+
border-top-left-radius: var(--kj-input-group-radius);
|
|
73
|
+
border-bottom-left-radius: var(--kj-input-group-radius);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Horizontal — last child */
|
|
77
|
+
.kj-input-group:not([data-orientation='vertical']) > :last-child,
|
|
78
|
+
.kj-input-group:not([data-orientation='vertical']) > :last-child [kjInput],
|
|
79
|
+
.kj-input-group:not([data-orientation='vertical']) > :last-child [kjInputGroupAddon] {
|
|
80
|
+
border-top-right-radius: var(--kj-input-group-radius);
|
|
81
|
+
border-bottom-right-radius: var(--kj-input-group-radius);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Vertical — first child */
|
|
85
|
+
.kj-input-group[data-orientation='vertical'] > :first-child,
|
|
86
|
+
.kj-input-group[data-orientation='vertical'] > :first-child [kjInput] {
|
|
87
|
+
border-top-left-radius: var(--kj-input-group-radius);
|
|
88
|
+
border-top-right-radius: var(--kj-input-group-radius);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Vertical — last child */
|
|
92
|
+
.kj-input-group[data-orientation='vertical'] > :last-child,
|
|
93
|
+
.kj-input-group[data-orientation='vertical'] > :last-child [kjInput] {
|
|
94
|
+
border-bottom-left-radius: var(--kj-input-group-radius);
|
|
95
|
+
border-bottom-right-radius: var(--kj-input-group-radius);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* ── Addon skin ─────────────────────────────────────────────────────── */
|
|
99
|
+
.kj-input-group__addon {
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
padding: var(--kj-input-group-padding-y) var(--kj-input-group-padding-x);
|
|
103
|
+
background: var(--kj-input-group-addon-bg);
|
|
104
|
+
color: var(--kj-input-group-addon-fg);
|
|
105
|
+
border: var(--kj-input-group-border-width) solid var(--kj-input-group-border-color);
|
|
106
|
+
font: var(--kj-input-group-addon-size) / 1.4 var(--kj-input-group-addon-font);
|
|
107
|
+
transition: var(--kj-transition);
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
user-select: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* ── Focus — lift focused child above siblings so its border isn't clipped */
|
|
113
|
+
.kj-input-group [kjInput]:focus-visible,
|
|
114
|
+
.kj-input-group .kj-input:focus-visible,
|
|
115
|
+
.kj-input-group .kj-button:focus-visible {
|
|
116
|
+
position: relative;
|
|
117
|
+
z-index: 1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ── Disabled state ─────────────────────────────────────────────────── */
|
|
121
|
+
.kj-input-group[data-disabled] {
|
|
122
|
+
opacity: 0.55;
|
|
123
|
+
pointer-events: none;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* Input mask wrapper — inherits base input visual tokens */
|
|
2
|
+
|
|
3
|
+
.kj-input-mask {
|
|
4
|
+
display: contents;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.kj-input-mask__input {
|
|
8
|
+
/* Inherits the same base-input tokens used by .kj-input. Explicit height
|
|
9
|
+
contract (matches input.css / button.css) so masked inputs line up with
|
|
10
|
+
sibling inputs in a form row. */
|
|
11
|
+
--kj-input-mask-height: 2.25rem;
|
|
12
|
+
background: var(--kj-bg-field);
|
|
13
|
+
color: var(--kj-fg-default);
|
|
14
|
+
border: var(--kj-border, 1px) solid var(--kj-border-default);
|
|
15
|
+
border-radius: var(--kj-radius-field, 0.375rem);
|
|
16
|
+
padding: 0 var(--kj-space-md, 0.75rem);
|
|
17
|
+
font: var(--kj-text-sm, 0.875rem) / 1.4 var(--kj-font-sans, sans-serif);
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: var(--kj-input-mask-height);
|
|
20
|
+
font-variant-numeric: tabular-nums;
|
|
21
|
+
}
|
|
22
|
+
.kj-input-mask__input[data-size="sm"] { --kj-input-mask-height: 2rem; font-size: 0.8125rem; }
|
|
23
|
+
.kj-input-mask__input[data-size="lg"] { --kj-input-mask-height: 2.75rem; font-size: 1rem; }
|
|
24
|
+
|
|
25
|
+
.kj-input-mask__input:focus-visible {
|
|
26
|
+
outline: 2px solid var(--kj-fg-primary);
|
|
27
|
+
outline-offset: 2px;
|
|
28
|
+
border-color: var(--kj-fg-primary);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.kj-input-mask__input[aria-invalid='true'] {
|
|
32
|
+
border-color: var(--kj-fg-danger, #ef4444);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.kj-input-mask__input[data-disabled],
|
|
36
|
+
.kj-input-mask__input:disabled {
|
|
37
|
+
opacity: 0.5;
|
|
38
|
+
cursor: not-allowed;
|
|
39
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-input-otp — OTP input widget.
|
|
3
|
+
|
|
4
|
+
Each cell is a square input (min 44×44 — WCAG 2.5.5).
|
|
5
|
+
Cells sit in a flex row with a small gap between them.
|
|
6
|
+
A decorative separator dash can appear between groups.
|
|
7
|
+
──────────────────────────────────────────────────────────── */
|
|
8
|
+
|
|
9
|
+
@layer kj.component {
|
|
10
|
+
.kj-input-otp {
|
|
11
|
+
display: inline-flex;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.kj-input-otp__group {
|
|
15
|
+
--kj-otp-cell-size: 2.75rem; /* 44px — WCAG 2.5.5 touch target */
|
|
16
|
+
--kj-otp-cell-gap: 0.5rem;
|
|
17
|
+
--kj-otp-cell-bg: var(--kj-bg-field);
|
|
18
|
+
--kj-otp-cell-fg: var(--kj-fg-default);
|
|
19
|
+
--kj-otp-cell-border-color: var(--kj-border-default);
|
|
20
|
+
--kj-otp-cell-border-width: var(--kj-border);
|
|
21
|
+
--kj-otp-cell-radius: var(--kj-radius-field);
|
|
22
|
+
--kj-otp-cell-font: var(--kj-font-sans);
|
|
23
|
+
--kj-otp-cell-font-size: var(--kj-text-base);
|
|
24
|
+
|
|
25
|
+
display: inline-flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
gap: var(--kj-otp-cell-gap);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.kj-input-otp__cell {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
width: var(--kj-otp-cell-size);
|
|
35
|
+
height: var(--kj-otp-cell-size);
|
|
36
|
+
min-width: var(--kj-otp-cell-size);
|
|
37
|
+
min-height: var(--kj-otp-cell-size);
|
|
38
|
+
background: var(--kj-otp-cell-bg);
|
|
39
|
+
color: var(--kj-otp-cell-fg);
|
|
40
|
+
border: var(--kj-otp-cell-border-width) solid var(--kj-otp-cell-border-color);
|
|
41
|
+
border-radius: var(--kj-otp-cell-radius);
|
|
42
|
+
font: var(--kj-otp-cell-font-size) / 1 var(--kj-otp-cell-font);
|
|
43
|
+
text-align: center;
|
|
44
|
+
caret-color: var(--kj-fg-primary);
|
|
45
|
+
transition: border-color var(--kj-transition), box-shadow var(--kj-transition);
|
|
46
|
+
/* Remove default browser padding so content stays centred */
|
|
47
|
+
padding: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Focus ring via data-focus-visible (KjFocusRing on each cell) */
|
|
51
|
+
.kj-input-otp__cell[data-focus-visible],
|
|
52
|
+
.kj-input-otp__cell:focus-visible {
|
|
53
|
+
outline: 2px solid var(--kj-fg-primary);
|
|
54
|
+
outline-offset: 2px;
|
|
55
|
+
--kj-otp-cell-border-color: var(--kj-fg-primary);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Invalid state */
|
|
59
|
+
.kj-input-otp__group[data-invalid] .kj-input-otp__cell,
|
|
60
|
+
.kj-input-otp__group[aria-invalid="true"] .kj-input-otp__cell {
|
|
61
|
+
--kj-otp-cell-border-color: var(--kj-fg-danger);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Disabled state */
|
|
65
|
+
.kj-input-otp__group[data-disabled] .kj-input-otp__cell,
|
|
66
|
+
.kj-input-otp__cell:disabled {
|
|
67
|
+
opacity: 0.55;
|
|
68
|
+
cursor: not-allowed;
|
|
69
|
+
background: var(--kj-bg-surface);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Readonly */
|
|
73
|
+
.kj-input-otp__cell:read-only {
|
|
74
|
+
background: var(--kj-bg-surface);
|
|
75
|
+
cursor: default;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Separator dash between cell groups */
|
|
79
|
+
.kj-input-otp__separator {
|
|
80
|
+
display: inline-flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
color: var(--kj-fg-muted);
|
|
83
|
+
font-size: var(--kj-text-sm);
|
|
84
|
+
user-select: none;
|
|
85
|
+
padding: 0 0.125rem;
|
|
86
|
+
}
|
|
87
|
+
}
|
package/src/kbd/kbd.css
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-kbd — component-layer tokens + structural CSS
|
|
3
|
+
|
|
4
|
+
Theme-token-only. The directive sets `data-size` on the host;
|
|
5
|
+
the rest is a small "raised key" silhouette: 1px border + soft
|
|
6
|
+
inner shadow + monospace text. No variant — the kbd is tonally
|
|
7
|
+
neutral (see analysis Open question section).
|
|
8
|
+
──────────────────────────────────────────────────────────── */
|
|
9
|
+
|
|
10
|
+
@layer kj.component {
|
|
11
|
+
.kj-kbd {
|
|
12
|
+
--kj-kbd-bg: var(--kj-bg-surface);
|
|
13
|
+
--kj-kbd-fg: var(--kj-fg-default);
|
|
14
|
+
--kj-kbd-border-color: var(--kj-border-default);
|
|
15
|
+
--kj-kbd-border-width: var(--kj-border, 1px);
|
|
16
|
+
--kj-kbd-radius: var(--kj-radius-selector, 0.25rem);
|
|
17
|
+
--kj-kbd-padding-x: 0.4em;
|
|
18
|
+
--kj-kbd-padding-y: 0.15em;
|
|
19
|
+
--kj-kbd-font: var(--kj-font-mono);
|
|
20
|
+
--kj-kbd-font-size: var(--kj-text-sm);
|
|
21
|
+
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
vertical-align: baseline;
|
|
26
|
+
|
|
27
|
+
background: var(--kj-kbd-bg);
|
|
28
|
+
color: var(--kj-kbd-fg);
|
|
29
|
+
border: var(--kj-kbd-border-width) solid var(--kj-kbd-border-color);
|
|
30
|
+
border-radius: var(--kj-kbd-radius);
|
|
31
|
+
padding: var(--kj-kbd-padding-y) var(--kj-kbd-padding-x);
|
|
32
|
+
|
|
33
|
+
font-family: var(--kj-kbd-font);
|
|
34
|
+
font-size: var(--kj-kbd-font-size);
|
|
35
|
+
line-height: 1;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Size routing — driven by the directive's [attr.data-size]. */
|
|
40
|
+
.kj-kbd[data-size='xs'] {
|
|
41
|
+
/* At xs the border carries most of the visual identity; bump it to a
|
|
42
|
+
higher-contrast token so it clears 3:1 against base-100/200/300 page
|
|
43
|
+
backgrounds (analysis Open question 5). */
|
|
44
|
+
--kj-kbd-border-color: color-mix(in oklab, var(--kj-fg-default) 30%, transparent);
|
|
45
|
+
--kj-kbd-font-size: var(--kj-text-xs);
|
|
46
|
+
--kj-kbd-padding-x: 0.3em;
|
|
47
|
+
--kj-kbd-padding-y: 0.1em;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.kj-kbd[data-size='sm'] {
|
|
51
|
+
--kj-kbd-font-size: var(--kj-text-xs);
|
|
52
|
+
--kj-kbd-padding-x: 0.35em;
|
|
53
|
+
--kj-kbd-padding-y: 0.12em;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.kj-kbd[data-size='md'] {
|
|
57
|
+
--kj-kbd-font-size: var(--kj-text-sm);
|
|
58
|
+
--kj-kbd-padding-x: 0.4em;
|
|
59
|
+
--kj-kbd-padding-y: 0.15em;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.kj-kbd[data-size='lg'] {
|
|
63
|
+
--kj-kbd-font-size: var(--kj-text-base);
|
|
64
|
+
--kj-kbd-padding-x: 0.5em;
|
|
65
|
+
--kj-kbd-padding-y: 0.2em;
|
|
66
|
+
}
|
|
67
|
+
}
|