@kouji-ui/components 0.9.2 → 0.9.4
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/fesm2022/kouji-ui-components.mjs +49 -3
- package/fesm2022/kouji-ui-components.mjs.map +1 -1
- 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
- package/types/kouji-ui-components.d.ts +19 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-pagination — component-layer tokens + structural CSS
|
|
3
|
+
The directive family reflects state via `data-current` /
|
|
4
|
+
`data-disabled` / `aria-current` / `aria-disabled` /
|
|
5
|
+
`data-pagination-action` — every selector here keys off those.
|
|
6
|
+
──────────────────────────────────────────────────────────── */
|
|
7
|
+
|
|
8
|
+
@layer kj.component {
|
|
9
|
+
.kj-pagination {
|
|
10
|
+
/* component knobs — themes/users can override any of these */
|
|
11
|
+
--kj-pagination-gap: var(--kj-space-xs);
|
|
12
|
+
--kj-pagination-item-size: 2.75rem; /* 44px — WCAG 2.5.5 */
|
|
13
|
+
--kj-pagination-item-radius: var(--kj-radius-field);
|
|
14
|
+
--kj-pagination-item-bg: transparent;
|
|
15
|
+
--kj-pagination-item-bg-hover: var(--kj-bg-surface);
|
|
16
|
+
--kj-pagination-item-bg-current: var(--kj-bg-primary);
|
|
17
|
+
--kj-pagination-item-fg: var(--kj-fg-default);
|
|
18
|
+
--kj-pagination-item-fg-current: var(--kj-fg-on-primary);
|
|
19
|
+
--kj-pagination-item-border-color: var(--kj-border-default);
|
|
20
|
+
--kj-pagination-item-border-width: var(--kj-border);
|
|
21
|
+
--kj-pagination-item-font: var(--kj-font-sans);
|
|
22
|
+
--kj-pagination-item-font-size: var(--kj-text-sm);
|
|
23
|
+
--kj-pagination-ellipsis-color: var(--kj-fg-default);
|
|
24
|
+
--kj-pagination-info-color: var(--kj-fg-default);
|
|
25
|
+
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
flex-wrap: wrap;
|
|
29
|
+
gap: var(--kj-pagination-gap);
|
|
30
|
+
color: var(--kj-fg-default);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Numeric items + boundary controls share the same visual chrome —
|
|
34
|
+
the `.kj-pagination-action` class names map to Previous / Next /
|
|
35
|
+
First / Last so consumers can target them individually if needed. */
|
|
36
|
+
.kj-pagination-item,
|
|
37
|
+
.kj-pagination-action {
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
min-width: var(--kj-pagination-item-size);
|
|
42
|
+
min-height: var(--kj-pagination-item-size);
|
|
43
|
+
padding: 0 var(--kj-space-sm);
|
|
44
|
+
background: var(--kj-pagination-item-bg);
|
|
45
|
+
color: var(--kj-pagination-item-fg);
|
|
46
|
+
border: var(--kj-pagination-item-border-width) solid var(--kj-pagination-item-border-color);
|
|
47
|
+
border-radius: var(--kj-pagination-item-radius);
|
|
48
|
+
font: var(--kj-pagination-item-font-size) / 1.2 var(--kj-pagination-item-font);
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
user-select: none;
|
|
51
|
+
transition: var(--kj-transition);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.kj-pagination-item:hover,
|
|
55
|
+
.kj-pagination-action:hover {
|
|
56
|
+
background: var(--kj-pagination-item-bg-hover);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.kj-pagination-item:focus-visible,
|
|
60
|
+
.kj-pagination-action:focus-visible {
|
|
61
|
+
outline: 2px solid var(--kj-fg-primary);
|
|
62
|
+
outline-offset: 2px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Active page — keep the touch target intact (don't shrink the active
|
|
66
|
+
state). The directive reflects `aria-current="page"` and
|
|
67
|
+
`data-current="true"`; we key off `aria-current` for AT correctness. */
|
|
68
|
+
.kj-pagination-item[aria-current='page'] {
|
|
69
|
+
background: var(--kj-pagination-item-bg-current);
|
|
70
|
+
color: var(--kj-pagination-item-fg-current);
|
|
71
|
+
border-color: var(--kj-pagination-item-bg-current);
|
|
72
|
+
cursor: default;
|
|
73
|
+
}
|
|
74
|
+
.kj-pagination-item[aria-current='page']:hover {
|
|
75
|
+
background: var(--kj-pagination-item-bg-current);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Disabled — `aria-disabled="true"` rather than the native attribute,
|
|
79
|
+
so the control stays focusable (kouji's WCAG 2.1 AAA stance). */
|
|
80
|
+
.kj-pagination-item[aria-disabled='true'],
|
|
81
|
+
.kj-pagination-action[aria-disabled='true'] {
|
|
82
|
+
opacity: 0.45;
|
|
83
|
+
cursor: not-allowed;
|
|
84
|
+
}
|
|
85
|
+
.kj-pagination-item[aria-disabled='true']:hover,
|
|
86
|
+
.kj-pagination-action[aria-disabled='true']:hover {
|
|
87
|
+
background: var(--kj-pagination-item-bg);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Ellipsis is non-interactive — no hover, no border, no touch target. */
|
|
91
|
+
.kj-pagination-ellipsis {
|
|
92
|
+
display: inline-flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
min-width: var(--kj-pagination-item-size);
|
|
96
|
+
min-height: var(--kj-pagination-item-size);
|
|
97
|
+
color: var(--kj-pagination-ellipsis-color);
|
|
98
|
+
user-select: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Info span — AT-readable, visible. */
|
|
102
|
+
.kj-pagination-info {
|
|
103
|
+
display: inline-flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
padding: 0 var(--kj-space-sm);
|
|
106
|
+
color: var(--kj-pagination-info-color);
|
|
107
|
+
font: var(--kj-pagination-item-font-size) / 1.2 var(--kj-pagination-item-font);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* ── Variants ──────────────────────────────────────────────── */
|
|
111
|
+
/* The root cascades `data-variant` to children via the context; each
|
|
112
|
+
child's KjVariant host directive reflects its own. We target the
|
|
113
|
+
children directly so the wrapper variant flows through. */
|
|
114
|
+
|
|
115
|
+
.kj-pagination-item[data-variant='outline'],
|
|
116
|
+
.kj-pagination-action[data-variant='outline'] {
|
|
117
|
+
--kj-pagination-item-bg: transparent;
|
|
118
|
+
--kj-pagination-item-border-color: var(--kj-border-default);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.kj-pagination-item[data-variant='ghost'],
|
|
122
|
+
.kj-pagination-action[data-variant='ghost'] {
|
|
123
|
+
--kj-pagination-item-bg: transparent;
|
|
124
|
+
--kj-pagination-item-border-color: transparent;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ── Sizes ─────────────────────────────────────────────────── */
|
|
128
|
+
.kj-pagination-item[data-size='xs'],
|
|
129
|
+
.kj-pagination-action[data-size='xs'] {
|
|
130
|
+
min-width: 1.5rem;
|
|
131
|
+
min-height: 1.5rem;
|
|
132
|
+
--kj-pagination-item-font-size: 0.75rem;
|
|
133
|
+
}
|
|
134
|
+
.kj-pagination-item[data-size='sm'],
|
|
135
|
+
.kj-pagination-action[data-size='sm'] {
|
|
136
|
+
min-width: 2rem;
|
|
137
|
+
min-height: 2rem;
|
|
138
|
+
--kj-pagination-item-font-size: 0.8125rem;
|
|
139
|
+
}
|
|
140
|
+
.kj-pagination-item[data-size='lg'],
|
|
141
|
+
.kj-pagination-action[data-size='lg'] {
|
|
142
|
+
min-width: 3rem;
|
|
143
|
+
min-height: 3rem;
|
|
144
|
+
--kj-pagination-item-font-size: 1rem;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-password-input — component-layer tokens + structural CSS
|
|
3
|
+
Same pattern as input.css / button.css: tokens default to the
|
|
4
|
+
shared layer; variants/states retarget the component tokens.
|
|
5
|
+
──────────────────────────────────────────────────────────── */
|
|
6
|
+
|
|
7
|
+
@layer kj.component {
|
|
8
|
+
.kj-password-input {
|
|
9
|
+
--kj-password-input-bg: var(--kj-bg-field);
|
|
10
|
+
--kj-password-input-fg: var(--kj-fg-default);
|
|
11
|
+
--kj-password-input-border-color: var(--kj-border-default);
|
|
12
|
+
--kj-password-input-border-style: solid;
|
|
13
|
+
--kj-password-input-border-width: var(--kj-border);
|
|
14
|
+
--kj-password-input-radius: var(--kj-radius-field);
|
|
15
|
+
--kj-password-input-padding-x: var(--kj-space-md);
|
|
16
|
+
--kj-password-input-padding-y: var(--kj-space-sm);
|
|
17
|
+
--kj-password-input-font: var(--kj-font-sans);
|
|
18
|
+
--kj-password-input-font-size: var(--kj-text-sm);
|
|
19
|
+
--kj-password-input-placeholder-fg: var(--kj-fg-muted);
|
|
20
|
+
--kj-password-input-toggle-fg: var(--kj-fg-muted);
|
|
21
|
+
--kj-password-input-toggle-fg-hover: var(--kj-fg-default);
|
|
22
|
+
/* Explicit height contract (same pattern as input.css / button.css) so
|
|
23
|
+
password-input lines up with sibling inputs in a form row. */
|
|
24
|
+
--kj-password-input-height: 2.25rem;
|
|
25
|
+
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: var(--kj-space-xs);
|
|
29
|
+
inline-size: 100%;
|
|
30
|
+
height: var(--kj-password-input-height);
|
|
31
|
+
background: var(--kj-password-input-bg);
|
|
32
|
+
color: var(--kj-password-input-fg);
|
|
33
|
+
border: var(--kj-password-input-border-width)
|
|
34
|
+
var(--kj-password-input-border-style)
|
|
35
|
+
var(--kj-password-input-border-color);
|
|
36
|
+
border-radius: var(--kj-password-input-radius);
|
|
37
|
+
padding: 0 var(--kj-password-input-padding-x);
|
|
38
|
+
font: var(--kj-password-input-font-size) / 1.4 var(--kj-password-input-font);
|
|
39
|
+
transition: var(--kj-transition);
|
|
40
|
+
}
|
|
41
|
+
.kj-password-input[data-size="sm"] { --kj-password-input-height: 2rem; --kj-password-input-font-size: 0.8125rem; }
|
|
42
|
+
.kj-password-input[data-size="lg"] { --kj-password-input-height: 2.75rem; --kj-password-input-font-size: 1rem; }
|
|
43
|
+
|
|
44
|
+
.kj-password-input__field {
|
|
45
|
+
flex: 1 1 auto;
|
|
46
|
+
inline-size: 100%;
|
|
47
|
+
height: 100%;
|
|
48
|
+
background: transparent;
|
|
49
|
+
color: inherit;
|
|
50
|
+
border: 0;
|
|
51
|
+
outline: 0;
|
|
52
|
+
padding: 0;
|
|
53
|
+
font: inherit;
|
|
54
|
+
}
|
|
55
|
+
.kj-password-input__field::placeholder {
|
|
56
|
+
color: var(--kj-password-input-placeholder-fg);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.kj-password-input:focus-within {
|
|
60
|
+
outline: 2px solid var(--kj-fg-primary);
|
|
61
|
+
outline-offset: 2px;
|
|
62
|
+
--kj-password-input-border-color: var(--kj-fg-primary);
|
|
63
|
+
}
|
|
64
|
+
.kj-password-input:has([aria-invalid="true"]) {
|
|
65
|
+
--kj-password-input-border-color: var(--kj-fg-danger);
|
|
66
|
+
}
|
|
67
|
+
.kj-password-input:has([aria-disabled="true"]),
|
|
68
|
+
.kj-password-input:has(:disabled) {
|
|
69
|
+
opacity: 0.55;
|
|
70
|
+
cursor: not-allowed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* eye-icon toggle button: ghost, square, in-row */
|
|
74
|
+
.kj-password-input__toggle {
|
|
75
|
+
display: inline-flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
inline-size: 1.75rem;
|
|
79
|
+
block-size: 1.75rem;
|
|
80
|
+
margin-inline-start: var(--kj-space-xs);
|
|
81
|
+
color: var(--kj-password-input-toggle-fg);
|
|
82
|
+
background: transparent;
|
|
83
|
+
border: 0;
|
|
84
|
+
border-radius: var(--kj-radius-field);
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
}
|
|
87
|
+
.kj-password-input__toggle:hover,
|
|
88
|
+
.kj-password-input__toggle:focus-visible {
|
|
89
|
+
color: var(--kj-password-input-toggle-fg-hover);
|
|
90
|
+
}
|
|
91
|
+
.kj-password-input__toggle:focus-visible {
|
|
92
|
+
outline: 2px solid var(--kj-fg-primary);
|
|
93
|
+
outline-offset: 2px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.kj-password-input__icon {
|
|
97
|
+
inline-size: 1rem;
|
|
98
|
+
block-size: 1rem;
|
|
99
|
+
fill: none;
|
|
100
|
+
stroke: currentColor;
|
|
101
|
+
stroke-width: 1.5;
|
|
102
|
+
stroke-linecap: round;
|
|
103
|
+
stroke-linejoin: round;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* strength meter — 4-segment bar, colour ramp owned here */
|
|
107
|
+
.kj-password-strength {
|
|
108
|
+
display: flex;
|
|
109
|
+
gap: var(--kj-space-xs);
|
|
110
|
+
inline-size: 100%;
|
|
111
|
+
margin-block-start: var(--kj-space-xs);
|
|
112
|
+
}
|
|
113
|
+
.kj-password-strength__seg {
|
|
114
|
+
flex: 1 1 0;
|
|
115
|
+
block-size: 0.25rem;
|
|
116
|
+
background: var(--kj-bg-surface);
|
|
117
|
+
border-radius: 2px;
|
|
118
|
+
transition: background-color var(--kj-transition);
|
|
119
|
+
}
|
|
120
|
+
.kj-password-strength[data-score="1"] .kj-password-strength__seg:nth-child(-n+1),
|
|
121
|
+
.kj-password-strength[data-score="2"] .kj-password-strength__seg:nth-child(-n+2),
|
|
122
|
+
.kj-password-strength[data-score="3"] .kj-password-strength__seg:nth-child(-n+3),
|
|
123
|
+
.kj-password-strength[data-score="4"] .kj-password-strength__seg:nth-child(-n+4) {
|
|
124
|
+
background: var(--kj-password-strength-color, var(--kj-bg-primary));
|
|
125
|
+
}
|
|
126
|
+
.kj-password-strength[data-score="1"] {
|
|
127
|
+
--kj-password-strength-color: var(--kj-bg-danger);
|
|
128
|
+
}
|
|
129
|
+
.kj-password-strength[data-score="2"] {
|
|
130
|
+
--kj-password-strength-color: var(--kj-bg-warning, #d97706);
|
|
131
|
+
}
|
|
132
|
+
.kj-password-strength[data-score="3"] {
|
|
133
|
+
--kj-password-strength-color: var(--kj-bg-success, #059669);
|
|
134
|
+
}
|
|
135
|
+
.kj-password-strength[data-score="4"] {
|
|
136
|
+
--kj-password-strength-color: var(--kj-bg-success, #059669);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.kj-password-strength__label {
|
|
140
|
+
display: block;
|
|
141
|
+
margin-block-start: var(--kj-space-xs);
|
|
142
|
+
font-size: var(--kj-text-xs);
|
|
143
|
+
color: var(--kj-fg-muted);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.kj-password-caps-lock-warning {
|
|
147
|
+
display: block;
|
|
148
|
+
margin-block-start: var(--kj-space-xs);
|
|
149
|
+
font-size: var(--kj-text-xs);
|
|
150
|
+
color: var(--kj-fg-warning, #d97706);
|
|
151
|
+
}
|
|
152
|
+
.kj-password-caps-lock-warning[hidden] {
|
|
153
|
+
display: none;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
@layer kj.component {
|
|
2
|
+
/* The wrapper hosts use display: contents — they don't add a box. The
|
|
3
|
+
panel is portal-mounted to <body> by the underlying directive; the
|
|
4
|
+
`.kj-popover-content` class lands on the panel via `kjPanelClass`. */
|
|
5
|
+
|
|
6
|
+
.kj-popover-content {
|
|
7
|
+
--kj-popover-bg: var(--kj-bg-elevated);
|
|
8
|
+
--kj-popover-fg: var(--kj-fg-default);
|
|
9
|
+
--kj-popover-border-color: var(--kj-border-default);
|
|
10
|
+
--kj-popover-radius: var(--kj-radius-box);
|
|
11
|
+
--kj-popover-padding-x: var(--kj-space-md);
|
|
12
|
+
--kj-popover-padding-y: var(--kj-space-md);
|
|
13
|
+
--kj-popover-shadow: var(--kj-shadow-md, 0 12px 32px rgb(0 0 0 / 0.18));
|
|
14
|
+
--kj-popover-arrow-size: 8px;
|
|
15
|
+
|
|
16
|
+
/* Layout — the directive applies position: fixed and writes top/left. */
|
|
17
|
+
display: block;
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
max-width: min(28rem, calc(100vw - 2rem));
|
|
20
|
+
background: var(--kj-popover-bg);
|
|
21
|
+
color: var(--kj-popover-fg);
|
|
22
|
+
border: var(--kj-border) solid var(--kj-popover-border-color);
|
|
23
|
+
border-radius: var(--kj-popover-radius);
|
|
24
|
+
padding: var(--kj-popover-padding-y) var(--kj-popover-padding-x);
|
|
25
|
+
font: 0.875rem / 1.5 var(--kj-font-sans);
|
|
26
|
+
box-shadow: var(--kj-popover-shadow);
|
|
27
|
+
pointer-events: auto;
|
|
28
|
+
z-index: var(--kj-overlay-z, 1000);
|
|
29
|
+
word-wrap: break-word;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.kj-popover-content[hidden] { display: none; }
|
|
33
|
+
|
|
34
|
+
.kj-popover-title {
|
|
35
|
+
display: block;
|
|
36
|
+
font-size: 0.9375rem;
|
|
37
|
+
font-weight: 600;
|
|
38
|
+
margin: 0 0 var(--kj-space-xs);
|
|
39
|
+
color: var(--kj-fg-default);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Arrow positioning relative to the panel's resolved data-side. */
|
|
43
|
+
.kj-popover-arrow {
|
|
44
|
+
position: absolute;
|
|
45
|
+
width: var(--kj-popover-arrow-size, 8px);
|
|
46
|
+
height: var(--kj-popover-arrow-size, 8px);
|
|
47
|
+
background: var(--kj-popover-bg, var(--kj-bg-elevated));
|
|
48
|
+
border: var(--kj-border) solid var(--kj-popover-border-color, var(--kj-border-default));
|
|
49
|
+
transform: rotate(45deg);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.kj-popover-content[data-side="top"] .kj-popover-arrow { bottom: calc(var(--kj-popover-arrow-size) / -2); left: 50%; margin-left: calc(var(--kj-popover-arrow-size) / -2); border-top-color: transparent; border-left-color: transparent; }
|
|
53
|
+
.kj-popover-content[data-side="bottom"] .kj-popover-arrow { top: calc(var(--kj-popover-arrow-size) / -2); left: 50%; margin-left: calc(var(--kj-popover-arrow-size) / -2); border-bottom-color: transparent; border-right-color: transparent; }
|
|
54
|
+
.kj-popover-content[data-side="left"] .kj-popover-arrow { right: calc(var(--kj-popover-arrow-size) / -2); top: 50%; margin-top: calc(var(--kj-popover-arrow-size) / -2); border-left-color: transparent; border-bottom-color: transparent; }
|
|
55
|
+
.kj-popover-content[data-side="right"] .kj-popover-arrow { left: calc(var(--kj-popover-arrow-size) / -2); top: 50%; margin-top: calc(var(--kj-popover-arrow-size) / -2); border-right-color: transparent; border-top-color: transparent; }
|
|
56
|
+
|
|
57
|
+
/* Modal-mode body scroll lock. Multiple stacked modals coordinate via
|
|
58
|
+
a counter in KjOverlayService; this just hides body overflow when the
|
|
59
|
+
attribute is present. */
|
|
60
|
+
body[data-kj-scroll-lock="true"] {
|
|
61
|
+
overflow: hidden;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
65
|
+
.kj-popover-content {
|
|
66
|
+
transition: opacity 140ms ease-out, transform 140ms ease-out;
|
|
67
|
+
}
|
|
68
|
+
.kj-popover-content[data-state="closed"] { opacity: 0; transform: scale(0.98); }
|
|
69
|
+
.kj-popover-content[data-state="open"] { opacity: 1; transform: scale(1); }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-progress-bar — component-layer tokens + structural CSS
|
|
3
|
+
The directive owns the value math + ARIA contract. The theme
|
|
4
|
+
only renders track + fill. Variant tokens flip the fill colour;
|
|
5
|
+
size tokens flip the bar height. The indeterminate stripe is a
|
|
6
|
+
pure CSS animation, neutralised under prefers-reduced-motion.
|
|
7
|
+
──────────────────────────────────────────────────────────── */
|
|
8
|
+
|
|
9
|
+
@layer kj.component {
|
|
10
|
+
.kj-progress-bar {
|
|
11
|
+
/* component knobs — themes/users can override any of these */
|
|
12
|
+
--kj-progress-bar-track: var(--kj-bg-field);
|
|
13
|
+
--kj-progress-bar-fill: var(--kj-bg-primary);
|
|
14
|
+
--kj-progress-bar-radius: 9999px;
|
|
15
|
+
--kj-progress-bar-height: 0.5rem; /* md default — 8px */
|
|
16
|
+
--kj-progress-bar-transition: 200ms ease-out;
|
|
17
|
+
--kj-progress-bar-indeterminate-duration: 1.6s;
|
|
18
|
+
|
|
19
|
+
display: block;
|
|
20
|
+
position: relative;
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: var(--kj-progress-bar-height);
|
|
23
|
+
background: var(--kj-progress-bar-track);
|
|
24
|
+
border-radius: var(--kj-progress-bar-radius);
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ── Sizes ── (data-size mirrored by KjSize host directive) */
|
|
29
|
+
.kj-progress-bar[data-size="xs"] { --kj-progress-bar-height: 0.125rem; } /* 2px */
|
|
30
|
+
.kj-progress-bar[data-size="sm"] { --kj-progress-bar-height: 0.25rem; } /* 4px */
|
|
31
|
+
.kj-progress-bar[data-size="md"] { --kj-progress-bar-height: 0.5rem; } /* 8px */
|
|
32
|
+
.kj-progress-bar[data-size="lg"] { --kj-progress-bar-height: 0.75rem; } /* 12px */
|
|
33
|
+
|
|
34
|
+
/* ── Variants ── (data-variant mirrored by KjVariant host directive) */
|
|
35
|
+
.kj-progress-bar[data-variant="primary"] { --kj-progress-bar-fill: var(--kj-bg-primary); }
|
|
36
|
+
.kj-progress-bar[data-variant="success"] { --kj-progress-bar-fill: var(--kj-bg-success); }
|
|
37
|
+
.kj-progress-bar[data-variant="warning"] { --kj-progress-bar-fill: var(--kj-bg-warning); }
|
|
38
|
+
.kj-progress-bar[data-variant="error"] { --kj-progress-bar-fill: var(--kj-bg-danger); }
|
|
39
|
+
|
|
40
|
+
/* ── Fill (determinate) ──
|
|
41
|
+
The directive sets --kj-progress-fraction (0..1) on the host AND on the
|
|
42
|
+
fill. Reading from the fill is one selector hop shorter and lets us scope
|
|
43
|
+
the transition cleanly. */
|
|
44
|
+
.kj-progress-bar__fill {
|
|
45
|
+
display: block;
|
|
46
|
+
width: 100%;
|
|
47
|
+
height: 100%;
|
|
48
|
+
background: var(--kj-progress-bar-fill);
|
|
49
|
+
border-radius: inherit;
|
|
50
|
+
transform-origin: left center;
|
|
51
|
+
transform: scaleX(var(--kj-progress-fraction, 0));
|
|
52
|
+
/* Transition the fill width on value changes — but never the colour, so
|
|
53
|
+
a variant flip mid-flight is instantaneous. */
|
|
54
|
+
transition: transform var(--kj-progress-bar-transition);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* RTL: the fill grows from the right edge. */
|
|
58
|
+
[dir="rtl"] .kj-progress-bar__fill {
|
|
59
|
+
transform-origin: right center;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* ── Fill (indeterminate) ──
|
|
63
|
+
When data-indeterminate="true", abandon the scaleX(fraction) transform
|
|
64
|
+
and run a stripe that travels left → right. The stripe is a 50%-wide
|
|
65
|
+
coloured band on a transparent track, which gives a clean "working"
|
|
66
|
+
read on every theme without needing the track itself to animate. */
|
|
67
|
+
.kj-progress-bar__fill[data-indeterminate="true"] {
|
|
68
|
+
transform: none;
|
|
69
|
+
background: linear-gradient(
|
|
70
|
+
90deg,
|
|
71
|
+
transparent 0%,
|
|
72
|
+
var(--kj-progress-bar-fill) 50%,
|
|
73
|
+
transparent 100%
|
|
74
|
+
);
|
|
75
|
+
background-size: 50% 100%;
|
|
76
|
+
background-repeat: no-repeat;
|
|
77
|
+
animation: kj-progress-bar-indeterminate
|
|
78
|
+
var(--kj-progress-bar-indeterminate-duration) linear infinite;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@keyframes kj-progress-bar-indeterminate {
|
|
82
|
+
from { background-position: -50% 0; }
|
|
83
|
+
to { background-position: 150% 0; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ── Reduced motion ──
|
|
87
|
+
APG / WCAG 2.3.3: animations from interactions must respect
|
|
88
|
+
prefers-reduced-motion. The directive reflects data-reduced-motion;
|
|
89
|
+
we also honour the media query directly so the rule kicks in even if
|
|
90
|
+
the directive's media-query subscription hasn't run yet (SSR, very
|
|
91
|
+
early paint). The indeterminate stripe collapses to a static, dimmed
|
|
92
|
+
fill — still visible as "something is happening", just not moving. */
|
|
93
|
+
.kj-progress-bar[data-reduced-motion="reduce"] .kj-progress-bar__fill,
|
|
94
|
+
.kj-progress-bar[data-reduced-motion="reduce"] .kj-progress-bar__fill[data-indeterminate="true"] {
|
|
95
|
+
animation: none;
|
|
96
|
+
transition: none;
|
|
97
|
+
}
|
|
98
|
+
.kj-progress-bar[data-reduced-motion="reduce"] .kj-progress-bar__fill[data-indeterminate="true"] {
|
|
99
|
+
background: var(--kj-progress-bar-fill);
|
|
100
|
+
opacity: 0.6;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@media (prefers-reduced-motion: reduce) {
|
|
104
|
+
.kj-progress-bar__fill {
|
|
105
|
+
transition: none;
|
|
106
|
+
}
|
|
107
|
+
.kj-progress-bar__fill[data-indeterminate="true"] {
|
|
108
|
+
animation: none;
|
|
109
|
+
background: var(--kj-progress-bar-fill);
|
|
110
|
+
opacity: 0.6;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
@layer kj.component {
|
|
2
|
+
.kj-radio-group {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: var(--kj-space-sm);
|
|
6
|
+
}
|
|
7
|
+
.kj-radio-group[data-orientation="horizontal"] { flex-direction: row; gap: var(--kj-space-lg); }
|
|
8
|
+
|
|
9
|
+
/* Outer host. Acts as a single click target whose label area forwards to
|
|
10
|
+
the inner dot. */
|
|
11
|
+
.kj-radio {
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
}
|
|
14
|
+
.kj-radio-inner {
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: var(--kj-space-sm);
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
color: var(--kj-fg-default);
|
|
20
|
+
font: 0.875rem / 1.4 var(--kj-font-sans);
|
|
21
|
+
}
|
|
22
|
+
.kj-radio-label { user-select: none; }
|
|
23
|
+
|
|
24
|
+
/* The interactive dot (carries the kjRadio directive). */
|
|
25
|
+
.kj-radio-dot {
|
|
26
|
+
--kj-radio-size: 1rem;
|
|
27
|
+
--kj-radio-border: var(--kj-border-default);
|
|
28
|
+
--kj-radio-bg-checked: var(--kj-bg-primary);
|
|
29
|
+
|
|
30
|
+
display: inline-block;
|
|
31
|
+
width: var(--kj-radio-size);
|
|
32
|
+
height: var(--kj-radio-size);
|
|
33
|
+
flex-shrink: 0;
|
|
34
|
+
border: var(--kj-border) solid var(--kj-radio-border);
|
|
35
|
+
border-radius: 9999px;
|
|
36
|
+
background: var(--kj-bg-field);
|
|
37
|
+
position: relative;
|
|
38
|
+
transition: var(--kj-transition);
|
|
39
|
+
}
|
|
40
|
+
.kj-radio-dot[data-checked]::after {
|
|
41
|
+
content: '';
|
|
42
|
+
position: absolute;
|
|
43
|
+
inset: 25%;
|
|
44
|
+
border-radius: 9999px;
|
|
45
|
+
background: var(--kj-radio-bg-checked);
|
|
46
|
+
}
|
|
47
|
+
.kj-radio-dot[data-checked] { border-color: var(--kj-radio-bg-checked); }
|
|
48
|
+
.kj-radio-dot:focus-visible { outline: 2px solid var(--kj-fg-primary); outline-offset: 2px; }
|
|
49
|
+
|
|
50
|
+
/* Disabled — dim the whole group (dot + label text) for visual consistency
|
|
51
|
+
with native form controls. */
|
|
52
|
+
.kj-radio[data-disabled] .kj-radio-inner { cursor: not-allowed; }
|
|
53
|
+
.kj-radio[data-disabled] .kj-radio-dot,
|
|
54
|
+
.kj-radio[data-disabled] .kj-radio-label {
|
|
55
|
+
opacity: 0.45;
|
|
56
|
+
}
|
|
57
|
+
.kj-radio[data-disabled] .kj-radio-dot { pointer-events: none; }
|
|
58
|
+
}
|