@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,295 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-button — component-layer tokens + structural CSS
|
|
3
|
+
Tokens default to shared-layer values from @kouji-ui/themes.
|
|
4
|
+
Variants/sizes/states retarget the component tokens; the
|
|
5
|
+
structural rules below are the only place raw var(--kj-button-*)
|
|
6
|
+
is read.
|
|
7
|
+
──────────────────────────────────────────────────────────── */
|
|
8
|
+
|
|
9
|
+
@layer kj.component {
|
|
10
|
+
.kj-button {
|
|
11
|
+
/* Component knobs are NOT declared here.
|
|
12
|
+
A custom property declared on an element always beats an inherited one,
|
|
13
|
+
so declaring the knobs on .kj-button made them unreachable from an
|
|
14
|
+
ancestor — including the <kj-button> host, which is display:contents and
|
|
15
|
+
is the only thing a consumer can put an inline style on. The knobs were
|
|
16
|
+
documented as "themes/users can override any of these" but in practice
|
|
17
|
+
could only be overridden by a rule targeting .kj-button itself.
|
|
18
|
+
Each knob is now read as var(name, <default>) at its use site, exactly
|
|
19
|
+
like --kj-button-shadow already was, so an ancestor value reaches the
|
|
20
|
+
component and the default still applies when nobody sets one.
|
|
21
|
+
Variant and size rules still DECLARE knobs on the element on purpose:
|
|
22
|
+
that is the component's own logic and must win. */
|
|
23
|
+
/* --kj-button-shadow / --kj-button-shadow-hover / --kj-button-shadow-active
|
|
24
|
+
are intentionally NOT declared here. Declaring them with element-level
|
|
25
|
+
specificity would win over the theme's `[data-theme="X"]` ancestor
|
|
26
|
+
declaration (e.g. kouji's `6px 6px 0 #2a2a2a` offset block), since
|
|
27
|
+
both selectors are (0,1,0) and same-layer source-order favours the
|
|
28
|
+
element. The hover/active rules below pull the value via
|
|
29
|
+
`var(name, fallback)` so the theme's inherited value reaches the
|
|
30
|
+
component AND the fallback (`none`) applies when no theme sets one. */
|
|
31
|
+
/* Typographic tone — themes override these for brand voice (e.g.
|
|
32
|
+
bauhaus bumps weight to 900; cyberpunk tightens tracking). */
|
|
33
|
+
/* Hover lift — pulls from the shared per-theme --kj-hover-translate
|
|
34
|
+
(bauhaus = -4px; everyone else = -2px). */
|
|
35
|
+
|
|
36
|
+
/* the only place raw var(--kj-button-*) is read */
|
|
37
|
+
display: inline-flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: var(--kj-button-justify, center);
|
|
40
|
+
gap: var(--kj-button-gap, var(--kj-space-sm));
|
|
41
|
+
min-width: var(--kj-button-min-width, auto);
|
|
42
|
+
/* Explicit per-size height + align-self prevents flex parents
|
|
43
|
+
(align-items: stretch) from over-growing the button vertically. */
|
|
44
|
+
height: var(--kj-button-height, auto);
|
|
45
|
+
align-self: center;
|
|
46
|
+
background: var(--kj-button-bg, var(--kj-bg-primary));
|
|
47
|
+
color: var(--kj-button-fg, var(--kj-fg-on-primary));
|
|
48
|
+
border: var(--kj-button-border-width, var(--kj-border)) var(--kj-button-border-style, solid) var(--kj-button-border-color, transparent);
|
|
49
|
+
border-radius: var(--kj-button-radius, var(--kj-radius-field));
|
|
50
|
+
padding: var(--kj-button-padding-y, var(--kj-space-sm)) var(--kj-button-padding-x, var(--kj-space-md));
|
|
51
|
+
font: var(--kj-button-font-weight, 600) var(--kj-button-font-size, var(--kj-text-sm)) / 1.2 var(--kj-button-font, var(--kj-font-sans));
|
|
52
|
+
letter-spacing: var(--kj-button-letter-spacing, -0.01em);
|
|
53
|
+
text-transform: var(--kj-button-text-transform, none);
|
|
54
|
+
/* Button shadow is opt-in PER PAGE CONTEXT (e.g. landing hero, docs
|
|
55
|
+
playground stage) AND restricted to the `default` (primary) variant
|
|
56
|
+
— never global. Pages set `--kj-button-shadow` at their scope; the
|
|
57
|
+
applying rules below only fire for `[data-variant="default"]`. */
|
|
58
|
+
/* Transition only properties tied to user interaction (hover / active /
|
|
59
|
+
focus). NOT background-color — variant-driven bg changes propagate
|
|
60
|
+
through the wrapper input on the same tick as the host attribute, and
|
|
61
|
+
transitioning bg here animates the initial primary→variant flip on
|
|
62
|
+
components like <kj-button variant="ghost"> when the wrapper resolves. */
|
|
63
|
+
transition: color var(--kj-transition), border-color var(--kj-transition),
|
|
64
|
+
box-shadow var(--kj-transition), transform var(--kj-transition),
|
|
65
|
+
opacity var(--kj-transition);
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
text-decoration: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Variants flip component tokens. Each variant owns its hover so the
|
|
71
|
+
hover surface is designed per intent, not derived heuristically.
|
|
72
|
+
|
|
73
|
+
Filled variants (default, destructive) darken on hover via a fixed
|
|
74
|
+
8% color-mix toward black. Themes can override the variant rules
|
|
75
|
+
entirely for a brand-correct shade. */
|
|
76
|
+
|
|
77
|
+
.kj-button[data-variant="destructive"] {
|
|
78
|
+
--kj-button-bg: var(--kj-bg-danger);
|
|
79
|
+
--kj-button-fg: var(--kj-fg-on-danger);
|
|
80
|
+
}
|
|
81
|
+
.kj-button[data-variant="destructive"]:hover {
|
|
82
|
+
--kj-button-bg: color-mix(in oklch, var(--kj-bg-danger), black 8%);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.kj-button[data-variant="ghost"] {
|
|
86
|
+
--kj-button-bg: transparent;
|
|
87
|
+
--kj-button-fg: var(--kj-fg-default);
|
|
88
|
+
--kj-button-border-color: transparent;
|
|
89
|
+
}
|
|
90
|
+
/* Use a foreground-tinted overlay rather than a static base-200 surface so
|
|
91
|
+
the hover state stays visible even on themes where base-100 / base-200
|
|
92
|
+
are nearly the same value (e.g. kouji's deep #0c0c0c / #141414 ramp). */
|
|
93
|
+
.kj-button[data-variant="ghost"]:hover {
|
|
94
|
+
--kj-button-bg: color-mix(in oklab, var(--kj-fg-default) 10%, transparent);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.kj-button[data-variant="outline"] {
|
|
98
|
+
--kj-button-bg: transparent;
|
|
99
|
+
--kj-button-fg: var(--kj-fg-default);
|
|
100
|
+
--kj-button-border-color: var(--kj-border-default);
|
|
101
|
+
}
|
|
102
|
+
.kj-button[data-variant="outline"]:hover {
|
|
103
|
+
--kj-button-bg: var(--kj-bg-surface);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Segmented — a member of a segmented control. Idle segments are quiet and
|
|
107
|
+
borderless: the GROUP draws one shared shell and the dividers (see
|
|
108
|
+
button-group.css), so per-button borders would double up. Only the
|
|
109
|
+
pressed segment is filled, so "which one is on" reads from fill and not
|
|
110
|
+
from colour alone.
|
|
111
|
+
|
|
112
|
+
The on-state pulls through `--kj-segmented-bg-on` / `-fg-on` rather than
|
|
113
|
+
naming the surface directly: variant rules declare knobs on the element
|
|
114
|
+
and would otherwise beat a consumer's ancestor value, which is the one
|
|
115
|
+
thing this variant needs to stay themable. */
|
|
116
|
+
.kj-button[data-variant="segmented"] {
|
|
117
|
+
--kj-button-bg: transparent;
|
|
118
|
+
--kj-button-fg: var(--kj-fg-muted, var(--kj-fg-default));
|
|
119
|
+
--kj-button-border-color: transparent;
|
|
120
|
+
--kj-button-border-width: 0;
|
|
121
|
+
--kj-button-radius: 0;
|
|
122
|
+
--kj-button-hover-translate: none;
|
|
123
|
+
}
|
|
124
|
+
.kj-button[data-variant="segmented"]:hover {
|
|
125
|
+
--kj-button-bg: transparent;
|
|
126
|
+
--kj-button-fg: var(--kj-fg-default);
|
|
127
|
+
}
|
|
128
|
+
.kj-button[data-variant="segmented"][aria-pressed="true"] {
|
|
129
|
+
--kj-button-bg: var(--kj-segmented-bg-on, var(--kj-bg-surface));
|
|
130
|
+
--kj-button-fg: var(--kj-segmented-fg-on, var(--kj-fg-default));
|
|
131
|
+
cursor: default;
|
|
132
|
+
}
|
|
133
|
+
.kj-button[data-variant="segmented"][aria-pressed="true"]:hover {
|
|
134
|
+
--kj-button-bg: var(--kj-segmented-bg-on, var(--kj-bg-surface));
|
|
135
|
+
--kj-button-fg: var(--kj-segmented-fg-on, var(--kj-fg-default));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.kj-button[data-variant="link"] {
|
|
139
|
+
--kj-button-bg: transparent;
|
|
140
|
+
--kj-button-fg: var(--kj-fg-primary);
|
|
141
|
+
--kj-button-padding-x: 0;
|
|
142
|
+
--kj-button-padding-y: 0;
|
|
143
|
+
/* Link variant is plain underlined text — explicitly null out any
|
|
144
|
+
theme-set --kj-button-shadow so kouji/bauhaus offsets don't leak
|
|
145
|
+
onto inline text links. Ghost / outline / destructive keep theirs. */
|
|
146
|
+
--kj-button-shadow: none;
|
|
147
|
+
--kj-button-shadow-hover: none;
|
|
148
|
+
--kj-button-shadow-active: none;
|
|
149
|
+
text-decoration: underline;
|
|
150
|
+
text-underline-offset: 4px;
|
|
151
|
+
}
|
|
152
|
+
.kj-button[data-variant="link"]:hover {
|
|
153
|
+
--kj-button-bg: transparent;
|
|
154
|
+
--kj-button-fg: color-mix(in oklch, var(--kj-fg-primary), black 8%);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* sizes — heights are explicit so flex parents can't stretch the button.
|
|
158
|
+
padding-y is kept for the `height: auto` (token-overridden) case and
|
|
159
|
+
for visual rhythm with content larger than the height. */
|
|
160
|
+
.kj-button[data-size="xs"] {
|
|
161
|
+
--kj-button-padding-x: var(--kj-space-sm);
|
|
162
|
+
--kj-button-padding-y: 0;
|
|
163
|
+
--kj-button-font-size: 0.75rem;
|
|
164
|
+
--kj-button-height: var(--kj-ctl-h-xs); /* 28px */
|
|
165
|
+
}
|
|
166
|
+
.kj-button[data-size="sm"] {
|
|
167
|
+
--kj-button-padding-x: var(--kj-space-sm);
|
|
168
|
+
--kj-button-padding-y: 0.25rem;
|
|
169
|
+
--kj-button-font-size: 0.8125rem;
|
|
170
|
+
--kj-button-height: var(--kj-ctl-h-sm); /* 32px */
|
|
171
|
+
}
|
|
172
|
+
.kj-button[data-size="md"] {
|
|
173
|
+
--kj-button-height: var(--kj-ctl-h-md); /* 36px */
|
|
174
|
+
}
|
|
175
|
+
.kj-button[data-size="lg"] {
|
|
176
|
+
--kj-button-padding-x: var(--kj-space-lg);
|
|
177
|
+
--kj-button-padding-y: 0.625rem;
|
|
178
|
+
--kj-button-font-size: 1rem;
|
|
179
|
+
--kj-button-height: var(--kj-ctl-h-lg); /* 44px (AAA touch target) */
|
|
180
|
+
}
|
|
181
|
+
.kj-button[data-size="xl"] {
|
|
182
|
+
--kj-button-padding-x: var(--kj-space-2xl);
|
|
183
|
+
--kj-button-padding-y: 0.875rem;
|
|
184
|
+
--kj-button-font-size: 1.0625rem;
|
|
185
|
+
--kj-button-gap: var(--kj-space-md);
|
|
186
|
+
--kj-button-height: var(--kj-ctl-h-xl); /* 52px */
|
|
187
|
+
}
|
|
188
|
+
.kj-button[data-size="icon"] {
|
|
189
|
+
--kj-button-padding-x: var(--kj-space-sm);
|
|
190
|
+
--kj-button-padding-y: var(--kj-space-sm);
|
|
191
|
+
--kj-button-height: var(--kj-ctl-h-md);
|
|
192
|
+
aspect-ratio: 1;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Full-width: spans the parent (flex / block / grid track). */
|
|
196
|
+
.kj-button[data-full="true"] {
|
|
197
|
+
width: 100%;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Default-variant hover (catches the unset-or-`default` case). Specificity
|
|
201
|
+
here is lower than the variant-specific rules above, so destructive /
|
|
202
|
+
ghost / outline / link override correctly.
|
|
203
|
+
|
|
204
|
+
Themes can retarget the hover surface by setting
|
|
205
|
+
`--kj-button-bg-hover` (and optionally `--kj-button-fg-hover`) on
|
|
206
|
+
`[data-theme="X"]`. The fallbacks below preserve the default 8%
|
|
207
|
+
darken behaviour for any theme that doesn't customise. Defining the
|
|
208
|
+
defaults via `var(name, fallback)` instead of a `--kj-button-bg-hover:`
|
|
209
|
+
declaration on `.kj-button` keeps the theme's ancestor declaration
|
|
210
|
+
winning the cascade (an element-level default would override the
|
|
211
|
+
inherited value from `[data-theme]`). */
|
|
212
|
+
.kj-button:hover {
|
|
213
|
+
--kj-button-bg: var(--kj-button-bg-hover, color-mix(in oklch, var(--kj-bg-primary), black 8%));
|
|
214
|
+
/* Hover lift — themed via --kj-hover-translate. Bauhaus = -4px,
|
|
215
|
+
everyone else = -2px (default in :root). */
|
|
216
|
+
transform: translateY(var(--kj-button-hover-translate, var(--kj-hover-translate, -2px)));
|
|
217
|
+
}
|
|
218
|
+
/* fg-hover only applies to the default variant; ghost/outline/link own
|
|
219
|
+
their hover fg already, and destructive keeps its on-danger fg. */
|
|
220
|
+
.kj-button[data-variant="default"]:hover {
|
|
221
|
+
--kj-button-fg: var(--kj-button-fg-hover, var(--kj-fg-on-primary));
|
|
222
|
+
}
|
|
223
|
+
/* Shadow application — `default` (primary) variant only. Themes leave
|
|
224
|
+
`--kj-button-shadow` unset by default; pages opt in by declaring it
|
|
225
|
+
at their scope (landing hero, docs playground stage). Hover/active
|
|
226
|
+
fall back to the static shadow so a single declaration is enough. */
|
|
227
|
+
.kj-button[data-variant="default"] {
|
|
228
|
+
box-shadow: var(--kj-button-shadow, none);
|
|
229
|
+
}
|
|
230
|
+
.kj-button[data-variant="default"]:hover {
|
|
231
|
+
box-shadow: var(--kj-button-shadow-hover, var(--kj-button-shadow, none));
|
|
232
|
+
}
|
|
233
|
+
.kj-button[data-variant="default"]:active {
|
|
234
|
+
box-shadow: var(--kj-button-shadow-active, var(--kj-button-shadow, none));
|
|
235
|
+
}
|
|
236
|
+
.kj-button:active {
|
|
237
|
+
transform: var(--kj-button-translate-active, none);
|
|
238
|
+
}
|
|
239
|
+
.kj-button:focus-visible {
|
|
240
|
+
outline: 2px solid var(--kj-border-focus);
|
|
241
|
+
outline-offset: 2px;
|
|
242
|
+
}
|
|
243
|
+
.kj-button[aria-disabled="true"] {
|
|
244
|
+
opacity: 0.45;
|
|
245
|
+
cursor: not-allowed;
|
|
246
|
+
pointer-events: none;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* ── Touch target enforcement (WCAG 2.5.5) ─────────────────────────── */
|
|
250
|
+
/* Icon-only buttons have no inline text affordance, so they need the
|
|
251
|
+
full 44×44 touch target. The `sm` text variant relies on its label
|
|
252
|
+
for the equivalent-inline exception and stays visually small. */
|
|
253
|
+
.kj-button[data-size='icon'] {
|
|
254
|
+
min-width: 2.75rem; /* 44px */
|
|
255
|
+
min-height: 2.75rem; /* 44px */
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* ── Busy / loading state ───────────────────────────────────────────── */
|
|
259
|
+
.kj-button[aria-busy='true'] {
|
|
260
|
+
/* `opacity` dims the entire button (spinner + bg) uniformly. The
|
|
261
|
+
previous rule re-pointed --kj-button-fg at --kj-fg-default, which
|
|
262
|
+
on dark themes is the off-white page text — making the spinner
|
|
263
|
+
paint white over a yellow primary button. Opacity preserves the
|
|
264
|
+
variant's resolved fg so the spinner stays the same colour as
|
|
265
|
+
the would-be label. */
|
|
266
|
+
opacity: 0.7;
|
|
267
|
+
cursor: progress;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* ── Pressed (toggle) state ─────────────────────────────────────────── */
|
|
271
|
+
.kj-button[aria-pressed='true'] {
|
|
272
|
+
--kj-button-bg: var(--kj-bg-field);
|
|
273
|
+
box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.12);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* ── Spinner element (placeholder until KjSpinner ships) ─────────────
|
|
277
|
+
The wrapper hides <ng-content> while loading and renders only this
|
|
278
|
+
spinner, so it should sit centred in the button (the host already
|
|
279
|
+
applies `justify-content: var(--kj-button-justify, center)` = center). No
|
|
280
|
+
trailing margin — there's nothing after it to push away. */
|
|
281
|
+
.kj-button__spinner {
|
|
282
|
+
display: inline-block;
|
|
283
|
+
width: 1em;
|
|
284
|
+
height: 1em;
|
|
285
|
+
vertical-align: -0.125em;
|
|
286
|
+
border: 2px solid currentColor;
|
|
287
|
+
border-right-color: transparent;
|
|
288
|
+
border-radius: 50%;
|
|
289
|
+
animation: kj-button-spinner-rotate 0.6s linear infinite;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
@keyframes kj-button-spinner-rotate {
|
|
294
|
+
to { transform: rotate(360deg); }
|
|
295
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-button-group — segmented cluster of <kj-button> children.
|
|
3
|
+
The wrapper itself is a flex container with no padding/bg of
|
|
4
|
+
its own; segmented "chunky" feel comes from collapsing the
|
|
5
|
+
inner borders between adjacent buttons and squaring their
|
|
6
|
+
touching edges while keeping the group's outer corners
|
|
7
|
+
rounded.
|
|
8
|
+
──────────────────────────────────────────────────────────── */
|
|
9
|
+
|
|
10
|
+
@layer kj.component {
|
|
11
|
+
.kj-button-group {
|
|
12
|
+
--kj-button-group-gap: 0;
|
|
13
|
+
display: inline-flex;
|
|
14
|
+
flex-direction: row;
|
|
15
|
+
align-items: stretch;
|
|
16
|
+
gap: var(--kj-button-group-gap);
|
|
17
|
+
border-radius: var(--kj-radius-field);
|
|
18
|
+
/* Hide pseudo-overflow from collapsed-corner siblings. Children also
|
|
19
|
+
inherit display:contents semantics from <kj-button>, so the inner
|
|
20
|
+
<button> is the actual flex item. */
|
|
21
|
+
isolation: isolate;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.kj-button-group[data-orientation='vertical'] {
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Each <kj-button> sets `display: contents` on its host (see button.ts);
|
|
29
|
+
the styled element is the inner `.kj-button`. Squarish inner edges +
|
|
30
|
+
rounded outer edges fall out of :first-child / :last-child rules on
|
|
31
|
+
those inner buttons. */
|
|
32
|
+
|
|
33
|
+
/* ── Horizontal: collapse left/right radii on middle items ─────────── */
|
|
34
|
+
.kj-button-group:not([data-orientation='vertical']) > kj-button > .kj-button {
|
|
35
|
+
border-radius: 0;
|
|
36
|
+
/* Pull adjacent borders into a single visual line. */
|
|
37
|
+
margin-left: calc(-1 * var(--kj-border));
|
|
38
|
+
}
|
|
39
|
+
.kj-button-group:not([data-orientation='vertical']) > kj-button:first-child > .kj-button {
|
|
40
|
+
border-top-left-radius: var(--kj-radius-field);
|
|
41
|
+
border-bottom-left-radius: var(--kj-radius-field);
|
|
42
|
+
margin-left: 0;
|
|
43
|
+
}
|
|
44
|
+
.kj-button-group:not([data-orientation='vertical']) > kj-button:last-child > .kj-button {
|
|
45
|
+
border-top-right-radius: var(--kj-radius-field);
|
|
46
|
+
border-bottom-right-radius: var(--kj-radius-field);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* ── Vertical: collapse top/bottom radii on middle items ───────────── */
|
|
50
|
+
.kj-button-group[data-orientation='vertical'] > kj-button > .kj-button {
|
|
51
|
+
border-radius: 0;
|
|
52
|
+
margin-top: calc(-1 * var(--kj-border));
|
|
53
|
+
width: 100%;
|
|
54
|
+
}
|
|
55
|
+
.kj-button-group[data-orientation='vertical'] > kj-button:first-child > .kj-button {
|
|
56
|
+
border-top-left-radius: var(--kj-radius-field);
|
|
57
|
+
border-top-right-radius: var(--kj-radius-field);
|
|
58
|
+
margin-top: 0;
|
|
59
|
+
}
|
|
60
|
+
.kj-button-group[data-orientation='vertical'] > kj-button:last-child > .kj-button {
|
|
61
|
+
border-bottom-left-radius: var(--kj-radius-field);
|
|
62
|
+
border-bottom-right-radius: var(--kj-radius-field);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Lift the focused / hovered / pressed button above its neighbours so its
|
|
66
|
+
border isn't clipped by the negative-margin overlap. */
|
|
67
|
+
.kj-button-group > kj-button > .kj-button:hover,
|
|
68
|
+
.kj-button-group > kj-button > .kj-button:focus-visible,
|
|
69
|
+
.kj-button-group > kj-button > .kj-button[aria-pressed='true'] {
|
|
70
|
+
z-index: 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ── Segmented: one shared shell, hairline dividers ────────────────
|
|
74
|
+
With `kjVariant="segmented"` the group — not each button — owns the
|
|
75
|
+
border and the rounded outer corners, and adjacent segments are parted
|
|
76
|
+
by a single hairline. The children go borderless (see button.css), so
|
|
77
|
+
the negative-margin overlap used by the default look is dropped here.
|
|
78
|
+
|
|
79
|
+
Typography and the on-state fill come from the `--kj-segmented-*`
|
|
80
|
+
hooks, set on this host by the consumer. */
|
|
81
|
+
.kj-button-group[data-variant='segmented'] {
|
|
82
|
+
border: var(--kj-border) solid var(--kj-segmented-border, var(--kj-border-default));
|
|
83
|
+
border-radius: var(--kj-radius-field);
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.kj-button-group[data-variant='segmented'] > kj-button > .kj-button {
|
|
88
|
+
margin-left: 0;
|
|
89
|
+
margin-top: 0;
|
|
90
|
+
border-radius: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.kj-button-group[data-variant='segmented']:not([data-orientation='vertical'])
|
|
94
|
+
> kj-button
|
|
95
|
+
+ kj-button
|
|
96
|
+
> .kj-button {
|
|
97
|
+
border-left: var(--kj-border) solid var(--kj-segmented-border, var(--kj-border-default));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.kj-button-group[data-variant='segmented'][data-orientation='vertical']
|
|
101
|
+
> kj-button
|
|
102
|
+
+ kj-button
|
|
103
|
+
> .kj-button {
|
|
104
|
+
border-top: var(--kj-border) solid var(--kj-segmented-border, var(--kj-border-default));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Group-level disabled: visually muted when the directive flags it. The
|
|
108
|
+
individual <kj-button>s still render their own aria-disabled when their
|
|
109
|
+
own kjDisabled is true; consumers wiring the group context as a fallback
|
|
110
|
+
will get aria-disabled on every child. */
|
|
111
|
+
.kj-button-group[data-disabled] {
|
|
112
|
+
opacity: 0.7;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
.kj-calendar {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
padding: var(--kj-space-sm);
|
|
4
|
+
/* No chrome of its own — when projected into `.kj-date-picker__panel`
|
|
5
|
+
(which already paints `bg-elevated` + border + radius + shadow), a
|
|
6
|
+
duplicate border reads as a hairline ring inside the panel. A
|
|
7
|
+
stand-alone calendar inherits its parent container's surface. */
|
|
8
|
+
background: transparent;
|
|
9
|
+
font-family: inherit;
|
|
10
|
+
font-size: 0.8125rem;
|
|
11
|
+
color: var(--kj-fg-default);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.kj-calendar__header {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
gap: var(--kj-space-xs, 0.25rem);
|
|
19
|
+
margin-bottom: var(--kj-space-xs, 0.25rem);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.kj-calendar__caption {
|
|
23
|
+
margin: 0;
|
|
24
|
+
font-size: 0.8125rem;
|
|
25
|
+
font-weight: 600;
|
|
26
|
+
flex: 1;
|
|
27
|
+
text-align: center;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.kj-calendar__nav {
|
|
31
|
+
appearance: none;
|
|
32
|
+
background: transparent;
|
|
33
|
+
border: var(--kj-border) solid transparent;
|
|
34
|
+
width: 1.75rem;
|
|
35
|
+
height: 1.75rem;
|
|
36
|
+
border-radius: var(--kj-radius-field);
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
font-size: 0.875rem;
|
|
42
|
+
color: inherit;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.kj-calendar__nav:hover:not([disabled]) {
|
|
46
|
+
/* `bg-field` (not `bg-surface`) — guarantees the hover is visible on
|
|
47
|
+
`bg-elevated` panels where surface and elevated collapse to the same
|
|
48
|
+
value (light / corporate themes). */
|
|
49
|
+
background: var(--kj-bg-field);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.kj-calendar__nav[disabled] {
|
|
53
|
+
opacity: 0.4;
|
|
54
|
+
cursor: not-allowed;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.kj-calendar__grid {
|
|
58
|
+
border-collapse: collapse;
|
|
59
|
+
/* Fixed layout so every column gets the same explicit `2rem` width set
|
|
60
|
+
on `.kj-calendar__weekday` / `.kj-calendar__cell` regardless of cell
|
|
61
|
+
content. */
|
|
62
|
+
width: auto;
|
|
63
|
+
table-layout: fixed;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* th/td share the same width contributor (the inner button or label) so
|
|
67
|
+
weekday headers align perfectly above day cells. `min-width: 2rem` on
|
|
68
|
+
both keeps the seven columns equal even when a weekday abbreviation is
|
|
69
|
+
narrower than a 2-digit day. */
|
|
70
|
+
.kj-calendar__weekday {
|
|
71
|
+
width: 2rem;
|
|
72
|
+
padding: 0;
|
|
73
|
+
font-size: 0.6875rem;
|
|
74
|
+
font-weight: 500;
|
|
75
|
+
color: var(--kj-fg-muted);
|
|
76
|
+
text-align: center;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.kj-calendar__cell {
|
|
80
|
+
width: 2rem;
|
|
81
|
+
padding: 1px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.kj-calendar__day {
|
|
85
|
+
appearance: none;
|
|
86
|
+
background: transparent;
|
|
87
|
+
border: var(--kj-border) solid transparent;
|
|
88
|
+
border-radius: var(--kj-radius-selector);
|
|
89
|
+
width: 100%; /* Fill the cell so the button hits-area aligns
|
|
90
|
+
with the header column above. */
|
|
91
|
+
height: 2rem;
|
|
92
|
+
min-height: 1.75rem; /* WCAG 2.5.5 AA: ≥ 24×24px */
|
|
93
|
+
cursor: pointer;
|
|
94
|
+
font: inherit;
|
|
95
|
+
font-size: 0.8125rem;
|
|
96
|
+
color: inherit;
|
|
97
|
+
display: inline-flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.kj-calendar__day:hover:not([disabled]) {
|
|
103
|
+
background: var(--kj-bg-field);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.kj-calendar__day:focus-visible {
|
|
107
|
+
outline: 2px solid var(--kj-border-focus);
|
|
108
|
+
outline-offset: 2px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.kj-calendar__day[data-today] {
|
|
112
|
+
border-color: var(--kj-border-focus);
|
|
113
|
+
font-weight: 600;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.kj-calendar__day[data-selected] {
|
|
117
|
+
background: var(--kj-bg-primary);
|
|
118
|
+
color: var(--kj-fg-on-primary);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.kj-calendar__day[data-outside-month] {
|
|
122
|
+
opacity: 0.35;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.kj-calendar__day[data-disabled],
|
|
126
|
+
.kj-calendar__day[disabled] {
|
|
127
|
+
opacity: 0.35;
|
|
128
|
+
cursor: not-allowed;
|
|
129
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-card — component-layer tokens + structural CSS
|
|
3
|
+
──────────────────────────────────────────────────────────── */
|
|
4
|
+
|
|
5
|
+
@layer kj.component {
|
|
6
|
+
.kj-card {
|
|
7
|
+
--kj-card-bg: var(--kj-bg-surface);
|
|
8
|
+
--kj-card-fg: var(--kj-fg-default);
|
|
9
|
+
--kj-card-border-color: transparent;
|
|
10
|
+
--kj-card-border-width: var(--kj-border);
|
|
11
|
+
--kj-card-radius: var(--kj-radius-box);
|
|
12
|
+
--kj-card-padding-x: var(--kj-space-xl);
|
|
13
|
+
--kj-card-padding-y: var(--kj-space-xl);
|
|
14
|
+
--kj-card-shadow: none;
|
|
15
|
+
|
|
16
|
+
display: block;
|
|
17
|
+
background: var(--kj-card-bg);
|
|
18
|
+
color: var(--kj-card-fg);
|
|
19
|
+
border: var(--kj-card-border-width) solid var(--kj-card-border-color);
|
|
20
|
+
border-radius: var(--kj-card-radius);
|
|
21
|
+
box-shadow: var(--kj-card-shadow);
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
transition: var(--kj-transition);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* When no sub-components are used, fall back to the padded wrapper for plain content. */
|
|
27
|
+
.kj-card:not(:has(.kj-card-header, .kj-card-content, .kj-card-footer, .kj-card-cover)) {
|
|
28
|
+
padding: var(--kj-card-padding-y) var(--kj-card-padding-x);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.kj-card[data-variant="outline"] {
|
|
32
|
+
--kj-card-bg: transparent;
|
|
33
|
+
--kj-card-border-color: var(--kj-border-default);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.kj-card[data-variant="subtle"] {
|
|
37
|
+
--kj-card-bg: var(--kj-bg-field);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Lift modifier — borrows the theme's button-shadow value so cards in
|
|
41
|
+
brutalist themes (kouji / bauhaus) get the same hard offset block as
|
|
42
|
+
primary buttons. Themes without an offset signature fall back to
|
|
43
|
+
`--kj-shadow-md` so the card still reads as elevated. */
|
|
44
|
+
.kj-card[data-shadow="lift"] {
|
|
45
|
+
--kj-card-shadow: var(--kj-button-shadow, var(--kj-shadow-md));
|
|
46
|
+
--kj-card-border-color: var(--kj-border-default);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Cover image. Fixed aspect ratio + cover-fit prevents tall hero images
|
|
50
|
+
from blowing the card. The `--kj-card-cover-height` knob lets a
|
|
51
|
+
consumer override the height; `--kj-card-cover-fit` swaps cover/contain. */
|
|
52
|
+
.kj-card-cover {
|
|
53
|
+
--kj-card-cover-height: 12rem;
|
|
54
|
+
--kj-card-cover-fit: cover;
|
|
55
|
+
|
|
56
|
+
display: block;
|
|
57
|
+
height: var(--kj-card-cover-height);
|
|
58
|
+
background: var(--kj-bg-field);
|
|
59
|
+
overflow: hidden;
|
|
60
|
+
}
|
|
61
|
+
.kj-card-cover img,
|
|
62
|
+
.kj-card-cover video {
|
|
63
|
+
width: 100%;
|
|
64
|
+
height: 100%;
|
|
65
|
+
display: block;
|
|
66
|
+
object-fit: var(--kj-card-cover-fit);
|
|
67
|
+
object-position: center;
|
|
68
|
+
}
|
|
69
|
+
/* Per-instance override via [data-fit]. */
|
|
70
|
+
.kj-card-cover[data-fit="contain"] { --kj-card-cover-fit: contain; }
|
|
71
|
+
.kj-card-cover[data-fit="cover"] { --kj-card-cover-fit: cover; }
|
|
72
|
+
.kj-card-cover[data-size="sm"] { --kj-card-cover-height: 8rem; }
|
|
73
|
+
.kj-card-cover[data-size="lg"] { --kj-card-cover-height: 16rem; }
|
|
74
|
+
|
|
75
|
+
/* Header */
|
|
76
|
+
.kj-card-header {
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
gap: var(--kj-space-xs, 0.25rem);
|
|
80
|
+
padding: var(--kj-card-padding-y) var(--kj-card-padding-x);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Title */
|
|
84
|
+
.kj-card-title {
|
|
85
|
+
font: 600 1rem / 1.3 var(--kj-font-sans);
|
|
86
|
+
margin: 0;
|
|
87
|
+
color: var(--kj-fg-default);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Subtitle */
|
|
91
|
+
.kj-card-subtitle {
|
|
92
|
+
font: 0.875rem / 1.4 var(--kj-font-sans);
|
|
93
|
+
color: var(--kj-fg-default);
|
|
94
|
+
opacity: 0.6;
|
|
95
|
+
margin: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Content */
|
|
99
|
+
.kj-card-content {
|
|
100
|
+
display: block;
|
|
101
|
+
color: var(--kj-fg-default);
|
|
102
|
+
font: 0.9375rem / 1.5 var(--kj-font-sans);
|
|
103
|
+
}
|
|
104
|
+
.kj-card-content[data-padded] {
|
|
105
|
+
padding: var(--kj-card-padding-y) var(--kj-card-padding-x);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Footer */
|
|
109
|
+
.kj-card-footer {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: var(--kj-space-sm);
|
|
113
|
+
padding: var(--kj-card-padding-y) var(--kj-card-padding-x);
|
|
114
|
+
border-top: var(--kj-border) solid var(--kj-border-default);
|
|
115
|
+
}
|
|
116
|
+
.kj-card-footer[data-align="start"] { justify-content: flex-start; }
|
|
117
|
+
.kj-card-footer[data-align="center"] { justify-content: center; }
|
|
118
|
+
.kj-card-footer[data-align="end"] { justify-content: flex-end; }
|
|
119
|
+
.kj-card-footer[data-align="between"] { justify-content: space-between; }
|
|
120
|
+
}
|