@open-mercato/ui 0.6.4-develop.4178.1.aad9ddaa95 → 0.6.4-develop.4210.1.d412061cfe
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +14 -0
- package/dist/backend/DataTable.js +28 -62
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/detail/AttachmentMetadataDialog.js +1 -1
- package/dist/backend/detail/AttachmentMetadataDialog.js.map +2 -2
- package/dist/primitives/activity-feed.js +196 -0
- package/dist/primitives/activity-feed.js.map +7 -0
- package/dist/primitives/avatar.js +140 -14
- package/dist/primitives/avatar.js.map +3 -3
- package/dist/primitives/badge.js +94 -5
- package/dist/primitives/badge.js.map +2 -2
- package/dist/primitives/button-group.js +52 -0
- package/dist/primitives/button-group.js.map +7 -0
- package/dist/primitives/color-picker.js +520 -0
- package/dist/primitives/color-picker.js.map +7 -0
- package/dist/primitives/command-menu.js +355 -0
- package/dist/primitives/command-menu.js.map +7 -0
- package/dist/primitives/dialog.js +210 -35
- package/dist/primitives/dialog.js.map +2 -2
- package/dist/primitives/drawer.js +219 -0
- package/dist/primitives/drawer.js.map +7 -0
- package/dist/primitives/notification-feed.js +213 -0
- package/dist/primitives/notification-feed.js.map +7 -0
- package/dist/primitives/notification.js +23 -2
- package/dist/primitives/notification.js.map +2 -2
- package/dist/primitives/pagination.js +245 -0
- package/dist/primitives/pagination.js.map +7 -0
- package/dist/primitives/progress.js +178 -5
- package/dist/primitives/progress.js.map +2 -2
- package/dist/primitives/rating.js +174 -0
- package/dist/primitives/rating.js.map +7 -0
- package/dist/primitives/scroll-area.js +82 -0
- package/dist/primitives/scroll-area.js.map +7 -0
- package/dist/primitives/segmented-control.js +101 -0
- package/dist/primitives/segmented-control.js.map +7 -0
- package/dist/primitives/separator.js +103 -4
- package/dist/primitives/separator.js.map +2 -2
- package/dist/primitives/slider.js +115 -0
- package/dist/primitives/slider.js.map +7 -0
- package/dist/primitives/step-indicator.js +187 -0
- package/dist/primitives/step-indicator.js.map +7 -0
- package/dist/primitives/table.js +102 -10
- package/dist/primitives/table.js.map +2 -2
- package/dist/primitives/tabs.js +122 -14
- package/dist/primitives/tabs.js.map +2 -2
- package/dist/primitives/time-picker.js +9 -11
- package/dist/primitives/time-picker.js.map +2 -2
- package/package.json +8 -4
- package/src/backend/DataTable.tsx +43 -60
- package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
- package/src/primitives/__tests__/activity-feed.test.tsx +216 -0
- package/src/primitives/__tests__/avatar.test.tsx +131 -0
- package/src/primitives/__tests__/badge.test.tsx +189 -0
- package/src/primitives/__tests__/button-group.test.tsx +117 -0
- package/src/primitives/__tests__/color-picker.test.tsx +589 -0
- package/src/primitives/__tests__/command-menu.test.tsx +231 -0
- package/src/primitives/__tests__/dialog.test.tsx +290 -0
- package/src/primitives/__tests__/drawer.test.tsx +264 -0
- package/src/primitives/__tests__/notification-feed.test.tsx +303 -0
- package/src/primitives/__tests__/notification.test.tsx +102 -2
- package/src/primitives/__tests__/pagination.test.tsx +268 -0
- package/src/primitives/__tests__/progress.test.tsx +222 -0
- package/src/primitives/__tests__/rating.test.tsx +164 -0
- package/src/primitives/__tests__/scroll-area.test.tsx +109 -0
- package/src/primitives/__tests__/segmented-control.test.tsx +129 -0
- package/src/primitives/__tests__/separator.test.tsx +143 -0
- package/src/primitives/__tests__/slider.test.tsx +86 -0
- package/src/primitives/__tests__/step-indicator.test.tsx +205 -0
- package/src/primitives/__tests__/table.test.tsx +199 -0
- package/src/primitives/__tests__/tabs.test.tsx +139 -0
- package/src/primitives/activity-feed.tsx +326 -0
- package/src/primitives/avatar.tsx +225 -22
- package/src/primitives/badge.tsx +141 -6
- package/src/primitives/button-group.tsx +100 -0
- package/src/primitives/color-picker.tsx +693 -0
- package/src/primitives/command-menu.tsx +503 -0
- package/src/primitives/dialog.tsx +315 -51
- package/src/primitives/drawer.tsx +338 -0
- package/src/primitives/notification-feed.tsx +358 -0
- package/src/primitives/notification.tsx +42 -2
- package/src/primitives/pagination.tsx +376 -0
- package/src/primitives/progress.tsx +267 -6
- package/src/primitives/rating.tsx +245 -0
- package/src/primitives/scroll-area.tsx +169 -0
- package/src/primitives/segmented-control.tsx +162 -0
- package/src/primitives/separator.tsx +163 -3
- package/src/primitives/slider.tsx +160 -0
- package/src/primitives/step-indicator.tsx +287 -0
- package/src/primitives/table.tsx +142 -11
- package/src/primitives/tabs.tsx +174 -12
- package/src/primitives/time-picker.tsx +9 -11
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
6
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
7
|
+
const NotificationFeed = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
8
|
+
"div",
|
|
9
|
+
{
|
|
10
|
+
ref,
|
|
11
|
+
"data-slot": "notification-feed",
|
|
12
|
+
className: cn(
|
|
13
|
+
"flex w-full flex-col overflow-hidden rounded-xl border border-input bg-background shadow-lg",
|
|
14
|
+
className
|
|
15
|
+
),
|
|
16
|
+
...props
|
|
17
|
+
}
|
|
18
|
+
));
|
|
19
|
+
NotificationFeed.displayName = "NotificationFeed";
|
|
20
|
+
const NotificationFeedHeader = React.forwardRef(
|
|
21
|
+
({ className, title, children, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
22
|
+
"div",
|
|
23
|
+
{
|
|
24
|
+
ref,
|
|
25
|
+
"data-slot": "notification-feed-header",
|
|
26
|
+
className: cn(
|
|
27
|
+
"flex items-center justify-between gap-3 border-b border-input px-4 py-3",
|
|
28
|
+
className
|
|
29
|
+
),
|
|
30
|
+
...props,
|
|
31
|
+
children: [
|
|
32
|
+
title ? /* @__PURE__ */ jsx(
|
|
33
|
+
"h3",
|
|
34
|
+
{
|
|
35
|
+
"data-slot": "notification-feed-header-title",
|
|
36
|
+
className: "text-base font-semibold text-foreground",
|
|
37
|
+
children: title
|
|
38
|
+
}
|
|
39
|
+
) : null,
|
|
40
|
+
children ? /* @__PURE__ */ jsx("div", { "data-slot": "notification-feed-header-actions", className: "inline-flex items-center gap-1", children }) : null
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
NotificationFeedHeader.displayName = "NotificationFeedHeader";
|
|
46
|
+
const NotificationFeedList = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
47
|
+
"ol",
|
|
48
|
+
{
|
|
49
|
+
ref,
|
|
50
|
+
"data-slot": "notification-feed-list",
|
|
51
|
+
className: cn("flex flex-col divide-y divide-input", className),
|
|
52
|
+
...props
|
|
53
|
+
}
|
|
54
|
+
));
|
|
55
|
+
NotificationFeedList.displayName = "NotificationFeedList";
|
|
56
|
+
const NotificationFeedItem = React.forwardRef(
|
|
57
|
+
({
|
|
58
|
+
className,
|
|
59
|
+
icon,
|
|
60
|
+
title,
|
|
61
|
+
body,
|
|
62
|
+
timestamp,
|
|
63
|
+
unread = false,
|
|
64
|
+
actions,
|
|
65
|
+
onClick,
|
|
66
|
+
clickAriaLabel,
|
|
67
|
+
children,
|
|
68
|
+
...props
|
|
69
|
+
}, ref) => {
|
|
70
|
+
const t = useT();
|
|
71
|
+
const interactive = typeof onClick === "function";
|
|
72
|
+
return /* @__PURE__ */ jsxs(
|
|
73
|
+
"li",
|
|
74
|
+
{
|
|
75
|
+
ref,
|
|
76
|
+
"data-slot": "notification-feed-item",
|
|
77
|
+
"data-unread": unread || void 0,
|
|
78
|
+
className: cn(
|
|
79
|
+
"group flex items-start gap-3 px-4 py-3",
|
|
80
|
+
interactive ? "cursor-pointer hover:bg-muted/40 focus-within:bg-muted/40" : "",
|
|
81
|
+
className
|
|
82
|
+
),
|
|
83
|
+
onClick,
|
|
84
|
+
role: interactive ? "button" : void 0,
|
|
85
|
+
tabIndex: interactive ? 0 : void 0,
|
|
86
|
+
"aria-label": interactive ? clickAriaLabel ?? (typeof title === "string" ? title : t("ui.notificationFeed.item.openAriaLabel", "Open notification")) : void 0,
|
|
87
|
+
onKeyDown: interactive ? (event) => {
|
|
88
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
onClick?.();
|
|
91
|
+
}
|
|
92
|
+
} : void 0,
|
|
93
|
+
...props,
|
|
94
|
+
children: [
|
|
95
|
+
icon ? /* @__PURE__ */ jsx(
|
|
96
|
+
"span",
|
|
97
|
+
{
|
|
98
|
+
"data-slot": "notification-feed-item-icon",
|
|
99
|
+
className: "inline-flex shrink-0 items-center justify-center",
|
|
100
|
+
children: icon
|
|
101
|
+
}
|
|
102
|
+
) : null,
|
|
103
|
+
/* @__PURE__ */ jsxs("div", { "data-slot": "notification-feed-item-body", className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
104
|
+
/* @__PURE__ */ jsxs(
|
|
105
|
+
"div",
|
|
106
|
+
{
|
|
107
|
+
"data-slot": "notification-feed-item-title",
|
|
108
|
+
className: "flex items-center gap-2 text-sm font-semibold text-foreground",
|
|
109
|
+
children: [
|
|
110
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: title }),
|
|
111
|
+
unread ? /* @__PURE__ */ jsx(
|
|
112
|
+
"span",
|
|
113
|
+
{
|
|
114
|
+
"data-slot": "notification-feed-item-unread-dot",
|
|
115
|
+
"aria-hidden": "true",
|
|
116
|
+
className: "inline-block size-2 shrink-0 rounded-full bg-accent-indigo"
|
|
117
|
+
}
|
|
118
|
+
) : null
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
),
|
|
122
|
+
body ? /* @__PURE__ */ jsx(
|
|
123
|
+
"div",
|
|
124
|
+
{
|
|
125
|
+
"data-slot": "notification-feed-item-text",
|
|
126
|
+
className: "text-sm leading-5 text-muted-foreground",
|
|
127
|
+
children: body
|
|
128
|
+
}
|
|
129
|
+
) : null,
|
|
130
|
+
timestamp ? /* @__PURE__ */ jsx(
|
|
131
|
+
"div",
|
|
132
|
+
{
|
|
133
|
+
"data-slot": "notification-feed-item-timestamp",
|
|
134
|
+
className: "text-xs leading-4 text-muted-foreground",
|
|
135
|
+
children: timestamp
|
|
136
|
+
}
|
|
137
|
+
) : null,
|
|
138
|
+
children ? /* @__PURE__ */ jsx("div", { "data-slot": "notification-feed-item-children", className: "mt-2 flex flex-wrap gap-2", children }) : null
|
|
139
|
+
] }),
|
|
140
|
+
actions ? /* @__PURE__ */ jsx(
|
|
141
|
+
"div",
|
|
142
|
+
{
|
|
143
|
+
"data-slot": "notification-feed-item-actions",
|
|
144
|
+
className: "ml-auto inline-flex shrink-0 items-start opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100",
|
|
145
|
+
onClick: (event) => event.stopPropagation(),
|
|
146
|
+
children: actions
|
|
147
|
+
}
|
|
148
|
+
) : null
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
NotificationFeedItem.displayName = "NotificationFeedItem";
|
|
155
|
+
const NotificationFeedFooter = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
156
|
+
"div",
|
|
157
|
+
{
|
|
158
|
+
ref,
|
|
159
|
+
"data-slot": "notification-feed-footer",
|
|
160
|
+
className: cn(
|
|
161
|
+
"flex items-center justify-between gap-3 border-t border-input px-4 py-3",
|
|
162
|
+
className
|
|
163
|
+
),
|
|
164
|
+
...props
|
|
165
|
+
}
|
|
166
|
+
));
|
|
167
|
+
NotificationFeedFooter.displayName = "NotificationFeedFooter";
|
|
168
|
+
const ICON_TONE_MAP = {
|
|
169
|
+
// Tints + matching icon color per Figma `Notifications Items [1.1]`
|
|
170
|
+
// assembled examples. Surface is a soft `tone/10` background; icon
|
|
171
|
+
// carries the semantic color directly.
|
|
172
|
+
indigo: "bg-accent-indigo/10 text-accent-indigo",
|
|
173
|
+
success: "bg-status-success-icon/10 text-status-success-icon",
|
|
174
|
+
warning: "bg-status-warning-icon/10 text-status-warning-icon",
|
|
175
|
+
error: "bg-status-error-icon/10 text-status-error-icon",
|
|
176
|
+
info: "bg-status-info-icon/10 text-status-info-icon",
|
|
177
|
+
brand: "bg-brand-violet/10 text-brand-violet",
|
|
178
|
+
neutral: "bg-muted text-muted-foreground"
|
|
179
|
+
};
|
|
180
|
+
const iconBadgeVariants = cva(
|
|
181
|
+
"inline-flex shrink-0 items-center justify-center rounded-full",
|
|
182
|
+
{
|
|
183
|
+
variants: {
|
|
184
|
+
tone: ICON_TONE_MAP,
|
|
185
|
+
size: {
|
|
186
|
+
sm: "size-8",
|
|
187
|
+
default: "size-10"
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
defaultVariants: { tone: "indigo", size: "default" }
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
const NotificationFeedIconBadge = React.forwardRef(({ className, tone, size, children, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
194
|
+
"span",
|
|
195
|
+
{
|
|
196
|
+
ref,
|
|
197
|
+
"data-slot": "notification-feed-icon-badge",
|
|
198
|
+
"data-tone": tone ?? "indigo",
|
|
199
|
+
className: cn(iconBadgeVariants({ tone, size }), className),
|
|
200
|
+
...props,
|
|
201
|
+
children
|
|
202
|
+
}
|
|
203
|
+
));
|
|
204
|
+
NotificationFeedIconBadge.displayName = "NotificationFeedIconBadge";
|
|
205
|
+
export {
|
|
206
|
+
NotificationFeed,
|
|
207
|
+
NotificationFeedFooter,
|
|
208
|
+
NotificationFeedHeader,
|
|
209
|
+
NotificationFeedIconBadge,
|
|
210
|
+
NotificationFeedItem,
|
|
211
|
+
NotificationFeedList
|
|
212
|
+
};
|
|
213
|
+
//# sourceMappingURL=notification-feed.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/primitives/notification-feed.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\n/**\n * Bell-icon inbox / notification list \u2014 the panel that opens when the\n * user clicks the bell affordance in the app shell. Distinct from\n * `Notification` (single toast in the top-right stack) and from\n * `ActivityFeed` (chronological audit log scoped to one entity).\n *\n * Compound API:\n *\n * <NotificationFeed> \u2014 root container card (rounded,\n * bg-background, overflow-hidden).\n * <NotificationFeedHeader> \u2014 top row: title + actions slot\n * (settings cog, \"Mark all as read\"\n * link, etc.). Bordered bottom.\n * <NotificationFeedList> \u2014 `<ol>` list, `divide-y` so items\n * auto-separate.\n * <NotificationFeedItem> \u2014 single notification entry. Slots:\n * `icon` \u2014 left, typically a\n * `NotificationFeedIconBadge`,\n * `title` \u2014 bold headline,\n * `body` \u2014 muted description,\n * `timestamp` \u2014 smaller muted suffix,\n * `actions` \u2014 right slot\n * (kebab IconButton),\n * `children` \u2014 optional indented\n * content (inline\n * action buttons,\n * file chip, reply).\n * `unread` toggles a small dot near\n * the title. `onClick` turns the\n * whole row into a clickable\n * affordance.\n * <NotificationFeedFooter> \u2014 bottom row. Bordered top. Typical\n * content: a full-width Archive All\n * button OR keyboard-hint + settings\n * link (matches the two Figma\n * `Notifications Footer [1.1]` /\n * block-example variants).\n * <NotificationFeedIconBadge> \u2014 helper: `size-10 rounded-full\n * bg-{tone}/10 text-{tone}` badge\n * matching the Figma assembled\n * example icon style.\n *\n * Figma source: DS Open Mercato `Notifications` page (`4096:21398`) \u2014\n * `Notifications Items [1.1]` (`4308:731`, 8 variants \u2014 4 designs\n * \u00D7 2 states: default + hover/selected), `Notifications Header [1.1]`\n * (`4308:1004`), `Notifications Footer [1.1]` (`4308:5526`),\n * `Notifications Tab Menu [1.1]` (`4349:46656`); assembled examples\n * `166926:7047`, `166926:7088`, `166926:7114`, `166926:7138`.\n *\n * ```tsx\n * <NotificationFeed>\n * <NotificationFeedHeader title=\"Notifications\">\n * <IconButton size=\"sm\" variant=\"ghost\" aria-label=\"Settings\">\n * <Settings />\n * </IconButton>\n * </NotificationFeedHeader>\n *\n * <NotificationFeedList>\n * <NotificationFeedItem\n * icon={\n * <NotificationFeedIconBadge tone=\"indigo\">\n * <UserPlus className=\"size-5\" />\n * </NotificationFeedIconBadge>\n * }\n * title=\"New Lead Generated\"\n * body=\"John Smith submitted web form\"\n * timestamp=\"10 minutes ago\"\n * unread\n * onClick={() => router.push('/leads/123')}\n * />\n * </NotificationFeedList>\n *\n * <NotificationFeedFooter>\n * <Button variant=\"outline\" className=\"w-full\">Archive all</Button>\n * </NotificationFeedFooter>\n * </NotificationFeed>\n * ```\n */\n\nconst NotificationFeed = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"notification-feed\"\n className={cn(\n 'flex w-full flex-col overflow-hidden rounded-xl border border-input bg-background shadow-lg',\n className,\n )}\n {...props}\n />\n))\nNotificationFeed.displayName = 'NotificationFeed'\n\nexport type NotificationFeedHeaderProps = React.HTMLAttributes<HTMLDivElement> & {\n /** Header title text. Renders as `<h3 className=\"text-base font-semibold\">`. */\n title?: React.ReactNode\n}\n\nconst NotificationFeedHeader = React.forwardRef<HTMLDivElement, NotificationFeedHeaderProps>(\n ({ className, title, children, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"notification-feed-header\"\n className={cn(\n 'flex items-center justify-between gap-3 border-b border-input px-4 py-3',\n className,\n )}\n {...props}\n >\n {title ? (\n <h3\n data-slot=\"notification-feed-header-title\"\n className=\"text-base font-semibold text-foreground\"\n >\n {title}\n </h3>\n ) : null}\n {children ? (\n <div data-slot=\"notification-feed-header-actions\" className=\"inline-flex items-center gap-1\">\n {children}\n </div>\n ) : null}\n </div>\n ),\n)\nNotificationFeedHeader.displayName = 'NotificationFeedHeader'\n\nconst NotificationFeedList = React.forwardRef<\n HTMLOListElement,\n React.HTMLAttributes<HTMLOListElement>\n>(({ className, ...props }, ref) => (\n <ol\n ref={ref}\n data-slot=\"notification-feed-list\"\n className={cn('flex flex-col divide-y divide-input', className)}\n {...props}\n />\n))\nNotificationFeedList.displayName = 'NotificationFeedList'\n\nexport type NotificationFeedItemProps = Omit<\n React.HTMLAttributes<HTMLLIElement>,\n 'title' | 'onClick'\n> & {\n /** Left slot \u2014 typically a `NotificationFeedIconBadge`. */\n icon?: React.ReactNode\n /** Bold headline. */\n title: React.ReactNode\n /** Optional muted body text under the title. */\n body?: React.ReactNode\n /** Optional smaller muted suffix under the body. */\n timestamp?: React.ReactNode\n /** Whether to render the unread indicator dot beside the title. */\n unread?: boolean\n /** Optional right-slot actions (e.g. kebab IconButton). Visible on\n * hover / focus per Figma. Pass `IconButton variant=\"ghost\" size=\"sm\"`. */\n actions?: React.ReactNode\n /** Optional click handler. When provided the whole row becomes a\n * clickable affordance (hover bg, focus ring). */\n onClick?: () => void\n /** Accessible label for the row when `onClick` is provided. Default:\n * the `title` if it's a string, otherwise `'Open notification'`. */\n clickAriaLabel?: string\n}\n\nconst NotificationFeedItem = React.forwardRef<HTMLLIElement, NotificationFeedItemProps>(\n (\n {\n className,\n icon,\n title,\n body,\n timestamp,\n unread = false,\n actions,\n onClick,\n clickAriaLabel,\n children,\n ...props\n },\n ref,\n ) => {\n const t = useT()\n const interactive = typeof onClick === 'function'\n return (\n <li\n ref={ref}\n data-slot=\"notification-feed-item\"\n data-unread={unread || undefined}\n className={cn(\n 'group flex items-start gap-3 px-4 py-3',\n interactive ? 'cursor-pointer hover:bg-muted/40 focus-within:bg-muted/40' : '',\n className,\n )}\n onClick={onClick}\n role={interactive ? 'button' : undefined}\n tabIndex={interactive ? 0 : undefined}\n aria-label={\n interactive\n ? clickAriaLabel ??\n (typeof title === 'string' ? title : t('ui.notificationFeed.item.openAriaLabel', 'Open notification'))\n : undefined\n }\n onKeyDown={\n interactive\n ? (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onClick?.()\n }\n }\n : undefined\n }\n {...props}\n >\n {icon ? (\n <span\n data-slot=\"notification-feed-item-icon\"\n className=\"inline-flex shrink-0 items-center justify-center\"\n >\n {icon}\n </span>\n ) : null}\n\n <div data-slot=\"notification-feed-item-body\" className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div\n data-slot=\"notification-feed-item-title\"\n className=\"flex items-center gap-2 text-sm font-semibold text-foreground\"\n >\n <span className=\"truncate\">{title}</span>\n {unread ? (\n <span\n data-slot=\"notification-feed-item-unread-dot\"\n aria-hidden=\"true\"\n className=\"inline-block size-2 shrink-0 rounded-full bg-accent-indigo\"\n />\n ) : null}\n </div>\n {body ? (\n <div\n data-slot=\"notification-feed-item-text\"\n className=\"text-sm leading-5 text-muted-foreground\"\n >\n {body}\n </div>\n ) : null}\n {timestamp ? (\n <div\n data-slot=\"notification-feed-item-timestamp\"\n className=\"text-xs leading-4 text-muted-foreground\"\n >\n {timestamp}\n </div>\n ) : null}\n {children ? (\n <div data-slot=\"notification-feed-item-children\" className=\"mt-2 flex flex-wrap gap-2\">\n {children}\n </div>\n ) : null}\n </div>\n\n {actions ? (\n <div\n data-slot=\"notification-feed-item-actions\"\n className=\"ml-auto inline-flex shrink-0 items-start opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100\"\n onClick={(event) => event.stopPropagation()}\n >\n {actions}\n </div>\n ) : null}\n </li>\n )\n },\n)\nNotificationFeedItem.displayName = 'NotificationFeedItem'\n\nconst NotificationFeedFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"notification-feed-footer\"\n className={cn(\n 'flex items-center justify-between gap-3 border-t border-input px-4 py-3',\n className,\n )}\n {...props}\n />\n))\nNotificationFeedFooter.displayName = 'NotificationFeedFooter'\n\nconst ICON_TONE_MAP = {\n // Tints + matching icon color per Figma `Notifications Items [1.1]`\n // assembled examples. Surface is a soft `tone/10` background; icon\n // carries the semantic color directly.\n indigo: 'bg-accent-indigo/10 text-accent-indigo',\n success: 'bg-status-success-icon/10 text-status-success-icon',\n warning: 'bg-status-warning-icon/10 text-status-warning-icon',\n error: 'bg-status-error-icon/10 text-status-error-icon',\n info: 'bg-status-info-icon/10 text-status-info-icon',\n brand: 'bg-brand-violet/10 text-brand-violet',\n neutral: 'bg-muted text-muted-foreground',\n} as const\n\nconst iconBadgeVariants = cva(\n 'inline-flex shrink-0 items-center justify-center rounded-full',\n {\n variants: {\n tone: ICON_TONE_MAP,\n size: {\n sm: 'size-8',\n default: 'size-10',\n },\n },\n defaultVariants: { tone: 'indigo', size: 'default' },\n },\n)\n\nexport type NotificationFeedIconBadgeProps = React.HTMLAttributes<HTMLSpanElement> &\n VariantProps<typeof iconBadgeVariants>\n\nconst NotificationFeedIconBadge = React.forwardRef<\n HTMLSpanElement,\n NotificationFeedIconBadgeProps\n>(({ className, tone, size, children, ...props }, ref) => (\n <span\n ref={ref}\n data-slot=\"notification-feed-icon-badge\"\n data-tone={tone ?? 'indigo'}\n className={cn(iconBadgeVariants({ tone, size }), className)}\n {...props}\n >\n {children}\n </span>\n))\nNotificationFeedIconBadge.displayName = 'NotificationFeedIconBadge'\n\nexport type NotificationFeedIconBadgeTone = keyof typeof ICON_TONE_MAP\n\nexport {\n NotificationFeed,\n NotificationFeedHeader,\n NotificationFeedList,\n NotificationFeedItem,\n NotificationFeedFooter,\n NotificationFeedIconBadge,\n}\n"],
|
|
5
|
+
"mappings": ";AA2FE,cAmBE,YAnBF;AAzFF,YAAY,WAAW;AACvB,SAAS,WAA8B;AAEvC,SAAS,UAAU;AACnB,SAAS,YAAY;AAiFrB,MAAM,mBAAmB,MAAM,WAG7B,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC;AAAA;AAAA,IACC;AAAA,IACA,aAAU;AAAA,IACV,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,IACC,GAAG;AAAA;AACN,CACD;AACD,iBAAiB,cAAc;AAO/B,MAAM,yBAAyB,MAAM;AAAA,EACnC,CAAC,EAAE,WAAW,OAAO,UAAU,GAAG,MAAM,GAAG,QACzC;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,gBACC;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAET;AAAA;AAAA,QACH,IACE;AAAA,QACH,WACC,oBAAC,SAAI,aAAU,oCAAmC,WAAU,kCACzD,UACH,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;AACA,uBAAuB,cAAc;AAErC,MAAM,uBAAuB,MAAM,WAGjC,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC;AAAA;AAAA,IACC;AAAA,IACA,aAAU;AAAA,IACV,WAAW,GAAG,uCAAuC,SAAS;AAAA,IAC7D,GAAG;AAAA;AACN,CACD;AACD,qBAAqB,cAAc;AA2BnC,MAAM,uBAAuB,MAAM;AAAA,EACjC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,IAAI,KAAK;AACf,UAAM,cAAc,OAAO,YAAY;AACvC,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,aAAU;AAAA,QACV,eAAa,UAAU;AAAA,QACvB,WAAW;AAAA,UACT;AAAA,UACA,cAAc,8DAA8D;AAAA,UAC5E;AAAA,QACF;AAAA,QACA;AAAA,QACA,MAAM,cAAc,WAAW;AAAA,QAC/B,UAAU,cAAc,IAAI;AAAA,QAC5B,cACE,cACI,mBACC,OAAO,UAAU,WAAW,QAAQ,EAAE,0CAA0C,mBAAmB,KACpG;AAAA,QAEN,WACE,cACI,CAAC,UAAU;AACT,cAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;AAC9C,kBAAM,eAAe;AACrB,sBAAU;AAAA,UACZ;AAAA,QACF,IACA;AAAA,QAEL,GAAG;AAAA,QAEH;AAAA,iBACC;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,WAAU;AAAA,cAET;AAAA;AAAA,UACH,IACE;AAAA,UAEJ,qBAAC,SAAI,aAAU,+BAA8B,WAAU,sCACrD;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA,gBAEV;AAAA,sCAAC,UAAK,WAAU,YAAY,iBAAM;AAAA,kBACjC,SACC;AAAA,oBAAC;AAAA;AAAA,sBACC,aAAU;AAAA,sBACV,eAAY;AAAA,sBACZ,WAAU;AAAA;AAAA,kBACZ,IACE;AAAA;AAAA;AAAA,YACN;AAAA,YACC,OACC;AAAA,cAAC;AAAA;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA,gBAET;AAAA;AAAA,YACH,IACE;AAAA,YACH,YACC;AAAA,cAAC;AAAA;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA,gBAET;AAAA;AAAA,YACH,IACE;AAAA,YACH,WACC,oBAAC,SAAI,aAAU,mCAAkC,WAAU,6BACxD,UACH,IACE;AAAA,aACN;AAAA,UAEC,UACC;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,WAAU;AAAA,cACV,SAAS,CAAC,UAAU,MAAM,gBAAgB;AAAA,cAEzC;AAAA;AAAA,UACH,IACE;AAAA;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AACA,qBAAqB,cAAc;AAEnC,MAAM,yBAAyB,MAAM,WAGnC,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC;AAAA;AAAA,IACC;AAAA,IACA,aAAU;AAAA,IACV,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,IACC,GAAG;AAAA;AACN,CACD;AACD,uBAAuB,cAAc;AAErC,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA,EAIpB,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AAAA,EACP,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AACX;AAEA,MAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,MAAM,UAAU,MAAM,UAAU;AAAA,EACrD;AACF;AAKA,MAAM,4BAA4B,MAAM,WAGtC,CAAC,EAAE,WAAW,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAChD;AAAA,EAAC;AAAA;AAAA,IACC;AAAA,IACA,aAAU;AAAA,IACV,aAAW,QAAQ;AAAA,IACnB,WAAW,GAAG,kBAAkB,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS;AAAA,IACzD,GAAG;AAAA,IAEH;AAAA;AACH,CACD;AACD,0BAA0B,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -20,8 +20,26 @@ const Notification = React.forwardRef(
|
|
|
20
20
|
onDismiss,
|
|
21
21
|
dismissAriaLabel,
|
|
22
22
|
className,
|
|
23
|
-
id
|
|
23
|
+
id,
|
|
24
|
+
autoDismissMs,
|
|
25
|
+
pauseOnHover
|
|
24
26
|
}, ref) => {
|
|
27
|
+
const shouldPauseOnHover = pauseOnHover ?? autoDismissMs !== void 0;
|
|
28
|
+
const [hovered, setHovered] = React.useState(false);
|
|
29
|
+
React.useEffect(() => {
|
|
30
|
+
if (autoDismissMs === void 0 || autoDismissMs <= 0) return;
|
|
31
|
+
if (shouldPauseOnHover && hovered) return;
|
|
32
|
+
const timer = window.setTimeout(() => {
|
|
33
|
+
onDismiss?.();
|
|
34
|
+
}, autoDismissMs);
|
|
35
|
+
return () => window.clearTimeout(timer);
|
|
36
|
+
}, [autoDismissMs, hovered, onDismiss, shouldPauseOnHover]);
|
|
37
|
+
const handleMouseEnter = React.useCallback(() => {
|
|
38
|
+
if (shouldPauseOnHover) setHovered(true);
|
|
39
|
+
}, [shouldPauseOnHover]);
|
|
40
|
+
const handleMouseLeave = React.useCallback(() => {
|
|
41
|
+
if (shouldPauseOnHover) setHovered(false);
|
|
42
|
+
}, [shouldPauseOnHover]);
|
|
25
43
|
return /* @__PURE__ */ jsxs(
|
|
26
44
|
Alert,
|
|
27
45
|
{
|
|
@@ -36,12 +54,15 @@ const Notification = React.forwardRef(
|
|
|
36
54
|
className,
|
|
37
55
|
"data-slot": "notification",
|
|
38
56
|
"data-notification-id": id,
|
|
57
|
+
"data-auto-dismiss-paused": autoDismissMs !== void 0 && shouldPauseOnHover && hovered ? "true" : void 0,
|
|
58
|
+
onMouseEnter: handleMouseEnter,
|
|
59
|
+
onMouseLeave: handleMouseLeave,
|
|
39
60
|
children: [
|
|
40
61
|
(title || timestamp) && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
|
|
41
62
|
title ? /* @__PURE__ */ jsx(AlertTitle, { className: "mb-0 flex-1 min-w-0", children: title }) : /* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
42
63
|
timestamp ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs leading-5 opacity-60", "data-slot": "notification-timestamp", children: timestamp }) : null
|
|
43
64
|
] }),
|
|
44
|
-
description ? /* @__PURE__ */ jsx(AlertDescription, { className: cn("opacity-
|
|
65
|
+
description ? /* @__PURE__ */ jsx(AlertDescription, { className: cn("opacity-70", title && "mt-1"), "data-slot": "notification-description", children: description }) : null,
|
|
45
66
|
actions ? /* @__PURE__ */ jsx(
|
|
46
67
|
"div",
|
|
47
68
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/primitives/notification.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport {\n Alert,\n AlertDescription,\n AlertTitle,\n type AlertStatus,\n type AlertStyle,\n} from './alert'\n\nexport type NotificationProps = {\n /**\n * Notification status. Same five values as `Alert`.\n * Default `'information'`.\n */\n status?: AlertStatus\n /**\n * Visual style. Same four values as `Alert`.\n * Default `'light'`.\n */\n style?: AlertStyle\n /**\n * Avatar/icon override. If provided, replaces the per-status default\n * Lucide icon \u2014 typical for user-driven notifications (\"John\n * commented on...\" with the author avatar). Pass an `Avatar`\n * primitive instance or any 20\u00D720 visual.\n */\n avatar?: React.ReactNode\n /**\n * Notification title. Rendered as `AlertTitle` (Label/Small,\n * `font-medium 14/20`).\n */\n title?: React.ReactNode\n /**\n * Notification body. Rendered as `AlertDescription` at 72% opacity\n * per Figma `170:1839` reference (Paragraph/Small, `font-regular\n * 14/20`).\n */\n description?: React.ReactNode\n /**\n * Right-aligned timestamp hint next to the title (e.g. `\"2 min\n * ago\"`). Pre-formatted by the caller \u2014 pass through\n * `formatRelativeTime()` from `@open-mercato/shared/lib/time` for\n * the standard look.\n */\n timestamp?: React.ReactNode\n /**\n * Action buttons / links rendered under the description. Pass a\n * `<>...</>` fragment or an array of action elements. Separators\n * are not auto-inserted \u2014 wrap your actions in a `<div>` with\n * `gap-2` if you need custom spacing.\n */\n actions?: React.ReactNode\n /** Render the trailing X close button. Defaults to `true` \u2014 Figma reference always shows it. */\n dismissible?: boolean\n onDismiss?: () => void\n dismissAriaLabel?: string\n className?: string\n /** Stable id used by the `NotificationStack` (or external trackers) to identify the card. Forwarded as `data-notification-id`. */\n id?: string\n}\n\n/**\n * Card composition over the `Alert` primitive matching the Figma\n * Notification reference (`170:1839` \u2014 Error/Light/Large). Renders a\n * status icon (or custom `avatar`), a title + timestamp row, an\n * `opacity-
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport {\n Alert,\n AlertDescription,\n AlertTitle,\n type AlertStatus,\n type AlertStyle,\n} from './alert'\n\nexport type NotificationProps = {\n /**\n * Notification status. Same five values as `Alert`.\n * Default `'information'`.\n */\n status?: AlertStatus\n /**\n * Visual style. Same four values as `Alert`.\n * Default `'light'`.\n */\n style?: AlertStyle\n /**\n * Avatar/icon override. If provided, replaces the per-status default\n * Lucide icon \u2014 typical for user-driven notifications (\"John\n * commented on...\" with the author avatar). Pass an `Avatar`\n * primitive instance or any 20\u00D720 visual.\n */\n avatar?: React.ReactNode\n /**\n * Notification title. Rendered as `AlertTitle` (Label/Small,\n * `font-medium 14/20`).\n */\n title?: React.ReactNode\n /**\n * Notification body. Rendered as `AlertDescription` at 72% opacity\n * per Figma `170:1839` reference (Paragraph/Small, `font-regular\n * 14/20`).\n */\n description?: React.ReactNode\n /**\n * Right-aligned timestamp hint next to the title (e.g. `\"2 min\n * ago\"`). Pre-formatted by the caller \u2014 pass through\n * `formatRelativeTime()` from `@open-mercato/shared/lib/time` for\n * the standard look.\n */\n timestamp?: React.ReactNode\n /**\n * Action buttons / links rendered under the description. Pass a\n * `<>...</>` fragment or an array of action elements. Separators\n * are not auto-inserted \u2014 wrap your actions in a `<div>` with\n * `gap-2` if you need custom spacing.\n */\n actions?: React.ReactNode\n /** Render the trailing X close button. Defaults to `true` \u2014 Figma reference always shows it. */\n dismissible?: boolean\n onDismiss?: () => void\n dismissAriaLabel?: string\n className?: string\n /** Stable id used by the `NotificationStack` (or external trackers) to identify the card. Forwarded as `data-notification-id`. */\n id?: string\n /**\n * Auto-dismiss the card after N milliseconds. When set, an internal\n * timer fires `onDismiss` after the delay. Standard toast UX:\n * 4000\u20136000 for success / info, 8000+ for warnings, no auto-dismiss\n * for errors. Defaults to undefined (manual dismiss only).\n */\n autoDismissMs?: number\n /**\n * Pause the auto-dismiss timer while the user is hovering the card.\n * Defaults to `true` whenever `autoDismissMs` is set \u2014 gives users\n * time to read/click without the card disappearing under their\n * cursor. Pass `false` to keep the timer running unconditionally.\n */\n pauseOnHover?: boolean\n}\n\n/**\n * Card composition over the `Alert` primitive matching the Figma\n * Notification reference (`170:1839` \u2014 Error/Light/Large). Renders a\n * status icon (or custom `avatar`), a title + timestamp row, an\n * `opacity-70` description, and an optional row of action links. Use\n * with `NotificationStack` for corner-floating manual-dismiss UX, or\n * standalone inside a panel / dialog content area.\n *\n * Notification is intentionally a thin wrapper \u2014 every visual token\n * comes from the `Alert` primitive so the look stays in sync with the\n * unified Alert / Notification / Toast design.\n */\nexport const Notification = React.forwardRef<HTMLDivElement, NotificationProps>(\n (\n {\n status,\n style,\n avatar,\n title,\n description,\n timestamp,\n actions,\n dismissible = true,\n onDismiss,\n dismissAriaLabel,\n className,\n id,\n autoDismissMs,\n pauseOnHover,\n },\n ref,\n ) => {\n const shouldPauseOnHover = pauseOnHover ?? autoDismissMs !== undefined\n const [hovered, setHovered] = React.useState(false)\n\n React.useEffect(() => {\n if (autoDismissMs === undefined || autoDismissMs <= 0) return\n if (shouldPauseOnHover && hovered) return\n const timer = window.setTimeout(() => {\n onDismiss?.()\n }, autoDismissMs)\n return () => window.clearTimeout(timer)\n }, [autoDismissMs, hovered, onDismiss, shouldPauseOnHover])\n\n const handleMouseEnter = React.useCallback(() => {\n if (shouldPauseOnHover) setHovered(true)\n }, [shouldPauseOnHover])\n const handleMouseLeave = React.useCallback(() => {\n if (shouldPauseOnHover) setHovered(false)\n }, [shouldPauseOnHover])\n\n return (\n <Alert\n ref={ref}\n status={status}\n style={style}\n size=\"default\"\n icon={avatar}\n dismissible={dismissible}\n onDismiss={onDismiss}\n dismissAriaLabel={dismissAriaLabel}\n className={className}\n data-slot=\"notification\"\n data-notification-id={id}\n data-auto-dismiss-paused={\n autoDismissMs !== undefined && shouldPauseOnHover && hovered ? 'true' : undefined\n }\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n >\n {(title || timestamp) && (\n <div className=\"flex items-start gap-2\">\n {title ? <AlertTitle className=\"mb-0 flex-1 min-w-0\">{title}</AlertTitle> : <span className=\"flex-1\" />}\n {timestamp ? (\n <span className=\"shrink-0 text-xs leading-5 opacity-60\" data-slot=\"notification-timestamp\">\n {timestamp}\n </span>\n ) : null}\n </div>\n )}\n {description ? (\n <AlertDescription className={cn('opacity-70', title && 'mt-1')} data-slot=\"notification-description\">\n {description}\n </AlertDescription>\n ) : null}\n {actions ? (\n <div\n className={cn('flex flex-wrap items-center gap-2', (title || description) && 'mt-2.5')}\n data-slot=\"notification-actions\"\n >\n {actions}\n </div>\n ) : null}\n </Alert>\n )\n },\n)\nNotification.displayName = 'Notification'\n"],
|
|
5
|
+
"mappings": ";AAqJU,SACW,KADX;AAnJV,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAgFA,MAAM,eAAe,MAAM;AAAA,EAChC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GACA,QACG;AACH,UAAM,qBAAqB,gBAAgB,kBAAkB;AAC7D,UAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAElD,UAAM,UAAU,MAAM;AACpB,UAAI,kBAAkB,UAAa,iBAAiB,EAAG;AACvD,UAAI,sBAAsB,QAAS;AACnC,YAAM,QAAQ,OAAO,WAAW,MAAM;AACpC,oBAAY;AAAA,MACd,GAAG,aAAa;AAChB,aAAO,MAAM,OAAO,aAAa,KAAK;AAAA,IACxC,GAAG,CAAC,eAAe,SAAS,WAAW,kBAAkB,CAAC;AAE1D,UAAM,mBAAmB,MAAM,YAAY,MAAM;AAC/C,UAAI,mBAAoB,YAAW,IAAI;AAAA,IACzC,GAAG,CAAC,kBAAkB,CAAC;AACvB,UAAM,mBAAmB,MAAM,YAAY,MAAM;AAC/C,UAAI,mBAAoB,YAAW,KAAK;AAAA,IAC1C,GAAG,CAAC,kBAAkB,CAAC;AAEvB,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAK;AAAA,QACL,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAU;AAAA,QACV,wBAAsB;AAAA,QACtB,4BACE,kBAAkB,UAAa,sBAAsB,UAAU,SAAS;AAAA,QAE1E,cAAc;AAAA,QACd,cAAc;AAAA,QAEZ;AAAA,oBAAS,cACT,qBAAC,SAAI,WAAU,0BACZ;AAAA,oBAAQ,oBAAC,cAAW,WAAU,uBAAuB,iBAAM,IAAgB,oBAAC,UAAK,WAAU,UAAS;AAAA,YACpG,YACC,oBAAC,UAAK,WAAU,yCAAwC,aAAU,0BAC/D,qBACH,IACE;AAAA,aACN;AAAA,UAED,cACC,oBAAC,oBAAiB,WAAW,GAAG,cAAc,SAAS,MAAM,GAAG,aAAU,4BACvE,uBACH,IACE;AAAA,UACH,UACC;AAAA,YAAC;AAAA;AAAA,cACC,WAAW,GAAG,sCAAsC,SAAS,gBAAgB,QAAQ;AAAA,cACrF,aAAU;AAAA,cAET;AAAA;AAAA,UACH,IACE;AAAA;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AACA,aAAa,cAAc;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import {
|
|
5
|
+
ChevronLeft,
|
|
6
|
+
ChevronRight,
|
|
7
|
+
ChevronsLeft,
|
|
8
|
+
ChevronsRight
|
|
9
|
+
} from "lucide-react";
|
|
10
|
+
import { cva } from "class-variance-authority";
|
|
11
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
12
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
13
|
+
import {
|
|
14
|
+
CompactSelectTrigger,
|
|
15
|
+
Select,
|
|
16
|
+
SelectContent,
|
|
17
|
+
SelectItem,
|
|
18
|
+
SelectValue
|
|
19
|
+
} from "./compact-select.js";
|
|
20
|
+
function buildPaginationItems(page, totalPages, siblingCount, boundaryCount) {
|
|
21
|
+
if (totalPages <= 0) return [];
|
|
22
|
+
const totalSlots = boundaryCount * 2 + siblingCount * 2 + 3;
|
|
23
|
+
if (totalPages <= totalSlots) {
|
|
24
|
+
return Array.from({ length: totalPages }, (_, i) => i + 1);
|
|
25
|
+
}
|
|
26
|
+
const startPages = Array.from({ length: boundaryCount }, (_, i) => i + 1);
|
|
27
|
+
const endPages = Array.from(
|
|
28
|
+
{ length: boundaryCount },
|
|
29
|
+
(_, i) => totalPages - boundaryCount + 1 + i
|
|
30
|
+
);
|
|
31
|
+
const siblingStart = Math.max(
|
|
32
|
+
Math.min(page - siblingCount, totalPages - boundaryCount - siblingCount * 2 - 1),
|
|
33
|
+
boundaryCount + 2
|
|
34
|
+
);
|
|
35
|
+
const siblingEnd = Math.min(
|
|
36
|
+
Math.max(page + siblingCount, boundaryCount + siblingCount * 2 + 2),
|
|
37
|
+
endPages.length > 0 ? endPages[0] - 2 : totalPages - 1
|
|
38
|
+
);
|
|
39
|
+
const middle = [];
|
|
40
|
+
for (let i = siblingStart; i <= siblingEnd; i += 1) middle.push(i);
|
|
41
|
+
const result = [];
|
|
42
|
+
for (const p of startPages) result.push(p);
|
|
43
|
+
if (siblingStart > boundaryCount + 2) {
|
|
44
|
+
result.push("ellipsis-left");
|
|
45
|
+
} else if (siblingStart === boundaryCount + 2) {
|
|
46
|
+
result.push(boundaryCount + 1);
|
|
47
|
+
}
|
|
48
|
+
for (const p of middle) result.push(p);
|
|
49
|
+
const firstEnd = endPages[0] ?? totalPages;
|
|
50
|
+
if (siblingEnd < firstEnd - 2) {
|
|
51
|
+
result.push("ellipsis-right");
|
|
52
|
+
} else if (siblingEnd === firstEnd - 2) {
|
|
53
|
+
result.push(firstEnd - 1);
|
|
54
|
+
}
|
|
55
|
+
for (const p of endPages) result.push(p);
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
const cellVariants = cva(
|
|
59
|
+
"inline-flex size-8 shrink-0 items-center justify-center rounded-lg text-sm font-medium outline-none transition-colors tabular-nums focus-visible:shadow-focus disabled:cursor-not-allowed disabled:opacity-50",
|
|
60
|
+
{
|
|
61
|
+
variants: {
|
|
62
|
+
selected: {
|
|
63
|
+
true: "bg-muted text-foreground",
|
|
64
|
+
false: "bg-background text-foreground hover:bg-muted/40"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
defaultVariants: { selected: false }
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
const navButtonVariants = cva(
|
|
71
|
+
"inline-flex size-8 shrink-0 items-center justify-center rounded-lg text-muted-foreground outline-none transition-colors hover:bg-muted/40 hover:text-foreground focus-visible:shadow-focus disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent disabled:hover:text-muted-foreground"
|
|
72
|
+
);
|
|
73
|
+
const Pagination = React.forwardRef(
|
|
74
|
+
({
|
|
75
|
+
className,
|
|
76
|
+
page,
|
|
77
|
+
pageSize,
|
|
78
|
+
total,
|
|
79
|
+
onPageChange,
|
|
80
|
+
onPageSizeChange,
|
|
81
|
+
pageSizeOptions = [10, 25, 50, 100],
|
|
82
|
+
showInfo = true,
|
|
83
|
+
showPageSize: showPageSizeProp,
|
|
84
|
+
showFirstLast = true,
|
|
85
|
+
showPrevNext = true,
|
|
86
|
+
siblingCount = 1,
|
|
87
|
+
boundaryCount = 1,
|
|
88
|
+
disabled = false,
|
|
89
|
+
formatPageInfo,
|
|
90
|
+
formatPageSizeLabel,
|
|
91
|
+
...props
|
|
92
|
+
}, ref) => {
|
|
93
|
+
const t = useT();
|
|
94
|
+
const resolvedFormatPageInfo = formatPageInfo ?? ((p, total2) => t("ui.pagination.info.pageOf", "Page {page} of {total}", { page: p, total: total2 }));
|
|
95
|
+
const resolvedFormatPageSizeLabel = formatPageSizeLabel ?? ((size) => t("ui.pagination.itemsPerPage.label", "{size} / page", { size }));
|
|
96
|
+
const totalPages = Math.max(1, Math.ceil(total / Math.max(1, pageSize)));
|
|
97
|
+
const safePage = Math.min(Math.max(1, page), totalPages);
|
|
98
|
+
const items = React.useMemo(
|
|
99
|
+
() => buildPaginationItems(safePage, totalPages, siblingCount, boundaryCount),
|
|
100
|
+
[safePage, totalPages, siblingCount, boundaryCount]
|
|
101
|
+
);
|
|
102
|
+
const showPageSize = showPageSizeProp ?? Boolean(onPageSizeChange);
|
|
103
|
+
const goTo = React.useCallback(
|
|
104
|
+
(next) => {
|
|
105
|
+
if (disabled) return;
|
|
106
|
+
const bounded = Math.min(Math.max(1, next), totalPages);
|
|
107
|
+
if (bounded !== safePage) onPageChange(bounded);
|
|
108
|
+
},
|
|
109
|
+
[disabled, onPageChange, safePage, totalPages]
|
|
110
|
+
);
|
|
111
|
+
return /* @__PURE__ */ jsxs(
|
|
112
|
+
"nav",
|
|
113
|
+
{
|
|
114
|
+
ref,
|
|
115
|
+
"data-slot": "pagination",
|
|
116
|
+
"aria-label": props["aria-label"] ?? t("ui.pagination.landmark.ariaLabel", "Pagination"),
|
|
117
|
+
className: cn("flex w-full items-center justify-between gap-6", className),
|
|
118
|
+
...props,
|
|
119
|
+
children: [
|
|
120
|
+
showInfo ? /* @__PURE__ */ jsx(
|
|
121
|
+
"div",
|
|
122
|
+
{
|
|
123
|
+
"data-slot": "pagination-info",
|
|
124
|
+
className: "shrink-0 text-sm text-muted-foreground tabular-nums",
|
|
125
|
+
children: resolvedFormatPageInfo(safePage, totalPages)
|
|
126
|
+
}
|
|
127
|
+
) : /* @__PURE__ */ jsx("div", {}),
|
|
128
|
+
/* @__PURE__ */ jsxs(
|
|
129
|
+
"div",
|
|
130
|
+
{
|
|
131
|
+
"data-slot": "pagination-controls",
|
|
132
|
+
className: "inline-flex items-center gap-2",
|
|
133
|
+
children: [
|
|
134
|
+
showFirstLast ? /* @__PURE__ */ jsx(
|
|
135
|
+
"button",
|
|
136
|
+
{
|
|
137
|
+
type: "button",
|
|
138
|
+
"data-slot": "pagination-first",
|
|
139
|
+
"aria-label": t("ui.pagination.first.ariaLabel", "First page"),
|
|
140
|
+
disabled: disabled || safePage <= 1,
|
|
141
|
+
onClick: () => goTo(1),
|
|
142
|
+
className: cn(navButtonVariants()),
|
|
143
|
+
children: /* @__PURE__ */ jsx(ChevronsLeft, { "aria-hidden": "true", className: "size-5" })
|
|
144
|
+
}
|
|
145
|
+
) : null,
|
|
146
|
+
showPrevNext ? /* @__PURE__ */ jsx(
|
|
147
|
+
"button",
|
|
148
|
+
{
|
|
149
|
+
type: "button",
|
|
150
|
+
"data-slot": "pagination-prev",
|
|
151
|
+
"aria-label": t("ui.pagination.previous.ariaLabel", "Previous page"),
|
|
152
|
+
disabled: disabled || safePage <= 1,
|
|
153
|
+
onClick: () => goTo(safePage - 1),
|
|
154
|
+
className: cn(navButtonVariants()),
|
|
155
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": "true", className: "size-5" })
|
|
156
|
+
}
|
|
157
|
+
) : null,
|
|
158
|
+
/* @__PURE__ */ jsx(
|
|
159
|
+
"ol",
|
|
160
|
+
{
|
|
161
|
+
"data-slot": "pagination-pages",
|
|
162
|
+
className: "inline-flex items-center gap-2 list-none",
|
|
163
|
+
children: items.map((entry, index) => {
|
|
164
|
+
if (entry === "ellipsis-left" || entry === "ellipsis-right") {
|
|
165
|
+
return /* @__PURE__ */ jsx(
|
|
166
|
+
"li",
|
|
167
|
+
{
|
|
168
|
+
"data-slot": "pagination-ellipsis",
|
|
169
|
+
"aria-hidden": "true",
|
|
170
|
+
className: "inline-flex size-8 items-center justify-center text-sm text-muted-foreground",
|
|
171
|
+
children: "\u2026"
|
|
172
|
+
},
|
|
173
|
+
`${entry}-${index}`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const selected = entry === safePage;
|
|
177
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
178
|
+
"button",
|
|
179
|
+
{
|
|
180
|
+
type: "button",
|
|
181
|
+
"data-slot": "pagination-page",
|
|
182
|
+
"data-state": selected ? "on" : "off",
|
|
183
|
+
"aria-current": selected ? "page" : void 0,
|
|
184
|
+
"aria-label": selected ? t("ui.pagination.page.currentAriaLabel", "Page {page}, current page", { page: entry }) : t("ui.pagination.page.goToAriaLabel", "Go to page {page}", { page: entry }),
|
|
185
|
+
disabled,
|
|
186
|
+
onClick: () => goTo(entry),
|
|
187
|
+
className: cn(cellVariants({ selected })),
|
|
188
|
+
children: entry
|
|
189
|
+
}
|
|
190
|
+
) }, entry);
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
),
|
|
194
|
+
showPrevNext ? /* @__PURE__ */ jsx(
|
|
195
|
+
"button",
|
|
196
|
+
{
|
|
197
|
+
type: "button",
|
|
198
|
+
"data-slot": "pagination-next",
|
|
199
|
+
"aria-label": t("ui.pagination.next.ariaLabel", "Next page"),
|
|
200
|
+
disabled: disabled || safePage >= totalPages,
|
|
201
|
+
onClick: () => goTo(safePage + 1),
|
|
202
|
+
className: cn(navButtonVariants()),
|
|
203
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true", className: "size-5" })
|
|
204
|
+
}
|
|
205
|
+
) : null,
|
|
206
|
+
showFirstLast ? /* @__PURE__ */ jsx(
|
|
207
|
+
"button",
|
|
208
|
+
{
|
|
209
|
+
type: "button",
|
|
210
|
+
"data-slot": "pagination-last",
|
|
211
|
+
"aria-label": t("ui.pagination.last.ariaLabel", "Last page"),
|
|
212
|
+
disabled: disabled || safePage >= totalPages,
|
|
213
|
+
onClick: () => goTo(totalPages),
|
|
214
|
+
className: cn(navButtonVariants()),
|
|
215
|
+
children: /* @__PURE__ */ jsx(ChevronsRight, { "aria-hidden": "true", className: "size-5" })
|
|
216
|
+
}
|
|
217
|
+
) : null
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
),
|
|
221
|
+
showPageSize && onPageSizeChange ? /* @__PURE__ */ jsx("div", { "data-slot": "pagination-page-size", className: "shrink-0", children: /* @__PURE__ */ jsxs(
|
|
222
|
+
Select,
|
|
223
|
+
{
|
|
224
|
+
value: String(pageSize),
|
|
225
|
+
onValueChange: (next) => onPageSizeChange(Number(next)),
|
|
226
|
+
disabled,
|
|
227
|
+
children: [
|
|
228
|
+
/* @__PURE__ */ jsx(CompactSelectTrigger, { "aria-label": t("ui.pagination.itemsPerPage.ariaLabel", "Items per page"), children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
229
|
+
/* @__PURE__ */ jsx(SelectContent, { children: pageSizeOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: String(option), children: resolvedFormatPageSizeLabel(option) }, option)) })
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
) }) : /* @__PURE__ */ jsx("div", {})
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
Pagination.displayName = "Pagination";
|
|
239
|
+
export {
|
|
240
|
+
Pagination,
|
|
241
|
+
buildPaginationItems,
|
|
242
|
+
cellVariants as paginationCellVariants,
|
|
243
|
+
navButtonVariants as paginationNavVariants
|
|
244
|
+
};
|
|
245
|
+
//# sourceMappingURL=pagination.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/primitives/pagination.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n ChevronLeft,\n ChevronRight,\n ChevronsLeft,\n ChevronsRight,\n} from 'lucide-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n CompactSelectTrigger,\n Select,\n SelectContent,\n SelectItem,\n SelectValue,\n} from './compact-select'\n\n/**\n * Page navigation primitive per Figma `Pagination Group [1.1]` (DS Open\n * Mercato componentSet `199985:4135`).\n *\n * Layout (Figma `Basic` variant):\n *\n * [Left] \"Page 2 of 16\"\n * [Center] \u23EE \u25C0 [1][2]\u2026[N-1][N] \u25B6 \u23ED\n * [Right] \"7 / page\" CompactSelect\n *\n * Figma's three boolean variant props are mapped 1:1 to React props:\n *\n * \uD83E\uDD47 First / Last \u2192 `showFirstLast` (default true)\n * \u23ED\uFE0F Next / Previous \u2192 `showPrevNext` (default true)\n * \uD83E\uDDEA Advanced \u2192 `showInfo` + `showPageSize` (default both true)\n *\n * The numeric page list uses the same ellipsis algorithm Material /\n * MUI / shadcn ship: `boundaryCount` pages at each end (default 1),\n * `siblingCount` pages on either side of the current page (default 1),\n * collapse the rest into `\u2026` placeholders.\n *\n * ```tsx\n * const [page, setPage] = React.useState(1)\n * const [pageSize, setPageSize] = React.useState(25)\n * <Pagination\n * page={page}\n * pageSize={pageSize}\n * total={items.length}\n * onPageChange={setPage}\n * onPageSizeChange={setPageSize}\n * />\n *\n * // Compact (no first/last, no page-size select)\n * <Pagination\n * page={page}\n * pageSize={20}\n * total={120}\n * onPageChange={setPage}\n * showFirstLast={false}\n * showPageSize={false}\n * />\n * ```\n */\n\n/**\n * Build a stable list of pages + ellipsis placeholders that fits a\n * pagination row of `boundaryCount + 2 + siblingCount * 2 + 1` cells.\n * Returns `number` entries (1-indexed) and `'ellipsis-left'` /\n * `'ellipsis-right'` placeholders that the renderer paints as `\u2026`.\n *\n * Exported for tests + consumer reuse (e.g. a server-side rendered\n * pagination indicator that needs the same shape).\n */\nexport function buildPaginationItems(\n page: number,\n totalPages: number,\n siblingCount: number,\n boundaryCount: number,\n): Array<number | 'ellipsis-left' | 'ellipsis-right'> {\n if (totalPages <= 0) return []\n // When everything fits, just list every page.\n const totalSlots = boundaryCount * 2 + siblingCount * 2 + 3\n if (totalPages <= totalSlots) {\n return Array.from({ length: totalPages }, (_, i) => i + 1)\n }\n const startPages = Array.from({ length: boundaryCount }, (_, i) => i + 1)\n const endPages = Array.from(\n { length: boundaryCount },\n (_, i) => totalPages - boundaryCount + 1 + i,\n )\n const siblingStart = Math.max(\n Math.min(page - siblingCount, totalPages - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n )\n const siblingEnd = Math.min(\n Math.max(page + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages.length > 0 ? endPages[0] - 2 : totalPages - 1,\n )\n const middle: Array<number> = []\n for (let i = siblingStart; i <= siblingEnd; i += 1) middle.push(i)\n\n // Bridge between the start boundary and the sibling window. The\n // ellipsis is only worth showing when \u22652 page numbers fall between\n // them; if exactly 1 page is missing, render that single number\n // instead of \"\u2026\" (cleaner UX \u2014 same width, no information loss).\n const result: Array<number | 'ellipsis-left' | 'ellipsis-right'> = []\n for (const p of startPages) result.push(p)\n if (siblingStart > boundaryCount + 2) {\n result.push('ellipsis-left')\n } else if (siblingStart === boundaryCount + 2) {\n result.push(boundaryCount + 1)\n }\n for (const p of middle) result.push(p)\n const firstEnd = endPages[0] ?? totalPages\n if (siblingEnd < firstEnd - 2) {\n result.push('ellipsis-right')\n } else if (siblingEnd === firstEnd - 2) {\n result.push(firstEnd - 1)\n }\n for (const p of endPages) result.push(p)\n return result\n}\n\nconst cellVariants = cva(\n 'inline-flex size-8 shrink-0 items-center justify-center rounded-lg text-sm font-medium outline-none transition-colors tabular-nums ' +\n 'focus-visible:shadow-focus ' +\n 'disabled:cursor-not-allowed disabled:opacity-50',\n {\n variants: {\n selected: {\n true: 'bg-muted text-foreground',\n false: 'bg-background text-foreground hover:bg-muted/40',\n },\n },\n defaultVariants: { selected: false },\n },\n)\n\nconst navButtonVariants = cva(\n 'inline-flex size-8 shrink-0 items-center justify-center rounded-lg text-muted-foreground outline-none transition-colors ' +\n 'hover:bg-muted/40 hover:text-foreground ' +\n 'focus-visible:shadow-focus ' +\n 'disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent disabled:hover:text-muted-foreground',\n)\n\nexport type PaginationProps = React.HTMLAttributes<HTMLDivElement> & {\n /** Current 1-indexed page. */\n page: number\n /** Items per page. */\n pageSize: number\n /** Total item count. Used to derive `Math.ceil(total / pageSize)` pages. */\n total: number\n /** Called when the user changes page. */\n onPageChange: (next: number) => void\n /** Called when the user changes page size. Optional \u2014 when omitted,\n * the \"X / page\" select is hidden. */\n onPageSizeChange?: (next: number) => void\n /** Page size options for the select. Default `[10, 25, 50, 100]`. */\n pageSizeOptions?: readonly number[]\n /** Show the left \"Page X of Y\" indicator. Default `true`. */\n showInfo?: boolean\n /** Show the right \"X / page\" select. Default `true` when\n * `onPageSizeChange` is provided; ignored otherwise. */\n showPageSize?: boolean\n /** Show \u23EE / \u23ED first / last buttons. Default `true`. */\n showFirstLast?: boolean\n /** Show \u25C0 / \u25B6 prev / next buttons. Default `true`. */\n showPrevNext?: boolean\n /** Pages on either side of the current page in the page list. Default `1`. */\n siblingCount?: number\n /** Pages pinned at each end of the page list. Default `1`. */\n boundaryCount?: number\n /** Block all interactions. Default `false`. */\n disabled?: boolean\n /** ARIA label for the navigation landmark. Default `\"Pagination\"`. */\n 'aria-label'?: string\n /** Format the \"Page X of Y\" label. */\n formatPageInfo?: (page: number, totalPages: number) => string\n /** Format the \"X / page\" label. */\n formatPageSizeLabel?: (pageSize: number) => string\n}\n\nexport const Pagination = React.forwardRef<HTMLDivElement, PaginationProps>(\n (\n {\n className,\n page,\n pageSize,\n total,\n onPageChange,\n onPageSizeChange,\n pageSizeOptions = [10, 25, 50, 100],\n showInfo = true,\n showPageSize: showPageSizeProp,\n showFirstLast = true,\n showPrevNext = true,\n siblingCount = 1,\n boundaryCount = 1,\n disabled = false,\n formatPageInfo,\n formatPageSizeLabel,\n ...props\n },\n ref,\n ) => {\n const t = useT()\n const resolvedFormatPageInfo =\n formatPageInfo ??\n ((p: number, total: number) =>\n t('ui.pagination.info.pageOf', 'Page {page} of {total}', { page: p, total }))\n const resolvedFormatPageSizeLabel =\n formatPageSizeLabel ??\n ((size: number) =>\n t('ui.pagination.itemsPerPage.label', '{size} / page', { size }))\n const totalPages = Math.max(1, Math.ceil(total / Math.max(1, pageSize)))\n const safePage = Math.min(Math.max(1, page), totalPages)\n const items = React.useMemo(\n () => buildPaginationItems(safePage, totalPages, siblingCount, boundaryCount),\n [safePage, totalPages, siblingCount, boundaryCount],\n )\n const showPageSize = showPageSizeProp ?? Boolean(onPageSizeChange)\n\n const goTo = React.useCallback(\n (next: number) => {\n if (disabled) return\n const bounded = Math.min(Math.max(1, next), totalPages)\n if (bounded !== safePage) onPageChange(bounded)\n },\n [disabled, onPageChange, safePage, totalPages],\n )\n\n return (\n <nav\n ref={ref}\n data-slot=\"pagination\"\n aria-label={props['aria-label'] ?? t('ui.pagination.landmark.ariaLabel', 'Pagination')}\n className={cn('flex w-full items-center justify-between gap-6', className)}\n {...props}\n >\n {showInfo ? (\n <div\n data-slot=\"pagination-info\"\n className=\"shrink-0 text-sm text-muted-foreground tabular-nums\"\n >\n {resolvedFormatPageInfo(safePage, totalPages)}\n </div>\n ) : (\n <div />\n )}\n\n <div\n data-slot=\"pagination-controls\"\n className=\"inline-flex items-center gap-2\"\n >\n {showFirstLast ? (\n <button\n type=\"button\"\n data-slot=\"pagination-first\"\n aria-label={t('ui.pagination.first.ariaLabel', 'First page')}\n disabled={disabled || safePage <= 1}\n onClick={() => goTo(1)}\n className={cn(navButtonVariants())}\n >\n <ChevronsLeft aria-hidden=\"true\" className=\"size-5\" />\n </button>\n ) : null}\n {showPrevNext ? (\n <button\n type=\"button\"\n data-slot=\"pagination-prev\"\n aria-label={t('ui.pagination.previous.ariaLabel', 'Previous page')}\n disabled={disabled || safePage <= 1}\n onClick={() => goTo(safePage - 1)}\n className={cn(navButtonVariants())}\n >\n <ChevronLeft aria-hidden=\"true\" className=\"size-5\" />\n </button>\n ) : null}\n\n <ol\n data-slot=\"pagination-pages\"\n className=\"inline-flex items-center gap-2 list-none\"\n >\n {items.map((entry, index) => {\n if (entry === 'ellipsis-left' || entry === 'ellipsis-right') {\n return (\n <li\n key={`${entry}-${index}`}\n data-slot=\"pagination-ellipsis\"\n aria-hidden=\"true\"\n className=\"inline-flex size-8 items-center justify-center text-sm text-muted-foreground\"\n >\n \u2026\n </li>\n )\n }\n const selected = entry === safePage\n return (\n <li key={entry}>\n <button\n type=\"button\"\n data-slot=\"pagination-page\"\n data-state={selected ? 'on' : 'off'}\n aria-current={selected ? 'page' : undefined}\n aria-label={\n selected\n ? t('ui.pagination.page.currentAriaLabel', 'Page {page}, current page', { page: entry })\n : t('ui.pagination.page.goToAriaLabel', 'Go to page {page}', { page: entry })\n }\n disabled={disabled}\n onClick={() => goTo(entry)}\n className={cn(cellVariants({ selected }))}\n >\n {entry}\n </button>\n </li>\n )\n })}\n </ol>\n\n {showPrevNext ? (\n <button\n type=\"button\"\n data-slot=\"pagination-next\"\n aria-label={t('ui.pagination.next.ariaLabel', 'Next page')}\n disabled={disabled || safePage >= totalPages}\n onClick={() => goTo(safePage + 1)}\n className={cn(navButtonVariants())}\n >\n <ChevronRight aria-hidden=\"true\" className=\"size-5\" />\n </button>\n ) : null}\n {showFirstLast ? (\n <button\n type=\"button\"\n data-slot=\"pagination-last\"\n aria-label={t('ui.pagination.last.ariaLabel', 'Last page')}\n disabled={disabled || safePage >= totalPages}\n onClick={() => goTo(totalPages)}\n className={cn(navButtonVariants())}\n >\n <ChevronsRight aria-hidden=\"true\" className=\"size-5\" />\n </button>\n ) : null}\n </div>\n\n {showPageSize && onPageSizeChange ? (\n <div data-slot=\"pagination-page-size\" className=\"shrink-0\">\n <Select\n value={String(pageSize)}\n onValueChange={(next) => onPageSizeChange(Number(next))}\n disabled={disabled}\n >\n <CompactSelectTrigger aria-label={t('ui.pagination.itemsPerPage.ariaLabel', 'Items per page')}>\n <SelectValue />\n </CompactSelectTrigger>\n <SelectContent>\n {pageSizeOptions.map((option) => (\n <SelectItem key={option} value={String(option)}>\n {resolvedFormatPageSizeLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : (\n <div />\n )}\n </nav>\n )\n },\n)\nPagination.displayName = 'Pagination'\n\nexport { cellVariants as paginationCellVariants, navButtonVariants as paginationNavVariants }\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["total"]
|
|
7
|
+
}
|