@neasg/design-system 0.4.5 → 0.4.7
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 +6 -139
- package/dist/animated-icons/shield.js +20 -8
- package/dist/copy-button.d.ts +2 -2
- package/dist/copy-button.js +6 -3
- package/dist/editable-table.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/layout.d.ts +23 -6
- package/dist/layout.js +72 -8
- package/dist/section-nav.d.ts +1 -0
- package/dist/section-nav.js +77 -11
- package/dist/select.js +26 -3
- package/dist/table.d.ts +3 -1
- package/dist/table.js +9 -6
- package/package.json +5 -161
package/README.md
CHANGED
|
@@ -4,6 +4,12 @@ Shared NEA UI primitives, theme tokens, and Storybook documentation.
|
|
|
4
4
|
|
|
5
5
|
## Local Development
|
|
6
6
|
|
|
7
|
+
Use Node 22 for local development:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
nvm use
|
|
11
|
+
```
|
|
12
|
+
|
|
7
13
|
```bash
|
|
8
14
|
npm install
|
|
9
15
|
npm test
|
|
@@ -11,145 +17,6 @@ npm run build
|
|
|
11
17
|
npm run storybook
|
|
12
18
|
```
|
|
13
19
|
|
|
14
|
-
## Package Usage
|
|
15
|
-
|
|
16
|
-
```ts
|
|
17
|
-
import "@neasg/design-system/styles.css";
|
|
18
|
-
import { Badge, Button, Typography } from "@neasg/design-system";
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
For Tailwind-based apps, prefer the exported preset so you inherit the shared colors, font tokens, radius tokens, container sizing, and motion defaults:
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
// tailwind.config.ts
|
|
25
|
-
import type { Config } from "tailwindcss";
|
|
26
|
-
import neaTailwindPreset, {
|
|
27
|
-
neaDesignSystemContent,
|
|
28
|
-
} from "@neasg/design-system/tailwind-preset";
|
|
29
|
-
|
|
30
|
-
const config: Config = {
|
|
31
|
-
presets: [neaTailwindPreset],
|
|
32
|
-
content: [
|
|
33
|
-
"./app/**/*.{ts,tsx}",
|
|
34
|
-
"./components/**/*.{ts,tsx}",
|
|
35
|
-
...neaDesignSystemContent,
|
|
36
|
-
],
|
|
37
|
-
};
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Theme tokens are provided through the exported stylesheet. Import `@neasg/design-system/styles.css` once at your app root before rendering package components.
|
|
41
|
-
|
|
42
|
-
If you want to change the package base font size, override `--font-size-root` after importing the stylesheet:
|
|
43
|
-
|
|
44
|
-
```css
|
|
45
|
-
:root {
|
|
46
|
-
--font-size-root: 15px;
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
If you want the package font token to resolve to the actual Inter font in Next.js, load it into `--font-sans`:
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
import { Inter } from "next/font/google";
|
|
54
|
-
|
|
55
|
-
const inter = Inter({
|
|
56
|
-
subsets: ["latin"],
|
|
57
|
-
variable: "--font-sans",
|
|
58
|
-
});
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The package currently includes:
|
|
62
|
-
|
|
63
|
-
- theme tokens for colors, typography, font weights, border radius, shared spacing, and dashboard layout sizing/padding
|
|
64
|
-
- typography primitives for titles, headings, body copy, labels, and captions
|
|
65
|
-
- dashboard layout primitives for sidebar + content
|
|
66
|
-
- page header with title, subtitle, and breadcrumbs
|
|
67
|
-
- draggable tabs for overview/case tab strips
|
|
68
|
-
- prescriptive table and pagination components with built-in sorting, loading skeletons, and table-level pagination wiring
|
|
69
|
-
|
|
70
|
-
## Package Rules
|
|
71
|
-
|
|
72
|
-
- Use `@neasg/design-system` as the source of truth for shared UI.
|
|
73
|
-
- Prefer package components before creating app-local duplicates.
|
|
74
|
-
- If a reusable pattern is missing, extract it into the design system instead of leaving it app-local.
|
|
75
|
-
- This design system is light-theme only. Do not add dark theme tokens, dark variants, or a theme toggle unless product direction changes.
|
|
76
|
-
|
|
77
|
-
## AI Usage
|
|
78
|
-
|
|
79
|
-
### MCP Server (recommended)
|
|
80
|
-
|
|
81
|
-
This design system includes the [Storybook MCP addon](https://storybook.js.org/docs/next/ai/mcp/overview/), which gives AI coding assistants direct access to component documentation, props, stories, and usage examples.
|
|
82
|
-
|
|
83
|
-
**Setup:**
|
|
84
|
-
|
|
85
|
-
1. Start the Storybook dev server in this repo:
|
|
86
|
-
```bash
|
|
87
|
-
npm run storybook
|
|
88
|
-
```
|
|
89
|
-
This starts on `http://localhost:6007` with the MCP endpoint at `/mcp`.
|
|
90
|
-
|
|
91
|
-
2. In your consuming project, add a `.mcp.json` at the project root:
|
|
92
|
-
```json
|
|
93
|
-
{
|
|
94
|
-
"mcpServers": {
|
|
95
|
-
"nea-design-system": {
|
|
96
|
-
"type": "http",
|
|
97
|
-
"url": "http://localhost:6007/mcp"
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
3. Restart Claude Code (or your MCP-compatible AI tool) in the consuming project. The following tools become available:
|
|
104
|
-
- **list-all-documentation** — discover all components and their story IDs
|
|
105
|
-
- **get-documentation** — get full component docs, props, and code examples
|
|
106
|
-
- **get-documentation-for-story** — get details for a specific story variant
|
|
107
|
-
- **preview-stories** — generate preview URLs for stories
|
|
108
|
-
- **get-storybook-story-instructions** — get guidance on writing stories
|
|
109
|
-
|
|
110
|
-
The MCP server requires the Storybook dev server to be running. Start it before opening your AI tool.
|
|
111
|
-
|
|
112
|
-
### Manual instructions
|
|
113
|
-
|
|
114
|
-
If you are not using the MCP server, give your AI coding assistant these instructions explicitly:
|
|
115
|
-
|
|
116
|
-
```text
|
|
117
|
-
Use @neasg/design-system as the source of truth for shared UI.
|
|
118
|
-
|
|
119
|
-
- Prefer components from @neasg/design-system before creating app-local UI.
|
|
120
|
-
- Import package components directly when they already exist instead of building app-local copies.
|
|
121
|
-
- Prefer this import pattern:
|
|
122
|
-
import { Button, Input, PageHeader, Table, Typography } from "@neasg/design-system";
|
|
123
|
-
- Use the package theme tokens and Tailwind preset instead of inventing ad hoc colors, spacing, or radius values.
|
|
124
|
-
- Do not introduce dark theme support. This design system is light-theme only.
|
|
125
|
-
- If a needed shared pattern is missing, extract it into @neasg/design-system rather than building a duplicate local component.
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Common Exports
|
|
129
|
-
|
|
130
|
-
- `@neasg/design-system`
|
|
131
|
-
- `@neasg/design-system/button`
|
|
132
|
-
- `@neasg/design-system/badge`
|
|
133
|
-
- `@neasg/design-system/breadcrumb`
|
|
134
|
-
- `@neasg/design-system/email-input`
|
|
135
|
-
- `@neasg/design-system/input`
|
|
136
|
-
- `@neasg/design-system/otp-input`
|
|
137
|
-
- `@neasg/design-system/phone-input`
|
|
138
|
-
- `@neasg/design-system/textarea`
|
|
139
|
-
- `@neasg/design-system/select`
|
|
140
|
-
- `@neasg/design-system/draggable-tabs`
|
|
141
|
-
- `@neasg/design-system/layout`
|
|
142
|
-
- `@neasg/design-system/page-header`
|
|
143
|
-
- `@neasg/design-system/popover`
|
|
144
|
-
- `@neasg/design-system/pagination`
|
|
145
|
-
- `@neasg/design-system/table`
|
|
146
|
-
- `@neasg/design-system/tailwind-preset`
|
|
147
|
-
- `@neasg/design-system/tabs`
|
|
148
|
-
- `@neasg/design-system/typography`
|
|
149
|
-
- `@neasg/design-system/tooltip`
|
|
150
|
-
- `@neasg/design-system/theme`
|
|
151
|
-
- `@neasg/design-system/styles.css`
|
|
152
|
-
|
|
153
20
|
## Publishing
|
|
154
21
|
|
|
155
22
|
This package is built to `dist/` before publish. The public package entry points are the compiled files in `dist`, not the raw `src` files.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { motion, useAnimation } from "motion/react";
|
|
4
|
-
import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
|
|
4
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, } from "react";
|
|
5
5
|
import { cn } from "../lib/utils";
|
|
6
6
|
const SVG_VARIANTS = {
|
|
7
7
|
normal: { scale: 1 },
|
|
@@ -16,29 +16,41 @@ const SVG_VARIANTS = {
|
|
|
16
16
|
const ShieldIcon = forwardRef(({ onMouseEnter, onMouseLeave, className, size = 24, ...props }, ref) => {
|
|
17
17
|
const controls = useAnimation();
|
|
18
18
|
const isControlledRef = useRef(false);
|
|
19
|
+
const isMountedRef = useRef(false);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
isMountedRef.current = true;
|
|
22
|
+
return () => {
|
|
23
|
+
isMountedRef.current = false;
|
|
24
|
+
};
|
|
25
|
+
}, []);
|
|
26
|
+
const startControls = useCallback((variant) => {
|
|
27
|
+
if (isMountedRef.current) {
|
|
28
|
+
controls.start(variant);
|
|
29
|
+
}
|
|
30
|
+
}, [controls]);
|
|
19
31
|
useImperativeHandle(ref, () => {
|
|
20
32
|
isControlledRef.current = true;
|
|
21
33
|
return {
|
|
22
|
-
startAnimation: () =>
|
|
23
|
-
stopAnimation: () =>
|
|
34
|
+
startAnimation: () => startControls("animate"),
|
|
35
|
+
stopAnimation: () => startControls("normal"),
|
|
24
36
|
};
|
|
25
|
-
});
|
|
37
|
+
}, [startControls]);
|
|
26
38
|
const handleMouseEnter = useCallback((e) => {
|
|
27
39
|
if (isControlledRef.current) {
|
|
28
40
|
onMouseEnter === null || onMouseEnter === void 0 ? void 0 : onMouseEnter(e);
|
|
29
41
|
}
|
|
30
42
|
else {
|
|
31
|
-
|
|
43
|
+
startControls("animate");
|
|
32
44
|
}
|
|
33
|
-
}, [
|
|
45
|
+
}, [onMouseEnter, startControls]);
|
|
34
46
|
const handleMouseLeave = useCallback((e) => {
|
|
35
47
|
if (isControlledRef.current) {
|
|
36
48
|
onMouseLeave === null || onMouseLeave === void 0 ? void 0 : onMouseLeave(e);
|
|
37
49
|
}
|
|
38
50
|
else {
|
|
39
|
-
|
|
51
|
+
startControls("normal");
|
|
40
52
|
}
|
|
41
|
-
}, [
|
|
53
|
+
}, [onMouseLeave, startControls]);
|
|
42
54
|
return (_jsx("div", { className: cn(className), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, ...props, children: _jsx(motion.svg, { animate: controls, fill: "none", height: size, stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", variants: SVG_VARIANTS, viewBox: "0 0 24 24", width: size, xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" }) }) }));
|
|
43
55
|
});
|
|
44
56
|
ShieldIcon.displayName = "ShieldIcon";
|
package/dist/copy-button.d.ts
CHANGED
|
@@ -12,8 +12,8 @@ export interface CopyButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLBut
|
|
|
12
12
|
onError?: (error: unknown) => void;
|
|
13
13
|
/** Accessible label for the icon-only button. Defaults to "Copy". */
|
|
14
14
|
copyLabel?: string;
|
|
15
|
-
/**
|
|
15
|
+
/** Feedback label after copying. Defaults to "Copied". */
|
|
16
16
|
copiedLabel?: string;
|
|
17
17
|
}
|
|
18
|
-
declare function CopyButton({ value, feedbackDuration, children, onCopy, onError, copyLabel, copiedLabel, className, type, ...props }: CopyButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
declare function CopyButton({ value, feedbackDuration, children, onCopy, onError, onClick, copyLabel, copiedLabel, className, type, ...props }: CopyButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
19
19
|
export { CopyButton };
|
package/dist/copy-button.js
CHANGED
|
@@ -4,14 +4,17 @@ import * as React from "react";
|
|
|
4
4
|
import { Copy } from "lucide-react";
|
|
5
5
|
import { CheckIcon } from "./animated-icons/check";
|
|
6
6
|
import { cn } from "./lib/utils";
|
|
7
|
-
function CopyButton({ value, feedbackDuration = 1500, children, onCopy, onError, copyLabel = "Copy", copiedLabel = "Copied", className, type = "button", ...props }) {
|
|
7
|
+
function CopyButton({ value, feedbackDuration = 1500, children, onCopy, onError, onClick, copyLabel = "Copy", copiedLabel = "Copied", className, type = "button", ...props }) {
|
|
8
8
|
const [copied, setCopied] = React.useState(false);
|
|
9
9
|
const timeoutRef = React.useRef(null);
|
|
10
10
|
React.useEffect(() => () => {
|
|
11
11
|
if (timeoutRef.current)
|
|
12
12
|
clearTimeout(timeoutRef.current);
|
|
13
13
|
}, []);
|
|
14
|
-
const handleCopy = React.useCallback(async () => {
|
|
14
|
+
const handleCopy = React.useCallback(async (event) => {
|
|
15
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
16
|
+
if (event.defaultPrevented)
|
|
17
|
+
return;
|
|
15
18
|
try {
|
|
16
19
|
await navigator.clipboard.writeText(value);
|
|
17
20
|
setCopied(true);
|
|
@@ -23,7 +26,7 @@ function CopyButton({ value, feedbackDuration = 1500, children, onCopy, onError,
|
|
|
23
26
|
catch (error) {
|
|
24
27
|
onError === null || onError === void 0 ? void 0 : onError(error);
|
|
25
28
|
}
|
|
26
|
-
}, [value, feedbackDuration, onCopy, onError]);
|
|
29
|
+
}, [value, feedbackDuration, onCopy, onClick, onError]);
|
|
27
30
|
const iconOnly = children === undefined;
|
|
28
31
|
const label = copied ? copiedLabel : copyLabel;
|
|
29
32
|
return (_jsxs(_Fragment, { children: [_jsxs("button", { type: type, onClick: handleCopy, "aria-label": iconOnly ? label : undefined, className: cn("inline-flex cursor-pointer items-center gap-1.5 rounded text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50", !iconOnly && "text-sm font-medium", className), ...props, children: [copied ? (_jsx(CheckIcon, { size: 16, className: "text-green-600" })) : (_jsx(Copy, { className: "h-4 w-4", "aria-hidden": "true" })), iconOnly ? null : _jsx("span", { children: copied ? copiedLabel : children })] }), _jsx("span", { className: "sr-only", "aria-live": "polite", children: copied ? copiedLabel : "" })] }));
|
package/dist/editable-table.js
CHANGED
|
@@ -117,12 +117,12 @@ function EditableTable({ value, onChange, columns, isEditing = false, className,
|
|
|
117
117
|
const cellValue = getNestedValue(row, column.path);
|
|
118
118
|
const isEditingCell = (editingCell === null || editingCell === void 0 ? void 0 : editingCell.rowIndex) === rowIndex &&
|
|
119
119
|
(editingCell === null || editingCell === void 0 ? void 0 : editingCell.colPath) === column.path;
|
|
120
|
-
return (_jsx(TableCell, { className: cn("whitespace-normal", isEditing && "cursor-pointer hover:bg-muted/50"), minWidth: getColumnMinWidth(column), onClick: () => handleCellClick(rowIndex, column.path), children: isEditingCell ? (_jsx(InputControl, { ref: inputRef, value: editValue, onChange: (event) => setEditValue(event.target.value), onBlur: commitEdit, onKeyDown: handleKeyDown, className: "h-control w-full text-sm" })) : (_jsx("span", { className: cn("block whitespace-normal break-words", cellValue === null ||
|
|
120
|
+
return (_jsx(TableCell, { className: cn("whitespace-normal", isEditing && "cursor-pointer hover:bg-muted/50"), width: column.width, minWidth: getColumnMinWidth(column), maxWidth: getColumnMaxWidth(column), onClick: () => handleCellClick(rowIndex, column.path), children: isEditingCell ? (_jsx(InputControl, { ref: inputRef, value: editValue, onChange: (event) => setEditValue(event.target.value), onBlur: commitEdit, onKeyDown: handleKeyDown, className: "h-control w-full text-sm" })) : (_jsx("span", { className: cn("block whitespace-normal break-words", cellValue === null ||
|
|
121
121
|
cellValue === undefined ||
|
|
122
122
|
cellValue === ""
|
|
123
123
|
? "text-muted-foreground"
|
|
124
124
|
: undefined), style: { maxWidth: getColumnMaxWidth(column) }, children: formatCellValue(cellValue) })) }, `cell-${rowIndex}-${columnIndex}`));
|
|
125
|
-
}), hasActionsColumn ? (_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [renderRowActions
|
|
125
|
+
}), hasActionsColumn ? (_jsx(TableCell, { width: renderRowActions ? undefined : "72px", minWidth: renderRowActions ? "120px" : "72px", children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [renderRowActions
|
|
126
126
|
? renderRowActions({
|
|
127
127
|
row,
|
|
128
128
|
rowIndex,
|
package/dist/index.d.ts
CHANGED
|
@@ -78,7 +78,7 @@ export { DraggableTabs, type DraggableTabItem, type DraggableTabsProps, } from "
|
|
|
78
78
|
export { Drawer, } from "./drawer";
|
|
79
79
|
export type { DrawerAction, DrawerProps, DrawerSide } from "./drawer";
|
|
80
80
|
export { DashboardLayout, DashboardSidebar, useSidebar, } from "./layout";
|
|
81
|
-
export type { DashboardLayoutProps, DashboardSidebarNavGroup, DashboardSidebarNavItem, } from "./layout";
|
|
81
|
+
export type { DashboardLayoutProps, DashboardSidebarNavDropdownItem, DashboardSidebarNavGroup, DashboardSidebarNavItem, DashboardSidebarNavLinkItem, } from "./layout";
|
|
82
82
|
export { PageHeader } from "./page-header";
|
|
83
83
|
export { PageSection, PageSectionGroup } from "./page-section";
|
|
84
84
|
export type { PageSectionGroupProps, PageSectionProps } from "./page-section";
|
package/dist/layout.d.ts
CHANGED
|
@@ -33,13 +33,10 @@ export interface DashboardSidebarProps extends React.HTMLAttributes<HTMLElement>
|
|
|
33
33
|
}) => React.ReactNode);
|
|
34
34
|
toggleAriaLabel?: string;
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
interface DashboardSidebarNavItemBase {
|
|
37
37
|
id: string;
|
|
38
38
|
label: string;
|
|
39
|
-
icon
|
|
40
|
-
href?: string;
|
|
41
|
-
target?: string;
|
|
42
|
-
rel?: string;
|
|
39
|
+
icon?: React.ElementType;
|
|
43
40
|
active?: boolean;
|
|
44
41
|
disabled?: boolean;
|
|
45
42
|
wip?: boolean;
|
|
@@ -47,10 +44,30 @@ export interface DashboardSidebarNavItem {
|
|
|
47
44
|
badgeVariant?: CountBadgeProps["variant"];
|
|
48
45
|
badgePulse?: boolean;
|
|
49
46
|
trailing?: React.ReactNode;
|
|
50
|
-
onClick?: () => void;
|
|
51
47
|
onHoverStart?: () => void;
|
|
52
48
|
onHoverEnd?: () => void;
|
|
53
49
|
}
|
|
50
|
+
export interface DashboardSidebarNavLinkItem extends DashboardSidebarNavItemBase {
|
|
51
|
+
href?: string;
|
|
52
|
+
target?: string;
|
|
53
|
+
rel?: string;
|
|
54
|
+
onClick?: () => void;
|
|
55
|
+
items?: never;
|
|
56
|
+
defaultExpanded?: never;
|
|
57
|
+
expanded?: never;
|
|
58
|
+
onExpandedChange?: never;
|
|
59
|
+
}
|
|
60
|
+
export interface DashboardSidebarNavDropdownItem extends DashboardSidebarNavItemBase {
|
|
61
|
+
items: DashboardSidebarNavItem[];
|
|
62
|
+
defaultExpanded?: boolean;
|
|
63
|
+
expanded?: boolean;
|
|
64
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
65
|
+
href?: never;
|
|
66
|
+
target?: never;
|
|
67
|
+
rel?: never;
|
|
68
|
+
onClick?: never;
|
|
69
|
+
}
|
|
70
|
+
export type DashboardSidebarNavItem = DashboardSidebarNavLinkItem | DashboardSidebarNavDropdownItem;
|
|
54
71
|
export interface DashboardSidebarNavGroup {
|
|
55
72
|
label?: string;
|
|
56
73
|
items: DashboardSidebarNavItem[];
|
package/dist/layout.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
|
5
|
+
import { ChevronDownIcon } from "./animated-icons/chevron-down";
|
|
4
6
|
import { ChevronLeftIcon } from "./animated-icons/chevron-left";
|
|
5
7
|
import { ChevronRightIcon } from "./animated-icons/chevron-right";
|
|
6
8
|
import { Badge } from "./badge";
|
|
@@ -80,7 +82,7 @@ function SidebarHeader({ className, children, }) {
|
|
|
80
82
|
return _jsx("div", { className: cn("shrink-0", className), children: children });
|
|
81
83
|
}
|
|
82
84
|
function SidebarContent({ className, children, }) {
|
|
83
|
-
return _jsx("div", { className: cn("flex-1 overflow-y-auto", className), children: children });
|
|
85
|
+
return (_jsx("div", { className: cn("flex-1 overflow-y-auto", className), children: children }));
|
|
84
86
|
}
|
|
85
87
|
function SidebarFooter({ className, children, }) {
|
|
86
88
|
return _jsx("div", { className: cn("shrink-0", className), children: children });
|
|
@@ -97,12 +99,25 @@ function SidebarMenu({ className, children, }) {
|
|
|
97
99
|
function SidebarMenuItem({ className, children, }) {
|
|
98
100
|
return _jsx("li", { className: className, children: children });
|
|
99
101
|
}
|
|
100
|
-
|
|
101
|
-
return (_jsx(Button, { asChild: asChild, variant: "ghost", disabled: disabled, className: cn("h-control w-full justify-start gap-3 rounded-lg text-sm font-medium shadow-none", collapsed ? "justify-center px-3" : "px-3", disabled
|
|
102
|
+
const SidebarMenuButton = React.forwardRef(({ active = false, collapsed = false, disabled = false, className, asChild = false, children, ...props }, ref) => {
|
|
103
|
+
return (_jsx(Button, { ref: ref, asChild: asChild, variant: "ghost", disabled: disabled, className: cn("h-control w-full justify-start gap-3 rounded-lg text-sm font-medium shadow-none", collapsed ? "justify-center px-3" : "px-3", disabled
|
|
102
104
|
? "text-muted-foreground/50 hover:bg-transparent hover:text-muted-foreground/50"
|
|
103
105
|
: active
|
|
104
106
|
? "bg-white text-foreground hover:bg-white hover:text-foreground"
|
|
105
107
|
: "text-muted-foreground hover:bg-white hover:text-foreground", className), ...props, children: children }));
|
|
108
|
+
});
|
|
109
|
+
SidebarMenuButton.displayName = "SidebarMenuButton";
|
|
110
|
+
function isDropdownSidebarItem(item) {
|
|
111
|
+
return Array.isArray(item.items);
|
|
112
|
+
}
|
|
113
|
+
function sidebarItemIsActive(item) {
|
|
114
|
+
if (item.active) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
if (isDropdownSidebarItem(item)) {
|
|
118
|
+
return item.items.some(sidebarItemIsActive);
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
106
121
|
}
|
|
107
122
|
function renderSidebarSlot(slot, isCollapsed) {
|
|
108
123
|
if (typeof slot === "function") {
|
|
@@ -110,10 +125,11 @@ function renderSidebarSlot(slot, isCollapsed) {
|
|
|
110
125
|
}
|
|
111
126
|
return slot !== null && slot !== void 0 ? slot : null;
|
|
112
127
|
}
|
|
113
|
-
function
|
|
128
|
+
function DashboardSidebarItemButton({ item, isCollapsed, active, expanded, depth = 0, onClick, ariaExpanded, }) {
|
|
114
129
|
const Icon = item.icon;
|
|
115
130
|
const iconRef = React.useRef(null);
|
|
116
131
|
const showWipBadge = item.disabled || item.wip;
|
|
132
|
+
const hasDropdown = isDropdownSidebarItem(item);
|
|
117
133
|
const handleMouseEnter = () => {
|
|
118
134
|
var _a, _b, _c;
|
|
119
135
|
if (!item.disabled) {
|
|
@@ -128,14 +144,62 @@ function DashboardSidebarItem({ item, isCollapsed, }) {
|
|
|
128
144
|
(_c = item.onHoverEnd) === null || _c === void 0 ? void 0 : _c.call(item);
|
|
129
145
|
}
|
|
130
146
|
};
|
|
131
|
-
const content = (_jsxs(_Fragment, { children: [_jsxs("div", { className: "relative flex h-5 w-5 shrink-0 items-center justify-center", children: [_jsx(Icon, { ref: iconRef, size: 16 }), isCollapsed && item.badgeCount !== undefined ? (_jsx(CountBadge, { count: item.badgeCount, variant: item.badgeVariant, size: "sm", pulse: item.badgePulse, className: "absolute -right-2 -top-2" })) : null] }), !isCollapsed ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "flex-1 truncate text-left", children: item.label }), showWipBadge ? _jsx(Badge, { variant: "muted", children: "WIP" }) : null, item.trailing, item.badgeCount !== undefined ? (_jsx(CountBadge, { count: item.badgeCount, variant: item.badgeVariant, size: "md", pulse: item.badgePulse })) : null] })) : null] }));
|
|
132
|
-
const
|
|
133
|
-
const
|
|
147
|
+
const content = (_jsxs(_Fragment, { children: [Icon ? (_jsxs("div", { className: "relative flex h-5 w-5 shrink-0 items-center justify-center", children: [_jsx(Icon, { ref: iconRef, size: 16 }), isCollapsed && item.badgeCount !== undefined ? (_jsx(CountBadge, { count: item.badgeCount, variant: item.badgeVariant, size: "sm", pulse: item.badgePulse, className: "absolute -right-2 -top-2" })) : null] })) : (_jsxs("div", { "aria-hidden": "true", className: "relative flex h-5 w-5 shrink-0 items-center justify-center", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-current/35" }), isCollapsed && item.badgeCount !== undefined ? (_jsx(CountBadge, { count: item.badgeCount, variant: item.badgeVariant, size: "sm", pulse: item.badgePulse, className: "absolute -right-2 -top-2" })) : null] })), !isCollapsed ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "flex-1 truncate text-left", children: item.label }), showWipBadge ? _jsx(Badge, { variant: "muted", children: "WIP" }) : null, item.trailing, item.badgeCount !== undefined ? (_jsx(CountBadge, { count: item.badgeCount, variant: item.badgeVariant, size: "md", pulse: item.badgePulse })) : null, hasDropdown ? (_jsx(ChevronDownIcon, { "aria-hidden": "true", className: cn("shrink-0 text-muted-foreground transition-transform duration-200", expanded && "rotate-180 text-foreground"), size: 16 })) : null] })) : null] }));
|
|
148
|
+
const tooltipLabel = `${item.label}${showWipBadge ? " (WIP)" : ""}`;
|
|
149
|
+
const button = (_jsx(SidebarMenuButton, { onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onFocus: handleMouseEnter, onBlur: handleMouseLeave, onClick: onClick, disabled: item.disabled, active: active, collapsed: isCollapsed, "aria-current": active && !hasDropdown ? "page" : undefined, "aria-expanded": ariaExpanded, "aria-label": isCollapsed ? tooltipLabel : undefined, children: content }));
|
|
150
|
+
const interactive = !hasDropdown && item.href && !item.disabled ? (_jsx(SidebarMenuButton, { asChild: true, onClick: item.onClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onFocus: handleMouseEnter, onBlur: handleMouseLeave, active: active, collapsed: isCollapsed, "aria-current": active ? "page" : undefined, "aria-label": isCollapsed ? tooltipLabel : undefined, children: _jsx("a", { href: item.href, target: item.target, rel: item.rel, children: content }) })) : (button);
|
|
134
151
|
if (isCollapsed) {
|
|
135
|
-
return (_jsx(Tooltip, { side: "right", content:
|
|
152
|
+
return (_jsx(Tooltip, { side: "right", content: _jsx("p", { children: tooltipLabel }), children: item.disabled ? (_jsx("span", { className: "block w-full cursor-not-allowed", children: interactive })) : (interactive) }));
|
|
136
153
|
}
|
|
137
154
|
return interactive;
|
|
138
155
|
}
|
|
156
|
+
function DashboardSidebarLeafItem({ item, isCollapsed, depth = 0, }) {
|
|
157
|
+
return (_jsx(DashboardSidebarItemButton, { item: item, isCollapsed: isCollapsed, active: sidebarItemIsActive(item), depth: depth, onClick: item.onClick }));
|
|
158
|
+
}
|
|
159
|
+
function DashboardSidebarDropdownItem({ item, isCollapsed, depth = 0, }) {
|
|
160
|
+
var _a;
|
|
161
|
+
const { toggle } = useSidebar();
|
|
162
|
+
const hasActiveDescendant = item.items.some(sidebarItemIsActive);
|
|
163
|
+
const isControlled = item.expanded !== undefined;
|
|
164
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = React.useState((_a = item.defaultExpanded) !== null && _a !== void 0 ? _a : Boolean(item.active || hasActiveDescendant));
|
|
165
|
+
const expanded = isControlled ? item.expanded : uncontrolledExpanded;
|
|
166
|
+
React.useEffect(() => {
|
|
167
|
+
if (!isControlled && (item.active || hasActiveDescendant)) {
|
|
168
|
+
setUncontrolledExpanded(true);
|
|
169
|
+
}
|
|
170
|
+
}, [hasActiveDescendant, isControlled, item.active]);
|
|
171
|
+
const setExpanded = React.useCallback((next) => {
|
|
172
|
+
var _a;
|
|
173
|
+
if (!isControlled) {
|
|
174
|
+
setUncontrolledExpanded(next);
|
|
175
|
+
}
|
|
176
|
+
(_a = item.onExpandedChange) === null || _a === void 0 ? void 0 : _a.call(item, next);
|
|
177
|
+
}, [isControlled, item]);
|
|
178
|
+
const handleOpenChange = React.useCallback((next) => {
|
|
179
|
+
if (item.disabled) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
setExpanded(next);
|
|
183
|
+
}, [item.disabled, setExpanded]);
|
|
184
|
+
const handleClick = React.useCallback(() => {
|
|
185
|
+
if (item.disabled) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (isCollapsed) {
|
|
189
|
+
setExpanded(true);
|
|
190
|
+
toggle();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
handleOpenChange(!expanded);
|
|
194
|
+
}, [expanded, handleOpenChange, isCollapsed, item.disabled, setExpanded, toggle]);
|
|
195
|
+
return (_jsxs(CollapsiblePrimitive.Root, { open: expanded, onOpenChange: handleOpenChange, children: [_jsx(DashboardSidebarItemButton, { item: item, isCollapsed: isCollapsed, active: item.active || hasActiveDescendant, expanded: expanded, depth: depth, onClick: handleClick, ariaExpanded: isCollapsed ? undefined : expanded }), !isCollapsed ? (_jsx(CollapsiblePrimitive.Content, { className: "overflow-hidden", children: _jsx("div", { className: "ml-5 mt-1 border-l border-border/70 pl-2", children: _jsx(SidebarMenu, { className: "space-y-1", children: item.items.map((child) => (_jsx(SidebarMenuItem, { children: _jsx(DashboardSidebarItem, { item: child, isCollapsed: false, depth: depth + 1 }) }, child.id))) }) }) })) : null] }));
|
|
196
|
+
}
|
|
197
|
+
function DashboardSidebarItem({ item, isCollapsed, depth = 0, }) {
|
|
198
|
+
if (isDropdownSidebarItem(item)) {
|
|
199
|
+
return (_jsx(DashboardSidebarDropdownItem, { item: item, isCollapsed: isCollapsed, depth: depth }));
|
|
200
|
+
}
|
|
201
|
+
return (_jsx(DashboardSidebarLeafItem, { item: item, isCollapsed: isCollapsed, depth: depth }));
|
|
202
|
+
}
|
|
139
203
|
function DashboardSidebar({ className, children, logo, search, navGroups, footerItems, footer, toggleAriaLabel = "Toggle sidebar", ...props }) {
|
|
140
204
|
var _a;
|
|
141
205
|
const { isCollapsed, toggle } = useSidebar();
|
package/dist/section-nav.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface SectionNavItem {
|
|
|
13
13
|
disabled?: boolean;
|
|
14
14
|
trailing?: React.ReactNode;
|
|
15
15
|
onClick?: () => void;
|
|
16
|
+
items?: SectionNavItem[];
|
|
16
17
|
}
|
|
17
18
|
export interface SectionNavProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
|
|
18
19
|
title?: React.ReactNode;
|
package/dist/section-nav.js
CHANGED
|
@@ -1,25 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import * as React from "react";
|
|
4
|
+
import { Check } from "lucide-react";
|
|
5
|
+
import { ChevronRightIcon } from "./animated-icons/chevron-right";
|
|
3
6
|
import { Button } from "./button";
|
|
4
7
|
import { cn } from "./lib/utils";
|
|
5
8
|
import { Link } from "./link";
|
|
9
|
+
import { Popover } from "./popover";
|
|
6
10
|
import { Typography } from "./typography";
|
|
11
|
+
function hasSectionNavChildren(item) {
|
|
12
|
+
return Array.isArray(item.items) && item.items.length > 0;
|
|
13
|
+
}
|
|
14
|
+
function sectionNavItemIsActive(item) {
|
|
15
|
+
if (item.active) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (hasSectionNavChildren(item)) {
|
|
19
|
+
return item.items.some(sectionNavItemIsActive);
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
function renderSectionNavItemContent({ item, active, surface, hasChildren, }) {
|
|
24
|
+
const Icon = item.icon;
|
|
25
|
+
return (_jsxs(_Fragment, { children: [Icon ? _jsx(Icon, { className: "h-4 w-4 shrink-0" }) : null, _jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label }), item.trailing ? _jsx("span", { className: "shrink-0", children: item.trailing }) : null, surface === "menu" && active && !hasChildren ? (_jsx(Check, { "aria-hidden": "true", className: "h-4 w-4 shrink-0" })) : null, hasChildren ? (_jsx(ChevronRightIcon, { "aria-hidden": "true", size: 14, className: "shrink-0 text-muted-foreground" })) : null] }));
|
|
26
|
+
}
|
|
27
|
+
const SectionNavRow = React.forwardRef(({ item, active, surface, hasChildren = false, onSelect, ariaExpanded, className: triggerClassName, onClick: externalOnClick, ...buttonProps }, ref) => {
|
|
28
|
+
const isLink = Boolean(item.href) && !item.disabled && !hasChildren;
|
|
29
|
+
const className = cn(surface === "list"
|
|
30
|
+
? "w-full justify-start gap-3 rounded-lg px-3 text-sm font-medium shadow-none"
|
|
31
|
+
: "h-auto min-h-control w-full justify-start gap-3 rounded-sm px-2 py-2 text-left text-sm font-normal shadow-none whitespace-normal", active && !item.disabled
|
|
32
|
+
? surface === "list"
|
|
33
|
+
? "bg-white hover:bg-white"
|
|
34
|
+
: "bg-accent text-accent-foreground hover:bg-accent hover:text-accent-foreground"
|
|
35
|
+
: surface === "list"
|
|
36
|
+
? "hover:bg-white"
|
|
37
|
+
: "hover:bg-accent hover:text-accent-foreground");
|
|
38
|
+
const content = renderSectionNavItemContent({
|
|
39
|
+
item,
|
|
40
|
+
active,
|
|
41
|
+
surface,
|
|
42
|
+
hasChildren,
|
|
43
|
+
});
|
|
44
|
+
if (isLink) {
|
|
45
|
+
return (_jsx(Button, { ref: ref, asChild: true, variant: "ghost", disabled: item.disabled, className: cn(className, triggerClassName), "aria-current": active ? "page" : undefined, ...buttonProps, children: _jsx(Link, { variant: "inherit", href: item.href, target: item.target, rel: item.rel, onClick: (event) => {
|
|
46
|
+
var _a;
|
|
47
|
+
externalOnClick === null || externalOnClick === void 0 ? void 0 : externalOnClick(event);
|
|
48
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect();
|
|
49
|
+
(_a = item.onClick) === null || _a === void 0 ? void 0 : _a.call(item);
|
|
50
|
+
}, children: content }) }));
|
|
51
|
+
}
|
|
52
|
+
return (_jsx(Button, { ref: ref, type: "button", variant: "ghost", disabled: item.disabled, className: cn(className, triggerClassName), "aria-current": active && !hasChildren ? "page" : undefined, "aria-expanded": hasChildren ? ariaExpanded : undefined, "aria-haspopup": hasChildren ? "menu" : undefined, ...buttonProps, onClick: hasChildren
|
|
53
|
+
? externalOnClick
|
|
54
|
+
: (event) => {
|
|
55
|
+
var _a;
|
|
56
|
+
externalOnClick === null || externalOnClick === void 0 ? void 0 : externalOnClick(event);
|
|
57
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect();
|
|
58
|
+
(_a = item.onClick) === null || _a === void 0 ? void 0 : _a.call(item);
|
|
59
|
+
}, children: content }));
|
|
60
|
+
});
|
|
61
|
+
SectionNavRow.displayName = "SectionNavRow";
|
|
62
|
+
function SectionNavMenu({ items, surface, onItemSelect }) {
|
|
63
|
+
const renderedItems = items.map((item) => {
|
|
64
|
+
const content = hasSectionNavChildren(item) ? (_jsx(SectionNavSubmenuItem, { item: item, surface: surface, onItemSelect: onItemSelect })) : (_jsx(SectionNavRow, { item: item, active: sectionNavItemIsActive(item), surface: surface, onSelect: onItemSelect }));
|
|
65
|
+
if (surface === "list") {
|
|
66
|
+
return _jsx("li", { children: content }, item.id);
|
|
67
|
+
}
|
|
68
|
+
return _jsx("div", { children: content }, item.id);
|
|
69
|
+
});
|
|
70
|
+
if (surface === "list") {
|
|
71
|
+
return _jsx("ul", { className: "space-y-1.5", children: renderedItems });
|
|
72
|
+
}
|
|
73
|
+
return _jsx("div", { className: "space-y-1", children: renderedItems });
|
|
74
|
+
}
|
|
75
|
+
function SectionNavSubmenuItem({ item, surface, onItemSelect, }) {
|
|
76
|
+
const active = sectionNavItemIsActive(item);
|
|
77
|
+
const [open, setOpen] = React.useState(false);
|
|
78
|
+
return (_jsx(Popover, { open: open, onOpenChange: setOpen, surface: "menu", align: "start", side: "right", sideOffset: surface === "list" ? 8 : 6, contentClassName: "min-w-[14rem]", trigger: _jsx(SectionNavRow, { item: item, active: active, surface: surface, hasChildren: true, ariaExpanded: open }), children: _jsx("div", { children: _jsx(SectionNavMenu, { items: item.items, surface: "menu", onItemSelect: () => {
|
|
79
|
+
setOpen(false);
|
|
80
|
+
onItemSelect === null || onItemSelect === void 0 ? void 0 : onItemSelect();
|
|
81
|
+
} }) }) }));
|
|
82
|
+
}
|
|
7
83
|
function SectionNav({ title, items, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }) {
|
|
8
84
|
const titleId = React.useId();
|
|
9
85
|
const resolvedLabelledBy = ariaLabelledBy !== null && ariaLabelledBy !== void 0 ? ariaLabelledBy : (!ariaLabel && title != null && !React.isValidElement(title)
|
|
10
86
|
? titleId
|
|
11
87
|
: undefined);
|
|
12
88
|
const renderedTitle = title == null ? null : React.isValidElement(title) ? (title) : (_jsx(Typography, { id: titleId, as: "h2", variant: "bodySm", className: "font-medium text-muted-foreground", children: title }));
|
|
13
|
-
return (_jsxs("nav", { className: cn("w-full space-y-3 rounded-xl bg-muted/40 p-3", className), "aria-label": ariaLabel, "aria-labelledby": resolvedLabelledBy, ...props, children: [renderedTitle, _jsx(
|
|
14
|
-
const Icon = item.icon;
|
|
15
|
-
const itemClasses = cn("w-full justify-start", item.active &&
|
|
16
|
-
!item.disabled &&
|
|
17
|
-
"bg-white hover:bg-white");
|
|
18
|
-
const content = (_jsxs(_Fragment, { children: [Icon ? _jsx(Icon, {}) : null, _jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label }), item.trailing ? (_jsx("span", { className: "shrink-0", children: item.trailing })) : null] }));
|
|
19
|
-
const isLink = Boolean(item.href) && !item.disabled;
|
|
20
|
-
return (_jsx("li", { children: _jsx(Button, { asChild: isLink, variant: "ghost", disabled: item.disabled, className: itemClasses, "aria-current": item.active ? "page" : undefined, ...(isLink
|
|
21
|
-
? {}
|
|
22
|
-
: { type: "button", onClick: item.onClick }), children: isLink ? (_jsx(Link, { variant: "inherit", href: item.href, target: item.target, rel: item.rel, onClick: item.onClick, children: content })) : (content) }) }, item.id));
|
|
23
|
-
}) })] }));
|
|
89
|
+
return (_jsxs("nav", { className: cn("w-full space-y-3 rounded-xl bg-muted/40 p-3", className), "aria-label": ariaLabel, "aria-labelledby": resolvedLabelledBy, ...props, children: [renderedTitle, _jsx(SectionNavMenu, { items: items, surface: "list" })] }));
|
|
24
90
|
}
|
|
25
91
|
export { SectionNav };
|
package/dist/select.js
CHANGED
|
@@ -62,6 +62,13 @@ function Select({ label, options, placeholder = "Select an option", description,
|
|
|
62
62
|
: options;
|
|
63
63
|
const optionGroups = groupOptions(filteredOptions);
|
|
64
64
|
const hasVisibleOptions = filteredOptions.length > 0;
|
|
65
|
+
const focusSearchInput = React.useCallback(() => {
|
|
66
|
+
const input = searchInputRef.current;
|
|
67
|
+
if (!input || document.activeElement === input) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
input.focus({ preventScroll: true });
|
|
71
|
+
}, []);
|
|
65
72
|
React.useEffect(() => {
|
|
66
73
|
if (!isOpen) {
|
|
67
74
|
setSearchQuery("");
|
|
@@ -71,13 +78,29 @@ function Select({ label, options, placeholder = "Select an option", description,
|
|
|
71
78
|
return;
|
|
72
79
|
}
|
|
73
80
|
const animationFrame = window.requestAnimationFrame(() => {
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
focusSearchInput();
|
|
82
|
+
});
|
|
83
|
+
return () => {
|
|
84
|
+
window.cancelAnimationFrame(animationFrame);
|
|
85
|
+
};
|
|
86
|
+
}, [focusSearchInput, isOpen, searchable]);
|
|
87
|
+
React.useEffect(() => {
|
|
88
|
+
if (!isOpen || !searchable || !searchQuery) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const animationFrame = window.requestAnimationFrame(() => {
|
|
92
|
+
focusSearchInput();
|
|
76
93
|
});
|
|
77
94
|
return () => {
|
|
78
95
|
window.cancelAnimationFrame(animationFrame);
|
|
79
96
|
};
|
|
80
|
-
}, [
|
|
97
|
+
}, [
|
|
98
|
+
filteredOptions.length,
|
|
99
|
+
focusSearchInput,
|
|
100
|
+
isOpen,
|
|
101
|
+
searchable,
|
|
102
|
+
searchQuery,
|
|
103
|
+
]);
|
|
81
104
|
const handleOpenChange = React.useCallback((nextOpen) => {
|
|
82
105
|
if (open === undefined) {
|
|
83
106
|
setUncontrolledOpen(nextOpen);
|
package/dist/table.d.ts
CHANGED
|
@@ -100,6 +100,8 @@ export interface TableProps<Row> extends Omit<React.HTMLAttributes<HTMLDivElemen
|
|
|
100
100
|
containerClassName?: string;
|
|
101
101
|
tableClassName?: string;
|
|
102
102
|
rowClassName?: string | ((row: Row, index: number) => string | undefined);
|
|
103
|
+
/** Constrains the table container height so the table body scrolls instead of the surrounding surface. */
|
|
104
|
+
maxHeight?: React.CSSProperties["maxHeight"];
|
|
103
105
|
sorting?: TableSortingProps;
|
|
104
106
|
pagination?: Omit<PaginationProps, "className">;
|
|
105
107
|
loading?: boolean;
|
|
@@ -109,5 +111,5 @@ export interface TableProps<Row> extends Omit<React.HTMLAttributes<HTMLDivElemen
|
|
|
109
111
|
/** When set, rows become interactive: cursor-pointer, hover highlight, and click handling. */
|
|
110
112
|
onRowClick?: (row: Row, index: number) => void;
|
|
111
113
|
}
|
|
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;
|
|
114
|
+
declare function Table<Row>({ columns, rows, getRowKey, emptyState, toolbar, caption, className, containerClassName, tableClassName, rowClassName, maxHeight, sorting, pagination, loading, loadingRows, striped, onRowClick, style, ...props }: TableProps<Row>): import("react/jsx-runtime").JSX.Element;
|
|
113
115
|
export { Table, TableRoot, TableContainer, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, SortableTableHead, TableRowSkeleton, };
|
package/dist/table.js
CHANGED
|
@@ -9,7 +9,7 @@ import { Skeleton } from "./skeleton";
|
|
|
9
9
|
import { cn } from "./lib/utils";
|
|
10
10
|
const TableRoot = React.forwardRef(({ className, wrapperClassName: _wrapperClassName, ...props }, ref) => (_jsx("table", { ref: ref, className: cn("w-full caption-bottom text-sm", className), ...props })));
|
|
11
11
|
TableRoot.displayName = "TableRoot";
|
|
12
|
-
const TableContainer = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("overflow-
|
|
12
|
+
const TableContainer = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("min-h-0 overflow-auto rounded-lg border bg-card", className), ...props })));
|
|
13
13
|
TableContainer.displayName = "TableContainer";
|
|
14
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 })));
|
|
15
15
|
TableHeader.displayName = "TableHeader";
|
|
@@ -17,7 +17,7 @@ const TableBody = React.forwardRef(({ className, ...props }, ref) => (_jsx("tbod
|
|
|
17
17
|
TableBody.displayName = "TableBody";
|
|
18
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 })));
|
|
19
19
|
TableFooter.displayName = "TableFooter";
|
|
20
|
-
const TableRow = React.forwardRef(({ className, ...props }, ref) => (_jsx("tr", { ref: ref, className: cn("border-b bg-card transition-colors
|
|
20
|
+
const TableRow = React.forwardRef(({ className, ...props }, ref) => (_jsx("tr", { ref: ref, className: cn("border-b bg-card transition-colors data-[state=selected]:bg-muted", className), ...props })));
|
|
21
21
|
TableRow.displayName = "TableRow";
|
|
22
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: {
|
|
23
23
|
...style,
|
|
@@ -115,18 +115,21 @@ function getDefaultSkeleton(column) {
|
|
|
115
115
|
height: "1rem",
|
|
116
116
|
};
|
|
117
117
|
}
|
|
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 }) {
|
|
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, maxHeight, sorting, pagination, loading = false, loadingRows = 5, striped = false, onRowClick, style, ...props }) {
|
|
119
119
|
// Use auto layout so the browser can grow a column to contain its (nowrap)
|
|
120
120
|
// header. Caller can opt back into fixed layout via `tableClassName="table-fixed"`.
|
|
121
121
|
const shouldUseFixedLayout = false;
|
|
122
|
-
const container = (_jsx(TableContainer, { className: cn("w-full", loading && "pointer-events-none select-none", className, containerClassName),
|
|
122
|
+
const container = (_jsx(TableContainer, { className: cn("w-full", loading && "pointer-events-none select-none", className, containerClassName), style: {
|
|
123
|
+
...style,
|
|
124
|
+
...(maxHeight !== undefined ? { maxHeight } : {}),
|
|
125
|
+
}, ...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) => {
|
|
123
126
|
var _a;
|
|
124
127
|
const sortField = (_a = column.sortKey) !== null && _a !== void 0 ? _a : column.key;
|
|
125
128
|
const isSortable = column.sortable && sorting;
|
|
126
129
|
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"
|
|
130
|
+
}) }) }), _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: cn(onRowClick && "cursor-pointer hover:bg-muted", typeof rowClassName === "function"
|
|
128
131
|
? rowClassName(row, rowIndex)
|
|
129
|
-
: rowClassName, children: columns.map((column) => {
|
|
132
|
+
: rowClassName), children: columns.map((column) => {
|
|
130
133
|
var _a, _b;
|
|
131
134
|
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
|
|
132
135
|
? row[column.accessorKey]
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neasg/design-system",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "NEA shared design system primitives, theme tokens, and Storybook docs.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22 <23"
|
|
9
|
+
},
|
|
7
10
|
"main": "./dist/index.js",
|
|
8
11
|
"module": "./dist/index.js",
|
|
9
12
|
"types": "./dist/index.d.ts",
|
|
@@ -82,166 +85,7 @@
|
|
|
82
85
|
"types": "./dist/index.d.ts",
|
|
83
86
|
"import": "./dist/index.js"
|
|
84
87
|
},
|
|
85
|
-
"./avatar": {
|
|
86
|
-
"types": "./dist/avatar.d.ts",
|
|
87
|
-
"import": "./dist/avatar.js"
|
|
88
|
-
},
|
|
89
|
-
"./animated-icons": {
|
|
90
|
-
"types": "./dist/animated-icons/index.d.ts",
|
|
91
|
-
"import": "./dist/animated-icons/index.js"
|
|
92
|
-
},
|
|
93
|
-
"./badge": {
|
|
94
|
-
"types": "./dist/badge.d.ts",
|
|
95
|
-
"import": "./dist/badge.js"
|
|
96
|
-
},
|
|
97
|
-
"./back-button": {
|
|
98
|
-
"types": "./dist/back-button.d.ts",
|
|
99
|
-
"import": "./dist/back-button.js"
|
|
100
|
-
},
|
|
101
|
-
"./button": {
|
|
102
|
-
"types": "./dist/button.d.ts",
|
|
103
|
-
"import": "./dist/button.js"
|
|
104
|
-
},
|
|
105
|
-
"./dialog": {
|
|
106
|
-
"types": "./dist/dialog.d.ts",
|
|
107
|
-
"import": "./dist/dialog.js"
|
|
108
|
-
},
|
|
109
|
-
"./command": {
|
|
110
|
-
"types": "./dist/command.d.ts",
|
|
111
|
-
"import": "./dist/command.js"
|
|
112
|
-
},
|
|
113
|
-
"./count-badge": {
|
|
114
|
-
"types": "./dist/count-badge.d.ts",
|
|
115
|
-
"import": "./dist/count-badge.js"
|
|
116
|
-
},
|
|
117
|
-
"./command-search": {
|
|
118
|
-
"types": "./dist/command-search.d.ts",
|
|
119
|
-
"import": "./dist/command-search.js"
|
|
120
|
-
},
|
|
121
|
-
"./empty-state": {
|
|
122
|
-
"types": "./dist/empty-state.d.ts",
|
|
123
|
-
"import": "./dist/empty-state.js"
|
|
124
|
-
},
|
|
125
|
-
"./editable-table": {
|
|
126
|
-
"types": "./dist/editable-table.d.ts",
|
|
127
|
-
"import": "./dist/editable-table.js"
|
|
128
|
-
},
|
|
129
|
-
"./email-input": {
|
|
130
|
-
"types": "./dist/email-input.d.ts",
|
|
131
|
-
"import": "./dist/email-input.js"
|
|
132
|
-
},
|
|
133
|
-
"./input": {
|
|
134
|
-
"types": "./dist/input.d.ts",
|
|
135
|
-
"import": "./dist/input.js"
|
|
136
|
-
},
|
|
137
|
-
"./phone-input": {
|
|
138
|
-
"types": "./dist/phone-input.d.ts",
|
|
139
|
-
"import": "./dist/phone-input.js"
|
|
140
|
-
},
|
|
141
|
-
"./otp-input": {
|
|
142
|
-
"types": "./dist/otp-input.d.ts",
|
|
143
|
-
"import": "./dist/otp-input.js"
|
|
144
|
-
},
|
|
145
|
-
"./select": {
|
|
146
|
-
"types": "./dist/select.d.ts",
|
|
147
|
-
"import": "./dist/select.js"
|
|
148
|
-
},
|
|
149
|
-
"./label-value": {
|
|
150
|
-
"types": "./dist/label-value.d.ts",
|
|
151
|
-
"import": "./dist/label-value.js"
|
|
152
|
-
},
|
|
153
|
-
"./label-value-grid": {
|
|
154
|
-
"types": "./dist/label-value-grid.d.ts",
|
|
155
|
-
"import": "./dist/label-value-grid.js"
|
|
156
|
-
},
|
|
157
|
-
"./breadcrumb": {
|
|
158
|
-
"types": "./dist/breadcrumb.d.ts",
|
|
159
|
-
"import": "./dist/breadcrumb.js"
|
|
160
|
-
},
|
|
161
|
-
"./draggable-tabs": {
|
|
162
|
-
"types": "./dist/draggable-tabs.d.ts",
|
|
163
|
-
"import": "./dist/draggable-tabs.js"
|
|
164
|
-
},
|
|
165
|
-
"./drawer": {
|
|
166
|
-
"types": "./dist/drawer.d.ts",
|
|
167
|
-
"import": "./dist/drawer.js"
|
|
168
|
-
},
|
|
169
|
-
"./right-drawer": {
|
|
170
|
-
"types": "./dist/right-drawer.d.ts",
|
|
171
|
-
"import": "./dist/right-drawer.js"
|
|
172
|
-
},
|
|
173
|
-
"./layout": {
|
|
174
|
-
"types": "./dist/layout.d.ts",
|
|
175
|
-
"import": "./dist/layout.js"
|
|
176
|
-
},
|
|
177
|
-
"./page-header": {
|
|
178
|
-
"types": "./dist/page-header.d.ts",
|
|
179
|
-
"import": "./dist/page-header.js"
|
|
180
|
-
},
|
|
181
|
-
"./page-section": {
|
|
182
|
-
"types": "./dist/page-section.d.ts",
|
|
183
|
-
"import": "./dist/page-section.js"
|
|
184
|
-
},
|
|
185
|
-
"./popover": {
|
|
186
|
-
"types": "./dist/popover.d.ts",
|
|
187
|
-
"import": "./dist/popover.js"
|
|
188
|
-
},
|
|
189
|
-
"./pagination": {
|
|
190
|
-
"types": "./dist/pagination.d.ts",
|
|
191
|
-
"import": "./dist/pagination.js"
|
|
192
|
-
},
|
|
193
|
-
"./search-input": {
|
|
194
|
-
"types": "./dist/search-input.d.ts",
|
|
195
|
-
"import": "./dist/search-input.js"
|
|
196
|
-
},
|
|
197
|
-
"./section-nav": {
|
|
198
|
-
"types": "./dist/section-nav.d.ts",
|
|
199
|
-
"import": "./dist/section-nav.js"
|
|
200
|
-
},
|
|
201
88
|
"./styles.css": "./dist/styles.css",
|
|
202
|
-
"./
|
|
203
|
-
"types": "./dist/table.d.ts",
|
|
204
|
-
"import": "./dist/table.js"
|
|
205
|
-
},
|
|
206
|
-
"./table-toolbar": {
|
|
207
|
-
"types": "./dist/table-toolbar.d.ts",
|
|
208
|
-
"import": "./dist/table-toolbar.js"
|
|
209
|
-
},
|
|
210
|
-
"./tailwind-preset": {
|
|
211
|
-
"types": "./dist/tailwind-preset.d.ts",
|
|
212
|
-
"import": "./dist/tailwind-preset.js"
|
|
213
|
-
},
|
|
214
|
-
"./tabs": {
|
|
215
|
-
"types": "./dist/tabs.d.ts",
|
|
216
|
-
"import": "./dist/tabs.js"
|
|
217
|
-
},
|
|
218
|
-
"./textarea": {
|
|
219
|
-
"types": "./dist/textarea.d.ts",
|
|
220
|
-
"import": "./dist/textarea.js"
|
|
221
|
-
},
|
|
222
|
-
"./typography": {
|
|
223
|
-
"types": "./dist/typography.d.ts",
|
|
224
|
-
"import": "./dist/typography.js"
|
|
225
|
-
},
|
|
226
|
-
"./theme": {
|
|
227
|
-
"types": "./dist/theme.d.ts",
|
|
228
|
-
"import": "./dist/theme.js"
|
|
229
|
-
},
|
|
230
|
-
"./tooltip": {
|
|
231
|
-
"types": "./dist/tooltip.d.ts",
|
|
232
|
-
"import": "./dist/tooltip.js"
|
|
233
|
-
},
|
|
234
|
-
"./separator": {
|
|
235
|
-
"types": "./dist/separator.d.ts",
|
|
236
|
-
"import": "./dist/separator.js"
|
|
237
|
-
},
|
|
238
|
-
"./collapsible-section": {
|
|
239
|
-
"types": "./dist/collapsible-section.d.ts",
|
|
240
|
-
"import": "./dist/collapsible-section.js"
|
|
241
|
-
},
|
|
242
|
-
"./lib/utils": {
|
|
243
|
-
"types": "./dist/lib/utils.d.ts",
|
|
244
|
-
"import": "./dist/lib/utils.js"
|
|
245
|
-
}
|
|
89
|
+
"./package.json": "./package.json"
|
|
246
90
|
}
|
|
247
91
|
}
|