@spaethtech/svelte-ui 0.5.1-dev.28.7f45387 → 0.6.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/.claude/skills/svelte-ui/SKILL.md +3 -0
- package/dist/components/Accordion.svelte +69 -0
- package/dist/components/Accordion.svelte.d.ts +20 -0
- package/dist/components/Disclosure.svelte +115 -0
- package/dist/components/Disclosure.svelte.d.ts +25 -0
- package/dist/components/disclosure.d.ts +13 -0
- package/dist/components/disclosure.js +13 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/docs/components.md +21 -0
- package/docs/usage.md +28 -0
- package/package.json +1 -1
|
@@ -166,6 +166,9 @@ examples):
|
|
|
166
166
|
- **Feedback:** `Alert` `Banner` `Badge` `Toaster` + `toast`
|
|
167
167
|
- **Layout:** `Card` `CardHeader` `CardBody` `CardFooter` · **`Grid`** (auto-placed equal-cell grid;
|
|
168
168
|
`columns` + `gap`)
|
|
169
|
+
- **Disclosure:** **`Disclosure`** (one expand/collapse section; `title`/`header`, `bind:open`, panel
|
|
170
|
+
stays mounted so state is preserved) · **`Accordion`** (coordinated group — `multiple` single/multi,
|
|
171
|
+
`bind:value`; children are `<Disclosure value="…">`, no per-item `bind:open`)
|
|
169
172
|
- **Display / theme:** `NotesEditor` · `ThemeSelector` (Auto/Light/Dark Select) · `ThemeToggle`
|
|
170
173
|
(compact ☀/☾ icon button, same persisted state)
|
|
171
174
|
- **Types / utils:** `Variant` `Size` `TabDefinition` (types), `responsiveClasses` / `resolveScalar`,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* Accordion — groups `Disclosure`s under coordinated open/close rules. `multiple={false}` (default) is
|
|
4
|
+
* single-open (opening one closes the rest); `multiple` lets each toggle independently. Bind `value`
|
|
5
|
+
* (a string for single, string[] for multiple); child `<Disclosure value="…">`s report through
|
|
6
|
+
* context — no per-item `bind:open`. `size`/`variant`/`disabled` propagate to children.
|
|
7
|
+
*/
|
|
8
|
+
-->
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import type { Snippet } from "svelte";
|
|
11
|
+
import type { Size } from "../types/sizes.js";
|
|
12
|
+
import type { Variant } from "../types/variants.js";
|
|
13
|
+
import type { Responsive } from "../types/responsive.js";
|
|
14
|
+
import { setDisclosureGroup } from "./disclosure.js";
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
multiple = false,
|
|
18
|
+
value = $bindable(),
|
|
19
|
+
collapsible = true,
|
|
20
|
+
size = "md",
|
|
21
|
+
variant,
|
|
22
|
+
disabled = false,
|
|
23
|
+
class: cls = "",
|
|
24
|
+
children,
|
|
25
|
+
}: {
|
|
26
|
+
/** false (default) = single-open; true = independent multi-open. */
|
|
27
|
+
multiple?: boolean;
|
|
28
|
+
/** Open key (single) or keys (multiple). Bindable. */
|
|
29
|
+
value?: string | string[];
|
|
30
|
+
/** Single mode: may the open panel be closed to none? Default true. */
|
|
31
|
+
collapsible?: boolean;
|
|
32
|
+
size?: Responsive<Size>;
|
|
33
|
+
variant?: Variant;
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
class?: string;
|
|
36
|
+
children: Snippet;
|
|
37
|
+
} = $props();
|
|
38
|
+
|
|
39
|
+
setDisclosureGroup({
|
|
40
|
+
inGroup: true,
|
|
41
|
+
isOpen(v) {
|
|
42
|
+
return multiple ? Array.isArray(value) && value.includes(v) : value === v;
|
|
43
|
+
},
|
|
44
|
+
toggle(v) {
|
|
45
|
+
if (multiple) {
|
|
46
|
+
const arr = Array.isArray(value) ? value : [];
|
|
47
|
+
value = arr.includes(v) ? arr.filter((x) => x !== v) : [...arr, v];
|
|
48
|
+
} else {
|
|
49
|
+
// open a new panel, or (if re-clicking the open one) close it when collapsible
|
|
50
|
+
value = value === v ? (collapsible ? "" : v) : v;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
get size() {
|
|
54
|
+
return size;
|
|
55
|
+
},
|
|
56
|
+
get variant() {
|
|
57
|
+
return variant;
|
|
58
|
+
},
|
|
59
|
+
get disabled() {
|
|
60
|
+
return disabled;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<div
|
|
66
|
+
class="flex flex-col [&>*:not(:last-child)]:border-b [&>*]:[border-color:var(--ui-border-color)] {cls}"
|
|
67
|
+
>
|
|
68
|
+
{@render children()}
|
|
69
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { Size } from "../types/sizes.js";
|
|
3
|
+
import type { Variant } from "../types/variants.js";
|
|
4
|
+
import type { Responsive } from "../types/responsive.js";
|
|
5
|
+
type $$ComponentProps = {
|
|
6
|
+
/** false (default) = single-open; true = independent multi-open. */
|
|
7
|
+
multiple?: boolean;
|
|
8
|
+
/** Open key (single) or keys (multiple). Bindable. */
|
|
9
|
+
value?: string | string[];
|
|
10
|
+
/** Single mode: may the open panel be closed to none? Default true. */
|
|
11
|
+
collapsible?: boolean;
|
|
12
|
+
size?: Responsive<Size>;
|
|
13
|
+
variant?: Variant;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
class?: string;
|
|
16
|
+
children: Snippet;
|
|
17
|
+
};
|
|
18
|
+
declare const Accordion: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
19
|
+
type Accordion = ReturnType<typeof Accordion>;
|
|
20
|
+
export default Accordion;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* Disclosure — a single expand/collapse section: a `<Button>` header (title + rotating chevron) over
|
|
4
|
+
* a content panel. Standalone it owns `bind:open`; inside an `Accordion` it reports to the group via
|
|
5
|
+
* context (keyed by `value`) and the group owns which panel(s) are open.
|
|
6
|
+
*
|
|
7
|
+
* The panel animates with `grid-template-rows: 0fr → 1fr` (smooth for unknown heights, pure CSS), and
|
|
8
|
+
* stays MOUNTED when closed — panel state is preserved. Collapsed, it's `inert` + `aria-hidden` (out
|
|
9
|
+
* of the tab order + a11y tree). The transition is `motion-safe:` (reduced-motion → instant).
|
|
10
|
+
*/
|
|
11
|
+
-->
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import type { Snippet } from "svelte";
|
|
14
|
+
import ChevronDown from "~icons/mdi/chevron-down";
|
|
15
|
+
import type { Size } from "../types/sizes.js";
|
|
16
|
+
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
17
|
+
import type { Variant } from "../types/variants.js";
|
|
18
|
+
import Button from "./Button.svelte";
|
|
19
|
+
import { nextFieldId } from "./FieldChrome.svelte";
|
|
20
|
+
import { getDisclosureGroup } from "./disclosure.js";
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
title = null,
|
|
24
|
+
header,
|
|
25
|
+
open = $bindable(false),
|
|
26
|
+
value,
|
|
27
|
+
disabled = false,
|
|
28
|
+
size,
|
|
29
|
+
variant,
|
|
30
|
+
icon,
|
|
31
|
+
id,
|
|
32
|
+
class: cls = "",
|
|
33
|
+
children,
|
|
34
|
+
}: {
|
|
35
|
+
/** Header text. Use the `header` snippet instead for full custom header content. */
|
|
36
|
+
title?: string | null;
|
|
37
|
+
header?: Snippet;
|
|
38
|
+
/** Bindable open state — standalone only (ignored inside an Accordion, which owns it). */
|
|
39
|
+
open?: boolean;
|
|
40
|
+
/** This panel's key. Required inside an `Accordion`; ignored standalone. */
|
|
41
|
+
value?: string;
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
size?: Responsive<Size>;
|
|
44
|
+
/** Header hover/accent (default `ghost`; inherits the Accordion's variant). */
|
|
45
|
+
variant?: Variant;
|
|
46
|
+
/** Optional leading icon in the header. */
|
|
47
|
+
icon?: Snippet;
|
|
48
|
+
id?: string;
|
|
49
|
+
class?: string;
|
|
50
|
+
children: Snippet;
|
|
51
|
+
} = $props();
|
|
52
|
+
|
|
53
|
+
const grp = getDisclosureGroup();
|
|
54
|
+
const inGroup = $derived(!!grp?.inGroup && value != null);
|
|
55
|
+
const effSize = $derived(size ?? grp?.size ?? "md");
|
|
56
|
+
const effVariant = $derived(variant ?? grp?.variant ?? "ghost");
|
|
57
|
+
const effDisabled = $derived(disabled || !!grp?.disabled);
|
|
58
|
+
|
|
59
|
+
const isOpen = $derived(inGroup ? grp!.isOpen(value!) : open);
|
|
60
|
+
function handleToggle() {
|
|
61
|
+
if (inGroup) grp!.toggle(value!);
|
|
62
|
+
else open = !open;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const uid = nextFieldId();
|
|
66
|
+
const headerId = $derived(id ?? uid);
|
|
67
|
+
const panelId = $derived(`${headerId}-panel`);
|
|
68
|
+
|
|
69
|
+
const textMap: Record<Size, string> = { sm: "text-xs", md: "text-sm", lg: "text-base" };
|
|
70
|
+
const labelText = $derived(responsiveClasses(effSize, textMap));
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<section class={cls}>
|
|
74
|
+
<Button
|
|
75
|
+
variant={effVariant}
|
|
76
|
+
size={effSize}
|
|
77
|
+
disabled={effDisabled}
|
|
78
|
+
onclick={handleToggle}
|
|
79
|
+
class="w-full [&>div]:w-full"
|
|
80
|
+
id={headerId}
|
|
81
|
+
aria-expanded={isOpen}
|
|
82
|
+
aria-controls={panelId}
|
|
83
|
+
>
|
|
84
|
+
{#if icon}<span class="inline-flex shrink-0">{@render icon()}</span>{/if}
|
|
85
|
+
{#if header}
|
|
86
|
+
<span class="flex-1 text-left">{@render header()}</span>
|
|
87
|
+
{:else}
|
|
88
|
+
<span class="flex-1 text-left font-medium {labelText}">{title}</span>
|
|
89
|
+
{/if}
|
|
90
|
+
<ChevronDown
|
|
91
|
+
class="shrink-0 motion-safe:transition-transform motion-safe:duration-200 {isOpen
|
|
92
|
+
? 'rotate-180'
|
|
93
|
+
: ''}"
|
|
94
|
+
/>
|
|
95
|
+
</Button>
|
|
96
|
+
|
|
97
|
+
<div
|
|
98
|
+
class="grid motion-safe:transition-[grid-template-rows] motion-safe:duration-200 {isOpen
|
|
99
|
+
? '[grid-template-rows:1fr]'
|
|
100
|
+
: '[grid-template-rows:0fr]'}"
|
|
101
|
+
>
|
|
102
|
+
<div
|
|
103
|
+
class="overflow-hidden [min-height:0]"
|
|
104
|
+
id={panelId}
|
|
105
|
+
role="region"
|
|
106
|
+
aria-labelledby={headerId}
|
|
107
|
+
inert={!isOpen || undefined}
|
|
108
|
+
aria-hidden={!isOpen || undefined}
|
|
109
|
+
>
|
|
110
|
+
<div class="px-1 pt-1 pb-3 {labelText} [color:var(--ui-color-text)]">
|
|
111
|
+
{@render children()}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</section>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { Size } from "../types/sizes.js";
|
|
3
|
+
import { type Responsive } from "../types/responsive.js";
|
|
4
|
+
import type { Variant } from "../types/variants.js";
|
|
5
|
+
type $$ComponentProps = {
|
|
6
|
+
/** Header text. Use the `header` snippet instead for full custom header content. */
|
|
7
|
+
title?: string | null;
|
|
8
|
+
header?: Snippet;
|
|
9
|
+
/** Bindable open state — standalone only (ignored inside an Accordion, which owns it). */
|
|
10
|
+
open?: boolean;
|
|
11
|
+
/** This panel's key. Required inside an `Accordion`; ignored standalone. */
|
|
12
|
+
value?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
size?: Responsive<Size>;
|
|
15
|
+
/** Header hover/accent (default `ghost`; inherits the Accordion's variant). */
|
|
16
|
+
variant?: Variant;
|
|
17
|
+
/** Optional leading icon in the header. */
|
|
18
|
+
icon?: Snippet;
|
|
19
|
+
id?: string;
|
|
20
|
+
class?: string;
|
|
21
|
+
children: Snippet;
|
|
22
|
+
};
|
|
23
|
+
declare const Disclosure: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
24
|
+
type Disclosure = ReturnType<typeof Disclosure>;
|
|
25
|
+
export default Disclosure;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Size } from "../types/sizes.js";
|
|
2
|
+
import type { Variant } from "../types/variants.js";
|
|
3
|
+
import type { Responsive } from "../types/responsive.js";
|
|
4
|
+
export interface DisclosureGroupContext {
|
|
5
|
+
inGroup: true;
|
|
6
|
+
isOpen(value: string): boolean;
|
|
7
|
+
toggle(value: string): void;
|
|
8
|
+
size?: Responsive<Size>;
|
|
9
|
+
variant?: Variant;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function setDisclosureGroup(ctx: DisclosureGroupContext): void;
|
|
13
|
+
export declare function getDisclosureGroup(): DisclosureGroupContext | undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared context for `Accordion` → `Disclosure` (mirrors `field-group.ts`). A `Disclosure` inside an
|
|
3
|
+
* `Accordion` reads its open-state from the group (keyed by `value`) and toggles through it, so the
|
|
4
|
+
* group owns single-open / multi-open rules; it also inherits the group's `size`/`variant`/`disabled`.
|
|
5
|
+
*/
|
|
6
|
+
import { getContext, setContext } from "svelte";
|
|
7
|
+
const KEY = Symbol("svelte-ui-disclosure-group");
|
|
8
|
+
export function setDisclosureGroup(ctx) {
|
|
9
|
+
setContext(KEY, ctx);
|
|
10
|
+
}
|
|
11
|
+
export function getDisclosureGroup() {
|
|
12
|
+
return getContext(KEY);
|
|
13
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export { default as Radio } from "./components/Radio.svelte";
|
|
|
37
37
|
export { default as FieldGroup } from "./components/FieldGroup.svelte";
|
|
38
38
|
export { default as Grid } from "./components/Grid.svelte";
|
|
39
39
|
export type { Columns, Gap } from "./components/field-group.js";
|
|
40
|
+
export { default as Disclosure } from "./components/Disclosure.svelte";
|
|
41
|
+
export { default as Accordion } from "./components/Accordion.svelte";
|
|
40
42
|
export { default as SideBarMenu } from "./components/SideBarMenu/SideBarMenu.svelte";
|
|
41
43
|
export type { SideBarMenuItem, SideBarMenuBadge, SideBarMenuBadgeVariant, } from "./components/SideBarMenu/SideBarMenu.svelte";
|
|
42
44
|
export { default as TabStrip } from "./components/TabStrip/TabStrip.svelte";
|
package/dist/index.js
CHANGED
|
@@ -41,6 +41,8 @@ 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
43
|
export { default as Grid } from "./components/Grid.svelte";
|
|
44
|
+
export { default as Disclosure } from "./components/Disclosure.svelte";
|
|
45
|
+
export { default as Accordion } from "./components/Accordion.svelte";
|
|
44
46
|
// Navigation Components
|
|
45
47
|
export { default as SideBarMenu } from "./components/SideBarMenu/SideBarMenu.svelte";
|
|
46
48
|
export { default as TabStrip } from "./components/TabStrip/TabStrip.svelte";
|
package/docs/components.md
CHANGED
|
@@ -145,6 +145,27 @@ per-child prop). Works with any content. Uniform cells only (for variable widths
|
|
|
145
145
|
- **Props**: `columns` (`number | { base, md, … }`, 1–6, default `1`), `gap` (`number | { x, y }`,
|
|
146
146
|
default `4`), `class`, `children`. Shares `gridColsClass`/`gapClass` with `FieldGroup`.
|
|
147
147
|
|
|
148
|
+
### Disclosure
|
|
149
|
+
|
|
150
|
+
A single expand/collapse section — a `<Button>` header (title + rotating chevron) over a content
|
|
151
|
+
panel. The panel animates via `grid-template-rows` and **stays mounted when collapsed** (panel state
|
|
152
|
+
preserved), `inert` + `aria-hidden` when closed. Standalone `bind:open`; inside an `Accordion` it
|
|
153
|
+
reports via context (keyed by `value`).
|
|
154
|
+
|
|
155
|
+
- **Location**: `src/lib/components/Disclosure.svelte`
|
|
156
|
+
- **Axes**: `size`, `variant` (header; default `ghost`)
|
|
157
|
+
- **Props**: `title` (or `header` Snippet), `open` (bindable, standalone), `value` (key — required in
|
|
158
|
+
an Accordion), `disabled`, `icon` (Snippet), `id`, `class`, `children` (panel).
|
|
159
|
+
|
|
160
|
+
### Accordion
|
|
161
|
+
|
|
162
|
+
Groups Disclosures under coordinated rules. `multiple={false}` (default) = single-open; `multiple` =
|
|
163
|
+
independent. Propagates `size`/`variant`/`disabled` to children.
|
|
164
|
+
|
|
165
|
+
- **Location**: `src/lib/components/Accordion.svelte`
|
|
166
|
+
- **Props**: `multiple` (default `false`), `value` (bindable — `string` single / `string[]` multiple),
|
|
167
|
+
`collapsible` (single mode, default `true`), `size`, `variant`, `disabled`, `class`, `children`.
|
|
168
|
+
|
|
148
169
|
### Rating
|
|
149
170
|
|
|
150
171
|
Read-only star-rating display (5-star, from a 10-point average).
|
package/docs/usage.md
CHANGED
|
@@ -160,6 +160,34 @@ responsive; `gap` is symmetric (a number) or per-axis (`{ x, y }`).
|
|
|
160
160
|
|
|
161
161
|
For variable-width cells, use your own grid + `col-span-*`; `Grid` is uniform cells by design.
|
|
162
162
|
|
|
163
|
+
### Disclosure & Accordion
|
|
164
|
+
|
|
165
|
+
A `Disclosure` is one expand/collapse section (`bind:open`); an `Accordion` coordinates a set
|
|
166
|
+
(single-open by default). The panel stays mounted when collapsed, so its state is preserved.
|
|
167
|
+
|
|
168
|
+
```svelte
|
|
169
|
+
<script>
|
|
170
|
+
import { Disclosure, Accordion } from "@spaethtech/svelte-ui";
|
|
171
|
+
let open = $state(false);
|
|
172
|
+
let section = $state("shipping"); // the open panel's key (single-open)
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<!-- Standalone -->
|
|
176
|
+
<Disclosure title="Advanced options" bind:open>
|
|
177
|
+
…panel content…
|
|
178
|
+
</Disclosure>
|
|
179
|
+
|
|
180
|
+
<!-- Coordinated group (single-open) -->
|
|
181
|
+
<Accordion bind:value={section}>
|
|
182
|
+
<Disclosure value="shipping" title="Shipping">…</Disclosure>
|
|
183
|
+
<Disclosure value="returns" title="Returns">…</Disclosure>
|
|
184
|
+
<Disclosure value="warranty" title="Warranty">…</Disclosure>
|
|
185
|
+
</Accordion>
|
|
186
|
+
|
|
187
|
+
<!-- Multi-open: bind an array -->
|
|
188
|
+
<!-- <Accordion multiple bind:value={openKeys}> … </Accordion> -->
|
|
189
|
+
```
|
|
190
|
+
|
|
163
191
|
### Button
|
|
164
192
|
|
|
165
193
|
```svelte
|