svelte-comp 1.3.3 → 1.3.6

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.
Files changed (138) hide show
  1. package/LICENSE.md +21 -21
  2. package/README.md +101 -100
  3. package/dist/App.svelte +507 -507
  4. package/dist/Container.svelte +59 -59
  5. package/dist/app.css +234 -235
  6. package/dist/app.d.ts +10 -0
  7. package/dist/lib/Accordion.svelte +155 -155
  8. package/dist/lib/Badge.svelte +44 -44
  9. package/dist/lib/Button.svelte +185 -170
  10. package/dist/lib/Calendar.svelte +384 -384
  11. package/dist/lib/Card.svelte +103 -103
  12. package/dist/lib/Carousel.svelte +293 -293
  13. package/dist/lib/Carousel.svelte.d.ts +1 -1
  14. package/dist/lib/CheckBox.svelte +210 -210
  15. package/dist/lib/CodeView.svelte +308 -307
  16. package/dist/lib/ColorPicker.svelte +159 -159
  17. package/dist/lib/ContextMenu.svelte +328 -322
  18. package/dist/lib/DatePicker.svelte +246 -246
  19. package/dist/lib/Dialog.svelte +233 -233
  20. package/dist/lib/Field.svelte +299 -299
  21. package/dist/lib/FilePicker.svelte +295 -240
  22. package/dist/lib/FilePicker.svelte.d.ts +6 -1
  23. package/dist/lib/Form.svelte +438 -438
  24. package/dist/lib/Hamburger.svelte +217 -217
  25. package/dist/lib/InstallPWA.svelte +94 -94
  26. package/dist/lib/Menu.svelte +623 -623
  27. package/dist/lib/NoticeBase.svelte +140 -140
  28. package/dist/lib/PaginatedCard.svelte +73 -73
  29. package/dist/lib/Pagination.svelte +119 -119
  30. package/dist/lib/PrimaryColorSelect.svelte +111 -111
  31. package/dist/lib/ProgressBar.svelte +141 -141
  32. package/dist/lib/ProgressCircle.svelte +190 -190
  33. package/dist/lib/Radio.svelte +189 -189
  34. package/dist/lib/SearchInput.svelte +104 -104
  35. package/dist/lib/Select.svelte +524 -524
  36. package/dist/lib/Slider.svelte +253 -253
  37. package/dist/lib/Splitter.svelte +159 -150
  38. package/dist/lib/Switch.svelte +168 -167
  39. package/dist/lib/Table.svelte +299 -299
  40. package/dist/lib/Tabs.svelte +213 -213
  41. package/dist/lib/ThemeToggle.svelte +128 -127
  42. package/dist/lib/TimePicker.svelte +312 -312
  43. package/dist/lib/TimePickerNew.svelte +634 -0
  44. package/dist/lib/TimePickerNew.svelte.d.ts +49 -0
  45. package/dist/lib/Toast.svelte +123 -123
  46. package/dist/lib/Tooltip.svelte +110 -110
  47. package/dist/lib/Topbar.svelte +107 -107
  48. package/dist/lib/__tests__/Accordion.test.d.ts +1 -0
  49. package/dist/lib/__tests__/Accordion.test.js +171 -0
  50. package/dist/lib/__tests__/Badge.test.d.ts +1 -0
  51. package/dist/lib/__tests__/Badge.test.js +41 -0
  52. package/dist/lib/__tests__/Button.test.d.ts +1 -0
  53. package/dist/lib/__tests__/Button.test.js +269 -0
  54. package/dist/lib/__tests__/Calendar.test.d.ts +1 -0
  55. package/dist/lib/__tests__/Calendar.test.js +171 -0
  56. package/dist/lib/__tests__/Card.test.d.ts +1 -0
  57. package/dist/lib/__tests__/Card.test.js +148 -0
  58. package/dist/lib/__tests__/Carousel.test.d.ts +1 -0
  59. package/dist/lib/__tests__/Carousel.test.js +439 -0
  60. package/dist/lib/__tests__/CheckBox.test.d.ts +1 -0
  61. package/dist/lib/__tests__/CheckBox.test.js +152 -0
  62. package/dist/lib/__tests__/CodeView.test.d.ts +1 -0
  63. package/dist/lib/__tests__/CodeView.test.js +157 -0
  64. package/dist/lib/__tests__/ColorPicker.test.d.ts +1 -0
  65. package/dist/lib/__tests__/ColorPicker.test.js +93 -0
  66. package/dist/lib/__tests__/ContextMenu.test.d.ts +1 -0
  67. package/dist/lib/__tests__/ContextMenu.test.js +67 -0
  68. package/dist/lib/__tests__/DatePicker.test.d.ts +1 -0
  69. package/dist/lib/__tests__/DatePicker.test.js +108 -0
  70. package/dist/lib/__tests__/Dialog.test.d.ts +1 -0
  71. package/dist/lib/__tests__/Dialog.test.js +183 -0
  72. package/dist/lib/__tests__/Field.test.d.ts +1 -0
  73. package/dist/lib/__tests__/Field.test.js +190 -0
  74. package/dist/lib/__tests__/FilePicker.test.d.ts +1 -0
  75. package/dist/lib/__tests__/FilePicker.test.js +179 -0
  76. package/dist/lib/__tests__/Form.integration.test.d.ts +1 -0
  77. package/dist/lib/__tests__/Form.integration.test.js +158 -0
  78. package/dist/lib/__tests__/Form.test.d.ts +1 -0
  79. package/dist/lib/__tests__/Form.test.js +463 -0
  80. package/dist/lib/__tests__/Hamburger.test.d.ts +1 -0
  81. package/dist/lib/__tests__/Hamburger.test.js +161 -0
  82. package/dist/lib/__tests__/InstallPWA.test.d.ts +1 -0
  83. package/dist/lib/__tests__/InstallPWA.test.js +15 -0
  84. package/dist/lib/__tests__/Menu.test.d.ts +1 -0
  85. package/dist/lib/__tests__/Menu.test.js +285 -0
  86. package/dist/lib/__tests__/NoticeBase.test.d.ts +1 -0
  87. package/dist/lib/__tests__/NoticeBase.test.js +60 -0
  88. package/dist/lib/__tests__/PaginatedCard.test.d.ts +1 -0
  89. package/dist/lib/__tests__/PaginatedCard.test.js +89 -0
  90. package/dist/lib/__tests__/Pagination.test.d.ts +1 -0
  91. package/dist/lib/__tests__/Pagination.test.js +168 -0
  92. package/dist/lib/__tests__/PrimaryColorSelect.test.d.ts +1 -0
  93. package/dist/lib/__tests__/PrimaryColorSelect.test.js +92 -0
  94. package/dist/lib/__tests__/ProgressBar.test.d.ts +1 -0
  95. package/dist/lib/__tests__/ProgressBar.test.js +69 -0
  96. package/dist/lib/__tests__/ProgressCircle.test.d.ts +1 -0
  97. package/dist/lib/__tests__/ProgressCircle.test.js +71 -0
  98. package/dist/lib/__tests__/Radio.test.d.ts +1 -0
  99. package/dist/lib/__tests__/Radio.test.js +127 -0
  100. package/dist/lib/__tests__/SearchInput.test.d.ts +1 -0
  101. package/dist/lib/__tests__/SearchInput.test.js +80 -0
  102. package/dist/lib/__tests__/Select.test.d.ts +1 -0
  103. package/dist/lib/__tests__/Select.test.js +408 -0
  104. package/dist/lib/__tests__/Slider.test.d.ts +1 -0
  105. package/dist/lib/__tests__/Slider.test.js +213 -0
  106. package/dist/lib/__tests__/Splitter.test.d.ts +1 -0
  107. package/dist/lib/__tests__/Splitter.test.js +87 -0
  108. package/dist/lib/__tests__/Switch.test.d.ts +1 -0
  109. package/dist/lib/__tests__/Switch.test.js +97 -0
  110. package/dist/lib/__tests__/Table.test.d.ts +1 -0
  111. package/dist/lib/__tests__/Table.test.js +349 -0
  112. package/dist/lib/__tests__/Tabs.test.d.ts +1 -0
  113. package/dist/lib/__tests__/Tabs.test.js +262 -0
  114. package/dist/lib/__tests__/ThemeToggle.test.d.ts +1 -0
  115. package/dist/lib/__tests__/ThemeToggle.test.js +84 -0
  116. package/dist/lib/__tests__/TimePicker.test.d.ts +1 -0
  117. package/dist/lib/__tests__/TimePicker.test.js +146 -0
  118. package/dist/lib/__tests__/TimePickerNew.test.d.ts +1 -0
  119. package/dist/lib/__tests__/TimePickerNew.test.js +322 -0
  120. package/dist/lib/__tests__/Toast.test.d.ts +1 -0
  121. package/dist/lib/__tests__/Toast.test.js +135 -0
  122. package/dist/lib/__tests__/Tooltip.test.d.ts +1 -0
  123. package/dist/lib/__tests__/Tooltip.test.js +171 -0
  124. package/dist/lib/__tests__/Topbar.test.d.ts +1 -0
  125. package/dist/lib/__tests__/Topbar.test.js +25 -0
  126. package/dist/lib/__tests__/setupLangContext.d.ts +1 -0
  127. package/dist/lib/__tests__/setupLangContext.js +65 -0
  128. package/dist/lib/__tests__/storage.test.d.ts +1 -0
  129. package/dist/lib/__tests__/storage.test.js +124 -0
  130. package/dist/lib/__tests__/utils.test.d.ts +1 -0
  131. package/dist/lib/__tests__/utils.test.js +11 -0
  132. package/dist/lib/index.d.ts +1 -0
  133. package/dist/lib/index.js +1 -0
  134. package/dist/lib/lang.d.ts +4 -0
  135. package/dist/lib/lang.js +4 -0
  136. package/dist/styles.css +234 -232
  137. package/dist/utils/index.js +15 -4
  138. package/package.json +52 -52
@@ -1,140 +1,140 @@
1
- <!-- src/lib/NoticeBase.svelte -->
2
- <script lang="ts">
3
- /**
4
- * @component NoticeBase
5
- * @description Shared base for Toast and Badge visuals.
6
- *
7
- * @prop title {string} - Optional title displayed above the message
8
- *
9
- * @prop message {string} - Notice text content
10
- *
11
- * @prop variant {ToastVariant} - Visual style
12
- * @options success|danger|warning|info
13
- * @default info
14
- *
15
- * @prop showIcon {boolean} - Shows an icon matching the variant
16
- * @default true
17
- *
18
- * @prop inline {boolean} - Inline layout without overlay styling
19
- * @default false
20
- *
21
- * @prop size {"sm" | "md"} - Size preset for spacing and typography
22
- * @default "sm"
23
- *
24
- * @prop end {Snippet} - Trailing content (e.g. close button)
25
- *
26
- * @prop class {string} - Additional wrapper classes
27
- * @default ""
28
- *
29
- * @note Used by Toast and Badge to keep styles consistent.
30
- */
31
- import type { Snippet } from "svelte";
32
- import type { ToastVariant } from "./types";
33
- import { cx } from "../utils";
34
-
35
- type Props = {
36
- title?: string;
37
- message: string;
38
- variant?: ToastVariant;
39
- showIcon?: boolean;
40
- class?: string;
41
- inline?: boolean;
42
- size?: "sm" | "md";
43
- end?: Snippet;
44
- };
45
-
46
- let {
47
- title,
48
- message,
49
- variant = "info",
50
- showIcon = true,
51
- inline = false,
52
- size = "sm",
53
- end,
54
- class: externalClass = "",
55
- }: Props = $props();
56
-
57
- function variantClasses(v: ToastVariant) {
58
- switch (v) {
59
- case "success":
60
- return "bg-[var(--color-bg-success)] text-[var(--color-text-success)]";
61
- case "danger":
62
- return "bg-[var(--color-bg-danger)] text-[var(--color-text-danger)]";
63
- case "warning":
64
- return "bg-[var(--color-bg-warning)] text-[var(--color-text-warning)]";
65
- default:
66
- return "bg-[var(--color-bg-page)] text-[var(--color-text-default)]";
67
- }
68
- }
69
-
70
- const sizeClasses = $derived(
71
- size === "md"
72
- ? "gap-3 px-4 py-3 rounded-[var(--radius-lg)]"
73
- : "gap-2 px-3 py-1.5 rounded-[var(--radius-md)]"
74
- );
75
-
76
- const iconClass = $derived(size === "md" ? "w-5 h-5" : "w-4 h-4");
77
-
78
- const titleClass = $derived(
79
- size === "md"
80
- ? "font-[var(--font-weight-medium)] truncate [font-size:var(--text-md)] max-sm:[font-size:var(--text-sm)]"
81
- : "font-[var(--font-weight-medium)] truncate text-sm"
82
- );
83
-
84
- const messageClass = $derived(
85
- size === "md"
86
- ? "line-clamp-3 [font-size:var(--text-sm)] max-sm:[font-size:var(--text-xs)]"
87
- : "truncate text-xs"
88
- );
89
-
90
- const rootClass = $derived(
91
- cx(
92
- "flex items-center border border-[var(--border-color-default)]",
93
- sizeClasses,
94
- !inline && "shadow-lg backdrop-blur-sm",
95
- variantClasses(variant),
96
- externalClass
97
- )
98
- );
99
- </script>
100
-
101
- <div class={rootClass} role="status" aria-live="polite">
102
- {#if showIcon}
103
- <span class={cx(iconClass, "flex-shrink-0")} aria-hidden="true">
104
- {#if variant === "success"}
105
- <svg fill="none" viewBox="0 0 26 26">
106
- <path d="M8.5 14L11.1 16.6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
107
- <path d="M18.2 10L11.6 16.6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
108
- <path d="M13 25C19.6 25 25 19.6 25 13C25 6.4 19.6 1 13 1C6.4 1 1 6.4 1 13C1 19.6 6.4 25 13 25Z" stroke="currentColor" stroke-width="2"/>
109
- </svg>
110
- {:else if variant === "danger"}
111
- <svg fill="none" viewBox="0 0 26 26">
112
- <path d="M13 25C19.6 25 25 19.6 25 13C25 6.4 19.6 1 13 1C6.4 1 1 6.4 1 13C1 19.6 6.4 25 13 25Z" stroke="currentColor" stroke-width="2"/>
113
- <path d="M9 9.5L16.7 17.3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
114
- <path d="M16.7 9.5L9 17.3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
115
- </svg>
116
- {:else if variant === "warning"}
117
- <svg fill="none" viewBox="0 0 27 27">
118
- <path d="M4.6 25.9H22.5C25.2 25.9 26.8 23 25.6 20.6L16.6 3.8C15.3 1.3 11.8 1.3 10.5 3.8L1.5 20.6C0.3 23 1.9 25.9 4.6 25.9Z" stroke="currentColor" stroke-width="2"/>
119
- <path d="M13.9 18H13.2L11.9 9.3C11.9 8.6 12.5 8 13.2 8H13.9C14.6 8 15.2 8.6 15.2 9.3L13.9 18Z" fill="currentColor"/>
120
- <circle cx="13.5" cy="20.6" r="1.3" fill="currentColor"/>
121
- </svg>
122
- {:else}
123
- <svg fill="none" viewBox="0 0 26 26">
124
- <path d="M13 25C19.6 25 25 19.6 25 13C25 6.4 19.6 1 13 1C6.4 1 1 6.4 1 13C1 19.6 6.4 25 13 25Z" stroke="currentColor" stroke-width="2"/>
125
- <circle cx="13" cy="7.7" r="1.3" fill="currentColor"/>
126
- <rect x="11.6" y="10.3" width="2.7" height="9.4" rx="1.3" fill="currentColor"/>
127
- </svg>
128
- {/if}
129
- </span>
130
- {/if}
131
-
132
- <div class="flex-1 min-w-0">
133
- {#if title}
134
- <div class={titleClass}>{title}</div>
135
- {/if}
136
- <div class={messageClass} title={message}>{message}</div>
137
- </div>
138
-
139
- {@render end?.()}
140
- </div>
1
+ <!-- src/lib/NoticeBase.svelte -->
2
+ <script lang="ts">
3
+ /**
4
+ * @component NoticeBase
5
+ * @description Shared base for Toast and Badge visuals.
6
+ *
7
+ * @prop title {string} - Optional title displayed above the message
8
+ *
9
+ * @prop message {string} - Notice text content
10
+ *
11
+ * @prop variant {ToastVariant} - Visual style
12
+ * @options success|danger|warning|info
13
+ * @default info
14
+ *
15
+ * @prop showIcon {boolean} - Shows an icon matching the variant
16
+ * @default true
17
+ *
18
+ * @prop inline {boolean} - Inline layout without overlay styling
19
+ * @default false
20
+ *
21
+ * @prop size {"sm" | "md"} - Size preset for spacing and typography
22
+ * @default "sm"
23
+ *
24
+ * @prop end {Snippet} - Trailing content (e.g. close button)
25
+ *
26
+ * @prop class {string} - Additional wrapper classes
27
+ * @default ""
28
+ *
29
+ * @note Used by Toast and Badge to keep styles consistent.
30
+ */
31
+ import type { Snippet } from "svelte";
32
+ import type { ToastVariant } from "./types";
33
+ import { cx } from "../utils";
34
+
35
+ type Props = {
36
+ title?: string;
37
+ message: string;
38
+ variant?: ToastVariant;
39
+ showIcon?: boolean;
40
+ class?: string;
41
+ inline?: boolean;
42
+ size?: "sm" | "md";
43
+ end?: Snippet;
44
+ };
45
+
46
+ let {
47
+ title,
48
+ message,
49
+ variant = "info",
50
+ showIcon = true,
51
+ inline = false,
52
+ size = "sm",
53
+ end,
54
+ class: externalClass = "",
55
+ }: Props = $props();
56
+
57
+ function variantClasses(v: ToastVariant) {
58
+ switch (v) {
59
+ case "success":
60
+ return "bg-[var(--color-bg-success)] text-[var(--color-text-success)]";
61
+ case "danger":
62
+ return "bg-[var(--color-bg-danger)] text-[var(--color-text-danger)]";
63
+ case "warning":
64
+ return "bg-[var(--color-bg-warning)] text-[var(--color-text-warning)]";
65
+ default:
66
+ return "bg-[var(--color-bg-page)] text-[var(--color-text-default)]";
67
+ }
68
+ }
69
+
70
+ const sizeClasses = $derived(
71
+ size === "md"
72
+ ? "gap-[calc(var(--spacing-sm)+var(--spacing-xs))] px-[var(--spacing-md)] py-[calc(var(--spacing-sm)+var(--spacing-xs))] rounded-[var(--radius-lg)]"
73
+ : "gap-[var(--spacing-sm)] px-[calc(var(--spacing-sm)+var(--spacing-xs))] py-[calc(var(--spacing-sm)+var(--spacing-xs)/2)] rounded-[var(--radius-md)]"
74
+ );
75
+
76
+ const iconClass = $derived(size === "md" ? "w-5 h-5" : "w-4 h-4");
77
+
78
+ const titleClass = $derived(
79
+ size === "md"
80
+ ? "font-[var(--font-weight-medium)] truncate [font-size:var(--text-md)] max-sm:[font-size:var(--text-sm)]"
81
+ : "font-[var(--font-weight-medium)] truncate [font-size:var(--text-sm)]"
82
+ );
83
+
84
+ const messageClass = $derived(
85
+ size === "md"
86
+ ? "line-clamp-3 [font-size:var(--text-sm)] max-sm:[font-size:var(--text-xs)]"
87
+ : "truncate [font-size:var(--text-xs)]"
88
+ );
89
+
90
+ const rootClass = $derived(
91
+ cx(
92
+ "flex items-center border border-[var(--border-color-default)]",
93
+ sizeClasses,
94
+ !inline && "shadow-[0_8px_16px_var(--shadow-color)] backdrop-blur-sm",
95
+ variantClasses(variant),
96
+ externalClass
97
+ )
98
+ );
99
+ </script>
100
+
101
+ <div class={rootClass} role="status" aria-live="polite">
102
+ {#if showIcon}
103
+ <span class={cx(iconClass, "flex-shrink-0")} aria-hidden="true">
104
+ {#if variant === "success"}
105
+ <svg fill="none" viewBox="0 0 26 26">
106
+ <path d="M8.5 14L11.1 16.6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
107
+ <path d="M18.2 10L11.6 16.6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
108
+ <path d="M13 25C19.6 25 25 19.6 25 13C25 6.4 19.6 1 13 1C6.4 1 1 6.4 1 13C1 19.6 6.4 25 13 25Z" stroke="currentColor" stroke-width="2"/>
109
+ </svg>
110
+ {:else if variant === "danger"}
111
+ <svg fill="none" viewBox="0 0 26 26">
112
+ <path d="M13 25C19.6 25 25 19.6 25 13C25 6.4 19.6 1 13 1C6.4 1 1 6.4 1 13C1 19.6 6.4 25 13 25Z" stroke="currentColor" stroke-width="2"/>
113
+ <path d="M9 9.5L16.7 17.3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
114
+ <path d="M16.7 9.5L9 17.3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
115
+ </svg>
116
+ {:else if variant === "warning"}
117
+ <svg fill="none" viewBox="0 0 27 27">
118
+ <path d="M4.6 25.9H22.5C25.2 25.9 26.8 23 25.6 20.6L16.6 3.8C15.3 1.3 11.8 1.3 10.5 3.8L1.5 20.6C0.3 23 1.9 25.9 4.6 25.9Z" stroke="currentColor" stroke-width="2"/>
119
+ <path d="M13.9 18H13.2L11.9 9.3C11.9 8.6 12.5 8 13.2 8H13.9C14.6 8 15.2 8.6 15.2 9.3L13.9 18Z" fill="currentColor"/>
120
+ <circle cx="13.5" cy="20.6" r="1.3" fill="currentColor"/>
121
+ </svg>
122
+ {:else}
123
+ <svg fill="none" viewBox="0 0 26 26">
124
+ <path d="M13 25C19.6 25 25 19.6 25 13C25 6.4 19.6 1 13 1C6.4 1 1 6.4 1 13C1 19.6 6.4 25 13 25Z" stroke="currentColor" stroke-width="2"/>
125
+ <circle cx="13" cy="7.7" r="1.3" fill="currentColor"/>
126
+ <rect x="11.6" y="10.3" width="2.7" height="9.4" rx="1.3" fill="currentColor"/>
127
+ </svg>
128
+ {/if}
129
+ </span>
130
+ {/if}
131
+
132
+ <div class="flex-1 min-w-0">
133
+ {#if title}
134
+ <div class={titleClass}>{title}</div>
135
+ {/if}
136
+ <div class={messageClass} title={message}>{message}</div>
137
+ </div>
138
+
139
+ {@render end?.()}
140
+ </div>
@@ -1,73 +1,73 @@
1
- <!-- src/lib/PaginatedCard.svelte -->
2
- <script lang="ts">
3
- /**
4
- * @component PaginatedCard
5
- * @description A card component with built-in pagination. Renders items page by page inside a `Card` and appends `Pagination` in the footer.
6
- *
7
- * @prop items {Snippet[]} - Array of renderable snippets for each item
8
- * @default []
9
- *
10
- * @prop itemsPerPage {number} - Items per page (must be >= 1)
11
- * @default 1
12
- *
13
- * @prop header {Snippet} - Optional `Card` header content
14
- *
15
- * @prop footer {Snippet} - Custom footer content shown above pagination
16
- *
17
- * @prop class {string} - Extra classes passed to the underlying `Card`
18
- * @default ""
19
- *
20
- * @note Maintains internal `currentPage` state (starts at `1`).
21
- * @note `totalPages` is clamped to at least `1`; empty `items` still yields one page.
22
- * @note Pagination is always visible; your `footer` snippet renders before it.
23
- * @note Uses `Pagination.svelte` internally with `{ currentPage, totalPages, onPageChange }`.
24
- * @note `itemsPerPage` must be `>= 1`; smaller values are not supported.
25
- */
26
- import Card from "./Card.svelte";
27
- import Pagination from "./Pagination.svelte";
28
- import type { Snippet } from "svelte";
29
-
30
- type Props = {
31
- items?: Snippet[];
32
- itemsPerPage?: number;
33
- header?: Snippet;
34
- footer?: Snippet;
35
- class?: string;
36
- };
37
-
38
- let {
39
- items = [],
40
- itemsPerPage = 1,
41
- header,
42
- footer,
43
- class: externalClass = "",
44
- }: Props = $props();
45
-
46
- let currentPage = $state(1);
47
-
48
- const totalPages = $derived(
49
- Math.max(1, Math.ceil(items.length / itemsPerPage))
50
- );
51
-
52
- const pageItems = $derived(
53
- items.slice(
54
- (currentPage - 1) * itemsPerPage,
55
- (currentPage - 1) * itemsPerPage + itemsPerPage
56
- )
57
- );
58
-
59
- function handlePageChange(p: number) {
60
- currentPage = p;
61
- }
62
- </script>
63
-
64
- {#snippet composedFooter()}
65
- {#if footer}{@render footer?.()}{/if}
66
- <Pagination {currentPage} {totalPages} onPageChange={handlePageChange} />
67
- {/snippet}
68
-
69
- <Card class={externalClass} {header} footer={composedFooter}>
70
- {#each pageItems as it, idx (idx)}
71
- {@render it?.()}
72
- {/each}
73
- </Card>
1
+ <!-- src/lib/PaginatedCard.svelte -->
2
+ <script lang="ts">
3
+ /**
4
+ * @component PaginatedCard
5
+ * @description A card component with built-in pagination. Renders items page by page inside a `Card` and appends `Pagination` in the footer.
6
+ *
7
+ * @prop items {Snippet[]} - Array of renderable snippets for each item
8
+ * @default []
9
+ *
10
+ * @prop itemsPerPage {number} - Items per page (must be >= 1)
11
+ * @default 1
12
+ *
13
+ * @prop header {Snippet} - Optional `Card` header content
14
+ *
15
+ * @prop footer {Snippet} - Custom footer content shown above pagination
16
+ *
17
+ * @prop class {string} - Extra classes passed to the underlying `Card`
18
+ * @default ""
19
+ *
20
+ * @note Maintains internal `currentPage` state (starts at `1`).
21
+ * @note `totalPages` is clamped to at least `1`; empty `items` still yields one page.
22
+ * @note Pagination is always visible; your `footer` snippet renders before it.
23
+ * @note Uses `Pagination.svelte` internally with `{ currentPage, totalPages, onPageChange }`.
24
+ * @note `itemsPerPage` must be `>= 1`; smaller values are not supported.
25
+ */
26
+ import Card from "./Card.svelte";
27
+ import Pagination from "./Pagination.svelte";
28
+ import type { Snippet } from "svelte";
29
+
30
+ type Props = {
31
+ items?: Snippet[];
32
+ itemsPerPage?: number;
33
+ header?: Snippet;
34
+ footer?: Snippet;
35
+ class?: string;
36
+ };
37
+
38
+ let {
39
+ items = [],
40
+ itemsPerPage = 1,
41
+ header,
42
+ footer,
43
+ class: externalClass = "",
44
+ }: Props = $props();
45
+
46
+ let currentPage = $state(1);
47
+
48
+ const totalPages = $derived(
49
+ Math.max(1, Math.ceil(items.length / itemsPerPage))
50
+ );
51
+
52
+ const pageItems = $derived(
53
+ items.slice(
54
+ (currentPage - 1) * itemsPerPage,
55
+ (currentPage - 1) * itemsPerPage + itemsPerPage
56
+ )
57
+ );
58
+
59
+ function handlePageChange(p: number) {
60
+ currentPage = p;
61
+ }
62
+ </script>
63
+
64
+ {#snippet composedFooter()}
65
+ {#if footer}{@render footer?.()}{/if}
66
+ <Pagination {currentPage} {totalPages} onPageChange={handlePageChange} />
67
+ {/snippet}
68
+
69
+ <Card class={externalClass} {header} footer={composedFooter}>
70
+ {#each pageItems as it, idx (idx)}
71
+ {@render it?.()}
72
+ {/each}
73
+ </Card>
@@ -1,119 +1,119 @@
1
- <!-- src/lib/Pagination.svelte -->
2
- <script lang="ts">
3
- /**
4
- * @component Pagination
5
- * @description Compact pagination component for table or list navigation.
6
- *
7
- * @prop currentPage {number} - The active page number (1-based)
8
- *
9
- * @prop totalPages {number} - Total number of pages available
10
- *
11
- * @prop onPageChange {(page: number) => void} - Fired when a page button is clicked
12
- *
13
- * @prop class {string} - Custom classes applied to the pagination wrapper
14
- * @default ""
15
- *
16
- * @note Displays “Page X of Y” and numbered page buttons.
17
- * @note Prev/next buttons are disabled at the edges.
18
- * @note Shows up to 3 numbered buttons centered around the current page.
19
- * @note Uses `aria-current=\"page\"` on the active page for accessibility.
20
- * @note Buttons are native `<button>` elements for keyboard support.
21
- */
22
- import { cx, times } from "../utils";
23
- import Button from "./Button.svelte";
24
-
25
- type Props = {
26
- currentPage: number;
27
- totalPages: number;
28
- onPageChange: (page: number) => void;
29
- class?: string;
30
- };
31
-
32
- let {
33
- currentPage,
34
- totalPages,
35
- onPageChange,
36
- class: externalClass = "",
37
- }: Props = $props();
38
-
39
- const wrapperClass = $derived(
40
- cx(
41
- "flex flex-wrap items-center justify-center gap-2 text-xs text-[var(--color-text-muted)] py-0.5 overflow-visible",
42
- externalClass
43
- )
44
- );
45
-
46
- function nextPage() {
47
- if (currentPage < totalPages) onPageChange(currentPage + 1);
48
- }
49
-
50
- function prevPage() {
51
- if (currentPage > 1) onPageChange(currentPage - 1);
52
- }
53
-
54
- function getVisiblePages(): number[] {
55
- const maxVisible = 3;
56
- if (totalPages <= maxVisible) return times(totalPages, (i) => i + 1);
57
-
58
- let start = currentPage - 1;
59
- if (start < 1) start = 1;
60
- if (start + maxVisible - 1 > totalPages)
61
- start = totalPages - maxVisible + 1;
62
-
63
- return times(maxVisible, (i) => start + i);
64
- }
65
- </script>
66
-
67
- <div class={wrapperClass}>
68
- <span class="pagination-count">Page {currentPage} of {totalPages}</span>
69
-
70
- <Button
71
- onClick={prevPage}
72
- disabled={currentPage === 1}
73
- sz="xs"
74
- variant="secondary"
75
- class="pagination-btn"
76
- >
77
- &lt;
78
- </Button>
79
-
80
- {#each getVisiblePages() as page (page)}
81
- <Button
82
- onClick={() => onPageChange(page)}
83
- sz="xs"
84
- variant={currentPage === page ? "primary" : "secondary"}
85
- aria-current={currentPage === page ? "page" : undefined}
86
- class="pagination-btn"
87
- >
88
- {page}
89
- </Button>
90
- {/each}
91
-
92
- <Button
93
- onClick={nextPage}
94
- disabled={currentPage === totalPages}
95
- sz="xs"
96
- variant="secondary"
97
- class="pagination-btn"
98
- >
99
- &gt;
100
- </Button>
101
- </div>
102
-
103
- <style>
104
- @media (max-width: 640px) {
105
- :global(.pagination-btn) {
106
- font-size: 10px;
107
- line-height: 1;
108
- height: 20px;
109
- padding: 0 6px;
110
- }
111
- }
112
-
113
- @media (max-width: 480px) {
114
- :global(.pagination-count) {
115
- display: none;
116
- }
117
- }
118
- </style>
119
-
1
+ <!-- src/lib/Pagination.svelte -->
2
+ <script lang="ts">
3
+ /**
4
+ * @component Pagination
5
+ * @description Compact pagination component for table or list navigation.
6
+ *
7
+ * @prop currentPage {number} - The active page number (1-based)
8
+ *
9
+ * @prop totalPages {number} - Total number of pages available
10
+ *
11
+ * @prop onPageChange {(page: number) => void} - Fired when a page button is clicked
12
+ *
13
+ * @prop class {string} - Custom classes applied to the pagination wrapper
14
+ * @default ""
15
+ *
16
+ * @note Displays “Page X of Y” and numbered page buttons.
17
+ * @note Prev/next buttons are disabled at the edges.
18
+ * @note Shows up to 3 numbered buttons centered around the current page.
19
+ * @note Uses `aria-current=\"page\"` on the active page for accessibility.
20
+ * @note Buttons are native `<button>` elements for keyboard support.
21
+ */
22
+ import { cx, times } from "../utils";
23
+ import Button from "./Button.svelte";
24
+
25
+ type Props = {
26
+ currentPage: number;
27
+ totalPages: number;
28
+ onPageChange: (page: number) => void;
29
+ class?: string;
30
+ };
31
+
32
+ let {
33
+ currentPage,
34
+ totalPages,
35
+ onPageChange,
36
+ class: externalClass = "",
37
+ }: Props = $props();
38
+
39
+ const wrapperClass = $derived(
40
+ cx(
41
+ "flex flex-wrap items-center justify-center gap-2 text-xs text-[var(--color-text-muted)] py-0.5 overflow-visible",
42
+ externalClass
43
+ )
44
+ );
45
+
46
+ function nextPage() {
47
+ if (currentPage < totalPages) onPageChange(currentPage + 1);
48
+ }
49
+
50
+ function prevPage() {
51
+ if (currentPage > 1) onPageChange(currentPage - 1);
52
+ }
53
+
54
+ function getVisiblePages(): number[] {
55
+ const maxVisible = 3;
56
+ if (totalPages <= maxVisible) return times(totalPages, (i) => i + 1);
57
+
58
+ let start = currentPage - 1;
59
+ if (start < 1) start = 1;
60
+ if (start + maxVisible - 1 > totalPages)
61
+ start = totalPages - maxVisible + 1;
62
+
63
+ return times(maxVisible, (i) => start + i);
64
+ }
65
+ </script>
66
+
67
+ <div class={wrapperClass}>
68
+ <span class="pagination-count">Page {currentPage} of {totalPages}</span>
69
+
70
+ <Button
71
+ onClick={prevPage}
72
+ disabled={currentPage === 1}
73
+ sz="xs"
74
+ variant="secondary"
75
+ class="pagination-btn"
76
+ >
77
+ &lt;
78
+ </Button>
79
+
80
+ {#each getVisiblePages() as page (page)}
81
+ <Button
82
+ onClick={() => onPageChange(page)}
83
+ sz="xs"
84
+ variant={currentPage === page ? "primary" : "secondary"}
85
+ aria-current={currentPage === page ? "page" : undefined}
86
+ class="pagination-btn"
87
+ >
88
+ {page}
89
+ </Button>
90
+ {/each}
91
+
92
+ <Button
93
+ onClick={nextPage}
94
+ disabled={currentPage === totalPages}
95
+ sz="xs"
96
+ variant="secondary"
97
+ class="pagination-btn"
98
+ >
99
+ &gt;
100
+ </Button>
101
+ </div>
102
+
103
+ <style>
104
+ @media (max-width: 640px) {
105
+ :global(.pagination-btn) {
106
+ font-size: 10px;
107
+ line-height: 1;
108
+ height: 20px;
109
+ padding: 0 6px;
110
+ }
111
+ }
112
+
113
+ @media (max-width: 480px) {
114
+ :global(.pagination-count) {
115
+ display: none;
116
+ }
117
+ }
118
+ </style>
119
+