@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
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2019 Muhamad Nauval Azhar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @stisla/style
|
|
2
|
+
|
|
3
|
+
The framework-agnostic style layer for [Stisla](https://github.com/stisla/stisla): the Tailwind `@theme` foundation (`theme.css`), the per-component BEM CSS source, and a pure-JS class composer. `@stisla/css` is built from this, and the framework wrappers (`@stisla/react`, …) consume the composer.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @stisla/style
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { composer, button } from "@stisla/style";
|
|
13
|
+
import "@stisla/style/theme.css"; // the @theme foundation — compile with Tailwind v4
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Docs and source: https://github.com/stisla/stisla
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/* @stisla/style — Accordion. Ported from src/scss/components/_accordion.scss. A stack of collapsible
|
|
2
|
+
* panels: closed items sit transparent on the frame, opening an item raises it as a soft chip
|
|
3
|
+
* (--color-surface-2 fill + --color-border rim) with a divider between heading and body. State lives
|
|
4
|
+
* in [data-state="open|closed"] on the item + [aria-expanded] on the trigger. References the @theme
|
|
5
|
+
* tokens: colors var(--color-*), sizes/spacing --spacing(n), type var(--font-weight-*) /
|
|
6
|
+
* var(--leading-*), radius var(--radius-*), shadow var(--shadow-*); only no-namespace customs use
|
|
7
|
+
* --st-* (border-width, duration). Knobs are --accordion-*. The height-transition on open ships with
|
|
8
|
+
* the JS layer; CSS here shows the resting open/closed states. @layer components.
|
|
9
|
+
* Authoring rules: ../../../../PORTING.md */
|
|
10
|
+
|
|
11
|
+
@layer components {
|
|
12
|
+
/* Geometry — large frame, so radius-lg. With the --spacing(1) wrapper inset the inner item lands
|
|
13
|
+
at a smaller concentric radius, so the open chip reads like a normal rounded element framed by a
|
|
14
|
+
softer outer rim. The radius + padding knobs are re-read in the concentric item-radius calc, so
|
|
15
|
+
their defaults repeat. */
|
|
16
|
+
.accordion {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: var(--accordion-gap, --spacing(1));
|
|
20
|
+
width: 100%;
|
|
21
|
+
padding: var(--accordion-padding-block, --spacing(1))
|
|
22
|
+
var(--accordion-padding-inline, --spacing(1));
|
|
23
|
+
background-color: var(--accordion-bg, var(--color-surface));
|
|
24
|
+
border: var(--accordion-border-width, var(--st-border-width)) solid
|
|
25
|
+
var(--accordion-border-color, var(--color-border));
|
|
26
|
+
border-radius: var(--accordion-radius, var(--radius-lg));
|
|
27
|
+
box-shadow: var(--accordion-shadow, var(--shadow-md));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Item carries a transparent border at rest so the chip-out on open doesn't reflow the row
|
|
31
|
+
(border-width stays constant in both states). overflow: hidden lets the header background fill
|
|
32
|
+
the chip without bleeding past the rounded edge while the body is hidden. */
|
|
33
|
+
.accordion__item {
|
|
34
|
+
border: var(--st-border-width) solid transparent;
|
|
35
|
+
border-radius: calc(
|
|
36
|
+
var(--accordion-radius, var(--radius-lg)) -
|
|
37
|
+
var(--accordion-padding-inline, --spacing(1))
|
|
38
|
+
);
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
transition:
|
|
41
|
+
background-color var(--transition-duration-fast) ease,
|
|
42
|
+
border-color var(--transition-duration-fast) ease;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.accordion__item[data-state="open"] {
|
|
46
|
+
background-color: var(--accordion-item-open-bg, var(--color-surface-2));
|
|
47
|
+
border-color: var(--accordion-item-open-border-color, var(--color-border));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* The <h3> (or h2/h4…) wrapping the trigger — the section sub-header, present for screen-reader
|
|
51
|
+
navigation. Pure structural wrapper; the trigger inside owns all the visual treatment. */
|
|
52
|
+
.accordion__heading {
|
|
53
|
+
margin: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.accordion__trigger {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: --spacing(3);
|
|
60
|
+
width: 100%;
|
|
61
|
+
padding-block: var(--accordion-trigger-padding-block, --spacing(3));
|
|
62
|
+
padding-inline: var(--accordion-trigger-padding-inline, --spacing(4));
|
|
63
|
+
font: inherit;
|
|
64
|
+
font-size: var(--accordion-trigger-font-size, inherit);
|
|
65
|
+
font-weight: var(
|
|
66
|
+
--accordion-trigger-font-weight,
|
|
67
|
+
var(--font-weight-medium)
|
|
68
|
+
);
|
|
69
|
+
text-align: start;
|
|
70
|
+
color: var(--accordion-trigger-color, var(--color-foreground));
|
|
71
|
+
background-color: var(--accordion-trigger-bg, transparent);
|
|
72
|
+
border: 0;
|
|
73
|
+
border-bottom: var(--st-border-width) solid transparent;
|
|
74
|
+
/* Round the top corners to the item radius so the inset focus ring follows the curve instead of
|
|
75
|
+
being clipped square by the item's overflow: hidden. (Bottom corners round only when the item
|
|
76
|
+
is closed — see below.) */
|
|
77
|
+
border-start-start-radius: calc(
|
|
78
|
+
var(--accordion-radius, var(--radius-lg)) -
|
|
79
|
+
var(--accordion-padding-inline, --spacing(1))
|
|
80
|
+
);
|
|
81
|
+
border-start-end-radius: calc(
|
|
82
|
+
var(--accordion-radius, var(--radius-lg)) -
|
|
83
|
+
var(--accordion-padding-inline, --spacing(1))
|
|
84
|
+
);
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
transition:
|
|
87
|
+
background-color var(--transition-duration-fast) ease,
|
|
88
|
+
border-color var(--transition-duration-fast) ease;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.accordion__trigger :is(svg, i) {
|
|
92
|
+
width: --spacing(4.5);
|
|
93
|
+
height: --spacing(4.5);
|
|
94
|
+
flex-shrink: 0;
|
|
95
|
+
color: var(--alert-icon-color, var(--color-muted-foreground));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.accordion__trigger:focus-visible {
|
|
99
|
+
outline: 2px solid var(--accordion-ring, var(--color-ring));
|
|
100
|
+
outline-offset: -2px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.accordion__trigger:disabled {
|
|
104
|
+
color: var(--color-muted-foreground);
|
|
105
|
+
cursor: not-allowed;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Hover paint applies only to closed items — the open chip already signals state via surface-2 +
|
|
109
|
+
rim + divider, so the open header is removed from the hover cascade entirely. The
|
|
110
|
+
`> .accordion__heading >` step matches the ARIA heading wrapper and fences scope: a nested
|
|
111
|
+
accordion inside an open item's body sits deeper, under its own heading, so it won't inherit. */
|
|
112
|
+
.accordion__item[data-state="closed"]
|
|
113
|
+
> .accordion__heading
|
|
114
|
+
> .accordion__trigger:hover:not(:disabled) {
|
|
115
|
+
background-color: var(--accordion-trigger-bg-hover, var(--color-accent));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Closed item: the header is the whole chip, so round its bottom corners too (the open header
|
|
119
|
+
keeps square bottoms where it meets the body) — keeps the focus ring fully rounded. */
|
|
120
|
+
.accordion__item[data-state="closed"]
|
|
121
|
+
> .accordion__heading
|
|
122
|
+
> .accordion__trigger {
|
|
123
|
+
border-end-start-radius: calc(
|
|
124
|
+
var(--accordion-radius, var(--radius-lg)) -
|
|
125
|
+
var(--accordion-padding-inline, --spacing(1))
|
|
126
|
+
);
|
|
127
|
+
border-end-end-radius: calc(
|
|
128
|
+
var(--accordion-radius, var(--radius-lg)) -
|
|
129
|
+
var(--accordion-padding-inline, --spacing(1))
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Open item: divider line between the trigger and the body emerges. */
|
|
134
|
+
.accordion__item[data-state="open"]
|
|
135
|
+
> .accordion__heading
|
|
136
|
+
> .accordion__trigger {
|
|
137
|
+
border-bottom-color: var(
|
|
138
|
+
--accordion-trigger-divider-color,
|
|
139
|
+
var(--color-border)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.accordion__icon {
|
|
144
|
+
flex-shrink: 0;
|
|
145
|
+
/* Pushed to the end of the trigger row; the title text packs to the start. Auto margin rather
|
|
146
|
+
than justify-content: space-between, so anything else added groups at the start. */
|
|
147
|
+
margin-inline-start: auto;
|
|
148
|
+
width: var(--accordion-icon-size, --spacing(4));
|
|
149
|
+
height: var(--accordion-icon-size, --spacing(4));
|
|
150
|
+
color: var(--accordion-icon-color, currentColor);
|
|
151
|
+
transition: transform
|
|
152
|
+
var(--accordion-icon-transition-duration, var(--transition-duration-fast)) ease;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.accordion__item[data-state="open"]
|
|
156
|
+
> .accordion__heading
|
|
157
|
+
> .accordion__trigger
|
|
158
|
+
> .accordion__icon {
|
|
159
|
+
transform: rotate(180deg);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* The body is a pure container with no padding — the JS height-transition animates this element,
|
|
163
|
+
and padding on the animating element causes content reflow during the transition. Padding sits
|
|
164
|
+
on the inner element so it stays at its natural size while the body clips it via overflow. */
|
|
165
|
+
.accordion__body {
|
|
166
|
+
color: var(--accordion-body-color, var(--color-foreground));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.accordion__body-inner {
|
|
170
|
+
padding-block: var(--accordion-body-padding-block, --spacing(3));
|
|
171
|
+
padding-inline: var(--accordion-body-padding-inline, --spacing(4));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* CSS-only collapse; the JS layer swaps in a measured height transition. */
|
|
175
|
+
.accordion__item[data-state="closed"] > .accordion__body {
|
|
176
|
+
display: none;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* === Flush === drop the frame border + shadow so the accordion fades into its parent surface
|
|
180
|
+
(a card or page section). Root padding stays — items still chip out concentrically when opened;
|
|
181
|
+
the parent owns inter-block spacing in flush contexts. */
|
|
182
|
+
.accordion--flush {
|
|
183
|
+
border: 0;
|
|
184
|
+
box-shadow: none;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@media (prefers-reduced-motion: reduce) {
|
|
188
|
+
.accordion__item,
|
|
189
|
+
.accordion__trigger,
|
|
190
|
+
.accordion__icon {
|
|
191
|
+
transition: none;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/* @stisla/style — Alert. Ported from src/scss/components/_alert.scss. References the Tailwind
|
|
2
|
+
* @theme tokens (var(--color-*), --spacing(n), var(--text-*) / var(--font-weight-*) /
|
|
3
|
+
* var(--leading-*), var(--radius-*), --alpha() for tints). Only no-namespace customs use
|
|
4
|
+
* --st-* (border-width). Tone is REQUIRED for visible chrome. Knobs are --alert-*. @layer components. */
|
|
5
|
+
|
|
6
|
+
@layer components {
|
|
7
|
+
.alert {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: --spacing(2.5);
|
|
11
|
+
width: 100%;
|
|
12
|
+
min-height: --spacing(11);
|
|
13
|
+
padding-block: var(--alert-padding-block, --spacing(2.5));
|
|
14
|
+
padding-inline: var(--alert-padding-inline, --spacing(4));
|
|
15
|
+
line-height: var(--leading-none);
|
|
16
|
+
color: var(--alert-color, var(--color-foreground));
|
|
17
|
+
background: var(--alert-bg, transparent);
|
|
18
|
+
border: var(--alert-border-width, var(--st-border-width)) solid
|
|
19
|
+
var(--alert-border-color, transparent);
|
|
20
|
+
border-radius: var(--alert-radius, var(--radius-md));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Leading icon: any direct <svg>/<i> child (same convention as .button). */
|
|
24
|
+
.alert > :is(svg, i) {
|
|
25
|
+
width: --spacing(4.5);
|
|
26
|
+
height: --spacing(4.5);
|
|
27
|
+
flex-shrink: 0;
|
|
28
|
+
color: var(--alert-icon-color, var(--color-muted-foreground));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* === Parts === */
|
|
32
|
+
.alert__title {
|
|
33
|
+
margin: 0;
|
|
34
|
+
font-size: var(--text-sm);
|
|
35
|
+
font-weight: var(--font-weight-semibold);
|
|
36
|
+
line-height: var(--leading-normal);
|
|
37
|
+
color: var(--color-foreground);
|
|
38
|
+
}
|
|
39
|
+
.alert__description {
|
|
40
|
+
margin: 0;
|
|
41
|
+
font-size: var(--text-sm);
|
|
42
|
+
line-height: var(--leading-normal);
|
|
43
|
+
color: var(--color-muted-foreground);
|
|
44
|
+
}
|
|
45
|
+
.alert__action {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: --spacing(1.5);
|
|
49
|
+
margin-inline-start: auto;
|
|
50
|
+
}
|
|
51
|
+
.alert__link {
|
|
52
|
+
color: var(--alert-link-color, var(--color-primary));
|
|
53
|
+
font-weight: var(--font-weight-medium);
|
|
54
|
+
text-decoration: underline;
|
|
55
|
+
text-underline-offset: 2px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* === Layout switch — heading + description === */
|
|
59
|
+
.alert:has(.alert__description) {
|
|
60
|
+
--alert-padding-block: --spacing(4);
|
|
61
|
+
display: grid;
|
|
62
|
+
grid-template-columns: auto 1fr auto;
|
|
63
|
+
column-gap: --spacing(2.5);
|
|
64
|
+
row-gap: 0;
|
|
65
|
+
align-items: start;
|
|
66
|
+
min-height: 0;
|
|
67
|
+
|
|
68
|
+
> :is(svg, i) {
|
|
69
|
+
grid-column: 1;
|
|
70
|
+
grid-row: 1;
|
|
71
|
+
align-self: center;
|
|
72
|
+
}
|
|
73
|
+
.alert__title {
|
|
74
|
+
grid-column: 2;
|
|
75
|
+
grid-row: 1;
|
|
76
|
+
line-height: var(--leading-none);
|
|
77
|
+
}
|
|
78
|
+
.alert__description {
|
|
79
|
+
grid-column: 2;
|
|
80
|
+
grid-row: 2;
|
|
81
|
+
}
|
|
82
|
+
.alert__action {
|
|
83
|
+
grid-column: 3;
|
|
84
|
+
grid-row: 1 / span 2;
|
|
85
|
+
align-self: center;
|
|
86
|
+
margin-inline-start: 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&:not(:has(> :is(svg, i))) {
|
|
90
|
+
grid-template-columns: 1fr auto;
|
|
91
|
+
.alert__title,
|
|
92
|
+
.alert__description {
|
|
93
|
+
grid-column: 1;
|
|
94
|
+
}
|
|
95
|
+
.alert__action {
|
|
96
|
+
grid-column: 2;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* === Tone modifiers ===
|
|
102
|
+
* Neutral wears the surface look; intents tint bg 7% + border 40% (via --alpha) and color
|
|
103
|
+
* the icon + link with the intent. Body text stays --color-foreground. */
|
|
104
|
+
.alert--neutral {
|
|
105
|
+
--alert-bg: var(--color-surface);
|
|
106
|
+
--alert-border-color: var(--color-border);
|
|
107
|
+
}
|
|
108
|
+
.alert--primary {
|
|
109
|
+
--alert-bg: --alpha(var(--color-primary) / 7%);
|
|
110
|
+
--alert-border-color: --alpha(var(--color-primary) / 40%);
|
|
111
|
+
--alert-icon-color: var(--color-primary);
|
|
112
|
+
--alert-link-color: var(--color-primary);
|
|
113
|
+
}
|
|
114
|
+
.alert--success {
|
|
115
|
+
--alert-bg: --alpha(var(--color-success) / 7%);
|
|
116
|
+
--alert-border-color: --alpha(var(--color-success) / 40%);
|
|
117
|
+
--alert-icon-color: var(--color-success);
|
|
118
|
+
--alert-link-color: var(--color-success);
|
|
119
|
+
}
|
|
120
|
+
.alert--warning {
|
|
121
|
+
--alert-bg: --alpha(var(--color-warning) / 7%);
|
|
122
|
+
--alert-border-color: --alpha(var(--color-warning) / 40%);
|
|
123
|
+
--alert-icon-color: var(--color-warning);
|
|
124
|
+
--alert-link-color: var(--color-warning);
|
|
125
|
+
}
|
|
126
|
+
.alert--danger {
|
|
127
|
+
--alert-bg: --alpha(var(--color-danger) / 7%);
|
|
128
|
+
--alert-border-color: --alpha(var(--color-danger) / 40%);
|
|
129
|
+
--alert-icon-color: var(--color-danger);
|
|
130
|
+
--alert-link-color: var(--color-danger);
|
|
131
|
+
}
|
|
132
|
+
.alert--info {
|
|
133
|
+
--alert-bg: --alpha(var(--color-info) / 7%);
|
|
134
|
+
--alert-border-color: --alpha(var(--color-info) / 40%);
|
|
135
|
+
--alert-icon-color: var(--color-info);
|
|
136
|
+
--alert-link-color: var(--color-info);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/* @stisla/style — Autocomplete. Ported from src/scss/components/_autocomplete.scss + the
|
|
2
|
+
* form-field-base & menu-* mixins. An <input> that suggests options as the user types (no chips, no
|
|
3
|
+
* tagging); picking a suggestion replaces the typed text. The field reuses the form-field-base shape
|
|
4
|
+
* (duplicated here with --autocomplete-*, keep in sync with input/select/textarea) and the popup
|
|
5
|
+
* reuses the shared menu-surface recipe (expanded inline with --autocomplete-*). References the
|
|
6
|
+
* @theme tokens: colors var(--color-*), sizes/spacing --spacing(n), type var(--text-*) /
|
|
7
|
+
* var(--leading-*) / var(--font-weight-*), radius var(--radius-*), shadow var(--shadow-*); only
|
|
8
|
+
* no-namespace customs use --st-* (border-width, duration, z-index). The popup z-index routes through
|
|
9
|
+
* the z-index scale (--z-index-dropdown). Keyboard focus is [data-highlighted] (recast from the legacy
|
|
10
|
+
* is-* class per the no-is-* convention). Knobs are --autocomplete-*. The suggest/filter/select
|
|
11
|
+
* behavior ships with the JS layer. @layer components. Authoring rules: ../../../../PORTING.md */
|
|
12
|
+
|
|
13
|
+
@layer components {
|
|
14
|
+
/* === Field === the form-field-base shape. */
|
|
15
|
+
.autocomplete {
|
|
16
|
+
display: block;
|
|
17
|
+
width: 100%;
|
|
18
|
+
height: var(--autocomplete-height, --spacing(9));
|
|
19
|
+
padding-block: var(--autocomplete-padding-block, 0);
|
|
20
|
+
padding-inline: var(--autocomplete-padding-inline, --spacing(2.5));
|
|
21
|
+
font-family: inherit;
|
|
22
|
+
font-size: var(--autocomplete-font-size, var(--text-sm));
|
|
23
|
+
font-weight: var(--font-weight-normal);
|
|
24
|
+
line-height: var(--leading-tight);
|
|
25
|
+
color: var(--autocomplete-color, var(--color-foreground));
|
|
26
|
+
background-color: var(--autocomplete-bg, var(--color-surface));
|
|
27
|
+
border: var(--autocomplete-border-width, var(--st-border-width)) solid
|
|
28
|
+
var(--autocomplete-border-color, var(--color-border-strong));
|
|
29
|
+
border-radius: var(--autocomplete-radius, var(--radius-md));
|
|
30
|
+
appearance: none;
|
|
31
|
+
transition:
|
|
32
|
+
border-color var(--transition-duration-fast) ease,
|
|
33
|
+
box-shadow var(--transition-duration-fast) ease,
|
|
34
|
+
background-color var(--transition-duration-fast) ease;
|
|
35
|
+
|
|
36
|
+
/* iOS Safari zooms when a focused field's font-size < 16px and never zooms back. */
|
|
37
|
+
@media (pointer: coarse) {
|
|
38
|
+
font-size: var(--autocomplete-font-size, var(--text-base));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&::placeholder {
|
|
42
|
+
color: var(--autocomplete-placeholder, var(--color-muted-foreground));
|
|
43
|
+
opacity: 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&:hover:not(:disabled, :focus-visible, [aria-invalid="true"], :user-invalid) {
|
|
47
|
+
border-color: color-mix(in oklch, var(--color-border-strong) 80%, var(--color-foreground));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&:focus-visible {
|
|
51
|
+
outline: none;
|
|
52
|
+
border-color: var(--color-primary);
|
|
53
|
+
box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-ring) 25%, transparent);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&[aria-invalid="true"],
|
|
57
|
+
&:user-invalid {
|
|
58
|
+
border-color: var(--color-danger);
|
|
59
|
+
}
|
|
60
|
+
&[aria-invalid="true"]:focus-visible,
|
|
61
|
+
&:user-invalid:focus-visible {
|
|
62
|
+
border-color: var(--color-danger);
|
|
63
|
+
box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-danger) 25%, transparent);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
&:disabled {
|
|
67
|
+
opacity: 0.55;
|
|
68
|
+
cursor: not-allowed;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&[readonly]:not(:disabled) {
|
|
72
|
+
background-color: var(--color-surface-2);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@media (prefers-reduced-motion: reduce) {
|
|
76
|
+
transition: none;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* === Popup === the menu-surface recipe, absolutely positioned under the field. */
|
|
81
|
+
.autocomplete__popup {
|
|
82
|
+
padding: var(--autocomplete-padding-block, --spacing(1)) var(--autocomplete-padding-inline, --spacing(1));
|
|
83
|
+
font-size: var(--autocomplete-font-size, var(--text-sm));
|
|
84
|
+
line-height: var(--leading-normal);
|
|
85
|
+
color: var(--autocomplete-color, var(--color-foreground));
|
|
86
|
+
background-color: var(--autocomplete-bg, var(--color-surface));
|
|
87
|
+
border: var(--autocomplete-border-width, var(--st-border-width)) solid
|
|
88
|
+
var(--autocomplete-popup-border-color, var(--color-border));
|
|
89
|
+
border-radius: var(--autocomplete-radius, var(--radius-md));
|
|
90
|
+
box-shadow: var(--autocomplete-shadow, var(--shadow-md));
|
|
91
|
+
position: absolute;
|
|
92
|
+
top: 0;
|
|
93
|
+
left: 0;
|
|
94
|
+
z-index: var(--autocomplete-z-index, var(--z-index-dropdown));
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
gap: var(--autocomplete-item-gap, --spacing(0.5));
|
|
98
|
+
margin: 0;
|
|
99
|
+
list-style: none;
|
|
100
|
+
overflow-y: auto;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.autocomplete__popup[hidden] {
|
|
104
|
+
display: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* === Item === the menu-item recipe; gap reset to 0 because search-highlight <mark> splits the
|
|
108
|
+
label into inline nodes, and a non-zero flex gap would paint whitespace between them. */
|
|
109
|
+
.autocomplete__item {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 0;
|
|
113
|
+
width: 100%;
|
|
114
|
+
min-height: var(--autocomplete-item-min-height, --spacing(8));
|
|
115
|
+
padding: var(--autocomplete-item-padding-block, --spacing(0.5))
|
|
116
|
+
var(--autocomplete-item-padding-inline, --spacing(3));
|
|
117
|
+
font: inherit;
|
|
118
|
+
color: var(--autocomplete-color, var(--color-foreground));
|
|
119
|
+
text-align: start;
|
|
120
|
+
text-decoration: none;
|
|
121
|
+
background-color: transparent;
|
|
122
|
+
border: 0;
|
|
123
|
+
border-radius: var(
|
|
124
|
+
--autocomplete-item-radius,
|
|
125
|
+
calc(var(--autocomplete-radius, var(--radius-md)) - var(--autocomplete-padding-inline, --spacing(1)))
|
|
126
|
+
);
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
user-select: none;
|
|
129
|
+
transition:
|
|
130
|
+
background-color var(--transition-duration-fast) ease,
|
|
131
|
+
color var(--transition-duration-fast) ease;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.autocomplete__item:hover,
|
|
135
|
+
.autocomplete__item[data-highlighted] {
|
|
136
|
+
color: var(--autocomplete-item-color-hover, var(--color-accent-foreground));
|
|
137
|
+
background-color: var(--autocomplete-item-bg-hover, var(--color-accent));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Search match highlight — the JS wraps each matching run in <mark>; just weight, no fill (a box
|
|
141
|
+
per matched character reads noisy in a list). */
|
|
142
|
+
.autocomplete__item mark {
|
|
143
|
+
background: none;
|
|
144
|
+
color: inherit;
|
|
145
|
+
font-weight: var(--autocomplete-mark-font-weight, var(--font-weight-semibold));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Empty / no-results row — a disabled, non-interactive italic item. */
|
|
149
|
+
.autocomplete__empty {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: 0;
|
|
153
|
+
width: 100%;
|
|
154
|
+
min-height: var(--autocomplete-item-min-height, --spacing(8));
|
|
155
|
+
padding: var(--autocomplete-item-padding-block, --spacing(0.5))
|
|
156
|
+
var(--autocomplete-item-padding-inline, --spacing(3));
|
|
157
|
+
font: inherit;
|
|
158
|
+
color: var(--autocomplete-item-color-disabled, var(--color-muted-foreground));
|
|
159
|
+
background-color: transparent;
|
|
160
|
+
border: 0;
|
|
161
|
+
border-radius: var(
|
|
162
|
+
--autocomplete-item-radius,
|
|
163
|
+
calc(var(--autocomplete-radius, var(--radius-md)) - var(--autocomplete-padding-inline, --spacing(1)))
|
|
164
|
+
);
|
|
165
|
+
pointer-events: none;
|
|
166
|
+
cursor: default;
|
|
167
|
+
font-style: italic;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@media (prefers-reduced-motion: reduce) {
|
|
171
|
+
.autocomplete__item {
|
|
172
|
+
transition: none;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* === Sizes (base = md) === */
|
|
177
|
+
.autocomplete--sm {
|
|
178
|
+
--autocomplete-radius: var(--radius-sm);
|
|
179
|
+
--autocomplete-height: --spacing(7);
|
|
180
|
+
--autocomplete-padding-inline: --spacing(2);
|
|
181
|
+
--autocomplete-font-size: var(--text-xs);
|
|
182
|
+
@media (pointer: coarse) {
|
|
183
|
+
--autocomplete-font-size: var(--text-base);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.autocomplete--lg {
|
|
188
|
+
--autocomplete-radius: var(--radius-lg);
|
|
189
|
+
--autocomplete-height: --spacing(11);
|
|
190
|
+
--autocomplete-padding-inline: --spacing(3.5);
|
|
191
|
+
--autocomplete-font-size: var(--text-base);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/* @stisla/style — Avatar. Ported from src/scss/components/_avatar.scss. Initials / image / icon in a
|
|
2
|
+
* rounded-square (or circle) tile, with an optional corner indicator. References the @theme tokens
|
|
3
|
+
* (colors var(--color-*), spacing/sizes --spacing(n), type var(--text-*) / var(--font-weight-*) /
|
|
4
|
+
* var(--leading-*), radius var(--radius-*)); only no-namespace customs use --st-* (duration). Knobs
|
|
5
|
+
* are --avatar-*. Sizes compact/roomy/spacious → sm/lg/xl.
|
|
6
|
+
*
|
|
7
|
+
* Image loading: the optional avatar.js preloader writes data-status on the root and CSS reveals the
|
|
8
|
+
* image only on "loaded". DEFERRED (vanilla-JS pass) — for a known-good static <img>, set
|
|
9
|
+
* data-status="loaded" inline to reveal it without JS. The pill radius is a literal 9999px (the radius
|
|
10
|
+
* scale is sm/md/lg only; same precedent as .button--icon-round). @layer components.
|
|
11
|
+
* Authoring rules: ../../../../PORTING.md */
|
|
12
|
+
|
|
13
|
+
@layer components {
|
|
14
|
+
.avatar {
|
|
15
|
+
position: relative;
|
|
16
|
+
display: inline-flex;
|
|
17
|
+
flex-shrink: 0;
|
|
18
|
+
align-items: center;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
width: var(--avatar-size, --spacing(10));
|
|
21
|
+
height: var(--avatar-size, --spacing(10));
|
|
22
|
+
font-family: inherit;
|
|
23
|
+
font-size: var(--avatar-font-size, var(--text-sm));
|
|
24
|
+
font-weight: var(--avatar-font-weight, var(--font-weight-semibold));
|
|
25
|
+
line-height: var(--leading-none);
|
|
26
|
+
color: var(--avatar-color, var(--color-neutral-foreground));
|
|
27
|
+
background: var(--avatar-bg, var(--color-neutral));
|
|
28
|
+
border-radius: var(--avatar-radius, var(--radius-md));
|
|
29
|
+
vertical-align: middle;
|
|
30
|
+
user-select: none;
|
|
31
|
+
/* No overflow: hidden on the root — the image clips itself via border-radius + object-fit, so the
|
|
32
|
+
indicator can translate past the corner without being chopped. Ring is off by default (width 0;
|
|
33
|
+
.avatar-group turns it on). */
|
|
34
|
+
box-shadow: 0 0 0 var(--avatar-ring-width, 0) var(--avatar-ring-color, var(--color-background));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* === Image === absolutely positioned over the fallback; hidden until data-status flips to
|
|
38
|
+
* "loaded" so the fallback paints during idle / loading / error. */
|
|
39
|
+
.avatar__image {
|
|
40
|
+
position: absolute;
|
|
41
|
+
inset: 0;
|
|
42
|
+
width: 100%;
|
|
43
|
+
height: 100%;
|
|
44
|
+
object-fit: cover;
|
|
45
|
+
border-radius: inherit;
|
|
46
|
+
opacity: 0;
|
|
47
|
+
transition: opacity var(--transition-duration-fast) ease-out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.avatar[data-status="loaded"] .avatar__image {
|
|
51
|
+
opacity: 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* === Fallback === sized to the root via the flex parent so it fills cleanly in document flow. */
|
|
55
|
+
.avatar__fallback {
|
|
56
|
+
display: inline-flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
justify-content: center;
|
|
59
|
+
width: 100%;
|
|
60
|
+
height: 100%;
|
|
61
|
+
border-radius: inherit;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Inline icons in the fallback scale to its font-size (em-relative, same as .button / .badge). */
|
|
65
|
+
.avatar__fallback :is(svg, i) {
|
|
66
|
+
width: 1em;
|
|
67
|
+
height: 1em;
|
|
68
|
+
flex-shrink: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* === Size modifiers (base = md) === */
|
|
72
|
+
.avatar--sm {
|
|
73
|
+
--avatar-size: --spacing(8);
|
|
74
|
+
--avatar-font-size: var(--text-xs);
|
|
75
|
+
}
|
|
76
|
+
.avatar--lg {
|
|
77
|
+
--avatar-size: --spacing(12);
|
|
78
|
+
--avatar-font-size: var(--text-base);
|
|
79
|
+
}
|
|
80
|
+
.avatar--xl {
|
|
81
|
+
--avatar-size: --spacing(16);
|
|
82
|
+
--avatar-font-size: var(--text-xl);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* === Shape === default is rounded square; circle is the modifier. */
|
|
86
|
+
.avatar--circle {
|
|
87
|
+
--avatar-radius: 9999px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* === Indicator === a small pip pinned to a corner (status dot, counter, verified mark). Ring
|
|
91
|
+
* punches it out of the avatar surface so it reads as a separate object. Default position bottom-end;
|
|
92
|
+
* the 25% translate lifts it half-out so the ring reads as a clean separator. */
|
|
93
|
+
.avatar__indicator {
|
|
94
|
+
position: absolute;
|
|
95
|
+
bottom: 0;
|
|
96
|
+
inset-inline-end: 0;
|
|
97
|
+
transform: translate(25%, 25%);
|
|
98
|
+
display: inline-flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
justify-content: center;
|
|
101
|
+
min-width: var(--avatar-indicator-size, --spacing(2.5));
|
|
102
|
+
height: var(--avatar-indicator-size, --spacing(2.5));
|
|
103
|
+
padding: 0 0.25em;
|
|
104
|
+
font-size: var(--avatar-indicator-font-size, 0.625rem);
|
|
105
|
+
font-weight: var(--font-weight-semibold);
|
|
106
|
+
line-height: var(--leading-none);
|
|
107
|
+
color: var(--avatar-indicator-color, var(--color-success-foreground));
|
|
108
|
+
background: var(--avatar-indicator-bg, var(--color-success));
|
|
109
|
+
border-radius: 9999px;
|
|
110
|
+
box-shadow: 0 0 0 var(--avatar-indicator-ring-width, 2px)
|
|
111
|
+
var(--avatar-indicator-ring-color, var(--color-background));
|
|
112
|
+
z-index: 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.avatar__indicator :is(svg, i) {
|
|
116
|
+
width: 1em;
|
|
117
|
+
height: 1em;
|
|
118
|
+
flex-shrink: 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Position modifier — flip to top-end. (--bottom is the base default, available as explicit markup.) */
|
|
122
|
+
.avatar__indicator--top {
|
|
123
|
+
top: 0;
|
|
124
|
+
bottom: auto;
|
|
125
|
+
transform: translate(25%, -25%);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Indicator sizes — pip diameter + font-size retune together so a digit or icon stays legible. The
|
|
129
|
+
font sizes are a deliberate sub-type-scale micro-progression (kept literal). */
|
|
130
|
+
.avatar__indicator--sm {
|
|
131
|
+
--avatar-indicator-size: --spacing(2);
|
|
132
|
+
--avatar-indicator-font-size: 0.5rem;
|
|
133
|
+
}
|
|
134
|
+
.avatar__indicator--lg {
|
|
135
|
+
--avatar-indicator-size: --spacing(3.5);
|
|
136
|
+
--avatar-indicator-font-size: 0.625rem;
|
|
137
|
+
}
|
|
138
|
+
.avatar__indicator--xl {
|
|
139
|
+
--avatar-indicator-size: --spacing(4.5);
|
|
140
|
+
--avatar-indicator-font-size: var(--text-xs); /* 12px — the one pip size with a type token */
|
|
141
|
+
}
|
|
142
|
+
}
|