@scripso-homepad/ui 0.4.2 → 0.4.4
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/dist/{chunk-WDSMJWKC.js → chunk-66WR57JJ.js} +35 -3
- package/dist/chunk-66WR57JJ.js.map +1 -0
- package/dist/index.cjs +2258 -384
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +134 -1
- package/dist/index.d.ts +134 -1
- package/dist/index.js +1959 -97
- package/dist/index.js.map +1 -1
- package/dist/web/index.cjs +30650 -1009
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.css +78 -0
- package/dist/web/index.css.map +1 -0
- package/dist/web/index.d.cts +267 -6
- package/dist/web/index.d.ts +267 -6
- package/dist/web/index.js +30179 -601
- package/dist/web/index.js.map +1 -1
- package/package.json +5 -4
- package/src/components/Calendar.stories.tsx +337 -0
- package/src/components/Calendar.tsx +441 -0
- package/src/components/DatePicker.stories.tsx +219 -0
- package/src/components/DatePicker.tsx +366 -0
- package/src/components/Select.stories.tsx +134 -0
- package/src/components/Select.tsx +465 -0
- package/src/css.d.ts +1 -0
- package/src/icons/CalendarIcon.tsx +28 -0
- package/src/icons/CalendarIcon.web.tsx +34 -0
- package/src/icons/ChevronDownIcon.tsx +11 -4
- package/src/icons/ChevronDownIcon.web.tsx +6 -4
- package/src/icons/ChevronNavIcons.tsx +44 -0
- package/src/icons/ChevronNavIcons.web.tsx +57 -0
- package/src/icons/calendarIconPaths.ts +2 -0
- package/src/icons/chevronDownPath.ts +1 -0
- package/src/icons/chevronNavPaths.ts +2 -0
- package/src/index.ts +41 -0
- package/src/theme/calendar.ts +357 -0
- package/src/theme/select.ts +247 -0
- package/src/utils/calendarDays.ts +273 -0
- package/src/utils/calendarLocaleContext.tsx +95 -0
- package/src/utils/calendarLocalization.ts +187 -0
- package/src/utils/countryDropdownScrollStyles.ts +53 -13
- package/src/utils/formatMultiSelectDisplay.ts +56 -0
- package/src/web/components/Badge.stories.tsx +43 -0
- package/src/web/components/Badge.tsx +35 -0
- package/src/web/components/Pagination.stories.tsx +78 -0
- package/src/web/components/Pagination.tsx +153 -0
- package/src/web/components/StatusBadge.tsx +20 -0
- package/src/web/components/Table.stories.tsx +415 -0
- package/src/web/components/Table.tsx +3 -0
- package/src/web/components/TableActionButton.tsx +67 -0
- package/src/web/components/TableActionsMenu.tsx +208 -0
- package/src/web/components/TableIconText.tsx +23 -0
- package/src/web/components/TableRowStatusActions.tsx +33 -0
- package/src/web/components/TableStatusActionsCell.tsx +29 -0
- package/src/web/components/pagination-utils.ts +39 -0
- package/src/web/components/table/DataTable.tsx +123 -0
- package/src/web/components/table/TablePrimitives.tsx +214 -0
- package/src/web/components/table/TableScrollArea.tsx +51 -0
- package/src/web/components/table/constants.ts +59 -0
- package/src/web/components/table/data-table-class-names.ts +26 -0
- package/src/web/components/table/index.ts +44 -0
- package/src/web/components/table/table-row-context.tsx +19 -0
- package/src/web/components/table/use-horizontal-scroll-state.ts +72 -0
- package/src/web/components/table-scroll.css +81 -0
- package/src/web/hooks/useOnClickOutside.ts +20 -5
- package/src/web/icons/TableMenuActivateIcon.tsx +27 -0
- package/src/web/icons/TableMenuCheckIcon.tsx +27 -0
- package/src/web/icons/TableMenuCloseIcon.tsx +27 -0
- package/src/web/icons/TableMenuEditIcon.tsx +27 -0
- package/src/web/icons/TableMenuInfoIcon.tsx +27 -0
- package/src/web/icons/TableMenuRefreshIcon.tsx +27 -0
- package/src/web/icons/TableMenuSuspendIcon.tsx +34 -0
- package/src/web/icons/TableMenuTrashIcon.tsx +27 -0
- package/src/web/icons/TableMoreIcon.tsx +41 -0
- package/src/web/icons/TableSortIcon.tsx +37 -0
- package/src/web/index.ts +74 -0
- package/src/web/layout/AppHeader.stories.tsx +2 -0
- package/src/web/layout/AppHeader.tsx +18 -14
- package/src/web/layout/DashboardLayout.stories.tsx +1 -0
- package/src/web/layout/DashboardLayout.tsx +4 -4
- package/src/web/layout/story-helpers.tsx +10 -2
- package/dist/chunk-WDSMJWKC.js.map +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../utils/cn';
|
|
4
|
+
|
|
5
|
+
export type TableIconTextProps = {
|
|
6
|
+
icon: ReactNode;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function TableIconText({ icon, children, className }: TableIconTextProps) {
|
|
12
|
+
return (
|
|
13
|
+
<span
|
|
14
|
+
className={cn(
|
|
15
|
+
'inline-flex items-center gap-2 font-sans text-sm font-medium leading-none tracking-normal text-storm-gray-900',
|
|
16
|
+
className,
|
|
17
|
+
)}
|
|
18
|
+
>
|
|
19
|
+
<span className="text-storm-gray-500">{icon}</span>
|
|
20
|
+
{children}
|
|
21
|
+
</span>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { TableActionsCell, TableCell } from './table/TablePrimitives';
|
|
4
|
+
import { TableActionsMenu, type TableActionsMenuItem } from './TableActionsMenu';
|
|
5
|
+
|
|
6
|
+
export type TableRowActionsConfig = {
|
|
7
|
+
status: ReactNode;
|
|
8
|
+
menuItems?: TableActionsMenuItem[];
|
|
9
|
+
inlineActions?: ReactNode;
|
|
10
|
+
menuAriaLabel?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type TableRowStatusActionsProps = TableRowActionsConfig;
|
|
14
|
+
|
|
15
|
+
export function TableRowStatusActions({
|
|
16
|
+
status,
|
|
17
|
+
menuItems,
|
|
18
|
+
inlineActions,
|
|
19
|
+
menuAriaLabel = 'Actions',
|
|
20
|
+
}: TableRowStatusActionsProps) {
|
|
21
|
+
const actions =
|
|
22
|
+
inlineActions ??
|
|
23
|
+
(menuItems && menuItems.length > 0 ? (
|
|
24
|
+
<TableActionsMenu items={menuItems} triggerAriaLabel={menuAriaLabel} />
|
|
25
|
+
) : null);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<TableCell>{status}</TableCell>
|
|
30
|
+
<TableActionsCell>{actions}</TableActionsCell>
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ReactNode, TdHTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../utils/cn';
|
|
4
|
+
import { tableBodyClassName, tableCellPaddingClassName } from './table/constants';
|
|
5
|
+
|
|
6
|
+
export type TableStatusActionsCellProps = TdHTMLAttributes<HTMLTableCellElement> & {
|
|
7
|
+
status: ReactNode;
|
|
8
|
+
actions?: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** @deprecated Prefer `TableCell` + `TableActionsCell` via `TableRowStatusActions`. */
|
|
12
|
+
export function TableStatusActionsCell({
|
|
13
|
+
status,
|
|
14
|
+
actions,
|
|
15
|
+
className,
|
|
16
|
+
...props
|
|
17
|
+
}: TableStatusActionsCellProps) {
|
|
18
|
+
return (
|
|
19
|
+
<td
|
|
20
|
+
className={cn(tableCellPaddingClassName, tableBodyClassName, className)}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
23
|
+
<div className="flex items-center justify-between gap-3">
|
|
24
|
+
<div className="min-w-0">{status}</div>
|
|
25
|
+
{actions ? <div className="flex shrink-0 items-center gap-2.5">{actions}</div> : null}
|
|
26
|
+
</div>
|
|
27
|
+
</td>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function getTotalPages(total: number, pageSize: number): number {
|
|
2
|
+
if (pageSize <= 0) {
|
|
3
|
+
return 1;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
return Math.max(1, Math.ceil(total / pageSize));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getPaginationRange(page: number, pageSize: number, total: number) {
|
|
10
|
+
if (total <= 0) {
|
|
11
|
+
return { start: 0, end: 0 };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const start = (page - 1) * pageSize + 1;
|
|
15
|
+
const end = Math.min(page * pageSize, total);
|
|
16
|
+
|
|
17
|
+
return { start, end };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getVisiblePageNumbers(
|
|
21
|
+
currentPage: number,
|
|
22
|
+
totalPages: number,
|
|
23
|
+
maxButtons = 3,
|
|
24
|
+
): number[] {
|
|
25
|
+
if (totalPages <= maxButtons) {
|
|
26
|
+
return Array.from({ length: totalPages }, (_, index) => index + 1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const half = Math.floor(maxButtons / 2);
|
|
30
|
+
let start = Math.max(1, currentPage - half);
|
|
31
|
+
let end = start + maxButtons - 1;
|
|
32
|
+
|
|
33
|
+
if (end > totalPages) {
|
|
34
|
+
end = totalPages;
|
|
35
|
+
start = Math.max(1, end - maxButtons + 1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return Array.from({ length: end - start + 1 }, (_, index) => start + index);
|
|
39
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useMemo, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
import {
|
|
5
|
+
tableBorderClassName,
|
|
6
|
+
tableFooterShellClassName,
|
|
7
|
+
tableShellClassName,
|
|
8
|
+
DEFAULT_TABLE_BODY_MIN_HEIGHT,
|
|
9
|
+
} from './constants';
|
|
10
|
+
import {
|
|
11
|
+
DataTableClassNamesContext,
|
|
12
|
+
type DataTableClassNames,
|
|
13
|
+
} from './data-table-class-names';
|
|
14
|
+
import { TableScrollArea } from './TableScrollArea';
|
|
15
|
+
|
|
16
|
+
export type { DataTableClassNames };
|
|
17
|
+
|
|
18
|
+
export type DataTableProps = {
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
footer?: ReactNode;
|
|
21
|
+
minWidth?: number | string;
|
|
22
|
+
/** Outer flex container — override layout spacing if needed. */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** Table shell — default `rounded-xl` alone, or `rounded-t-xl` when a footer is present. */
|
|
25
|
+
tableClassName?: string;
|
|
26
|
+
/** Footer shell — default `rounded-b-xl` when a footer is present. */
|
|
27
|
+
footerClassName?: string;
|
|
28
|
+
scrollClassName?: string;
|
|
29
|
+
/** Class names for individual table parts. Merged with the matching `*ClassName` props above. */
|
|
30
|
+
classNames?: DataTableClassNames;
|
|
31
|
+
/** Minimum height of the scrollable table body. Default: 320px. */
|
|
32
|
+
minBodyHeight?: number | string;
|
|
33
|
+
/** Fixed height of the scrollable table body (scrolls when content overflows). */
|
|
34
|
+
bodyHeight?: number | string;
|
|
35
|
+
/** Grow the table region to fill a flex parent (`flex-1 min-h-0`). */
|
|
36
|
+
fillHeight?: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function mergeDataTableClassNames({
|
|
40
|
+
className,
|
|
41
|
+
tableClassName,
|
|
42
|
+
footerClassName,
|
|
43
|
+
scrollClassName,
|
|
44
|
+
classNames,
|
|
45
|
+
}: Pick<
|
|
46
|
+
DataTableProps,
|
|
47
|
+
'className' | 'tableClassName' | 'footerClassName' | 'scrollClassName' | 'classNames'
|
|
48
|
+
>): DataTableClassNames {
|
|
49
|
+
return {
|
|
50
|
+
root: cn(classNames?.root, className),
|
|
51
|
+
shell: cn(classNames?.shell, tableClassName),
|
|
52
|
+
scroll: cn(classNames?.scroll, scrollClassName),
|
|
53
|
+
footer: cn(classNames?.footer, footerClassName),
|
|
54
|
+
table: classNames?.table,
|
|
55
|
+
header: classNames?.header,
|
|
56
|
+
body: classNames?.body,
|
|
57
|
+
pagination: classNames?.pagination,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function DataTable({
|
|
62
|
+
children,
|
|
63
|
+
footer,
|
|
64
|
+
minWidth,
|
|
65
|
+
className,
|
|
66
|
+
tableClassName,
|
|
67
|
+
footerClassName,
|
|
68
|
+
scrollClassName,
|
|
69
|
+
classNames,
|
|
70
|
+
minBodyHeight = DEFAULT_TABLE_BODY_MIN_HEIGHT,
|
|
71
|
+
bodyHeight,
|
|
72
|
+
fillHeight = false,
|
|
73
|
+
}: DataTableProps) {
|
|
74
|
+
const hasFooter = footer != null;
|
|
75
|
+
const mergedClassNames = useMemo(
|
|
76
|
+
() =>
|
|
77
|
+
mergeDataTableClassNames({
|
|
78
|
+
className,
|
|
79
|
+
tableClassName,
|
|
80
|
+
footerClassName,
|
|
81
|
+
scrollClassName,
|
|
82
|
+
classNames,
|
|
83
|
+
}),
|
|
84
|
+
[className, classNames, footerClassName, scrollClassName, tableClassName],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<DataTableClassNamesContext.Provider value={mergedClassNames}>
|
|
89
|
+
<div className={cn('flex flex-col', fillHeight && 'min-h-0 flex-1', mergedClassNames.root)}>
|
|
90
|
+
<div
|
|
91
|
+
className={cn(
|
|
92
|
+
tableShellClassName,
|
|
93
|
+
tableBorderClassName,
|
|
94
|
+
fillHeight && 'min-h-0 flex-1',
|
|
95
|
+
hasFooter ? 'rounded-t-xl border-b-0' : 'rounded-xl',
|
|
96
|
+
mergedClassNames.shell,
|
|
97
|
+
)}
|
|
98
|
+
>
|
|
99
|
+
<TableScrollArea
|
|
100
|
+
minWidth={minWidth}
|
|
101
|
+
minHeight={bodyHeight === undefined ? minBodyHeight : undefined}
|
|
102
|
+
height={bodyHeight}
|
|
103
|
+
fillHeight={fillHeight}
|
|
104
|
+
className={mergedClassNames.scroll}
|
|
105
|
+
>
|
|
106
|
+
{children}
|
|
107
|
+
</TableScrollArea>
|
|
108
|
+
</div>
|
|
109
|
+
{hasFooter ? (
|
|
110
|
+
<div
|
|
111
|
+
className={cn(
|
|
112
|
+
tableFooterShellClassName,
|
|
113
|
+
tableBorderClassName,
|
|
114
|
+
mergedClassNames.footer,
|
|
115
|
+
)}
|
|
116
|
+
>
|
|
117
|
+
{footer}
|
|
118
|
+
</div>
|
|
119
|
+
) : null}
|
|
120
|
+
</div>
|
|
121
|
+
</DataTableClassNamesContext.Provider>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode, TdHTMLAttributes, ThHTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
import { TableSortIcon, type TableSortDirection } from '../../icons/TableSortIcon';
|
|
4
|
+
import { cn } from '../../utils/cn';
|
|
5
|
+
import { useDataTableClassNames } from './data-table-class-names';
|
|
6
|
+
import {
|
|
7
|
+
tableAlignClasses,
|
|
8
|
+
tableBodyClassName,
|
|
9
|
+
tableBorderClassName,
|
|
10
|
+
tableCellPaddingClassName,
|
|
11
|
+
tableCellVariantClasses,
|
|
12
|
+
tableHeadPaddingClassName,
|
|
13
|
+
tableHeadSortButtonAlignClasses,
|
|
14
|
+
tableHeaderClassName,
|
|
15
|
+
tableRowDisabledCellClassName,
|
|
16
|
+
tableStickyRightHeadClassName,
|
|
17
|
+
tableStickyRightShadowClassName,
|
|
18
|
+
type TableCellVariant,
|
|
19
|
+
} from './constants';
|
|
20
|
+
import { TableRowDisabledProvider, useTableRowDisabled } from './table-row-context';
|
|
21
|
+
|
|
22
|
+
export type TableProps = HTMLAttributes<HTMLTableElement>;
|
|
23
|
+
|
|
24
|
+
export function Table({ className, ...props }: TableProps) {
|
|
25
|
+
const dataTableClassNames = useDataTableClassNames();
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<table
|
|
29
|
+
className={cn('w-full border-collapse text-left', dataTableClassNames.table, className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type TableHeaderProps = HTMLAttributes<HTMLTableSectionElement>;
|
|
36
|
+
|
|
37
|
+
export function TableHeader({ className, ...props }: TableHeaderProps) {
|
|
38
|
+
const dataTableClassNames = useDataTableClassNames();
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<thead
|
|
42
|
+
className={cn(
|
|
43
|
+
'border-b bg-storm-gray-0',
|
|
44
|
+
tableBorderClassName,
|
|
45
|
+
dataTableClassNames.header,
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type TableBodyProps = HTMLAttributes<HTMLTableSectionElement>;
|
|
54
|
+
|
|
55
|
+
export function TableBody({ className, ...props }: TableBodyProps) {
|
|
56
|
+
const dataTableClassNames = useDataTableClassNames();
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<tbody
|
|
60
|
+
className={cn(tableBodyClassName, dataTableClassNames.body, className)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type TableRowProps = HTMLAttributes<HTMLTableRowElement> & {
|
|
67
|
+
/** Muted row state — Storm Gray/50 background at 30% with 70% content opacity. */
|
|
68
|
+
disabled?: boolean;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function TableRow({ className, disabled = false, ...props }: TableRowProps) {
|
|
72
|
+
return (
|
|
73
|
+
<TableRowDisabledProvider disabled={disabled}>
|
|
74
|
+
<tr
|
|
75
|
+
className={cn('border-b last:border-b-0', tableBorderClassName, className)}
|
|
76
|
+
aria-disabled={disabled || undefined}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
</TableRowDisabledProvider>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type TableHeadProps = ThHTMLAttributes<HTMLTableCellElement> & {
|
|
84
|
+
align?: keyof typeof tableAlignClasses;
|
|
85
|
+
/** Pins the column to the right edge during horizontal scroll (e.g. actions menu). */
|
|
86
|
+
stickyRight?: boolean;
|
|
87
|
+
/** Enables sort UI and click handler for this column header. */
|
|
88
|
+
sortable?: boolean;
|
|
89
|
+
/** Active sort direction for this column. Omit or `null` when unsorted. */
|
|
90
|
+
sortDirection?: TableSortDirection | null;
|
|
91
|
+
/** Called when the sortable header is clicked. */
|
|
92
|
+
onSort?: () => void;
|
|
93
|
+
children?: ReactNode;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
function resolveAriaSort(
|
|
97
|
+
sortDirection: TableSortDirection | null,
|
|
98
|
+
isSortable: boolean,
|
|
99
|
+
): 'ascending' | 'descending' | 'none' | undefined {
|
|
100
|
+
if (sortDirection === 'asc') {
|
|
101
|
+
return 'ascending';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (sortDirection === 'desc') {
|
|
105
|
+
return 'descending';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return isSortable ? 'none' : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function TableHead({
|
|
112
|
+
className,
|
|
113
|
+
align = 'left',
|
|
114
|
+
stickyRight = false,
|
|
115
|
+
sortable = false,
|
|
116
|
+
sortDirection = null,
|
|
117
|
+
onSort,
|
|
118
|
+
children,
|
|
119
|
+
...props
|
|
120
|
+
}: TableHeadProps) {
|
|
121
|
+
const isSortable = sortable && onSort != null;
|
|
122
|
+
|
|
123
|
+
const content = isSortable ? (
|
|
124
|
+
<button
|
|
125
|
+
type="button"
|
|
126
|
+
onClick={onSort}
|
|
127
|
+
className={cn(
|
|
128
|
+
'inline-flex w-full cursor-pointer items-center gap-2.5 border-0 bg-transparent p-0 font-[inherit] text-[length:inherit] leading-[inherit] tracking-[inherit] text-inherit transition-colors hover:text-storm-gray-500',
|
|
129
|
+
tableHeadSortButtonAlignClasses[align],
|
|
130
|
+
)}
|
|
131
|
+
>
|
|
132
|
+
<span>{children}</span>
|
|
133
|
+
<TableSortIcon direction={sortDirection} />
|
|
134
|
+
</button>
|
|
135
|
+
) : (
|
|
136
|
+
children
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<th
|
|
141
|
+
className={cn(
|
|
142
|
+
'sticky top-0 z-10',
|
|
143
|
+
tableHeadPaddingClassName,
|
|
144
|
+
tableHeaderClassName,
|
|
145
|
+
stickyRight && [
|
|
146
|
+
'sticky right-0 z-20',
|
|
147
|
+
tableStickyRightHeadClassName,
|
|
148
|
+
tableStickyRightShadowClassName,
|
|
149
|
+
],
|
|
150
|
+
!isSortable && tableAlignClasses[align],
|
|
151
|
+
className,
|
|
152
|
+
)}
|
|
153
|
+
aria-sort={resolveAriaSort(sortDirection, isSortable)}
|
|
154
|
+
{...props}
|
|
155
|
+
>
|
|
156
|
+
{content}
|
|
157
|
+
</th>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export type { TableCellVariant };
|
|
162
|
+
|
|
163
|
+
export type TableCellProps = TdHTMLAttributes<HTMLTableCellElement> & {
|
|
164
|
+
variant?: TableCellVariant;
|
|
165
|
+
align?: keyof typeof tableAlignClasses;
|
|
166
|
+
children?: ReactNode;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export function TableCell({
|
|
170
|
+
className,
|
|
171
|
+
variant = 'default',
|
|
172
|
+
align = 'left',
|
|
173
|
+
children,
|
|
174
|
+
...props
|
|
175
|
+
}: TableCellProps) {
|
|
176
|
+
const disabled = useTableRowDisabled();
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<td
|
|
180
|
+
className={cn(
|
|
181
|
+
tableCellPaddingClassName,
|
|
182
|
+
tableBodyClassName,
|
|
183
|
+
tableCellVariantClasses[variant],
|
|
184
|
+
disabled && tableRowDisabledCellClassName,
|
|
185
|
+
tableAlignClasses[align],
|
|
186
|
+
className,
|
|
187
|
+
)}
|
|
188
|
+
{...props}
|
|
189
|
+
>
|
|
190
|
+
{children}
|
|
191
|
+
</td>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export type TableActionsCellProps = TdHTMLAttributes<HTMLTableCellElement> & {
|
|
196
|
+
children?: ReactNode;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export function TableActionsCell({ className, children, ...props }: TableActionsCellProps) {
|
|
200
|
+
return (
|
|
201
|
+
<td
|
|
202
|
+
className={cn(
|
|
203
|
+
'sticky right-0 z-10 whitespace-nowrap bg-white',
|
|
204
|
+
tableCellPaddingClassName,
|
|
205
|
+
tableBodyClassName,
|
|
206
|
+
tableStickyRightShadowClassName,
|
|
207
|
+
className,
|
|
208
|
+
)}
|
|
209
|
+
{...props}
|
|
210
|
+
>
|
|
211
|
+
<div className="relative z-[1] flex items-center justify-end gap-2">{children}</div>
|
|
212
|
+
</td>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
import {
|
|
5
|
+
resolveTableMinWidth,
|
|
6
|
+
resolveTableSize,
|
|
7
|
+
tableScrollAtEndClassName,
|
|
8
|
+
tableScrollHasOverflowClassName,
|
|
9
|
+
} from './constants';
|
|
10
|
+
import { useHorizontalScrollState } from './use-horizontal-scroll-state';
|
|
11
|
+
|
|
12
|
+
export type TableScrollAreaProps = {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
minWidth?: number | string;
|
|
15
|
+
className?: string;
|
|
16
|
+
minHeight?: number | string;
|
|
17
|
+
height?: number | string;
|
|
18
|
+
fillHeight?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function TableScrollArea({
|
|
22
|
+
children,
|
|
23
|
+
minWidth = 980,
|
|
24
|
+
className,
|
|
25
|
+
minHeight,
|
|
26
|
+
height,
|
|
27
|
+
fillHeight = false,
|
|
28
|
+
}: TableScrollAreaProps) {
|
|
29
|
+
const { scrollRef, scrollState } = useHorizontalScrollState();
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
ref={scrollRef}
|
|
34
|
+
className={cn(
|
|
35
|
+
'table-scroll-area w-full overflow-auto',
|
|
36
|
+
scrollState.hasOverflow && tableScrollHasOverflowClassName,
|
|
37
|
+
scrollState.atEnd && tableScrollAtEndClassName,
|
|
38
|
+
fillHeight && 'min-h-0 flex-1',
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
style={{
|
|
42
|
+
...(minHeight !== undefined ? { minHeight: resolveTableSize(minHeight) } : {}),
|
|
43
|
+
...(height !== undefined ? { height: resolveTableSize(height) } : {}),
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<div className="w-full" style={{ minWidth: resolveTableMinWidth(minWidth) }}>
|
|
47
|
+
{children}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const tableBorderClassName = 'border-storm-gray-50';
|
|
2
|
+
|
|
3
|
+
export const tableStickyRightShadowClassName = 'table-sticky-right-shadow';
|
|
4
|
+
|
|
5
|
+
export const tableStickyRightHeadClassName = 'table-sticky-right-head';
|
|
6
|
+
|
|
7
|
+
export const tableScrollAtEndClassName = 'table-scroll-at-end';
|
|
8
|
+
|
|
9
|
+
export const tableScrollHasOverflowClassName = 'table-scroll-has-overflow';
|
|
10
|
+
|
|
11
|
+
export const tableRowDisabledCellClassName = 'bg-[#ECEEF0]/30 [&>*]:opacity-70';
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_TABLE_BODY_MIN_HEIGHT = 320;
|
|
14
|
+
|
|
15
|
+
export const tableCellPaddingClassName = 'px-3 py-3';
|
|
16
|
+
|
|
17
|
+
export const tableHeadPaddingClassName = 'px-3 py-4';
|
|
18
|
+
|
|
19
|
+
export const tableShellClassName = 'flex flex-col overflow-hidden border bg-storm-gray-0';
|
|
20
|
+
|
|
21
|
+
export const tableFooterShellClassName = 'overflow-hidden rounded-b-xl border bg-storm-gray-0';
|
|
22
|
+
|
|
23
|
+
export const tableHeaderClassName =
|
|
24
|
+
'bg-storm-gray-0 font-sans text-xs font-normal leading-none tracking-normal text-storm-gray-400';
|
|
25
|
+
|
|
26
|
+
export const tableBodyClassName =
|
|
27
|
+
'bg-white font-sans text-sm font-medium leading-none tracking-normal text-storm-gray-900';
|
|
28
|
+
|
|
29
|
+
export const tableAlignClasses = {
|
|
30
|
+
left: '',
|
|
31
|
+
right: 'text-right',
|
|
32
|
+
center: 'text-center',
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
export const tableHeadSortButtonAlignClasses = {
|
|
36
|
+
left: 'text-left',
|
|
37
|
+
right: 'justify-end text-right',
|
|
38
|
+
center: 'justify-center text-center',
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
export type TableCellVariant = 'default' | 'emphasis' | 'muted' | 'strong';
|
|
42
|
+
|
|
43
|
+
const tableCellEmphasisClassName = 'font-bold';
|
|
44
|
+
|
|
45
|
+
/** Typography presets applied on top of `tableBodyClassName`. */
|
|
46
|
+
export const tableCellVariantClasses: Record<TableCellVariant, string> = {
|
|
47
|
+
default: '',
|
|
48
|
+
emphasis: tableCellEmphasisClassName,
|
|
49
|
+
muted: 'text-storm-gray-400',
|
|
50
|
+
strong: tableCellEmphasisClassName,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function resolveTableSize(value: number | string): string {
|
|
54
|
+
return typeof value === 'number' ? `${value}px` : value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function resolveTableMinWidth(minWidth: number | string): string {
|
|
58
|
+
return resolveTableSize(minWidth);
|
|
59
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
export type DataTableClassNames = {
|
|
4
|
+
/** Outer flex wrapper around the whole table block. */
|
|
5
|
+
root?: string;
|
|
6
|
+
/** Bordered shell around the scroll area (border, radius, background). */
|
|
7
|
+
shell?: string;
|
|
8
|
+
/** Scrollable table region. */
|
|
9
|
+
scroll?: string;
|
|
10
|
+
/** Footer wrapper around pagination. */
|
|
11
|
+
footer?: string;
|
|
12
|
+
/** The `<table>` element. */
|
|
13
|
+
table?: string;
|
|
14
|
+
/** The `<thead>` section. */
|
|
15
|
+
header?: string;
|
|
16
|
+
/** The `<tbody>` section. */
|
|
17
|
+
body?: string;
|
|
18
|
+
/** Pagination component when used as a table footer. */
|
|
19
|
+
pagination?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const DataTableClassNamesContext = createContext<DataTableClassNames>({});
|
|
23
|
+
|
|
24
|
+
export function useDataTableClassNames() {
|
|
25
|
+
return useContext(DataTableClassNamesContext);
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type { DataTableClassNames } from './data-table-class-names';
|
|
2
|
+
export type { TableSortDirection } from '../../icons/TableSortIcon';
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
DEFAULT_TABLE_BODY_MIN_HEIGHT,
|
|
6
|
+
tableAlignClasses,
|
|
7
|
+
tableBodyClassName,
|
|
8
|
+
tableBorderClassName,
|
|
9
|
+
tableCellPaddingClassName,
|
|
10
|
+
tableCellVariantClasses,
|
|
11
|
+
tableFooterShellClassName,
|
|
12
|
+
tableHeadPaddingClassName,
|
|
13
|
+
tableRowDisabledCellClassName,
|
|
14
|
+
tableScrollAtEndClassName,
|
|
15
|
+
tableScrollHasOverflowClassName,
|
|
16
|
+
tableShellClassName,
|
|
17
|
+
tableStickyRightShadowClassName,
|
|
18
|
+
} from './constants';
|
|
19
|
+
export type { TableCellVariant } from './constants';
|
|
20
|
+
|
|
21
|
+
export { DataTable } from './DataTable';
|
|
22
|
+
export type { DataTableProps } from './DataTable';
|
|
23
|
+
|
|
24
|
+
export { TableScrollArea } from './TableScrollArea';
|
|
25
|
+
export type { TableScrollAreaProps } from './TableScrollArea';
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
Table,
|
|
29
|
+
TableActionsCell,
|
|
30
|
+
TableBody,
|
|
31
|
+
TableCell,
|
|
32
|
+
TableHead,
|
|
33
|
+
TableHeader,
|
|
34
|
+
TableRow,
|
|
35
|
+
} from './TablePrimitives';
|
|
36
|
+
export type {
|
|
37
|
+
TableActionsCellProps,
|
|
38
|
+
TableBodyProps,
|
|
39
|
+
TableCellProps,
|
|
40
|
+
TableHeadProps,
|
|
41
|
+
TableHeaderProps,
|
|
42
|
+
TableProps,
|
|
43
|
+
TableRowProps,
|
|
44
|
+
} from './TablePrimitives';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const TableRowDisabledContext = createContext(false);
|
|
4
|
+
|
|
5
|
+
export function TableRowDisabledProvider({
|
|
6
|
+
disabled,
|
|
7
|
+
children,
|
|
8
|
+
}: {
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<TableRowDisabledContext.Provider value={disabled}>{children}</TableRowDisabledContext.Provider>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useTableRowDisabled() {
|
|
18
|
+
return useContext(TableRowDisabledContext);
|
|
19
|
+
}
|