@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,14 @@
1
+ import { C as ComposerProps, a as Composed } from './config-CARtrJ7I.js';
2
+ export { b as ComposerConfig, T as Tune, V as VariantDefs, c as button, d as composer } from './config-CARtrJ7I.js';
3
+
4
+ declare const sidebarKnobs: readonly ["gap", "width", "widthCollapsed", "bg", "color", "paddingBlock", "paddingInline", "transitionDuration", "brandColor", "brandIconSize", "brandGap", "groupGap", "groupTitleColor", "groupTitleFontSize", "groupTitleFontWeight", "buttonGap", "buttonHeight", "buttonPaddingInline", "buttonRadius", "buttonColor", "buttonFontWeight", "buttonIconSize", "buttonIconColor", "buttonBgHover", "buttonColorHover", "buttonBgActive", "buttonColorActive", "itemActionSize", "submenuBorderColor", "submenuPaddingInlineStart", "submenuMarginInlineStart"];
5
+ type SidebarKnob = (typeof sidebarKnobs)[number];
6
+ declare const sidebar: (props?: ComposerProps<{
7
+ size: {
8
+ sm: string;
9
+ md: string;
10
+ lg: string;
11
+ };
12
+ }, "gap" | "paddingInline" | "color" | "bg" | "width" | "widthCollapsed" | "paddingBlock" | "transitionDuration" | "brandColor" | "brandIconSize" | "brandGap" | "groupGap" | "groupTitleColor" | "groupTitleFontSize" | "groupTitleFontWeight" | "buttonGap" | "buttonHeight" | "buttonPaddingInline" | "buttonRadius" | "buttonColor" | "buttonFontWeight" | "buttonIconSize" | "buttonIconColor" | "buttonBgHover" | "buttonColorHover" | "buttonBgActive" | "buttonColorActive" | "itemActionSize" | "submenuBorderColor" | "submenuPaddingInlineStart" | "submenuMarginInlineStart"> | undefined) => Composed;
13
+
14
+ export { Composed, ComposerProps, type SidebarKnob, sidebar, sidebarKnobs };
package/dist/index.js ADDED
@@ -0,0 +1,60 @@
1
+ import {
2
+ button,
3
+ composer
4
+ } from "./chunk-K45KLI3Y.js";
5
+
6
+ // src/sidebar/config.ts
7
+ var sidebarKnobs = [
8
+ // Shell
9
+ "gap",
10
+ "width",
11
+ "widthCollapsed",
12
+ "bg",
13
+ "color",
14
+ "paddingBlock",
15
+ "paddingInline",
16
+ "transitionDuration",
17
+ // Brand
18
+ "brandColor",
19
+ "brandIconSize",
20
+ "brandGap",
21
+ // Group
22
+ "groupGap",
23
+ "groupTitleColor",
24
+ "groupTitleFontSize",
25
+ "groupTitleFontWeight",
26
+ // Button (item)
27
+ "buttonGap",
28
+ "buttonHeight",
29
+ "buttonPaddingInline",
30
+ "buttonRadius",
31
+ "buttonColor",
32
+ "buttonFontWeight",
33
+ "buttonIconSize",
34
+ "buttonIconColor",
35
+ "buttonBgHover",
36
+ "buttonColorHover",
37
+ "buttonBgActive",
38
+ "buttonColorActive",
39
+ // Item action
40
+ "itemActionSize",
41
+ // Submenu
42
+ "submenuBorderColor",
43
+ "submenuPaddingInlineStart",
44
+ "submenuMarginInlineStart"
45
+ ];
46
+ var sidebar = composer({
47
+ base: "sidebar",
48
+ variants: {
49
+ size: { sm: "sidebar--sm", md: "", lg: "sidebar--lg" }
50
+ },
51
+ defaultVariants: { size: "md" },
52
+ knobPrefix: "sidebar",
53
+ knobs: sidebarKnobs
54
+ });
55
+ export {
56
+ button,
57
+ composer,
58
+ sidebar,
59
+ sidebarKnobs
60
+ };
@@ -0,0 +1,84 @@
1
+ /* @stisla/style — Indicator. Ported from src/scss/components/_indicator.scss. A contentless status dot
2
+ * (presence, liveness, unread) — distinct from .badge, which labels with a count or word. The dot is
3
+ * the ::after; an opt-in pulse halo is the ::before. Pin it over an avatar/icon corner with absolute
4
+ * positioning on a relative host. References the @theme tokens (colors var(--color-*), spacing/sizes
5
+ * --spacing(n)). Knobs are --indicator-*. Sizes compact/roomy → sm/lg. @layer components.
6
+ * Authoring rules: ../../../../PORTING.md */
7
+
8
+ @layer components {
9
+ .indicator {
10
+ position: relative;
11
+ display: inline-block;
12
+ flex-shrink: 0;
13
+ width: var(--indicator-size, --spacing(2.5));
14
+ height: var(--indicator-size, --spacing(2.5));
15
+ vertical-align: middle;
16
+ }
17
+
18
+ /* The dot — ::after so it paints above the ::before pulse without a z-index. The ring is an opt-in
19
+ separator halo (0-width by default) for sitting the dot over a busy surface. */
20
+ .indicator::after {
21
+ content: "";
22
+ position: absolute;
23
+ inset: 0;
24
+ background: var(--indicator-color, var(--color-muted-foreground));
25
+ border-radius: 50%;
26
+ box-shadow: 0 0 0 var(--indicator-ring-width, 0px) var(--indicator-ring-color, var(--color-background));
27
+ }
28
+
29
+ /* === Intent modifiers === */
30
+ .indicator--primary {
31
+ --indicator-color: var(--color-primary);
32
+ }
33
+ .indicator--success {
34
+ --indicator-color: var(--color-success);
35
+ }
36
+ .indicator--warning {
37
+ --indicator-color: var(--color-warning);
38
+ }
39
+ .indicator--danger {
40
+ --indicator-color: var(--color-danger);
41
+ }
42
+ .indicator--info {
43
+ --indicator-color: var(--color-info);
44
+ }
45
+
46
+ /* === Sizes (base = md) === */
47
+ .indicator--sm {
48
+ --indicator-size: --spacing(2);
49
+ }
50
+ .indicator--lg {
51
+ --indicator-size: --spacing(3.5);
52
+ }
53
+
54
+ /* === Pulse (ping halo) === an expanding, fading ring in the dot's color for "live" signals. The
55
+ ::before paints behind the ::after dot and emanates outward. */
56
+ .indicator--pulse::before {
57
+ content: "";
58
+ position: absolute;
59
+ inset: 0;
60
+ background: var(--indicator-color, var(--color-muted-foreground));
61
+ border-radius: 50%;
62
+ opacity: 0;
63
+ animation: st-indicator-pulse var(--indicator-pulse-duration, 1.2s) cubic-bezier(0, 0, 0.2, 1) infinite;
64
+ }
65
+
66
+ /* A still dot reads as a valid status (unlike a frozen spinner), so drop the ping rather than slow it. */
67
+ @media (prefers-reduced-motion: reduce) {
68
+ .indicator--pulse::before {
69
+ animation: none;
70
+ }
71
+ }
72
+ }
73
+
74
+ @keyframes st-indicator-pulse {
75
+ 0% {
76
+ transform: scale(1);
77
+ opacity: 0.55;
78
+ }
79
+ 75%,
80
+ 100% {
81
+ transform: scale(2.5);
82
+ opacity: 0;
83
+ }
84
+ }
@@ -0,0 +1,220 @@
1
+ /* @stisla/style — Input. Ported from src/scss/components/_input.scss + the form-field-base mixin
2
+ * (foundation/_mixins.scss). References the @theme tokens: colors var(--color-*), spacing
3
+ * --spacing(n), type var(--text-*) / var(--font-weight-*), radius var(--radius-*). Only no-namespace
4
+ * customs use --st-* (border-width, duration). Knobs are --input-* (fallback-default). @layer components.
5
+ *
6
+ * The shared field base (border, height, padding, focus halo, validation, hover, disabled, readonly)
7
+ * is DUPLICATED here with the --input-* namespace — plain CSS has no parameterized mixin. Keep the
8
+ * base in sync with textarea.css + select.css (the other two consumers of the former form-field-base).
9
+ * Authoring rules: ../../../../PORTING.md */
10
+
11
+ @layer components {
12
+ /* === Shared field base (= former form-field-base, --input-* namespace) === */
13
+ .input {
14
+ display: block;
15
+ width: 100%;
16
+ height: var(--input-height, --spacing(9));
17
+ padding-block: var(--input-padding-block, 0);
18
+ padding-inline: var(--input-padding-inline, --spacing(2.5));
19
+ font-family: inherit;
20
+ font-size: var(--input-font-size, var(--text-sm));
21
+ font-weight: var(--font-weight-normal);
22
+ line-height: var(--leading-tight);
23
+ color: var(--input-color, var(--color-foreground));
24
+ background: var(--input-bg, var(--color-surface));
25
+ border: var(--input-border-width, var(--st-border-width)) solid
26
+ var(--input-border-color, var(--color-border-strong));
27
+ border-radius: var(--input-radius, var(--radius-md));
28
+ /* Strip UA chrome (search cancel, number spinner); types that want an indicator paint it back. */
29
+ appearance: none;
30
+ transition:
31
+ border-color var(--transition-duration-fast) ease,
32
+ box-shadow var(--transition-duration-fast) ease,
33
+ background-color var(--transition-duration-fast) ease;
34
+
35
+ /* iOS Safari zooms when a focused field's font-size < 16px and never zooms back. Bump touch to 1rem. */
36
+ @media (pointer: coarse) {
37
+ font-size: var(--input-font-size, var(--text-base));
38
+ }
39
+
40
+ &::placeholder {
41
+ color: var(--input-placeholder, var(--color-muted-foreground));
42
+ opacity: 1;
43
+ }
44
+
45
+ /* Hover — only when idle. Focus owns the ring; invalid owns the red rim; disabled rejects the cue. */
46
+ &:hover:not(
47
+ :disabled,
48
+ :focus-visible,
49
+ [aria-invalid="true"],
50
+ :user-invalid
51
+ ) {
52
+ border-color: color-mix(
53
+ in oklch,
54
+ var(--color-border-strong) 80%,
55
+ var(--color-foreground)
56
+ );
57
+ }
58
+
59
+ /* Focus — soft halo + primary border (the field already wears a border; an offset outline reads
60
+ aggressive against a continuously-bordered shape). */
61
+ &:focus-visible {
62
+ outline: none;
63
+ border-color: var(--color-primary);
64
+ box-shadow: 0 0 0 3px
65
+ color-mix(in oklch, var(--color-ring) 25%, transparent);
66
+ }
67
+
68
+ /* Validation. aria-invalid for server / JS / explicit; :user-invalid for native validation. */
69
+ &[aria-invalid="true"],
70
+ &:user-invalid {
71
+ border-color: var(--color-danger);
72
+ }
73
+ &[aria-invalid="true"]:focus-visible,
74
+ &:user-invalid:focus-visible {
75
+ border-color: var(--color-danger);
76
+ box-shadow: 0 0 0 3px
77
+ color-mix(in oklch, var(--color-danger) 25%, transparent);
78
+ }
79
+
80
+ &:disabled {
81
+ opacity: 0.55;
82
+ cursor: not-allowed;
83
+ }
84
+
85
+ /* Readonly (not disabled) — swap bg one tier so it reads "selectable, not editable". */
86
+ &[readonly]:not(:disabled) {
87
+ background: var(--color-surface-2);
88
+ }
89
+
90
+ @media (prefers-reduced-motion: reduce) {
91
+ transition: none;
92
+ }
93
+ }
94
+
95
+ /* === <input type="file"> ===
96
+ * Flex row: the selector button is a small inset chip (mirrors .button--sm.button--neutral; the
97
+ * shadow DOM blocks .button classes, so the rules are inlined) and the filename trails after it. */
98
+ .input[type="file"] {
99
+ display: flex;
100
+ align-items: center;
101
+ cursor: pointer;
102
+ color: var(--color-muted-foreground);
103
+ }
104
+ .input[type="file"]::file-selector-button {
105
+ margin-block-start: 0.4063rem;
106
+ margin-inline-end: --spacing(2.5);
107
+ padding: --spacing(0.5) --spacing(2);
108
+ font-family: inherit;
109
+ font-size: var(--text-xs);
110
+ font-weight: var(--font-weight-medium);
111
+ line-height: var(--leading-tight);
112
+ color: var(--color-neutral-foreground);
113
+ background: var(--color-neutral);
114
+ border: var(--st-border-width) solid
115
+ color-mix(
116
+ in oklch,
117
+ var(--color-neutral) 85%,
118
+ var(--color-muted-foreground)
119
+ );
120
+ border-radius: calc(var(--input-radius, var(--radius-md)) - --spacing(1));
121
+ cursor: pointer;
122
+ transition: background-color var(--transition-duration-fast) ease;
123
+ }
124
+ .input[type="file"]::file-selector-button:hover {
125
+ background: color-mix(in oklch, var(--color-neutral) 88%, black);
126
+ }
127
+
128
+ /* === Native picker indicators ===
129
+ * The UA paints ::-webkit-calendar-picker-indicator black in both themes (invisible on a dark
130
+ * surface). Replace with a lucide glyph painted via background-color + mask so it tracks
131
+ * --color-muted-foreground and themes for free. Firefox has no such pseudo (fully native picker). */
132
+ .input[type="date"]::-webkit-calendar-picker-indicator,
133
+ .input[type="datetime-local"]::-webkit-calendar-picker-indicator,
134
+ .input[type="month"]::-webkit-calendar-picker-indicator,
135
+ .input[type="week"]::-webkit-calendar-picker-indicator {
136
+ width: --spacing(4);
137
+ height: --spacing(4);
138
+ background-color: var(--color-muted-foreground);
139
+ background-image: none;
140
+ -webkit-mask: 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='M8 2v4'/%3E%3Cpath d='M16 2v4'/%3E%3Crect width='18' height='18' x='3' y='4' rx='2'/%3E%3Cpath d='M3 10h18'/%3E%3C/svg%3E")
141
+ no-repeat center / contain;
142
+ mask: 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='M8 2v4'/%3E%3Cpath d='M16 2v4'/%3E%3Crect width='18' height='18' x='3' y='4' rx='2'/%3E%3Cpath d='M3 10h18'/%3E%3C/svg%3E")
143
+ no-repeat center / contain;
144
+ cursor: pointer;
145
+ opacity: 1;
146
+ transition: background-color var(--transition-duration-fast) ease;
147
+ }
148
+
149
+ .input[type="time"]::-webkit-calendar-picker-indicator {
150
+ width: --spacing(4);
151
+ height: --spacing(4);
152
+ background-color: var(--color-muted-foreground);
153
+ background-image: none;
154
+ -webkit-mask: 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%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpolyline points='12 6 12 12 16 14'/%3E%3C/svg%3E")
155
+ no-repeat center / contain;
156
+ mask: 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%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpolyline points='12 6 12 12 16 14'/%3E%3C/svg%3E")
157
+ no-repeat center / contain;
158
+ cursor: pointer;
159
+ opacity: 1;
160
+ transition: background-color var(--transition-duration-fast) ease;
161
+ }
162
+
163
+ .input::-webkit-calendar-picker-indicator:hover {
164
+ background-color: var(--color-foreground);
165
+ }
166
+
167
+ /* === <input type="color"> ===
168
+ * Native swatch inside our field shape. Width collapses to a small fixed swatch; inner padding lets
169
+ * the swatch wear the field radius via the inner-radius rule (outer − outer-padding). */
170
+ .input[type="color"] {
171
+ width: --spacing(12);
172
+ padding: --spacing(1);
173
+ cursor: pointer;
174
+ }
175
+ .input[type="color"]::-webkit-color-swatch {
176
+ border: 0;
177
+ border-radius: calc(var(--input-radius, var(--radius-md)) - --spacing(1));
178
+ }
179
+ .input[type="color"]::-moz-color-swatch {
180
+ border: 0;
181
+ border-radius: calc(var(--input-radius, var(--radius-md)) - --spacing(1));
182
+ }
183
+
184
+ /* === Sizes (base = md) === */
185
+ .input--sm {
186
+ --input-radius: var(--radius-sm);
187
+ --input-height: --spacing(7);
188
+ --input-padding-inline: --spacing(2);
189
+ --input-font-size: var(--text-xs);
190
+ @media (pointer: coarse) {
191
+ --input-font-size: var(--text-base);
192
+ }
193
+ }
194
+
195
+ .input--lg {
196
+ --input-radius: var(--radius-lg);
197
+ --input-height: --spacing(11);
198
+ --input-padding-inline: --spacing(3.5);
199
+ --input-font-size: var(--text-base);
200
+ }
201
+
202
+ /* === Plaintext ===
203
+ * Readonly bare-text variant. Strips the field affordance but keeps the height so it aligns with
204
+ * sibling .input fields in a form row. Pair with `readonly` on the input. */
205
+ .input--plaintext {
206
+ display: inline-flex;
207
+ align-items: center;
208
+ width: 100%;
209
+ height: --spacing(9);
210
+ padding: 0;
211
+ font-family: inherit;
212
+ font-size: var(--text-sm);
213
+ line-height: var(--leading-tight);
214
+ color: var(--color-foreground);
215
+ background: transparent;
216
+ border: var(--st-border-width) solid transparent;
217
+ outline: none;
218
+ appearance: none;
219
+ }
220
+ }
@@ -0,0 +1,141 @@
1
+ /* @stisla/style — Input group. Ported from src/scss/components/_input-group.scss. Wrapper-owns-chrome:
2
+ * the .input-group carries the border, bg, radius, and focus halo; field children go transparent so
3
+ * the addon + input pair shares one continuous surface with one focus state. References the @theme
4
+ * tokens (colors var(--color-*), spacing --spacing(n), radius var(--radius-*)); only no-namespace
5
+ * customs use --st-* (border-width, duration). Knobs are --input-group-*. State via attributes
6
+ * (:focus-visible / [aria-invalid] / :user-invalid / :disabled forwarded through :has()), no is-*.
7
+ * @layer components. Authoring rules: ../../../../PORTING.md */
8
+
9
+ @layer components {
10
+ .input-group {
11
+ display: flex;
12
+ align-items: stretch;
13
+ width: 100%;
14
+ /* Border-inclusive height (border-box): the wrapper owns the control height; its border eats INTO
15
+ this value. Inner fields drop their own height and stretch to fill. */
16
+ height: var(--input-group-height, --spacing(9));
17
+ background: var(--input-group-bg, var(--color-surface));
18
+ border: var(--input-group-border-width, var(--st-border-width)) solid
19
+ var(--input-group-border-color, var(--color-border-strong));
20
+ border-radius: var(--input-group-radius, var(--radius-md));
21
+ transition:
22
+ border-color var(--transition-duration-fast) ease,
23
+ box-shadow var(--transition-duration-fast) ease;
24
+
25
+ /* Focus the whole group when an editable child is focused. */
26
+ &:has(> :is(.input, .select, .textarea):focus-visible) {
27
+ border-color: var(--color-primary);
28
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-ring) 25%, transparent);
29
+ }
30
+
31
+ /* Validation forwarding — an invalid child paints the wrapper red so the message reads on one row. */
32
+ &:has(> [aria-invalid="true"]),
33
+ &:has(> :user-invalid) {
34
+ border-color: var(--color-danger);
35
+ }
36
+ &:has(> [aria-invalid="true"]:focus-visible),
37
+ &:has(> :user-invalid:focus-visible) {
38
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-danger) 25%, transparent);
39
+ }
40
+
41
+ /* Strip chrome from field children — wrapper owns it. Zero only background-color (not the
42
+ `background` shorthand) so a <select>'s chevron image survives. Reset hover + focus too so the
43
+ field's own halo doesn't fight the group focus. */
44
+ > :is(.input, .select, .textarea) {
45
+ background-color: transparent;
46
+ border-color: transparent;
47
+ box-shadow: none;
48
+ height: auto;
49
+
50
+ &:hover,
51
+ &:focus-visible {
52
+ background-color: transparent;
53
+ border-color: transparent;
54
+ box-shadow: none;
55
+ }
56
+ }
57
+
58
+ /* Textarea groups grow with content — release the fixed wrapper height. */
59
+ &:has(> .textarea) {
60
+ height: auto;
61
+ }
62
+
63
+ > :is(.input, .select, .textarea)[readonly] {
64
+ background-color: transparent;
65
+ }
66
+
67
+ /* Inset buttons — a chip floating inside the field, not a slab welded to its edge. Height
68
+ re-derives from the wrapper (minus its border and 2× inset) so the chip centers with even
69
+ gaps; inner radius stays concentric with the wrapper. */
70
+ > .button {
71
+ height: calc(
72
+ var(--input-group-height, --spacing(9)) - 2 * var(--st-border-width) - 2 *
73
+ var(--input-group-inset, --spacing(1))
74
+ );
75
+ margin: var(--input-group-inset, --spacing(1));
76
+ border-radius: calc(
77
+ var(--input-group-radius, var(--radius-md)) - var(--input-group-inset, --spacing(1))
78
+ );
79
+ }
80
+
81
+ /* Drop the addon's inner padding where it meets a sibling field (no seam → matching padding would
82
+ double into a too-wide gap). Keep the outside padding so the addon hugs the wrapper edge. */
83
+ > .input-group__text:first-child {
84
+ padding-inline-end: 0;
85
+ }
86
+ > .input-group__text:last-child {
87
+ padding-inline-start: 0;
88
+ }
89
+
90
+ /* Textareas grow past the height floor; anchor addons to the first line. */
91
+ &:has(> .textarea) > .input-group__text {
92
+ align-self: flex-start;
93
+ height: var(--input-group-height, --spacing(9));
94
+ }
95
+
96
+ /* Disabled forwarding — the transparent inner field can't show its own dim, so move it to the
97
+ wrapper and reset the child's opacity so the dim doesn't compound. */
98
+ &:has(> :disabled) {
99
+ opacity: 0.55;
100
+ > :is(.input, .select, .textarea):disabled {
101
+ opacity: 1;
102
+ }
103
+ }
104
+
105
+ @media (prefers-reduced-motion: reduce) {
106
+ transition: none;
107
+ }
108
+ }
109
+
110
+ /* === Sizes (base = md) — pair with the field child's own size modifier (.input--sm, etc.). === */
111
+ .input-group--sm {
112
+ --input-group-radius: var(--radius-sm);
113
+ --input-group-height: --spacing(7);
114
+ --input-group-padding-inline: --spacing(2);
115
+ }
116
+
117
+ .input-group--lg {
118
+ --input-group-radius: var(--radius-lg);
119
+ --input-group-height: --spacing(11);
120
+ --input-group-padding-inline: --spacing(3.5);
121
+ }
122
+
123
+ /* === Addon === no bg, no border, muted color; inline-flex centers icon + label across the height. */
124
+ .input-group__text {
125
+ display: inline-flex;
126
+ align-items: center;
127
+ padding-inline: var(--input-group-padding-inline, --spacing(2.5));
128
+ background: transparent;
129
+ border: 0;
130
+ color: var(--input-group-addon-color, var(--color-muted-foreground));
131
+ font-size: inherit;
132
+ line-height: 1.2;
133
+ white-space: nowrap;
134
+
135
+ > :is(svg, i) {
136
+ width: --spacing(4);
137
+ height: --spacing(4);
138
+ flex-shrink: 0;
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,55 @@
1
+ /* @stisla/style — Kbd. Ported from src/scss/components/_kbd.scss. A keyboard-shortcut chip. <kbd> is
2
+ * unstyled at the bare-element level (reboot); .kbd opts in. Sits in the neutral interactional tier;
3
+ * the rim uses the same color-mix recipe as .button--neutral so the border stays visible in dark mode.
4
+ * Font-size is rem-based (a deliberate UI element, not tracking surrounding text). References the
5
+ * @theme tokens (colors var(--color-*), spacing --spacing(n), type var(--text-*) / var(--font-mono) /
6
+ * var(--font-weight-*) / var(--leading-*), radius var(--radius-*)); only no-namespace customs use
7
+ * --st-* (border-width). Knobs are --kbd-*. @layer components. Authoring rules: ../../../../PORTING.md */
8
+
9
+ @layer components {
10
+ .kbd {
11
+ display: inline-flex;
12
+ align-items: center;
13
+ gap: var(--kbd-gap, --spacing(1));
14
+ padding-block: var(--kbd-padding-block, --spacing(0.75));
15
+ padding-inline: var(--kbd-padding-inline, --spacing(2));
16
+ font-family: var(--font-mono);
17
+ font-size: var(--kbd-font-size, var(--text-xs));
18
+ font-weight: var(--kbd-font-weight, var(--font-weight-medium));
19
+ line-height: var(--leading-none);
20
+ color: var(--kbd-color, var(--color-neutral-foreground));
21
+ background: var(--kbd-bg, var(--color-neutral));
22
+ border: var(--st-border-width) solid
23
+ var(
24
+ --kbd-rim,
25
+ color-mix(
26
+ in oklch,
27
+ var(--color-neutral) 85%,
28
+ var(--color-muted-foreground)
29
+ )
30
+ );
31
+ border-radius: var(--kbd-radius, 0.5rem); /* intentional for 8px */
32
+ /* Bottom edge in the rim color so the chip reads as a key cap. */
33
+ box-shadow: 0 1px 0
34
+ var(
35
+ --kbd-rim,
36
+ color-mix(
37
+ in oklch,
38
+ var(--color-neutral) 85%,
39
+ var(--color-muted-foreground)
40
+ )
41
+ );
42
+ }
43
+
44
+ /* Combo shortcuts — a .kbd wrapping nested .kbd elements becomes an invisible wrapper so each inner
45
+ cap reads as its own chip side-by-side. */
46
+ .kbd:has(> .kbd) {
47
+ display: inline-flex;
48
+ gap: --spacing(1);
49
+ align-items: center;
50
+ padding: 0;
51
+ background: transparent;
52
+ border: 0;
53
+ box-shadow: none;
54
+ }
55
+ }
@@ -0,0 +1,28 @@
1
+ /* @stisla/style — Link. Ported from src/scss/components/_link.scss. Reboot strips <a> color +
2
+ * underline globally; .link is the opt-back-in for inline links in app UI (helper text, explainers,
3
+ * cross-references) — the visual twin of .prose a. References the @theme tokens (colors var(--color-*),
4
+ * spacing/sizes --spacing(n)). Knobs are --link-*; --link-color-hover derives from --link-color so
5
+ * moving the color moves the hover. @layer components. Authoring rules: ../../../../PORTING.md */
6
+
7
+ @layer components {
8
+ .link {
9
+ display: inline-flex;
10
+ align-items: center;
11
+ gap: var(--link-gap, --spacing(1));
12
+ color: var(--link-color, var(--color-primary));
13
+ text-decoration: underline;
14
+ text-underline-offset: var(--link-decoration-offset, 2px);
15
+ text-decoration-thickness: var(--link-decoration-thickness, 1px);
16
+ }
17
+
18
+ .link svg {
19
+ width: --spacing(4.5);
20
+ }
21
+
22
+ .link:hover {
23
+ color: var(
24
+ --link-color-hover,
25
+ color-mix(in oklch, var(--link-color, var(--color-primary)) 80%, black)
26
+ );
27
+ }
28
+ }