@neasg/design-system 0.2.1 → 0.4.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/README.md +37 -1
- package/dist/alert.d.ts +15 -0
- package/dist/alert.js +24 -0
- package/dist/avatar.d.ts +5 -2
- package/dist/avatar.js +8 -4
- package/dist/back-button.d.ts +6 -0
- package/dist/back-button.js +8 -0
- package/dist/badge.d.ts +4 -2
- package/dist/badge.js +13 -2
- package/dist/breadcrumb.js +2 -2
- package/dist/button.d.ts +2 -2
- package/dist/button.js +3 -4
- package/dist/calendar.d.ts +8 -0
- package/dist/calendar.js +80 -0
- package/dist/card.d.ts +17 -0
- package/dist/card.js +16 -0
- package/dist/checkbox.d.ts +13 -0
- package/dist/checkbox.js +29 -0
- package/dist/collapsible.d.ts +19 -0
- package/dist/collapsible.js +20 -0
- package/dist/command-search.js +4 -3
- package/dist/copy-button.d.ts +19 -0
- package/dist/copy-button.js +31 -0
- package/dist/date-input.d.ts +22 -0
- package/dist/date-input.js +61 -0
- package/dist/dialog-primitive.js +1 -1
- package/dist/draggable-tabs.js +1 -1
- package/dist/drawer.js +1 -1
- package/dist/editable-table.js +2 -2
- package/dist/empty-state.js +1 -1
- package/dist/field.d.ts +4 -1
- package/dist/field.js +3 -3
- package/dist/file-upload.d.ts +12 -0
- package/dist/file-upload.js +106 -0
- package/dist/index.d.ts +56 -4
- package/dist/index.js +30 -3
- package/dist/input-control.d.ts +1 -1
- package/dist/input-control.js +1 -1
- package/dist/input.js +2 -2
- package/dist/label-value-grid.d.ts +3 -1
- package/dist/label-value-grid.js +15 -2
- package/dist/label-value.d.ts +4 -1
- package/dist/label-value.js +5 -3
- package/dist/layout.d.ts +7 -1
- package/dist/layout.js +3 -3
- package/dist/lib/date-utils.d.ts +9 -0
- package/dist/lib/date-utils.js +34 -0
- package/dist/link.d.ts +9 -0
- package/dist/link.js +19 -0
- package/dist/multi-select.d.ts +31 -0
- package/dist/multi-select.js +78 -0
- package/dist/otp-input.js +1 -1
- package/dist/page-section.d.ts +14 -0
- package/dist/page-section.js +13 -0
- package/dist/pagination.js +3 -2
- package/dist/popover-menu.d.ts +35 -0
- package/dist/popover-menu.js +36 -0
- package/dist/popover-primitive.d.ts +7 -0
- package/dist/popover-primitive.js +11 -0
- package/dist/popover.d.ts +12 -0
- package/dist/popover.js +8 -0
- package/dist/progress.d.ts +10 -0
- package/dist/progress.js +17 -0
- package/dist/search-input.js +1 -1
- package/dist/section-nav.d.ts +22 -0
- package/dist/section-nav.js +25 -0
- package/dist/select-primitive.d.ts +3 -1
- package/dist/select-primitive.js +3 -2
- package/dist/select.d.ts +1 -0
- package/dist/select.js +3 -3
- package/dist/separator.d.ts +6 -0
- package/dist/separator.js +6 -0
- package/dist/skeleton.d.ts +4 -0
- package/dist/skeleton.js +6 -0
- package/dist/spinner.d.ts +22 -0
- package/dist/spinner.js +24 -0
- package/dist/styles.css +42 -0
- package/dist/switch.d.ts +12 -0
- package/dist/switch.js +16 -0
- package/dist/table.d.ts +7 -1
- package/dist/table.js +26 -12
- package/dist/tabs.d.ts +24 -1
- package/dist/tabs.js +61 -3
- package/dist/tailwind-preset.js +9 -0
- package/dist/textarea.js +1 -1
- package/dist/toaster.d.ts +13 -0
- package/dist/toaster.js +27 -0
- package/dist/typography.d.ts +2 -0
- package/dist/typography.js +9 -2
- package/dist/use-error-shake.d.ts +7 -0
- package/dist/use-error-shake.js +36 -0
- package/dist/use-file-upload.d.ts +34 -0
- package/dist/use-file-upload.js +133 -0
- package/dist/use-sticky-sentinel.d.ts +16 -0
- package/dist/use-sticky-sentinel.js +62 -0
- package/dist/use-table-sort.d.ts +28 -0
- package/dist/use-table-sort.js +53 -0
- package/dist/use-viewport-threshold.d.ts +12 -0
- package/dist/use-viewport-threshold.js +43 -0
- package/package.json +36 -6
package/dist/skeleton.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "./lib/utils";
|
|
3
|
+
function Skeleton({ className, ...props }) {
|
|
4
|
+
return (_jsx("div", { "aria-hidden": "true", className: cn("animate-pulse rounded-md bg-muted/80", className), ...props }));
|
|
5
|
+
}
|
|
6
|
+
export { Skeleton };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
type SpinnerSize = "sm" | "md" | "lg";
|
|
3
|
+
export interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
size?: SpinnerSize;
|
|
5
|
+
/** Accessible label announced to screen readers. */
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
declare function Spinner({ size, label, className, ...props }: SpinnerProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export interface PageLoaderProps extends Omit<SpinnerProps, "size"> {
|
|
10
|
+
size?: SpinnerSize;
|
|
11
|
+
/** Optional visible label shown beneath the spinner. */
|
|
12
|
+
message?: React.ReactNode;
|
|
13
|
+
/** Optional supporting description below the message. */
|
|
14
|
+
description?: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
declare function PageLoader({ size, label, message, description, className, ...props }: PageLoaderProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export interface FullPageLoaderProps extends PageLoaderProps {
|
|
18
|
+
/** Background overlay. `"solid"` blocks the page; `"blur"` keeps content visible. */
|
|
19
|
+
surface?: "solid" | "blur";
|
|
20
|
+
}
|
|
21
|
+
declare function FullPageLoader({ size, label, message, description, surface, className, ...props }: FullPageLoaderProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export { Spinner, PageLoader, FullPageLoader };
|
package/dist/spinner.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "./lib/utils";
|
|
4
|
+
import { Typography } from "./typography";
|
|
5
|
+
const SIZE_CLASS = {
|
|
6
|
+
sm: "h-4 w-4 border-2",
|
|
7
|
+
md: "h-6 w-6 border-2",
|
|
8
|
+
lg: "h-10 w-10 border-[3px]",
|
|
9
|
+
};
|
|
10
|
+
function Spinner({ size = "md", label = "Loading", className, ...props }) {
|
|
11
|
+
return (_jsxs("div", { role: "status", "aria-live": "polite", className: cn("inline-flex items-center justify-center", className), ...props, children: [_jsx("span", { "aria-hidden": "true", className: cn("shrink-0 animate-spin rounded-full border-current border-t-transparent text-primary", SIZE_CLASS[size]) }), _jsx("span", { className: "sr-only", children: label })] }));
|
|
12
|
+
}
|
|
13
|
+
function LoaderStack({ size, label, message, description, }) {
|
|
14
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "mb-4", children: _jsx(Spinner, { size: size, label: label }) }), message || description ? (_jsxs("div", { className: "space-y-1.5", children: [message ? (_jsx(Typography, { as: "p", variant: "bodySm", className: "font-semibold", children: message })) : null, description ? (_jsx(Typography, { as: "p", variant: "caption", className: "max-w-md", children: description })) : null] })) : null] }));
|
|
15
|
+
}
|
|
16
|
+
function PageLoader({ size = "lg", label = "Loading", message, description, className, ...props }) {
|
|
17
|
+
return (_jsx("div", { className: cn("flex flex-col items-center justify-center px-6 py-10 text-center", className), ...props, children: _jsx(LoaderStack, { size: size, label: label, message: message, description: description }) }));
|
|
18
|
+
}
|
|
19
|
+
function FullPageLoader({ size = "lg", label = "Loading", message, description, surface = "solid", className, ...props }) {
|
|
20
|
+
return (_jsx("div", { className: cn("fixed inset-0 z-50 flex flex-col items-center justify-center px-6 py-10 text-center", surface === "solid"
|
|
21
|
+
? "bg-background"
|
|
22
|
+
: "bg-background/60 backdrop-blur-sm", className), ...props, children: _jsx(LoaderStack, { size: size, label: label, message: message, description: description }) }));
|
|
23
|
+
}
|
|
24
|
+
export { Spinner, PageLoader, FullPageLoader };
|
package/dist/styles.css
CHANGED
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
@tailwind components;
|
|
3
3
|
@tailwind utilities;
|
|
4
4
|
|
|
5
|
+
@keyframes collapsible-down {
|
|
6
|
+
from { height: 0; }
|
|
7
|
+
to { height: var(--radix-collapsible-content-height); }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@keyframes collapsible-up {
|
|
11
|
+
from { height: var(--radix-collapsible-content-height); }
|
|
12
|
+
to { height: 0; }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
[data-slot="collapsible-content"][data-state="open"] {
|
|
16
|
+
animation: collapsible-down 200ms ease-out;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
[data-slot="collapsible-content"][data-state="closed"] {
|
|
20
|
+
animation: collapsible-up 200ms ease-out;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@keyframes shake {
|
|
24
|
+
0%,
|
|
25
|
+
100% {
|
|
26
|
+
transform: translateX(0);
|
|
27
|
+
}
|
|
28
|
+
20%,
|
|
29
|
+
60% {
|
|
30
|
+
transform: translateX(-3px);
|
|
31
|
+
}
|
|
32
|
+
40%,
|
|
33
|
+
80% {
|
|
34
|
+
transform: translateX(3px);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.animate-shake {
|
|
39
|
+
animation: shake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97);
|
|
40
|
+
}
|
|
41
|
+
|
|
5
42
|
@layer base {
|
|
6
43
|
:root {
|
|
7
44
|
--space-0: 0rem;
|
|
@@ -15,6 +52,8 @@
|
|
|
15
52
|
--space-10: 2.5rem;
|
|
16
53
|
--space-12: 3rem;
|
|
17
54
|
|
|
55
|
+
--size-control: 2.5rem;
|
|
56
|
+
|
|
18
57
|
--nea-green-50: 140 57% 96%;
|
|
19
58
|
--nea-green-100: 141 50% 90%;
|
|
20
59
|
--nea-green-200: 142 45% 80%;
|
|
@@ -208,5 +247,8 @@
|
|
|
208
247
|
@apply bg-background text-foreground;
|
|
209
248
|
font-family: var(--font-sans);
|
|
210
249
|
font-weight: var(--font-weight-regular);
|
|
250
|
+
-webkit-font-smoothing: antialiased;
|
|
251
|
+
-moz-osx-font-smoothing: grayscale;
|
|
252
|
+
font-smooth: never;
|
|
211
253
|
}
|
|
212
254
|
}
|
package/dist/switch.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
|
3
|
+
export interface SwitchProps extends React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> {
|
|
4
|
+
/** Label rendered next to the switch. When provided, wraps the control in a clickable `<label>`. */
|
|
5
|
+
label?: React.ReactNode;
|
|
6
|
+
/** Description shown below the label in smaller muted text. */
|
|
7
|
+
description?: React.ReactNode;
|
|
8
|
+
/** Optional class for the outer wrapping `<label>` (only used when `label` is set). */
|
|
9
|
+
wrapperClassName?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const Switch: React.ForwardRefExoticComponent<SwitchProps & React.RefAttributes<HTMLButtonElement>>;
|
|
12
|
+
export { Switch };
|
package/dist/switch.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
|
5
|
+
import { cn } from "./lib/utils";
|
|
6
|
+
import { Typography } from "./typography";
|
|
7
|
+
const Switch = React.forwardRef(({ className, label, description, wrapperClassName, id, ...props }, ref) => {
|
|
8
|
+
const generatedId = React.useId();
|
|
9
|
+
const switchId = id !== null && id !== void 0 ? id : `switch-${generatedId}`;
|
|
10
|
+
const control = (_jsx(SwitchPrimitives.Root, { id: switchId, ref: ref, className: cn("peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", !label && className), ...props, children: _jsx(SwitchPrimitives.Thumb, { className: "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0" }) }));
|
|
11
|
+
if (!label)
|
|
12
|
+
return control;
|
|
13
|
+
return (_jsxs("label", { htmlFor: switchId, className: cn("inline-flex cursor-pointer items-center gap-2 text-sm", props.disabled && "cursor-not-allowed opacity-50", wrapperClassName, className), children: [control, _jsxs("div", { className: "select-none", children: [_jsx(Typography, { as: "span", variant: "bodySm", children: label }), description ? (_jsx(Typography, { as: "p", variant: "caption", className: "mt-1", children: description })) : null] })] }));
|
|
14
|
+
});
|
|
15
|
+
Switch.displayName = "Switch";
|
|
16
|
+
export { Switch };
|
package/dist/table.d.ts
CHANGED
|
@@ -67,6 +67,8 @@ export interface TableColumn<Row> {
|
|
|
67
67
|
skeleton?: TableSkeletonConfig;
|
|
68
68
|
headerClassName?: string;
|
|
69
69
|
cellClassName?: string;
|
|
70
|
+
/** Pins the column to the left or right edge while the table scrolls horizontally. */
|
|
71
|
+
sticky?: "left" | "right";
|
|
70
72
|
}
|
|
71
73
|
export interface TableSortingProps {
|
|
72
74
|
field: string | null;
|
|
@@ -102,6 +104,10 @@ export interface TableProps<Row> extends Omit<React.HTMLAttributes<HTMLDivElemen
|
|
|
102
104
|
pagination?: Omit<PaginationProps, "className">;
|
|
103
105
|
loading?: boolean;
|
|
104
106
|
loadingRows?: number;
|
|
107
|
+
/** When true, applies alternating row backgrounds for easier scanning. */
|
|
108
|
+
striped?: boolean;
|
|
109
|
+
/** When set, rows become interactive: cursor-pointer, hover highlight, and click handling. */
|
|
110
|
+
onRowClick?: (row: Row, index: number) => void;
|
|
105
111
|
}
|
|
106
|
-
declare function Table<Row>({ columns, rows, getRowKey, emptyState, toolbar, caption, className, containerClassName, tableClassName, rowClassName, sorting, pagination, loading, loadingRows, ...props }: TableProps<Row>): import("react/jsx-runtime").JSX.Element;
|
|
112
|
+
declare function Table<Row>({ columns, rows, getRowKey, emptyState, toolbar, caption, className, containerClassName, tableClassName, rowClassName, sorting, pagination, loading, loadingRows, striped, onRowClick, ...props }: TableProps<Row>): import("react/jsx-runtime").JSX.Element;
|
|
107
113
|
export { Table, TableRoot, TableContainer, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, SortableTableHead, TableRowSkeleton, };
|
package/dist/table.js
CHANGED
|
@@ -5,18 +5,19 @@ import { ArrowDownIcon } from "./animated-icons/arrow-down";
|
|
|
5
5
|
import { ArrowUpIcon } from "./animated-icons/arrow-up";
|
|
6
6
|
import { EmptyState } from "./empty-state";
|
|
7
7
|
import { Pagination } from "./pagination";
|
|
8
|
+
import { Skeleton } from "./skeleton";
|
|
8
9
|
import { cn } from "./lib/utils";
|
|
9
10
|
const TableRoot = React.forwardRef(({ className, wrapperClassName: _wrapperClassName, ...props }, ref) => (_jsx("table", { ref: ref, className: cn("w-full caption-bottom text-sm", className), ...props })));
|
|
10
11
|
TableRoot.displayName = "TableRoot";
|
|
11
12
|
const TableContainer = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("overflow-x-auto rounded-lg border bg-card", className), ...props })));
|
|
12
13
|
TableContainer.displayName = "TableContainer";
|
|
13
|
-
const TableHeader = React.forwardRef(({ className, ...props }, ref) => (_jsx("thead", { ref: ref, className: cn("sticky top-0 z-10 bg-muted
|
|
14
|
+
const TableHeader = React.forwardRef(({ className, ...props }, ref) => (_jsx("thead", { ref: ref, className: cn("sticky top-0 z-10 bg-muted [&_tr]:border-b [&>tr]:bg-inherit", className), ...props })));
|
|
14
15
|
TableHeader.displayName = "TableHeader";
|
|
15
16
|
const TableBody = React.forwardRef(({ className, ...props }, ref) => (_jsx("tbody", { ref: ref, className: cn("[&_tr:last-child]:border-0", className), ...props })));
|
|
16
17
|
TableBody.displayName = "TableBody";
|
|
17
18
|
const TableFooter = React.forwardRef(({ className, ...props }, ref) => (_jsx("tfoot", { ref: ref, className: cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className), ...props })));
|
|
18
19
|
TableFooter.displayName = "TableFooter";
|
|
19
|
-
const TableRow = React.forwardRef(({ className, ...props }, ref) => (_jsx("tr", { ref: ref, className: cn("border-b transition-colors hover:bg-muted
|
|
20
|
+
const TableRow = React.forwardRef(({ className, ...props }, ref) => (_jsx("tr", { ref: ref, className: cn("border-b bg-card transition-colors cursor-pointer hover:bg-muted data-[state=selected]:bg-muted", className), ...props })));
|
|
20
21
|
TableRow.displayName = "TableRow";
|
|
21
22
|
const TableHead = React.forwardRef(({ className, style, width, minWidth, maxWidth, wrap = false, ...props }, ref) => (_jsx("th", { ref: ref, className: cn("h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", wrap ? "whitespace-normal" : "whitespace-nowrap", className), style: {
|
|
22
23
|
...style,
|
|
@@ -35,6 +36,19 @@ const TableCell = React.forwardRef(({ className, style, width, minWidth, maxWidt
|
|
|
35
36
|
TableCell.displayName = "TableCell";
|
|
36
37
|
const TableCaption = React.forwardRef(({ className, ...props }, ref) => (_jsx("caption", { ref: ref, className: cn("mt-4 text-sm text-muted-foreground", className), ...props })));
|
|
37
38
|
TableCaption.displayName = "TableCaption";
|
|
39
|
+
function stickyClassName(sticky, kind) {
|
|
40
|
+
if (!sticky)
|
|
41
|
+
return undefined;
|
|
42
|
+
const side = sticky === "left" ? "left-0" : "right-0";
|
|
43
|
+
const shadow = sticky === "left"
|
|
44
|
+
? "shadow-[inset_-1px_0_0_0_var(--color-border)]"
|
|
45
|
+
: "shadow-[inset_1px_0_0_0_var(--color-border)]";
|
|
46
|
+
// bg-inherit follows the row's current bg (default/hover/striped/header),
|
|
47
|
+
// so sticky cells stay in sync with the rest of the row.
|
|
48
|
+
// z-20 on header cells so they layer above sticky body cells on the other axis.
|
|
49
|
+
const z = kind === "head" ? "z-20" : "z-10";
|
|
50
|
+
return cn("sticky bg-inherit", side, z, shadow);
|
|
51
|
+
}
|
|
38
52
|
function alignClassName(align) {
|
|
39
53
|
switch (align) {
|
|
40
54
|
case "center":
|
|
@@ -46,7 +60,7 @@ function alignClassName(align) {
|
|
|
46
60
|
}
|
|
47
61
|
}
|
|
48
62
|
function SkeletonBlock({ width = "6rem", height = "1rem", badge = false, }) {
|
|
49
|
-
return (_jsx(
|
|
63
|
+
return (_jsx(Skeleton, { className: badge ? "rounded-full" : "rounded", style: { width, height } }));
|
|
50
64
|
}
|
|
51
65
|
function SkeletonCell({ config }) {
|
|
52
66
|
var _a;
|
|
@@ -101,24 +115,24 @@ function getDefaultSkeleton(column) {
|
|
|
101
115
|
height: "1rem",
|
|
102
116
|
};
|
|
103
117
|
}
|
|
104
|
-
function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { message: "No data available", description: "There are no rows to display in this table yet.", className: "border-0 bg-transparent px-0 py-6" })), toolbar, caption, className, containerClassName, tableClassName, rowClassName, sorting, pagination, loading = false, loadingRows = 5, ...props }) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const container = (_jsx(TableContainer, { className: cn("w-full", className, containerClassName), ...props, children: _jsxs(TableRoot, { className: cn(shouldUseFixedLayout && "table-fixed", tableClassName), children: [caption ? _jsx(TableCaption, { children: caption }) : null, _jsx(TableHeader, { children: _jsx(TableRow, { children: columns.map((column) => {
|
|
118
|
+
function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { message: "No data available", description: "There are no rows to display in this table yet.", className: "border-0 bg-transparent px-0 py-6" })), toolbar, caption, className, containerClassName, tableClassName, rowClassName, sorting, pagination, loading = false, loadingRows = 5, striped = false, onRowClick, ...props }) {
|
|
119
|
+
// Use auto layout so the browser can grow a column to contain its (nowrap)
|
|
120
|
+
// header. Caller can opt back into fixed layout via `tableClassName="table-fixed"`.
|
|
121
|
+
const shouldUseFixedLayout = false;
|
|
122
|
+
const container = (_jsx(TableContainer, { className: cn("w-full", loading && "pointer-events-none select-none", className, containerClassName), ...props, children: _jsxs(TableRoot, { className: cn(shouldUseFixedLayout && "table-fixed", tableClassName), children: [caption ? _jsx(TableCaption, { children: caption }) : null, _jsx(TableHeader, { children: _jsx(TableRow, { children: columns.map((column) => {
|
|
109
123
|
var _a;
|
|
110
124
|
const sortField = (_a = column.sortKey) !== null && _a !== void 0 ? _a : column.key;
|
|
111
125
|
const isSortable = column.sortable && sorting;
|
|
112
|
-
return (isSortable ? (_jsx(SortableTableHead, { field: sortField, currentSortField: sorting.field, currentSortDirection: sorting.direction, onSort: sorting.onSort, className: column.headerClassName, allowWrap: column.wrap, width: column.width, minWidth: column.minWidth, maxWidth: column.maxWidth, align: column.align, sortLabel: column.sortLabel, children: column.header }, column.key)) : (_jsx(TableHead, { width: column.width, minWidth: column.minWidth, maxWidth: column.maxWidth, wrap: column.wrap, className: cn(alignClassName(column.align), column.headerClassName), children: column.header }, column.key)));
|
|
113
|
-
}) }) }), _jsx(TableBody, { children: loading ? (_jsx(TableRowSkeleton, { rows: loadingRows, columns: columns.map((column) => { var _a; return (_a = column.skeleton) !== null && _a !== void 0 ? _a : getDefaultSkeleton(column); }) })) : rows.length ? (rows.map((row, rowIndex) => (_jsx(TableRow, { className: typeof rowClassName === "function"
|
|
126
|
+
return (isSortable ? (_jsx(SortableTableHead, { field: sortField, currentSortField: sorting.field, currentSortDirection: sorting.direction, onSort: sorting.onSort, className: cn(stickyClassName(column.sticky, "head"), column.headerClassName), allowWrap: column.wrap, width: column.width, minWidth: column.minWidth, maxWidth: column.maxWidth, align: column.align, sortLabel: column.sortLabel, children: column.header }, column.key)) : (_jsx(TableHead, { width: column.width, minWidth: column.minWidth, maxWidth: column.maxWidth, wrap: column.wrap, className: cn(alignClassName(column.align), stickyClassName(column.sticky, "head"), column.headerClassName), children: column.header }, column.key)));
|
|
127
|
+
}) }) }), _jsx(TableBody, { className: cn(striped && "[&>tr:nth-child(even)]:bg-muted/60"), children: loading ? (_jsx(TableRowSkeleton, { rows: loadingRows, columns: columns.map((column) => { var _a; return (_a = column.skeleton) !== null && _a !== void 0 ? _a : getDefaultSkeleton(column); }) })) : rows.length ? (rows.map((row, rowIndex) => (_jsx(TableRow, { onClick: onRowClick ? () => onRowClick(row, rowIndex) : undefined, className: typeof rowClassName === "function"
|
|
114
128
|
? rowClassName(row, rowIndex)
|
|
115
129
|
: rowClassName, children: columns.map((column) => {
|
|
116
130
|
var _a, _b;
|
|
117
131
|
const value = (_b = (_a = column.cell) === null || _a === void 0 ? void 0 : _a.call(column, row, rowIndex)) !== null && _b !== void 0 ? _b : (column.accessorKey !== undefined
|
|
118
132
|
? row[column.accessorKey]
|
|
119
133
|
: null);
|
|
120
|
-
return (_jsx(TableCell, { width: column.width, minWidth: column.minWidth, maxWidth: column.maxWidth, wrap: column.wrap, className: cn(alignClassName(column.align), column.cellClassName), children: value }, column.key));
|
|
121
|
-
}) }, getRowKey(row, rowIndex))))) : (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length, className: "p-6", children: emptyState }) })) })] }) }));
|
|
134
|
+
return (_jsx(TableCell, { width: column.width, minWidth: column.minWidth, maxWidth: column.maxWidth, wrap: column.wrap, className: cn(alignClassName(column.align), stickyClassName(column.sticky, "cell"), column.cellClassName), children: value }, column.key));
|
|
135
|
+
}) }, getRowKey(row, rowIndex))))) : (_jsx(TableRow, { className: "cursor-default hover:bg-card", children: _jsx(TableCell, { colSpan: columns.length, className: "p-6", children: emptyState }) })) })] }) }));
|
|
122
136
|
if (!pagination) {
|
|
123
137
|
return (_jsxs("div", { className: "space-y-4", children: [toolbar, container] }));
|
|
124
138
|
}
|
package/dist/tabs.d.ts
CHANGED
|
@@ -20,12 +20,35 @@ interface TabsContentProps extends React.ComponentPropsWithoutRef<typeof TabsPri
|
|
|
20
20
|
value: string;
|
|
21
21
|
}
|
|
22
22
|
declare const TabsContent: React.ForwardRefExoticComponent<TabsContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
23
|
+
export interface TabDropdownSection {
|
|
24
|
+
id: string;
|
|
25
|
+
label: React.ReactNode;
|
|
26
|
+
}
|
|
23
27
|
export interface TabsItem {
|
|
24
28
|
value: string;
|
|
25
29
|
label: React.ReactNode;
|
|
26
30
|
content: React.ReactNode;
|
|
27
31
|
disabled?: boolean;
|
|
32
|
+
/** When provided, the tab shows a chevron and opens a dropdown menu of in-page sections. */
|
|
33
|
+
sections?: TabDropdownSection[];
|
|
34
|
+
/** Scroll offset in pixels when scrolling to a section (default 200). */
|
|
35
|
+
scrollOffset?: number;
|
|
36
|
+
/** Override the default scroll-to-id behavior when a section is picked. */
|
|
37
|
+
onSectionSelect?: (sectionId: string) => void;
|
|
38
|
+
/** Currently-active section id. When set, the parent tab renders as active and the item is highlighted in the dropdown. */
|
|
39
|
+
activeSection?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface TabWithDropdownProps {
|
|
42
|
+
value: string;
|
|
43
|
+
label: React.ReactNode;
|
|
44
|
+
sections?: TabDropdownSection[];
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
scrollOffset?: number;
|
|
47
|
+
onSectionSelect?: (sectionId: string) => void;
|
|
48
|
+
activeSection?: string;
|
|
49
|
+
className?: string;
|
|
28
50
|
}
|
|
51
|
+
declare function TabWithDropdown({ value, label, sections, disabled, scrollOffset, onSectionSelect, activeSection, className, }: TabWithDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
29
52
|
export interface TabsProps extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>, "children" | "value" | "defaultValue" | "onValueChange"> {
|
|
30
53
|
items: TabsItem[];
|
|
31
54
|
defaultValue?: string;
|
|
@@ -36,4 +59,4 @@ export interface TabsProps extends Omit<React.ComponentPropsWithoutRef<typeof Ta
|
|
|
36
59
|
panelClassName?: string;
|
|
37
60
|
}
|
|
38
61
|
declare function Tabs({ items, defaultValue, value, onValueChange, className, listClassName, triggerClassName, panelClassName, ...props }: TabsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
39
|
-
export { Tabs, TabsRoot, TabsList, TabsTrigger, TabsContent };
|
|
62
|
+
export { Tabs, TabsRoot, TabsList, TabsTrigger, TabsContent, TabWithDropdown };
|
package/dist/tabs.js
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
5
|
+
import { ChevronDownIcon } from "./animated-icons/chevron-down";
|
|
5
6
|
import { cn } from "./lib/utils";
|
|
7
|
+
import { Popover } from "./popover";
|
|
8
|
+
import { PopoverMenuItem, PopoverMenuSection } from "./popover-menu";
|
|
6
9
|
const TabsContext = React.createContext(null);
|
|
7
10
|
export function useTabsContext() {
|
|
8
11
|
return React.useContext(TabsContext);
|
|
@@ -23,16 +26,71 @@ const TabsRoot = React.forwardRef(({ defaultValue = "", value: valueProp, onValu
|
|
|
23
26
|
TabsRoot.displayName = TabsPrimitive.Root.displayName;
|
|
24
27
|
const TabsList = React.forwardRef(({ className, ...props }, ref) => (_jsx(TabsPrimitive.List, { ref: ref, className: cn("inline-flex h-fit items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", className), ...props })));
|
|
25
28
|
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
26
|
-
const TabsTrigger = React.forwardRef(({ className, value, ...props }, ref) => (_jsx(TabsPrimitive.Trigger, { ref: ref, value: value, className: cn("inline-flex cursor-pointer items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm", className), ...props })));
|
|
29
|
+
const TabsTrigger = React.forwardRef(({ className, value, ...props }, ref) => (_jsx(TabsPrimitive.Trigger, { ref: ref, value: value, className: cn("inline-flex cursor-pointer items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=inactive]:hover:bg-background/50 data-[state=inactive]:hover:text-foreground data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm", className), ...props })));
|
|
27
30
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
28
31
|
const TabsContent = React.forwardRef(({ className, value, ...props }, ref) => (_jsx(TabsPrimitive.Content, { ref: ref, value: value, className: cn("focus-visible:outline-none", className), ...props })));
|
|
29
32
|
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
33
|
+
function TabTriggerWithDropdown({ item, triggerClassName, }) {
|
|
34
|
+
var _a, _b, _c, _d, _e;
|
|
35
|
+
const [open, setOpen] = React.useState(false);
|
|
36
|
+
const sections = (_a = item.sections) !== null && _a !== void 0 ? _a : [];
|
|
37
|
+
const [internalActiveSection, setInternalActiveSection] = React.useState((_c = (_b = sections[0]) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : null);
|
|
38
|
+
const tabsContext = useTabsContext();
|
|
39
|
+
const scrollOffset = (_d = item.scrollOffset) !== null && _d !== void 0 ? _d : 200;
|
|
40
|
+
const activeSection = (_e = item.activeSection) !== null && _e !== void 0 ? _e : internalActiveSection;
|
|
41
|
+
const handleSectionClick = (sectionId) => {
|
|
42
|
+
setOpen(false);
|
|
43
|
+
setInternalActiveSection(sectionId);
|
|
44
|
+
if (item.onSectionSelect) {
|
|
45
|
+
item.onSectionSelect(sectionId);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const doScroll = () => {
|
|
49
|
+
const el = document.getElementById(sectionId);
|
|
50
|
+
if (!el)
|
|
51
|
+
return;
|
|
52
|
+
const top = el.getBoundingClientRect().top + window.scrollY - scrollOffset;
|
|
53
|
+
window.scrollTo({ top, behavior: "smooth" });
|
|
54
|
+
};
|
|
55
|
+
if (tabsContext && tabsContext.value !== item.value) {
|
|
56
|
+
tabsContext.setValue(item.value);
|
|
57
|
+
setTimeout(doScroll, 50);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
doScroll();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
// Clear the internal "active section" when the user switches away from
|
|
64
|
+
// this tab, so we don't leave a stale highlight in the dropdown menu.
|
|
65
|
+
const isTabSelected = tabsContext
|
|
66
|
+
? tabsContext.value === item.value
|
|
67
|
+
: false;
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
var _a, _b;
|
|
70
|
+
if (!isTabSelected && item.activeSection === undefined) {
|
|
71
|
+
setInternalActiveSection((_b = (_a = sections[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null);
|
|
72
|
+
}
|
|
73
|
+
}, [isTabSelected, item.activeSection, sections]);
|
|
74
|
+
return (_jsx(Popover, { open: open, onOpenChange: setOpen, surface: "menu", align: "start", contentClassName: "min-w-[12rem]", trigger: _jsxs(TabsTrigger, { value: item.value, disabled: item.disabled, "data-state": isTabSelected ? "active" : "inactive", className: cn("flex items-center gap-1", triggerClassName), children: [item.label, _jsx(ChevronDownIcon, { size: 12, className: cn("transition-transform duration-200", open && "rotate-180") })] }), children: _jsx(PopoverMenuSection, { children: sections.map((section) => (_jsx(PopoverMenuItem, { active: isTabSelected && activeSection === section.id, className: "py-2 pl-2 pr-8", onClick: () => handleSectionClick(section.id), children: section.label }, section.id))) }) }));
|
|
75
|
+
}
|
|
76
|
+
function TabWithDropdown({ value, label, sections, disabled, scrollOffset, onSectionSelect, activeSection, className, }) {
|
|
77
|
+
return (_jsx(TabTriggerWithDropdown, { item: {
|
|
78
|
+
value,
|
|
79
|
+
label,
|
|
80
|
+
content: null,
|
|
81
|
+
sections,
|
|
82
|
+
disabled,
|
|
83
|
+
scrollOffset,
|
|
84
|
+
onSectionSelect,
|
|
85
|
+
activeSection,
|
|
86
|
+
}, triggerClassName: className }));
|
|
87
|
+
}
|
|
30
88
|
function Tabs({ items, defaultValue, value, onValueChange, className, listClassName, triggerClassName, panelClassName, ...props }) {
|
|
31
89
|
var _a, _b;
|
|
32
90
|
const fallbackValue = (_b = defaultValue !== null && defaultValue !== void 0 ? defaultValue : (_a = items[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : "";
|
|
33
91
|
if (!items.length) {
|
|
34
92
|
return null;
|
|
35
93
|
}
|
|
36
|
-
return (_jsxs(TabsRoot, { defaultValue: fallbackValue, value: value, onValueChange: onValueChange, className: cn("space-y-4", className), ...props, children: [_jsx(TabsList, { className: listClassName, children: items.map((item) => (_jsx(TabsTrigger, { value: item.value, disabled: item.disabled, className: triggerClassName, children: item.label }, item.value))) }), items.map((item) => (_jsx(TabsContent, { value: item.value, className: cn("
|
|
94
|
+
return (_jsxs(TabsRoot, { defaultValue: fallbackValue, value: value, onValueChange: onValueChange, className: cn("space-y-4", className), ...props, children: [_jsx(TabsList, { className: listClassName, children: items.map((item) => item.sections && item.sections.length > 0 ? (_jsx(TabTriggerWithDropdown, { item: item, triggerClassName: triggerClassName }, item.value)) : (_jsx(TabsTrigger, { value: item.value, disabled: item.disabled, className: triggerClassName, children: item.label }, item.value))) }), items.map((item) => (_jsx(TabsContent, { value: item.value, className: cn("pt-4", panelClassName), children: item.content }, item.value)))] }));
|
|
37
95
|
}
|
|
38
|
-
export { Tabs, TabsRoot, TabsList, TabsTrigger, TabsContent };
|
|
96
|
+
export { Tabs, TabsRoot, TabsList, TabsTrigger, TabsContent, TabWithDropdown };
|
package/dist/tailwind-preset.js
CHANGED
|
@@ -64,6 +64,15 @@ export const neaTailwindPreset = {
|
|
|
64
64
|
semibold: "var(--font-weight-semibold)",
|
|
65
65
|
bold: "var(--font-weight-bold)",
|
|
66
66
|
},
|
|
67
|
+
height: {
|
|
68
|
+
control: "var(--size-control)",
|
|
69
|
+
},
|
|
70
|
+
width: {
|
|
71
|
+
control: "var(--size-control)",
|
|
72
|
+
},
|
|
73
|
+
minHeight: {
|
|
74
|
+
control: "var(--size-control)",
|
|
75
|
+
},
|
|
67
76
|
spacing: {
|
|
68
77
|
0: "var(--space-0)",
|
|
69
78
|
1: "var(--space-1)",
|
package/dist/textarea.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Field, FieldDescription, FieldError, FieldLabel } from "./field";
|
|
4
4
|
import { cn } from "./lib/utils";
|
|
5
|
-
const TextareaControl = React.forwardRef(({ className, ...props }, ref) => (_jsx("textarea", { ref: ref, "data-slot": "textarea", className: cn("flex min-h-20 w-full rounded-md border border-input bg-transparent px-3 py-2 text-
|
|
5
|
+
const TextareaControl = React.forwardRef(({ className, ...props }, ref) => (_jsx("textarea", { ref: ref, "data-slot": "textarea", className: cn("flex min-h-20 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/20", className), ...props })));
|
|
6
6
|
TextareaControl.displayName = "TextareaControl";
|
|
7
7
|
const Textarea = React.forwardRef(({ label, description, error, invalid = false, hideLabel = false, className, textareaClassName, labelClassName, descriptionClassName, errorClassName, id, disabled, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, ...props }, ref) => {
|
|
8
8
|
const generatedId = React.useId();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { toast } from "sonner";
|
|
2
|
+
export interface ToasterProps {
|
|
3
|
+
/** Position of the toast container. */
|
|
4
|
+
position?: "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
|
5
|
+
/** Maximum number of toasts visible at once. */
|
|
6
|
+
visibleToasts?: number;
|
|
7
|
+
/** Whether to show the close button on each toast. */
|
|
8
|
+
closeButton?: boolean;
|
|
9
|
+
/** Duration in milliseconds before auto-dismiss. */
|
|
10
|
+
duration?: number;
|
|
11
|
+
}
|
|
12
|
+
declare function Toaster({ position, visibleToasts, closeButton, duration, }: ToasterProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export { Toaster, toast };
|
package/dist/toaster.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Toaster as SonnerToaster, toast } from "sonner";
|
|
4
|
+
import { buttonVariants } from "./button";
|
|
5
|
+
import { XIcon } from "./animated-icons/x";
|
|
6
|
+
import { cn } from "./lib/utils";
|
|
7
|
+
function Toaster({ position = "top-right", visibleToasts = 3, closeButton = true, duration = 4000, }) {
|
|
8
|
+
return (_jsx(SonnerToaster, { position: position, visibleToasts: visibleToasts, closeButton: closeButton, duration: duration, icons: {
|
|
9
|
+
close: _jsx(XIcon, { size: 14 }),
|
|
10
|
+
}, toastOptions: {
|
|
11
|
+
classNames: {
|
|
12
|
+
toast: "group relative border border-border bg-background text-foreground shadow-md rounded-lg p-4 pr-10 flex items-start gap-3",
|
|
13
|
+
content: "min-w-0 flex-1 gap-1",
|
|
14
|
+
title: "text-sm font-medium leading-5",
|
|
15
|
+
description: "text-sm text-muted-foreground leading-5",
|
|
16
|
+
icon: "mt-0.5 text-muted-foreground",
|
|
17
|
+
actionButton: cn(buttonVariants({ variant: "link" }), "!h-auto !min-h-0 !self-start !p-0 !text-sm !font-medium !text-primary !no-underline hover:!text-primary/80 hover:!no-underline"),
|
|
18
|
+
cancelButton: cn(buttonVariants({ variant: "link" }), "!h-auto !min-h-0 !self-start !bg-transparent !p-0 !text-sm !font-medium !text-muted-foreground !no-underline hover:!bg-transparent hover:!text-foreground hover:!no-underline"),
|
|
19
|
+
closeButton: "!absolute !left-auto !right-2 !top-2 !h-6 !w-6 !translate-x-0 !translate-y-0 !rounded-md !border-0 !bg-transparent !text-muted-foreground hover:!bg-accent hover:!text-foreground",
|
|
20
|
+
success: "border-[hsl(var(--status-success-foreground))]/20 [&_[data-icon]]:text-[hsl(var(--status-success-foreground))]",
|
|
21
|
+
error: "border-destructive/20 [&_[data-icon]]:text-[hsl(var(--status-destructive-foreground))]",
|
|
22
|
+
warning: "border-[hsl(var(--status-warning-foreground))]/20 [&_[data-icon]]:text-[hsl(var(--status-warning-foreground))]",
|
|
23
|
+
info: "border-[hsl(var(--status-info-foreground))]/20 [&_[data-icon]]:text-[hsl(var(--status-info-foreground))]",
|
|
24
|
+
},
|
|
25
|
+
} }));
|
|
26
|
+
}
|
|
27
|
+
export { Toaster, toast };
|
package/dist/typography.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ declare const typographyVariants: (props?: ({
|
|
|
6
6
|
export interface TypographyProps extends React.HTMLAttributes<HTMLElement>, VariantProps<typeof typographyVariants> {
|
|
7
7
|
as?: keyof React.JSX.IntrinsicElements;
|
|
8
8
|
asChild?: boolean;
|
|
9
|
+
/** Override the built-in text colour. Use "inherit" to inherit from a parent container. */
|
|
10
|
+
color?: "inherit" | "foreground" | "muted";
|
|
9
11
|
}
|
|
10
12
|
declare const Typography: React.ForwardRefExoticComponent<TypographyProps & React.RefAttributes<HTMLElement>>;
|
|
11
13
|
export { Typography, typographyVariants };
|
package/dist/typography.js
CHANGED
|
@@ -30,12 +30,19 @@ const defaultElementByVariant = {
|
|
|
30
30
|
caption: "span",
|
|
31
31
|
eyebrow: "span",
|
|
32
32
|
};
|
|
33
|
-
const Typography = React.forwardRef(({ as, asChild = false, className, variant, ...props }, ref) => {
|
|
33
|
+
const Typography = React.forwardRef(({ as, asChild = false, className, variant, color, ...props }, ref) => {
|
|
34
34
|
const resolvedVariant = (variant !== null && variant !== void 0 ? variant : "body");
|
|
35
35
|
const Comp = asChild
|
|
36
36
|
? Slot
|
|
37
37
|
: as !== null && as !== void 0 ? as : defaultElementByVariant[resolvedVariant];
|
|
38
|
-
|
|
38
|
+
const colorClass = color === "inherit"
|
|
39
|
+
? "text-inherit"
|
|
40
|
+
: color === "foreground"
|
|
41
|
+
? "text-foreground"
|
|
42
|
+
: color === "muted"
|
|
43
|
+
? "text-muted-foreground"
|
|
44
|
+
: undefined;
|
|
45
|
+
return (_jsx(Comp, { ref: ref, className: cn(typographyVariants({ variant: resolvedVariant }), colorClass, className), ...props }));
|
|
39
46
|
});
|
|
40
47
|
Typography.displayName = "Typography";
|
|
41
48
|
export { Typography, typographyVariants };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
const DEFAULT_SHAKE_DURATION_MS = 500;
|
|
4
|
+
export function useErrorShake(options = {}) {
|
|
5
|
+
const { durationMs = DEFAULT_SHAKE_DURATION_MS } = options;
|
|
6
|
+
const [shouldShake, setShouldShake] = useState(false);
|
|
7
|
+
const timeoutRef = useRef(null);
|
|
8
|
+
const frameRef = useRef(null);
|
|
9
|
+
const clearPendingAnimation = useCallback(() => {
|
|
10
|
+
if (timeoutRef.current !== null) {
|
|
11
|
+
window.clearTimeout(timeoutRef.current);
|
|
12
|
+
timeoutRef.current = null;
|
|
13
|
+
}
|
|
14
|
+
if (frameRef.current !== null) {
|
|
15
|
+
window.cancelAnimationFrame(frameRef.current);
|
|
16
|
+
frameRef.current = null;
|
|
17
|
+
}
|
|
18
|
+
}, []);
|
|
19
|
+
const triggerShake = useCallback(() => {
|
|
20
|
+
clearPendingAnimation();
|
|
21
|
+
setShouldShake(false);
|
|
22
|
+
frameRef.current = window.requestAnimationFrame(() => {
|
|
23
|
+
frameRef.current = null;
|
|
24
|
+
setShouldShake(true);
|
|
25
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
26
|
+
setShouldShake(false);
|
|
27
|
+
timeoutRef.current = null;
|
|
28
|
+
}, durationMs);
|
|
29
|
+
});
|
|
30
|
+
}, [clearPendingAnimation, durationMs]);
|
|
31
|
+
useEffect(() => clearPendingAnimation, [clearPendingAnimation]);
|
|
32
|
+
return {
|
|
33
|
+
shouldShake,
|
|
34
|
+
triggerShake,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type FileUploadStatus = "pending" | "uploading" | "complete" | "error";
|
|
2
|
+
export interface FileUploadResult {
|
|
3
|
+
name: string;
|
|
4
|
+
size: string;
|
|
5
|
+
sizeBytes: number;
|
|
6
|
+
type: string;
|
|
7
|
+
key: string;
|
|
8
|
+
url: string;
|
|
9
|
+
}
|
|
10
|
+
export interface FileUploadProgress {
|
|
11
|
+
name: string;
|
|
12
|
+
progress: number;
|
|
13
|
+
status: FileUploadStatus;
|
|
14
|
+
error?: string;
|
|
15
|
+
result?: FileUploadResult;
|
|
16
|
+
}
|
|
17
|
+
export interface UseFileUploadOptions {
|
|
18
|
+
/** Upload endpoint URL. Defaults to `/api/upload`. */
|
|
19
|
+
endpoint?: string;
|
|
20
|
+
/** Folder path sent alongside each file as form field `folder`. */
|
|
21
|
+
folder?: string;
|
|
22
|
+
/** Additional form fields appended to every request. */
|
|
23
|
+
extraFields?: Record<string, string>;
|
|
24
|
+
onComplete?: (results: FileUploadProgress[]) => void;
|
|
25
|
+
onError?: (error: Error) => void;
|
|
26
|
+
}
|
|
27
|
+
export interface UseFileUploadReturn {
|
|
28
|
+
uploadProgress: Map<string, FileUploadProgress>;
|
|
29
|
+
isUploading: boolean;
|
|
30
|
+
uploadFiles: (files: File[], folder?: string) => Promise<FileUploadProgress[]>;
|
|
31
|
+
reset: () => void;
|
|
32
|
+
cancel: () => void;
|
|
33
|
+
}
|
|
34
|
+
export declare function useFileUpload(options?: UseFileUploadOptions): UseFileUploadReturn;
|