@fivem-nui/svelte-components 0.0.0-stage → 1.0.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/README.md +101 -2
- package/dist/components/actions/NuiButton.svelte +292 -0
- package/dist/components/actions/NuiButton.svelte.d.ts +60 -0
- package/dist/components/data/NuiBadge.svelte +277 -0
- package/dist/components/data/NuiBadge.svelte.d.ts +74 -0
- package/dist/components/data/NuiCodeBlock.svelte +256 -0
- package/dist/components/data/NuiCodeBlock.svelte.d.ts +49 -0
- package/dist/components/data/NuiList.svelte +341 -0
- package/dist/components/data/NuiList.svelte.d.ts +52 -0
- package/dist/components/data/NuiProgressBar.svelte +264 -0
- package/dist/components/data/NuiProgressBar.svelte.d.ts +51 -0
- package/dist/components/data/NuiRichText.svelte +260 -0
- package/dist/components/data/NuiRichText.svelte.d.ts +33 -0
- package/dist/components/data/NuiTable.svelte +470 -0
- package/dist/components/data/NuiTable.svelte.d.ts +53 -0
- package/dist/components/data/chart/NuiChart.svelte +384 -0
- package/dist/components/data/chart/NuiChart.svelte.d.ts +49 -0
- package/dist/components/data/chart/color.d.ts +9 -0
- package/dist/components/data/chart/color.js +78 -0
- package/dist/components/data/chart/index.d.ts +10 -0
- package/dist/components/data/chart/index.js +10 -0
- package/dist/components/data/chart/registry.d.ts +14 -0
- package/dist/components/data/chart/registry.js +53 -0
- package/dist/components/data/chart/renderers/bar.d.ts +8 -0
- package/dist/components/data/chart/renderers/bar.js +79 -0
- package/dist/components/data/chart/renderers/base.d.ts +18 -0
- package/dist/components/data/chart/renderers/base.js +10 -0
- package/dist/components/data/chart/renderers/line.d.ts +8 -0
- package/dist/components/data/chart/renderers/line.js +89 -0
- package/dist/components/data/chart/renderers/pie.d.ts +10 -0
- package/dist/components/data/chart/renderers/pie.js +55 -0
- package/dist/components/data/chart/scale.d.ts +22 -0
- package/dist/components/data/chart/scale.js +101 -0
- package/dist/components/data/chart/types.d.ts +84 -0
- package/dist/components/data/chart/types.js +1 -0
- package/dist/components/data/chart/validator.d.ts +10 -0
- package/dist/components/data/chart/validator.js +82 -0
- package/dist/components/forms/NuiCheckbox.svelte +218 -0
- package/dist/components/forms/NuiCheckbox.svelte.d.ts +37 -0
- package/dist/components/forms/NuiDatepicker.svelte +755 -0
- package/dist/components/forms/NuiDatepicker.svelte.d.ts +34 -0
- package/dist/components/forms/NuiFileInput.svelte +1047 -0
- package/dist/components/forms/NuiFileInput.svelte.d.ts +93 -0
- package/dist/components/forms/NuiFileInput.types.d.ts +28 -0
- package/dist/components/forms/NuiFileInput.types.js +1 -0
- package/dist/components/forms/NuiForm.svelte +391 -0
- package/dist/components/forms/NuiForm.svelte.d.ts +69 -0
- package/dist/components/forms/NuiInput.svelte +296 -0
- package/dist/components/forms/NuiInput.svelte.d.ts +62 -0
- package/dist/components/forms/NuiRadioGroup.svelte +238 -0
- package/dist/components/forms/NuiRadioGroup.svelte.d.ts +47 -0
- package/dist/components/forms/NuiSelect.svelte +819 -0
- package/dist/components/forms/NuiSelect.svelte.d.ts +49 -0
- package/dist/components/layout/NuiCard.svelte +314 -0
- package/dist/components/layout/NuiCard.svelte.d.ts +48 -0
- package/dist/components/layout/NuiCardGrid.svelte +98 -0
- package/dist/components/layout/NuiCardGrid.svelte.d.ts +22 -0
- package/dist/components/layout/NuiFlex.svelte +121 -0
- package/dist/components/layout/NuiFlex.svelte.d.ts +23 -0
- package/dist/components/layout/NuiGrid.svelte +66 -0
- package/dist/components/layout/NuiGrid.svelte.d.ts +14 -0
- package/dist/components/layout/NuiStack.svelte +138 -0
- package/dist/components/layout/NuiStack.svelte.d.ts +26 -0
- package/dist/components/navigation/NuiAccordion.svelte +323 -0
- package/dist/components/navigation/NuiAccordion.svelte.d.ts +38 -0
- package/dist/components/navigation/NuiBreadcrumbs.svelte +133 -0
- package/dist/components/navigation/NuiBreadcrumbs.svelte.d.ts +19 -0
- package/dist/components/navigation/NuiMenu.svelte +596 -0
- package/dist/components/navigation/NuiMenu.svelte.d.ts +28 -0
- package/dist/components/navigation/NuiPagination.svelte +306 -0
- package/dist/components/navigation/NuiPagination.svelte.d.ts +40 -0
- package/dist/components/navigation/NuiSidebar.svelte +186 -0
- package/dist/components/navigation/NuiSidebar.svelte.d.ts +26 -0
- package/dist/components/navigation/NuiTabs.svelte +249 -0
- package/dist/components/navigation/NuiTabs.svelte.d.ts +31 -0
- package/dist/components/overlay/NuiDialog.svelte +118 -0
- package/dist/components/overlay/NuiDialog.svelte.d.ts +34 -0
- package/dist/components/overlay/NuiModal.svelte +992 -0
- package/dist/components/overlay/NuiModal.svelte.d.ts +72 -0
- package/dist/components/overlay/NuiTooltip.svelte +159 -0
- package/dist/components/overlay/NuiTooltip.svelte.d.ts +13 -0
- package/dist/composables/useClickOutside.d.ts +43 -0
- package/dist/composables/useClickOutside.js +73 -0
- package/dist/composables/useFormValidation.d.ts +23 -0
- package/dist/composables/useFormValidation.js +72 -0
- package/dist/composables/useIdle.svelte.d.ts +18 -0
- package/dist/composables/useIdle.svelte.js +144 -0
- package/dist/composables/useKeyboardShortcut.svelte.d.ts +114 -0
- package/dist/composables/useKeyboardShortcut.svelte.js +153 -0
- package/dist/composables/useLocalStorage.svelte.d.ts +22 -0
- package/dist/composables/useLocalStorage.svelte.js +105 -0
- package/dist/composables/useMediaQuery.svelte.d.ts +51 -0
- package/dist/composables/useMediaQuery.svelte.js +80 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +42 -0
- package/package.json +58 -3
- package/src/components/actions/NuiButton.svelte +292 -0
- package/src/components/data/NuiBadge.svelte +277 -0
- package/src/components/data/NuiCodeBlock.svelte +256 -0
- package/src/components/data/NuiList.svelte +341 -0
- package/src/components/data/NuiProgressBar.svelte +264 -0
- package/src/components/data/NuiRichText.svelte +260 -0
- package/src/components/data/NuiTable.svelte +470 -0
- package/src/components/data/chart/NuiChart.svelte +384 -0
- package/src/components/data/chart/color.ts +83 -0
- package/src/components/data/chart/index.ts +10 -0
- package/src/components/data/chart/registry.ts +71 -0
- package/src/components/data/chart/renderers/bar.ts +90 -0
- package/src/components/data/chart/renderers/base.ts +32 -0
- package/src/components/data/chart/renderers/line.ts +99 -0
- package/src/components/data/chart/renderers/pie.ts +66 -0
- package/src/components/data/chart/scale.ts +139 -0
- package/src/components/data/chart/types.ts +98 -0
- package/src/components/data/chart/validator.ts +98 -0
- package/src/components/forms/NuiCheckbox.svelte +218 -0
- package/src/components/forms/NuiDatepicker.svelte +755 -0
- package/src/components/forms/NuiFileInput.svelte +1047 -0
- package/src/components/forms/NuiFileInput.types.ts +29 -0
- package/src/components/forms/NuiForm.svelte +391 -0
- package/src/components/forms/NuiInput.svelte +296 -0
- package/src/components/forms/NuiRadioGroup.svelte +238 -0
- package/src/components/forms/NuiSelect.svelte +819 -0
- package/src/components/layout/NuiCard.svelte +314 -0
- package/src/components/layout/NuiCardGrid.svelte +98 -0
- package/src/components/layout/NuiFlex.svelte +121 -0
- package/src/components/layout/NuiGrid.svelte +66 -0
- package/src/components/layout/NuiStack.svelte +138 -0
- package/src/components/navigation/NuiAccordion.svelte +323 -0
- package/src/components/navigation/NuiBreadcrumbs.svelte +133 -0
- package/src/components/navigation/NuiMenu.svelte +596 -0
- package/src/components/navigation/NuiPagination.svelte +306 -0
- package/src/components/navigation/NuiSidebar.svelte +186 -0
- package/src/components/navigation/NuiTabs.svelte +249 -0
- package/src/components/overlay/NuiDialog.svelte +118 -0
- package/src/components/overlay/NuiModal.svelte +992 -0
- package/src/components/overlay/NuiTooltip.svelte +159 -0
- package/src/composables/useClickOutside.ts +132 -0
- package/src/composables/useFormValidation.ts +105 -0
- package/src/composables/useIdle.svelte.ts +191 -0
- package/src/composables/useKeyboardShortcut.svelte.ts +278 -0
- package/src/composables/useLocalStorage.svelte.ts +137 -0
- package/src/composables/useMediaQuery.svelte.ts +102 -0
- package/src/index.ts +66 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type PageItem = number | "ellipsis-start" | "ellipsis-end";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a stable-width list of page items.
|
|
6
|
+
* Always renders `slots` items (when total > slots): first, last, current window and ellipses.
|
|
7
|
+
*/
|
|
8
|
+
export function getPageItems(
|
|
9
|
+
total: number,
|
|
10
|
+
current: number,
|
|
11
|
+
maxVisible: number,
|
|
12
|
+
ellipsis: boolean,
|
|
13
|
+
): PageItem[] {
|
|
14
|
+
const tot = Math.max(1, Number.isFinite(total) ? Math.floor(total) : 1);
|
|
15
|
+
const slots = Math.max(
|
|
16
|
+
5,
|
|
17
|
+
Number.isFinite(maxVisible) ? Math.floor(maxVisible) : 5,
|
|
18
|
+
);
|
|
19
|
+
const cur = Math.min(Math.max(1, Math.floor(current) || 1), tot);
|
|
20
|
+
|
|
21
|
+
if (tot <= slots) return Array.from({ length: tot }, (_, i) => i + 1);
|
|
22
|
+
|
|
23
|
+
// Without ellipses we simply slide a window of `slots` pages
|
|
24
|
+
if (!ellipsis) {
|
|
25
|
+
const start = Math.min(
|
|
26
|
+
Math.max(1, cur - Math.floor(slots / 2)),
|
|
27
|
+
tot - slots + 1,
|
|
28
|
+
);
|
|
29
|
+
return Array.from({ length: slots }, (_, i) => start + i);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const inner = slots - 2; // slots minus first and last
|
|
33
|
+
const nearStart = cur <= Math.ceil(slots / 2);
|
|
34
|
+
const nearEnd = cur > tot - Math.ceil(slots / 2);
|
|
35
|
+
|
|
36
|
+
if (nearStart) {
|
|
37
|
+
const pages = Array.from({ length: inner }, (_, i) => i + 1);
|
|
38
|
+
return [...pages, "ellipsis-end", tot];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (nearEnd) {
|
|
42
|
+
const start = tot - inner + 1;
|
|
43
|
+
const pages = Array.from({ length: inner }, (_, i) => start + i);
|
|
44
|
+
return [1, "ellipsis-start", ...pages];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const middle = inner - 2; // minus two ellipses
|
|
48
|
+
const start = cur - Math.floor((middle - 1) / 2);
|
|
49
|
+
const pages = Array.from({ length: middle }, (_, i) => start + i);
|
|
50
|
+
return [1, "ellipsis-start", ...pages, "ellipsis-end", tot];
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<script lang="ts">
|
|
55
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
56
|
+
|
|
57
|
+
interface Labels {
|
|
58
|
+
nav: string;
|
|
59
|
+
first: string;
|
|
60
|
+
previous: string;
|
|
61
|
+
next: string;
|
|
62
|
+
last: string;
|
|
63
|
+
page: (n: number) => string;
|
|
64
|
+
status: (page: number, total: number) => string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
68
|
+
/** Total number of items */
|
|
69
|
+
totalItems: number;
|
|
70
|
+
/** Items per page */
|
|
71
|
+
pageSize?: number;
|
|
72
|
+
/** Current page (1-based). Supports `bind:page` */
|
|
73
|
+
page?: number;
|
|
74
|
+
/** Number of slots in the list (min 5). The width stays stable while navigating */
|
|
75
|
+
maxPages?: number;
|
|
76
|
+
/** Show first/last buttons */
|
|
77
|
+
showFirstLast?: boolean;
|
|
78
|
+
/** Show ellipses for skipped ranges. When false, a sliding window is used */
|
|
79
|
+
showEllipsis?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Called after the page changed through direct user interaction (clicking a page button or next/prev).
|
|
82
|
+
* Note: programmatic adjustments (such as clamp adjustments via $effect when `totalItems` decreases) do not trigger this callback.
|
|
83
|
+
*/
|
|
84
|
+
onChange?: (page: number) => void;
|
|
85
|
+
/** Override texts (localization / accessibility) */
|
|
86
|
+
labels?: Partial<Labels>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const defaultLabels: Labels = {
|
|
90
|
+
nav: "Pagination",
|
|
91
|
+
first: "First page",
|
|
92
|
+
previous: "Previous page",
|
|
93
|
+
next: "Next page",
|
|
94
|
+
last: "Last page",
|
|
95
|
+
page: (n) => `Page ${n}`,
|
|
96
|
+
status: (p, t) => `Page ${p} of ${t}`,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
let {
|
|
100
|
+
totalItems,
|
|
101
|
+
pageSize = 10,
|
|
102
|
+
page = $bindable(1),
|
|
103
|
+
maxPages = 7,
|
|
104
|
+
showFirstLast = true,
|
|
105
|
+
showEllipsis = true,
|
|
106
|
+
onChange,
|
|
107
|
+
labels,
|
|
108
|
+
class: className = "",
|
|
109
|
+
...rest
|
|
110
|
+
}: Props = $props();
|
|
111
|
+
|
|
112
|
+
const t = $derived({ ...defaultLabels, ...labels });
|
|
113
|
+
|
|
114
|
+
const safeSize = $derived(
|
|
115
|
+
Number.isFinite(pageSize) && pageSize >= 1 ? Math.floor(pageSize) : 10,
|
|
116
|
+
);
|
|
117
|
+
const safeItems = $derived(
|
|
118
|
+
Number.isFinite(totalItems) && totalItems > 0 ? totalItems : 0,
|
|
119
|
+
);
|
|
120
|
+
const totalPages = $derived(Math.max(1, Math.ceil(safeItems / safeSize)));
|
|
121
|
+
const current = $derived(
|
|
122
|
+
Math.min(Math.max(1, Math.floor(page) || 1), totalPages),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const items = $derived(
|
|
126
|
+
getPageItems(totalPages, current, maxPages, showEllipsis),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Keep the bound value valid after totalItems/pageSize shrink
|
|
130
|
+
$effect(() => {
|
|
131
|
+
if (page !== current) page = current;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
function goToPage(next: number) {
|
|
135
|
+
if (next < 1 || next > totalPages || next === current) return;
|
|
136
|
+
page = next;
|
|
137
|
+
onChange?.(next);
|
|
138
|
+
}
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<nav {...rest} class={["nui-pagination", className]} aria-label={t.nav}>
|
|
142
|
+
<span class="nui-pagination__info" aria-live="polite">
|
|
143
|
+
{t.status(current, totalPages)}
|
|
144
|
+
</span>
|
|
145
|
+
|
|
146
|
+
<ol class="nui-pagination__list">
|
|
147
|
+
{#if showFirstLast}
|
|
148
|
+
<li>
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
class="nui-pagination__btn nui-pagination__btn--edge"
|
|
152
|
+
aria-label={t.first}
|
|
153
|
+
disabled={current === 1}
|
|
154
|
+
onclick={() => goToPage(1)}
|
|
155
|
+
>
|
|
156
|
+
<span aria-hidden="true">«</span>
|
|
157
|
+
</button>
|
|
158
|
+
</li>
|
|
159
|
+
{/if}
|
|
160
|
+
|
|
161
|
+
<li>
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
class="nui-pagination__btn"
|
|
165
|
+
aria-label={t.previous}
|
|
166
|
+
disabled={current === 1}
|
|
167
|
+
onclick={() => goToPage(current - 1)}
|
|
168
|
+
>
|
|
169
|
+
<span aria-hidden="true">‹</span>
|
|
170
|
+
</button>
|
|
171
|
+
</li>
|
|
172
|
+
|
|
173
|
+
{#each items as item (item)}
|
|
174
|
+
{#if typeof item === "string"}
|
|
175
|
+
<li class="nui-pagination__ellipsis" aria-hidden="true">…</li>
|
|
176
|
+
{:else}
|
|
177
|
+
<li>
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
class="nui-pagination__btn"
|
|
181
|
+
class:nui-pagination__btn--active={item === current}
|
|
182
|
+
aria-label={t.page(item)}
|
|
183
|
+
aria-current={item === current ? "page" : undefined}
|
|
184
|
+
onclick={() => goToPage(item)}
|
|
185
|
+
>
|
|
186
|
+
{item}
|
|
187
|
+
</button>
|
|
188
|
+
</li>
|
|
189
|
+
{/if}
|
|
190
|
+
{/each}
|
|
191
|
+
|
|
192
|
+
<li>
|
|
193
|
+
<button
|
|
194
|
+
type="button"
|
|
195
|
+
class="nui-pagination__btn"
|
|
196
|
+
aria-label={t.next}
|
|
197
|
+
disabled={current === totalPages}
|
|
198
|
+
onclick={() => goToPage(current + 1)}
|
|
199
|
+
>
|
|
200
|
+
<span aria-hidden="true">›</span>
|
|
201
|
+
</button>
|
|
202
|
+
</li>
|
|
203
|
+
|
|
204
|
+
{#if showFirstLast}
|
|
205
|
+
<li>
|
|
206
|
+
<button
|
|
207
|
+
type="button"
|
|
208
|
+
class="nui-pagination__btn nui-pagination__btn--edge"
|
|
209
|
+
aria-label={t.last}
|
|
210
|
+
disabled={current === totalPages}
|
|
211
|
+
onclick={() => goToPage(totalPages)}
|
|
212
|
+
>
|
|
213
|
+
<span aria-hidden="true">»</span>
|
|
214
|
+
</button>
|
|
215
|
+
</li>
|
|
216
|
+
{/if}
|
|
217
|
+
</ol>
|
|
218
|
+
</nav>
|
|
219
|
+
|
|
220
|
+
<style>
|
|
221
|
+
.nui-pagination {
|
|
222
|
+
--_size: var(--nui-pagination-size, 2.5rem);
|
|
223
|
+
display: flex;
|
|
224
|
+
flex-direction: column;
|
|
225
|
+
align-items: center;
|
|
226
|
+
gap: 0.5rem;
|
|
227
|
+
font-family: inherit;
|
|
228
|
+
box-sizing: border-box;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.nui-pagination__info {
|
|
232
|
+
font-size: 0.875rem;
|
|
233
|
+
opacity: 0.7;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.nui-pagination__list {
|
|
237
|
+
display: flex;
|
|
238
|
+
flex-wrap: wrap;
|
|
239
|
+
justify-content: center;
|
|
240
|
+
gap: 0.25rem;
|
|
241
|
+
list-style: none;
|
|
242
|
+
margin: 0;
|
|
243
|
+
padding: 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.nui-pagination__btn {
|
|
247
|
+
display: flex;
|
|
248
|
+
align-items: center;
|
|
249
|
+
justify-content: center;
|
|
250
|
+
width: var(--_size);
|
|
251
|
+
height: var(--_size);
|
|
252
|
+
padding: 0;
|
|
253
|
+
background: transparent;
|
|
254
|
+
color: inherit;
|
|
255
|
+
border: 1px solid var(--nui-border, #3f3f46);
|
|
256
|
+
border-radius: 0.25rem;
|
|
257
|
+
font: inherit;
|
|
258
|
+
cursor: pointer;
|
|
259
|
+
transition:
|
|
260
|
+
background 0.15s ease,
|
|
261
|
+
border-color 0.15s ease;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.nui-pagination__btn:hover:not(:disabled) {
|
|
265
|
+
background: var(--nui-hover, rgb(255 255 255 / 0.06));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.nui-pagination__btn:focus-visible {
|
|
269
|
+
outline: 2px solid var(--nui-primary, #3b82f6);
|
|
270
|
+
outline-offset: 2px;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.nui-pagination__btn:disabled {
|
|
274
|
+
opacity: 0.4;
|
|
275
|
+
cursor: not-allowed;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.nui-pagination__btn--active {
|
|
279
|
+
background: var(--nui-primary, #3b82f6);
|
|
280
|
+
border-color: var(--nui-primary, #3b82f6);
|
|
281
|
+
color: var(--nui-primary-fg, #fff);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.nui-pagination__btn--active:hover:not(:disabled) {
|
|
285
|
+
background: var(--nui-primary, #3b82f6);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.nui-pagination__btn--edge {
|
|
289
|
+
width: calc(var(--_size) * 0.8);
|
|
290
|
+
font-size: 0.75rem;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.nui-pagination__ellipsis {
|
|
294
|
+
display: flex;
|
|
295
|
+
align-items: center;
|
|
296
|
+
justify-content: center;
|
|
297
|
+
width: var(--_size);
|
|
298
|
+
opacity: 0.5;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@media (prefers-reduced-motion: reduce) {
|
|
302
|
+
.nui-pagination__btn {
|
|
303
|
+
transition: none;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type PageItem = number | "ellipsis-start" | "ellipsis-end";
|
|
2
|
+
/**
|
|
3
|
+
* Returns a stable-width list of page items.
|
|
4
|
+
* Always renders `slots` items (when total > slots): first, last, current window and ellipses.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getPageItems(total: number, current: number, maxVisible: number, ellipsis: boolean): PageItem[];
|
|
7
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
8
|
+
interface Labels {
|
|
9
|
+
nav: string;
|
|
10
|
+
first: string;
|
|
11
|
+
previous: string;
|
|
12
|
+
next: string;
|
|
13
|
+
last: string;
|
|
14
|
+
page: (n: number) => string;
|
|
15
|
+
status: (page: number, total: number) => string;
|
|
16
|
+
}
|
|
17
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
18
|
+
/** Total number of items */
|
|
19
|
+
totalItems: number;
|
|
20
|
+
/** Items per page */
|
|
21
|
+
pageSize?: number;
|
|
22
|
+
/** Current page (1-based). Supports `bind:page` */
|
|
23
|
+
page?: number;
|
|
24
|
+
/** Number of slots in the list (min 5). The width stays stable while navigating */
|
|
25
|
+
maxPages?: number;
|
|
26
|
+
/** Show first/last buttons */
|
|
27
|
+
showFirstLast?: boolean;
|
|
28
|
+
/** Show ellipses for skipped ranges. When false, a sliding window is used */
|
|
29
|
+
showEllipsis?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Called after the page changed through direct user interaction (clicking a page button or next/prev).
|
|
32
|
+
* Note: programmatic adjustments (such as clamp adjustments via $effect when `totalItems` decreases) do not trigger this callback.
|
|
33
|
+
*/
|
|
34
|
+
onChange?: (page: number) => void;
|
|
35
|
+
/** Override texts (localization / accessibility) */
|
|
36
|
+
labels?: Partial<Labels>;
|
|
37
|
+
}
|
|
38
|
+
declare const NuiPagination: import("svelte").Component<Props, {}, "page">;
|
|
39
|
+
type NuiPagination = ReturnType<typeof NuiPagination>;
|
|
40
|
+
export default NuiPagination;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
interface Labels {
|
|
6
|
+
nav: string;
|
|
7
|
+
toggle: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
11
|
+
/** Whether the sidebar is open. Supports `bind:open` */
|
|
12
|
+
open?: boolean;
|
|
13
|
+
/** Show the toggle button */
|
|
14
|
+
collapsible?: boolean;
|
|
15
|
+
/** Sidebar width. Numbers are treated as px */
|
|
16
|
+
width?: string | number;
|
|
17
|
+
/** Called after the user toggled the sidebar */
|
|
18
|
+
onToggle?: (open: boolean) => void;
|
|
19
|
+
/** Optional class applied directly to the inner <aside> panel */
|
|
20
|
+
panelClass?: string;
|
|
21
|
+
/** Override texts (localization) */
|
|
22
|
+
labels?: Partial<Labels>;
|
|
23
|
+
brand?: Snippet;
|
|
24
|
+
children?: Snippet;
|
|
25
|
+
footer?: Snippet;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const uid = $props.id();
|
|
29
|
+
const panelId = `nui-sidebar-${uid}`;
|
|
30
|
+
|
|
31
|
+
const defaultLabels: Labels = { nav: "Sidebar", toggle: "Toggle sidebar" };
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
id: idProp,
|
|
35
|
+
open = $bindable(true),
|
|
36
|
+
collapsible = false,
|
|
37
|
+
width = "250px",
|
|
38
|
+
onToggle,
|
|
39
|
+
labels,
|
|
40
|
+
brand,
|
|
41
|
+
children,
|
|
42
|
+
footer,
|
|
43
|
+
class: className = "",
|
|
44
|
+
style,
|
|
45
|
+
panelClass = "",
|
|
46
|
+
...rest
|
|
47
|
+
}: Props = $props();
|
|
48
|
+
|
|
49
|
+
const resolvedId = $derived(idProp || panelId);
|
|
50
|
+
const t = $derived({ ...defaultLabels, ...labels });
|
|
51
|
+
const toCss = (v: string | number) => (typeof v === "number" ? `${v}px` : v);
|
|
52
|
+
const styleVars = $derived(`--nui-sidebar-width:${toCss(width)};`);
|
|
53
|
+
const styleExtra = $derived(typeof style === "string" ? style : "");
|
|
54
|
+
|
|
55
|
+
function toggle() {
|
|
56
|
+
open = !open;
|
|
57
|
+
onToggle?.(open);
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<div
|
|
62
|
+
class={[
|
|
63
|
+
"nui-sidebar-wrap",
|
|
64
|
+
!open && "nui-sidebar-wrap--collapsed",
|
|
65
|
+
className,
|
|
66
|
+
]}
|
|
67
|
+
style={styleVars + styleExtra}
|
|
68
|
+
>
|
|
69
|
+
<aside
|
|
70
|
+
{...rest}
|
|
71
|
+
id={resolvedId}
|
|
72
|
+
class={["nui-sidebar", panelClass]}
|
|
73
|
+
aria-label={t.nav}
|
|
74
|
+
inert={!open || undefined}
|
|
75
|
+
>
|
|
76
|
+
{#if brand}
|
|
77
|
+
<div class="nui-sidebar__brand">{@render brand()}</div>
|
|
78
|
+
{/if}
|
|
79
|
+
|
|
80
|
+
<div class="nui-sidebar__content">{@render children?.()}</div>
|
|
81
|
+
|
|
82
|
+
{#if footer}
|
|
83
|
+
<div class="nui-sidebar__footer">{@render footer()}</div>
|
|
84
|
+
{/if}
|
|
85
|
+
</aside>
|
|
86
|
+
|
|
87
|
+
{#if collapsible}
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
class="nui-sidebar__toggle"
|
|
91
|
+
aria-label={t.toggle}
|
|
92
|
+
aria-expanded={open}
|
|
93
|
+
aria-controls={resolvedId}
|
|
94
|
+
onclick={toggle}
|
|
95
|
+
>
|
|
96
|
+
<span aria-hidden="true">{open ? "◀" : "▶"}</span>
|
|
97
|
+
</button>
|
|
98
|
+
{/if}
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<style>
|
|
102
|
+
.nui-sidebar-wrap {
|
|
103
|
+
position: relative;
|
|
104
|
+
flex: none;
|
|
105
|
+
width: var(--nui-sidebar-width, 250px);
|
|
106
|
+
max-width: 100%;
|
|
107
|
+
height: 100%;
|
|
108
|
+
box-sizing: border-box;
|
|
109
|
+
transition: width 0.25s ease;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.nui-sidebar-wrap--collapsed {
|
|
113
|
+
width: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.nui-sidebar {
|
|
117
|
+
display: flex;
|
|
118
|
+
flex-direction: column;
|
|
119
|
+
width: var(--nui-sidebar-width, 250px);
|
|
120
|
+
height: 100%;
|
|
121
|
+
background: var(--nui-surface-alt, #18181b);
|
|
122
|
+
border-right: 1px solid var(--nui-border, #27272a);
|
|
123
|
+
font-family: inherit;
|
|
124
|
+
box-sizing: border-box;
|
|
125
|
+
overflow: hidden;
|
|
126
|
+
transition:
|
|
127
|
+
transform 0.25s ease,
|
|
128
|
+
visibility 0.25s;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.nui-sidebar-wrap--collapsed .nui-sidebar {
|
|
132
|
+
transform: translateX(-100%);
|
|
133
|
+
visibility: hidden;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.nui-sidebar__brand {
|
|
137
|
+
border-bottom: 1px solid var(--nui-border, #27272a);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.nui-sidebar__content {
|
|
141
|
+
flex: 1;
|
|
142
|
+
overflow-y: auto;
|
|
143
|
+
min-height: 0;
|
|
144
|
+
padding: 1rem;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.nui-sidebar__footer {
|
|
148
|
+
border-top: 1px solid var(--nui-border, #27272a);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.nui-sidebar__toggle {
|
|
152
|
+
position: absolute;
|
|
153
|
+
top: 0.5rem;
|
|
154
|
+
left: 100%;
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
justify-content: center;
|
|
158
|
+
padding: 0.5rem 0.4rem;
|
|
159
|
+
background: var(--nui-surface-alt, #18181b);
|
|
160
|
+
border: 1px solid var(--nui-border, #27272a);
|
|
161
|
+
border-left: 0;
|
|
162
|
+
border-radius: 0 var(--nui-radius, 0.375rem) var(--nui-radius, 0.375rem) 0;
|
|
163
|
+
color: var(--nui-fg-secondary, #8b8b99);
|
|
164
|
+
font-size: 0.85rem;
|
|
165
|
+
cursor: pointer;
|
|
166
|
+
z-index: var(--nui-z-sidebar-toggle, 50);
|
|
167
|
+
transition: color 0.15s ease;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.nui-sidebar__toggle:hover {
|
|
171
|
+
color: var(--nui-fg, #fafafa);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.nui-sidebar__toggle:focus-visible {
|
|
175
|
+
outline: 2px solid var(--nui-primary, #3b82f6);
|
|
176
|
+
outline-offset: 2px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@media (prefers-reduced-motion: reduce) {
|
|
180
|
+
.nui-sidebar-wrap,
|
|
181
|
+
.nui-sidebar,
|
|
182
|
+
.nui-sidebar__toggle {
|
|
183
|
+
transition: none;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
interface Labels {
|
|
4
|
+
nav: string;
|
|
5
|
+
toggle: string;
|
|
6
|
+
}
|
|
7
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
8
|
+
/** Whether the sidebar is open. Supports `bind:open` */
|
|
9
|
+
open?: boolean;
|
|
10
|
+
/** Show the toggle button */
|
|
11
|
+
collapsible?: boolean;
|
|
12
|
+
/** Sidebar width. Numbers are treated as px */
|
|
13
|
+
width?: string | number;
|
|
14
|
+
/** Called after the user toggled the sidebar */
|
|
15
|
+
onToggle?: (open: boolean) => void;
|
|
16
|
+
/** Optional class applied directly to the inner <aside> panel */
|
|
17
|
+
panelClass?: string;
|
|
18
|
+
/** Override texts (localization) */
|
|
19
|
+
labels?: Partial<Labels>;
|
|
20
|
+
brand?: Snippet;
|
|
21
|
+
children?: Snippet;
|
|
22
|
+
footer?: Snippet;
|
|
23
|
+
}
|
|
24
|
+
declare const NuiSidebar: import("svelte").Component<Props, {}, "open">;
|
|
25
|
+
type NuiSidebar = ReturnType<typeof NuiSidebar>;
|
|
26
|
+
export default NuiSidebar;
|