@spaethtech/svelte-ui 0.14.0 → 0.15.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 +14 -11
- package/dist/components/Carousel/Carousel.svelte +147 -0
- package/dist/components/Carousel/Carousel.svelte.d.ts +38 -0
- package/dist/components/Carousel/index.d.ts +1 -0
- package/dist/components/Carousel/index.js +1 -0
- package/dist/components/Combobox/Combobox.svelte +62 -31
- package/dist/components/Combobox/Combobox.svelte.d.ts +1 -1
- package/dist/components/CommandPalette/CommandPalette.svelte +203 -0
- package/dist/components/CommandPalette/CommandPalette.svelte.d.ts +23 -0
- package/dist/components/CommandPalette/index.d.ts +2 -0
- package/dist/components/CommandPalette/index.js +1 -0
- package/dist/components/ContextMenu/ContextMenu.svelte +58 -0
- package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +16 -0
- package/dist/components/ContextMenu/index.d.ts +1 -0
- package/dist/components/ContextMenu/index.js +1 -0
- package/dist/components/EmptyState/EmptyState.svelte +68 -0
- package/dist/components/EmptyState/EmptyState.svelte.d.ts +19 -0
- package/dist/components/EmptyState/index.d.ts +1 -0
- package/dist/components/EmptyState/index.js +1 -0
- package/dist/components/Kbd/Kbd.svelte +53 -0
- package/dist/components/Kbd/Kbd.svelte.d.ts +15 -0
- package/dist/components/Kbd/index.d.ts +1 -0
- package/dist/components/Kbd/index.js +1 -0
- package/dist/components/Pagination/Pagination.svelte +58 -43
- package/dist/components/Pagination/Pagination.svelte.d.ts +7 -2
- package/dist/components/Rating.svelte +169 -30
- package/dist/components/Rating.svelte.d.ts +22 -4
- package/dist/components/ScrollArea/ScrollArea.svelte +71 -0
- package/dist/components/ScrollArea/ScrollArea.svelte.d.ts +15 -0
- package/dist/components/ScrollArea/index.d.ts +1 -0
- package/dist/components/ScrollArea/index.js +1 -0
- package/dist/components/Slider/Slider.svelte +5 -3
- package/dist/components/Splitter/Splitter.svelte +127 -0
- package/dist/components/Splitter/Splitter.svelte.d.ts +18 -0
- package/dist/components/Splitter/index.d.ts +1 -0
- package/dist/components/Splitter/index.js +1 -0
- package/dist/components/Stat/Stat.svelte +95 -0
- package/dist/components/Stat/Stat.svelte.d.ts +21 -0
- package/dist/components/Stat/index.d.ts +1 -0
- package/dist/components/Stat/index.js +1 -0
- package/dist/components/Timeline/Timeline.svelte +98 -0
- package/dist/components/Timeline/Timeline.svelte.d.ts +21 -0
- package/dist/components/Timeline/index.d.ts +2 -0
- package/dist/components/Timeline/index.js +1 -0
- package/dist/components/TokenInput/TokenInput.svelte +12 -6
- package/dist/components/Tree/Tree.svelte +178 -0
- package/dist/components/Tree/Tree.svelte.d.ts +26 -0
- package/dist/components/Tree/index.d.ts +2 -0
- package/dist/components/Tree/index.js +1 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +10 -1
- package/docs/components.md +125 -22
- package/docs/usage.md +180 -13
- package/package.json +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* ContextMenu — a right-click menu. Wraps its `children` and, on contextmenu, opens the shared `Menu`
|
|
4
|
+
* anchored to a 0-size element positioned at the pointer. Composes `Menu` + `MenuItem[]`.
|
|
5
|
+
* See ContextMenu.spec.md.
|
|
6
|
+
*/
|
|
7
|
+
-->
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import type { Snippet } from "svelte";
|
|
10
|
+
import Menu from "../Menu.svelte";
|
|
11
|
+
import type { MenuItem } from "../../data/table/types.js";
|
|
12
|
+
import type { Variant } from "../../types/variants.js";
|
|
13
|
+
import type { Size } from "../../types/sizes.js";
|
|
14
|
+
import type { Responsive } from "../../types/responsive.js";
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
items,
|
|
18
|
+
size = "md",
|
|
19
|
+
variant,
|
|
20
|
+
disabled = false,
|
|
21
|
+
class: cls = "",
|
|
22
|
+
children,
|
|
23
|
+
}: {
|
|
24
|
+
items: MenuItem[];
|
|
25
|
+
size?: Responsive<Size>;
|
|
26
|
+
variant?: Variant;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
class?: string;
|
|
29
|
+
children: Snippet;
|
|
30
|
+
} = $props();
|
|
31
|
+
|
|
32
|
+
let open = $state(false);
|
|
33
|
+
let anchorEl = $state<HTMLElement>();
|
|
34
|
+
let x = $state(0);
|
|
35
|
+
let y = $state(0);
|
|
36
|
+
|
|
37
|
+
function onContextMenu(e: MouseEvent) {
|
|
38
|
+
if (disabled) return;
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
x = e.clientX;
|
|
41
|
+
y = e.clientY;
|
|
42
|
+
open = true;
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
47
|
+
<div class="contents {cls}" oncontextmenu={onContextMenu}>
|
|
48
|
+
{@render children()}
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- Virtual anchor: a 0-size fixed point at the pointer that `Menu` positions against. -->
|
|
52
|
+
<span
|
|
53
|
+
bind:this={anchorEl}
|
|
54
|
+
aria-hidden="true"
|
|
55
|
+
style="position: fixed; left: {x}px; top: {y}px; width: 0; height: 0; pointer-events: none;"
|
|
56
|
+
></span>
|
|
57
|
+
|
|
58
|
+
<Menu anchor={anchorEl} bind:open {items} {size} {variant} side="bottom" align="start" />
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { MenuItem } from "../../data/table/types.js";
|
|
3
|
+
import type { Variant } from "../../types/variants.js";
|
|
4
|
+
import type { Size } from "../../types/sizes.js";
|
|
5
|
+
import type { Responsive } from "../../types/responsive.js";
|
|
6
|
+
type $$ComponentProps = {
|
|
7
|
+
items: MenuItem[];
|
|
8
|
+
size?: Responsive<Size>;
|
|
9
|
+
variant?: Variant;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
children: Snippet;
|
|
13
|
+
};
|
|
14
|
+
declare const ContextMenu: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
15
|
+
type ContextMenu = ReturnType<typeof ContextMenu>;
|
|
16
|
+
export default ContextMenu;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ContextMenu } from "./ContextMenu.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ContextMenu } from "./ContextMenu.svelte";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* EmptyState — a zero-data / empty placeholder: a centered icon + title + description + optional
|
|
4
|
+
* action slot. Shared `size`/`variant` axes, `--ui-*` tokens. See EmptyState.spec.md.
|
|
5
|
+
*/
|
|
6
|
+
-->
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import type { Snippet } from "svelte";
|
|
9
|
+
import IconInbox from "~icons/mdi/inbox-outline";
|
|
10
|
+
import type { Variant } from "../../types/variants.js";
|
|
11
|
+
import { variantToken } from "../../types/variants.js";
|
|
12
|
+
import type { Size } from "../../types/sizes.js";
|
|
13
|
+
import { responsiveClasses, type Responsive } from "../../types/responsive.js";
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
title,
|
|
17
|
+
description,
|
|
18
|
+
icon,
|
|
19
|
+
action,
|
|
20
|
+
variant = "secondary",
|
|
21
|
+
size = "md",
|
|
22
|
+
class: cls = "",
|
|
23
|
+
children,
|
|
24
|
+
}: {
|
|
25
|
+
title?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
/** Large centered icon; defaults to an inbox glyph. */
|
|
28
|
+
icon?: Snippet;
|
|
29
|
+
/** Action slot (CTA buttons) below the text. */
|
|
30
|
+
action?: Snippet;
|
|
31
|
+
variant?: Variant;
|
|
32
|
+
size?: Responsive<Size>;
|
|
33
|
+
class?: string;
|
|
34
|
+
children?: Snippet;
|
|
35
|
+
} = $props();
|
|
36
|
+
|
|
37
|
+
const token = $derived(variantToken[variant]);
|
|
38
|
+
const iconBox: Record<Size, string> = {
|
|
39
|
+
sm: "w-10 h-10 [&_svg]:w-6 [&_svg]:h-6",
|
|
40
|
+
md: "w-14 h-14 [&_svg]:w-8 [&_svg]:h-8",
|
|
41
|
+
lg: "w-20 h-20 [&_svg]:w-12 [&_svg]:h-12",
|
|
42
|
+
};
|
|
43
|
+
const titleSize: Record<Size, string> = { sm: "text-base", md: "text-lg", lg: "text-2xl" };
|
|
44
|
+
const padSize: Record<Size, string> = { sm: "py-6 gap-2", md: "py-10 gap-3", lg: "py-16 gap-4" };
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<div class="flex flex-col items-center text-center {responsiveClasses(size, padSize)} {cls}">
|
|
48
|
+
<div
|
|
49
|
+
class="inline-flex items-center justify-center rounded-full {responsiveClasses(size, iconBox)}"
|
|
50
|
+
style="background-color: color-mix(in srgb, var({token}) 14%, transparent); color: color-mix(in srgb, var({token}) var(--ui-tint-strong), var(--ui-color-text));"
|
|
51
|
+
aria-hidden="true"
|
|
52
|
+
>
|
|
53
|
+
{#if icon}{@render icon()}{:else}<IconInbox />{/if}
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{#if title}
|
|
57
|
+
<h3 class="font-semibold [color:var(--ui-color-text)] {responsiveClasses(size, titleSize)}">
|
|
58
|
+
{title}
|
|
59
|
+
</h3>
|
|
60
|
+
{/if}
|
|
61
|
+
{#if description}
|
|
62
|
+
<p class="max-w-sm text-sm [color:color-mix(in_srgb,var(--ui-color-text)_60%,transparent)]">
|
|
63
|
+
{description}
|
|
64
|
+
</p>
|
|
65
|
+
{/if}
|
|
66
|
+
{#if children}{@render children()}{/if}
|
|
67
|
+
{#if action}<div class="mt-1 flex flex-wrap items-center justify-center gap-2">{@render action()}</div>{/if}
|
|
68
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { Variant } from "../../types/variants.js";
|
|
3
|
+
import type { Size } from "../../types/sizes.js";
|
|
4
|
+
import { type Responsive } from "../../types/responsive.js";
|
|
5
|
+
type $$ComponentProps = {
|
|
6
|
+
title?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
/** Large centered icon; defaults to an inbox glyph. */
|
|
9
|
+
icon?: Snippet;
|
|
10
|
+
/** Action slot (CTA buttons) below the text. */
|
|
11
|
+
action?: Snippet;
|
|
12
|
+
variant?: Variant;
|
|
13
|
+
size?: Responsive<Size>;
|
|
14
|
+
class?: string;
|
|
15
|
+
children?: Snippet;
|
|
16
|
+
};
|
|
17
|
+
declare const EmptyState: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
18
|
+
type EmptyState = ReturnType<typeof EmptyState>;
|
|
19
|
+
export default EmptyState;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as EmptyState } from "./EmptyState.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as EmptyState } from "./EmptyState.svelte";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* Kbd — a keyboard-key display. Render a single key (`text`/children) or a combo (`keys` → joined with
|
|
4
|
+
* "+"). Styled as a keycap from `--ui-*` tokens; shared `size` axis. See Kbd.spec.md.
|
|
5
|
+
*/
|
|
6
|
+
-->
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import type { Snippet } from "svelte";
|
|
9
|
+
import type { Size } from "../../types/sizes.js";
|
|
10
|
+
import { responsiveClasses, type Responsive } from "../../types/responsive.js";
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
keys,
|
|
14
|
+
text,
|
|
15
|
+
size = "md",
|
|
16
|
+
class: cls = "",
|
|
17
|
+
children,
|
|
18
|
+
}: {
|
|
19
|
+
/** A combo — each string is rendered as its own keycap, joined with "+". */
|
|
20
|
+
keys?: string[];
|
|
21
|
+
/** A single key label (alternative to `keys`/children). */
|
|
22
|
+
text?: string;
|
|
23
|
+
size?: Responsive<Size>;
|
|
24
|
+
class?: string;
|
|
25
|
+
children?: Snippet;
|
|
26
|
+
} = $props();
|
|
27
|
+
|
|
28
|
+
const list = $derived(keys ?? null);
|
|
29
|
+
|
|
30
|
+
const capSize: Record<Size, string> = {
|
|
31
|
+
sm: "text-[0.6875rem] min-w-5 h-5 px-1",
|
|
32
|
+
md: "text-xs min-w-6 h-6 px-1.5",
|
|
33
|
+
lg: "text-sm min-w-7 h-7 px-2",
|
|
34
|
+
};
|
|
35
|
+
// Keycap: subtle surface fill, tinted border with a slightly darker bottom edge (a shallow 3D key).
|
|
36
|
+
const cap =
|
|
37
|
+
"inline-flex items-center justify-center rounded border font-medium [font-family:ui-monospace,SFMono-Regular,Menlo,monospace] [background-color:var(--ui-color-surface)] [border-color:var(--ui-border-color)] [box-shadow:inset_0_-1px_0_var(--ui-border-color)] [color:var(--ui-color-text)] select-none";
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
{#if list}
|
|
41
|
+
<span class="inline-flex items-center gap-1 {cls}">
|
|
42
|
+
{#each list as k, i (i)}
|
|
43
|
+
{#if i > 0}<span class="text-xs [color:color-mix(in_srgb,var(--ui-color-text)_50%,transparent)]"
|
|
44
|
+
>+</span
|
|
45
|
+
>{/if}
|
|
46
|
+
<kbd class="{cap} {responsiveClasses(size, capSize)}">{k}</kbd>
|
|
47
|
+
{/each}
|
|
48
|
+
</span>
|
|
49
|
+
{:else}
|
|
50
|
+
<kbd class="{cap} {responsiveClasses(size, capSize)} {cls}"
|
|
51
|
+
>{#if children}{@render children()}{:else}{text}{/if}</kbd
|
|
52
|
+
>
|
|
53
|
+
{/if}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { Size } from "../../types/sizes.js";
|
|
3
|
+
import { type Responsive } from "../../types/responsive.js";
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
/** A combo — each string is rendered as its own keycap, joined with "+". */
|
|
6
|
+
keys?: string[];
|
|
7
|
+
/** A single key label (alternative to `keys`/children). */
|
|
8
|
+
text?: string;
|
|
9
|
+
size?: Responsive<Size>;
|
|
10
|
+
class?: string;
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
};
|
|
13
|
+
declare const Kbd: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
14
|
+
type Kbd = ReturnType<typeof Kbd>;
|
|
15
|
+
export default Kbd;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Kbd } from "./Kbd.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Kbd } from "./Kbd.svelte";
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
/**
|
|
3
|
-
* Pagination — a standalone page navigator
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Pagination — a standalone page navigator laid out like DataTable's footer: a tinted, bordered band
|
|
4
|
+
* with an optional per-page `Select` on the left and a compact `First · Prev · [page]/N · Next · Last`
|
|
5
|
+
* stepper on the right. The page indicator is an editable `NumberInput` for quick-jump. `variant` tints
|
|
6
|
+
* the band surface + button hover (via the shared `.ui-accent` override) — it does NOT fill buttons.
|
|
7
|
+
* Built only from `Button`, `Select`, and `NumberInput`. See Pagination.spec.md.
|
|
6
8
|
*/
|
|
7
9
|
-->
|
|
8
10
|
<script lang="ts">
|
|
9
11
|
import Button from "../Button.svelte";
|
|
10
12
|
import Select from "../Select.svelte";
|
|
13
|
+
import NumberInput from "../NumberInput.svelte";
|
|
11
14
|
import type { Variant } from "../../types/variants.js";
|
|
15
|
+
import { variantToken } from "../../types/variants.js";
|
|
12
16
|
import type { Size } from "../../types/sizes.js";
|
|
13
17
|
import { responsiveClasses, type Responsive } from "../../types/responsive.js";
|
|
14
18
|
import IconFirst from "~icons/mdi/page-first";
|
|
@@ -21,25 +25,29 @@
|
|
|
21
25
|
total,
|
|
22
26
|
perPage = $bindable(10),
|
|
23
27
|
perPageOptions,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
showTotal = false,
|
|
28
|
-
variant = "primary",
|
|
28
|
+
showEdges = true,
|
|
29
|
+
showTotal = true,
|
|
30
|
+
variant,
|
|
29
31
|
size = "md",
|
|
32
|
+
borderless = false,
|
|
30
33
|
disabled = false,
|
|
31
34
|
class: cls = "",
|
|
32
35
|
}: {
|
|
33
36
|
page?: number;
|
|
34
37
|
total: number;
|
|
35
38
|
perPage?: number;
|
|
39
|
+
/** When set, renders the per-page `Select` on the left. */
|
|
36
40
|
perPageOptions?: number[];
|
|
37
|
-
|
|
38
|
-
boundaryCount?: number;
|
|
41
|
+
/** Show the First/Last edge buttons (default true, matching DataTable's footer). */
|
|
39
42
|
showEdges?: boolean;
|
|
43
|
+
/** Show the "start–end of total" range text (default true). */
|
|
40
44
|
showTotal?: boolean;
|
|
45
|
+
/** Color axis — tints the band surface + button hover (like DataTable), NOT the buttons. Omit for
|
|
46
|
+
* a neutral surface band; `ghost` makes the band transparent. */
|
|
41
47
|
variant?: Variant;
|
|
42
48
|
size?: Responsive<Size>;
|
|
49
|
+
/** Drop the band border/surface, leaving just the controls. */
|
|
50
|
+
borderless?: boolean;
|
|
43
51
|
disabled?: boolean;
|
|
44
52
|
class?: string;
|
|
45
53
|
} = $props();
|
|
@@ -51,23 +59,10 @@
|
|
|
51
59
|
if (clamped !== page) page = clamped;
|
|
52
60
|
});
|
|
53
61
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
set.add(i);
|
|
59
|
-
set.add(pageCount - i + 1);
|
|
60
|
-
}
|
|
61
|
-
for (let i = page - siblingCount; i <= page + siblingCount; i++) set.add(i);
|
|
62
|
-
const sorted = [...set].filter((n) => n >= 1 && n <= pageCount).sort((a, b) => a - b);
|
|
63
|
-
const out: (number | "ellipsis")[] = [];
|
|
64
|
-
let prev = 0;
|
|
65
|
-
for (const n of sorted) {
|
|
66
|
-
if (n - prev > 1) out.push("ellipsis");
|
|
67
|
-
out.push(n);
|
|
68
|
-
prev = n;
|
|
69
|
-
}
|
|
70
|
-
return out;
|
|
62
|
+
// The quick-jump field mirrors `page`; committed on change (Enter/blur), not on every keystroke.
|
|
63
|
+
let pageInput = $state(page);
|
|
64
|
+
$effect(() => {
|
|
65
|
+
pageInput = page;
|
|
71
66
|
});
|
|
72
67
|
|
|
73
68
|
const rangeText = $derived(
|
|
@@ -78,18 +73,38 @@
|
|
|
78
73
|
const subtle = $derived(
|
|
79
74
|
`${responsiveClasses(size, { sm: "text-xs", md: "text-sm", lg: "text-base" })} [color:color-mix(in_srgb,var(--ui-color-text)_60%,transparent)]`,
|
|
80
75
|
);
|
|
76
|
+
const bandPad = $derived(responsiveClasses(size, { sm: "px-2 py-1.5", md: "px-3 py-2", lg: "px-3.5 py-2.5" }));
|
|
77
|
+
|
|
78
|
+
// Band surface + border: variant re-points --ui-accent so --ui-color-surface/hover re-mix from it
|
|
79
|
+
// (the shared .ui-accent mechanism DataTable's footer uses). `ghost` ⇒ transparent band.
|
|
80
|
+
const accentVar = $derived(variant ? `--ui-accent: var(${variantToken[variant]});` : "");
|
|
81
|
+
const sectionBorder = "color-mix(in srgb, var(--ui-color-secondary) var(--ui-tint-border), transparent)";
|
|
82
|
+
const bandBg = $derived(variant === "ghost" ? "transparent" : "var(--ui-color-surface)");
|
|
83
|
+
const bandBorder = $derived(borderless ? "transparent" : sectionBorder);
|
|
81
84
|
|
|
82
85
|
const go = (to: number) => {
|
|
83
86
|
if (disabled) return;
|
|
84
87
|
page = Math.min(Math.max(to, 1), pageCount);
|
|
88
|
+
pageInput = page;
|
|
85
89
|
};
|
|
86
90
|
const setPerPage = (n: number) => {
|
|
87
91
|
perPage = n;
|
|
88
92
|
page = 1;
|
|
89
93
|
};
|
|
94
|
+
const commitJump = () => {
|
|
95
|
+
if (pageInput == null) {
|
|
96
|
+
pageInput = page;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
go(pageInput);
|
|
100
|
+
};
|
|
90
101
|
</script>
|
|
91
102
|
|
|
92
|
-
<nav
|
|
103
|
+
<nav
|
|
104
|
+
aria-label="Pagination"
|
|
105
|
+
class="ui-accent flex flex-wrap items-center justify-between gap-3 border rounded-[var(--ui-border-radius)] {bandPad} {cls}"
|
|
106
|
+
style="border-color: {bandBorder}; background-color: {bandBg}; {accentVar}"
|
|
107
|
+
>
|
|
93
108
|
{#if perPageOptions?.length}
|
|
94
109
|
<div class="flex items-center gap-2">
|
|
95
110
|
<Select
|
|
@@ -104,7 +119,7 @@
|
|
|
104
119
|
</div>
|
|
105
120
|
{/if}
|
|
106
121
|
|
|
107
|
-
<div class="flex items-center gap-3">
|
|
122
|
+
<div class="flex items-center gap-3 {perPageOptions?.length ? '' : 'ml-auto'}">
|
|
108
123
|
{#if showTotal}<span class={subtle}>{rangeText}</span>{/if}
|
|
109
124
|
<div class="inline-flex items-center gap-0.5">
|
|
110
125
|
{#snippet navBtn(Icon: typeof IconFirst, to: number, off: boolean, aria: string)}
|
|
@@ -123,22 +138,22 @@
|
|
|
123
138
|
{#if showEdges}{@render navBtn(IconFirst, 1, page <= 1, "First page")}{/if}
|
|
124
139
|
{@render navBtn(IconPrev, page - 1, page <= 1, "Previous page")}
|
|
125
140
|
|
|
126
|
-
|
|
127
|
-
{
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
141
|
+
<div class="flex items-center gap-1.5 px-1 {subtle}">
|
|
142
|
+
<span class="inline-block {responsiveClasses(size, { sm: 'w-11', md: 'w-12', lg: 'w-14' })}">
|
|
143
|
+
<NumberInput
|
|
144
|
+
bind:value={pageInput}
|
|
145
|
+
min={1}
|
|
146
|
+
max={pageCount}
|
|
147
|
+
clamp
|
|
132
148
|
{size}
|
|
133
149
|
{disabled}
|
|
134
|
-
|
|
135
|
-
aria-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
{/each}
|
|
150
|
+
inputClass="text-center px-1"
|
|
151
|
+
aria-label="Page number"
|
|
152
|
+
onchange={commitJump}
|
|
153
|
+
/>
|
|
154
|
+
</span>
|
|
155
|
+
<span aria-hidden="true">/ {pageCount}</span>
|
|
156
|
+
</div>
|
|
142
157
|
|
|
143
158
|
{@render navBtn(IconNext, page + 1, page >= pageCount, "Next page")}
|
|
144
159
|
{#if showEdges}{@render navBtn(IconLast, pageCount, page >= pageCount, "Last page")}{/if}
|
|
@@ -5,13 +5,18 @@ type $$ComponentProps = {
|
|
|
5
5
|
page?: number;
|
|
6
6
|
total: number;
|
|
7
7
|
perPage?: number;
|
|
8
|
+
/** When set, renders the per-page `Select` on the left. */
|
|
8
9
|
perPageOptions?: number[];
|
|
9
|
-
|
|
10
|
-
boundaryCount?: number;
|
|
10
|
+
/** Show the First/Last edge buttons (default true, matching DataTable's footer). */
|
|
11
11
|
showEdges?: boolean;
|
|
12
|
+
/** Show the "start–end of total" range text (default true). */
|
|
12
13
|
showTotal?: boolean;
|
|
14
|
+
/** Color axis — tints the band surface + button hover (like DataTable), NOT the buttons. Omit for
|
|
15
|
+
* a neutral surface band; `ghost` makes the band transparent. */
|
|
13
16
|
variant?: Variant;
|
|
14
17
|
size?: Responsive<Size>;
|
|
18
|
+
/** Drop the band border/surface, leaving just the controls. */
|
|
19
|
+
borderless?: boolean;
|
|
15
20
|
disabled?: boolean;
|
|
16
21
|
class?: string;
|
|
17
22
|
};
|