@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.
Files changed (73) hide show
  1. package/fesm2022/kouji-ui-components.mjs +49 -3
  2. package/fesm2022/kouji-ui-components.mjs.map +1 -1
  3. package/package.json +1 -1
  4. package/src/accordion/accordion.css +68 -0
  5. package/src/action-sheet/action-sheet.css +79 -0
  6. package/src/alert/alert.css +133 -0
  7. package/src/avatar/avatar.css +93 -0
  8. package/src/badge/badge.css +59 -0
  9. package/src/breadcrumb/breadcrumb.css +145 -0
  10. package/src/button/button.css +295 -0
  11. package/src/button-group/button-group.css +114 -0
  12. package/src/calendar/calendar.css +129 -0
  13. package/src/card/card.css +120 -0
  14. package/src/carousel/carousel.css +181 -0
  15. package/src/cascade-select/cascade-select.css +120 -0
  16. package/src/chart/chart.css +26 -0
  17. package/src/chat/chat-ai.css +352 -0
  18. package/src/chat/chat.css +231 -0
  19. package/src/checkbox/checkbox.css +74 -0
  20. package/src/color-picker/color-picker.css +183 -0
  21. package/src/combobox/combobox.css +101 -0
  22. package/src/command-palette/command-palette.css +234 -0
  23. package/src/confirm-popup/confirm-popup.css +47 -0
  24. package/src/date-picker/date-picker.css +61 -0
  25. package/src/date-range-presets/date-range-presets.css +49 -0
  26. package/src/datetime-picker/datetime-picker.css +84 -0
  27. package/src/dialog/dialog.css +56 -0
  28. package/src/direction-toggle/direction-toggle.css +55 -0
  29. package/src/divider/divider.css +93 -0
  30. package/src/drawer/drawer.css +130 -0
  31. package/src/dropdown-menu/dropdown-menu.css +108 -0
  32. package/src/editor/editor.css +140 -0
  33. package/src/empty-state/empty-state.css +114 -0
  34. package/src/field/field.css +148 -0
  35. package/src/file-upload/file-upload.css +207 -0
  36. package/src/form/form.css +63 -0
  37. package/src/input/input.css +70 -0
  38. package/src/input-group/input-group.css +125 -0
  39. package/src/input-mask/input-mask.css +39 -0
  40. package/src/input-otp/input-otp.css +87 -0
  41. package/src/kbd/kbd.css +67 -0
  42. package/src/link/link.css +104 -0
  43. package/src/list/list.css +166 -0
  44. package/src/menubar/menubar.css +62 -0
  45. package/src/number-input/number-input.css +115 -0
  46. package/src/overflow/overflow.css +43 -0
  47. package/src/overlay/overlay.css +45 -0
  48. package/src/overlay-badge/overlay-badge.css +118 -0
  49. package/src/pagination/pagination.css +146 -0
  50. package/src/password-input/password-input.css +155 -0
  51. package/src/popover/popover.css +71 -0
  52. package/src/progress-bar/progress-bar.css +113 -0
  53. package/src/radio/radio.css +58 -0
  54. package/src/rich-text/rich-text-editor.css +351 -0
  55. package/src/select/select.css +91 -0
  56. package/src/sheet/sheet.css +121 -0
  57. package/src/skeleton/skeleton.css +111 -0
  58. package/src/skip-link/skip-link.css +58 -0
  59. package/src/slider/slider.css +172 -0
  60. package/src/speed-dial/speed-dial.css +137 -0
  61. package/src/spinner/spinner.css +161 -0
  62. package/src/stepper/stepper.css +147 -0
  63. package/src/table/table-filters/filters.css +53 -0
  64. package/src/table/table.css +509 -0
  65. package/src/tabs/tabs.css +162 -0
  66. package/src/tag/tag.css +183 -0
  67. package/src/textarea/textarea.css +117 -0
  68. package/src/time-picker/time-picker.css +126 -0
  69. package/src/toast/toast.css +115 -0
  70. package/src/toggle/toggle.css +123 -0
  71. package/src/tooltip/tooltip.css +61 -0
  72. package/src/tree-select/tree-select.css +220 -0
  73. package/types/kouji-ui-components.d.ts +19 -0
@@ -0,0 +1,162 @@
1
+ @layer kj.component {
2
+ /* Two tab shapes, because that is what real products use:
3
+ - `default` — an UNDERLINE strip. The tabs sit flat on the surface and the
4
+ active one is marked by a short bar on the strip's edge. This is the
5
+ panel/section tab: it divides a surface that is already there.
6
+ - `pills` — a recessed TRAY holding chips. The active tab lifts to the
7
+ body colour with a hairline ring. This is the view-mode switch: a
8
+ control that floats on top of a surface.
9
+ Everything either shape needs is a knob, so a consumer re-themes them
10
+ without redeclaring the recipe. */
11
+ .kj-tabs {
12
+ display: block;
13
+
14
+ /* shared */
15
+ --kj-tab-fg: var(--kj-fg-muted);
16
+ --kj-tab-fg-active: var(--kj-fg-default);
17
+ --kj-tab-font-size: 0.875rem;
18
+ --kj-tab-font-weight: 400;
19
+ --kj-tab-padding-x: var(--kj-space-md);
20
+ --kj-tab-padding-y: var(--kj-space-sm);
21
+ --kj-tab-gap: 0;
22
+
23
+ /* default (underline) */
24
+ --kj-tab-indicator-color: var(--kj-bg-primary);
25
+ --kj-tab-indicator-size: 2px;
26
+ /* how far the bar is held back from each edge of the tab. 0 makes it span
27
+ the full tab, which is the classic material underline. */
28
+ --kj-tab-indicator-inset: 0px;
29
+ --kj-tab-list-border-color: var(--kj-border-default);
30
+ --kj-tab-list-gap: var(--kj-space-lg);
31
+
32
+ /* pills (tray) */
33
+ --kj-tab-tray-bg: var(--kj-bg-surface);
34
+ --kj-tab-tray-border-color: var(--kj-border-default);
35
+ --kj-tab-tray-radius: var(--kj-radius-field);
36
+ --kj-tab-tray-padding: 3px;
37
+ --kj-tab-tray-gap: 2px;
38
+ --kj-tab-chip-radius: calc(var(--kj-radius-field) - 2px);
39
+ --kj-tab-chip-bg-active: var(--kj-bg-body);
40
+ --kj-tab-chip-ring-active: var(--kj-border-default);
41
+ }
42
+ .kj-tabs[data-orientation="vertical"] {
43
+ display: grid;
44
+ grid-template-columns: auto 1fr;
45
+ gap: var(--kj-space-lg);
46
+ }
47
+
48
+ .kj-tab-list {
49
+ display: flex;
50
+ gap: var(--kj-tab-gap);
51
+ border-bottom: 1px solid var(--kj-tab-list-border-color);
52
+ margin-bottom: var(--kj-tab-list-gap);
53
+ }
54
+ .kj-tabs[data-orientation="vertical"] .kj-tab-list {
55
+ flex-direction: column;
56
+ border-bottom: 0;
57
+ border-right: 1px solid var(--kj-tab-list-border-color);
58
+ margin: 0;
59
+ }
60
+
61
+ .kj-tab {
62
+ position: relative;
63
+ background: none;
64
+ border: 0;
65
+ color: var(--kj-tab-fg);
66
+ padding: var(--kj-tab-padding-y) var(--kj-tab-padding-x);
67
+ font: var(--kj-tab-font-weight) var(--kj-tab-font-size) / 1.2 var(--kj-font-sans);
68
+ cursor: pointer;
69
+ transition: color var(--kj-transition), background-color var(--kj-transition),
70
+ box-shadow var(--kj-transition);
71
+ }
72
+ /* The active mark is a pseudo-element, not a border: a border cannot be
73
+ inset from the tab's edges, and the old `opacity: .6` for the rest state
74
+ dimmed the tab's ICON and any badge along with the label. */
75
+ .kj-tab::after {
76
+ content: "";
77
+ position: absolute;
78
+ left: var(--kj-tab-indicator-inset);
79
+ right: var(--kj-tab-indicator-inset);
80
+ bottom: 0;
81
+ height: var(--kj-tab-indicator-size);
82
+ background: var(--kj-tab-indicator-color);
83
+ opacity: 0;
84
+ transition: opacity var(--kj-transition);
85
+ }
86
+ .kj-tab[aria-selected="true"] {
87
+ color: var(--kj-tab-fg-active);
88
+ }
89
+ .kj-tab[aria-selected="true"]::after {
90
+ opacity: 1;
91
+ }
92
+ .kj-tab:hover {
93
+ color: var(--kj-tab-fg-active);
94
+ }
95
+ .kj-tab:focus-visible {
96
+ outline: 2px solid var(--kj-border-focus, var(--kj-bg-primary));
97
+ outline-offset: -2px;
98
+ }
99
+ .kj-tab:disabled,
100
+ .kj-tab[aria-disabled="true"] {
101
+ opacity: 0.4;
102
+ cursor: not-allowed;
103
+ }
104
+
105
+ /* ---- pills: a tray of chips ---- */
106
+ .kj-tabs[data-variant="pills"] {
107
+ /* the host IS the tray when only a tab-list is rendered (a view-mode
108
+ toggle with no panels) */
109
+ display: inline-block;
110
+ }
111
+ .kj-tabs[data-variant="pills"] .kj-tab-list {
112
+ display: inline-flex;
113
+ gap: var(--kj-tab-tray-gap);
114
+ padding: var(--kj-tab-tray-padding);
115
+ background-color: var(--kj-tab-tray-bg);
116
+ border: 1px solid var(--kj-tab-tray-border-color);
117
+ border-radius: var(--kj-tab-tray-radius);
118
+ width: fit-content;
119
+ margin-bottom: 0;
120
+ }
121
+ .kj-tabs[data-variant="pills"] .kj-tab {
122
+ border-radius: var(--kj-tab-chip-radius);
123
+ margin: 0;
124
+ }
125
+ /* no underline in a tray — the chip itself carries the state */
126
+ .kj-tabs[data-variant="pills"] .kj-tab::after {
127
+ content: none;
128
+ }
129
+ .kj-tabs[data-variant="pills"] .kj-tab[aria-selected="true"] {
130
+ background-color: var(--kj-tab-chip-bg-active);
131
+ color: var(--kj-tab-fg-active);
132
+ box-shadow: inset 0 0 0 1px var(--kj-tab-chip-ring-active);
133
+ }
134
+
135
+ /* ---- vertical: the bar moves to the inline edge ---- */
136
+ .kj-tabs[data-orientation="vertical"] .kj-tab {
137
+ text-align: left;
138
+ }
139
+ .kj-tabs[data-orientation="vertical"] .kj-tab::after {
140
+ left: auto;
141
+ right: 0;
142
+ top: var(--kj-tab-indicator-inset);
143
+ bottom: var(--kj-tab-indicator-inset);
144
+ width: var(--kj-tab-indicator-size);
145
+ height: auto;
146
+ }
147
+
148
+ .kj-tab-panel {
149
+ /* Default `display: block` — without it the host renders as inline
150
+ (default for unknown custom elements), which collapses any block
151
+ child's width to the inline flow width. That broke the virtualized
152
+ table demo: `.kj-table-body` couldn't get a non-zero ResizeObserver
153
+ rect, so the virtualizer rendered 0 visible rows. */
154
+ display: block;
155
+ color: var(--kj-fg-default);
156
+ font: 0.875rem var(--kj-font-sans);
157
+ line-height: 1.5;
158
+ }
159
+ .kj-tab-panel[hidden] {
160
+ display: none;
161
+ }
162
+ }
@@ -0,0 +1,183 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ .kj-tag — component-layer tokens + structural CSS
3
+ Mirrors .kj-badge's chrome (rounded pill, density tokens) and
4
+ layers in the interactive states (hover, focus-visible, pressed,
5
+ disabled) that distinguish a tag from a static badge.
6
+ ──────────────────────────────────────────────────────────── */
7
+
8
+ @layer kj.component {
9
+ .kj-tag {
10
+ --kj-tag-bg: var(--kj-bg-primary);
11
+ --kj-tag-fg: var(--kj-fg-on-primary);
12
+ --kj-tag-border-color: transparent;
13
+ --kj-tag-radius: var(--kj-radius-selector);
14
+ --kj-tag-padding-x: var(--kj-space-sm);
15
+ --kj-tag-padding-y: 0.125rem;
16
+ --kj-tag-font-size: 0.75rem;
17
+ --kj-tag-gap: 0.25rem;
18
+
19
+ display: inline-flex;
20
+ align-items: center;
21
+ gap: var(--kj-tag-gap);
22
+ background: var(--kj-tag-bg);
23
+ color: var(--kj-tag-fg);
24
+ border: var(--kj-border) solid var(--kj-tag-border-color);
25
+ border-radius: var(--kj-tag-radius);
26
+ padding: var(--kj-tag-padding-y) var(--kj-tag-padding-x);
27
+ font: var(--kj-tag-font-size) / 1 var(--kj-font-sans);
28
+ font-weight: 500;
29
+ transition:
30
+ background-color var(--kj-transition),
31
+ color var(--kj-transition),
32
+ border-color var(--kj-transition);
33
+ }
34
+
35
+ /* ── Variants (parity with KjBadge) ──────────────────────────────── */
36
+ .kj-tag[data-variant='secondary'] {
37
+ --kj-tag-bg: var(--kj-bg-field);
38
+ --kj-tag-fg: var(--kj-fg-default);
39
+ }
40
+ .kj-tag[data-variant='destructive'],
41
+ .kj-tag[data-variant='danger'] {
42
+ --kj-tag-bg: var(--kj-bg-danger);
43
+ --kj-tag-fg: var(--kj-fg-on-danger);
44
+ }
45
+ .kj-tag[data-variant='success'] {
46
+ --kj-tag-bg: var(--kj-bg-success, var(--kj-bg-primary));
47
+ --kj-tag-fg: var(--kj-fg-on-success, var(--kj-fg-on-primary));
48
+ }
49
+ .kj-tag[data-variant='warning'] {
50
+ --kj-tag-bg: var(--kj-bg-warning, var(--kj-bg-primary));
51
+ --kj-tag-fg: var(--kj-fg-on-warning, var(--kj-fg-on-primary));
52
+ }
53
+ .kj-tag[data-variant='info'] {
54
+ --kj-tag-bg: var(--kj-bg-info, var(--kj-bg-primary));
55
+ --kj-tag-fg: var(--kj-fg-on-info, var(--kj-fg-on-primary));
56
+ }
57
+ .kj-tag[data-variant='outline'] {
58
+ --kj-tag-bg: transparent;
59
+ --kj-tag-fg: var(--kj-fg-default);
60
+ --kj-tag-border-color: var(--kj-border-default);
61
+ }
62
+ .kj-tag[data-variant='ghost'] {
63
+ --kj-tag-bg: transparent;
64
+ --kj-tag-fg: var(--kj-fg-default);
65
+ --kj-tag-border-color: transparent;
66
+ }
67
+
68
+ /* ── Sizes (strict ladder: xs < sm < default/md < lg) ───────────── */
69
+ .kj-tag[data-size='xs'] {
70
+ --kj-tag-padding-x: 0.1875rem;
71
+ --kj-tag-padding-y: 0.03125rem;
72
+ --kj-tag-font-size: 0.5625rem; /* 9px — dense/meta chips only */
73
+ --kj-tag-gap: 0.125rem;
74
+ }
75
+ .kj-tag[data-size='sm'] {
76
+ --kj-tag-padding-x: 0.375rem;
77
+ --kj-tag-padding-y: 0.125rem;
78
+ --kj-tag-font-size: 0.6875rem; /* 11px — step above xs */
79
+ }
80
+ .kj-tag[data-size='lg'] {
81
+ --kj-tag-padding-x: var(--kj-space-md);
82
+ --kj-tag-font-size: 0.875rem;
83
+ }
84
+
85
+ /* ── Interactive states (selectable / listbox option) ────────────── */
86
+ .kj-tag[role='button'],
87
+ .kj-tag[role='option'] {
88
+ cursor: pointer;
89
+ user-select: none;
90
+ }
91
+ .kj-tag[role='button']:hover,
92
+ .kj-tag[role='option']:hover {
93
+ --kj-tag-bg: color-mix(in oklab, var(--kj-tag-bg) 88%, var(--kj-fg-default));
94
+ }
95
+ .kj-tag[aria-pressed='true'],
96
+ .kj-tag[aria-selected='true'],
97
+ .kj-tag[data-selected] {
98
+ --kj-tag-bg: var(--kj-bg-primary);
99
+ --kj-tag-fg: var(--kj-fg-on-primary);
100
+ --kj-tag-border-color: var(--kj-bg-primary);
101
+ }
102
+ .kj-tag[data-focus-visible]:focus,
103
+ .kj-tag:focus-visible {
104
+ outline: 2px solid var(--kj-bg-primary);
105
+ outline-offset: 2px;
106
+ }
107
+
108
+ /* ── Disabled ─────────────────────────────────────────────────────── */
109
+ .kj-tag[aria-disabled='true'],
110
+ .kj-tag[data-disabled] {
111
+ opacity: 0.45;
112
+ cursor: not-allowed;
113
+ pointer-events: none;
114
+ }
115
+
116
+ /* ── Remove button ────────────────────────────────────────────────── */
117
+ .kj-tag-remove {
118
+ display: inline-flex;
119
+ align-items: center;
120
+ justify-content: center;
121
+ width: 1rem;
122
+ height: 1rem;
123
+ margin-inline-start: 0.125rem;
124
+ padding: 0;
125
+ border: none;
126
+ background: transparent;
127
+ color: inherit;
128
+ border-radius: 999px;
129
+ cursor: pointer;
130
+ line-height: 1;
131
+ font: inherit;
132
+ /* Touch-target enlargement (WCAG 2.5.5): visible glyph stays small,
133
+ the hit area grows via a transparent ::after overlay so it does
134
+ not inflate the chip's visible footprint. */
135
+ position: relative;
136
+ }
137
+ .kj-tag-remove::after {
138
+ content: '';
139
+ position: absolute;
140
+ inset: -0.875rem;
141
+ border-radius: inherit;
142
+ }
143
+ .kj-tag-remove:hover {
144
+ background: color-mix(in oklab, currentColor 18%, transparent);
145
+ }
146
+ .kj-tag-remove:focus-visible {
147
+ outline: 2px solid currentColor;
148
+ outline-offset: 2px;
149
+ }
150
+ .kj-tag-remove[aria-disabled='true'] {
151
+ opacity: 0.45;
152
+ cursor: not-allowed;
153
+ pointer-events: none;
154
+ }
155
+
156
+ /* The host's `[hidden]` (written by kj-tag-list when a chip overflows
157
+ `kjMax`) needs an explicit `display: none`: the `inline-flex` above beats
158
+ the UA `[hidden]` rule on specificity. */
159
+ .kj-tag[hidden] {
160
+ display: none;
161
+ }
162
+
163
+ /* ── Tag list container ───────────────────────────────────────────── */
164
+ .kj-tag-list {
165
+ display: inline-flex;
166
+ flex-wrap: wrap;
167
+ gap: var(--kj-space-sm);
168
+ align-items: center;
169
+ }
170
+ /* The "+N" chip: a native button painted as a tag. */
171
+ .kj-tag-list-overflow {
172
+ border-style: dashed;
173
+ --kj-tag-border-color: var(--kj-border-default);
174
+ }
175
+ .kj-tag-list-overflow[aria-expanded='true'] {
176
+ --kj-tag-bg: var(--kj-bg-field);
177
+ }
178
+ .kj-tag-list[role='listbox']:focus-visible,
179
+ .kj-tag-list[role='grid']:focus-visible {
180
+ outline: 2px solid var(--kj-bg-primary);
181
+ outline-offset: 2px;
182
+ }
183
+ }
@@ -0,0 +1,117 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ .kj-textarea — component-layer tokens + structural CSS
3
+ Same pattern as button.css / input.css: tokens default to
4
+ shared-layer values, variants/sizes/states retarget tokens,
5
+ structural CSS reads tokens only.
6
+ ──────────────────────────────────────────────────────────── */
7
+
8
+ @layer kj.component {
9
+ .kj-textarea {
10
+ /* component knobs — themes/users can override any of these */
11
+ --kj-textarea-bg: var(--kj-bg-field);
12
+ --kj-textarea-fg: var(--kj-fg-default);
13
+ --kj-textarea-border-color: var(--kj-border-default);
14
+ --kj-textarea-border-style: solid;
15
+ --kj-textarea-border-width: var(--kj-border);
16
+ --kj-textarea-radius: var(--kj-radius-field);
17
+ --kj-textarea-padding-x: var(--kj-space-md);
18
+ --kj-textarea-padding-y: var(--kj-space-sm);
19
+ --kj-textarea-font: var(--kj-font-sans);
20
+ --kj-textarea-font-size: var(--kj-text-sm);
21
+ --kj-textarea-line-height: 1.5;
22
+ --kj-textarea-placeholder-fg: var(--kj-fg-muted);
23
+ --kj-textarea-min-height: 2.75rem; /* WCAG 2.5.5 touch target floor */
24
+
25
+ display: block;
26
+ width: 100%;
27
+ min-height: var(--kj-textarea-min-height);
28
+ background: var(--kj-textarea-bg);
29
+ color: var(--kj-textarea-fg);
30
+ border: var(--kj-textarea-border-width) var(--kj-textarea-border-style) var(--kj-textarea-border-color);
31
+ border-radius: var(--kj-textarea-radius);
32
+ padding: var(--kj-textarea-padding-y) var(--kj-textarea-padding-x);
33
+ font: var(--kj-textarea-font-size) / var(--kj-textarea-line-height) var(--kj-textarea-font);
34
+ transition: var(--kj-transition);
35
+ /* `resize` is set inline by KjTextarea (`[style.resize]`) and overrides
36
+ this fallback when present. */
37
+ resize: vertical;
38
+ }
39
+
40
+ .kj-textarea::placeholder {
41
+ color: var(--kj-textarea-placeholder-fg);
42
+ }
43
+
44
+ .kj-textarea:focus-visible {
45
+ outline: 2px solid var(--kj-bg-primary);
46
+ outline-offset: 2px;
47
+ --kj-textarea-border-color: var(--kj-bg-primary);
48
+ }
49
+
50
+ .kj-textarea[aria-invalid='true'] {
51
+ --kj-textarea-border-color: var(--kj-bg-danger);
52
+ }
53
+
54
+ .kj-textarea[aria-disabled='true'],
55
+ .kj-textarea:disabled {
56
+ opacity: 0.55;
57
+ cursor: not-allowed;
58
+ }
59
+
60
+ /* ── Variants ─────────────────────────────────────────────── */
61
+ .kj-textarea[data-variant='filled'] {
62
+ --kj-textarea-bg: var(--kj-bg-surface);
63
+ --kj-textarea-border-color: transparent;
64
+ }
65
+ .kj-textarea[data-variant='filled']:focus-visible {
66
+ --kj-textarea-border-color: var(--kj-bg-primary);
67
+ }
68
+
69
+ /* ── Sizes ────────────────────────────────────────────────── */
70
+ .kj-textarea[data-size='sm'] {
71
+ --kj-textarea-padding-x: var(--kj-space-sm);
72
+ --kj-textarea-padding-y: 0.25rem;
73
+ --kj-textarea-font-size: 0.8125rem;
74
+ }
75
+ .kj-textarea[data-size='lg'] {
76
+ --kj-textarea-padding-x: var(--kj-space-lg);
77
+ --kj-textarea-padding-y: 0.625rem;
78
+ --kj-textarea-font-size: 1rem;
79
+ }
80
+
81
+ /* ── Auto-resize: hide the user drag handle ──────────────── */
82
+ .kj-textarea[data-autoresize] {
83
+ /* `[style.resize]` from the directive already sets resize:none, but
84
+ keeping the rule here for older browsers / SSR before the directive
85
+ initialises. */
86
+ resize: none;
87
+ overflow-y: hidden;
88
+ }
89
+
90
+ /* ── Counter ─────────────────────────────────────────────── */
91
+ .kj-textarea__counter {
92
+ margin-top: var(--kj-space-xs, 0.25rem);
93
+ font: var(--kj-text-xs, 0.75rem) / 1.2 var(--kj-font-mono, var(--kj-font-sans));
94
+ color: var(--kj-fg-muted);
95
+ text-align: end;
96
+ }
97
+ .kj-textarea__counter[data-state='warn'] {
98
+ color: var(--kj-fg-warning, var(--kj-fg-muted));
99
+ }
100
+ .kj-textarea__counter[data-state='limit'] {
101
+ color: var(--kj-fg-danger);
102
+ }
103
+
104
+ /* Live region: visually hidden but readable to AT. Mirrors the
105
+ `.sr-only` pattern. */
106
+ .kj-textarea__live {
107
+ position: absolute;
108
+ width: 1px;
109
+ height: 1px;
110
+ padding: 0;
111
+ margin: -1px;
112
+ overflow: hidden;
113
+ clip: rect(0, 0, 0, 0);
114
+ white-space: nowrap;
115
+ border: 0;
116
+ }
117
+ }
@@ -0,0 +1,126 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ .kj-time-picker — wrapper around KjTimePicker + segment directives.
3
+
4
+ Three (or four) segmented spinbuttons sit inline. Each segment is a
5
+ 2-character `<input>` with right-aligned text; the colon separator
6
+ between them is presentational. The meridiem toggle is a kj-button-
7
+ like surface that flips AM ↔ PM.
8
+ ──────────────────────────────────────────────────────────── */
9
+
10
+ @layer kj.component {
11
+ .kj-time-picker {
12
+ --kj-time-picker-bg: var(--kj-bg-field);
13
+ --kj-time-picker-fg: var(--kj-fg-default);
14
+ --kj-time-picker-border-color: var(--kj-border-default);
15
+ --kj-time-picker-border-width: var(--kj-border);
16
+ --kj-time-picker-radius: var(--kj-radius-field);
17
+ --kj-time-picker-padding-x: var(--kj-space-md);
18
+ --kj-time-picker-font: var(--kj-font-sans);
19
+ --kj-time-picker-font-size: var(--kj-text-sm);
20
+ --kj-time-picker-segment-width: 2rem;
21
+ --kj-time-picker-meridiem-bg: var(--kj-bg-surface);
22
+ --kj-time-picker-meridiem-fg: var(--kj-fg-default);
23
+ /* Mirror `.kj-input` so a time-picker lines up vertically with sibling
24
+ inputs in a form row. Explicit height pins the box (sm/md/lg). Don't
25
+ set `align-self` here — when the parent is a column-flex stack
26
+ (e.g. <kj-form-field>'s label + control), `center` would horizontally
27
+ center this `inline-flex` host instead of leaving it at start. */
28
+ --kj-time-picker-height: 2.25rem;
29
+
30
+ display: inline-flex;
31
+ align-items: center;
32
+ gap: 0.125rem;
33
+ height: var(--kj-time-picker-height);
34
+ background: var(--kj-time-picker-bg);
35
+ color: var(--kj-time-picker-fg);
36
+ border: var(--kj-time-picker-border-width) solid var(--kj-time-picker-border-color);
37
+ border-radius: var(--kj-time-picker-radius);
38
+ padding: 0 var(--kj-time-picker-padding-x);
39
+ font: var(--kj-time-picker-font-size) / 1.4 var(--kj-time-picker-font);
40
+ transition: var(--kj-transition);
41
+ }
42
+ .kj-time-picker[data-size="sm"] { --kj-time-picker-height: 2rem; --kj-time-picker-font-size: 0.8125rem; }
43
+ .kj-time-picker[data-size="lg"] { --kj-time-picker-height: 2.75rem; --kj-time-picker-font-size: 1rem; }
44
+
45
+ .kj-time-picker:focus-within {
46
+ --kj-time-picker-border-color: var(--kj-bg-primary);
47
+ outline: 2px solid var(--kj-bg-primary);
48
+ outline-offset: 2px;
49
+ }
50
+
51
+ .kj-time-picker[aria-invalid="true"] {
52
+ --kj-time-picker-border-color: var(--kj-bg-danger);
53
+ }
54
+
55
+ .kj-time-picker[data-disabled] {
56
+ opacity: 0.55;
57
+ cursor: not-allowed;
58
+ }
59
+
60
+ .kj-time-picker__segment {
61
+ width: var(--kj-time-picker-segment-width);
62
+ height: 100%;
63
+ background: transparent;
64
+ color: inherit;
65
+ border: 0;
66
+ padding: 0 0.25rem;
67
+ font: inherit;
68
+ font-variant-numeric: tabular-nums;
69
+ text-align: center;
70
+ appearance: textfield;
71
+ border-radius: var(--kj-radius-sm, 0.25rem);
72
+ transition: background var(--kj-transition);
73
+ }
74
+
75
+ .kj-time-picker__segment:focus {
76
+ outline: none;
77
+ background: color-mix(in oklab, var(--kj-bg-primary) 12%, transparent);
78
+ }
79
+
80
+ .kj-time-picker__segment[aria-invalid="true"] {
81
+ color: var(--kj-fg-danger);
82
+ }
83
+
84
+ .kj-time-picker__segment::-webkit-outer-spin-button,
85
+ .kj-time-picker__segment::-webkit-inner-spin-button {
86
+ appearance: none;
87
+ margin: 0;
88
+ }
89
+
90
+ .kj-time-picker__separator {
91
+ user-select: none;
92
+ color: color-mix(in oklab, var(--kj-time-picker-fg) 70%, transparent);
93
+ }
94
+
95
+ .kj-time-picker__meridiem {
96
+ margin-inline-start: var(--kj-space-sm);
97
+ min-width: 2.25rem;
98
+ min-height: 2.25rem;
99
+ display: inline-flex;
100
+ align-items: center;
101
+ justify-content: center;
102
+ background: var(--kj-time-picker-meridiem-bg);
103
+ color: var(--kj-time-picker-meridiem-fg);
104
+ border: 0;
105
+ border-radius: var(--kj-radius-field);
106
+ cursor: pointer;
107
+ font: inherit;
108
+ font-weight: 600;
109
+ transition: background var(--kj-transition), color var(--kj-transition);
110
+ }
111
+
112
+ .kj-time-picker__meridiem:hover {
113
+ background: color-mix(in oklab, var(--kj-fg-default) 8%, var(--kj-time-picker-meridiem-bg));
114
+ }
115
+
116
+ .kj-time-picker__meridiem[aria-pressed="true"] {
117
+ background: var(--kj-bg-primary);
118
+ color: var(--kj-fg-on-primary);
119
+ }
120
+
121
+ .kj-time-picker__meridiem[aria-disabled="true"] {
122
+ opacity: 0.4;
123
+ cursor: not-allowed;
124
+ pointer-events: none;
125
+ }
126
+ }
@@ -0,0 +1,115 @@
1
+ @layer kj.component {
2
+ /* ── Viewport ── */
3
+ .kj-toast-viewport {
4
+ position: fixed;
5
+ list-style: none;
6
+ padding: 0;
7
+ margin: 0;
8
+ z-index: var(--kj-toast-z-index, 2000);
9
+ /* Default position: bottom-right (mirrors KJ_TOAST_SONNER_STRATEGY) */
10
+ bottom: 1rem;
11
+ right: 1rem;
12
+ width: 22rem;
13
+ max-width: calc(100vw - 2rem);
14
+ }
15
+
16
+ /* Positional overrides driven by data-position-* from the directive */
17
+ .kj-toast-viewport[data-position-y="bottom"] { bottom: 1rem; top: auto; }
18
+ .kj-toast-viewport[data-position-y="top"] { top: 1rem; bottom: auto; }
19
+ .kj-toast-viewport[data-position-x="end"] { right: 1rem; left: auto; }
20
+ .kj-toast-viewport[data-position-x="start"] { left: 1rem; right: auto; }
21
+ .kj-toast-viewport[data-position-x="center"] { left: 50%; right: auto; transform: translateX(-50%); }
22
+
23
+ /* Each <li> wraps a toast — absolutely positioned so siblings overlap. */
24
+ .kj-toast-viewport li {
25
+ position: absolute;
26
+ left: 0;
27
+ right: 0;
28
+ transition:
29
+ transform 0.4s cubic-bezier(0.21, 0.61, 0.35, 1),
30
+ opacity 0.3s ease;
31
+ }
32
+ .kj-toast-viewport[data-position-y="bottom"] li { bottom: 0; }
33
+ .kj-toast-viewport[data-position-y="top"] li { top: 0; }
34
+
35
+ /* ── Collapsed (stacked) state ── */
36
+ .kj-toast-viewport[data-expanded="false"] .kj-toast:not([data-front="true"]) {
37
+ height: var(--kj-toast-front-height);
38
+ overflow: hidden;
39
+ transform:
40
+ translateY(calc((var(--kj-toast-index) - 1) * var(--kj-toast-gap, 14px) * -1))
41
+ scale(calc(1 - (var(--kj-toast-index) - 1) * 0.06));
42
+ opacity: calc(1 - (var(--kj-toast-index) - 1) * 0.25);
43
+ }
44
+ .kj-toast-viewport[data-position-y="top"][data-expanded="false"] .kj-toast:not([data-front="true"]) {
45
+ transform:
46
+ translateY(calc((var(--kj-toast-index) - 1) * var(--kj-toast-gap, 14px)))
47
+ scale(calc(1 - (var(--kj-toast-index) - 1) * 0.06));
48
+ }
49
+
50
+ /* ── Expanded state ── */
51
+ .kj-toast-viewport[data-expanded="true"] .kj-toast {
52
+ transform: translateY(
53
+ calc((var(--kj-toast-index) - 1) * (var(--kj-toast-height, 0px) + var(--kj-toast-gap, 14px)) * -1)
54
+ );
55
+ }
56
+ .kj-toast-viewport[data-position-y="top"][data-expanded="true"] .kj-toast {
57
+ transform: translateY(
58
+ calc((var(--kj-toast-index) - 1) * (var(--kj-toast-height, 0px) + var(--kj-toast-gap, 14px)))
59
+ );
60
+ }
61
+
62
+ /* ── Toast ── */
63
+ .kj-toast {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: var(--kj-space-md, 0.75rem);
67
+ padding: var(--kj-space-md, 0.75rem) var(--kj-space-lg, 1rem);
68
+ min-width: 18rem;
69
+ font: 0.875rem / 1.4 var(--kj-font-sans, sans-serif);
70
+ border-radius: var(--kj-radius-box, 0.5rem);
71
+ box-shadow: 0 4px 16px rgb(0 0 0 / 0.18), 0 1px 0 rgb(255 255 255 / 0.04) inset;
72
+ border: var(--kj-border, 1px) solid var(--kj-border-default, #e2e8f0);
73
+ background: var(--kj-bg-elevated, #fff);
74
+ color: var(--kj-fg-default, #1a202c);
75
+ animation: kj-toast-enter 0.35s cubic-bezier(0.21, 0.61, 0.35, 1);
76
+ }
77
+
78
+ @keyframes kj-toast-enter {
79
+ from { opacity: 0; transform: translateY(100%) scale(0.95); }
80
+ to { opacity: 1; transform: translateY(0) scale(1); }
81
+ }
82
+
83
+ .kj-toast[data-variant="success"] {
84
+ background: var(--kj-bg-success, #16a34a);
85
+ color: var(--kj-fg-on-success, #fff);
86
+ border-color: transparent;
87
+ }
88
+ .kj-toast[data-variant="warning"] {
89
+ background: var(--kj-bg-warning, #d97706);
90
+ color: var(--kj-fg-on-warning, #fff);
91
+ border-color: transparent;
92
+ }
93
+ .kj-toast[data-variant="destructive"] {
94
+ background: var(--kj-bg-danger, #dc2626);
95
+ color: var(--kj-fg-on-danger, #fff);
96
+ border-color: transparent;
97
+ }
98
+
99
+ /* ── Close button ── */
100
+ .kj-toast-close {
101
+ background: transparent;
102
+ border: 0;
103
+ color: inherit;
104
+ cursor: pointer;
105
+ padding: 4px 6px;
106
+ border-radius: 4px;
107
+ line-height: 1;
108
+ font-size: 1.1rem;
109
+ opacity: 0.7;
110
+ margin-inline-start: auto;
111
+ flex-shrink: 0;
112
+ }
113
+ .kj-toast-close:hover { opacity: 1; background: rgb(0 0 0 / 0.08); }
114
+ .kj-toast-close:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
115
+ }