@emara/ui 1.1.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 (218) hide show
  1. package/components/ui/.gitkeep +0 -0
  2. package/components/ui/accordion.stories.tsx +231 -0
  3. package/components/ui/accordion.tsx +250 -0
  4. package/components/ui/app-shell.stories.tsx +270 -0
  5. package/components/ui/app-shell.tsx +491 -0
  6. package/components/ui/avatar.stories.tsx +174 -0
  7. package/components/ui/avatar.tsx +257 -0
  8. package/components/ui/badge.stories.tsx +127 -0
  9. package/components/ui/badge.tsx +146 -0
  10. package/components/ui/breadcrumb.stories.tsx +92 -0
  11. package/components/ui/breadcrumb.tsx +302 -0
  12. package/components/ui/button.stories.tsx +186 -0
  13. package/components/ui/button.tsx +128 -0
  14. package/components/ui/card.stories.tsx +279 -0
  15. package/components/ui/card.tsx +250 -0
  16. package/components/ui/checkbox.stories.tsx +93 -0
  17. package/components/ui/checkbox.tsx +131 -0
  18. package/components/ui/combobox.stories.tsx +489 -0
  19. package/components/ui/combobox.tsx +874 -0
  20. package/components/ui/context-menu.stories.tsx +202 -0
  21. package/components/ui/context-menu.tsx +309 -0
  22. package/components/ui/data-table.stories.tsx +227 -0
  23. package/components/ui/data-table.tsx +539 -0
  24. package/components/ui/date-picker.stories.tsx +225 -0
  25. package/components/ui/date-picker.tsx +597 -0
  26. package/components/ui/dialog.stories.tsx +193 -0
  27. package/components/ui/dialog.tsx +262 -0
  28. package/components/ui/divider.stories.tsx +84 -0
  29. package/components/ui/divider.tsx +135 -0
  30. package/components/ui/drawer.stories.tsx +218 -0
  31. package/components/ui/drawer.tsx +329 -0
  32. package/components/ui/dropdown-menu.stories.tsx +270 -0
  33. package/components/ui/dropdown-menu.tsx +353 -0
  34. package/components/ui/empty-state.stories.tsx +121 -0
  35. package/components/ui/empty-state.tsx +289 -0
  36. package/components/ui/field-group.stories.tsx +201 -0
  37. package/components/ui/field-group.tsx +276 -0
  38. package/components/ui/form.stories.tsx +219 -0
  39. package/components/ui/form.tsx +542 -0
  40. package/components/ui/input.stories.tsx +154 -0
  41. package/components/ui/input.tsx +208 -0
  42. package/components/ui/label.stories.tsx +84 -0
  43. package/components/ui/label.tsx +98 -0
  44. package/components/ui/page-header.stories.tsx +136 -0
  45. package/components/ui/page-header.tsx +315 -0
  46. package/components/ui/pagination.stories.tsx +136 -0
  47. package/components/ui/pagination.tsx +427 -0
  48. package/components/ui/popover.stories.tsx +212 -0
  49. package/components/ui/popover.tsx +167 -0
  50. package/components/ui/radio-group.stories.tsx +96 -0
  51. package/components/ui/radio-group.tsx +250 -0
  52. package/components/ui/select.stories.tsx +203 -0
  53. package/components/ui/select.tsx +318 -0
  54. package/components/ui/sidebar.stories.tsx +186 -0
  55. package/components/ui/sidebar.tsx +623 -0
  56. package/components/ui/skeleton.stories.tsx +131 -0
  57. package/components/ui/skeleton.tsx +311 -0
  58. package/components/ui/switch.stories.tsx +74 -0
  59. package/components/ui/switch.tsx +186 -0
  60. package/components/ui/table.stories.tsx +107 -0
  61. package/components/ui/table.tsx +285 -0
  62. package/components/ui/tabs.stories.tsx +222 -0
  63. package/components/ui/tabs.tsx +287 -0
  64. package/components/ui/textarea.stories.tsx +96 -0
  65. package/components/ui/textarea.tsx +182 -0
  66. package/components/ui/toast.stories.tsx +169 -0
  67. package/components/ui/toast.tsx +250 -0
  68. package/components/ui/tooltip.stories.tsx +146 -0
  69. package/components/ui/tooltip.tsx +156 -0
  70. package/components/ui/top-bar.stories.tsx +182 -0
  71. package/components/ui/top-bar.tsx +155 -0
  72. package/dist/components/ui/accordion.d.ts +45 -0
  73. package/dist/components/ui/accordion.d.ts.map +1 -0
  74. package/dist/components/ui/accordion.js +99 -0
  75. package/dist/components/ui/accordion.js.map +1 -0
  76. package/dist/components/ui/app-shell.d.ts +70 -0
  77. package/dist/components/ui/app-shell.d.ts.map +1 -0
  78. package/dist/components/ui/app-shell.js +199 -0
  79. package/dist/components/ui/app-shell.js.map +1 -0
  80. package/dist/components/ui/avatar.d.ts +41 -0
  81. package/dist/components/ui/avatar.d.ts.map +1 -0
  82. package/dist/components/ui/avatar.js +104 -0
  83. package/dist/components/ui/avatar.js.map +1 -0
  84. package/dist/components/ui/badge.d.ts +27 -0
  85. package/dist/components/ui/badge.d.ts.map +1 -0
  86. package/dist/components/ui/badge.js +65 -0
  87. package/dist/components/ui/badge.js.map +1 -0
  88. package/dist/components/ui/breadcrumb.d.ts +35 -0
  89. package/dist/components/ui/breadcrumb.d.ts.map +1 -0
  90. package/dist/components/ui/breadcrumb.js +88 -0
  91. package/dist/components/ui/breadcrumb.js.map +1 -0
  92. package/dist/components/ui/button.d.ts +26 -0
  93. package/dist/components/ui/button.d.ts.map +1 -0
  94. package/dist/components/ui/button.js +73 -0
  95. package/dist/components/ui/button.js.map +1 -0
  96. package/dist/components/ui/card.d.ts +52 -0
  97. package/dist/components/ui/card.d.ts.map +1 -0
  98. package/dist/components/ui/card.js +96 -0
  99. package/dist/components/ui/card.js.map +1 -0
  100. package/dist/components/ui/checkbox.d.ts +18 -0
  101. package/dist/components/ui/checkbox.d.ts.map +1 -0
  102. package/dist/components/ui/checkbox.js +59 -0
  103. package/dist/components/ui/checkbox.js.map +1 -0
  104. package/dist/components/ui/combobox.d.ts +194 -0
  105. package/dist/components/ui/combobox.d.ts.map +1 -0
  106. package/dist/components/ui/combobox.js +361 -0
  107. package/dist/components/ui/combobox.js.map +1 -0
  108. package/dist/components/ui/context-menu.d.ts +46 -0
  109. package/dist/components/ui/context-menu.d.ts.map +1 -0
  110. package/dist/components/ui/context-menu.js +95 -0
  111. package/dist/components/ui/context-menu.js.map +1 -0
  112. package/dist/components/ui/data-table.d.ts +53 -0
  113. package/dist/components/ui/data-table.d.ts.map +1 -0
  114. package/dist/components/ui/data-table.js +163 -0
  115. package/dist/components/ui/data-table.js.map +1 -0
  116. package/dist/components/ui/date-picker.d.ts +103 -0
  117. package/dist/components/ui/date-picker.d.ts.map +1 -0
  118. package/dist/components/ui/date-picker.js +306 -0
  119. package/dist/components/ui/date-picker.js.map +1 -0
  120. package/dist/components/ui/dialog.d.ts +40 -0
  121. package/dist/components/ui/dialog.d.ts.map +1 -0
  122. package/dist/components/ui/dialog.js +110 -0
  123. package/dist/components/ui/dialog.js.map +1 -0
  124. package/dist/components/ui/divider.d.ts +30 -0
  125. package/dist/components/ui/divider.d.ts.map +1 -0
  126. package/dist/components/ui/divider.js +62 -0
  127. package/dist/components/ui/divider.js.map +1 -0
  128. package/dist/components/ui/drawer.d.ts +56 -0
  129. package/dist/components/ui/drawer.d.ts.map +1 -0
  130. package/dist/components/ui/drawer.js +147 -0
  131. package/dist/components/ui/drawer.js.map +1 -0
  132. package/dist/components/ui/dropdown-menu.d.ts +63 -0
  133. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  134. package/dist/components/ui/dropdown-menu.js +116 -0
  135. package/dist/components/ui/dropdown-menu.js.map +1 -0
  136. package/dist/components/ui/empty-state.d.ts +43 -0
  137. package/dist/components/ui/empty-state.d.ts.map +1 -0
  138. package/dist/components/ui/empty-state.js +128 -0
  139. package/dist/components/ui/empty-state.js.map +1 -0
  140. package/dist/components/ui/field-group.d.ts +38 -0
  141. package/dist/components/ui/field-group.d.ts.map +1 -0
  142. package/dist/components/ui/field-group.js +107 -0
  143. package/dist/components/ui/field-group.js.map +1 -0
  144. package/dist/components/ui/form.d.ts +67 -0
  145. package/dist/components/ui/form.d.ts.map +1 -0
  146. package/dist/components/ui/form.js +286 -0
  147. package/dist/components/ui/form.js.map +1 -0
  148. package/dist/components/ui/input.d.ts +36 -0
  149. package/dist/components/ui/input.d.ts.map +1 -0
  150. package/dist/components/ui/input.js +99 -0
  151. package/dist/components/ui/input.js.map +1 -0
  152. package/dist/components/ui/label.d.ts +37 -0
  153. package/dist/components/ui/label.d.ts.map +1 -0
  154. package/dist/components/ui/label.js +34 -0
  155. package/dist/components/ui/label.js.map +1 -0
  156. package/dist/components/ui/page-header.d.ts +65 -0
  157. package/dist/components/ui/page-header.d.ts.map +1 -0
  158. package/dist/components/ui/page-header.js +140 -0
  159. package/dist/components/ui/page-header.js.map +1 -0
  160. package/dist/components/ui/pagination.d.ts +67 -0
  161. package/dist/components/ui/pagination.d.ts.map +1 -0
  162. package/dist/components/ui/pagination.js +109 -0
  163. package/dist/components/ui/pagination.js.map +1 -0
  164. package/dist/components/ui/popover.d.ts +28 -0
  165. package/dist/components/ui/popover.d.ts.map +1 -0
  166. package/dist/components/ui/popover.js +85 -0
  167. package/dist/components/ui/popover.js.map +1 -0
  168. package/dist/components/ui/radio-group.d.ts +35 -0
  169. package/dist/components/ui/radio-group.d.ts.map +1 -0
  170. package/dist/components/ui/radio-group.js +103 -0
  171. package/dist/components/ui/radio-group.js.map +1 -0
  172. package/dist/components/ui/select.d.ts +42 -0
  173. package/dist/components/ui/select.d.ts.map +1 -0
  174. package/dist/components/ui/select.js +86 -0
  175. package/dist/components/ui/select.js.map +1 -0
  176. package/dist/components/ui/sidebar.d.ts +59 -0
  177. package/dist/components/ui/sidebar.d.ts.map +1 -0
  178. package/dist/components/ui/sidebar.js +189 -0
  179. package/dist/components/ui/sidebar.js.map +1 -0
  180. package/dist/components/ui/skeleton.d.ts +77 -0
  181. package/dist/components/ui/skeleton.d.ts.map +1 -0
  182. package/dist/components/ui/skeleton.js +115 -0
  183. package/dist/components/ui/skeleton.js.map +1 -0
  184. package/dist/components/ui/switch.d.ts +26 -0
  185. package/dist/components/ui/switch.d.ts.map +1 -0
  186. package/dist/components/ui/switch.js +84 -0
  187. package/dist/components/ui/switch.js.map +1 -0
  188. package/dist/components/ui/table.d.ts +52 -0
  189. package/dist/components/ui/table.d.ts.map +1 -0
  190. package/dist/components/ui/table.js +109 -0
  191. package/dist/components/ui/table.js.map +1 -0
  192. package/dist/components/ui/tabs.d.ts +42 -0
  193. package/dist/components/ui/tabs.d.ts.map +1 -0
  194. package/dist/components/ui/tabs.js +163 -0
  195. package/dist/components/ui/tabs.js.map +1 -0
  196. package/dist/components/ui/textarea.d.ts +26 -0
  197. package/dist/components/ui/textarea.d.ts.map +1 -0
  198. package/dist/components/ui/textarea.js +96 -0
  199. package/dist/components/ui/textarea.js.map +1 -0
  200. package/dist/components/ui/toast.d.ts +77 -0
  201. package/dist/components/ui/toast.d.ts.map +1 -0
  202. package/dist/components/ui/toast.js +141 -0
  203. package/dist/components/ui/toast.js.map +1 -0
  204. package/dist/components/ui/tooltip.d.ts +31 -0
  205. package/dist/components/ui/tooltip.d.ts.map +1 -0
  206. package/dist/components/ui/tooltip.js +71 -0
  207. package/dist/components/ui/tooltip.js.map +1 -0
  208. package/dist/components/ui/top-bar.d.ts +30 -0
  209. package/dist/components/ui/top-bar.d.ts.map +1 -0
  210. package/dist/components/ui/top-bar.js +64 -0
  211. package/dist/components/ui/top-bar.js.map +1 -0
  212. package/dist/lib/utils.d.ts +3 -0
  213. package/dist/lib/utils.d.ts.map +1 -0
  214. package/dist/lib/utils.js +6 -0
  215. package/dist/lib/utils.js.map +1 -0
  216. package/lib/utils.ts +6 -0
  217. package/package.json +112 -0
  218. package/styles/globals.css +685 -0
@@ -0,0 +1,315 @@
1
+ "use client";
2
+
3
+ import { createContext, forwardRef, useContext, useMemo } from "react";
4
+ import { Slot } from "@radix-ui/react-slot";
5
+ import { RiArrowLeftSLine } from "@remixicon/react";
6
+ import { cva, type VariantProps } from "class-variance-authority";
7
+
8
+ import { cn } from "@/lib/utils";
9
+ import { Button } from "./button";
10
+
11
+ // Per docs/emara-ui-phase-5-components.md §4.
12
+
13
+ type PageHeaderVariant = "default" | "compact" | "splash";
14
+ type PageHeaderSize = "sm" | "md" | "lg";
15
+
16
+ interface PageHeaderContextValue {
17
+ variant: PageHeaderVariant;
18
+ size: PageHeaderSize;
19
+ }
20
+
21
+ const PageHeaderContext = createContext<PageHeaderContextValue | null>(null);
22
+
23
+ function usePageHeader(): PageHeaderContextValue {
24
+ const ctx = useContext(PageHeaderContext);
25
+ if (!ctx) throw new Error("PageHeader subcomponents must be used inside <PageHeader>");
26
+ return ctx;
27
+ }
28
+
29
+ // ---- Root ------------------------------------------------------------------
30
+
31
+ const rootVariants = cva("w-full", {
32
+ variants: {
33
+ variant: {
34
+ default: "",
35
+ compact: "",
36
+ splash: "",
37
+ },
38
+ size: {
39
+ sm: "",
40
+ md: "",
41
+ lg: "",
42
+ },
43
+ divider: { true: "border-b border-border", false: "" },
44
+ sticky: { true: "sticky top-0 z-sticky bg-background/95 backdrop-blur", false: "" },
45
+ },
46
+ compoundVariants: [
47
+ { variant: "default", size: "sm", class: "py-3 space-y-2" },
48
+ { variant: "default", size: "md", class: "py-4 space-y-3" },
49
+ { variant: "default", size: "lg", class: "py-6 space-y-4" },
50
+ { variant: "compact", size: "sm", class: "py-2 space-y-1" },
51
+ { variant: "compact", size: "md", class: "py-2.5 space-y-1" },
52
+ { variant: "compact", size: "lg", class: "py-3 space-y-2" },
53
+ { variant: "splash", size: "sm", class: "py-6 space-y-3" },
54
+ { variant: "splash", size: "md", class: "py-10 space-y-4" },
55
+ { variant: "splash", size: "lg", class: "py-14 space-y-6" },
56
+ ],
57
+ defaultVariants: { variant: "default", size: "md", divider: false, sticky: false },
58
+ });
59
+
60
+ type RootVariants = VariantProps<typeof rootVariants>;
61
+
62
+ type PageHeaderProps = Omit<React.HTMLAttributes<HTMLElement>, "title"> &
63
+ RootVariants & {
64
+ asChild?: boolean;
65
+ /** Convenience: rendered as PageHeaderTitle (with `as="h1"`). */
66
+ title?: React.ReactNode;
67
+ /** Convenience: rendered as PageHeaderDescription. */
68
+ description?: React.ReactNode;
69
+ /** Convenience: slot for breadcrumb (`<Breadcrumbs />` or primitives). */
70
+ breadcrumb?: React.ReactNode;
71
+ /** Convenience: rendered into PageHeaderActions. */
72
+ actions?: React.ReactNode;
73
+ /** Convenience: renders a default back button linking here. */
74
+ backHref?: string;
75
+ /** Localized aria-label for the auto-injected BackButton. Default "Back". */
76
+ backLabel?: string;
77
+ /** Convenience: rendered as PageHeaderTabs. */
78
+ tabs?: React.ReactNode;
79
+ };
80
+
81
+ const PageHeader = forwardRef<HTMLElement, PageHeaderProps>(function PageHeader(
82
+ {
83
+ className,
84
+ variant = "default",
85
+ size = "md",
86
+ divider = false,
87
+ sticky = false,
88
+ asChild = false,
89
+ title,
90
+ description,
91
+ breadcrumb,
92
+ actions,
93
+ backHref,
94
+ backLabel = "Back",
95
+ tabs,
96
+ children,
97
+ ...props
98
+ },
99
+ ref,
100
+ ) {
101
+ const Comp = asChild ? Slot : "header";
102
+ const resolvedVariant: PageHeaderVariant = variant ?? "default";
103
+ const resolvedSize: PageHeaderSize = size ?? "md";
104
+ const ctx = useMemo(
105
+ () => ({ variant: resolvedVariant, size: resolvedSize }),
106
+ [resolvedVariant, resolvedSize],
107
+ );
108
+
109
+ const usingConvenience =
110
+ title !== undefined ||
111
+ description !== undefined ||
112
+ breadcrumb !== undefined ||
113
+ actions !== undefined ||
114
+ backHref !== undefined ||
115
+ tabs !== undefined;
116
+
117
+ return (
118
+ <PageHeaderContext.Provider value={ctx}>
119
+ <Comp
120
+ ref={ref}
121
+ className={cn(rootVariants({ variant, size, divider, sticky }), className)}
122
+ {...props}
123
+ >
124
+ {usingConvenience ? (
125
+ <>
126
+ {breadcrumb ? <PageHeaderBreadcrumb>{breadcrumb}</PageHeaderBreadcrumb> : null}
127
+ <PageHeaderTop>
128
+ {backHref ? <PageHeaderBackButton href={backHref} label={backLabel} /> : null}
129
+ {title ? <PageHeaderTitle>{title}</PageHeaderTitle> : null}
130
+ <span className="flex-1" />
131
+ {actions ? <PageHeaderActions>{actions}</PageHeaderActions> : null}
132
+ </PageHeaderTop>
133
+ {description ? <PageHeaderDescription>{description}</PageHeaderDescription> : null}
134
+ {tabs ? <PageHeaderTabs>{tabs}</PageHeaderTabs> : null}
135
+ </>
136
+ ) : (
137
+ children
138
+ )}
139
+ </Comp>
140
+ </PageHeaderContext.Provider>
141
+ );
142
+ });
143
+ PageHeader.displayName = "PageHeader";
144
+
145
+ // ---- Breadcrumb slot -------------------------------------------------------
146
+
147
+ const PageHeaderBreadcrumb = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
148
+ function PageHeaderBreadcrumb({ className, ...props }, ref) {
149
+ return <div ref={ref} className={cn("text-sm", className)} {...props} />;
150
+ },
151
+ );
152
+ PageHeaderBreadcrumb.displayName = "PageHeaderBreadcrumb";
153
+
154
+ // ---- Top (back + title + actions row) --------------------------------------
155
+
156
+ const PageHeaderTop = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
157
+ function PageHeaderTop({ className, ...props }, ref) {
158
+ return (
159
+ <div ref={ref} className={cn("flex flex-wrap items-center gap-3", className)} {...props} />
160
+ );
161
+ },
162
+ );
163
+ PageHeaderTop.displayName = "PageHeaderTop";
164
+
165
+ // ---- BackButton ------------------------------------------------------------
166
+
167
+ type PageHeaderBackButtonProps = Omit<React.ComponentProps<typeof Button>, "size"> & {
168
+ /** Required: where to go back to. */
169
+ href: string;
170
+ /** aria-label. Default "Back". */
171
+ label?: string;
172
+ };
173
+
174
+ const PageHeaderBackButton = forwardRef<HTMLButtonElement, PageHeaderBackButtonProps>(
175
+ function PageHeaderBackButton({ className, href, label = "Back", children, ...props }, ref) {
176
+ return (
177
+ <Button
178
+ ref={ref}
179
+ asChild
180
+ variant="ghost"
181
+ size="icon-sm"
182
+ aria-label={label}
183
+ className={cn(className)}
184
+ {...props}
185
+ >
186
+ <a href={href}>
187
+ <RiArrowLeftSLine className="rtl-mirror" />
188
+ {children ? <span className="ms-1">{children}</span> : null}
189
+ </a>
190
+ </Button>
191
+ );
192
+ },
193
+ );
194
+ PageHeaderBackButton.displayName = "PageHeaderBackButton";
195
+
196
+ // ---- Title -----------------------------------------------------------------
197
+
198
+ const titleVariants = cva("font-semibold tracking-tight text-foreground", {
199
+ variants: {
200
+ variant: {
201
+ default: "",
202
+ compact: "",
203
+ splash: "",
204
+ },
205
+ size: {
206
+ sm: "",
207
+ md: "",
208
+ lg: "",
209
+ },
210
+ },
211
+ compoundVariants: [
212
+ { variant: "default", size: "sm", class: "text-lg" },
213
+ { variant: "default", size: "md", class: "text-2xl" },
214
+ { variant: "default", size: "lg", class: "text-3xl" },
215
+ { variant: "compact", size: "sm", class: "text-base" },
216
+ { variant: "compact", size: "md", class: "text-lg" },
217
+ { variant: "compact", size: "lg", class: "text-xl" },
218
+ { variant: "splash", size: "sm", class: "text-2xl" },
219
+ { variant: "splash", size: "md", class: "text-4xl" },
220
+ { variant: "splash", size: "lg", class: "text-5xl" },
221
+ ],
222
+ defaultVariants: { variant: "default", size: "md" },
223
+ });
224
+
225
+ type PageHeaderTitleProps = React.HTMLAttributes<HTMLHeadingElement> & {
226
+ as?: "h1" | "h2";
227
+ };
228
+
229
+ const PageHeaderTitle = forwardRef<HTMLHeadingElement, PageHeaderTitleProps>(
230
+ function PageHeaderTitle({ as: Heading = "h1", className, ...props }, ref) {
231
+ const { variant, size } = usePageHeader();
232
+ return (
233
+ <Heading ref={ref} className={cn(titleVariants({ variant, size }), className)} {...props} />
234
+ );
235
+ },
236
+ );
237
+ PageHeaderTitle.displayName = "PageHeaderTitle";
238
+
239
+ // ---- Badge slot (next to title) -------------------------------------------
240
+
241
+ const PageHeaderBadge = forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
242
+ function PageHeaderBadge({ className, ...props }, ref) {
243
+ return <span ref={ref} className={cn("inline-flex items-center", className)} {...props} />;
244
+ },
245
+ );
246
+ PageHeaderBadge.displayName = "PageHeaderBadge";
247
+
248
+ // ---- Actions ---------------------------------------------------------------
249
+
250
+ const PageHeaderActions = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
251
+ function PageHeaderActions({ className, ...props }, ref) {
252
+ return (
253
+ <div ref={ref} className={cn("flex flex-wrap items-center gap-2", className)} {...props} />
254
+ );
255
+ },
256
+ );
257
+ PageHeaderActions.displayName = "PageHeaderActions";
258
+
259
+ // ---- Description -----------------------------------------------------------
260
+
261
+ const descriptionVariants = cva("text-muted-foreground", {
262
+ variants: {
263
+ size: {
264
+ sm: "text-xs",
265
+ md: "text-sm",
266
+ lg: "text-base",
267
+ },
268
+ },
269
+ defaultVariants: { size: "md" },
270
+ });
271
+
272
+ const PageHeaderDescription = forwardRef<
273
+ HTMLParagraphElement,
274
+ React.HTMLAttributes<HTMLParagraphElement>
275
+ >(function PageHeaderDescription({ className, ...props }, ref) {
276
+ const { size } = usePageHeader();
277
+ return (
278
+ <p
279
+ ref={ref}
280
+ className={cn(descriptionVariants({ size }), "max-w-prose", className)}
281
+ {...props}
282
+ />
283
+ );
284
+ });
285
+ PageHeaderDescription.displayName = "PageHeaderDescription";
286
+
287
+ // ---- Tabs slot (no padding underneath) ------------------------------------
288
+
289
+ const PageHeaderTabs = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
290
+ function PageHeaderTabs({ className, ...props }, ref) {
291
+ return (
292
+ <div
293
+ ref={ref}
294
+ // The slot sits flush with the bottom edge so a `divider` line passes
295
+ // under the active tab indicator.
296
+ className={cn("-mb-px", className)}
297
+ {...props}
298
+ />
299
+ );
300
+ },
301
+ );
302
+ PageHeaderTabs.displayName = "PageHeaderTabs";
303
+
304
+ export {
305
+ PageHeader,
306
+ PageHeaderBreadcrumb,
307
+ PageHeaderTop,
308
+ PageHeaderBackButton,
309
+ PageHeaderTitle,
310
+ PageHeaderBadge,
311
+ PageHeaderActions,
312
+ PageHeaderDescription,
313
+ PageHeaderTabs,
314
+ };
315
+ export type { PageHeaderProps, PageHeaderTitleProps, PageHeaderBackButtonProps };
@@ -0,0 +1,136 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { useState } from "react";
3
+
4
+ import {
5
+ DataPagination,
6
+ Pagination,
7
+ PaginationContent,
8
+ PaginationEllipsis,
9
+ PaginationItem,
10
+ PaginationLink,
11
+ PaginationNext,
12
+ PaginationPrevious,
13
+ } from "./pagination";
14
+
15
+ const meta: Meta<typeof DataPagination> = {
16
+ title: "Data/Pagination",
17
+ component: DataPagination,
18
+ parameters: { layout: "padded" },
19
+ argTypes: {
20
+ size: { control: "select", options: ["sm", "md", "lg"] },
21
+ siblingCount: { control: "number" },
22
+ boundaryCount: { control: "number" },
23
+ showInfo: { control: "boolean" },
24
+ showPageSize: { control: "boolean" },
25
+ showFirstLast: { control: "boolean" },
26
+ disabled: { control: "boolean" },
27
+ loading: { control: "boolean" },
28
+ },
29
+ };
30
+
31
+ export default meta;
32
+ type Story = StoryObj<typeof DataPagination>;
33
+
34
+ function DataDemo(props: Partial<React.ComponentProps<typeof DataPagination>>) {
35
+ const [page, setPage] = useState(props.page ?? 3);
36
+ const [size, setSize] = useState(props.pageSize ?? 25);
37
+ return (
38
+ <DataPagination
39
+ page={page}
40
+ pageCount={props.pageCount ?? 10}
41
+ onPageChange={setPage}
42
+ pageSize={size}
43
+ pageSizeOptions={props.pageSizeOptions ?? [10, 25, 50, 100]}
44
+ onPageSizeChange={setSize}
45
+ totalItems={props.totalItems ?? 247}
46
+ size={props.size ?? "md"}
47
+ siblingCount={props.siblingCount ?? 1}
48
+ boundaryCount={props.boundaryCount ?? 1}
49
+ {...(props.showInfo !== undefined ? { showInfo: props.showInfo } : {})}
50
+ {...(props.showPageSize !== undefined ? { showPageSize: props.showPageSize } : {})}
51
+ {...(props.showFirstLast !== undefined ? { showFirstLast: props.showFirstLast } : {})}
52
+ {...(props.disabled !== undefined ? { disabled: props.disabled } : {})}
53
+ {...(props.loading !== undefined ? { loading: props.loading } : {})}
54
+ />
55
+ );
56
+ }
57
+
58
+ export const Default: Story = {
59
+ render: () => <DataDemo />,
60
+ };
61
+
62
+ export const WithFirstLast: Story = {
63
+ render: () => <DataDemo showFirstLast />,
64
+ };
65
+
66
+ export const NoPageSize: Story = {
67
+ render: () => (
68
+ <DataPagination
69
+ page={3}
70
+ pageCount={10}
71
+ onPageChange={() => {}}
72
+ totalItems={247}
73
+ pageSize={25}
74
+ />
75
+ ),
76
+ };
77
+
78
+ export const LargePageCount: Story = {
79
+ render: () => <DataDemo pageCount={50} page={20} totalItems={1200} />,
80
+ };
81
+
82
+ export const Sizes: Story = {
83
+ render: () => (
84
+ <div className="space-y-4">
85
+ <DataDemo size="sm" />
86
+ <DataDemo size="md" />
87
+ <DataDemo size="lg" />
88
+ </div>
89
+ ),
90
+ };
91
+
92
+ export const Disabled: Story = {
93
+ render: () => <DataDemo disabled />,
94
+ };
95
+
96
+ /**
97
+ * Loading state — all interactive controls become non-interactive while
98
+ * the underlying data is fetching. Mirrors `disabled` visually but
99
+ * communicates "fetching" rather than "unavailable".
100
+ */
101
+ export const Loading: Story = {
102
+ render: () => <DataDemo loading />,
103
+ };
104
+
105
+ /** Pagination primitives — composed manually. */
106
+ export const Primitives: Story = {
107
+ render: () => (
108
+ <Pagination>
109
+ <span />
110
+ <PaginationContent>
111
+ <PaginationItem>
112
+ <PaginationPrevious />
113
+ </PaginationItem>
114
+ <PaginationItem>
115
+ <PaginationLink>1</PaginationLink>
116
+ </PaginationItem>
117
+ <PaginationItem>
118
+ <PaginationLink isActive>2</PaginationLink>
119
+ </PaginationItem>
120
+ <PaginationItem>
121
+ <PaginationLink>3</PaginationLink>
122
+ </PaginationItem>
123
+ <PaginationItem>
124
+ <PaginationEllipsis />
125
+ </PaginationItem>
126
+ <PaginationItem>
127
+ <PaginationLink>10</PaginationLink>
128
+ </PaginationItem>
129
+ <PaginationItem>
130
+ <PaginationNext />
131
+ </PaginationItem>
132
+ </PaginationContent>
133
+ <span />
134
+ </Pagination>
135
+ ),
136
+ };