@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.
Files changed (63) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +16 -0
  3. package/dist/accordion/accordion.css +194 -0
  4. package/dist/alert/alert.css +138 -0
  5. package/dist/autocomplete/autocomplete.css +193 -0
  6. package/dist/avatar/avatar.css +142 -0
  7. package/dist/avatar-group/avatar-group.css +42 -0
  8. package/dist/badge/badge.css +74 -0
  9. package/dist/breadcrumb/breadcrumb.css +71 -0
  10. package/dist/button/button.css +318 -0
  11. package/dist/button/index.d.ts +1 -0
  12. package/dist/button/index.js +6 -0
  13. package/dist/button-group/button-group.css +108 -0
  14. package/dist/card/card.css +219 -0
  15. package/dist/carousel/carousel.css +170 -0
  16. package/dist/checkbox/checkbox.css +98 -0
  17. package/dist/chunk-K45KLI3Y.js +74 -0
  18. package/dist/collapsible/collapsible.css +36 -0
  19. package/dist/combobox/combobox.css +106 -0
  20. package/dist/combobox/combobox.tomselect.css +251 -0
  21. package/dist/config-CARtrJ7I.d.ts +61 -0
  22. package/dist/dialog/dialog.css +258 -0
  23. package/dist/drawer/drawer.css +318 -0
  24. package/dist/empty-state/empty-state.css +138 -0
  25. package/dist/field/field.css +70 -0
  26. package/dist/icon-box/icon-box.css +64 -0
  27. package/dist/illustration/illustration.css +103 -0
  28. package/dist/index.d.ts +14 -0
  29. package/dist/index.js +60 -0
  30. package/dist/indicator/indicator.css +84 -0
  31. package/dist/input/input.css +220 -0
  32. package/dist/input-group/input-group.css +141 -0
  33. package/dist/kbd/kbd.css +55 -0
  34. package/dist/link/link.css +28 -0
  35. package/dist/list-group/list-group.css +261 -0
  36. package/dist/media/media.css +115 -0
  37. package/dist/menu/menu.css +237 -0
  38. package/dist/meter/meter.css +124 -0
  39. package/dist/navbar/navbar.css +170 -0
  40. package/dist/page/page.css +95 -0
  41. package/dist/pagination/pagination.css +125 -0
  42. package/dist/placeholders/placeholders.css +58 -0
  43. package/dist/popover/popover.css +251 -0
  44. package/dist/progress/progress.css +139 -0
  45. package/dist/radio/radio.css +81 -0
  46. package/dist/scroll-area/scroll-area.css +25 -0
  47. package/dist/scroll-area/scroll-area.overlayscrollbars.css +42 -0
  48. package/dist/select/select.css +282 -0
  49. package/dist/separator/separator.css +26 -0
  50. package/dist/sidebar/sidebar.css +493 -0
  51. package/dist/slider/slider.css +159 -0
  52. package/dist/spinner/spinner.css +65 -0
  53. package/dist/switch/switch.css +91 -0
  54. package/dist/table/table.css +284 -0
  55. package/dist/tabs/tabs.css +137 -0
  56. package/dist/textarea/textarea.css +99 -0
  57. package/dist/timeline/timeline.css +271 -0
  58. package/dist/toast/toast.css +267 -0
  59. package/dist/toggle/toggle.css +125 -0
  60. package/dist/toggle-group/toggle-group.css +87 -0
  61. package/dist/tooltip/tooltip.css +95 -0
  62. package/package.json +46 -0
  63. package/src/theme.css +151 -0
@@ -0,0 +1,65 @@
1
+ /* @stisla/style — Spinner. Ported from src/scss/components/_spinner.scss. Default is a border ring;
2
+ * .spinner--grow is a pulsing dot. Color inherits via currentColor (any parent .text-* recolors it).
3
+ * References the @theme tokens (spacing/sizes --spacing(n)). Knobs are --spinner-*; sizes compact/roomy
4
+ * → sm/lg. Stays round. Under prefers-reduced-motion the loop SLOWS rather than stops (a static
5
+ * spinner reads as "frozen", not "loading"). @layer components. Authoring rules: ../../../../PORTING.md */
6
+
7
+ @layer components {
8
+ .spinner {
9
+ display: inline-block;
10
+ width: var(--spinner-size, --spacing(5));
11
+ height: var(--spinner-size, --spacing(5));
12
+ vertical-align: -0.125em;
13
+ color: inherit;
14
+ border: var(--spinner-width, 2px) solid currentColor;
15
+ border-right-color: transparent;
16
+ border-radius: 50%;
17
+ animation: st-spinner-border var(--spinner-duration, 0.75s) linear infinite;
18
+ }
19
+
20
+ /* Grow — a pulsing solid disc: replace the ring with a filled circle and swap the animation. */
21
+ .spinner--grow {
22
+ --spinner-duration: 0.75s;
23
+ border: 0;
24
+ background: currentColor;
25
+ opacity: 0;
26
+ animation-name: st-spinner-grow;
27
+ }
28
+
29
+ /* === Sizes (base = md) === */
30
+ .spinner--sm {
31
+ --spinner-size: --spacing(3.5);
32
+ --spinner-width: 1.5px;
33
+ }
34
+ .spinner--lg {
35
+ --spinner-size: --spacing(8);
36
+ --spinner-width: 3px;
37
+ }
38
+
39
+ /* Slow the loop instead of stopping it — a static spinner reads as "stuck", not "loading". */
40
+ @media (prefers-reduced-motion: reduce) {
41
+ .spinner {
42
+ --spinner-duration: 1.5s;
43
+ }
44
+ }
45
+ }
46
+
47
+ @keyframes st-spinner-border {
48
+ to {
49
+ transform: rotate(360deg);
50
+ }
51
+ }
52
+
53
+ @keyframes st-spinner-grow {
54
+ 0% {
55
+ transform: scale(0);
56
+ opacity: 0;
57
+ }
58
+ 50% {
59
+ opacity: 1;
60
+ }
61
+ 100% {
62
+ transform: scale(1);
63
+ opacity: 0;
64
+ }
65
+ }
@@ -0,0 +1,91 @@
1
+ /* @stisla/style — Switch. Ported from src/scss/components/_switch.scss. Track + thumb toggle: the
2
+ * track is the <input> itself (rounded rect); the thumb is a radial-gradient circle that slides via
3
+ * background-position on :checked. References the @theme tokens (colors var(--color-*), spacing
4
+ * --spacing(n)); only no-namespace customs use --st-* (border-width). Knobs are --switch-*.
5
+ *
6
+ * Authoring (matches .checkbox / .radio — primitive input + separate <label>, composed via .field__item):
7
+ * <div class="field__item">
8
+ * <input class="switch" type="checkbox" role="switch" id="x">
9
+ * <label class="field__label" for="x">Notifications</label>
10
+ * </div>
11
+ * Pair role="switch" so assistive tech announces "switch". State is native (:checked / :disabled /
12
+ * :focus-visible / [aria-invalid] / :user-invalid), no is-*. NOTE: the pill radius default is a
13
+ * literal 9999px — the radius scale is sm/md/lg only (no full); same precedent as .button--icon-round.
14
+ * Authoring rules: ../../../../PORTING.md */
15
+
16
+ @layer components {
17
+ .switch {
18
+ flex-shrink: 0;
19
+ width: var(--switch-track-width, --spacing(7));
20
+ height: var(--switch-track-height, --spacing(4));
21
+ margin: 0;
22
+ vertical-align: middle;
23
+ border: var(--st-border-width) solid transparent;
24
+ /* Pill by default; a preset squares the track by setting --switch-radius. */
25
+ border-radius: var(--switch-radius, 9999px);
26
+ /* Off-state track: a semi-transparent foreground tone (not --color-neutral, which sits too light
27
+ in light mode to contrast a white thumb). Resolves to a mid-gray that adapts per theme. */
28
+ background-color: var(--switch-off-bg, color-mix(in oklch, var(--color-foreground) 25%, transparent));
29
+ /* Thumb lives in the paint (<input> is void, no pseudo). Default is a hard-edged radial circle;
30
+ a preset swaps --switch-thumb-paint to a linear-gradient for a solid square thumb. */
31
+ background-image: var(
32
+ --switch-thumb-paint,
33
+ radial-gradient(circle at center, var(--switch-thumb-color, white) 49%, transparent 51%)
34
+ );
35
+ background-size: var(--switch-thumb-size, --spacing(3)) var(--switch-thumb-size, --spacing(3));
36
+ background-position: var(--switch-inset, --spacing(0.5)) center;
37
+ background-repeat: no-repeat;
38
+ appearance: none;
39
+ cursor: pointer;
40
+ transition:
41
+ background-color var(--transition-duration-fast) ease,
42
+ background-position 0.15s ease,
43
+ border-color var(--transition-duration-fast) ease,
44
+ box-shadow var(--transition-duration-fast) ease;
45
+ print-color-adjust: exact;
46
+
47
+ /* Hover (idle only). Skip focus / invalid / checked / disabled. */
48
+ &:hover:not(:disabled, :focus-visible, :checked, [aria-invalid="true"], :user-invalid) {
49
+ background-color: color-mix(in oklch, var(--color-foreground) 35%, transparent);
50
+ }
51
+
52
+ &:focus-visible {
53
+ outline: none;
54
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-ring) 25%, transparent);
55
+ }
56
+
57
+ &:checked {
58
+ background-color: var(--switch-on-bg, var(--color-primary));
59
+ background-position: calc(100% - var(--switch-inset, --spacing(0.5))) center;
60
+ }
61
+
62
+ /* Validation — touches the border only; the fill follows on/off so both signals compose. */
63
+ &[aria-invalid="true"],
64
+ &:user-invalid {
65
+ border-color: var(--color-danger);
66
+ }
67
+ &[aria-invalid="true"]:focus-visible,
68
+ &:user-invalid:focus-visible {
69
+ border-color: var(--color-danger);
70
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-danger) 25%, transparent);
71
+ }
72
+
73
+ &:disabled {
74
+ opacity: 0.55;
75
+ cursor: not-allowed;
76
+ }
77
+
78
+ @media (prefers-reduced-motion: reduce) {
79
+ transition: none;
80
+ }
81
+ }
82
+
83
+ /* === Large (base = md) === a bigger variant for standalone settings rows where the switch is the
84
+ * row's primary affordance and reads small at the default. */
85
+ .switch--lg {
86
+ --switch-track-width: --spacing(11);
87
+ --switch-track-height: --spacing(6);
88
+ --switch-thumb-size: --spacing(5);
89
+ --switch-inset: --spacing(0.5);
90
+ }
91
+ }
@@ -0,0 +1,284 @@
1
+ /* @stisla/style — Table. Ported from src/scss/components/_table.scss. Dashboard data grid: dense cells,
2
+ * first/last column bump so it sits flush in a .card. References the @theme tokens (colors var(--color-*),
3
+ * spacing/sizes --spacing(n), type var(--text-*) / var(--font-weight-*), radius var(--radius-*)); only
4
+ * no-namespace customs use --st-* (border-width). Knobs are --table-*. State via <tr data-state="active">
5
+ * / .table__sort[data-state], no is-*. `.table--compact` → `.table--sm`; responsive wrappers `xxl` → `2xl`.
6
+ *
7
+ * Layered-bg mechanic: a per-cell inset box-shadow chain. Three layers — rest (--table-bg), variant
8
+ * (--table-bg-type), runtime state (--table-bg-state) — so hover can light a striped+variant row without
9
+ * erasing the layers underneath. The huge inset shadow paints the cell box without touching
10
+ * background-color, so layers stack. @layer components. Authoring rules: ../../../../PORTING.md */
11
+
12
+ @layer components {
13
+ .table {
14
+ /* Layered paint slots: initial here, set by variant/state rules, consumed via the var() chain. */
15
+ --table-bg-type: initial;
16
+ --table-color-type: initial;
17
+ --table-bg-state: initial;
18
+ --table-color-state: initial;
19
+
20
+ width: 100%;
21
+ color: var(--table-color, var(--color-foreground));
22
+ vertical-align: top;
23
+ border-color: var(--table-border-color, var(--color-border));
24
+ }
25
+
26
+ /* Per-cell layered chain. Variants repaint via --table-bg-type; hover/active stomp via --table-bg-state.
27
+ The inset shadow fills the cell box without touching background-color, so layers stack. */
28
+ .table > :not(caption) > * > * {
29
+ padding: var(--table-cell-padding-block, --spacing(3)) var(--table-cell-padding-inline, --spacing(3));
30
+ /* <th> defaults to center in the UA stylesheet; inherit so headers align with text direction. */
31
+ text-align: inherit;
32
+ color: var(--table-color-state, var(--table-color-type, var(--table-color, var(--color-foreground))));
33
+ background-color: var(--table-bg, transparent);
34
+ /* Preflight sets border to currentColor; pin all sides to the table palette so a modifier that
35
+ turns a side on reads the right color. */
36
+ border-color: var(--table-border-color, var(--color-border));
37
+ border-bottom-width: var(--st-border-width);
38
+ box-shadow: inset 0 0 0 9999px var(--table-bg-state, var(--table-bg-type, transparent));
39
+ }
40
+
41
+ /* Rows pick up the table border color too (.table--bordered turns on row borders). */
42
+ .table > :not(caption) > * {
43
+ border-color: var(--table-border-color, var(--color-border));
44
+ }
45
+
46
+ /* Edge-cell bump — lines the first/last column up with the card gutter so a table drops in flush. */
47
+ .table > :not(caption) > * > :first-child {
48
+ padding-inline-start: var(--table-edge-padding, --spacing(5));
49
+ }
50
+ .table > :not(caption) > * > :last-child {
51
+ padding-inline-end: var(--table-edge-padding, --spacing(5));
52
+ }
53
+
54
+ .table > thead {
55
+ vertical-align: bottom;
56
+ }
57
+ .table > tbody {
58
+ vertical-align: inherit;
59
+ }
60
+
61
+ /* === Head === smaller, muted, semi-bold so thead reads as a label row. */
62
+ .table > thead > tr > * {
63
+ font-size: var(--table-head-font-size, var(--text-xs));
64
+ font-weight: var(--table-head-font-weight, var(--font-weight-medium));
65
+ /* Tight label line-height (no exact token; 1.2 sits between leading-none and leading-tight). */
66
+ line-height: var(--table-head-line-height, 1.2);
67
+ --table-color: var(--table-head-color, var(--color-muted-foreground));
68
+ }
69
+
70
+ /* Header alt — opts the head onto surface-2 (same two-tone language as .card__header--alt). */
71
+ .table__head--alt {
72
+ --table-bg: var(--table-head-bg-alt, var(--color-surface-2));
73
+ }
74
+
75
+ /* === Sortable header === a full-label button/<a> + a CSS-drawn direction caret (no icon dep). The
76
+ caret is dim while sortable, solid once active; direction flips with data-state. */
77
+ .table__sort {
78
+ display: inline-flex;
79
+ align-items: center;
80
+ gap: var(--table-sort-gap, --spacing(1.5));
81
+ margin: 0;
82
+ padding: 0;
83
+ border: 0;
84
+ background: none;
85
+ color: inherit;
86
+ font: inherit;
87
+ text-align: inherit;
88
+ text-decoration: none;
89
+ cursor: pointer;
90
+
91
+ &::after {
92
+ content: "";
93
+ inline-size: var(--table-sort-caret-size, 1em);
94
+ block-size: var(--table-sort-caret-size, 1em);
95
+ flex-shrink: 0;
96
+ background-color: currentColor;
97
+ opacity: var(--table-sort-caret-opacity, 0.4);
98
+ -webkit-mask: center / contain no-repeat var(--table-sort-caret);
99
+ mask: center / contain no-repeat var(--table-sort-caret);
100
+ --table-sort-caret: var(--table-sort-caret-none);
101
+ --table-sort-caret-none: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 15 5 5 5-5'/%3E%3Cpath d='m7 9 5-5 5 5'/%3E%3C/svg%3E");
102
+ --table-sort-caret-asc: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m18 15-6-6-6 6'/%3E%3C/svg%3E");
103
+ --table-sort-caret-desc: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
104
+ transition: opacity var(--transition-duration-fast) ease;
105
+ }
106
+
107
+ &:hover {
108
+ color: var(--table-sort-color-hover, var(--color-foreground));
109
+ --table-sort-caret-opacity: 0.7;
110
+ }
111
+
112
+ &[data-state="asc"],
113
+ &[data-state="desc"] {
114
+ color: var(--table-sort-color-active, var(--color-foreground));
115
+ --table-sort-caret-opacity: 1;
116
+ }
117
+ &[data-state="asc"]::after {
118
+ --table-sort-caret: var(--table-sort-caret-asc);
119
+ }
120
+ &[data-state="desc"]::after {
121
+ --table-sort-caret: var(--table-sort-caret-desc);
122
+ }
123
+ }
124
+
125
+ /* === Body divider === heavier rule above the tbody. */
126
+ .table__body--divider {
127
+ border-top: var(--table-group-divider-width, 2px) solid currentColor;
128
+ }
129
+
130
+ /* === Row variants === 10% bg tint / 25% border mix. Border mix uses `in oklab` (not oklch): the
131
+ neutral border has chroma 0, and oklch would pull every intent toward red; oklab preserves hue. */
132
+ .table__row--primary {
133
+ --table-bg-type: color-mix(in oklab, var(--color-primary) 10%, transparent);
134
+ --table-color-type: var(--color-foreground);
135
+ --table-border-color: color-mix(in oklab, var(--color-primary) 25%, var(--color-border));
136
+ }
137
+ .table__row--success {
138
+ --table-bg-type: color-mix(in oklab, var(--color-success) 10%, transparent);
139
+ --table-color-type: var(--color-foreground);
140
+ --table-border-color: color-mix(in oklab, var(--color-success) 25%, var(--color-border));
141
+ }
142
+ .table__row--info {
143
+ --table-bg-type: color-mix(in oklab, var(--color-info) 10%, transparent);
144
+ --table-color-type: var(--color-foreground);
145
+ --table-border-color: color-mix(in oklab, var(--color-info) 25%, var(--color-border));
146
+ }
147
+ .table__row--warning {
148
+ --table-bg-type: color-mix(in oklab, var(--color-warning) 10%, transparent);
149
+ --table-color-type: var(--color-foreground);
150
+ --table-border-color: color-mix(in oklab, var(--color-warning) 25%, var(--color-border));
151
+ }
152
+ .table__row--danger {
153
+ --table-bg-type: color-mix(in oklab, var(--color-danger) 10%, transparent);
154
+ --table-color-type: var(--color-foreground);
155
+ --table-border-color: color-mix(in oklab, var(--color-danger) 25%, var(--color-border));
156
+ }
157
+ .table__row--neutral {
158
+ --table-bg-type: var(--color-neutral);
159
+ --table-color-type: var(--color-neutral-foreground);
160
+ }
161
+
162
+ /* === Runtime states === */
163
+ .table--striped > tbody > tr:nth-of-type(odd) > * {
164
+ --table-bg-type: var(--table-striped-bg, color-mix(in oklch, var(--color-foreground) 4%, transparent));
165
+ --table-color-type: var(--table-striped-color, var(--color-foreground));
166
+ }
167
+
168
+ .table--striped-cols > :not(caption) > tr > :nth-child(2n) {
169
+ --table-bg-type: var(--table-striped-bg, color-mix(in oklch, var(--color-foreground) 4%, transparent));
170
+ --table-color-type: var(--table-striped-color, var(--color-foreground));
171
+ }
172
+
173
+ .table--hover > tbody > tr:hover > * {
174
+ --table-bg-state: var(--table-bg-hover, var(--color-accent));
175
+ --table-color-state: var(--table-color-hover, var(--color-accent-foreground));
176
+ }
177
+
178
+ /* Active row — persistent selection, highlight tier (matches sidebar / list-group). */
179
+ .table > tbody > tr[data-state="active"] > * {
180
+ --table-bg-state: var(--table-bg-active, var(--color-highlight));
181
+ --table-color-state: var(--table-color-active, var(--color-highlight-foreground));
182
+ }
183
+
184
+ /* === Borders === */
185
+ .table--bordered > :not(caption) > * {
186
+ border-width: var(--st-border-width) 0;
187
+ }
188
+ .table--bordered > :not(caption) > * > * {
189
+ border-width: 0 var(--st-border-width);
190
+ }
191
+
192
+ .table--borderless > :not(caption) > * > * {
193
+ border-bottom-width: 0;
194
+ }
195
+ .table--borderless > :not(:first-child) {
196
+ border-top-width: 0;
197
+ }
198
+
199
+ /* === Small — denser padding === inner cells shrink; edge cells keep the flush-in-card gutter. */
200
+ .table--sm > :not(caption) > * > * {
201
+ padding: var(--table-cell-padding-sm, --spacing(1));
202
+ }
203
+ .table--sm > :not(caption) > * > :first-child {
204
+ padding-inline-start: var(--table-edge-padding, --spacing(5));
205
+ }
206
+ .table--sm > :not(caption) > * > :last-child {
207
+ padding-inline-end: var(--table-edge-padding, --spacing(5));
208
+ }
209
+
210
+ /* === Align middle === */
211
+ .table--align-middle > :not(caption) > * > * {
212
+ vertical-align: middle;
213
+ }
214
+
215
+ /* === Responsive wrappers === (not .table parts) — wrap the table so it can scroll unchanged. */
216
+ .table-responsive {
217
+ overflow-x: auto;
218
+ }
219
+ @variant max-sm {
220
+ .table-responsive-sm {
221
+ overflow-x: auto;
222
+ }
223
+ }
224
+ @variant max-md {
225
+ .table-responsive-md {
226
+ overflow-x: auto;
227
+ }
228
+ }
229
+ @variant max-lg {
230
+ .table-responsive-lg {
231
+ overflow-x: auto;
232
+ }
233
+ }
234
+ @variant max-xl {
235
+ .table-responsive-xl {
236
+ overflow-x: auto;
237
+ }
238
+ }
239
+ @variant max-2xl {
240
+ .table-responsive-2xl {
241
+ overflow-x: auto;
242
+ }
243
+ }
244
+
245
+ /* === Card integration === a flush table composes with the card frame via its own CSS (no overflow
246
+ on the card). Corner cells round to the card's inner radius at the edges that meet the card; the
247
+ last row's bottom border and a bordered table's outer perimeter drop so they don't double the card. */
248
+ .card > .table:last-child > :last-child > tr:last-child > *,
249
+ .card > .table-responsive:last-child > .table > :last-child > tr:last-child > * {
250
+ border-bottom-width: 0;
251
+ }
252
+
253
+ .card > .table:last-child > :last-child > tr:last-child > :first-child,
254
+ .card > .table-responsive:last-child > .table > :last-child > tr:last-child > :first-child {
255
+ border-end-start-radius: calc(var(--card-radius, var(--radius-lg)) - var(--st-border-width));
256
+ }
257
+ .card > .table:last-child > :last-child > tr:last-child > :last-child,
258
+ .card > .table-responsive:last-child > .table > :last-child > tr:last-child > :last-child {
259
+ border-end-end-radius: calc(var(--card-radius, var(--radius-lg)) - var(--st-border-width));
260
+ }
261
+
262
+ .card > .table:first-child > :first-child > tr:first-child > :first-child,
263
+ .card > .table-responsive:first-child > .table > :first-child > tr:first-child > :first-child {
264
+ border-start-start-radius: calc(var(--card-radius, var(--radius-lg)) - var(--st-border-width));
265
+ }
266
+ .card > .table:first-child > :first-child > tr:first-child > :last-child,
267
+ .card > .table-responsive:first-child > .table > :first-child > tr:first-child > :last-child {
268
+ border-start-end-radius: calc(var(--card-radius, var(--radius-lg)) - var(--st-border-width));
269
+ }
270
+
271
+ /* Bordered table: drop the outer perimeter so it doesn't double the card border. */
272
+ .card > .table--bordered:first-child > :first-child > tr:first-child {
273
+ border-top-width: 0;
274
+ }
275
+ .card > .table--bordered:last-child > :last-child > tr:last-child {
276
+ border-bottom-width: 0;
277
+ }
278
+ .card > .table--bordered > :not(caption) > tr > :first-child {
279
+ border-inline-start-width: 0;
280
+ }
281
+ .card > .table--bordered > :not(caption) > tr > :last-child {
282
+ border-inline-end-width: 0;
283
+ }
284
+ }
@@ -0,0 +1,137 @@
1
+ /* @stisla/style — Tabs. Ported from src/scss/components/_tabs.scss. A content-panel switcher: a muted
2
+ * rail hosts triggers, the active one rises out as a paper pill (Radix-aligned). References the @theme
3
+ * tokens (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-* (border-width).
5
+ * Knobs are --tabs-*. State via data-state="active|inactive" / data-disabled / data-orientation, no
6
+ * is-*. Switching behavior (data-stisla-tabs) ships with the JS layer. @layer components.
7
+ * Authoring rules: ../../../../PORTING.md */
8
+
9
+ @layer components {
10
+ .tabs {
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: var(--tabs-gap, --spacing(3));
14
+ }
15
+
16
+ /* === List (the muted rail) === */
17
+ .tabs__list {
18
+ display: inline-flex;
19
+ align-items: stretch;
20
+ align-self: flex-start; /* pill hugs content, doesn't stretch */
21
+ flex-direction: var(--tabs-orientation, row);
22
+ height: var(--tabs-list-height, --spacing(9));
23
+ padding: var(--tabs-list-padding-block, --spacing(0.5))
24
+ var(--tabs-list-padding-inline, --spacing(0.5));
25
+ gap: var(--tabs-list-gap, --spacing(0.5));
26
+ background: var(--tabs-list-bg, var(--color-surface-2));
27
+ border-radius: var(--tabs-list-radius, var(--radius-md));
28
+ color: var(--tabs-list-color, var(--color-muted-foreground));
29
+ }
30
+
31
+ /* === Trigger === border at 1px transparent at rest so the active painted border doesn't shift it. */
32
+ .tabs__trigger {
33
+ appearance: none;
34
+ background: transparent;
35
+ border: var(--st-border-width) solid transparent;
36
+ font: inherit;
37
+ cursor: pointer;
38
+ user-select: none;
39
+ white-space: nowrap;
40
+ display: inline-flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ gap: var(--tabs-trigger-gap, --spacing(2));
44
+ height: 100%; /* fills the rail's inner area via box model */
45
+ padding-block: 0;
46
+ padding-inline: var(--tabs-trigger-padding-inline, --spacing(3));
47
+ color: inherit; /* inherits the rail's muted-foreground */
48
+ font-size: var(--tabs-trigger-font-size, var(--text-sm));
49
+ font-weight: var(--tabs-trigger-font-weight, var(--font-weight-medium));
50
+ line-height: var(--leading-none);
51
+ border-radius: var(
52
+ --tabs-trigger-radius,
53
+ calc(var(--tabs-list-radius, var(--radius-md)) - var(--tabs-list-padding-inline, --spacing(0.5)))
54
+ );
55
+ text-decoration: none;
56
+ transition:
57
+ background-color var(--tabs-transition-duration, var(--transition-duration-fast)) ease,
58
+ color var(--tabs-transition-duration, var(--transition-duration-fast)) ease,
59
+ border-color var(--tabs-transition-duration, var(--transition-duration-fast)) ease;
60
+
61
+ &:hover:not([data-state="active"]) {
62
+ color: var(--tabs-trigger-color-hover, var(--color-foreground));
63
+ }
64
+
65
+ &[data-state="active"] {
66
+ background: var(--tabs-trigger-bg-active, var(--color-highlight));
67
+ color: var(--tabs-trigger-color-active, var(--color-highlight-foreground));
68
+ border-color: var(--tabs-trigger-border-color-active, var(--tabs-trigger-bg-active, var(--color-highlight)));
69
+ }
70
+
71
+ &:focus-visible {
72
+ outline: 2px solid var(--tabs-ring, var(--color-ring));
73
+ outline-offset: 2px;
74
+ }
75
+
76
+ &[data-disabled],
77
+ &:disabled {
78
+ cursor: not-allowed;
79
+ opacity: 0.55;
80
+ }
81
+ }
82
+
83
+ /* Icons scale with the trigger font-size (1em) so a custom font-size retunes glyph + icon together. */
84
+ .tabs__trigger :is(svg, i) {
85
+ width: 1em;
86
+ height: 1em;
87
+ flex-shrink: 0;
88
+ }
89
+
90
+ /* === Panel === */
91
+ .tabs__panel {
92
+ &[data-state="inactive"] {
93
+ display: none;
94
+ }
95
+ &:focus-visible {
96
+ outline: 2px solid var(--tabs-ring, var(--color-ring));
97
+ outline-offset: 2px;
98
+ border-radius: var(--radius-sm);
99
+ }
100
+ }
101
+
102
+ /* === Vertical orientation === root flips to a row (rail inline-start, panels fill the rest); the
103
+ list becomes a column. */
104
+ .tabs--vertical {
105
+ --tabs-orientation: column;
106
+ flex-direction: row;
107
+ align-items: flex-start;
108
+ }
109
+ .tabs--vertical .tabs__list {
110
+ height: auto;
111
+ flex-direction: column;
112
+ align-self: stretch;
113
+ }
114
+ /* Vertical triggers stretch + read left-aligned like a menu list; concrete row height (the list is
115
+ height: auto in vertical mode, so the base height: 100% would collapse). */
116
+ .tabs--vertical .tabs__trigger {
117
+ width: 100%;
118
+ height: --spacing(8);
119
+ justify-content: flex-start;
120
+ text-align: start;
121
+ }
122
+ .tabs--vertical .tabs__panel {
123
+ flex: 1;
124
+ min-width: 0;
125
+ }
126
+
127
+ @media (prefers-reduced-motion: reduce) {
128
+ .tabs {
129
+ --tabs-transition-duration: 0s;
130
+ }
131
+ }
132
+
133
+ /* Block — fills its container width per SPEC §9 escape ramp. */
134
+ .tabs--block {
135
+ width: 100%;
136
+ }
137
+ }
@@ -0,0 +1,99 @@
1
+ /* @stisla/style — Textarea. Ported from src/scss/components/_textarea.scss + the form-field-base
2
+ * mixin. Multi-line opt-out from the input's hard-height contract: restores padding-block, lets rows
3
+ * drive height, and turns --textarea-height into a min-height floor. References the @theme tokens
4
+ * (colors var(--color-*), spacing --spacing(n), type var(--text-*) / var(--font-weight-*), radius
5
+ * var(--radius-*)); only no-namespace customs use --st-* (border-width, duration). Knobs --textarea-*.
6
+ *
7
+ * The shared field base is DUPLICATED here with the --textarea-* namespace (plain CSS has no
8
+ * parameterized mixin). Keep in sync with input.css + select.css. @layer components.
9
+ * Authoring rules: ../../../../PORTING.md */
10
+
11
+ @layer components {
12
+ /* === Shared field base (= former form-field-base, --textarea-* namespace) === */
13
+ .textarea {
14
+ display: block;
15
+ width: 100%;
16
+ font-family: inherit;
17
+ font-size: var(--textarea-font-size, var(--text-sm));
18
+ font-weight: var(--font-weight-normal);
19
+ color: var(--textarea-color, var(--color-foreground));
20
+ background: var(--textarea-bg, var(--color-surface));
21
+ border: var(--textarea-border-width, var(--st-border-width)) solid
22
+ var(--textarea-border-color, var(--color-border-strong));
23
+ border-radius: var(--textarea-radius, var(--radius-md));
24
+ appearance: none;
25
+ transition:
26
+ border-color var(--transition-duration-fast) ease,
27
+ box-shadow var(--transition-duration-fast) ease,
28
+ background-color var(--transition-duration-fast) ease;
29
+
30
+ /* === Multi-line overrides === */
31
+ height: auto;
32
+ min-height: var(--textarea-height, --spacing(9));
33
+ padding-block: var(--textarea-padding-block, --spacing(1.5)); /* restore vertical padding */
34
+ padding-inline: var(--textarea-padding-inline, --spacing(2.5));
35
+ line-height: var(--textarea-line-height, var(--leading-normal));
36
+ resize: vertical;
37
+
38
+ /* iOS Safari zooms when a focused field's font-size < 16px and never zooms back. */
39
+ @media (pointer: coarse) {
40
+ font-size: var(--textarea-font-size, var(--text-base));
41
+ }
42
+
43
+ &::placeholder {
44
+ color: var(--textarea-placeholder, var(--color-muted-foreground));
45
+ opacity: 1;
46
+ }
47
+
48
+ &:hover:not(:disabled, :focus-visible, [aria-invalid="true"], :user-invalid) {
49
+ border-color: color-mix(in oklch, var(--color-border-strong) 80%, var(--color-foreground));
50
+ }
51
+
52
+ &:focus-visible {
53
+ outline: none;
54
+ border-color: var(--color-primary);
55
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-ring) 25%, transparent);
56
+ }
57
+
58
+ &[aria-invalid="true"],
59
+ &:user-invalid {
60
+ border-color: var(--color-danger);
61
+ }
62
+ &[aria-invalid="true"]:focus-visible,
63
+ &:user-invalid:focus-visible {
64
+ border-color: var(--color-danger);
65
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-danger) 25%, transparent);
66
+ }
67
+
68
+ &:disabled {
69
+ opacity: 0.55;
70
+ cursor: not-allowed;
71
+ }
72
+
73
+ &[readonly]:not(:disabled) {
74
+ background: var(--color-surface-2);
75
+ }
76
+
77
+ @media (prefers-reduced-motion: reduce) {
78
+ transition: none;
79
+ }
80
+ }
81
+
82
+ /* === Sizes (base = md) — mirror .input so a textarea sits in a form row without breaking rhythm === */
83
+ .textarea--sm {
84
+ --textarea-radius: var(--radius-sm);
85
+ --textarea-height: --spacing(7);
86
+ --textarea-padding-inline: --spacing(2);
87
+ --textarea-font-size: var(--text-xs);
88
+ @media (pointer: coarse) {
89
+ --textarea-font-size: var(--text-base);
90
+ }
91
+ }
92
+
93
+ .textarea--lg {
94
+ --textarea-radius: var(--radius-lg);
95
+ --textarea-height: --spacing(11);
96
+ --textarea-padding-inline: --spacing(3.5);
97
+ --textarea-font-size: var(--text-base);
98
+ }
99
+ }