@spaethtech/svelte-ui 0.5.1-dev.24.9a705e3 → 0.5.1-dev.27.5011e8e

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.
@@ -99,7 +99,10 @@ surfaces through the **native validity tooltip** (shown on form submit); the con
99
99
  prop is the inline row. Both flip the field's error border.
100
100
 
101
101
  **Groups:** for a set of radios/checkboxes/toggles under one legend, wrap them in **`FieldGroup`**
102
- (same `label`/`description`/`error`/`required` vocab on a `<fieldset>`, plus `orientation`). Inside a
102
+ (same `label`/`description`/`error`/`required` vocab on a `<fieldset>`, plus `columns` a number or
103
+ responsive map; default 1 = stacked, higher lays items across equal auto-placed columns — and `gap`,
104
+ a number or `{ x, y }`). For non-field content, **`Grid`** is the same auto-placement as a plain
105
+ primitive (`columns` + `gap`, any children). Inside a
103
106
  group, item labels render **inline** (right of the control). For a radio group, `bind:value` on the
104
107
  `FieldGroup` (+ optional `name`) and the child `<Radio>`s share it automatically — no `bind:group`:
105
108
 
@@ -161,7 +164,8 @@ examples):
161
164
  · `Popup` `Menu` · `tooltip` (a Svelte `use:` action) · `anchored` (positioning engine)
162
165
  - **Navigation:** `TabStrip` `SideBarMenu`
163
166
  - **Feedback:** `Alert` `Banner` `Badge` `Toaster` + `toast`
164
- - **Layout:** `Card` `CardHeader` `CardBody` `CardFooter`
167
+ - **Layout:** `Card` `CardHeader` `CardBody` `CardFooter` · **`Grid`** (auto-placed equal-cell grid;
168
+ `columns` + `gap`)
165
169
  - **Display / theme:** `NotesEditor` · `ThemeSelector` (Auto/Light/Dark Select) · `ThemeToggle`
166
170
  (compact ☀/☾ icon button, same persisted state)
167
171
  - **Types / utils:** `Variant` `Size` `TabDefinition` (types), `responsiveClasses` / `resolveScalar`,
@@ -15,7 +15,7 @@
15
15
  import type { Variant } from "../types/variants.js";
16
16
  import type { Responsive } from "../types/responsive.js";
17
17
  import FieldsetChrome from "./FieldsetChrome.svelte";
18
- import { setFieldGroup } from "./field-group.js";
18
+ import { setFieldGroup, type Columns, type Gap } from "./field-group.js";
19
19
 
20
20
  let {
21
21
  value = $bindable(),
@@ -25,7 +25,8 @@
25
25
  error = null,
26
26
  description = null,
27
27
  required = false,
28
- orientation = "vertical",
28
+ columns = 1,
29
+ gap,
29
30
  size = "md",
30
31
  variant,
31
32
  disabled = false,
@@ -41,7 +42,10 @@
41
42
  error?: string | Snippet | null;
42
43
  description?: string | Snippet | null;
43
44
  required?: boolean;
44
- orientation?: "vertical" | "horizontal";
45
+ /** Grid track count (default 1 = stacked vertical). Set >1 (or a responsive map) for columns. */
46
+ columns?: Columns;
47
+ /** Cell gap. Default `{ x: 6, y: 1 }` (wide columns, tight rows). Number = symmetric. */
48
+ gap?: Gap;
45
49
  size?: Responsive<Size>;
46
50
  variant?: Variant;
47
51
  disabled?: boolean;
@@ -49,13 +53,11 @@
49
53
  children: Snippet;
50
54
  } = $props();
51
55
 
52
- // Reactive context: getters keep size/variant/orientation/disabled live; getValue/select share the
53
- // radio-group value. Set once (context is stable); the getters read current prop values.
56
+ // Reactive context: getters keep size/variant/disabled live; getValue/select share the radio-group
57
+ // value. Set once (context is stable); the getters read current prop values. Layout is the grid's
58
+ // job (columns), so items need no orientation awareness.
54
59
  setFieldGroup({
55
60
  inGroup: true,
56
- get orientation() {
57
- return orientation;
58
- },
59
61
  get size() {
60
62
  return size;
61
63
  },
@@ -75,6 +77,6 @@
75
77
  });
76
78
  </script>
77
79
 
78
- <FieldsetChrome {label} {aside} {error} {description} {required} {orientation} {size} class={cls}>
80
+ <FieldsetChrome {label} {aside} {error} {description} {required} {columns} {gap} {size} class={cls}>
79
81
  {@render children()}
80
82
  </FieldsetChrome>
@@ -2,6 +2,7 @@ import type { Snippet } from "svelte";
2
2
  import type { Size } from "../types/sizes.js";
3
3
  import type { Variant } from "../types/variants.js";
4
4
  import type { Responsive } from "../types/responsive.js";
5
+ import { type Columns, type Gap } from "./field-group.js";
5
6
  type $$ComponentProps = {
6
7
  /** Radio groups: the selected value (bindable). Ignored by checkbox/toggle children. */
7
8
  value?: string | number;
@@ -12,7 +13,10 @@ type $$ComponentProps = {
12
13
  error?: string | Snippet | null;
13
14
  description?: string | Snippet | null;
14
15
  required?: boolean;
15
- orientation?: "vertical" | "horizontal";
16
+ /** Grid track count (default 1 = stacked vertical). Set >1 (or a responsive map) for columns. */
17
+ columns?: Columns;
18
+ /** Cell gap. Default `{ x: 6, y: 1 }` (wide columns, tight rows). Number = symmetric. */
19
+ gap?: Gap;
16
20
  size?: Responsive<Size>;
17
21
  variant?: Variant;
18
22
  disabled?: boolean;
@@ -2,11 +2,12 @@
2
2
  import type { Snippet } from "svelte";
3
3
  import type { Size } from "../types/sizes.js";
4
4
  import { responsiveClasses, type Responsive } from "../types/responsive.js";
5
+ import { gridColsClass, gapClass, type Columns, type Gap } from "./field-group.js";
5
6
 
6
7
  /**
7
8
  * FieldsetChrome — the group counterpart of FieldChrome: a `<fieldset>` with a `<legend>` (the
8
- * group label) plus the same error/description rows, and an orientation-controlled slot for its
9
- * child controls. Used by `RadioGroup` / `FieldGroup`. Same prop vocabulary as FieldChrome.
9
+ * group label) plus the same error/description rows, and a `columns`-driven CSS-grid slot. Child
10
+ * controls are auto-placed into cells (no per-child prop). Used by `FieldGroup`.
10
11
  */
11
12
  interface Props {
12
13
  label?: string | null;
@@ -14,7 +15,10 @@
14
15
  error?: string | Snippet | null;
15
16
  description?: string | Snippet | null;
16
17
  required?: boolean;
17
- orientation?: "vertical" | "horizontal";
18
+ /** Grid track count (default 1 = stacked). Items auto-flow into equal cells. */
19
+ columns?: Columns;
20
+ /** Cell gap. Default `{ x: 6, y: 1 }` — wide columns, tight rows (the field-list look). */
21
+ gap?: Gap;
18
22
  size?: Responsive<Size>;
19
23
  class?: string;
20
24
  children: Snippet;
@@ -26,7 +30,8 @@
26
30
  error = null,
27
31
  description = null,
28
32
  required = false,
29
- orientation = "vertical",
33
+ columns = 1,
34
+ gap,
30
35
  size = "md",
31
36
  class: cls = "",
32
37
  children,
@@ -41,9 +46,9 @@
41
46
  const hasError = $derived(isSnippet(error) ? true : !!(error && String(error).trim()));
42
47
  const hasDesc = $derived(isSnippet(description) ? true : !!(description && String(description).trim()));
43
48
 
44
- const itemsClass = $derived(
45
- orientation === "horizontal" ? "flex flex-wrap gap-x-6 gap-y-2" : "flex flex-col gap-2",
46
- );
49
+ // Auto-placed CSS grid: each direct child (a Radio/Checkbox/Toggle label) is one cell — no
50
+ // per-child prop. `columns` sets the equal-width tracks; `gap` is the single density knob.
51
+ const itemsClass = $derived(`grid ${gridColsClass(columns)} ${gapClass(gap, { x: 6, y: 1 })}`);
47
52
  </script>
48
53
 
49
54
  <fieldset class="m-0 flex min-w-0 flex-col gap-1 border-0 p-0 {cls}">
@@ -1,10 +1,11 @@
1
1
  import type { Snippet } from "svelte";
2
2
  import type { Size } from "../types/sizes.js";
3
3
  import { type Responsive } from "../types/responsive.js";
4
+ import { type Columns, type Gap } from "./field-group.js";
4
5
  /**
5
6
  * FieldsetChrome — the group counterpart of FieldChrome: a `<fieldset>` with a `<legend>` (the
6
- * group label) plus the same error/description rows, and an orientation-controlled slot for its
7
- * child controls. Used by `RadioGroup` / `FieldGroup`. Same prop vocabulary as FieldChrome.
7
+ * group label) plus the same error/description rows, and a `columns`-driven CSS-grid slot. Child
8
+ * controls are auto-placed into cells (no per-child prop). Used by `FieldGroup`.
8
9
  */
9
10
  interface Props {
10
11
  label?: string | null;
@@ -12,7 +13,10 @@ interface Props {
12
13
  error?: string | Snippet | null;
13
14
  description?: string | Snippet | null;
14
15
  required?: boolean;
15
- orientation?: "vertical" | "horizontal";
16
+ /** Grid track count (default 1 = stacked). Items auto-flow into equal cells. */
17
+ columns?: Columns;
18
+ /** Cell gap. Default `{ x: 6, y: 1 }` — wide columns, tight rows (the field-list look). */
19
+ gap?: Gap;
16
20
  size?: Responsive<Size>;
17
21
  class?: string;
18
22
  children: Snippet;
@@ -0,0 +1,34 @@
1
+ <!--
2
+ /**
3
+ * Grid — a minimal auto-placed CSS-grid layout primitive. Sets `grid grid-cols-{columns}` and lets
4
+ * every direct child flow into an equal cell (no per-child prop). Works with any content — Cards,
5
+ * Buttons, arbitrary markup. `columns` is responsive; `gap` is a single symmetric step or `{ x, y }`.
6
+ *
7
+ * Uniform cells only (by design). For variable-width layouts, use a bespoke grid + `col-span-*` on
8
+ * the cells yourself. `FieldGroup` is the field-specific sibling (adds legend/error chrome).
9
+ */
10
+ -->
11
+ <script lang="ts">
12
+ import type { Snippet } from "svelte";
13
+ import { gridColsClass, gapClass, type Columns, type Gap } from "./field-group.js";
14
+
15
+ let {
16
+ columns = 1,
17
+ gap = 4,
18
+ class: cls = "",
19
+ children,
20
+ }: {
21
+ /** Track count — a number (1–6) or a responsive map like `{ base: 1, md: 3 }`. Default 1. */
22
+ columns?: Columns;
23
+ /** Cell gap — a Tailwind step (symmetric) or `{ x, y }` per-axis. Default 4. */
24
+ gap?: Gap;
25
+ class?: string;
26
+ children: Snippet;
27
+ } = $props();
28
+
29
+ const gridClass = $derived(`grid ${gridColsClass(columns)} ${gapClass(gap, 4)}`);
30
+ </script>
31
+
32
+ <div class="{gridClass} {cls}">
33
+ {@render children()}
34
+ </div>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from "svelte";
2
+ import { type Columns, type Gap } from "./field-group.js";
3
+ type $$ComponentProps = {
4
+ /** Track count — a number (1–6) or a responsive map like `{ base: 1, md: 3 }`. Default 1. */
5
+ columns?: Columns;
6
+ /** Cell gap — a Tailwind step (symmetric) or `{ x, y }` per-axis. Default 4. */
7
+ gap?: Gap;
8
+ class?: string;
9
+ children: Snippet;
10
+ };
11
+ declare const Grid: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type Grid = ReturnType<typeof Grid>;
13
+ export default Grid;
@@ -1,17 +1,31 @@
1
+ import type { Breakpoint } from "../types/breakpoints.js";
1
2
  import type { Size } from "../types/sizes.js";
2
3
  import type { Variant } from "../types/variants.js";
3
- import type { Responsive } from "../types/responsive.js";
4
+ import { type Responsive } from "../types/responsive.js";
4
5
  export interface FieldGroupContext {
5
6
  /** Present ⇒ the item is inside a group and renders its label inline (right of the control). */
6
7
  inGroup: true;
7
- orientation: "vertical" | "horizontal";
8
8
  size?: Responsive<Size>;
9
9
  variant?: Variant;
10
10
  disabled?: boolean;
11
- /** RadioGroup only — the shared radio-group name + controlled value. */
11
+ /** Radio groups only — the shared radio-group name + controlled value. */
12
12
  name?: string;
13
13
  getValue?: () => string | number | undefined;
14
14
  select?: (v: string | number) => void;
15
15
  }
16
+ /**
17
+ * FieldGroup `columns`: a track count (1–6) as a scalar or a per-breakpoint map. `1` (default) = a
18
+ * stacked vertical list; anything higher lays the auto-placed items across that many equal columns.
19
+ */
20
+ export type Columns = number | Partial<Record<Breakpoint, number>>;
21
+ /** Resolve a `Columns` value to `grid-cols-*` classes (responsive-aware). */
22
+ export declare function gridColsClass(columns: Columns | undefined): string;
23
+ /** Grid gap: a single Tailwind gap step (symmetric) or `{ x, y }` for per-axis (column/row) gaps. */
24
+ export type Gap = number | {
25
+ x?: number;
26
+ y?: number;
27
+ };
28
+ /** Resolve a `Gap` (or the fallback when unset) to `gap-*` / `gap-x-* gap-y-*` classes. */
29
+ export declare function gapClass(gap: Gap | undefined, fallback: Gap): string;
16
30
  export declare function setFieldGroup(ctx: FieldGroupContext): void;
17
31
  export declare function getFieldGroup(): FieldGroupContext | undefined;
@@ -5,6 +5,48 @@
5
5
  * the radio-group value + `name` so a child `<Radio>` needs no `bind:group`.
6
6
  */
7
7
  import { getContext, setContext } from "svelte";
8
+ import { responsiveClasses } from "../types/responsive.js";
9
+ // Static literal map so Tailwind's scanner emits the base classes; the bp:-prefixed variants are
10
+ // safelisted in theme.css (`@source inline(... grid-cols-{1..6})`).
11
+ const COLS_MAP = {
12
+ "1": "grid-cols-1",
13
+ "2": "grid-cols-2",
14
+ "3": "grid-cols-3",
15
+ "4": "grid-cols-4",
16
+ "5": "grid-cols-5",
17
+ "6": "grid-cols-6",
18
+ };
19
+ /** Resolve a `Columns` value to `grid-cols-*` classes (responsive-aware). */
20
+ export function gridColsClass(columns) {
21
+ if (columns == null)
22
+ return COLS_MAP["1"];
23
+ const value = typeof columns === "number"
24
+ ? String(columns)
25
+ : Object.fromEntries(Object.entries(columns).map(([bp, n]) => [bp, String(n)]));
26
+ return responsiveClasses(value, COLS_MAP) || COLS_MAP["1"];
27
+ }
28
+ // Static literal maps (Tailwind-scannable via the shipped dist). Non-responsive — one gap per grid.
29
+ const GAP = {
30
+ "0": "gap-0", "1": "gap-1", "1.5": "gap-1.5", "2": "gap-2", "3": "gap-3",
31
+ "4": "gap-4", "5": "gap-5", "6": "gap-6", "8": "gap-8",
32
+ };
33
+ const GAP_X = {
34
+ "0": "gap-x-0", "1": "gap-x-1", "1.5": "gap-x-1.5", "2": "gap-x-2", "3": "gap-x-3",
35
+ "4": "gap-x-4", "5": "gap-x-5", "6": "gap-x-6", "8": "gap-x-8",
36
+ };
37
+ const GAP_Y = {
38
+ "0": "gap-y-0", "1": "gap-y-1", "1.5": "gap-y-1.5", "2": "gap-y-2", "3": "gap-y-3",
39
+ "4": "gap-y-4", "5": "gap-y-5", "6": "gap-y-6", "8": "gap-y-8",
40
+ };
41
+ /** Resolve a `Gap` (or the fallback when unset) to `gap-*` / `gap-x-* gap-y-*` classes. */
42
+ export function gapClass(gap, fallback) {
43
+ const g = gap ?? fallback;
44
+ if (typeof g === "number")
45
+ return GAP[String(g)] ?? "";
46
+ return [g.x != null ? GAP_X[String(g.x)] : "", g.y != null ? GAP_Y[String(g.y)] : ""]
47
+ .filter(Boolean)
48
+ .join(" ");
49
+ }
8
50
  const KEY = Symbol("svelte-ui-field-group");
9
51
  export function setFieldGroup(ctx) {
10
52
  setContext(KEY, ctx);
package/dist/index.d.ts CHANGED
@@ -35,6 +35,8 @@ export { default as Checkbox } from "./components/Checkbox.svelte";
35
35
  export { default as Toggle } from "./components/Toggle.svelte";
36
36
  export { default as Radio } from "./components/Radio.svelte";
37
37
  export { default as FieldGroup } from "./components/FieldGroup.svelte";
38
+ export { default as Grid } from "./components/Grid.svelte";
39
+ export type { Columns, Gap } from "./components/field-group.js";
38
40
  export { default as SideBarMenu } from "./components/SideBarMenu/SideBarMenu.svelte";
39
41
  export type { SideBarMenuItem, SideBarMenuBadge, SideBarMenuBadgeVariant, } from "./components/SideBarMenu/SideBarMenu.svelte";
40
42
  export { default as TabStrip } from "./components/TabStrip/TabStrip.svelte";
package/dist/index.js CHANGED
@@ -40,6 +40,7 @@ export { default as Checkbox } from "./components/Checkbox.svelte";
40
40
  export { default as Toggle } from "./components/Toggle.svelte";
41
41
  export { default as Radio } from "./components/Radio.svelte";
42
42
  export { default as FieldGroup } from "./components/FieldGroup.svelte";
43
+ export { default as Grid } from "./components/Grid.svelte";
43
44
  // Navigation Components
44
45
  export { default as SideBarMenu } from "./components/SideBarMenu/SideBarMenu.svelte";
45
46
  export { default as TabStrip } from "./components/TabStrip/TabStrip.svelte";
package/dist/theme.css CHANGED
@@ -203,3 +203,5 @@ body {
203
203
  @source inline("{,4xs:,3xs:,2xs:,xs:,sm:,md:,lg:,xl:,2xl:,3xl:}gap-{1.5,2,3}");
204
204
  @source inline("{,4xs:,3xs:,2xs:,xs:,sm:,md:,lg:,xl:,2xl:,3xl:}text-{xs,sm,base,lg,2xl}");
205
205
  @source inline("{,4xs:,3xs:,2xs:,xs:,sm:,md:,lg:,xl:,2xl:,3xl:}[&_svg]:{w,h}-{3.5,4,5,6}");
206
+ /* FieldGroup `columns` → responsive grid tracks (auto-placed cells). */
207
+ @source inline("{,4xs:,3xs:,2xs:,xs:,sm:,md:,lg:,xl:,2xl:,3xl:}grid-cols-{1,2,3,4,5,6}");
@@ -129,9 +129,20 @@ One wrapper for a set of Radios, Checkboxes, or Toggles under a shared legend (a
129
129
 
130
130
  - **Location**: `src/lib/components/FieldGroup.svelte`
131
131
  - **Props**: `label` (legend), `aside`, `description`, `error`, `required` (the shared field-chrome
132
- vocabulary), `orientation` (`'vertical'` default | `'horizontal'`), `size`, `variant`, `disabled`
133
- (propagate to children), and for radio groups `value` (bindable) + `name`. Children render their
134
- label inline; radios auto-share the group value/name via context.
132
+ vocabulary), `columns` (`number | { base, md, … }`, default `1` = stacked; higher = that many
133
+ equal auto-placed columns), `gap` (`number | { x, y }`, default `{ x: 6, y: 1 }`), `size`,
134
+ `variant`, `disabled` (propagate to children), and for radio groups `value` (bindable) + `name`.
135
+ Children render their label inline; radios auto-share the group value/name via context.
136
+
137
+ ### Grid
138
+
139
+ Minimal auto-placed CSS-grid layout primitive — every direct child flows into an equal cell (no
140
+ per-child prop). Works with any content. Uniform cells only (for variable widths, use your own grid +
141
+ `col-span-*`).
142
+
143
+ - **Location**: `src/lib/components/Grid.svelte`
144
+ - **Props**: `columns` (`number | { base, md, … }`, 1–6, default `1`), `gap` (`number | { x, y }`,
145
+ default `4`), `class`, `children`. Shares `gridColsClass`/`gapClass` with `FieldGroup`.
135
146
 
136
147
  ### Rating
137
148
 
package/docs/usage.md CHANGED
@@ -135,12 +135,31 @@ inline. For radios, bind the group `value` — children need no `bind:group`.
135
135
  <Radio value="push" label="Push notification" />
136
136
  </FieldGroup>
137
137
 
138
- <FieldGroup label="Capabilities" orientation="horizontal">
138
+ <FieldGroup label="Capabilities" columns={2}>
139
139
  <Checkbox label="Webhook triggers" bind:checked={caps.webhook} />
140
140
  <Checkbox label="Script actions" bind:checked={caps.script} />
141
141
  </FieldGroup>
142
142
  ```
143
143
 
144
+ ### Grid
145
+
146
+ Auto-placed layout — direct children flow into equal cells, no per-child prop. `columns` is
147
+ responsive; `gap` is symmetric (a number) or per-axis (`{ x, y }`).
148
+
149
+ ```svelte
150
+ <script>
151
+ import { Grid, Card } from "@spaethtech/svelte-ui";
152
+ </script>
153
+
154
+ <Grid columns={{ base: 1, sm: 2, lg: 3 }} gap={4}>
155
+ <Card>One</Card>
156
+ <Card>Two</Card>
157
+ <Card>Three</Card>
158
+ </Grid>
159
+ ```
160
+
161
+ For variable-width cells, use your own grid + `col-span-*`; `Grid` is uniform cells by design.
162
+
144
163
  ### Button
145
164
 
146
165
  ```svelte
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaethtech/svelte-ui",
3
- "version": "0.5.1-dev.24.9a705e3",
3
+ "version": "0.5.1-dev.27.5011e8e",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/spaethtech/svelte-ui.git"