@marianmeres/stuic 3.173.0 → 3.175.0

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/dist/index.css CHANGED
@@ -29,6 +29,20 @@ In practice:
29
29
  These tokens provide a single point of control for cross-component properties.
30
30
  Override globally: :root { --stuic-radius: 0; }
31
31
  Override per-component: --stuic-button-radius: 9999px; (takes precedence)
32
+
33
+ NOTE on the mirrored names. The tokens below are TIER tokens, named
34
+ --stuic-{property}-{tier}, and they ARE declared here. Component override tokens
35
+ are the transposition, --stuic-{component}-{property}, and are NEVER declared --
36
+ they exist only as the first argument of a var() fallback at the usage site:
37
+
38
+ border-radius: var(--stuic-button-radius, var(--stuic-radius-button));
39
+ ^ component token (3) ^ tier token (2, declared here)
40
+
41
+ So --stuic-radius-button (tier) applies to Button, SplitButton and ButtonGroupRadio,
42
+ while --stuic-button-radius (component) applies to Button alone. Same relationship
43
+ between --stuic-border-width-button and --stuic-button-border-width. "button" is the
44
+ only tier name that is also a component name, hence the only confusable pair -- do
45
+ not "fix" one into the other.
32
46
  ============================================================================ */
33
47
  :root {
34
48
  /* Radius: three tiers */
@@ -81,6 +95,7 @@ In practice:
81
95
  @import "./components/CopyButton/index.css";
82
96
  @import "./components/CronInput/index.css";
83
97
  @import "./components/DataTable/index.css";
98
+ @import "./components/DescriptionList/index.css";
84
99
  @import "./components/DismissibleMessage/index.css";
85
100
  @import "./components/DropdownMenu/index.css";
86
101
  @import "./components/EmailVerifyForm/index.css";
package/dist/index.d.ts CHANGED
@@ -46,6 +46,7 @@ export * from "./components/ContextMenu/index.js";
46
46
  export * from "./components/CopyButton/index.js";
47
47
  export * from "./components/CronInput/index.js";
48
48
  export * from "./components/DataTable/index.js";
49
+ export * from "./components/DescriptionList/index.js";
49
50
  export * from "./components/DismissibleMessage/index.js";
50
51
  export * from "./components/Drawer/index.js";
51
52
  export * from "./components/DropdownMenu/index.js";
package/dist/index.js CHANGED
@@ -52,6 +52,7 @@ export * from "./components/ContextMenu/index.js";
52
52
  export * from "./components/CopyButton/index.js";
53
53
  export * from "./components/CronInput/index.js";
54
54
  export * from "./components/DataTable/index.js";
55
+ export * from "./components/DescriptionList/index.js";
55
56
  export * from "./components/DismissibleMessage/index.js";
56
57
  export * from "./components/Drawer/index.js";
57
58
  export * from "./components/DropdownMenu/index.js";
@@ -43,12 +43,14 @@ Global tokens in `src/lib/index.css` that control cross-component visual propert
43
43
  /* Brutalist example — 7 lines to transform everything */
44
44
  :root {
45
45
  --stuic-radius: 0;
46
+ --stuic-radius-button: 0;
46
47
  --stuic-radius-container: 0;
47
48
  --stuic-shadow: none;
48
49
  --stuic-shadow-hover: none;
49
50
  --stuic-shadow-overlay: none;
50
51
  --stuic-shadow-dialog: none;
51
52
  --stuic-border-width: 0;
53
+ --stuic-border-width-button: 0;
52
54
  }
53
55
  ```
54
56
 
@@ -79,16 +81,34 @@ Component CSS must reference shared tokens as **fallbacks at usage sites**, not
79
81
 
80
82
  Per-component overrides (`--stuic-widget-radius: 0`) still take precedence over the shared fallback.
81
83
 
82
- ### Element vs Container
84
+ ### Element vs Button vs Container
83
85
 
84
- Two tiers of radius for natural visual hierarchy:
86
+ Three tiers of radius for natural visual hierarchy:
85
87
 
86
- | Tier | Token | Default | Use for |
87
- | --------- | -------------------------- | ------------------ | ----------------------------------------------------- |
88
- | Element | `--stuic-radius` | `var(--radius-md)` | Buttons, inputs, badges, list items, checkboxes, tabs |
89
- | Container | `--stuic-radius-container` | `var(--radius-lg)` | Cards, modals, dropdowns, notifications, accordions |
88
+ | Tier | Token | Default | Use for |
89
+ | --------- | -------------------------- | ------------------ | --------------------------------------------------- |
90
+ | Element | `--stuic-radius` | `var(--radius-md)` | Inputs, badges, list items, checkboxes, tabs |
91
+ | Button | `--stuic-radius-button` | `var(--radius-md)` | Buttons, split buttons, button groups |
92
+ | Container | `--stuic-radius-container` | `var(--radius-lg)` | Cards, modals, dropdowns, notifications, accordions |
90
93
 
91
- **Rule of thumb:** if it wraps other interactive elements, it's a container.
94
+ **Rule of thumb:** if it wraps other interactive elements, it's a container. Buttons get
95
+ their own tier so a flat-input theme can still have pill buttons (and vice versa);
96
+ `--stuic-border-width-button` splits out the same way.
97
+
98
+ ### Tier tokens vs component tokens
99
+
100
+ The two layers use mirrored names, which is easy to misread for the button tier:
101
+
102
+ | Name | Layer | Declared? | Scope |
103
+ | ----------------------------- | ------------------- | --------------------- | ----------------------------- |
104
+ | `--stuic-radius-button` | shared tier (2) | yes, in `index.css` | every button-family component |
105
+ | `--stuic-button-radius` | component token (3) | no, fallback arg only | `Button` only |
106
+ | `--stuic-border-width-button` | shared tier (2) | yes, in `index.css` | every button-family component |
107
+ | `--stuic-button-border-width` | component token (3) | no, fallback arg only | `Button` only |
108
+
109
+ Read it as `--stuic-{property}-{tier}` for tier tokens and `--stuic-{component}-{property}`
110
+ for component tokens. `button` is the only tier name that is also a component name, so it
111
+ is the only pair that reads as a transposition.
92
112
 
93
113
  ---
94
114
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- 78 Svelte 5 component directories with consistent API patterns. All use runes-based reactivity.
5
+ 79 Svelte 5 component directories with consistent API patterns. All use runes-based reactivity.
6
6
 
7
7
  ## Component Categories
8
8
 
@@ -105,6 +105,7 @@
105
105
  | Thc | Flexible renderer for text, HTML, components, or snippets |
106
106
  | Card | Flexible card with image, title, footer; vertical/horizontal layout |
107
107
  | Stat | KPI/stat card: label + value + delta with trend arrow and semantic coloring |
108
+ | DescriptionList | Term/value list (`<dl>`): stacked or two-column by container query, hairlines, truncation, totals |
108
109
  | Timeline | Vertical event list on a rail: dot/icon/custom markers, inline or opposite time, alternate layout |
109
110
  | TrendChart | Svelte wrapper for `@marianmeres/trend-chart` (subpath-only: `@marianmeres/stuic/trend-chart`) |
110
111
  | Tree | Hierarchical tree view with keyboard nav and drag-and-drop |
@@ -970,6 +971,55 @@ Prefix: `--stuic-copy-button-*` (the surface themes via `--stuic-button-*`)
970
971
 
971
972
  ---
972
973
 
974
+ ## DescriptionList
975
+
976
+ The term-and-value list — the read-only block on every detail page, drawer and back-office panel. Renders `<dl>` › `<div>` per pair › `<dt>` + `<dd>` (plus an optional second `<dd>` qualifying the value), which is what the spec provides for grouping a term with its details and what lets each row be a grid without subgrid. No ARIA is added: a `<dl>` already has list semantics. Two equal ways to feed it — `items` for flat lists, `children` for markup-heavy ones — because the CSS selects **structurally** (`dl > div > dt`, `> div > dd + dd`), so a hand-written row renders identically to a generated one. Not an editor (that is `FieldKeyValues`) and not a table.
977
+
978
+ ### Exports
979
+
980
+ | Export | Kind | Description |
981
+ | ---------------------------- | --------- | -------------------------------------------------------------------------------------------------------- |
982
+ | `DescriptionList` | component | Main component |
983
+ | `DescriptionListProps` | type | Props type |
984
+ | `DescriptionListItem` | type | `{ key?, label, value?, description?, href?, title?, labelLang?, valueLang?, emphasis?, wrap?, class* }` |
985
+ | `DescriptionListLayout` | type | `"auto" \| "stacked" \| "columns"` |
986
+ | `DescriptionListColumnsFrom` | type | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` (20 / 24 / 28 / 32 / 36rem) |
987
+ | `DescriptionListDivide` | type | `"none" \| "inside" \| "outside"` |
988
+ | `DescriptionListWrap` | type | `"anywhere" \| "truncate" \| "normal"` |
989
+ | `DescriptionListValueAlign` | type | `"start" \| "end"` |
990
+ | `DescriptionListSnippetArg` | type | `{ item, index }` — argument of all three snippets |
991
+
992
+ ### Key Props
993
+
994
+ | Prop | Type | Default | Description |
995
+ | ------------- | -------------------------------------- | ------------ | ----------------------------------------------------------------------------------- |
996
+ | `items` | `DescriptionListItem[]` | — | The rows, in order (label/value/description are THC) |
997
+ | `children` | `Snippet` | — | Rendered inside the `<dl>` _instead of_ `items` |
998
+ | `layout` | `"auto" \| "stacked" \| "columns"` | `"auto"` | Label over value, beside it, or the former until the list is `columnsFrom` wide |
999
+ | `columnsFrom` | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` | `"sm"` | The **list's own** width at which `"auto"` flips. Dropped unless `layout="auto"` |
1000
+ | `divide` | `"none" \| "inside" \| "outside"` | `"inside"` | n−1 rules (list in a box), n+1 (list loose on a page), or none |
1001
+ | `wrap` | `"anywhere" \| "truncate" \| "normal"` | `"anywhere"` | Long-value behavior; per-row override via `item.wrap` |
1002
+ | `valueAlign` | `"start" \| "end"` | `"start"` | `"end"` is the totals shape (pair with `--stuic-description-list-label-width: 1fr`) |
1003
+ | `emptyValue` | `THC` | `"—"` | Used when `value` is `undefined`/`null`/`""` — `0` is a value, not empty |
1004
+
1005
+ Snippets (all receive `{ item, index }`): `renderLabel`, `renderValue`, `renderItem` (the row `<div>` stays — it is what the grid is on). Class slots: `class`, `classItem`, `classLabel`, `classValue`, `classDescription`, each also available per item. Empty `items` with no `children` renders **nothing**, not an empty `<dl>`.
1006
+
1007
+ ### Why a container query
1008
+
1009
+ `layout="auto"` makes the `<dl>` a named inline-size container (`stuic-description-list`) and flips its **rows** to a two-column grid at `columnsFrom` — an element cannot query itself. The viewport answers a different question: a 200px-wide list inside a 1400px page must stay stacked, and `sm:` would have flipped it. The breakpoint is a prop on a fixed scale (Tailwind's `@xs`…`@xl`) and not a token because `@container (min-width: var(--x))` is not valid CSS — size queries take literals. Side effect worth knowing: `container-type: inline-size` gives the list inline-axis containment, so its min-content contribution to a flex parent is zero (the list can sit beside a fixed-width element and shrink into what is left; `min-w-0` is already there in the static layouts too).
1010
+
1011
+ ### Row-level contract (works in both forms)
1012
+
1013
+ `data-wrap="anywhere|truncate|normal"` and `data-emphasis` on a row `<div>` are honoured by the same selectors the data form uses, so `children` rows opt into per-row wrapping and the _Total_ look without a prop. Under `wrap="truncate"` a plain-string value auto-fills the `<dd>`'s `title` (a clipped value must stay reachable); pass `item.title` for html/component/snippet values.
1014
+
1015
+ ### CSS Tokens
1016
+
1017
+ Prefix: `--stuic-description-list-*`
1018
+
1019
+ `label-width` (not declared — fallback `minmax(7rem, 10rem)` at the usage site), `gap-x`, `gap-y`, `item-padding-y`, `rule-color`, `rule-width`, `label-font-size`, `label-font-weight`, `label-text`, `value-text`, `description-font-size`, `description-text`, `label-text-emphasis`, `value-font-weight-emphasis`
1020
+
1021
+ ---
1022
+
973
1023
  ## EmptyState
974
1024
 
975
1025
  Icon + title + description + CTA placeholder for empty lists, tables, and search results. Centered, non-interactive; the CTA area is a snippet filled with consumer `Button`s/links. Pairs naturally with `DataTable` ("no rows") and search UIs ("no results").
@@ -186,39 +186,55 @@ const css = generateThemeCss(custom, "stuic-");
186
186
 
187
187
  Global tokens defined in `src/lib/index.css` that control cross-component visual properties. Override these to change the entire library's visual character:
188
188
 
189
- | Token | Default | Purpose |
190
- | -------------------------- | ------------------ | ------------------------------------------------- |
191
- | `--stuic-radius` | `var(--radius-md)` | Element-level radius (buttons, inputs, badges) |
192
- | `--stuic-radius-container` | `var(--radius-lg)` | Container-level radius (cards, modals, dropdowns) |
193
- | `--stuic-shadow` | `var(--shadow-sm)` | Default resting shadow |
194
- | `--stuic-shadow-hover` | `var(--shadow-md)` | Hover/elevated shadow |
195
- | `--stuic-shadow-overlay` | `var(--shadow-lg)` | Overlays (dropdowns, notifications) |
196
- | `--stuic-shadow-dialog` | `var(--shadow-xl)` | Dialogs/modals |
197
- | `--stuic-border-width` | `1px` | Default border width |
198
- | `--stuic-transition` | `150ms` | Default transition duration |
189
+ | Token | Default | Purpose |
190
+ | ----------------------------- | ------------------ | ----------------------------------------------------------- |
191
+ | `--stuic-radius` | `var(--radius-md)` | Element-level radius (inputs, badges, list items) |
192
+ | `--stuic-radius-button` | `var(--radius-md)` | Button-level radius (buttons, split buttons, button groups) |
193
+ | `--stuic-radius-container` | `var(--radius-lg)` | Container-level radius (cards, modals, dropdowns) |
194
+ | `--stuic-shadow` | `var(--shadow-sm)` | Default resting shadow |
195
+ | `--stuic-shadow-hover` | `var(--shadow-md)` | Hover/elevated shadow |
196
+ | `--stuic-shadow-overlay` | `var(--shadow-lg)` | Overlays (dropdowns, notifications) |
197
+ | `--stuic-shadow-dialog` | `var(--shadow-xl)` | Dialogs/modals |
198
+ | `--stuic-border-width` | `1px` | Default border width |
199
+ | `--stuic-border-width-button` | `1px` | Button border width (independent from general elements) |
200
+ | `--stuic-transition` | `150ms` | Default transition duration |
201
+
202
+ Radius and border-width come in **three tiers** — elements, buttons, containers — so a
203
+ theme can flatten inputs while keeping pill buttons, or vice versa. See
204
+ [Conventions: Element vs Button vs Container](../conventions.md#element-vs-button-vs-container).
199
205
 
200
206
  Example — brutalist style in 7 lines:
201
207
 
202
208
  ```css
203
209
  :root {
204
210
  --stuic-radius: 0;
211
+ --stuic-radius-button: 0;
205
212
  --stuic-radius-container: 0;
206
213
  --stuic-shadow: none;
207
214
  --stuic-shadow-hover: none;
208
215
  --stuic-shadow-overlay: none;
209
216
  --stuic-shadow-dialog: none;
210
217
  --stuic-border-width: 0;
218
+ --stuic-border-width-button: 0;
211
219
  }
212
220
  ```
213
221
 
214
- Components reference these via the fallback pattern (not `:root` declarations):
222
+ Components reference these via the fallback pattern (not `:root` declarations) — the
223
+ per-component token first, the shared tier token as its fallback:
215
224
 
216
225
  ```css
217
226
  .stuic-button {
218
- border-radius: var(--stuic-button-radius, var(--stuic-radius));
227
+ border-radius: var(--stuic-button-radius, var(--stuic-radius-button));
228
+ /* ^ component token ^ shared tier token */
219
229
  }
220
230
  ```
221
231
 
232
+ > **Note the mirrored names.** `--stuic-radius-button` is the shared tier token (declared
233
+ > in `src/lib/index.css`, applies to every button-family component);
234
+ > `--stuic-button-radius` is the `Button` component's own override token (never declared —
235
+ > it only ever appears as the first argument of a `var()` fallback). Same for
236
+ > `--stuic-border-width-button` vs `--stuic-button-border-width`.
237
+
222
238
  ---
223
239
 
224
240
  ## Component Tokens
@@ -239,7 +255,7 @@ Override per-component tokens globally:
239
255
 
240
256
  ```css
241
257
  :root {
242
- --stuic-button-radius: 9999px; /* Pill buttons — overrides the shared fallback */
258
+ --stuic-button-radius: 9999px; /* Pill buttons — overrides --stuic-radius-button */
243
259
  }
244
260
  ```
245
261
 
@@ -251,6 +267,34 @@ Override locally:
251
267
 
252
268
  Some token sets belong to a CSS-only preset rather than to a component — e.g. `--stuic-frame-*` (ratio-locked frame / letterbox). Those are consumer **inputs** that stuic never declares; see [CSS presets](./css-presets.md).
253
269
 
270
+ ### Pitfall: never pin a nested component's tokens inline
271
+
272
+ When a component renders another stuic component internally and wants to restyle it, the
273
+ opinionated values must go in the outer component's `index.css`, behind its own
274
+ `--stuic-{outer}-{part}-*` tokens — never into the inner component's inline `style`
275
+ attribute:
276
+
277
+ ```svelte
278
+ <!-- WRONG — an inline custom property outranks every :root declaration for that
279
+ element and its subtree, so no theme can reach the inner component -->
280
+ <ButtonGroupRadio style="--stuic-button-group-radius: 9999px;" />
281
+
282
+ <!-- RIGHT — the class carries the look, and `style` stays the caller's escape hatch -->
283
+ <ButtonGroupRadio class="stuic-pricing-table-toggle" style={styleToggle} />
284
+ ```
285
+
286
+ ```css
287
+ .stuic-pricing-table-toggle {
288
+ --stuic-button-group-radius: var(--stuic-pricing-table-toggle-radius, 9999px);
289
+ }
290
+ ```
291
+
292
+ No specificity or `@layer` trick outranks an inline declaration, so the inline form makes
293
+ that part of the component permanently unthemeable. `PricingTable`'s billing toggle is the
294
+ worked example. Note the trade: whatever the outer component declares on the element is no
295
+ longer reachable through the _inner_ component's global token, so document the
296
+ `--stuic-{outer}-*` replacement in the outer component's README.
297
+
254
298
  ---
255
299
 
256
300
  ## Key Files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.173.0",
3
+ "version": "3.175.0",
4
4
  "packageManager": "pnpm@11.5.0",
5
5
  "scripts": {
6
6
  "dev": "vite dev",