@selvajs/ui 6.1.0 → 6.1.2

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.
@@ -0,0 +1,65 @@
1
+ <script lang="ts">
2
+ import { Button } from '../primitives';
3
+ import { ChevronLeft, ChevronRight } from '@lucide/svelte';
4
+
5
+ interface Props {
6
+ /** Current 1-based page. Bindable. */
7
+ page: number;
8
+ total: number;
9
+ perPage?: number;
10
+ /** Plural noun for the range readout: "1–25 of 122 members". */
11
+ label?: string;
12
+ class?: string;
13
+ }
14
+
15
+ let {
16
+ page = $bindable(1),
17
+ total,
18
+ perPage = 25,
19
+ label = 'items',
20
+ class: className = ''
21
+ }: Props = $props();
22
+
23
+ const pageCount = $derived(Math.max(1, Math.ceil(total / perPage)));
24
+
25
+ // A filter can shrink `total` under the current page — clamp on read so the
26
+ // list never renders an empty slice while the control still says "page 4".
27
+ $effect(() => {
28
+ if (page > pageCount) page = pageCount;
29
+ });
30
+
31
+ const first = $derived(total === 0 ? 0 : (page - 1) * perPage + 1);
32
+ const last = $derived(Math.min(page * perPage, total));
33
+ </script>
34
+
35
+ {#if pageCount > 1}
36
+ <div class={`gap-4 flex items-center justify-between ${className}`}>
37
+ <p class="text-xs text-muted-foreground">
38
+ {first}–{last} of {total}
39
+ {label}
40
+ </p>
41
+ <div class="gap-1 flex items-center">
42
+ <Button
43
+ size="sm"
44
+ variant="outline"
45
+ disabled={page <= 1}
46
+ onclick={() => (page -= 1)}
47
+ aria-label="Previous page"
48
+ class="h-8 w-8 p-0"
49
+ >
50
+ <ChevronLeft class="h-4 w-4" />
51
+ </Button>
52
+ <span class="px-2 font-mono text-xs text-muted-foreground">{page} / {pageCount}</span>
53
+ <Button
54
+ size="sm"
55
+ variant="outline"
56
+ disabled={page >= pageCount}
57
+ onclick={() => (page += 1)}
58
+ aria-label="Next page"
59
+ class="h-8 w-8 p-0"
60
+ >
61
+ <ChevronRight class="h-4 w-4" />
62
+ </Button>
63
+ </div>
64
+ </div>
65
+ {/if}
@@ -0,0 +1,12 @@
1
+ interface Props {
2
+ /** Current 1-based page. Bindable. */
3
+ page: number;
4
+ total: number;
5
+ perPage?: number;
6
+ /** Plural noun for the range readout: "1–25 of 122 members". */
7
+ label?: string;
8
+ class?: string;
9
+ }
10
+ declare const Pagination: import("svelte").Component<Props, {}, "page">;
11
+ type Pagination = ReturnType<typeof Pagination>;
12
+ export default Pagination;
@@ -8,3 +8,4 @@ export { default as SideNav } from './SideNav.svelte';
8
8
  export type { SideNavItem } from './SideNav.svelte';
9
9
  export { default as SectionHeader } from './SectionHeader.svelte';
10
10
  export { default as EmptyState } from './EmptyState.svelte';
11
+ export { default as Pagination } from './Pagination.svelte';
@@ -6,3 +6,4 @@ export { default as SubNav } from './SubNav.svelte';
6
6
  export { default as SideNav } from './SideNav.svelte';
7
7
  export { default as SectionHeader } from './SectionHeader.svelte';
8
8
  export { default as EmptyState } from './EmptyState.svelte';
9
+ export { default as Pagination } from './Pagination.svelte';
@@ -25,7 +25,7 @@
25
25
  bind:ref
26
26
  data-slot="dialog-content"
27
27
  class={cn(
28
- 'gap-4 p-6 shadow-lg data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] rounded-lg border bg-background duration-200',
28
+ 'gap-4 p-6 shadow-lg data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg fixed top-[50%] left-[50%] z-50 grid max-h-[85dvh] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] overflow-y-auto rounded-lg border bg-background duration-200',
29
29
  className
30
30
  )}
31
31
  {...restProps}
@@ -389,7 +389,7 @@
389
389
  <img
390
390
  src={logoUrl}
391
391
  alt=""
392
- class="h-8 max-w-32 drop-shadow-sm sm:h-10 w-auto object-contain opacity-80"
392
+ class="h-10 max-w-40 drop-shadow-sm sm:h-14 sm:max-w-56 w-auto object-contain opacity-80"
393
393
  />
394
394
  </div>
395
395
  {/if}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@selvajs/ui",
3
- "version": "6.1.0",
3
+ "version": "6.1.2",
4
4
  "description": "Shared UI components and utilities for Selva applications",
5
5
  "license": "MIT",
6
6
  "author": "VektorNode",
7
- "homepage": "https://github.com/VektorNode/selva#readme",
7
+ "homepage": "https://selva.dev",
8
8
  "bugs": "https://github.com/VektorNode/selva/issues",
9
9
  "keywords": [
10
10
  "svelte",
@@ -59,10 +59,10 @@
59
59
  "svelte": "^5",
60
60
  "tailwind-variants": "^3.3.1",
61
61
  "three": "^0.185.1",
62
- "@selvajs/compute": "^4.0.1",
63
- "@selvajs/schemas": "^5.0.0",
64
- "@selvajs/solve": "^1.0.2",
65
- "@selvajs/visualization": "^1.0.0"
62
+ "@selvajs/compute": "^4.0.2",
63
+ "@selvajs/solve": "^1.0.4",
64
+ "@selvajs/visualization": "^1.0.1",
65
+ "@selvajs/schemas": "^5.0.1"
66
66
  },
67
67
  "peerDependenciesMeta": {
68
68
  "three": {
@@ -76,25 +76,25 @@
76
76
  "clsx": "^2.1.1",
77
77
  "mode-watcher": "^1.1.0",
78
78
  "paneforge": "^1.0.2",
79
- "svelte-sonner": "^1.1.1",
79
+ "svelte-sonner": "^1.2.1",
80
80
  "tailwind-merge": "^3.5.0",
81
81
  "tw-animate-css": "^1.4.0"
82
82
  },
83
83
  "devDependencies": {
84
84
  "@internationalized/date": "^3.12.3",
85
85
  "@sveltejs/kit": "2.70.2",
86
- "@sveltejs/vite-plugin-svelte": "^7.2.0",
86
+ "@sveltejs/vite-plugin-svelte": "^7.3.0",
87
87
  "@types/three": "^0.185.4",
88
88
  "bits-ui": "^2.18.0",
89
89
  "rimraf": "^6.1.3",
90
- "svelte": "5.56.8",
90
+ "svelte": "5.56.9",
91
91
  "tailwind-variants": "^3.3.1",
92
92
  "vitest": "^4.1.10",
93
- "@selvajs/visualization": "1.0.0",
94
- "@selvajs/schemas": "5.0.0",
95
- "@selvajs/solve": "1.0.2",
96
93
  "@selvajs/config": "0.0.4",
97
- "@selvajs/compute": "4.0.1"
94
+ "@selvajs/schemas": "5.0.1",
95
+ "@selvajs/visualization": "1.0.1",
96
+ "@selvajs/solve": "1.0.4",
97
+ "@selvajs/compute": "4.0.2"
98
98
  },
99
99
  "scripts": {
100
100
  "predev": "node ../../scripts/sync-shared-assets.js",
@@ -109,6 +109,7 @@
109
109
  "test": "vitest run",
110
110
  "test:watch": "vitest",
111
111
  "format": "prettier --write .",
112
- "lint": "prettier --check . && eslint ."
112
+ "lint": "prettier --check . && eslint .",
113
+ "lint:types": "eslint . --config eslint.typed.config.js"
113
114
  }
114
115
  }
@@ -0,0 +1,65 @@
1
+ <script lang="ts">
2
+ import { Button } from '../primitives';
3
+ import { ChevronLeft, ChevronRight } from '@lucide/svelte';
4
+
5
+ interface Props {
6
+ /** Current 1-based page. Bindable. */
7
+ page: number;
8
+ total: number;
9
+ perPage?: number;
10
+ /** Plural noun for the range readout: "1–25 of 122 members". */
11
+ label?: string;
12
+ class?: string;
13
+ }
14
+
15
+ let {
16
+ page = $bindable(1),
17
+ total,
18
+ perPage = 25,
19
+ label = 'items',
20
+ class: className = ''
21
+ }: Props = $props();
22
+
23
+ const pageCount = $derived(Math.max(1, Math.ceil(total / perPage)));
24
+
25
+ // A filter can shrink `total` under the current page — clamp on read so the
26
+ // list never renders an empty slice while the control still says "page 4".
27
+ $effect(() => {
28
+ if (page > pageCount) page = pageCount;
29
+ });
30
+
31
+ const first = $derived(total === 0 ? 0 : (page - 1) * perPage + 1);
32
+ const last = $derived(Math.min(page * perPage, total));
33
+ </script>
34
+
35
+ {#if pageCount > 1}
36
+ <div class={`gap-4 flex items-center justify-between ${className}`}>
37
+ <p class="text-xs text-muted-foreground">
38
+ {first}–{last} of {total}
39
+ {label}
40
+ </p>
41
+ <div class="gap-1 flex items-center">
42
+ <Button
43
+ size="sm"
44
+ variant="outline"
45
+ disabled={page <= 1}
46
+ onclick={() => (page -= 1)}
47
+ aria-label="Previous page"
48
+ class="h-8 w-8 p-0"
49
+ >
50
+ <ChevronLeft class="h-4 w-4" />
51
+ </Button>
52
+ <span class="px-2 font-mono text-xs text-muted-foreground">{page} / {pageCount}</span>
53
+ <Button
54
+ size="sm"
55
+ variant="outline"
56
+ disabled={page >= pageCount}
57
+ onclick={() => (page += 1)}
58
+ aria-label="Next page"
59
+ class="h-8 w-8 p-0"
60
+ >
61
+ <ChevronRight class="h-4 w-4" />
62
+ </Button>
63
+ </div>
64
+ </div>
65
+ {/if}
@@ -8,3 +8,4 @@ export { default as SideNav } from './SideNav.svelte';
8
8
  export type { SideNavItem } from './SideNav.svelte';
9
9
  export { default as SectionHeader } from './SectionHeader.svelte';
10
10
  export { default as EmptyState } from './EmptyState.svelte';
11
+ export { default as Pagination } from './Pagination.svelte';
@@ -25,7 +25,7 @@
25
25
  bind:ref
26
26
  data-slot="dialog-content"
27
27
  class={cn(
28
- 'gap-4 p-6 shadow-lg data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] rounded-lg border bg-background duration-200',
28
+ 'gap-4 p-6 shadow-lg data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg fixed top-[50%] left-[50%] z-50 grid max-h-[85dvh] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] overflow-y-auto rounded-lg border bg-background duration-200',
29
29
  className
30
30
  )}
31
31
  {...restProps}
@@ -389,7 +389,7 @@
389
389
  <img
390
390
  src={logoUrl}
391
391
  alt=""
392
- class="h-8 max-w-32 drop-shadow-sm sm:h-10 w-auto object-contain opacity-80"
392
+ class="h-10 max-w-40 drop-shadow-sm sm:h-14 sm:max-w-56 w-auto object-contain opacity-80"
393
393
  />
394
394
  </div>
395
395
  {/if}