@nikala-ui/core 0.10.1 → 0.11.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.
Files changed (55) hide show
  1. package/package.json +1 -1
  2. package/registry/bubble.json +18 -0
  3. package/registry/button-group.json +20 -0
  4. package/registry/create-chat-scroll.json +13 -0
  5. package/registry/create-drop-zone.json +13 -0
  6. package/registry/create-pagination.json +13 -0
  7. package/registry/dropzone.json +21 -0
  8. package/registry/footer.json +18 -0
  9. package/registry/forgot-password-01.json +28 -0
  10. package/registry/hero-01.json +22 -0
  11. package/registry/index.json +331 -0
  12. package/registry/login-01.json +28 -0
  13. package/registry/marker.json +17 -0
  14. package/registry/marquee.json +17 -0
  15. package/registry/message.json +17 -0
  16. package/registry/navbar.json +19 -0
  17. package/registry/navigation-menu.json +22 -0
  18. package/registry/otp-verification-01.json +26 -0
  19. package/registry/pagination.json +22 -0
  20. package/registry/rating.json +19 -0
  21. package/registry/register-01.json +31 -0
  22. package/registry/review-card.json +23 -0
  23. package/registry/scroll-area.json +1 -1
  24. package/registry/sidebar.json +24 -0
  25. package/registry/spinner.json +1 -1
  26. package/registry/stat.json +19 -0
  27. package/registry/table.json +17 -0
  28. package/registry/timeline.json +18 -0
  29. package/registry/toggle-group.json +21 -0
  30. package/src/registry/blocks/forgot-password-01.tsx +141 -0
  31. package/src/registry/blocks/hero-01.tsx +28 -0
  32. package/src/registry/blocks/login-01.tsx +231 -0
  33. package/src/registry/blocks/otp-verification-01.tsx +170 -0
  34. package/src/registry/blocks/register-01.tsx +318 -0
  35. package/src/registry/components/ui/bubble.tsx +142 -0
  36. package/src/registry/components/ui/button-group.tsx +42 -0
  37. package/src/registry/components/ui/dropzone.tsx +189 -0
  38. package/src/registry/components/ui/footer.tsx +247 -0
  39. package/src/registry/components/ui/marker.tsx +102 -0
  40. package/src/registry/components/ui/marquee.tsx +118 -0
  41. package/src/registry/components/ui/message.tsx +168 -0
  42. package/src/registry/components/ui/navbar.tsx +368 -0
  43. package/src/registry/components/ui/navigation-menu.tsx +358 -0
  44. package/src/registry/components/ui/pagination.tsx +258 -0
  45. package/src/registry/components/ui/rating.tsx +185 -0
  46. package/src/registry/components/ui/review-card.tsx +195 -0
  47. package/src/registry/components/ui/scroll-area.tsx +2 -2
  48. package/src/registry/components/ui/sidebar.tsx +692 -0
  49. package/src/registry/components/ui/spinner.tsx +1 -1
  50. package/src/registry/components/ui/stat.tsx +245 -0
  51. package/src/registry/components/ui/table.tsx +160 -0
  52. package/src/registry/components/ui/timeline.tsx +350 -0
  53. package/src/registry/components/ui/toggle-group.tsx +203 -0
  54. package/src/registry/index.ts +3 -3
  55. package/src/registry/metadata.ts +141 -0
@@ -0,0 +1,358 @@
1
+ import {
2
+ createContext,
3
+ createSignal,
4
+ useContext,
5
+ splitProps,
6
+ onCleanup,
7
+ type Component,
8
+ type JSX,
9
+ type ParentComponent,
10
+ type Accessor,
11
+ Show,
12
+ } from "solid-js";
13
+ import { cva, type VariantProps } from "class-variance-authority";
14
+ import { createClickOutside } from "@nikala-ui/hooks";
15
+ import { cn } from "@/lib/cn";
16
+ import { ChevronDown } from "lucide-solid";
17
+
18
+ /* --- Context Definition --- */
19
+ interface NavigationMenuContextValue {
20
+ activeValue: Accessor<string | null>;
21
+ setActiveValue: (value: string | null) => void;
22
+ onItemMouseEnter: (value: string) => void;
23
+ onItemMouseLeave: () => void;
24
+ delayMs: Accessor<number>;
25
+ }
26
+
27
+ const NavigationMenuContext = createContext<NavigationMenuContextValue>();
28
+
29
+ export function useNavigationMenu() {
30
+ const context = useContext(NavigationMenuContext);
31
+ if (!context) {
32
+ throw new Error("useNavigationMenu must be used within a <NavigationMenu />");
33
+ }
34
+ return context;
35
+ }
36
+
37
+ /* --- 1. NavigationMenu Root --- */
38
+ export interface NavigationMenuProps extends JSX.HTMLAttributes<HTMLElement> {
39
+ class?: string;
40
+ delayMs?: number;
41
+ value?: string | null;
42
+ onValueChange?: (value: string | null) => void;
43
+ }
44
+
45
+ export const NavigationMenu: ParentComponent<NavigationMenuProps> = (props) => {
46
+ const [local, rest] = splitProps(props, ["class", "delayMs", "value", "onValueChange", "children"]);
47
+
48
+ const [internalValue, setInternalValue] = createSignal<string | null>(local.value ?? null);
49
+ const activeValue = () => (local.value !== undefined ? local.value : internalValue());
50
+
51
+ const setActiveValue = (val: string | null) => {
52
+ if (local.value === undefined) {
53
+ setInternalValue(val);
54
+ }
55
+ local.onValueChange?.(val);
56
+ };
57
+
58
+ let rootRef!: HTMLElement;
59
+ let timer: ReturnType<typeof setTimeout> | undefined;
60
+ const delay = () => local.delayMs ?? 150;
61
+
62
+ const onItemMouseEnter = (val: string) => {
63
+ clearTimeout(timer);
64
+ const ms = delay();
65
+ if (ms <= 0) {
66
+ setActiveValue(val);
67
+ } else {
68
+ timer = setTimeout(() => {
69
+ setActiveValue(val);
70
+ }, ms);
71
+ }
72
+ };
73
+
74
+ const onItemMouseLeave = () => {
75
+ clearTimeout(timer);
76
+ const ms = delay();
77
+ timer = setTimeout(() => {
78
+ setActiveValue(null);
79
+ }, Math.max(50, ms));
80
+ };
81
+
82
+ createClickOutside({
83
+ target: () => rootRef,
84
+ onInteractOutside: () => {
85
+ setActiveValue(null);
86
+ },
87
+ });
88
+
89
+ onCleanup(() => {
90
+ clearTimeout(timer);
91
+ });
92
+
93
+ const contextValue: NavigationMenuContextValue = {
94
+ activeValue,
95
+ setActiveValue,
96
+ onItemMouseEnter,
97
+ onItemMouseLeave,
98
+ delayMs: delay,
99
+ };
100
+
101
+ return (
102
+ <NavigationMenuContext.Provider value={contextValue}>
103
+ <nav
104
+ ref={rootRef}
105
+ aria-label="Main Navigation"
106
+ class={cn("relative z-10 flex max-w-max flex-1 items-center justify-center", local.class)}
107
+ {...rest}
108
+ >
109
+ {local.children}
110
+ </nav>
111
+ </NavigationMenuContext.Provider>
112
+ );
113
+ };
114
+
115
+ /* --- 2. NavigationMenuList --- */
116
+ export interface NavigationMenuListProps extends JSX.HTMLAttributes<HTMLUListElement> {
117
+ class?: string;
118
+ }
119
+
120
+ export const NavigationMenuList: ParentComponent<NavigationMenuListProps> = (props) => {
121
+ const [local, rest] = splitProps(props, ["class", "children"]);
122
+
123
+ return (
124
+ <ul
125
+ class={cn(
126
+ "group flex flex-1 list-none items-center justify-center space-x-1 p-1",
127
+ local.class
128
+ )}
129
+ {...rest}
130
+ >
131
+ {local.children}
132
+ </ul>
133
+ );
134
+ };
135
+
136
+ /* --- 3. NavigationMenuItem Context & Component --- */
137
+ interface NavigationMenuItemContextValue {
138
+ value: string;
139
+ isOpen: Accessor<boolean>;
140
+ }
141
+
142
+ const NavigationMenuItemContext = createContext<NavigationMenuItemContextValue>();
143
+
144
+ function useNavigationMenuItem() {
145
+ const context = useContext(NavigationMenuItemContext);
146
+ if (!context) {
147
+ throw new Error("useNavigationMenuItem must be used within a <NavigationMenuItem />");
148
+ }
149
+ return context;
150
+ }
151
+
152
+ export interface NavigationMenuItemProps extends JSX.HTMLAttributes<HTMLLIElement> {
153
+ value?: string;
154
+ class?: string;
155
+ }
156
+
157
+ let itemCounter = 0;
158
+
159
+ export const NavigationMenuItem: ParentComponent<NavigationMenuItemProps> = (props) => {
160
+ const itemId = props.value ?? `nav-item-${++itemCounter}`;
161
+ const [local, rest] = splitProps(props, ["class", "value", "children"]);
162
+ const rootContext = useNavigationMenu();
163
+
164
+ const isOpen = () => rootContext.activeValue() === itemId;
165
+
166
+ const itemContext: NavigationMenuItemContextValue = {
167
+ value: itemId,
168
+ isOpen,
169
+ };
170
+
171
+ return (
172
+ <NavigationMenuItemContext.Provider value={itemContext}>
173
+ <li
174
+ class={cn("relative", local.class)}
175
+ onMouseEnter={() => rootContext.onItemMouseEnter(itemId)}
176
+ onMouseLeave={() => rootContext.onItemMouseLeave()}
177
+ {...rest}
178
+ >
179
+ {local.children}
180
+ </li>
181
+ </NavigationMenuItemContext.Provider>
182
+ );
183
+ };
184
+
185
+ /* --- 4. Trigger Style Helper --- */
186
+ export const navigationMenuTriggerVariants = cva(
187
+ "group inline-flex h-9 items-center justify-center rounded-md px-3 text-sm font-medium transition-colors focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50 cursor-pointer select-none gap-1",
188
+ {
189
+ variants: {
190
+ variant: {
191
+ default:
192
+ "bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground data-[state=open]:bg-muted data-[state=open]:text-foreground",
193
+ filled:
194
+ "bg-muted/60 text-foreground hover:bg-muted border border-border/40 data-[state=open]:bg-muted",
195
+ ghost:
196
+ "bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent/60",
197
+ outline:
198
+ "border border-border bg-background hover:bg-accent hover:text-accent-foreground shadow-2xs data-[state=open]:bg-accent",
199
+ },
200
+ size: {
201
+ default: "h-9 px-3 text-sm",
202
+ sm: "h-8 px-2.5 text-xs",
203
+ lg: "h-10 px-4 text-base",
204
+ },
205
+ },
206
+ defaultVariants: {
207
+ variant: "default",
208
+ size: "default",
209
+ },
210
+ }
211
+ );
212
+
213
+ export function navigationMenuTriggerStyle(
214
+ variants?: VariantProps<typeof navigationMenuTriggerVariants>
215
+ ) {
216
+ return navigationMenuTriggerVariants(variants);
217
+ }
218
+
219
+ /* --- 5. NavigationMenuTrigger --- */
220
+ export interface NavigationMenuTriggerProps
221
+ extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,
222
+ VariantProps<typeof navigationMenuTriggerVariants> {
223
+ class?: string;
224
+ hideChevron?: boolean;
225
+ }
226
+
227
+ export const NavigationMenuTrigger: ParentComponent<NavigationMenuTriggerProps> = (props) => {
228
+ const [local, rest] = splitProps(props, ["class", "variant", "size", "hideChevron", "children"]);
229
+ const rootContext = useNavigationMenu();
230
+ const itemContext = useNavigationMenuItem();
231
+
232
+ const handleClick = (e: MouseEvent) => {
233
+ e.preventDefault();
234
+ if (itemContext.isOpen()) {
235
+ rootContext.setActiveValue(null);
236
+ } else {
237
+ rootContext.setActiveValue(itemContext.value);
238
+ }
239
+ };
240
+
241
+ return (
242
+ <button
243
+ type="button"
244
+ aria-expanded={itemContext.isOpen()}
245
+ data-state={itemContext.isOpen() ? "open" : "closed"}
246
+ onClick={handleClick}
247
+ class={cn(
248
+ navigationMenuTriggerVariants({ variant: local.variant, size: local.size }),
249
+ "gap-1.5",
250
+ local.class
251
+ )}
252
+ {...rest}
253
+ >
254
+ {local.children}
255
+ <Show when={!local.hideChevron}>
256
+ <ChevronDown
257
+ class={cn(
258
+ "size-3.5 text-muted-foreground transition-transform duration-200",
259
+ itemContext.isOpen() && "rotate-180 text-foreground"
260
+ )}
261
+ aria-hidden="true"
262
+ />
263
+ </Show>
264
+ </button>
265
+ );
266
+ };
267
+
268
+ /* --- 6. NavigationMenuContent (Mega Menu Flyout) --- */
269
+ export interface NavigationMenuContentProps extends JSX.HTMLAttributes<HTMLDivElement> {
270
+ class?: string;
271
+ }
272
+
273
+ export const NavigationMenuContent: ParentComponent<NavigationMenuContentProps> = (props) => {
274
+ const [local, rest] = splitProps(props, ["class", "children"]);
275
+ const itemContext = useNavigationMenuItem();
276
+
277
+ return (
278
+ <Show when={itemContext.isOpen()}>
279
+ <div
280
+ data-state={itemContext.isOpen() ? "open" : "closed"}
281
+ class={cn(
282
+ "absolute left-0 top-full mt-1.5 w-auto rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-lg outline-hidden z-50",
283
+ "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 duration-200",
284
+ local.class
285
+ )}
286
+ {...rest}
287
+ >
288
+ {local.children}
289
+ </div>
290
+ </Show>
291
+ );
292
+ };
293
+
294
+ /* --- 7. NavigationMenuLink --- */
295
+ export interface NavigationMenuLinkProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {
296
+ class?: string;
297
+ active?: boolean;
298
+ }
299
+
300
+ export const NavigationMenuLink: ParentComponent<NavigationMenuLinkProps> = (props) => {
301
+ const [local, rest] = splitProps(props, ["class", "active", "children"]);
302
+
303
+ return (
304
+ <a
305
+ data-active={local.active ? "" : undefined}
306
+ class={cn(
307
+ "block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-hidden transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
308
+ local.active && "bg-accent/60 text-accent-foreground",
309
+ local.class
310
+ )}
311
+ {...rest}
312
+ >
313
+ {local.children}
314
+ </a>
315
+ );
316
+ };
317
+
318
+ /* --- 8. NavigationMenuViewport --- */
319
+ export interface NavigationMenuViewportProps extends JSX.HTMLAttributes<HTMLDivElement> {
320
+ class?: string;
321
+ }
322
+
323
+ export const NavigationMenuViewport: Component<NavigationMenuViewportProps> = (props) => {
324
+ const [local, rest] = splitProps(props, ["class"]);
325
+
326
+ return (
327
+ <div class="absolute left-0 top-full flex justify-center">
328
+ <div
329
+ class={cn(
330
+ "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-lg transition-all",
331
+ local.class
332
+ )}
333
+ {...rest}
334
+ />
335
+ </div>
336
+ );
337
+ };
338
+
339
+ /* --- 9. NavigationMenuIndicator --- */
340
+ export interface NavigationMenuIndicatorProps extends JSX.HTMLAttributes<HTMLDivElement> {
341
+ class?: string;
342
+ }
343
+
344
+ export const NavigationMenuIndicator: Component<NavigationMenuIndicatorProps> = (props) => {
345
+ const [local, rest] = splitProps(props, ["class"]);
346
+
347
+ return (
348
+ <div
349
+ class={cn(
350
+ "top-full z-1 flex h-1.5 items-end justify-center overflow-hidden transition-[width,transform] duration-200",
351
+ local.class
352
+ )}
353
+ {...rest}
354
+ >
355
+ <div class="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-xs bg-border shadow-md" />
356
+ </div>
357
+ );
358
+ };
@@ -0,0 +1,258 @@
1
+ import { splitProps, type Component, type JSX, type ParentComponent, Show } from "solid-js";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/cn";
4
+ import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, MoreHorizontal } from "lucide-solid";
5
+
6
+ /* --- 1. Button Variants for Pagination Links --- */
7
+ export const paginationButtonVariants = cva(
8
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 cursor-pointer select-none",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "hover:bg-accent hover:text-accent-foreground data-[active=true]:bg-primary data-[active=true]:text-primary-foreground data-[active=true]:font-bold data-[active=true]:shadow-2xs",
13
+ outline: "border border-border bg-background hover:bg-accent hover:text-accent-foreground data-[active=true]:border-primary data-[active=true]:bg-primary/10 data-[active=true]:text-primary data-[active=true]:font-bold",
14
+ ghost: "hover:bg-accent/80 hover:text-accent-foreground data-[active=true]:bg-accent data-[active=true]:text-foreground data-[active=true]:font-bold",
15
+ flat: "hover:bg-muted/80 data-[active=true]:bg-muted data-[active=true]:text-foreground data-[active=true]:font-bold",
16
+ },
17
+ size: {
18
+ default: "h-9 min-w-9 px-3 text-sm",
19
+ sm: "h-8 min-w-8 px-2 text-xs",
20
+ lg: "h-10 min-w-10 px-4 text-base",
21
+ icon: "h-9 w-9 p-0 text-sm",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ size: "default",
27
+ },
28
+ }
29
+ );
30
+
31
+ /* --- 2. Pagination Root --- */
32
+ export interface PaginationProps extends JSX.HTMLAttributes<HTMLElement> {
33
+ class?: string;
34
+ }
35
+
36
+ export const Pagination: ParentComponent<PaginationProps> = (props) => {
37
+ const [local, rest] = splitProps(props, ["class", "children"]);
38
+
39
+ return (
40
+ <nav
41
+ role="navigation"
42
+ aria-label="pagination"
43
+ class={cn("mx-auto flex w-full justify-center", local.class)}
44
+ {...rest}
45
+ >
46
+ {local.children}
47
+ </nav>
48
+ );
49
+ };
50
+
51
+ /* --- 3. PaginationContent --- */
52
+ export interface PaginationContentProps extends JSX.HTMLAttributes<HTMLUListElement> {
53
+ class?: string;
54
+ }
55
+
56
+ export const PaginationContent: ParentComponent<PaginationContentProps> = (props) => {
57
+ const [local, rest] = splitProps(props, ["class", "children"]);
58
+
59
+ return (
60
+ <ul
61
+ class={cn("flex flex-row items-center gap-1 flex-wrap", local.class)}
62
+ {...rest}
63
+ >
64
+ {local.children}
65
+ </ul>
66
+ );
67
+ };
68
+
69
+ /* --- 4. PaginationItem --- */
70
+ export interface PaginationItemProps extends JSX.HTMLAttributes<HTMLLIElement> {
71
+ class?: string;
72
+ }
73
+
74
+ export const PaginationItem: ParentComponent<PaginationItemProps> = (props) => {
75
+ const [local, rest] = splitProps(props, ["class", "children"]);
76
+
77
+ return (
78
+ <li class={cn("", local.class)} {...rest}>
79
+ {local.children}
80
+ </li>
81
+ );
82
+ };
83
+
84
+ /* --- 5. PaginationLink --- */
85
+ export interface PaginationLinkProps
86
+ extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,
87
+ VariantProps<typeof paginationButtonVariants> {
88
+ isActive?: boolean;
89
+ class?: string;
90
+ }
91
+
92
+ export const PaginationLink: ParentComponent<PaginationLinkProps> = (props) => {
93
+ const [local, rest] = splitProps(props, ["class", "variant", "size", "isActive", "children"]);
94
+
95
+ return (
96
+ <button
97
+ type="button"
98
+ aria-current={local.isActive ? "page" : undefined}
99
+ data-active={local.isActive ? "true" : "false"}
100
+ class={cn(
101
+ paginationButtonVariants({
102
+ variant: local.variant,
103
+ size: local.size,
104
+ }),
105
+ local.class
106
+ )}
107
+ {...rest}
108
+ >
109
+ {local.children}
110
+ </button>
111
+ );
112
+ };
113
+
114
+ /* --- 6. PaginationPrevious --- */
115
+ export interface PaginationPreviousProps
116
+ extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,
117
+ VariantProps<typeof paginationButtonVariants> {
118
+ class?: string;
119
+ hideText?: boolean;
120
+ }
121
+
122
+ export const PaginationPrevious: Component<PaginationPreviousProps> = (props) => {
123
+ const [local, rest] = splitProps(props, ["class", "variant", "size", "hideText"]);
124
+
125
+ return (
126
+ <PaginationLink
127
+ aria-label="Go to previous page"
128
+ variant={local.variant}
129
+ size={local.size}
130
+ class={cn("gap-1 pl-2.5", local.hideText && "p-0 min-w-8", local.class)}
131
+ {...rest}
132
+ >
133
+ <ChevronLeft class="size-4" />
134
+ <Show when={!local.hideText}>
135
+ <span>Previous</span>
136
+ </Show>
137
+ </PaginationLink>
138
+ );
139
+ };
140
+
141
+ /* --- 7. PaginationNext --- */
142
+ export interface PaginationNextProps
143
+ extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,
144
+ VariantProps<typeof paginationButtonVariants> {
145
+ class?: string;
146
+ hideText?: boolean;
147
+ }
148
+
149
+ export const PaginationNext: Component<PaginationNextProps> = (props) => {
150
+ const [local, rest] = splitProps(props, ["class", "variant", "size", "hideText"]);
151
+
152
+ return (
153
+ <PaginationLink
154
+ aria-label="Go to next page"
155
+ variant={local.variant}
156
+ size={local.size}
157
+ class={cn("gap-1 pr-2.5", local.hideText && "p-0 min-w-8", local.class)}
158
+ {...rest}
159
+ >
160
+ <Show when={!local.hideText}>
161
+ <span>Next</span>
162
+ </Show>
163
+ <ChevronRight class="size-4" />
164
+ </PaginationLink>
165
+ );
166
+ };
167
+
168
+ /* --- 8. PaginationFirst --- */
169
+ export interface PaginationFirstProps
170
+ extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,
171
+ VariantProps<typeof paginationButtonVariants> {
172
+ class?: string;
173
+ hideText?: boolean;
174
+ }
175
+
176
+ export const PaginationFirst: Component<PaginationFirstProps> = (props) => {
177
+ const [local, rest] = splitProps(props, ["class", "variant", "size", "hideText"]);
178
+
179
+ return (
180
+ <PaginationLink
181
+ aria-label="Go to first page"
182
+ variant={local.variant}
183
+ size={local.size}
184
+ class={cn("gap-1 p-0 min-w-8", local.class)}
185
+ {...rest}
186
+ >
187
+ <ChevronsLeft class="size-4" />
188
+ <Show when={!local.hideText}>
189
+ <span class="sr-only">First page</span>
190
+ </Show>
191
+ </PaginationLink>
192
+ );
193
+ };
194
+
195
+ /* --- 9. PaginationLast --- */
196
+ export interface PaginationLastProps
197
+ extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,
198
+ VariantProps<typeof paginationButtonVariants> {
199
+ class?: string;
200
+ hideText?: boolean;
201
+ }
202
+
203
+ export const PaginationLast: Component<PaginationLastProps> = (props) => {
204
+ const [local, rest] = splitProps(props, ["class", "variant", "size", "hideText"]);
205
+
206
+ return (
207
+ <PaginationLink
208
+ aria-label="Go to last page"
209
+ variant={local.variant}
210
+ size={local.size}
211
+ class={cn("gap-1 p-0 min-w-8", local.class)}
212
+ {...rest}
213
+ >
214
+ <ChevronsRight class="size-4" />
215
+ <Show when={!local.hideText}>
216
+ <span class="sr-only">Last page</span>
217
+ </Show>
218
+ </PaginationLink>
219
+ );
220
+ };
221
+
222
+ /* --- 10. PaginationEllipsis --- */
223
+ export interface PaginationEllipsisProps extends JSX.HTMLAttributes<HTMLSpanElement> {
224
+ class?: string;
225
+ }
226
+
227
+ export const PaginationEllipsis: Component<PaginationEllipsisProps> = (props) => {
228
+ const [local, rest] = splitProps(props, ["class"]);
229
+
230
+ return (
231
+ <span
232
+ aria-hidden="true"
233
+ class={cn("flex h-9 w-9 items-center justify-center text-muted-foreground", local.class)}
234
+ {...rest}
235
+ >
236
+ <MoreHorizontal class="size-4" />
237
+ <span class="sr-only">More pages</span>
238
+ </span>
239
+ );
240
+ };
241
+
242
+ /* --- 11. PaginationSummary --- */
243
+ export interface PaginationSummaryProps extends JSX.HTMLAttributes<HTMLDivElement> {
244
+ class?: string;
245
+ }
246
+
247
+ export const PaginationSummary: ParentComponent<PaginationSummaryProps> = (props) => {
248
+ const [local, rest] = splitProps(props, ["class", "children"]);
249
+
250
+ return (
251
+ <div
252
+ class={cn("text-xs text-muted-foreground font-mono select-none", local.class)}
253
+ {...rest}
254
+ >
255
+ {local.children}
256
+ </div>
257
+ );
258
+ };