harebear-ui 0.0.0-stage → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +66 -0
- package/LICENSE +21 -0
- package/README.md +92 -2
- package/bin/harebear-ui.mjs +125 -0
- package/dist/components/admin/activity-feed.d.ts +15 -0
- package/dist/components/admin/activity-feed.js +50 -0
- package/dist/components/admin/app-shell.d.ts +47 -0
- package/dist/components/admin/app-shell.js +108 -0
- package/dist/components/admin/callout.d.ts +7 -0
- package/dist/components/admin/callout.js +18 -0
- package/dist/components/admin/key-value-grid.d.ts +11 -0
- package/dist/components/admin/key-value-grid.js +23 -0
- package/dist/components/admin/link-tile.d.ts +17 -0
- package/dist/components/admin/link-tile.js +36 -0
- package/dist/components/admin/page-header.d.ts +14 -0
- package/dist/components/admin/page-header.js +41 -0
- package/dist/components/admin/search-input.d.ts +12 -0
- package/dist/components/admin/search-input.js +32 -0
- package/dist/components/admin/stat-card.d.ts +20 -0
- package/dist/components/admin/stat-card.js +53 -0
- package/dist/components/admin/status.d.ts +12 -0
- package/dist/components/admin/status.js +32 -0
- package/dist/components/admin/timeline.d.ts +13 -0
- package/dist/components/admin/timeline.js +36 -0
- package/dist/components/admin/usage-meter.d.ts +17 -0
- package/dist/components/admin/usage-meter.js +43 -0
- package/dist/components/admin/user-chip.d.ts +14 -0
- package/dist/components/admin/user-chip.js +38 -0
- package/dist/components/ai/chat.d.ts +44 -0
- package/dist/components/ai/chat.js +151 -0
- package/dist/components/ai/code-block.d.ts +27 -0
- package/dist/components/ai/code-block.js +63 -0
- package/dist/components/ai/conversation-list.d.ts +18 -0
- package/dist/components/ai/conversation-list.js +30 -0
- package/dist/components/ai/model-picker.d.ts +21 -0
- package/dist/components/ai/model-picker.js +55 -0
- package/dist/components/ai/prompt-input.d.ts +27 -0
- package/dist/components/ai/prompt-input.js +99 -0
- package/dist/components/auth/auth.d.ts +85 -0
- package/dist/components/auth/auth.js +325 -0
- package/dist/components/data/bar-list.d.ts +14 -0
- package/dist/components/data/bar-list.js +27 -0
- package/dist/components/data/data-table.d.ts +70 -0
- package/dist/components/data/data-table.js +208 -0
- package/dist/components/data/sparkline.d.ts +16 -0
- package/dist/components/data/sparkline.js +72 -0
- package/dist/components/data/status-bars.d.ts +17 -0
- package/dist/components/data/status-bars.js +37 -0
- package/dist/components/data/trend-chart.d.ts +31 -0
- package/dist/components/data/trend-chart.js +136 -0
- package/dist/components/theme/theme-provider.d.ts +42 -0
- package/dist/components/theme/theme-provider.js +110 -0
- package/dist/components/theme/theme-toggle.d.ts +5 -0
- package/dist/components/theme/theme-toggle.js +21 -0
- package/dist/components/ui/accordion.d.ts +7 -0
- package/dist/components/ui/accordion.js +53 -0
- package/dist/components/ui/alert-dialog.d.ts +18 -0
- package/dist/components/ui/alert-dialog.js +99 -0
- package/dist/components/ui/alert.d.ts +10 -0
- package/dist/components/ui/alert.js +42 -0
- package/dist/components/ui/aspect-ratio.d.ts +3 -0
- package/dist/components/ui/aspect-ratio.js +11 -0
- package/dist/components/ui/attachment.d.ts +24 -0
- package/dist/components/ui/attachment.js +100 -0
- package/dist/components/ui/avatar.d.ts +11 -0
- package/dist/components/ui/avatar.js +49 -0
- package/dist/components/ui/badge.d.ts +9 -0
- package/dist/components/ui/badge.js +30 -0
- package/dist/components/ui/breadcrumb.d.ts +11 -0
- package/dist/components/ui/breadcrumb.js +70 -0
- package/dist/components/ui/bubble.d.ts +17 -0
- package/dist/components/ui/bubble.js +71 -0
- package/dist/components/ui/button-group.d.ts +11 -0
- package/dist/components/ui/button-group.js +39 -0
- package/dist/components/ui/button.d.ts +11 -0
- package/dist/components/ui/button.js +55 -0
- package/dist/components/ui/calendar.d.ts +10 -0
- package/dist/components/ui/calendar.js +108 -0
- package/dist/components/ui/card.d.ts +11 -0
- package/dist/components/ui/card.js +55 -0
- package/dist/components/ui/carousel.d.ts +28 -0
- package/dist/components/ui/carousel.js +131 -0
- package/dist/components/ui/chart.d.ts +44 -0
- package/dist/components/ui/chart.js +148 -0
- package/dist/components/ui/checkbox.d.ts +4 -0
- package/dist/components/ui/checkbox.js +19 -0
- package/dist/components/ui/code-block.d.ts +26 -0
- package/dist/components/ui/code-block.js +123 -0
- package/dist/components/ui/collapsible.d.ts +5 -0
- package/dist/components/ui/collapsible.js +23 -0
- package/dist/components/ui/combobox.d.ts +24 -0
- package/dist/components/ui/combobox.js +162 -0
- package/dist/components/ui/command.d.ts +18 -0
- package/dist/components/ui/command.js +86 -0
- package/dist/components/ui/context-menu.d.ts +31 -0
- package/dist/components/ui/context-menu.js +123 -0
- package/dist/components/ui/dialog.d.ts +17 -0
- package/dist/components/ui/dialog.js +94 -0
- package/dist/components/ui/direction.d.ts +7 -0
- package/dist/components/ui/direction.js +12 -0
- package/dist/components/ui/drawer.d.ts +13 -0
- package/dist/components/ui/drawer.js +76 -0
- package/dist/components/ui/dropdown-menu.d.ts +29 -0
- package/dist/components/ui/dropdown-menu.js +126 -0
- package/dist/components/ui/empty.d.ts +11 -0
- package/dist/components/ui/empty.js +59 -0
- package/dist/components/ui/field.d.ts +24 -0
- package/dist/components/ui/field.js +109 -0
- package/dist/components/ui/hover-card.d.ts +6 -0
- package/dist/components/ui/hover-card.js +30 -0
- package/dist/components/ui/input-group.d.ts +16 -0
- package/dist/components/ui/input-group.js +77 -0
- package/dist/components/ui/input-otp.d.ts +11 -0
- package/dist/components/ui/input-otp.js +46 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/input.js +13 -0
- package/dist/components/ui/item.d.ts +23 -0
- package/dist/components/ui/item.js +117 -0
- package/dist/components/ui/kbd.d.ts +3 -0
- package/dist/components/ui/kbd.js +19 -0
- package/dist/components/ui/label.d.ts +4 -0
- package/dist/components/ui/label.js +13 -0
- package/dist/components/ui/marker.d.ts +11 -0
- package/dist/components/ui/marker.js +39 -0
- package/dist/components/ui/menubar.d.ts +30 -0
- package/dist/components/ui/menubar.js +133 -0
- package/dist/components/ui/message-scroller.d.ts +10 -0
- package/dist/components/ui/message-scroller.js +59 -0
- package/dist/components/ui/message.d.ts +10 -0
- package/dist/components/ui/message.js +48 -0
- package/dist/components/ui/native-select.d.ts +8 -0
- package/dist/components/ui/native-select.js +37 -0
- package/dist/components/ui/navigation-menu.d.ts +14 -0
- package/dist/components/ui/navigation-menu.js +79 -0
- package/dist/components/ui/pagination.d.ts +17 -0
- package/dist/components/ui/pagination.js +79 -0
- package/dist/components/ui/popover.d.ts +10 -0
- package/dist/components/ui/popover.js +54 -0
- package/dist/components/ui/progress.d.ts +4 -0
- package/dist/components/ui/progress.js +18 -0
- package/dist/components/ui/questionnaire.d.ts +19 -0
- package/dist/components/ui/questionnaire.js +168 -0
- package/dist/components/ui/radio-group.d.ts +5 -0
- package/dist/components/ui/radio-group.js +25 -0
- package/dist/components/ui/resizable.d.ts +7 -0
- package/dist/components/ui/resizable.js +27 -0
- package/dist/components/ui/scroll-area.d.ts +5 -0
- package/dist/components/ui/scroll-area.js +35 -0
- package/dist/components/ui/select.d.ts +15 -0
- package/dist/components/ui/select.js +98 -0
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/separator.js +15 -0
- package/dist/components/ui/sheet.d.ts +14 -0
- package/dist/components/ui/sheet.js +88 -0
- package/dist/components/ui/sidebar.d.ts +69 -0
- package/dist/components/ui/sidebar.js +377 -0
- package/dist/components/ui/skeleton.d.ts +2 -0
- package/dist/components/ui/skeleton.js +12 -0
- package/dist/components/ui/slider.d.ts +4 -0
- package/dist/components/ui/slider.js +35 -0
- package/dist/components/ui/sonner.d.ts +3 -0
- package/dist/components/ui/sonner.js +29 -0
- package/dist/components/ui/spinner.d.ts +2 -0
- package/dist/components/ui/spinner.js +15 -0
- package/dist/components/ui/switch.d.ts +6 -0
- package/dist/components/ui/switch.js +18 -0
- package/dist/components/ui/table.d.ts +10 -0
- package/dist/components/ui/table.js +65 -0
- package/dist/components/ui/tabs.d.ts +11 -0
- package/dist/components/ui/tabs.js +45 -0
- package/dist/components/ui/textarea.d.ts +3 -0
- package/dist/components/ui/textarea.js +12 -0
- package/dist/components/ui/toggle-group.d.ts +10 -0
- package/dist/components/ui/toggle-group.js +50 -0
- package/dist/components/ui/toggle.d.ts +9 -0
- package/dist/components/ui/toggle.js +35 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/components/ui/tooltip.js +34 -0
- package/dist/components/ui/trace.d.ts +18 -0
- package/dist/components/ui/trace.js +74 -0
- package/dist/hooks/use-copy-to-clipboard.d.ts +5 -0
- package/dist/hooks/use-copy-to-clipboard.js +24 -0
- package/dist/hooks/use-highlighted-code.d.ts +2 -0
- package/dist/hooks/use-highlighted-code.js +54 -0
- package/dist/hooks/use-mobile.d.ts +1 -0
- package/dist/hooks/use-mobile.js +17 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +94 -0
- package/dist/lib/highlighter.d.ts +9 -0
- package/dist/lib/highlighter.js +77 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.js +8 -0
- package/dist/shadcn-base.css +638 -0
- package/dist/styles.css +188 -0
- package/docs/COMPONENTS.md +405 -0
- package/llms.txt +36 -0
- package/package.json +119 -4
- package/skills/harebear-ui/SKILL.md +34 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Button } from "../ui/button.js";
|
|
3
|
+
import { Card, CardContent } from "../ui/card.js";
|
|
4
|
+
import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldSeparator } from "../ui/field.js";
|
|
5
|
+
import { Input } from "../ui/input.js";
|
|
6
|
+
import { Spinner } from "../ui/spinner.js";
|
|
7
|
+
import { cn } from "../../lib/utils.js";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
// src/components/auth/auth.tsx
|
|
11
|
+
/** Full-page wrapper for sign-in and sign-up screens. Put LoginForm or SignupForm inside. */
|
|
12
|
+
function AuthLayout({ variant = "centered", brand, image, footer, className, children }) {
|
|
13
|
+
const img = image && /* @__PURE__ */ jsx("img", {
|
|
14
|
+
src: image.src,
|
|
15
|
+
alt: image.alt,
|
|
16
|
+
className: "absolute inset-0 size-full object-cover dark:brightness-[0.6] dark:grayscale-[0.3]"
|
|
17
|
+
});
|
|
18
|
+
const small = footer && /* @__PURE__ */ jsx("div", {
|
|
19
|
+
className: "text-center text-xs text-balance text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 hover:[&_a]:text-foreground",
|
|
20
|
+
children: footer
|
|
21
|
+
});
|
|
22
|
+
if (variant === "split") return /* @__PURE__ */ jsxs("div", {
|
|
23
|
+
"data-slot": "auth-layout",
|
|
24
|
+
"data-variant": variant,
|
|
25
|
+
className: cn("grid min-h-svh lg:grid-cols-2", className),
|
|
26
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
27
|
+
className: "flex flex-col gap-4 p-6 md:p-10",
|
|
28
|
+
children: [brand && /* @__PURE__ */ jsx("div", {
|
|
29
|
+
className: "flex justify-center md:justify-start",
|
|
30
|
+
children: brand
|
|
31
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
32
|
+
className: "flex flex-1 items-center justify-center",
|
|
33
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
34
|
+
className: "flex w-full max-w-sm flex-col gap-6",
|
|
35
|
+
children: [children, small]
|
|
36
|
+
})
|
|
37
|
+
})]
|
|
38
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
39
|
+
className: "relative hidden bg-muted lg:block",
|
|
40
|
+
children: img
|
|
41
|
+
})]
|
|
42
|
+
});
|
|
43
|
+
const wide = variant === "card-image";
|
|
44
|
+
return /* @__PURE__ */ jsx("div", {
|
|
45
|
+
"data-slot": "auth-layout",
|
|
46
|
+
"data-variant": variant,
|
|
47
|
+
className: cn("flex min-h-svh flex-col items-center justify-center p-6 md:p-10", variant === "muted" && "bg-muted", className),
|
|
48
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
49
|
+
className: cn("flex w-full flex-col gap-6", wide ? "max-w-sm md:max-w-4xl" : "max-w-sm"),
|
|
50
|
+
children: [
|
|
51
|
+
brand && /* @__PURE__ */ jsx("div", {
|
|
52
|
+
className: "flex justify-center",
|
|
53
|
+
children: brand
|
|
54
|
+
}),
|
|
55
|
+
wide ? /* @__PURE__ */ jsx(Card, {
|
|
56
|
+
className: "overflow-hidden py-0",
|
|
57
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
58
|
+
className: "grid md:grid-cols-2",
|
|
59
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
60
|
+
className: "p-6 md:p-8",
|
|
61
|
+
children
|
|
62
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
63
|
+
className: "relative hidden min-h-80 bg-muted md:block",
|
|
64
|
+
children: img
|
|
65
|
+
})]
|
|
66
|
+
})
|
|
67
|
+
}) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { children }) }),
|
|
68
|
+
small
|
|
69
|
+
]
|
|
70
|
+
})
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function ProviderButtons({ providers, verb, onProviderClick, disabled }) {
|
|
74
|
+
const compact = providers.length >= 3 && providers.every((p) => p.icon);
|
|
75
|
+
return /* @__PURE__ */ jsx(Field, {
|
|
76
|
+
className: cn(compact && "grid gap-3"),
|
|
77
|
+
style: compact ? { gridTemplateColumns: `repeat(${providers.length}, minmax(0, 1fr))` } : void 0,
|
|
78
|
+
children: providers.map((p) => /* @__PURE__ */ jsxs(Button, {
|
|
79
|
+
type: "button",
|
|
80
|
+
variant: "outline",
|
|
81
|
+
disabled,
|
|
82
|
+
onClick: () => onProviderClick?.(p.id),
|
|
83
|
+
"aria-label": compact ? `${verb} with ${p.label}` : void 0,
|
|
84
|
+
children: [p.icon, !compact && `${verb} with ${p.label}`]
|
|
85
|
+
}, p.id))
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function BrandSvg({ d, ...props }) {
|
|
89
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
90
|
+
viewBox: "0 0 24 24",
|
|
91
|
+
"aria-hidden": "true",
|
|
92
|
+
...props,
|
|
93
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
94
|
+
d,
|
|
95
|
+
fill: "currentColor"
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** GitHub mark, for AuthProvider icons. */
|
|
100
|
+
function GitHubIcon(props) {
|
|
101
|
+
return /* @__PURE__ */ jsx(BrandSvg, {
|
|
102
|
+
d: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12",
|
|
103
|
+
...props
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Google "G" mark, for AuthProvider icons. */
|
|
107
|
+
const GOOGLE_PATH = "M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z";
|
|
108
|
+
function GoogleIcon(props) {
|
|
109
|
+
return /* @__PURE__ */ jsx(BrandSvg, {
|
|
110
|
+
d: GOOGLE_PATH,
|
|
111
|
+
...props
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const APPLE_PATH = "M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701";
|
|
115
|
+
/** Apple mark, for AuthProvider icons. */
|
|
116
|
+
function AppleIcon(props) {
|
|
117
|
+
return /* @__PURE__ */ jsx(BrandSvg, {
|
|
118
|
+
d: APPLE_PATH,
|
|
119
|
+
...props
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function Header({ title, description }) {
|
|
123
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
124
|
+
className: "flex flex-col items-center gap-1.5 text-center",
|
|
125
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
126
|
+
className: "text-2xl font-semibold tracking-[-0.02em]",
|
|
127
|
+
children: title
|
|
128
|
+
}), description && /* @__PURE__ */ jsx("p", {
|
|
129
|
+
className: "text-sm text-balance text-muted-foreground",
|
|
130
|
+
children: description
|
|
131
|
+
})]
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function readForm(form) {
|
|
135
|
+
return Object.fromEntries([...new FormData(form).entries()].map(([k, v]) => [k, typeof v === "string" ? v : ""]));
|
|
136
|
+
}
|
|
137
|
+
/** Email + password sign-in form with optional SSO buttons. Pair with AuthLayout. */
|
|
138
|
+
function LoginForm({ title = "Welcome back", description, onSubmit, mode = "password", providers = [], onProviderClick, providersFirst, forgotPasswordHref, signupHref, submitLabel, loading, error, className, ...props }) {
|
|
139
|
+
const id = React.useId();
|
|
140
|
+
const emailOnly = mode === "email-only";
|
|
141
|
+
const desc = description ?? (emailOnly ? "Enter your email and we'll send you a sign-in link" : "Sign in to your account");
|
|
142
|
+
const sso = providers.length > 0 && /* @__PURE__ */ jsx(ProviderButtons, {
|
|
143
|
+
providers,
|
|
144
|
+
verb: "Continue",
|
|
145
|
+
onProviderClick,
|
|
146
|
+
disabled: loading
|
|
147
|
+
});
|
|
148
|
+
return /* @__PURE__ */ jsx("form", {
|
|
149
|
+
"data-slot": "login-form",
|
|
150
|
+
className: cn("flex flex-col gap-6", className),
|
|
151
|
+
onSubmit: onSubmit ? (e) => {
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
const v = readForm(e.currentTarget);
|
|
154
|
+
onSubmit(emailOnly ? { email: v.email ?? "" } : {
|
|
155
|
+
email: v.email ?? "",
|
|
156
|
+
password: v.password ?? ""
|
|
157
|
+
});
|
|
158
|
+
} : void 0,
|
|
159
|
+
"aria-busy": loading || void 0,
|
|
160
|
+
...props,
|
|
161
|
+
children: /* @__PURE__ */ jsxs(FieldGroup, { children: [
|
|
162
|
+
/* @__PURE__ */ jsx(Header, {
|
|
163
|
+
title,
|
|
164
|
+
description: desc
|
|
165
|
+
}),
|
|
166
|
+
providersFirst && sso,
|
|
167
|
+
providersFirst && sso && /* @__PURE__ */ jsx(FieldSeparator, { children: "Or continue with email" }),
|
|
168
|
+
/* @__PURE__ */ jsxs(Field, { children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
169
|
+
htmlFor: `${id}-email`,
|
|
170
|
+
children: "Email"
|
|
171
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
172
|
+
id: `${id}-email`,
|
|
173
|
+
name: "email",
|
|
174
|
+
type: "email",
|
|
175
|
+
autoComplete: "email",
|
|
176
|
+
placeholder: "you@example.com",
|
|
177
|
+
required: true,
|
|
178
|
+
disabled: loading
|
|
179
|
+
})] }),
|
|
180
|
+
!emailOnly && /* @__PURE__ */ jsxs(Field, { children: [/* @__PURE__ */ jsxs("div", {
|
|
181
|
+
className: "flex items-center",
|
|
182
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
183
|
+
htmlFor: `${id}-password`,
|
|
184
|
+
children: "Password"
|
|
185
|
+
}), forgotPasswordHref && /* @__PURE__ */ jsx("a", {
|
|
186
|
+
href: forgotPasswordHref,
|
|
187
|
+
className: "ms-auto text-sm text-muted-foreground underline-offset-4 hover:text-foreground hover:underline",
|
|
188
|
+
children: "Forgot password?"
|
|
189
|
+
})]
|
|
190
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
191
|
+
id: `${id}-password`,
|
|
192
|
+
name: "password",
|
|
193
|
+
type: "password",
|
|
194
|
+
autoComplete: "current-password",
|
|
195
|
+
required: true,
|
|
196
|
+
disabled: loading
|
|
197
|
+
})] }),
|
|
198
|
+
error && /* @__PURE__ */ jsx(FieldError, { children: error }),
|
|
199
|
+
/* @__PURE__ */ jsx(Field, { children: /* @__PURE__ */ jsxs(Button, {
|
|
200
|
+
type: "submit",
|
|
201
|
+
disabled: loading,
|
|
202
|
+
children: [loading && /* @__PURE__ */ jsx(Spinner, {}), submitLabel ?? (emailOnly ? "Send sign-in link" : "Sign in")]
|
|
203
|
+
}) }),
|
|
204
|
+
!providersFirst && sso && /* @__PURE__ */ jsx(FieldSeparator, { children: "Or continue with" }),
|
|
205
|
+
!providersFirst && sso,
|
|
206
|
+
signupHref && /* @__PURE__ */ jsxs(FieldDescription, {
|
|
207
|
+
className: "text-center",
|
|
208
|
+
children: ["Don't have an account? ", /* @__PURE__ */ jsx("a", {
|
|
209
|
+
href: signupHref,
|
|
210
|
+
children: "Sign up"
|
|
211
|
+
})]
|
|
212
|
+
})
|
|
213
|
+
] })
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/** Account creation form: name, email, password (+ confirm), optional SSO. Pair with AuthLayout. */
|
|
217
|
+
function SignupForm({ title = "Create your account", description = "Enter your details to get started", onSubmit, nameField = true, confirmPassword = true, passwordHint, minPasswordLength = 8, providers = [], onProviderClick, providersFirst, loginHref, submitLabel = "Create account", loading, error, className, ...props }) {
|
|
218
|
+
const id = React.useId();
|
|
219
|
+
const sso = providers.length > 0 && /* @__PURE__ */ jsx(ProviderButtons, {
|
|
220
|
+
providers,
|
|
221
|
+
verb: "Sign up",
|
|
222
|
+
onProviderClick,
|
|
223
|
+
disabled: loading
|
|
224
|
+
});
|
|
225
|
+
return /* @__PURE__ */ jsx("form", {
|
|
226
|
+
"data-slot": "signup-form",
|
|
227
|
+
className: cn("flex flex-col gap-6", className),
|
|
228
|
+
onSubmit: (e) => {
|
|
229
|
+
const form = e.currentTarget;
|
|
230
|
+
const v = readForm(form);
|
|
231
|
+
const confirm = form.elements.namedItem("confirm");
|
|
232
|
+
if (confirm) {
|
|
233
|
+
confirm.setCustomValidity(v.confirm === v.password ? "" : "Passwords do not match");
|
|
234
|
+
if (!confirm.reportValidity()) {
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (!onSubmit) return;
|
|
240
|
+
e.preventDefault();
|
|
241
|
+
onSubmit({
|
|
242
|
+
...nameField ? { name: v.name ?? "" } : {},
|
|
243
|
+
email: v.email ?? "",
|
|
244
|
+
password: v.password ?? ""
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
"aria-busy": loading || void 0,
|
|
248
|
+
...props,
|
|
249
|
+
children: /* @__PURE__ */ jsxs(FieldGroup, { children: [
|
|
250
|
+
/* @__PURE__ */ jsx(Header, {
|
|
251
|
+
title,
|
|
252
|
+
description
|
|
253
|
+
}),
|
|
254
|
+
providersFirst && sso,
|
|
255
|
+
providersFirst && sso && /* @__PURE__ */ jsx(FieldSeparator, { children: "Or sign up with email" }),
|
|
256
|
+
nameField && /* @__PURE__ */ jsxs(Field, { children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
257
|
+
htmlFor: `${id}-name`,
|
|
258
|
+
children: "Full name"
|
|
259
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
260
|
+
id: `${id}-name`,
|
|
261
|
+
name: "name",
|
|
262
|
+
autoComplete: "name",
|
|
263
|
+
required: true,
|
|
264
|
+
disabled: loading
|
|
265
|
+
})] }),
|
|
266
|
+
/* @__PURE__ */ jsxs(Field, { children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
267
|
+
htmlFor: `${id}-email`,
|
|
268
|
+
children: "Email"
|
|
269
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
270
|
+
id: `${id}-email`,
|
|
271
|
+
name: "email",
|
|
272
|
+
type: "email",
|
|
273
|
+
autoComplete: "email",
|
|
274
|
+
placeholder: "you@example.com",
|
|
275
|
+
required: true,
|
|
276
|
+
disabled: loading
|
|
277
|
+
})] }),
|
|
278
|
+
/* @__PURE__ */ jsxs(Field, { children: [
|
|
279
|
+
/* @__PURE__ */ jsx(FieldLabel, {
|
|
280
|
+
htmlFor: `${id}-password`,
|
|
281
|
+
children: "Password"
|
|
282
|
+
}),
|
|
283
|
+
/* @__PURE__ */ jsx(Input, {
|
|
284
|
+
id: `${id}-password`,
|
|
285
|
+
name: "password",
|
|
286
|
+
type: "password",
|
|
287
|
+
autoComplete: "new-password",
|
|
288
|
+
onInput: (e) => (e.currentTarget.form?.elements.namedItem("confirm"))?.setCustomValidity(""),
|
|
289
|
+
minLength: minPasswordLength,
|
|
290
|
+
required: true,
|
|
291
|
+
disabled: loading
|
|
292
|
+
}),
|
|
293
|
+
/* @__PURE__ */ jsx(FieldDescription, { children: passwordHint ?? `At least ${minPasswordLength} characters.` })
|
|
294
|
+
] }),
|
|
295
|
+
confirmPassword && /* @__PURE__ */ jsxs(Field, { children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
296
|
+
htmlFor: `${id}-confirm`,
|
|
297
|
+
children: "Confirm password"
|
|
298
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
299
|
+
id: `${id}-confirm`,
|
|
300
|
+
name: "confirm",
|
|
301
|
+
type: "password",
|
|
302
|
+
autoComplete: "new-password",
|
|
303
|
+
required: true,
|
|
304
|
+
disabled: loading,
|
|
305
|
+
onInput: (e) => e.currentTarget.setCustomValidity("")
|
|
306
|
+
})] }),
|
|
307
|
+
error && /* @__PURE__ */ jsx(FieldError, { children: error }),
|
|
308
|
+
/* @__PURE__ */ jsx(Field, { children: /* @__PURE__ */ jsxs(Button, {
|
|
309
|
+
type: "submit",
|
|
310
|
+
disabled: loading,
|
|
311
|
+
children: [loading && /* @__PURE__ */ jsx(Spinner, {}), submitLabel]
|
|
312
|
+
}) }),
|
|
313
|
+
!providersFirst && sso && /* @__PURE__ */ jsx(FieldSeparator, { children: "Or continue with" }),
|
|
314
|
+
!providersFirst && sso,
|
|
315
|
+
loginHref && /* @__PURE__ */ jsxs(FieldDescription, {
|
|
316
|
+
className: "text-center",
|
|
317
|
+
children: ["Already have an account? ", /* @__PURE__ */ jsx("a", {
|
|
318
|
+
href: loginHref,
|
|
319
|
+
children: "Sign in"
|
|
320
|
+
})]
|
|
321
|
+
})
|
|
322
|
+
] })
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
export { AppleIcon, AuthLayout, GitHubIcon, GoogleIcon, LoginForm, SignupForm };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface BarListItem {
|
|
3
|
+
label: React.ReactNode;
|
|
4
|
+
/** Share 0-100 used for the bar width and the bold percent. */
|
|
5
|
+
percent: number;
|
|
6
|
+
/** Optional absolute value shown muted before the percent, e.g. "32,068". */
|
|
7
|
+
value?: React.ReactNode;
|
|
8
|
+
color?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Ranked list of labeled bars, e.g. traffic sources or top pages. */
|
|
11
|
+
declare function BarList({ items, className, ...props }: Omit<React.ComponentProps<"div">, "children"> & {
|
|
12
|
+
items: BarListItem[];
|
|
13
|
+
}): React.JSX.Element;
|
|
14
|
+
export { BarList };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { UsageMeter } from "../admin/usage-meter.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
// src/components/data/bar-list.tsx
|
|
6
|
+
/** Ranked list of labeled bars, e.g. traffic sources or top pages. */
|
|
7
|
+
function BarList({ items, className, ...props }) {
|
|
8
|
+
return /* @__PURE__ */ jsx("div", {
|
|
9
|
+
"data-slot": "bar-list",
|
|
10
|
+
className: cn("flex flex-col gap-4.5", className),
|
|
11
|
+
...props,
|
|
12
|
+
children: items.map((item, i) => /* @__PURE__ */ jsx(UsageMeter, {
|
|
13
|
+
label: item.label,
|
|
14
|
+
percent: item.percent,
|
|
15
|
+
color: item.color,
|
|
16
|
+
value: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17
|
+
item.value !== void 0 && /* @__PURE__ */ jsxs("span", {
|
|
18
|
+
className: "font-normal text-muted-foreground",
|
|
19
|
+
children: [item.value, " · "]
|
|
20
|
+
}),
|
|
21
|
+
item.percent,
|
|
22
|
+
"%"
|
|
23
|
+
] })
|
|
24
|
+
}, i))
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export { BarList };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ColumnDef, RowData } from "@tanstack/react-table";
|
|
3
|
+
/** Feature set used by DataTable. Use with createDataTableColumnHelper for typed columns. */
|
|
4
|
+
export declare const dataTableFeatures: {
|
|
5
|
+
columnFilteringFeature: import("@tanstack/react-table").TableFeature;
|
|
6
|
+
globalFilteringFeature: import("@tanstack/react-table").TableFeature;
|
|
7
|
+
rowSortingFeature: import("@tanstack/react-table").TableFeature;
|
|
8
|
+
rowSelectionFeature: import("@tanstack/react-table").TableFeature;
|
|
9
|
+
rowPaginationFeature: import("@tanstack/react-table").TableFeature;
|
|
10
|
+
filteredRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
11
|
+
sortedRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
12
|
+
paginatedRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
13
|
+
filterFns: {
|
|
14
|
+
includesString: import("@tanstack/react-table").CreatedFilterFn<any, any>;
|
|
15
|
+
};
|
|
16
|
+
sortFns: {
|
|
17
|
+
alphanumeric: import("@tanstack/react-table").CreatedSortFn<any, any>;
|
|
18
|
+
text: import("@tanstack/react-table").CreatedSortFn<any, any>;
|
|
19
|
+
basic: import("@tanstack/react-table").CreatedSortFn<any, any>;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type DataTableFeatures = typeof dataTableFeatures;
|
|
23
|
+
export type DataTableColumnDef<TData extends RowData> = ColumnDef<DataTableFeatures, TData, any>;
|
|
24
|
+
/** Typed column helper for DataTable: `const col = createDataTableColumnHelper<Customer>()`. */
|
|
25
|
+
export declare function createDataTableColumnHelper<TData extends RowData>(): import("@tanstack/react-table").ColumnHelper<{
|
|
26
|
+
columnFilteringFeature: import("@tanstack/react-table").TableFeature;
|
|
27
|
+
globalFilteringFeature: import("@tanstack/react-table").TableFeature;
|
|
28
|
+
rowSortingFeature: import("@tanstack/react-table").TableFeature;
|
|
29
|
+
rowSelectionFeature: import("@tanstack/react-table").TableFeature;
|
|
30
|
+
rowPaginationFeature: import("@tanstack/react-table").TableFeature;
|
|
31
|
+
filteredRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
32
|
+
sortedRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
33
|
+
paginatedRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
34
|
+
filterFns: {
|
|
35
|
+
includesString: import("@tanstack/react-table").CreatedFilterFn<any, any>;
|
|
36
|
+
};
|
|
37
|
+
sortFns: {
|
|
38
|
+
alphanumeric: import("@tanstack/react-table").CreatedSortFn<any, any>;
|
|
39
|
+
text: import("@tanstack/react-table").CreatedSortFn<any, any>;
|
|
40
|
+
basic: import("@tanstack/react-table").CreatedSortFn<any, any>;
|
|
41
|
+
};
|
|
42
|
+
}, TData>;
|
|
43
|
+
export interface DataTableProps<TData extends RowData> {
|
|
44
|
+
columns: DataTableColumnDef<TData>[];
|
|
45
|
+
data: TData[];
|
|
46
|
+
/** Stable row id. Defaults to the row index. Needed for selection and activeRowId. */
|
|
47
|
+
getRowId?: (row: TData, index: number) => string;
|
|
48
|
+
/** Show a search box that filters across all string/number columns. */
|
|
49
|
+
searchable?: boolean;
|
|
50
|
+
searchPlaceholder?: string;
|
|
51
|
+
/** Extra controls rendered after the search box (filters, sort, actions). */
|
|
52
|
+
toolbar?: React.ReactNode;
|
|
53
|
+
/** Adds a checkbox column. */
|
|
54
|
+
selectable?: boolean;
|
|
55
|
+
onSelectionChange?: (rows: TData[]) => void;
|
|
56
|
+
onRowClick?: (row: TData) => void;
|
|
57
|
+
/** Highlights this row (e.g. the one open in a detail panel). */
|
|
58
|
+
activeRowId?: string;
|
|
59
|
+
/** Rows per page. 0 disables pagination. Default 10. */
|
|
60
|
+
pageSize?: number;
|
|
61
|
+
/** Shown when there are no rows. */
|
|
62
|
+
emptyMessage?: React.ReactNode;
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Sortable, searchable, selectable, paginated table. Client-side processing via
|
|
67
|
+
* TanStack Table v9. For server-side data, compose Table primitives with useTable directly.
|
|
68
|
+
*/
|
|
69
|
+
declare function DataTable<TData extends RowData>({ columns: columnsProp, data, getRowId, searchable, searchPlaceholder, toolbar, selectable, onSelectionChange, onRowClick, activeRowId, pageSize, emptyMessage, className }: DataTableProps<TData>): React.JSX.Element;
|
|
70
|
+
export { DataTable };
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { SearchInput } from "../admin/search-input.js";
|
|
3
|
+
import { Button } from "../ui/button.js";
|
|
4
|
+
import { Checkbox } from "../ui/checkbox.js";
|
|
5
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table.js";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
|
10
|
+
import { columnFilteringFeature, createColumnHelper, createFilteredRowModel, createPaginatedRowModel, createSortedRowModel, filterFn_includesString, globalFilteringFeature, rowPaginationFeature, rowSelectionFeature, rowSortingFeature, sortFn_alphanumeric, sortFn_basic, sortFn_text, tableFeatures, useTable } from "@tanstack/react-table";
|
|
11
|
+
// src/components/data/data-table.tsx
|
|
12
|
+
/** Feature set used by DataTable. Use with createDataTableColumnHelper for typed columns. */
|
|
13
|
+
const dataTableFeatures = tableFeatures({
|
|
14
|
+
columnFilteringFeature,
|
|
15
|
+
globalFilteringFeature,
|
|
16
|
+
rowSortingFeature,
|
|
17
|
+
rowSelectionFeature,
|
|
18
|
+
rowPaginationFeature,
|
|
19
|
+
filteredRowModel: createFilteredRowModel(),
|
|
20
|
+
sortedRowModel: createSortedRowModel(),
|
|
21
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
22
|
+
filterFns: { includesString: filterFn_includesString },
|
|
23
|
+
sortFns: {
|
|
24
|
+
alphanumeric: sortFn_alphanumeric,
|
|
25
|
+
text: sortFn_text,
|
|
26
|
+
basic: sortFn_basic
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
/** Typed column helper for DataTable: `const col = createDataTableColumnHelper<Customer>()`. */
|
|
30
|
+
function createDataTableColumnHelper() {
|
|
31
|
+
return createColumnHelper();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Sortable, searchable, selectable, paginated table. Client-side processing via
|
|
35
|
+
* TanStack Table v9. For server-side data, compose Table primitives with useTable directly.
|
|
36
|
+
*/
|
|
37
|
+
function DataTable({ columns: columnsProp, data, getRowId, searchable, searchPlaceholder = "Search", toolbar, selectable, onSelectionChange, onRowClick, activeRowId, pageSize = 10, emptyMessage = "No results.", className }) {
|
|
38
|
+
const columns = React.useMemo(() => {
|
|
39
|
+
if (!selectable) return columnsProp;
|
|
40
|
+
return [{
|
|
41
|
+
id: "__select",
|
|
42
|
+
enableSorting: false,
|
|
43
|
+
enableGlobalFilter: false,
|
|
44
|
+
header: ({ table }) => /* @__PURE__ */ jsx(Checkbox, {
|
|
45
|
+
"aria-label": "Select all rows",
|
|
46
|
+
checked: table.getIsAllPageRowsSelected() ? true : table.getIsSomePageRowsSelected() ? "indeterminate" : false,
|
|
47
|
+
onCheckedChange: (v) => table.toggleAllPageRowsSelected(!!v)
|
|
48
|
+
}),
|
|
49
|
+
cell: ({ row }) => /* @__PURE__ */ jsx(Checkbox, {
|
|
50
|
+
"aria-label": "Select row",
|
|
51
|
+
checked: row.getIsSelected(),
|
|
52
|
+
onClick: (e) => e.stopPropagation(),
|
|
53
|
+
onCheckedChange: (v) => row.toggleSelected(!!v)
|
|
54
|
+
})
|
|
55
|
+
}, ...columnsProp];
|
|
56
|
+
}, [columnsProp, selectable]);
|
|
57
|
+
const paginate = pageSize > 0;
|
|
58
|
+
const table = useTable({
|
|
59
|
+
features: dataTableFeatures,
|
|
60
|
+
columns,
|
|
61
|
+
data,
|
|
62
|
+
getRowId,
|
|
63
|
+
globalFilterFn: "includesString",
|
|
64
|
+
initialState: { pagination: {
|
|
65
|
+
pageIndex: 0,
|
|
66
|
+
pageSize: paginate ? pageSize : Number.MAX_SAFE_INTEGER
|
|
67
|
+
} }
|
|
68
|
+
});
|
|
69
|
+
const rowSelection = table.state.rowSelection;
|
|
70
|
+
const onSelectionChangeRef = React.useRef(onSelectionChange);
|
|
71
|
+
onSelectionChangeRef.current = onSelectionChange;
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
onSelectionChangeRef.current?.(table.getSelectedRowModel().rows.map((r) => r.original));
|
|
74
|
+
}, [rowSelection]);
|
|
75
|
+
const filteredCount = table.getFilteredRowModel().rows.length;
|
|
76
|
+
const { pageIndex, pageSize: size } = table.state.pagination;
|
|
77
|
+
const pageCount = table.getPageCount();
|
|
78
|
+
const rows = table.getRowModel().rows;
|
|
79
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
80
|
+
"data-slot": "data-table",
|
|
81
|
+
className: cn("flex min-w-0 flex-col gap-4", className),
|
|
82
|
+
children: [
|
|
83
|
+
(searchable || toolbar) && /* @__PURE__ */ jsxs("div", {
|
|
84
|
+
className: "flex flex-wrap items-center gap-2.5",
|
|
85
|
+
children: [searchable && /* @__PURE__ */ jsx(SearchInput, {
|
|
86
|
+
wrapperClassName: "min-w-50 flex-[0_1_300px]",
|
|
87
|
+
placeholder: searchPlaceholder,
|
|
88
|
+
value: String(table.state.globalFilter ?? ""),
|
|
89
|
+
onChange: (e) => table.setGlobalFilter(e.target.value)
|
|
90
|
+
}), toolbar]
|
|
91
|
+
}),
|
|
92
|
+
/* @__PURE__ */ jsxs(Table, {
|
|
93
|
+
className: "border-spacing-y-0.5",
|
|
94
|
+
children: [/* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((group) => /* @__PURE__ */ jsx(TableRow, { children: group.headers.map((header) => {
|
|
95
|
+
const canSort = header.column.getCanSort();
|
|
96
|
+
const sorted = header.column.getIsSorted();
|
|
97
|
+
const SortIcon = sorted === "asc" ? ArrowUpIcon : sorted === "desc" ? ArrowDownIcon : ArrowUpDownIcon;
|
|
98
|
+
const meta = header.column.columnDef.meta;
|
|
99
|
+
return /* @__PURE__ */ jsx(TableHead, {
|
|
100
|
+
className: cn(header.column.id === "__select" && "w-11", meta?.align === "right" && "text-right"),
|
|
101
|
+
"aria-sort": sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : void 0,
|
|
102
|
+
children: header.isPlaceholder ? null : canSort ? /* @__PURE__ */ jsxs("button", {
|
|
103
|
+
type: "button",
|
|
104
|
+
onClick: header.column.getToggleSortingHandler(),
|
|
105
|
+
className: cn("-mx-1 inline-flex items-center gap-1 rounded-sm px-1 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:outline-none", sorted && "text-foreground"),
|
|
106
|
+
children: [/* @__PURE__ */ jsx(table.FlexRender, { header }), /* @__PURE__ */ jsx(SortIcon, { className: cn("size-3.5", !sorted && "opacity-40") })]
|
|
107
|
+
}) : /* @__PURE__ */ jsx(table.FlexRender, { header })
|
|
108
|
+
}, header.id);
|
|
109
|
+
}) }, group.id)) }), /* @__PURE__ */ jsx(TableBody, { children: rows.length === 0 ? /* @__PURE__ */ jsx(TableRow, {
|
|
110
|
+
className: "hover:bg-transparent",
|
|
111
|
+
children: /* @__PURE__ */ jsx(TableCell, {
|
|
112
|
+
colSpan: columns.length,
|
|
113
|
+
className: "h-24 text-center text-muted-foreground",
|
|
114
|
+
children: emptyMessage
|
|
115
|
+
})
|
|
116
|
+
}) : rows.map((row) => /* @__PURE__ */ jsx(TableRow, {
|
|
117
|
+
"data-state": row.getIsSelected() || row.id === activeRowId ? "selected" : void 0,
|
|
118
|
+
onClick: onRowClick ? () => onRowClick(row.original) : void 0,
|
|
119
|
+
className: cn(onRowClick && "cursor-pointer"),
|
|
120
|
+
children: row.getAllCells().map((cell) => {
|
|
121
|
+
const meta = cell.column.columnDef.meta;
|
|
122
|
+
return /* @__PURE__ */ jsx(TableCell, {
|
|
123
|
+
className: cn("py-3", meta?.align === "right" && "text-right"),
|
|
124
|
+
children: /* @__PURE__ */ jsx(table.FlexRender, { cell })
|
|
125
|
+
}, cell.id);
|
|
126
|
+
})
|
|
127
|
+
}, row.id)) })]
|
|
128
|
+
}),
|
|
129
|
+
paginate && filteredCount > 0 && /* @__PURE__ */ jsxs("div", {
|
|
130
|
+
className: "flex flex-wrap items-center justify-between gap-3 px-1.5",
|
|
131
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
132
|
+
className: "text-[13px] text-muted-foreground tabular-nums",
|
|
133
|
+
children: [
|
|
134
|
+
"Showing ",
|
|
135
|
+
Math.min(filteredCount, pageIndex * size + 1),
|
|
136
|
+
"-",
|
|
137
|
+
Math.min(filteredCount, (pageIndex + 1) * size),
|
|
138
|
+
" of",
|
|
139
|
+
" ",
|
|
140
|
+
filteredCount
|
|
141
|
+
]
|
|
142
|
+
}), pageCount > 1 && /* @__PURE__ */ jsxs("nav", {
|
|
143
|
+
"aria-label": "Pagination",
|
|
144
|
+
className: "flex items-center gap-1",
|
|
145
|
+
children: [
|
|
146
|
+
/* @__PURE__ */ jsx(Button, {
|
|
147
|
+
variant: "ghost",
|
|
148
|
+
size: "icon-sm",
|
|
149
|
+
"aria-label": "Previous page",
|
|
150
|
+
disabled: !table.getCanPreviousPage(),
|
|
151
|
+
onClick: () => table.previousPage(),
|
|
152
|
+
children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
|
|
153
|
+
}),
|
|
154
|
+
pageList(pageIndex, pageCount).map((p, i) => p === "…" ? /* @__PURE__ */ jsx("span", {
|
|
155
|
+
className: "px-1 text-muted-foreground",
|
|
156
|
+
children: "…"
|
|
157
|
+
}, `gap-${i}`) : /* @__PURE__ */ jsx(Button, {
|
|
158
|
+
variant: p === pageIndex ? "secondary" : "ghost",
|
|
159
|
+
size: "icon-sm",
|
|
160
|
+
className: "tabular-nums",
|
|
161
|
+
"aria-current": p === pageIndex ? "page" : void 0,
|
|
162
|
+
onClick: () => table.setPageIndex(p),
|
|
163
|
+
children: p + 1
|
|
164
|
+
}, p)),
|
|
165
|
+
/* @__PURE__ */ jsx(Button, {
|
|
166
|
+
variant: "ghost",
|
|
167
|
+
size: "icon-sm",
|
|
168
|
+
"aria-label": "Next page",
|
|
169
|
+
disabled: !table.getCanNextPage(),
|
|
170
|
+
onClick: () => table.nextPage(),
|
|
171
|
+
children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
|
|
172
|
+
})
|
|
173
|
+
]
|
|
174
|
+
})]
|
|
175
|
+
})
|
|
176
|
+
]
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/** Zero-based page indexes with ellipses: 0 1 2 … 27 */
|
|
180
|
+
function pageList(current, count) {
|
|
181
|
+
if (count <= 7) return Array.from({ length: count }, (_, i) => i);
|
|
182
|
+
const set = new Set([
|
|
183
|
+
0,
|
|
184
|
+
count - 1,
|
|
185
|
+
current - 1,
|
|
186
|
+
current,
|
|
187
|
+
current + 1
|
|
188
|
+
].filter((p) => p >= 0 && p < count));
|
|
189
|
+
if (current <= 2) for (const p of [
|
|
190
|
+
1,
|
|
191
|
+
2,
|
|
192
|
+
3
|
|
193
|
+
]) set.add(p);
|
|
194
|
+
if (current >= count - 3) for (const p of [
|
|
195
|
+
count - 4,
|
|
196
|
+
count - 3,
|
|
197
|
+
count - 2
|
|
198
|
+
]) set.add(p);
|
|
199
|
+
const sorted = [...set].sort((a, b) => a - b);
|
|
200
|
+
const out = [];
|
|
201
|
+
sorted.forEach((p, i) => {
|
|
202
|
+
const prev = sorted[i - 1];
|
|
203
|
+
if (i > 0 && prev !== void 0 && p - prev > 1) out.push("…");
|
|
204
|
+
out.push(p);
|
|
205
|
+
});
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
export { DataTable, createDataTableColumnHelper, dataTableFeatures };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Catmull-Rom smoothed SVG path through points. */
|
|
3
|
+
export declare function smoothPath(points: ReadonlyArray<readonly [number, number]>): string;
|
|
4
|
+
export interface SparklineProps extends Omit<React.ComponentProps<"svg">, "children" | "fill"> {
|
|
5
|
+
data: number[];
|
|
6
|
+
/** Stroke/fill color. Any CSS color; default the primary token. */
|
|
7
|
+
color?: string;
|
|
8
|
+
/** Show the soft area fill under the line. Default true. */
|
|
9
|
+
area?: boolean;
|
|
10
|
+
/** Fixed y-range; defaults to the data's min/max. */
|
|
11
|
+
min?: number;
|
|
12
|
+
max?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Tiny smoothed trend line. Stretches to its container width; set height via className. */
|
|
15
|
+
declare function Sparkline({ data, color, area: showArea, min, max, className, ...props }: SparklineProps): React.JSX.Element;
|
|
16
|
+
export { Sparkline };
|