@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.
Files changed (70) hide show
  1. package/package.json +1 -1
  2. package/src/accordion/accordion.css +68 -0
  3. package/src/action-sheet/action-sheet.css +79 -0
  4. package/src/alert/alert.css +133 -0
  5. package/src/avatar/avatar.css +93 -0
  6. package/src/badge/badge.css +59 -0
  7. package/src/breadcrumb/breadcrumb.css +145 -0
  8. package/src/button/button.css +295 -0
  9. package/src/button-group/button-group.css +114 -0
  10. package/src/calendar/calendar.css +129 -0
  11. package/src/card/card.css +120 -0
  12. package/src/carousel/carousel.css +181 -0
  13. package/src/cascade-select/cascade-select.css +120 -0
  14. package/src/chart/chart.css +26 -0
  15. package/src/chat/chat-ai.css +352 -0
  16. package/src/chat/chat.css +231 -0
  17. package/src/checkbox/checkbox.css +74 -0
  18. package/src/color-picker/color-picker.css +183 -0
  19. package/src/combobox/combobox.css +101 -0
  20. package/src/command-palette/command-palette.css +234 -0
  21. package/src/confirm-popup/confirm-popup.css +47 -0
  22. package/src/date-picker/date-picker.css +61 -0
  23. package/src/date-range-presets/date-range-presets.css +49 -0
  24. package/src/datetime-picker/datetime-picker.css +84 -0
  25. package/src/dialog/dialog.css +56 -0
  26. package/src/direction-toggle/direction-toggle.css +55 -0
  27. package/src/divider/divider.css +93 -0
  28. package/src/drawer/drawer.css +130 -0
  29. package/src/dropdown-menu/dropdown-menu.css +108 -0
  30. package/src/editor/editor.css +140 -0
  31. package/src/empty-state/empty-state.css +114 -0
  32. package/src/field/field.css +148 -0
  33. package/src/file-upload/file-upload.css +207 -0
  34. package/src/form/form.css +63 -0
  35. package/src/input/input.css +70 -0
  36. package/src/input-group/input-group.css +125 -0
  37. package/src/input-mask/input-mask.css +39 -0
  38. package/src/input-otp/input-otp.css +87 -0
  39. package/src/kbd/kbd.css +67 -0
  40. package/src/link/link.css +104 -0
  41. package/src/list/list.css +166 -0
  42. package/src/menubar/menubar.css +62 -0
  43. package/src/number-input/number-input.css +115 -0
  44. package/src/overflow/overflow.css +43 -0
  45. package/src/overlay/overlay.css +45 -0
  46. package/src/overlay-badge/overlay-badge.css +118 -0
  47. package/src/pagination/pagination.css +146 -0
  48. package/src/password-input/password-input.css +155 -0
  49. package/src/popover/popover.css +71 -0
  50. package/src/progress-bar/progress-bar.css +113 -0
  51. package/src/radio/radio.css +58 -0
  52. package/src/rich-text/rich-text-editor.css +351 -0
  53. package/src/select/select.css +91 -0
  54. package/src/sheet/sheet.css +121 -0
  55. package/src/skeleton/skeleton.css +111 -0
  56. package/src/skip-link/skip-link.css +58 -0
  57. package/src/slider/slider.css +172 -0
  58. package/src/speed-dial/speed-dial.css +137 -0
  59. package/src/spinner/spinner.css +161 -0
  60. package/src/stepper/stepper.css +147 -0
  61. package/src/table/table-filters/filters.css +53 -0
  62. package/src/table/table.css +509 -0
  63. package/src/tabs/tabs.css +162 -0
  64. package/src/tag/tag.css +183 -0
  65. package/src/textarea/textarea.css +117 -0
  66. package/src/time-picker/time-picker.css +126 -0
  67. package/src/toast/toast.css +115 -0
  68. package/src/toggle/toggle.css +123 -0
  69. package/src/tooltip/tooltip.css +61 -0
  70. package/src/tree-select/tree-select.css +220 -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
+ }