@stisla/style 3.0.0-beta.8
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/LICENSE +9 -0
- package/README.md +16 -0
- package/dist/accordion/accordion.css +194 -0
- package/dist/alert/alert.css +138 -0
- package/dist/autocomplete/autocomplete.css +193 -0
- package/dist/avatar/avatar.css +142 -0
- package/dist/avatar-group/avatar-group.css +42 -0
- package/dist/badge/badge.css +74 -0
- package/dist/breadcrumb/breadcrumb.css +71 -0
- package/dist/button/button.css +318 -0
- package/dist/button/index.d.ts +1 -0
- package/dist/button/index.js +6 -0
- package/dist/button-group/button-group.css +108 -0
- package/dist/card/card.css +219 -0
- package/dist/carousel/carousel.css +170 -0
- package/dist/checkbox/checkbox.css +98 -0
- package/dist/chunk-K45KLI3Y.js +74 -0
- package/dist/collapsible/collapsible.css +36 -0
- package/dist/combobox/combobox.css +106 -0
- package/dist/combobox/combobox.tomselect.css +251 -0
- package/dist/config-CARtrJ7I.d.ts +61 -0
- package/dist/dialog/dialog.css +258 -0
- package/dist/drawer/drawer.css +318 -0
- package/dist/empty-state/empty-state.css +138 -0
- package/dist/field/field.css +70 -0
- package/dist/icon-box/icon-box.css +64 -0
- package/dist/illustration/illustration.css +103 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +60 -0
- package/dist/indicator/indicator.css +84 -0
- package/dist/input/input.css +220 -0
- package/dist/input-group/input-group.css +141 -0
- package/dist/kbd/kbd.css +55 -0
- package/dist/link/link.css +28 -0
- package/dist/list-group/list-group.css +261 -0
- package/dist/media/media.css +115 -0
- package/dist/menu/menu.css +237 -0
- package/dist/meter/meter.css +124 -0
- package/dist/navbar/navbar.css +170 -0
- package/dist/page/page.css +95 -0
- package/dist/pagination/pagination.css +125 -0
- package/dist/placeholders/placeholders.css +58 -0
- package/dist/popover/popover.css +251 -0
- package/dist/progress/progress.css +139 -0
- package/dist/radio/radio.css +81 -0
- package/dist/scroll-area/scroll-area.css +25 -0
- package/dist/scroll-area/scroll-area.overlayscrollbars.css +42 -0
- package/dist/select/select.css +282 -0
- package/dist/separator/separator.css +26 -0
- package/dist/sidebar/sidebar.css +493 -0
- package/dist/slider/slider.css +159 -0
- package/dist/spinner/spinner.css +65 -0
- package/dist/switch/switch.css +91 -0
- package/dist/table/table.css +284 -0
- package/dist/tabs/tabs.css +137 -0
- package/dist/textarea/textarea.css +99 -0
- package/dist/timeline/timeline.css +271 -0
- package/dist/toast/toast.css +267 -0
- package/dist/toggle/toggle.css +125 -0
- package/dist/toggle-group/toggle-group.css +87 -0
- package/dist/tooltip/tooltip.css +95 -0
- package/package.json +46 -0
- package/src/theme.css +151 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/* @stisla/style — Combobox (portable contract). Ported from src/scss/components/_combobox.scss + the
|
|
2
|
+
* form-field-base mixin. This file is the design contract any implementation shares: the .combobox
|
|
3
|
+
* field (a native <select class="combobox"> that works with no JS), the --combobox-* token knobs, the
|
|
4
|
+
* size modifiers, and the dark chevron. A React/Vue combobox reuses these tokens with its own widget.
|
|
5
|
+
*
|
|
6
|
+
* The vanilla JS binding (Tom Select's .ts-wrapper / .ts-control / .ts-dropdown / .option / .item DOM,
|
|
7
|
+
* mapped onto these tokens) lives in the sibling combobox.tomselect.css — load it alongside this file
|
|
8
|
+
* only when you use the Tom-Select-backed combobox.
|
|
9
|
+
*
|
|
10
|
+
* References the @theme tokens: colors var(--color-*), sizes/spacing --spacing(n), type var(--text-*)
|
|
11
|
+
* / var(--leading-*), radius var(--radius-*); only no-namespace customs use --st-* (border-width,
|
|
12
|
+
* duration). The field reuses form-field-base (duplicated here with --combobox-*, keep in sync with
|
|
13
|
+
* input/select/textarea). Knobs are --combobox-*. @layer components. Authoring rules: ../../../../PORTING.md */
|
|
14
|
+
|
|
15
|
+
@layer components {
|
|
16
|
+
/* === Field === the form-field-base shape + a chevron well. Uses background-color (not the
|
|
17
|
+
`background` shorthand) so the chevron image survives. */
|
|
18
|
+
.combobox {
|
|
19
|
+
display: block;
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: var(--combobox-height, --spacing(9));
|
|
22
|
+
padding-block: var(--combobox-padding-block, 0);
|
|
23
|
+
padding-inline: var(--combobox-padding-inline, --spacing(2.5));
|
|
24
|
+
padding-inline-end: --spacing(9);
|
|
25
|
+
font-family: inherit;
|
|
26
|
+
font-size: var(--combobox-font-size, var(--text-sm));
|
|
27
|
+
font-weight: var(--font-weight-normal);
|
|
28
|
+
line-height: var(--leading-tight);
|
|
29
|
+
color: var(--combobox-color, var(--color-foreground));
|
|
30
|
+
background-color: var(--combobox-bg, var(--color-surface));
|
|
31
|
+
/* Chevron — fallback-default custom prop so a scope can swap the SVG. The stroke must be a literal
|
|
32
|
+
(CSS vars don't resolve in url()); the dark scope below swaps to a lighter chevron. */
|
|
33
|
+
background-image: var(
|
|
34
|
+
--combobox-indicator,
|
|
35
|
+
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='4 6 8 10 12 6'/%3E%3C/svg%3E")
|
|
36
|
+
);
|
|
37
|
+
background-position: right --spacing(3) center;
|
|
38
|
+
background-repeat: no-repeat;
|
|
39
|
+
background-size: --spacing(3) --spacing(3);
|
|
40
|
+
border: var(--combobox-border-width, var(--st-border-width)) solid
|
|
41
|
+
var(--combobox-border-color, var(--color-border-strong));
|
|
42
|
+
border-radius: var(--combobox-radius, var(--radius-md));
|
|
43
|
+
appearance: none;
|
|
44
|
+
cursor: text;
|
|
45
|
+
transition:
|
|
46
|
+
border-color var(--transition-duration-fast) ease,
|
|
47
|
+
box-shadow var(--transition-duration-fast) ease,
|
|
48
|
+
background-color var(--transition-duration-fast) ease;
|
|
49
|
+
|
|
50
|
+
@media (pointer: coarse) {
|
|
51
|
+
font-size: var(--combobox-font-size, var(--text-base));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&::placeholder {
|
|
55
|
+
color: var(--combobox-placeholder, var(--color-muted-foreground));
|
|
56
|
+
opacity: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&:hover:not(:disabled, :focus-visible, [aria-invalid="true"], :user-invalid) {
|
|
60
|
+
border-color: color-mix(in oklch, var(--color-border-strong) 80%, var(--color-foreground));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&:focus-visible {
|
|
64
|
+
outline: none;
|
|
65
|
+
border-color: var(--color-primary);
|
|
66
|
+
box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-ring) 25%, transparent);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&[aria-invalid="true"],
|
|
70
|
+
&:user-invalid {
|
|
71
|
+
border-color: var(--color-danger);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
&:disabled {
|
|
75
|
+
opacity: 0.55;
|
|
76
|
+
cursor: not-allowed;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@media (prefers-reduced-motion: reduce) {
|
|
80
|
+
transition: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Dark — swap the chevron to a lighter stroke (data: URLs can't read tokens). */
|
|
84
|
+
@variant dark {
|
|
85
|
+
--combobox-indicator: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='4 6 8 10 12 6'/%3E%3C/svg%3E");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* === Sizes (base = md) === */
|
|
90
|
+
.combobox--sm {
|
|
91
|
+
--combobox-radius: var(--radius-sm);
|
|
92
|
+
--combobox-height: --spacing(7);
|
|
93
|
+
--combobox-padding-inline: --spacing(2);
|
|
94
|
+
--combobox-font-size: var(--text-xs);
|
|
95
|
+
@media (pointer: coarse) {
|
|
96
|
+
--combobox-font-size: var(--text-base);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.combobox--lg {
|
|
101
|
+
--combobox-radius: var(--radius-lg);
|
|
102
|
+
--combobox-height: --spacing(11);
|
|
103
|
+
--combobox-padding-inline: --spacing(3.5);
|
|
104
|
+
--combobox-font-size: var(--text-base);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/* @stisla/style — Combobox / Tom Select adapter (vanilla-JS binding). The combobox's vanilla
|
|
2
|
+
* implementation is backed by Tom Select; this file maps Tom Select's own DOM vocabulary
|
|
3
|
+
* (.ts-wrapper / .ts-control / .ts-dropdown / .option / .item / .focus / .has-items / .active /
|
|
4
|
+
* .selected / .tomselected …) onto the --combobox-* tokens and the menu-surface recipe. It is the ONE
|
|
5
|
+
* lib-coupled stylesheet in @stisla/style; a different combobox lib (or a React/Vue impl) would
|
|
6
|
+
* provide its own adapter and reuse the same --combobox-* tokens.
|
|
7
|
+
*
|
|
8
|
+
* Load AFTER combobox.css (it depends on the --combobox-* contract defined there) and only when you
|
|
9
|
+
* use the Tom-Select-backed combobox. The .combobox base rule in combobox.css also paints
|
|
10
|
+
* .ts-wrapper.combobox (it carries the combobox class), so the field shape/border/chevron come from
|
|
11
|
+
* there; this file adds the flex trigger layout, chips, and dropdown surface. These classes are a
|
|
12
|
+
* third-party lib's vocabulary, not Stisla state — the no-is-* convention does not apply to them.
|
|
13
|
+
*
|
|
14
|
+
* References the @theme tokens (colors var(--color-*), spacing --spacing(n), type var(--text-*) /
|
|
15
|
+
* var(--leading-*), radius var(--radius-*), shadow var(--shadow-*)); only no-namespace customs use
|
|
16
|
+
* --st-* (border-width, duration, z-index). The dropdown z-index routes through the z-index scale
|
|
17
|
+
* (--z-index-dropdown). @layer components. Authoring rules: ../../../../PORTING.md */
|
|
18
|
+
|
|
19
|
+
@layer components {
|
|
20
|
+
/* === Wrapper === .ts-wrapper.combobox is the visible trigger after hydration. Flex so the inner
|
|
21
|
+
.ts-control stretches; multi grows past the height as chips wrap. */
|
|
22
|
+
.ts-wrapper.combobox {
|
|
23
|
+
position: relative;
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: stretch;
|
|
26
|
+
padding: 0;
|
|
27
|
+
cursor: text;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.ts-wrapper.combobox.multi {
|
|
31
|
+
height: auto;
|
|
32
|
+
min-height: var(--combobox-height, --spacing(9));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Inner row — chips + typing input. padding-block lives here (not the wrapper) so the whole padded
|
|
36
|
+
area stays clickable, since Tom Select binds its click handler to .ts-control only. */
|
|
37
|
+
.ts-wrapper.combobox .ts-control {
|
|
38
|
+
flex: 1 1 auto;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-wrap: wrap;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: --spacing(1.5);
|
|
43
|
+
min-width: 0;
|
|
44
|
+
padding-block: var(--combobox-control-padding-block, --spacing(1));
|
|
45
|
+
padding-inline-start: var(--combobox-padding-inline, --spacing(2.5));
|
|
46
|
+
padding-inline-end: 0;
|
|
47
|
+
background: transparent;
|
|
48
|
+
border: 0;
|
|
49
|
+
outline: none;
|
|
50
|
+
cursor: text;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.ts-wrapper.combobox .ts-control > input {
|
|
54
|
+
flex: 1 1 auto;
|
|
55
|
+
min-width: 4ch;
|
|
56
|
+
padding: 0;
|
|
57
|
+
margin: 0;
|
|
58
|
+
font: inherit;
|
|
59
|
+
color: inherit;
|
|
60
|
+
background: transparent;
|
|
61
|
+
border: 0;
|
|
62
|
+
outline: none;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.ts-wrapper.combobox .ts-control > input::placeholder {
|
|
66
|
+
color: var(--combobox-placeholder, var(--color-muted-foreground));
|
|
67
|
+
opacity: 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Reset Stisla's .item styling that would otherwise leak onto Tom Select's chip divs; multi
|
|
71
|
+
re-paints below. */
|
|
72
|
+
.ts-wrapper.combobox .ts-control .item {
|
|
73
|
+
padding: 0;
|
|
74
|
+
margin: 0;
|
|
75
|
+
color: inherit;
|
|
76
|
+
background: transparent;
|
|
77
|
+
border: 0;
|
|
78
|
+
border-radius: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Multi chip — a small inline tag with an X remove handle (remove_button plugin). */
|
|
82
|
+
.ts-wrapper.combobox.multi .ts-control .item {
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: --spacing(1);
|
|
86
|
+
padding: --spacing(0.5) --spacing(2);
|
|
87
|
+
font-size: var(--text-xs);
|
|
88
|
+
line-height: var(--leading-snug);
|
|
89
|
+
color: var(--color-accent-foreground);
|
|
90
|
+
background: var(--color-accent);
|
|
91
|
+
border-radius: calc(var(--combobox-radius, var(--radius-md)) - --spacing(1));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.ts-wrapper.combobox.multi .ts-control .item .remove {
|
|
95
|
+
margin-inline-start: --spacing(0.5);
|
|
96
|
+
color: inherit;
|
|
97
|
+
text-decoration: none;
|
|
98
|
+
opacity: 0.6;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
transition: opacity var(--transition-duration-fast) ease;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.ts-wrapper.combobox.multi .ts-control .item .remove:hover {
|
|
104
|
+
opacity: 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Once any value is selected, suppress the search input's placeholder so it doesn't trail the
|
|
108
|
+
chosen value. Tom Select toggles .has-items on the wrapper. */
|
|
109
|
+
.ts-wrapper.combobox.has-items .ts-control > input::placeholder {
|
|
110
|
+
color: transparent;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Focus — Tom Select toggles .focus / .input-active on the wrapper (focus lives on the inner
|
|
114
|
+
input, so :focus-visible doesn't match here); re-emit the field halo. */
|
|
115
|
+
.ts-wrapper.combobox.focus,
|
|
116
|
+
.ts-wrapper.combobox.input-active {
|
|
117
|
+
outline: none;
|
|
118
|
+
border-color: var(--color-primary);
|
|
119
|
+
box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-ring) 25%, transparent);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Invalid — bridge aria-invalid from the hidden <select> to the wrapper, which Tom Select inserts
|
|
123
|
+
as a following sibling (not a parent), so adjacent-sibling does the job. */
|
|
124
|
+
select.combobox[aria-invalid="true"] + .ts-wrapper.combobox {
|
|
125
|
+
border-color: var(--color-danger);
|
|
126
|
+
}
|
|
127
|
+
select.combobox[aria-invalid="true"] + .ts-wrapper.combobox.focus,
|
|
128
|
+
select.combobox[aria-invalid="true"] + .ts-wrapper.combobox.input-active {
|
|
129
|
+
border-color: var(--color-danger);
|
|
130
|
+
box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-danger) 25%, transparent);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Disabled — Tom Select adds .disabled to the wrapper; override the field hover (its :disabled
|
|
134
|
+
guard doesn't match a <div>). */
|
|
135
|
+
.ts-wrapper.combobox.disabled {
|
|
136
|
+
opacity: 0.55;
|
|
137
|
+
cursor: not-allowed;
|
|
138
|
+
}
|
|
139
|
+
.ts-wrapper.combobox.disabled .ts-control {
|
|
140
|
+
cursor: not-allowed;
|
|
141
|
+
}
|
|
142
|
+
.ts-wrapper.combobox.disabled:hover {
|
|
143
|
+
border-color: var(--combobox-border-color, var(--color-border-strong));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Hide the original <select> once hydrated (display:none, not the inline style, so user CSS can't
|
|
147
|
+
accidentally unhide it). */
|
|
148
|
+
select.combobox.tomselected,
|
|
149
|
+
select.combobox.ts-hidden-accessible {
|
|
150
|
+
display: none !important;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* === Dropdown === the menu-surface recipe via Tom Select's .ts-dropdown. The trigger anchors the
|
|
154
|
+
position; this paints the surface. */
|
|
155
|
+
.ts-wrapper.combobox .ts-dropdown {
|
|
156
|
+
padding: var(--combobox-popup-padding-block, --spacing(1)) var(--combobox-popup-padding-inline, --spacing(1));
|
|
157
|
+
font-size: var(--combobox-popup-font-size, var(--text-sm));
|
|
158
|
+
line-height: var(--leading-normal);
|
|
159
|
+
color: var(--combobox-color, var(--color-foreground));
|
|
160
|
+
background-color: var(--combobox-bg, var(--color-surface));
|
|
161
|
+
border: var(--combobox-border-width, var(--st-border-width)) solid
|
|
162
|
+
var(--combobox-popup-border-color, var(--color-border));
|
|
163
|
+
border-radius: var(--combobox-radius, var(--radius-md));
|
|
164
|
+
box-shadow: var(--combobox-shadow, var(--shadow-md));
|
|
165
|
+
position: absolute;
|
|
166
|
+
top: 100%;
|
|
167
|
+
inset-inline-start: 0;
|
|
168
|
+
z-index: var(--combobox-z-index, var(--z-index-dropdown));
|
|
169
|
+
min-width: 100%;
|
|
170
|
+
margin: --spacing(2) 0 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.ts-wrapper.combobox .ts-dropdown .ts-dropdown-content {
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-direction: column;
|
|
176
|
+
gap: var(--combobox-item-gap, --spacing(0.5));
|
|
177
|
+
max-height: --spacing(64);
|
|
178
|
+
overflow-y: auto;
|
|
179
|
+
padding: 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.ts-wrapper.combobox .ts-dropdown .optgroup {
|
|
183
|
+
display: flex;
|
|
184
|
+
flex-direction: column;
|
|
185
|
+
gap: var(--combobox-item-gap, --spacing(0.5));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Hide the empty <option value=""> (authors keep it as the "no selection" placeholder anchor). */
|
|
189
|
+
.ts-wrapper.combobox .ts-dropdown .option[data-value=""] {
|
|
190
|
+
display: none;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Option rows — the menu-item recipe; gap reset to 0 because Tom Select's search highlight splits
|
|
194
|
+
the label into inline nodes that a non-zero flex gap would pad with whitespace. */
|
|
195
|
+
.ts-wrapper.combobox .ts-dropdown .option,
|
|
196
|
+
.ts-wrapper.combobox .ts-dropdown .create {
|
|
197
|
+
display: flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
gap: 0;
|
|
200
|
+
width: 100%;
|
|
201
|
+
min-height: var(--combobox-item-min-height, --spacing(8));
|
|
202
|
+
padding: var(--combobox-item-padding-block, --spacing(0.5))
|
|
203
|
+
var(--combobox-item-padding-inline, --spacing(3));
|
|
204
|
+
font: inherit;
|
|
205
|
+
color: var(--combobox-color, var(--color-foreground));
|
|
206
|
+
text-align: start;
|
|
207
|
+
text-decoration: none;
|
|
208
|
+
background-color: transparent;
|
|
209
|
+
border: 0;
|
|
210
|
+
border-radius: var(
|
|
211
|
+
--combobox-item-radius,
|
|
212
|
+
calc(var(--combobox-radius, var(--radius-md)) - var(--combobox-padding-inline, --spacing(1)))
|
|
213
|
+
);
|
|
214
|
+
cursor: pointer;
|
|
215
|
+
user-select: none;
|
|
216
|
+
transition:
|
|
217
|
+
background-color var(--transition-duration-fast) ease,
|
|
218
|
+
color var(--transition-duration-fast) ease;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* Tom Select uses .active for the keyboard-highlighted row, .selected for the chosen value. */
|
|
222
|
+
.ts-wrapper.combobox .ts-dropdown .option:hover,
|
|
223
|
+
.ts-wrapper.combobox .ts-dropdown .option.active,
|
|
224
|
+
.ts-wrapper.combobox .ts-dropdown .create.active {
|
|
225
|
+
color: var(--combobox-item-color-hover, var(--color-accent-foreground));
|
|
226
|
+
background-color: var(--combobox-item-bg-hover, var(--color-accent));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.ts-wrapper.combobox .ts-dropdown .option.selected,
|
|
230
|
+
.ts-wrapper.combobox .ts-dropdown .option.selected.active,
|
|
231
|
+
.ts-wrapper.combobox .ts-dropdown .option.selected:hover {
|
|
232
|
+
color: var(--combobox-item-color-active, var(--color-highlight-foreground));
|
|
233
|
+
background-color: var(--combobox-item-bg-active, var(--color-highlight));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.ts-wrapper.combobox .ts-dropdown .option[data-disabled],
|
|
237
|
+
.ts-wrapper.combobox .ts-dropdown .no-results,
|
|
238
|
+
.ts-wrapper.combobox .ts-dropdown .spinner {
|
|
239
|
+
color: var(--combobox-item-color-disabled, var(--color-muted-foreground));
|
|
240
|
+
background-color: transparent;
|
|
241
|
+
pointer-events: none;
|
|
242
|
+
cursor: default;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@media (prefers-reduced-motion: reduce) {
|
|
246
|
+
.ts-wrapper.combobox .ts-dropdown .option,
|
|
247
|
+
.ts-wrapper.combobox .ts-dropdown .create {
|
|
248
|
+
transition: none;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
type VariantDefs = Record<string, Record<string, string>>;
|
|
2
|
+
interface ComposerConfig<V extends VariantDefs = VariantDefs, K extends string = string> {
|
|
3
|
+
/** Base BEM class, e.g. "button". */
|
|
4
|
+
base: string;
|
|
5
|
+
/** variant name → { value → className }. Empty string = valid value, no class. */
|
|
6
|
+
variants?: V;
|
|
7
|
+
/** Default value per variant (e.g. size: "md" when base already is md). */
|
|
8
|
+
defaultVariants?: {
|
|
9
|
+
[P in keyof V]?: keyof V[P] & string;
|
|
10
|
+
};
|
|
11
|
+
/** CSS-var namespace for knobs, e.g. "button" → --button-<key>. */
|
|
12
|
+
knobPrefix?: string;
|
|
13
|
+
/** Allowed knob keys (camelCase), for typing / autocomplete. */
|
|
14
|
+
knobs?: readonly K[];
|
|
15
|
+
}
|
|
16
|
+
type Tune<K extends string> = Partial<Record<K, string>>;
|
|
17
|
+
type ComposerProps<V extends VariantDefs, K extends string> = {
|
|
18
|
+
[P in keyof V]?: (keyof V[P] & string) | boolean;
|
|
19
|
+
} & {
|
|
20
|
+
tune?: Tune<K>;
|
|
21
|
+
className?: string;
|
|
22
|
+
};
|
|
23
|
+
interface Composed {
|
|
24
|
+
className: string;
|
|
25
|
+
style: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
declare function composer<V extends VariantDefs, K extends string>(config: ComposerConfig<V, K>): (props?: ComposerProps<V, K>) => Composed;
|
|
28
|
+
|
|
29
|
+
declare const button: (props?: ComposerProps<{
|
|
30
|
+
tone: {
|
|
31
|
+
primary: string;
|
|
32
|
+
danger: string;
|
|
33
|
+
neutral: string;
|
|
34
|
+
tertiary: string;
|
|
35
|
+
};
|
|
36
|
+
shape: {
|
|
37
|
+
outline: string;
|
|
38
|
+
ghost: string;
|
|
39
|
+
soft: string;
|
|
40
|
+
};
|
|
41
|
+
size: {
|
|
42
|
+
sm: string;
|
|
43
|
+
md: string;
|
|
44
|
+
lg: string;
|
|
45
|
+
xl: string;
|
|
46
|
+
};
|
|
47
|
+
iconOnly: {
|
|
48
|
+
true: string;
|
|
49
|
+
};
|
|
50
|
+
iconRound: {
|
|
51
|
+
true: string;
|
|
52
|
+
};
|
|
53
|
+
block: {
|
|
54
|
+
true: string;
|
|
55
|
+
};
|
|
56
|
+
wrap: {
|
|
57
|
+
true: string;
|
|
58
|
+
};
|
|
59
|
+
}, "tone" | "gap" | "height" | "paddingInline" | "fontSize" | "fontWeight" | "color" | "bg" | "bgHover" | "bgActive" | "borderWidth" | "borderColor" | "rimMix" | "radius" | "bevel" | "iconSize"> | undefined) => Composed;
|
|
60
|
+
|
|
61
|
+
export { type ComposerProps as C, type Tune as T, type VariantDefs as V, type Composed as a, type ComposerConfig as b, button as c, composer as d };
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/* @stisla/style — Dialog. Ported from src/scss/components/_dialog.scss. A centered modal: a frosted
|
|
2
|
+
* backdrop dims the page, a height-bounded panel holds the visible card (header / body / footer +
|
|
3
|
+
* floating close chip). Shown via [data-state="open"] on the root. References the @theme tokens:
|
|
4
|
+
* colors var(--color-*), sizes/spacing --spacing(n), type var(--text-*) / var(--leading-*) /
|
|
5
|
+
* var(--font-weight-*), radius var(--radius-*), shadow var(--shadow-*); only no-namespace customs use
|
|
6
|
+
* --st-* (border-width, duration, z-index); z-index routes through the z-index scale (--z-index-modal). The backdrop scrim and
|
|
7
|
+
* the frosted close chip use bespoke oklch alphas (no scrim token), and blur radii stay literal
|
|
8
|
+
* (filter geometry, not spacing). Knobs are --dialog-*. State + scroll-lock hooks ([data-shaking],
|
|
9
|
+
* html[data-dialog-open]) are attributes, recast from the legacy is-* classes per the no-is-*
|
|
10
|
+
* convention. Open/close + focus-trap behavior ships with the JS layer. @layer components.
|
|
11
|
+
* Authoring rules: ../../../../PORTING.md */
|
|
12
|
+
|
|
13
|
+
@layer components {
|
|
14
|
+
.dialog {
|
|
15
|
+
position: fixed;
|
|
16
|
+
inset: 0;
|
|
17
|
+
z-index: var(--dialog-z-index, var(--z-index-modal));
|
|
18
|
+
display: none;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
padding: var(--dialog-margin-block, --spacing(4)) var(--dialog-margin-inline, --spacing(4));
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
|
|
24
|
+
/* Light mode runs a solid dark close chip for contrast over the white surface; dark mode flips
|
|
25
|
+
to a subtle light chip so it doesn't dominate the deep surface. Same affordance, inverted
|
|
26
|
+
luminance. @variant dark uses the registered custom-variant ([data-theme="dark"] / .dark). */
|
|
27
|
+
@variant dark {
|
|
28
|
+
--dialog-close-bg: oklch(1 0 0 / 0.1);
|
|
29
|
+
--dialog-close-bg-hover: oklch(1 0 0 / 0.18);
|
|
30
|
+
--dialog-close-color: oklch(1 0 0 / 0.85);
|
|
31
|
+
--dialog-close-color-hover: oklch(1 0 0);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.dialog[data-state="open"] {
|
|
36
|
+
display: flex;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.dialog__backdrop {
|
|
40
|
+
position: absolute;
|
|
41
|
+
inset: 0;
|
|
42
|
+
background-color: var(--dialog-backdrop-bg, oklch(0 0 0 / 0.55));
|
|
43
|
+
backdrop-filter: blur(var(--dialog-backdrop-blur, 12px));
|
|
44
|
+
-webkit-backdrop-filter: blur(var(--dialog-backdrop-blur, 12px));
|
|
45
|
+
opacity: 0;
|
|
46
|
+
transition: opacity var(--dialog-transition-duration, var(--transition-duration-normal)) ease;
|
|
47
|
+
cursor: pointer;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.dialog[data-state="open"] .dialog__backdrop {
|
|
51
|
+
opacity: 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.dialog__panel {
|
|
55
|
+
position: relative;
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
width: 100%;
|
|
59
|
+
max-width: var(--dialog-width, --spacing(112));
|
|
60
|
+
max-height: calc(100dvh - 2 * var(--dialog-margin-block, --spacing(4)));
|
|
61
|
+
margin: auto;
|
|
62
|
+
opacity: 0;
|
|
63
|
+
transform: scale(0.9);
|
|
64
|
+
transition:
|
|
65
|
+
opacity var(--dialog-transition-duration, var(--transition-duration-normal)) ease,
|
|
66
|
+
transform var(--dialog-transition-duration, var(--transition-duration-normal)) ease;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.dialog[data-state="open"] .dialog__panel {
|
|
70
|
+
opacity: 1;
|
|
71
|
+
transform: none;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.dialog__content {
|
|
75
|
+
position: relative;
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
width: 100%;
|
|
79
|
+
max-height: 100%;
|
|
80
|
+
background-color: var(--dialog-bg, var(--color-surface));
|
|
81
|
+
color: var(--dialog-color, var(--color-foreground));
|
|
82
|
+
border: var(--dialog-border-width, var(--st-border-width)) solid
|
|
83
|
+
var(--dialog-border-color, var(--color-border));
|
|
84
|
+
border-radius: var(--dialog-radius, var(--radius-lg));
|
|
85
|
+
box-shadow: var(--dialog-shadow, var(--shadow-lg));
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
outline: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.dialog__header {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
flex: 0 0 auto;
|
|
94
|
+
padding: var(--dialog-padding-block, --spacing(5)) var(--dialog-padding-inline, --spacing(5));
|
|
95
|
+
padding-block-end: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.dialog__title {
|
|
99
|
+
margin: 0;
|
|
100
|
+
font-size: var(--dialog-title-font-size, var(--text-lg));
|
|
101
|
+
font-weight: var(--dialog-title-font-weight, var(--font-weight-semibold));
|
|
102
|
+
line-height: var(--leading-tight);
|
|
103
|
+
color: var(--dialog-color, var(--color-foreground));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.dialog__body {
|
|
107
|
+
flex: 1 1 auto;
|
|
108
|
+
min-height: 0;
|
|
109
|
+
padding: var(--dialog-padding-block, --spacing(5)) var(--dialog-padding-inline, --spacing(5));
|
|
110
|
+
color: var(--dialog-color, var(--color-foreground));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.dialog__footer {
|
|
114
|
+
display: flex;
|
|
115
|
+
flex: 0 0 auto;
|
|
116
|
+
flex-wrap: wrap;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: flex-end;
|
|
119
|
+
gap: --spacing(2);
|
|
120
|
+
padding: var(--dialog-footer-padding-block, --spacing(3.5))
|
|
121
|
+
var(--dialog-padding-inline, --spacing(5));
|
|
122
|
+
background-color: var(--dialog-footer-bg, var(--color-surface-2));
|
|
123
|
+
border-top: var(--st-border-width) solid var(--dialog-footer-border-color, var(--color-border));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Floating dismiss chip — a frosted glass disc that reads on a light surface, a dark surface, and
|
|
127
|
+
over imagery. The semi-transparent oklch fills are bespoke (no scrim token); the dark @variant
|
|
128
|
+
on .dialog above swaps them for the inverted-luminance pair. */
|
|
129
|
+
.dialog__close {
|
|
130
|
+
position: absolute;
|
|
131
|
+
top: --spacing(3);
|
|
132
|
+
inset-inline-end: --spacing(3);
|
|
133
|
+
z-index: 2;
|
|
134
|
+
display: inline-flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
justify-content: center;
|
|
137
|
+
width: var(--dialog-close-size, --spacing(7));
|
|
138
|
+
height: var(--dialog-close-size, --spacing(7));
|
|
139
|
+
padding: 0;
|
|
140
|
+
color: var(--dialog-close-color, oklch(1 0 0 / 0.92));
|
|
141
|
+
background-color: var(--dialog-close-bg, oklch(0 0 0 / 0.35));
|
|
142
|
+
border: 0;
|
|
143
|
+
border-radius: 50%;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
backdrop-filter: blur(6px);
|
|
146
|
+
-webkit-backdrop-filter: blur(6px);
|
|
147
|
+
transition:
|
|
148
|
+
background-color var(--transition-duration-fast) ease,
|
|
149
|
+
color var(--transition-duration-fast) ease;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.dialog__close:hover {
|
|
153
|
+
color: var(--dialog-close-color-hover, oklch(1 0 0));
|
|
154
|
+
background-color: var(--dialog-close-bg-hover, oklch(0 0 0 / 0.55));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.dialog__close:focus-visible {
|
|
158
|
+
outline: 2px solid var(--color-ring);
|
|
159
|
+
outline-offset: 2px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.dialog__close > svg,
|
|
163
|
+
.dialog__close > i {
|
|
164
|
+
width: --spacing(3.5);
|
|
165
|
+
height: --spacing(3.5);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* === Size modifiers === retune the panel width cap only. */
|
|
169
|
+
.dialog--compact {
|
|
170
|
+
--dialog-width: --spacing(80);
|
|
171
|
+
}
|
|
172
|
+
.dialog--roomy {
|
|
173
|
+
--dialog-width: --spacing(160);
|
|
174
|
+
}
|
|
175
|
+
.dialog--spacious {
|
|
176
|
+
--dialog-width: --spacing(240);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.dialog--fullscreen {
|
|
180
|
+
--dialog-margin-inline: 0;
|
|
181
|
+
--dialog-margin-block: 0;
|
|
182
|
+
}
|
|
183
|
+
.dialog--fullscreen .dialog__panel {
|
|
184
|
+
max-width: 100vw;
|
|
185
|
+
max-height: 100dvh;
|
|
186
|
+
height: 100dvh;
|
|
187
|
+
}
|
|
188
|
+
.dialog--fullscreen .dialog__content {
|
|
189
|
+
height: 100%;
|
|
190
|
+
border: 0;
|
|
191
|
+
border-radius: 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* Almost-fullscreen — keeps a breathing strip around the dialog so the page hints through, but the
|
|
195
|
+
dialog takes the rest of the viewport. For immersive editors, onboarding, lightbox-adjacent UIs. */
|
|
196
|
+
.dialog--almost-fullscreen {
|
|
197
|
+
--dialog-margin-inline: --spacing(10);
|
|
198
|
+
--dialog-margin-block: --spacing(10);
|
|
199
|
+
--dialog-width: calc(100vw - 2 * var(--dialog-margin-inline, --spacing(4)));
|
|
200
|
+
}
|
|
201
|
+
.dialog--almost-fullscreen .dialog__panel {
|
|
202
|
+
height: calc(100dvh - 2 * var(--dialog-margin-block, --spacing(4)));
|
|
203
|
+
}
|
|
204
|
+
.dialog--almost-fullscreen .dialog__content {
|
|
205
|
+
height: 100%;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* === Position modifiers === default is centered; --top / --bottom override via auto margins. */
|
|
209
|
+
.dialog__panel--top {
|
|
210
|
+
margin-block-start: 0;
|
|
211
|
+
margin-block-end: auto;
|
|
212
|
+
}
|
|
213
|
+
.dialog__panel--bottom {
|
|
214
|
+
margin-block-start: auto;
|
|
215
|
+
margin-block-end: 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* === Scrollable body === */
|
|
219
|
+
.dialog__panel--scrollable .dialog__body {
|
|
220
|
+
overflow-y: auto;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* === Static backdrop shake === the behavior layer sets [data-shaking] on the panel when a click
|
|
224
|
+
hits the backdrop while the backdrop is static. */
|
|
225
|
+
.dialog__panel[data-shaking] {
|
|
226
|
+
animation: st-dialog-shake var(--transition-duration-normal) ease-in-out;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* === Scroll lock === applied to <html> while any dialog is open; scrollbar-gutter keeps the page
|
|
231
|
+
width stable so opening a dialog doesn't reflow content behind it. */
|
|
232
|
+
html[data-dialog-open] {
|
|
233
|
+
overflow: hidden;
|
|
234
|
+
scrollbar-gutter: stable;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@keyframes st-dialog-shake {
|
|
238
|
+
0%,
|
|
239
|
+
100% {
|
|
240
|
+
transform: none;
|
|
241
|
+
}
|
|
242
|
+
25% {
|
|
243
|
+
transform: translateX(calc(--spacing(1) * -1));
|
|
244
|
+
}
|
|
245
|
+
75% {
|
|
246
|
+
transform: translateX(--spacing(1));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@media (prefers-reduced-motion: reduce) {
|
|
251
|
+
.dialog__backdrop,
|
|
252
|
+
.dialog__panel {
|
|
253
|
+
transition: none;
|
|
254
|
+
}
|
|
255
|
+
.dialog__panel[data-shaking] {
|
|
256
|
+
animation: none;
|
|
257
|
+
}
|
|
258
|
+
}
|