@kouji-ui/components 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/kouji-ui-components.mjs +137 -65
- 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 +17 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-link — 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-link-*)
|
|
6
|
+
is read.
|
|
7
|
+
──────────────────────────────────────────────────────────── */
|
|
8
|
+
|
|
9
|
+
@layer kj.component {
|
|
10
|
+
.kj-link {
|
|
11
|
+
/* component knobs — themes / users can override any of these */
|
|
12
|
+
--kj-link-fg: var(--kj-fg-primary);
|
|
13
|
+
--kj-link-hover-fg: color-mix(in oklch, var(--kj-fg-primary), black 8%);
|
|
14
|
+
--kj-link-visited-fg: var(--kj-fg-accent);
|
|
15
|
+
--kj-link-decoration: underline;
|
|
16
|
+
--kj-link-underline-offset: 3px;
|
|
17
|
+
--kj-link-font-size: inherit;
|
|
18
|
+
--kj-link-radius: var(--kj-radius-field, 0.25rem);
|
|
19
|
+
|
|
20
|
+
/* the only place raw var(--kj-link-*) is read */
|
|
21
|
+
color: var(--kj-link-fg);
|
|
22
|
+
text-decoration: var(--kj-link-decoration);
|
|
23
|
+
text-underline-offset: var(--kj-link-underline-offset);
|
|
24
|
+
font-size: var(--kj-link-font-size);
|
|
25
|
+
border-radius: var(--kj-link-radius);
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
transition: color var(--kj-transition), text-decoration-color var(--kj-transition);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.kj-link:hover {
|
|
31
|
+
color: var(--kj-link-hover-fg);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.kj-link:visited {
|
|
35
|
+
color: var(--kj-link-visited-fg);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.kj-link:focus-visible {
|
|
39
|
+
outline: 2px solid var(--kj-fg-primary);
|
|
40
|
+
outline-offset: 2px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ── Underline modes ────────────────────────────────────────── */
|
|
44
|
+
.kj-link[data-underline="always"] { --kj-link-decoration: underline; }
|
|
45
|
+
.kj-link[data-underline="hover"] { --kj-link-decoration: none; }
|
|
46
|
+
.kj-link[data-underline="hover"]:hover,
|
|
47
|
+
.kj-link[data-underline="hover"]:focus-visible { --kj-link-decoration: underline; }
|
|
48
|
+
.kj-link[data-underline="none"] { --kj-link-decoration: none; }
|
|
49
|
+
|
|
50
|
+
/* ── Variants ───────────────────────────────────────────────── */
|
|
51
|
+
.kj-link[data-variant="primary"] {
|
|
52
|
+
--kj-link-fg: var(--kj-fg-primary);
|
|
53
|
+
--kj-link-hover-fg: color-mix(in oklab, var(--kj-fg-primary) 80%, var(--kj-fg-default));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.kj-link[data-variant="secondary"] {
|
|
57
|
+
--kj-link-fg: var(--kj-fg-accent);
|
|
58
|
+
--kj-link-hover-fg: color-mix(in oklab, var(--kj-fg-accent) 80%, var(--kj-fg-default));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.kj-link[data-variant="muted"] {
|
|
62
|
+
--kj-link-fg: color-mix(in oklab, var(--kj-fg-default) 70%, transparent);
|
|
63
|
+
--kj-link-hover-fg: var(--kj-fg-default);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.kj-link[data-variant="destructive"] {
|
|
67
|
+
--kj-link-fg: var(--kj-fg-danger);
|
|
68
|
+
--kj-link-hover-fg: color-mix(in oklab, var(--kj-fg-danger) 80%, var(--kj-fg-default));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ── Sizes ──────────────────────────────────────────────────── */
|
|
72
|
+
.kj-link[data-size="sm"] { --kj-link-font-size: 0.8125rem; }
|
|
73
|
+
.kj-link[data-size="md"] { --kj-link-font-size: 0.9375rem; }
|
|
74
|
+
.kj-link[data-size="lg"] { --kj-link-font-size: 1.0625rem; }
|
|
75
|
+
.kj-link[data-size="inherit"] { --kj-link-font-size: inherit; }
|
|
76
|
+
|
|
77
|
+
/* ── External-link icon (sighted-user story) ───────────────── */
|
|
78
|
+
.kj-link[data-external="true"]::after {
|
|
79
|
+
content: "";
|
|
80
|
+
display: inline-block;
|
|
81
|
+
width: 0.75em;
|
|
82
|
+
height: 0.75em;
|
|
83
|
+
margin-inline-start: 0.25em;
|
|
84
|
+
background-color: currentColor;
|
|
85
|
+
/* Mask is a tiny "arrow leaving box" glyph; aria-hidden by virtue of CSS. */
|
|
86
|
+
-webkit-mask: var(
|
|
87
|
+
--kj-link-external-icon,
|
|
88
|
+
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M9 3h4v4M13 3 7 9M11 9v4H3V5h4'/></svg>")
|
|
89
|
+
) center / contain no-repeat;
|
|
90
|
+
mask: var(
|
|
91
|
+
--kj-link-external-icon,
|
|
92
|
+
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M9 3h4v4M13 3 7 9M11 9v4H3V5h4'/></svg>")
|
|
93
|
+
) center / contain no-repeat;
|
|
94
|
+
vertical-align: -0.05em;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ── Disabled state ─────────────────────────────────────────── */
|
|
98
|
+
.kj-link[aria-disabled="true"],
|
|
99
|
+
.kj-link[data-disabled] {
|
|
100
|
+
opacity: 0.5;
|
|
101
|
+
cursor: not-allowed;
|
|
102
|
+
text-decoration: none;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-list — component-layer tokens + structural CSS
|
|
3
|
+
The list root carries layout (flex direction, gap) and reads
|
|
4
|
+
`data-orientation` / `data-divided` / `data-hoverable` from the
|
|
5
|
+
composed `KjList` directive to flip variants. Per-row chrome is
|
|
6
|
+
driven by `data-active` / `data-disabled` on `<kj-list-item>`.
|
|
7
|
+
──────────────────────────────────────────────────────────── */
|
|
8
|
+
|
|
9
|
+
@layer kj.component {
|
|
10
|
+
.kj-list {
|
|
11
|
+
--kj-list-bg: transparent;
|
|
12
|
+
--kj-list-fg: var(--kj-fg-default);
|
|
13
|
+
--kj-list-border-color: var(--kj-border-default);
|
|
14
|
+
--kj-list-radius: var(--kj-radius-box);
|
|
15
|
+
--kj-list-row-gap: 0;
|
|
16
|
+
--kj-list-row-padding-x: var(--kj-space-md);
|
|
17
|
+
--kj-list-row-padding-y: var(--kj-space-sm);
|
|
18
|
+
--kj-list-row-radius: var(--kj-radius-field);
|
|
19
|
+
--kj-list-row-min-h: 2.75rem; /* 44px — WCAG 2.5.5 */
|
|
20
|
+
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
gap: var(--kj-list-row-gap);
|
|
24
|
+
background: var(--kj-list-bg);
|
|
25
|
+
color: var(--kj-list-fg);
|
|
26
|
+
border-radius: var(--kj-list-radius);
|
|
27
|
+
margin: 0;
|
|
28
|
+
padding: 0;
|
|
29
|
+
list-style: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Horizontal axis flips flex-direction. The `data-orientation` mirror is
|
|
33
|
+
emitted by `KjList` so themes do not need the JS-level orientation. */
|
|
34
|
+
.kj-list[data-orientation='horizontal'] {
|
|
35
|
+
flex-direction: row;
|
|
36
|
+
flex-wrap: wrap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Sizes — drive row density via the row-padding / min-height tokens. */
|
|
40
|
+
.kj-list[data-size='xs'] {
|
|
41
|
+
--kj-list-row-padding-y: 0.125rem;
|
|
42
|
+
--kj-list-row-padding-x: var(--kj-space-sm);
|
|
43
|
+
--kj-list-row-min-h: 1.5rem;
|
|
44
|
+
}
|
|
45
|
+
.kj-list[data-size='sm'] {
|
|
46
|
+
--kj-list-row-padding-y: 0.25rem;
|
|
47
|
+
--kj-list-row-padding-x: var(--kj-space-sm);
|
|
48
|
+
--kj-list-row-min-h: 2rem;
|
|
49
|
+
}
|
|
50
|
+
.kj-list[data-size='lg'] {
|
|
51
|
+
--kj-list-row-padding-y: var(--kj-space-md);
|
|
52
|
+
--kj-list-row-padding-x: var(--kj-space-lg);
|
|
53
|
+
--kj-list-row-min-h: 3rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Variants — chrome on the container itself. */
|
|
57
|
+
.kj-list[data-variant='bordered'] {
|
|
58
|
+
border: var(--kj-border) solid var(--kj-list-border-color);
|
|
59
|
+
overflow: hidden;
|
|
60
|
+
}
|
|
61
|
+
.kj-list[data-variant='flat'] {
|
|
62
|
+
border: 0;
|
|
63
|
+
--kj-list-bg: transparent;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Per-row layout. Rows are flex containers so consumers can drop in
|
|
67
|
+
leading icons / trailing badges without juggling spans. */
|
|
68
|
+
.kj-list-item {
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
gap: var(--kj-space-sm);
|
|
72
|
+
padding: var(--kj-list-row-padding-y) var(--kj-list-row-padding-x);
|
|
73
|
+
min-height: var(--kj-list-row-min-h);
|
|
74
|
+
border-radius: var(--kj-list-row-radius);
|
|
75
|
+
color: inherit;
|
|
76
|
+
/* Transition only state-tied properties so the active / hover flip is
|
|
77
|
+
buttery without animating layout. */
|
|
78
|
+
transition: background-color var(--kj-transition),
|
|
79
|
+
color var(--kj-transition),
|
|
80
|
+
opacity var(--kj-transition);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* One projected link/button: move row padding onto it so the clickable box
|
|
84
|
+
matches the full row (padding on the host sat outside the hit target).
|
|
85
|
+
Use `nth-child(1 of …)` / `nth-last-child(1 of …)` so whitespace text
|
|
86
|
+
nodes between the host and the control do not break `:only-child`. */
|
|
87
|
+
.kj-list-item:has(
|
|
88
|
+
> :where(a, button):nth-child(1 of :where(a, button)):nth-last-child(1 of :where(a, button))
|
|
89
|
+
) {
|
|
90
|
+
padding: 0;
|
|
91
|
+
align-items: stretch;
|
|
92
|
+
min-height: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Divided rows — between-row border via `:not(:last-child)` so the final
|
|
96
|
+
row stays clean. The `data-divided` mirror lives on the root. */
|
|
97
|
+
.kj-list[data-divided] .kj-list-item {
|
|
98
|
+
border-radius: 0;
|
|
99
|
+
}
|
|
100
|
+
.kj-list[data-divided][data-orientation='vertical'] .kj-list-item:not(:last-child) {
|
|
101
|
+
border-bottom: var(--kj-border) solid var(--kj-list-border-color);
|
|
102
|
+
}
|
|
103
|
+
.kj-list[data-divided][data-orientation='horizontal'] .kj-list-item:not(:last-child) {
|
|
104
|
+
border-right: var(--kj-border) solid var(--kj-list-border-color);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Hoverable rows. Cascade order matters: the `:hover` rule comes BEFORE
|
|
108
|
+
the `[data-active]` rule so the active row wins when both are true. */
|
|
109
|
+
.kj-list[data-hoverable] .kj-list-item:hover {
|
|
110
|
+
background: color-mix(in oklab, var(--kj-fg-default) 6%, transparent);
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Active row — current location in a nav. Painted via background tint plus
|
|
115
|
+
a left bar (≥3:1 against background per WCAG 1.4.11). */
|
|
116
|
+
.kj-list-item[data-active] {
|
|
117
|
+
background: color-mix(in oklab, var(--kj-bg-primary) 12%, transparent);
|
|
118
|
+
color: var(--kj-fg-primary);
|
|
119
|
+
box-shadow: inset 3px 0 0 0 var(--kj-bg-primary);
|
|
120
|
+
}
|
|
121
|
+
.kj-list[data-orientation='horizontal'] .kj-list-item[data-active] {
|
|
122
|
+
box-shadow: inset 0 -3px 0 0 var(--kj-bg-primary);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Disabled row — dim the chrome and any projected interactive child.
|
|
126
|
+
Keyboard reachability is the projected child's responsibility. */
|
|
127
|
+
.kj-list-item[data-disabled] {
|
|
128
|
+
opacity: 0.45;
|
|
129
|
+
cursor: not-allowed;
|
|
130
|
+
}
|
|
131
|
+
.kj-list-item[data-disabled] :where(a, button) {
|
|
132
|
+
pointer-events: none;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Shared styles for interactive children */
|
|
136
|
+
.kj-list-item > :where(a, button) {
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
gap: var(--kj-space-sm);
|
|
140
|
+
color: inherit;
|
|
141
|
+
text-decoration: none;
|
|
142
|
+
background: none;
|
|
143
|
+
border: 0;
|
|
144
|
+
padding: 0;
|
|
145
|
+
font: inherit;
|
|
146
|
+
cursor: pointer;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.kj-list-item:has(
|
|
150
|
+
> :where(a, button):nth-child(1 of :where(a, button)):nth-last-child(1 of :where(a, button))
|
|
151
|
+
)
|
|
152
|
+
> :where(a, button) {
|
|
153
|
+
flex: 1 1 auto;
|
|
154
|
+
align-self: stretch;
|
|
155
|
+
min-width: 0;
|
|
156
|
+
min-height: var(--kj-list-row-min-h);
|
|
157
|
+
padding: var(--kj-list-row-padding-y) var(--kj-list-row-padding-x);
|
|
158
|
+
box-sizing: border-box;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.kj-list-item > :where(a, button):focus-visible {
|
|
162
|
+
outline: 2px solid var(--kj-fg-primary);
|
|
163
|
+
outline-offset: 2px;
|
|
164
|
+
border-radius: var(--kj-list-row-radius);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
@layer kj.component {
|
|
2
|
+
.kj-menubar {
|
|
3
|
+
--kj-menubar-bg: var(--kj-bg-body);
|
|
4
|
+
--kj-menubar-fg: var(--kj-fg-default);
|
|
5
|
+
--kj-menubar-border-color: var(--kj-border-default);
|
|
6
|
+
--kj-menubar-radius: var(--kj-radius-box);
|
|
7
|
+
--kj-menubar-padding: var(--kj-space-2xs);
|
|
8
|
+
--kj-menubar-gap: var(--kj-space-2xs);
|
|
9
|
+
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: row;
|
|
12
|
+
align-items: stretch;
|
|
13
|
+
gap: var(--kj-menubar-gap);
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
background: var(--kj-menubar-bg);
|
|
16
|
+
color: var(--kj-menubar-fg);
|
|
17
|
+
border: var(--kj-border) solid var(--kj-menubar-border-color);
|
|
18
|
+
border-radius: var(--kj-menubar-radius);
|
|
19
|
+
padding: var(--kj-menubar-padding);
|
|
20
|
+
font: 0.875rem / 1.5 var(--kj-font-sans);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.kj-menubar-item {
|
|
24
|
+
--kj-menubar-item-padding-x: var(--kj-space-md);
|
|
25
|
+
--kj-menubar-item-padding-y: var(--kj-space-xs);
|
|
26
|
+
--kj-menubar-item-min-height: 2.75rem; /* 44px AAA touch target */
|
|
27
|
+
--kj-menubar-item-radius: var(--kj-radius-field);
|
|
28
|
+
--kj-menubar-item-hover-bg: var(--kj-bg-surface);
|
|
29
|
+
--kj-menubar-item-active-bg: var(--kj-bg-elevated);
|
|
30
|
+
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
min-height: var(--kj-menubar-item-min-height);
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
padding: var(--kj-menubar-item-padding-y) var(--kj-menubar-item-padding-x);
|
|
37
|
+
margin: 0;
|
|
38
|
+
background: transparent;
|
|
39
|
+
color: inherit;
|
|
40
|
+
border: none;
|
|
41
|
+
border-radius: var(--kj-menubar-item-radius);
|
|
42
|
+
font: inherit;
|
|
43
|
+
line-height: 1;
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
user-select: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.kj-menubar-item:hover:not([aria-disabled="true"]) {
|
|
49
|
+
background: var(--kj-menubar-item-hover-bg);
|
|
50
|
+
}
|
|
51
|
+
.kj-menubar-item:focus-visible {
|
|
52
|
+
outline: 2px solid var(--kj-fg-primary);
|
|
53
|
+
outline-offset: -2px;
|
|
54
|
+
}
|
|
55
|
+
.kj-menubar-item[data-state="active"] {
|
|
56
|
+
background: var(--kj-menubar-item-active-bg);
|
|
57
|
+
}
|
|
58
|
+
.kj-menubar-item[aria-disabled="true"] {
|
|
59
|
+
cursor: not-allowed;
|
|
60
|
+
opacity: 0.5;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
.kj-number-input — wrapper around KjNumberInput + KjNumberStepper.
|
|
3
|
+
|
|
4
|
+
Tokens default to shared layer values; the stepper buttons live as
|
|
5
|
+
sibling buttons inside the group. Bounds-aware aria-disabled is
|
|
6
|
+
reflected from the directive — CSS only reads the attribute.
|
|
7
|
+
──────────────────────────────────────────────────────────── */
|
|
8
|
+
|
|
9
|
+
@layer kj.component {
|
|
10
|
+
.kj-number-input {
|
|
11
|
+
--kj-number-input-bg: var(--kj-bg-field);
|
|
12
|
+
--kj-number-input-fg: var(--kj-fg-default);
|
|
13
|
+
--kj-number-input-border-color: var(--kj-border-default);
|
|
14
|
+
--kj-number-input-border-width: var(--kj-border);
|
|
15
|
+
--kj-number-input-radius: var(--kj-radius-field);
|
|
16
|
+
--kj-number-input-padding-x: var(--kj-space-md);
|
|
17
|
+
--kj-number-input-font: var(--kj-font-sans);
|
|
18
|
+
--kj-number-input-font-size: var(--kj-text-sm);
|
|
19
|
+
--kj-number-input-stepper-bg: var(--kj-bg-surface);
|
|
20
|
+
--kj-number-input-stepper-fg: var(--kj-fg-default);
|
|
21
|
+
--kj-number-input-stepper-size: 2.25rem;
|
|
22
|
+
/* Same height contract as .kj-input — keep form rows aligned. */
|
|
23
|
+
--kj-number-input-height: 2.25rem;
|
|
24
|
+
|
|
25
|
+
display: inline-flex;
|
|
26
|
+
align-items: stretch;
|
|
27
|
+
height: var(--kj-number-input-height);
|
|
28
|
+
background: var(--kj-number-input-bg);
|
|
29
|
+
color: var(--kj-number-input-fg);
|
|
30
|
+
border: var(--kj-number-input-border-width) solid var(--kj-number-input-border-color);
|
|
31
|
+
border-radius: var(--kj-number-input-radius);
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
font: var(--kj-number-input-font-size) / 1.4 var(--kj-number-input-font);
|
|
34
|
+
transition: var(--kj-transition);
|
|
35
|
+
}
|
|
36
|
+
.kj-number-input[data-size="xs"] { --kj-number-input-height: 1.75rem; --kj-number-input-stepper-size: 1.75rem; --kj-number-input-font-size: 0.75rem; --kj-number-input-padding-x: var(--kj-space-sm); }
|
|
37
|
+
.kj-number-input[data-size="sm"] { --kj-number-input-height: 2rem; --kj-number-input-stepper-size: 2rem; --kj-number-input-font-size: 0.8125rem; }
|
|
38
|
+
.kj-number-input[data-size="lg"] { --kj-number-input-height: 2.75rem; --kj-number-input-stepper-size: 2.75rem; --kj-number-input-font-size: 1rem; }
|
|
39
|
+
|
|
40
|
+
/* At `xs` we hide the stepper buttons — at 28px the +/- glyphs become
|
|
41
|
+
too cramped to hit accurately, and the typical xs context (filter rows,
|
|
42
|
+
inline editors) doesn't want a stepper anyway. The keyboard contract
|
|
43
|
+
(Arrow/Page/Home/End) still works on the inner <input>. */
|
|
44
|
+
.kj-number-input[data-size="xs"] .kj-number-input__stepper { display: none; }
|
|
45
|
+
.kj-number-input[data-size="xs"] .kj-number-input__field { text-align: left; }
|
|
46
|
+
|
|
47
|
+
.kj-number-input:focus-within {
|
|
48
|
+
--kj-number-input-border-color: var(--kj-fg-primary);
|
|
49
|
+
outline: 2px solid var(--kj-fg-primary);
|
|
50
|
+
outline-offset: 2px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.kj-number-input[data-invalid] {
|
|
54
|
+
--kj-number-input-border-color: var(--kj-fg-danger);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.kj-number-input[data-disabled] {
|
|
58
|
+
opacity: 0.55;
|
|
59
|
+
cursor: not-allowed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.kj-number-input__field {
|
|
63
|
+
flex: 1 1 auto;
|
|
64
|
+
min-width: 0;
|
|
65
|
+
height: 100%;
|
|
66
|
+
background: transparent;
|
|
67
|
+
color: inherit;
|
|
68
|
+
border: 0;
|
|
69
|
+
padding: 0 var(--kj-number-input-padding-x);
|
|
70
|
+
font: inherit;
|
|
71
|
+
text-align: right;
|
|
72
|
+
}
|
|
73
|
+
.kj-number-input__field:focus { outline: none; }
|
|
74
|
+
.kj-number-input__field::placeholder { color: var(--kj-fg-muted); }
|
|
75
|
+
|
|
76
|
+
.kj-number-input__stepper {
|
|
77
|
+
display: inline-flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
min-width: var(--kj-number-input-stepper-size);
|
|
81
|
+
min-height: var(--kj-number-input-stepper-size);
|
|
82
|
+
background: var(--kj-number-input-stepper-bg);
|
|
83
|
+
color: var(--kj-number-input-stepper-fg);
|
|
84
|
+
border: 0;
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
font: inherit;
|
|
87
|
+
line-height: 1;
|
|
88
|
+
transition: background var(--kj-transition), color var(--kj-transition), opacity var(--kj-transition);
|
|
89
|
+
}
|
|
90
|
+
.kj-number-input__stepper:hover {
|
|
91
|
+
background: color-mix(in oklab, var(--kj-fg-default) 8%, var(--kj-number-input-stepper-bg));
|
|
92
|
+
}
|
|
93
|
+
.kj-number-input__stepper[aria-disabled="true"] {
|
|
94
|
+
opacity: 0.4;
|
|
95
|
+
cursor: not-allowed;
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* Flanking layout (default): decrement | input | increment */
|
|
100
|
+
.kj-number-input[data-stepper-layout="flanking"] .kj-number-input__stepper--down { order: -1; }
|
|
101
|
+
.kj-number-input[data-stepper-layout="flanking"] .kj-number-input__stepper--up { order: 1; }
|
|
102
|
+
|
|
103
|
+
/* Stacked layout: input on the left, up / down stacked vertically on the right. */
|
|
104
|
+
.kj-number-input[data-stepper-layout="stacked"] {
|
|
105
|
+
align-items: stretch;
|
|
106
|
+
}
|
|
107
|
+
.kj-number-input[data-stepper-layout="stacked"] .kj-number-input__steppers {
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
}
|
|
111
|
+
.kj-number-input[data-stepper-layout="stacked"] .kj-number-input__stepper {
|
|
112
|
+
flex: 1 1 auto;
|
|
113
|
+
min-height: calc(var(--kj-number-input-stepper-size) / 2);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
kj-overflow-panel — the "+N" chip's panel (tag list / avatar group).
|
|
3
|
+
The panel itself is a kj-popover-content; only the default label
|
|
4
|
+
list and the trigger chips' shared reset live here.
|
|
5
|
+
──────────────────────────────────────────────────────────── */
|
|
6
|
+
|
|
7
|
+
@layer kj.component {
|
|
8
|
+
.kj-overflow-panel {
|
|
9
|
+
--kj-popover-padding-x: var(--kj-space-sm);
|
|
10
|
+
--kj-popover-padding-y: var(--kj-space-sm);
|
|
11
|
+
min-width: 10rem;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.kj-overflow-list {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
gap: var(--kj-space-xs);
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
list-style: none;
|
|
21
|
+
}
|
|
22
|
+
.kj-overflow-item {
|
|
23
|
+
padding: var(--kj-space-xs) var(--kj-space-sm);
|
|
24
|
+
font-size: var(--kj-text-sm);
|
|
25
|
+
line-height: 1.4;
|
|
26
|
+
color: var(--kj-fg-default);
|
|
27
|
+
white-space: nowrap;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
text-overflow: ellipsis;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* The "+N" chip is a native <button> so it is focusable and reachable on
|
|
33
|
+
touch; strip the UA chrome, the group's own chip styles paint it. */
|
|
34
|
+
.kj-overflow-trigger {
|
|
35
|
+
appearance: none;
|
|
36
|
+
font: inherit;
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
}
|
|
39
|
+
.kj-overflow-trigger:focus-visible {
|
|
40
|
+
outline: 2px solid var(--kj-bg-primary);
|
|
41
|
+
outline-offset: 2px;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
@kouji-ui/components — overlay-family surface stylesheet
|
|
3
|
+
--------------------------------------------------------------
|
|
4
|
+
Register this file once in your build, next to the themes and
|
|
5
|
+
the core overlay primitive:
|
|
6
|
+
|
|
7
|
+
"styles": [
|
|
8
|
+
"node_modules/@kouji-ui/themes/src/index.css",
|
|
9
|
+
"node_modules/@kouji-ui/core/overlay/overlay.css",
|
|
10
|
+
"node_modules/@kouji-ui/components/src/overlay/overlay.css"
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
WHY IT MUST BE REGISTERED. Every overlay panel in the kit is
|
|
14
|
+
rendered by a HEADLESS directive from `@kouji-ui/core` —
|
|
15
|
+
`<kj-popover-content>`, `<kj-dropdown-menu-content>`,
|
|
16
|
+
`<kj-tooltip-content>`, `<kj-dialog-content>`. Directives carry
|
|
17
|
+
no styles, and Angular injects a component's `styleUrl` only
|
|
18
|
+
when that component is INSTANTIATED. So a panel composed the
|
|
19
|
+
documented way (trigger directive + content directive, with no
|
|
20
|
+
`<kj-popover>`-style wrapper anywhere on the page) used to
|
|
21
|
+
paint with no background, no border and no shadow: the surface
|
|
22
|
+
CSS lived solely inside a wrapper component nobody rendered.
|
|
23
|
+
This stylesheet is that surface CSS, decoupled from the
|
|
24
|
+
wrappers — the wrapper components still `styleUrl` the same
|
|
25
|
+
files, so registering it is idempotent.
|
|
26
|
+
|
|
27
|
+
Kept in the SOURCE layout on purpose: `ng-packagr` copies every
|
|
28
|
+
stylesheet under `src` into the published package unchanged, so
|
|
29
|
+
these relative imports resolve identically from
|
|
30
|
+
`node_modules/@kouji-ui/components/src/overlay/overlay.css`.
|
|
31
|
+
The core primitive is NOT imported from here — CSS cannot reach
|
|
32
|
+
across packages by relative path once installed; register
|
|
33
|
+
`@kouji-ui/core/overlay/overlay.css` for the container /
|
|
34
|
+
wrapper / backdrop chrome.
|
|
35
|
+
──────────────────────────────────────────────────────────── */
|
|
36
|
+
|
|
37
|
+
@import "../popover/popover.css";
|
|
38
|
+
@import "../tooltip/tooltip.css";
|
|
39
|
+
@import "../dropdown-menu/dropdown-menu.css";
|
|
40
|
+
@import "../dialog/dialog.css";
|
|
41
|
+
@import "../drawer/drawer.css";
|
|
42
|
+
@import "../toast/toast.css";
|
|
43
|
+
@import "../confirm-popup/confirm-popup.css";
|
|
44
|
+
@import "../sheet/sheet.css";
|
|
45
|
+
@import "../action-sheet/action-sheet.css";
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
@layer kj.component {
|
|
2
|
+
.kj-overlay-badge-anchor {
|
|
3
|
+
/* The anchor is also the positioning context. `position: relative` is
|
|
4
|
+
inline-style-bound by `KjOverlayBadge`; we add the inline-flex layout
|
|
5
|
+
here so projected anchors keep their inline rhythm. */
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
vertical-align: middle;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.kj-overlay-badge {
|
|
11
|
+
/* Visual chrome inherited from <kj-badge>. We re-apply the same token
|
|
12
|
+
set here so the slot renders identically whether the consumer uses
|
|
13
|
+
<kj-overlay-badge> or projects a manual <span kjOverlayBadgeContent>. */
|
|
14
|
+
--kj-badge-bg: var(--kj-bg-primary);
|
|
15
|
+
--kj-badge-fg: var(--kj-fg-on-primary);
|
|
16
|
+
--kj-badge-border-color: transparent;
|
|
17
|
+
--kj-badge-radius: var(--kj-radius-selector);
|
|
18
|
+
--kj-badge-padding-x: var(--kj-space-sm);
|
|
19
|
+
--kj-badge-padding-y: 0.125rem;
|
|
20
|
+
--kj-badge-font-size: 0.6875rem;
|
|
21
|
+
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
min-width: 1.25rem;
|
|
26
|
+
height: 1.25rem;
|
|
27
|
+
background: var(--kj-badge-bg);
|
|
28
|
+
color: var(--kj-badge-fg);
|
|
29
|
+
border: var(--kj-border) solid var(--kj-badge-border-color);
|
|
30
|
+
border-radius: var(--kj-badge-radius);
|
|
31
|
+
padding: var(--kj-badge-padding-y) var(--kj-badge-padding-x);
|
|
32
|
+
font: var(--kj-badge-font-size) / 1 var(--kj-font-sans);
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
|
|
35
|
+
/* 2px halo against the anchor for WCAG 1.4.11 non-text contrast — the
|
|
36
|
+
badge is a small element sitting on top of arbitrary surfaces. */
|
|
37
|
+
box-shadow: 0 0 0 2px var(--kj-bg-body);
|
|
38
|
+
|
|
39
|
+
/* Logical-property positioning so RTL flips for free. The translate
|
|
40
|
+
pulls the badge half-out of the anchor at each corner — keeps the
|
|
41
|
+
majority of the chip visible above an avatar's overflow mask, etc. */
|
|
42
|
+
inset-block-start: auto;
|
|
43
|
+
inset-block-end: auto;
|
|
44
|
+
inset-inline-start: auto;
|
|
45
|
+
inset-inline-end: auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Variant overrides — mirror <kj-badge>'s data-variant rules. */
|
|
49
|
+
.kj-overlay-badge[data-variant="secondary"] {
|
|
50
|
+
--kj-badge-bg: var(--kj-bg-surface);
|
|
51
|
+
--kj-badge-fg: var(--kj-fg-default);
|
|
52
|
+
}
|
|
53
|
+
.kj-overlay-badge[data-variant="destructive"] {
|
|
54
|
+
--kj-badge-bg: var(--kj-bg-danger);
|
|
55
|
+
--kj-badge-fg: var(--kj-fg-on-danger);
|
|
56
|
+
}
|
|
57
|
+
.kj-overlay-badge[data-variant="outline"] {
|
|
58
|
+
--kj-badge-bg: transparent;
|
|
59
|
+
--kj-badge-fg: var(--kj-fg-default);
|
|
60
|
+
--kj-badge-border-color: var(--kj-border-default);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Sizes. */
|
|
64
|
+
.kj-overlay-badge[data-size="sm"] {
|
|
65
|
+
min-width: 1rem;
|
|
66
|
+
height: 1rem;
|
|
67
|
+
--kj-badge-font-size: 0.625rem;
|
|
68
|
+
--kj-badge-padding-x: 0.25rem;
|
|
69
|
+
}
|
|
70
|
+
.kj-overlay-badge[data-size="lg"] {
|
|
71
|
+
min-width: 1.5rem;
|
|
72
|
+
height: 1.5rem;
|
|
73
|
+
--kj-badge-font-size: 0.75rem;
|
|
74
|
+
--kj-badge-padding-x: 0.5rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Dot mode — fixed 0.5rem circle, no padding, no min-width. */
|
|
78
|
+
.kj-overlay-badge[data-dot] {
|
|
79
|
+
min-width: 0;
|
|
80
|
+
width: 0.625rem;
|
|
81
|
+
height: 0.625rem;
|
|
82
|
+
padding: 0;
|
|
83
|
+
border-radius: 9999px;
|
|
84
|
+
}
|
|
85
|
+
.kj-overlay-badge[data-dot][data-size="sm"] { width: 0.5rem; height: 0.5rem; }
|
|
86
|
+
.kj-overlay-badge[data-dot][data-size="lg"] { width: 0.75rem; height: 0.75rem; }
|
|
87
|
+
|
|
88
|
+
/* Logical-corner placement. RTL flips automatically because we use
|
|
89
|
+
inline-start / inline-end. */
|
|
90
|
+
.kj-overlay-badge[data-position="top-end"] {
|
|
91
|
+
inset-block-start: 0;
|
|
92
|
+
inset-inline-end: 0;
|
|
93
|
+
transform: translate(50%, -50%);
|
|
94
|
+
}
|
|
95
|
+
.kj-overlay-badge[data-position="top-start"] {
|
|
96
|
+
inset-block-start: 0;
|
|
97
|
+
inset-inline-start: 0;
|
|
98
|
+
transform: translate(-50%, -50%);
|
|
99
|
+
}
|
|
100
|
+
.kj-overlay-badge[data-position="bottom-end"] {
|
|
101
|
+
inset-block-end: 0;
|
|
102
|
+
inset-inline-end: 0;
|
|
103
|
+
transform: translate(50%, 50%);
|
|
104
|
+
}
|
|
105
|
+
.kj-overlay-badge[data-position="bottom-start"] {
|
|
106
|
+
inset-block-end: 0;
|
|
107
|
+
inset-inline-start: 0;
|
|
108
|
+
transform: translate(-50%, 50%);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* RTL inverts the inline-axis translation so the badge stays outside the
|
|
112
|
+
anchor's edge rather than tucking inward. Logical insets handle the
|
|
113
|
+
position itself; only the translate sign needs flipping. */
|
|
114
|
+
[dir="rtl"] .kj-overlay-badge[data-position="top-end"] { transform: translate(-50%, -50%); }
|
|
115
|
+
[dir="rtl"] .kj-overlay-badge[data-position="top-start"] { transform: translate(50%, -50%); }
|
|
116
|
+
[dir="rtl"] .kj-overlay-badge[data-position="bottom-end"] { transform: translate(-50%, 50%); }
|
|
117
|
+
[dir="rtl"] .kj-overlay-badge[data-position="bottom-start"] { transform: translate(50%, 50%); }
|
|
118
|
+
}
|