@structuralists/scaffolding 0.0.1
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/.storybook/main.ts +9 -0
- package/.storybook/manager.ts +13 -0
- package/.storybook/preview.tsx +18 -0
- package/CLAUDE.md +30 -0
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/bun.lock +947 -0
- package/bunfig.toml +2 -0
- package/eslint.config.mjs +106 -0
- package/index.ts +1 -0
- package/package.json +50 -0
- package/src/components/Chat/ChatComposer/ChatComposer.stories.tsx +68 -0
- package/src/components/Chat/ChatComposer/index.tsx +74 -0
- package/src/components/Chat/ChatComposer/styles.module.css +88 -0
- package/src/components/Chat/ChatComposer/types.ts +11 -0
- package/src/components/Chat/ChatMessage/ChatMessage.stories.tsx +111 -0
- package/src/components/Chat/ChatMessage/index.tsx +42 -0
- package/src/components/Chat/ChatMessage/styles.module.css +58 -0
- package/src/components/Chat/ChatMessage/types.ts +14 -0
- package/src/components/Chat/ChatRecipientsHeader/ChatRecipientsHeader.stories.tsx +145 -0
- package/src/components/Chat/ChatRecipientsHeader/index.tsx +29 -0
- package/src/components/Chat/ChatRecipientsHeader/styles.module.css +48 -0
- package/src/components/Chat/ChatRecipientsHeader/types.ts +26 -0
- package/src/components/Chat/ChatShell/ChatShell.stories.tsx +203 -0
- package/src/components/Chat/ChatShell/index.tsx +16 -0
- package/src/components/Chat/ChatShell/styles.module.css +27 -0
- package/src/components/Chat/ChatShell/types.ts +7 -0
- package/src/components/Chat/PillCombobox/PillCombobox.stories.tsx +59 -0
- package/src/components/Chat/PillCombobox/index.tsx +17 -0
- package/src/components/Chat/PillCombobox/styles.module.css +29 -0
- package/src/components/Chat/PillCombobox/types.ts +28 -0
- package/src/components/Chat/PillComboboxCore/Core.tsx +235 -0
- package/src/components/Chat/PillComboboxCore/styles.module.css +79 -0
- package/src/components/Chat/index.ts +12 -0
- package/src/components/Content/Badge/Badge.stories.tsx +31 -0
- package/src/components/Content/Badge/index.tsx +22 -0
- package/src/components/Content/Badge/styles.module.css +25 -0
- package/src/components/Content/Badge/types.ts +7 -0
- package/src/components/Content/Card/Card.stories.tsx +24 -0
- package/src/components/Content/Card/index.tsx +21 -0
- package/src/components/Content/Card/styles.module.css +13 -0
- package/src/components/Content/Card/types.ts +8 -0
- package/src/components/Content/EditableMarkdown/EditableMarkdown.stories.tsx +58 -0
- package/src/components/Content/EditableMarkdown/index.tsx +140 -0
- package/src/components/Content/EditableMarkdown/styles.module.css +221 -0
- package/src/components/Content/EditableMarkdown/types.ts +11 -0
- package/src/components/Content/Heading/Heading.stories.tsx +26 -0
- package/src/components/Content/Heading/index.tsx +20 -0
- package/src/components/Content/Heading/styles.module.css +19 -0
- package/src/components/Content/Heading/types.ts +8 -0
- package/src/components/Content/Link/Link.stories.tsx +21 -0
- package/src/components/Content/Link/index.tsx +19 -0
- package/src/components/Content/Link/styles.module.css +11 -0
- package/src/components/Content/Link/types.ts +8 -0
- package/src/components/Content/List/List.stories.tsx +62 -0
- package/src/components/Content/List/index.tsx +26 -0
- package/src/components/Content/List/styles.module.css +41 -0
- package/src/components/Content/List/types.ts +33 -0
- package/src/components/Content/LoadingContainer/LoadingContainer.stories.tsx +105 -0
- package/src/components/Content/LoadingContainer/index.tsx +36 -0
- package/src/components/Content/LoadingContainer/styles.module.css +54 -0
- package/src/components/Content/LoadingContainer/types.ts +8 -0
- package/src/components/Content/Markdown/Markdown.stories.tsx +39 -0
- package/src/components/Content/Markdown/index.tsx +28 -0
- package/src/components/Content/Markdown/styles.module.css +79 -0
- package/src/components/Content/Markdown/types.ts +8 -0
- package/src/components/Content/Menu/Menu.stories.tsx +186 -0
- package/src/components/Content/Menu/index.tsx +259 -0
- package/src/components/Content/Menu/styles.module.css +103 -0
- package/src/components/Content/Menu/types.ts +25 -0
- package/src/components/Content/Text/Text.stories.tsx +36 -0
- package/src/components/Content/Text/index.tsx +35 -0
- package/src/components/Content/Text/styles.module.css +30 -0
- package/src/components/Content/Text/types.ts +11 -0
- package/src/components/Forms/Button/Button.stories.tsx +40 -0
- package/src/components/Forms/Button/index.tsx +43 -0
- package/src/components/Forms/Button/styles.module.css +67 -0
- package/src/components/Forms/Button/types.ts +16 -0
- package/src/components/Forms/ColorInput/index.tsx +22 -0
- package/src/components/Forms/ColorInput/styles.module.css +19 -0
- package/src/components/Forms/ColorInput/types.ts +12 -0
- package/src/components/Forms/Field/Field.stories.tsx +35 -0
- package/src/components/Forms/Field/index.tsx +17 -0
- package/src/components/Forms/Field/styles.module.css +21 -0
- package/src/components/Forms/Field/types.ts +9 -0
- package/src/components/Forms/IconButton/IconButton.stories.tsx +91 -0
- package/src/components/Forms/IconButton/index.tsx +55 -0
- package/src/components/Forms/IconButton/styles.module.css +61 -0
- package/src/components/Forms/IconButton/types.ts +23 -0
- package/src/components/Forms/Input/Input.stories.tsx +22 -0
- package/src/components/Forms/Input/index.tsx +42 -0
- package/src/components/Forms/Input/styles.module.css +30 -0
- package/src/components/Forms/Input/types.ts +18 -0
- package/src/components/Forms/SearchInput/index.tsx +41 -0
- package/src/components/Forms/SearchInput/styles.module.css +30 -0
- package/src/components/Forms/SearchInput/types.ts +17 -0
- package/src/components/Forms/Select/MultiSelect/MultiSelect.stories.tsx +116 -0
- package/src/components/Forms/Select/MultiSelect/index.tsx +74 -0
- package/src/components/Forms/Select/MultiSelect/types.ts +15 -0
- package/src/components/Forms/Select/SingleSelect/SingleSelect.stories.tsx +174 -0
- package/src/components/Forms/Select/SingleSelect/index.tsx +62 -0
- package/src/components/Forms/Select/SingleSelect/types.ts +12 -0
- package/src/components/Forms/Select/index.ts +4 -0
- package/src/components/Forms/Select/internal/OptionList.tsx +124 -0
- package/src/components/Forms/Select/internal/SelectTrigger.tsx +60 -0
- package/src/components/Forms/Select/internal/styles.module.css +122 -0
- package/src/components/Forms/Textarea/Textarea.stories.tsx +25 -0
- package/src/components/Forms/Textarea/index.tsx +48 -0
- package/src/components/Forms/Textarea/styles.module.css +34 -0
- package/src/components/Forms/Textarea/types.ts +24 -0
- package/src/components/Json/Json/Json.stories.tsx +33 -0
- package/src/components/Json/Json/index.tsx +38 -0
- package/src/components/Json/Json/types.ts +21 -0
- package/src/components/Json/JsonTable/JsonLeafNode.tsx +31 -0
- package/src/components/Json/JsonTable/JsonTable.stories.tsx +52 -0
- package/src/components/Json/JsonTable/index.tsx +33 -0
- package/src/components/Json/JsonTable/types.ts +13 -0
- package/src/components/Json/JsonTable/utils.ts +6 -0
- package/src/components/Layout/Bar/Bar.stories.tsx +100 -0
- package/src/components/Layout/Bar/index.tsx +17 -0
- package/src/components/Layout/Bar/styles.module.css +34 -0
- package/src/components/Layout/Bar/types.ts +10 -0
- package/src/components/Layout/Debug/Debug.stories.tsx +86 -0
- package/src/components/Layout/Debug/index.tsx +41 -0
- package/src/components/Layout/Debug/styles.module.css +13 -0
- package/src/components/Layout/Debug/types.ts +12 -0
- package/src/components/Layout/Divider/Divider.stories.tsx +22 -0
- package/src/components/Layout/Divider/index.tsx +3 -0
- package/src/components/Layout/Divider/styles.module.css +6 -0
- package/src/components/Layout/Grid/Grid.stories.tsx +28 -0
- package/src/components/Layout/Grid/index.tsx +29 -0
- package/src/components/Layout/Grid/styles.module.css +12 -0
- package/src/components/Layout/Grid/types.ts +9 -0
- package/src/components/Layout/Panels/Panels.stories.tsx +287 -0
- package/src/components/Layout/Panels/index.tsx +119 -0
- package/src/components/Layout/Panels/styles.module.css +103 -0
- package/src/components/Layout/Panels/types.ts +36 -0
- package/src/components/Layout/Stack/Stack.stories.tsx +45 -0
- package/src/components/Layout/Stack/index.tsx +73 -0
- package/src/components/Layout/Stack/styles.module.css +41 -0
- package/src/components/Layout/Stack/types.ts +17 -0
- package/src/components/Modals/ConfirmModal/ConfirmModal.stories.tsx +73 -0
- package/src/components/Modals/ConfirmModal/index.tsx +72 -0
- package/src/components/Modals/ConfirmModal/styles.module.css +62 -0
- package/src/components/Modals/ConfirmModal/types.ts +14 -0
- package/src/components/Modals/LargeModal/LargeModal.stories.tsx +75 -0
- package/src/components/Modals/LargeModal/index.tsx +9 -0
- package/src/components/Modals/LargeModal/styles.module.css +6 -0
- package/src/components/Modals/LargeModal/types.ts +18 -0
- package/src/components/Modals/MediumModal/MediumModal.stories.tsx +121 -0
- package/src/components/Modals/MediumModal/MediumModal.test.tsx +48 -0
- package/src/components/Modals/MediumModal/index.tsx +9 -0
- package/src/components/Modals/MediumModal/styles.module.css +5 -0
- package/src/components/Modals/MediumModal/types.ts +18 -0
- package/src/components/Modals/index.ts +3 -0
- package/src/components/Modals/internal/ModalBody.tsx +21 -0
- package/src/components/Modals/internal/ModalFooter.tsx +12 -0
- package/src/components/Modals/internal/ModalHeader.tsx +27 -0
- package/src/components/Modals/internal/ModalShell.tsx +112 -0
- package/src/components/Modals/internal/styles.module.css +141 -0
- package/src/components/Navigation/TabBar/TabBar.stories.tsx +59 -0
- package/src/components/Navigation/TabBar/index.tsx +25 -0
- package/src/components/Navigation/TabBar/styles.module.css +32 -0
- package/src/components/Navigation/TabBar/types.ts +22 -0
- package/src/components/Navigation/VerticalNav/VerticalNav.stories.tsx +41 -0
- package/src/components/Navigation/VerticalNav/index.tsx +25 -0
- package/src/components/Navigation/VerticalNav/styles.module.css +28 -0
- package/src/components/Navigation/VerticalNav/types.ts +19 -0
- package/src/components/Overlays/Popover/Popover.stories.tsx +154 -0
- package/src/components/Overlays/Popover/index.tsx +175 -0
- package/src/components/Overlays/Popover/styles.module.css +59 -0
- package/src/components/Overlays/Popover/types.ts +34 -0
- package/src/components/Overlays/Tooltip/Tooltip.stories.tsx +41 -0
- package/src/components/Overlays/Tooltip/index.tsx +115 -0
- package/src/components/Overlays/Tooltip/styles.module.css +25 -0
- package/src/components/Overlays/Tooltip/types.ts +15 -0
- package/src/components/Primitives/EmptyValue/EmptyValue.stories.tsx +18 -0
- package/src/components/Primitives/EmptyValue/index.tsx +3 -0
- package/src/components/Primitives/EmptyValue/styles.module.css +3 -0
- package/src/components/Primitives/LinedStack/LinedStack.stories.tsx +101 -0
- package/src/components/Primitives/LinedStack/index.tsx +41 -0
- package/src/components/Primitives/LinedStack/styles.module.css +27 -0
- package/src/components/Primitives/LinedStack/types.ts +49 -0
- package/src/components/Primitives/LongText/LongText.stories.tsx +72 -0
- package/src/components/Primitives/LongText/index.tsx +67 -0
- package/src/components/Primitives/LongText/styles.module.css +30 -0
- package/src/components/Primitives/LongText/types.ts +4 -0
- package/src/components/Primitives/Num/Num.stories.tsx +51 -0
- package/src/components/Primitives/Num/index.tsx +37 -0
- package/src/components/Primitives/Num/types.ts +19 -0
- package/src/components/Primitives/Percent/Percent.stories.tsx +48 -0
- package/src/components/Primitives/Percent/index.tsx +15 -0
- package/src/components/Primitives/Percent/types.ts +10 -0
- package/src/components/Primitives/RelativeTime/RelativeTime.stories.tsx +57 -0
- package/src/components/Primitives/RelativeTime/index.tsx +31 -0
- package/src/components/Primitives/RelativeTime/types.ts +3 -0
- package/src/components/Tables/BigTable/BigTable.stories.tsx +367 -0
- package/src/components/Tables/BigTable/CLAUDE.md +118 -0
- package/src/components/Tables/BigTable/columnDefs.tsx +208 -0
- package/src/components/Tables/BigTable/index.tsx +104 -0
- package/src/components/Tables/BigTable/styles.module.css +83 -0
- package/src/components/Tables/BigTable/types.ts +20 -0
- package/src/components/Tables/QuickTable/CLAUDE.md +118 -0
- package/src/components/Tables/QuickTable/QuickTable.stories.tsx +121 -0
- package/src/components/Tables/QuickTable/index.tsx +86 -0
- package/src/components/Tables/QuickTable/internal.tsx +48 -0
- package/src/components/Tables/QuickTable/styles.module.css +65 -0
- package/src/components/Tables/QuickTable/types.ts +40 -0
- package/src/env.d.ts +4 -0
- package/src/index.ts +87 -0
- package/src/storybook/CLAUDE.md +35 -0
- package/src/storybook/Composition.stories.tsx +269 -0
- package/src/storybook/Lorem/index.tsx +54 -0
- package/src/storybook/Placeholder/index.tsx +27 -0
- package/src/storybook/Placeholder/styles.module.css +20 -0
- package/src/storybook/Repeat/index.tsx +23 -0
- package/src/storybook/Toggle/index.tsx +29 -0
- package/src/storybook/_StoryUtils.stories.tsx +58 -0
- package/src/storybook/index.ts +4 -0
- package/src/tokens.ts +31 -0
- package/src/utils.test.ts +24 -0
- package/src/utils.ts +2 -0
- package/test-setup.ts +3 -0
- package/tokens.css +323 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { CSSProperties, MouseEvent } from 'react';
|
|
2
|
+
import { cx } from '../../../utils';
|
|
3
|
+
import type { BigTableProps, ColumnAlign } from './types';
|
|
4
|
+
import styles from './styles.module.css';
|
|
5
|
+
|
|
6
|
+
const INTERACTIVE_CHILD_SELECTOR =
|
|
7
|
+
'a, button, input, select, textarea, label, [role="button"]';
|
|
8
|
+
|
|
9
|
+
const alignClass = (align: ColumnAlign | undefined): string | undefined => {
|
|
10
|
+
if (align === 'right') return styles.alignRight;
|
|
11
|
+
if (align === 'center') return styles.alignCenter;
|
|
12
|
+
return undefined;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const widthStyle = (
|
|
16
|
+
width: number | string | undefined,
|
|
17
|
+
minWidth: number | undefined,
|
|
18
|
+
): CSSProperties | undefined => {
|
|
19
|
+
if (width == null && minWidth == null) return undefined;
|
|
20
|
+
const style: CSSProperties = {};
|
|
21
|
+
if (width != null) style.width = typeof width === 'number' ? `${width}px` : width;
|
|
22
|
+
if (minWidth != null) style.minWidth = `${minWidth}px`;
|
|
23
|
+
return style;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const BigTable = <T,>(props: BigTableProps<T>) => {
|
|
27
|
+
const { data, columnDefs, getRowKey, hasStickyFirstColumn, onRowClick } = props;
|
|
28
|
+
|
|
29
|
+
const handleRowClick = onRowClick
|
|
30
|
+
? (row: T) => (e: MouseEvent<HTMLTableRowElement>) => {
|
|
31
|
+
const target = e.target as HTMLElement;
|
|
32
|
+
if (target.closest(INTERACTIVE_CHILD_SELECTOR)) return;
|
|
33
|
+
onRowClick(row);
|
|
34
|
+
}
|
|
35
|
+
: undefined;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className={styles.shell}>
|
|
39
|
+
<table className={styles.table}>
|
|
40
|
+
<thead>
|
|
41
|
+
<tr>
|
|
42
|
+
{columnDefs.map((col, i) => {
|
|
43
|
+
const isSticky = hasStickyFirstColumn && i === 0;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<th
|
|
47
|
+
key={col.id}
|
|
48
|
+
className={cx(
|
|
49
|
+
styles.headerCell,
|
|
50
|
+
alignClass(col.align),
|
|
51
|
+
isSticky && styles.stickyColumn,
|
|
52
|
+
)}
|
|
53
|
+
style={widthStyle(col.width, col.minWidth)}
|
|
54
|
+
>
|
|
55
|
+
{col.header}
|
|
56
|
+
</th>
|
|
57
|
+
);
|
|
58
|
+
})}
|
|
59
|
+
</tr>
|
|
60
|
+
</thead>
|
|
61
|
+
<tbody>
|
|
62
|
+
{data.map((row) => (
|
|
63
|
+
<tr
|
|
64
|
+
key={getRowKey(row)}
|
|
65
|
+
className={cx(styles.row, handleRowClick && styles.rowClickable)}
|
|
66
|
+
onClick={handleRowClick?.(row)}
|
|
67
|
+
>
|
|
68
|
+
{columnDefs.map((col, colIndex) => {
|
|
69
|
+
const isSticky = hasStickyFirstColumn && colIndex === 0;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<td
|
|
73
|
+
key={col.id}
|
|
74
|
+
className={cx(
|
|
75
|
+
styles.cell,
|
|
76
|
+
alignClass(col.align),
|
|
77
|
+
isSticky && styles.stickyColumn,
|
|
78
|
+
)}
|
|
79
|
+
style={widthStyle(col.width, col.minWidth)}
|
|
80
|
+
>
|
|
81
|
+
{col.cell(row)}
|
|
82
|
+
</td>
|
|
83
|
+
);
|
|
84
|
+
})}
|
|
85
|
+
</tr>
|
|
86
|
+
))}
|
|
87
|
+
</tbody>
|
|
88
|
+
</table>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export type { BigTableProps, ColumnDef, ColumnAlign } from './types';
|
|
94
|
+
export {
|
|
95
|
+
textColumn,
|
|
96
|
+
numberColumn,
|
|
97
|
+
currencyColumn,
|
|
98
|
+
dateColumn,
|
|
99
|
+
relativeDateColumn,
|
|
100
|
+
badgeColumn,
|
|
101
|
+
booleanColumn,
|
|
102
|
+
linkColumn,
|
|
103
|
+
idColumn,
|
|
104
|
+
} from './columnDefs';
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
.shell {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
overflow: auto;
|
|
5
|
+
background-color: var(--ui-background-0);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.table {
|
|
9
|
+
border-collapse: separate;
|
|
10
|
+
border-spacing: 0;
|
|
11
|
+
min-width: max-content;
|
|
12
|
+
width: 100%;
|
|
13
|
+
font-size: var(--ui-text-small);
|
|
14
|
+
color: var(--ui-foreground);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.headerCell {
|
|
18
|
+
position: sticky;
|
|
19
|
+
top: 0;
|
|
20
|
+
z-index: 2;
|
|
21
|
+
background-color: var(--ui-background-1);
|
|
22
|
+
text-align: left;
|
|
23
|
+
vertical-align: middle;
|
|
24
|
+
font-weight: 500;
|
|
25
|
+
color: var(--ui-muted);
|
|
26
|
+
font-size: var(--ui-text-xsmall);
|
|
27
|
+
text-transform: uppercase;
|
|
28
|
+
letter-spacing: 0.03em;
|
|
29
|
+
padding: var(--ui-space-2);
|
|
30
|
+
border-bottom: 1px solid var(--ui-border);
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.cell {
|
|
35
|
+
text-align: left;
|
|
36
|
+
vertical-align: middle;
|
|
37
|
+
padding: var(--ui-space-2);
|
|
38
|
+
line-height: var(--ui-line-height-tight);
|
|
39
|
+
border-bottom: 1px solid var(--ui-border-subtle);
|
|
40
|
+
background-color: var(--ui-background-0);
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.row:nth-child(even) .cell {
|
|
45
|
+
background-color: var(--ui-background-0-offset);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.rowClickable {
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.rowClickable:hover .cell,
|
|
53
|
+
.rowClickable:nth-child(even):hover .cell {
|
|
54
|
+
background-color: var(--ui-background-1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.alignRight {
|
|
58
|
+
text-align: right;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.alignCenter {
|
|
62
|
+
text-align: center;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.stickyColumn {
|
|
66
|
+
position: sticky;
|
|
67
|
+
left: 0;
|
|
68
|
+
z-index: 1;
|
|
69
|
+
border-right: 1px solid var(--ui-border-subtle);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.headerCell.stickyColumn {
|
|
73
|
+
z-index: 3;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.cellMono {
|
|
77
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
78
|
+
color: var(--ui-muted);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.cellMuted {
|
|
82
|
+
color: var(--ui-muted);
|
|
83
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type ColumnAlign = 'left' | 'right' | 'center';
|
|
4
|
+
|
|
5
|
+
export type ColumnDef<T> = {
|
|
6
|
+
id: string;
|
|
7
|
+
header: ReactNode;
|
|
8
|
+
cell: (row: T) => ReactNode;
|
|
9
|
+
align?: ColumnAlign;
|
|
10
|
+
width?: number | string;
|
|
11
|
+
minWidth?: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type BigTableProps<T> = {
|
|
15
|
+
data: T[];
|
|
16
|
+
columnDefs: ColumnDef<T>[];
|
|
17
|
+
getRowKey: (row: T) => string | number;
|
|
18
|
+
hasStickyFirstColumn?: boolean;
|
|
19
|
+
onRowClick?: (row: T) => void;
|
|
20
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# QuickTable — read this before editing
|
|
2
|
+
|
|
3
|
+
**This primitive deliberately violates the typed-data-driven table pattern you'd default to.** It accepts JSX children, not `rows` / `columns` props. Do not "fix" it.
|
|
4
|
+
|
|
5
|
+
## What it is
|
|
6
|
+
|
|
7
|
+
`QuickTable` is for throwaway tables — admin surfaces, debug views, quick-and-dirty lists. The API reads like the `<table><tr><td>…</td></tr></table>` DOM it produces, but elides the boilerplate.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<QuickTable headerCells={['Name', 'Role', 'Amount']}>
|
|
11
|
+
{rows.map((r) => (
|
|
12
|
+
<QuickTableRow key={r.id}>
|
|
13
|
+
{r.name}
|
|
14
|
+
{r.role}
|
|
15
|
+
{r.amount.toLocaleString()}
|
|
16
|
+
</QuickTableRow>
|
|
17
|
+
))}
|
|
18
|
+
</QuickTable>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Every child that isn't already a row gets auto-wrapped into one. Every child of a row that isn't already a cell gets auto-wrapped into one. That's it.
|
|
22
|
+
|
|
23
|
+
## What it is NOT
|
|
24
|
+
|
|
25
|
+
- **Not a data-driven table.** There is no `rows: T[]` prop. There is no `columns: ColumnDef<T>[]` prop. Do not add one. If you want that, build a separate primitive (eventually `Table/DataTable`, react-table-backed).
|
|
26
|
+
- **Not for production tables with sorting / filtering / selection / virtualization / sticky headers / server-side pagination.** Those are a different primitive, not yet built.
|
|
27
|
+
- **Not exhaustive or type-safe across columns.** The caller can emit a row with the wrong number of cells and the table will render it. That's acceptable for "quick."
|
|
28
|
+
|
|
29
|
+
If you feel the pull to add `sortable`, `onRowClick`, `selectable`, `stickyHeader`, `virtualized`, `pageSize`, or `columnDef` — stop. That's the signal to build a new primitive (e.g. `DataTable`), not to grow this one.
|
|
30
|
+
|
|
31
|
+
## Why the JSX-children pattern
|
|
32
|
+
|
|
33
|
+
Two reasons:
|
|
34
|
+
|
|
35
|
+
1. **Reads like DOM.** A reader understands what renders without translating a column config in their head. `<QuickTableRow>{a}{b}</QuickTableRow>` is one line; the data-driven equivalent is a column array plus a row array plus a render function.
|
|
36
|
+
2. **Zero friction for ad-hoc content.** Mixing literal strings, `<Badge>` components, `<em>` tags, and formatted numbers in the same row is just JSX. No `render: (row) => …` callbacks, no column configs.
|
|
37
|
+
|
|
38
|
+
The cost (no per-column typing, no shared formatting) is the right trade-off for quick tables. The upgrade path when the cost stops being worth it is "reach for a different primitive," not "grow this one."
|
|
39
|
+
|
|
40
|
+
## Auto-wrap mechanics
|
|
41
|
+
|
|
42
|
+
`wrapList(children, Wrapper)` iterates children and:
|
|
43
|
+
|
|
44
|
+
- Keeps nodes that are already the right wrapper (matched by either reference equality on `element.type`, or by a symbol-brand fallback that survives re-export / HOC wrapping).
|
|
45
|
+
- Wraps everything else into `<Wrapper>` with the iteration index as `key`.
|
|
46
|
+
|
|
47
|
+
Applied at two levels:
|
|
48
|
+
|
|
49
|
+
- Inside `QuickTable`: `wrapList(children, QuickTableRow)` — raw children become rows.
|
|
50
|
+
- Inside `QuickTableRow`: `wrapList(children, QuickTableCell)` — raw children become cells.
|
|
51
|
+
|
|
52
|
+
Applied once on the header side:
|
|
53
|
+
|
|
54
|
+
- Inside `QuickTableHeaderRow`: `wrapList(children, QuickTableHeaderCell)` — raw children become header cells. The `headerCells` prop on `QuickTable` is sugar for wrapping an array inside a `QuickTableHeaderRow`.
|
|
55
|
+
|
|
56
|
+
Every wrapper (`QuickTableRow`, `QuickTableCell`, `QuickTableHeaderRow`, `QuickTableHeaderCell`) is branded on export via `brandQuickTableType`. Keep that call when adding new wrappers — without it, re-exports through a barrel or a `React.memo` wrapper break the "already-the-right-thing" check and the component gets re-wrapped into another layer of itself (which renders as an invalid table).
|
|
57
|
+
|
|
58
|
+
## Four valid forms
|
|
59
|
+
|
|
60
|
+
All of these produce the same 3-row × 2-column body:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// Fully verbose
|
|
64
|
+
<QuickTable>
|
|
65
|
+
<QuickTableRow>
|
|
66
|
+
<QuickTableCell>Alice</QuickTableCell>
|
|
67
|
+
<QuickTableCell>12000</QuickTableCell>
|
|
68
|
+
</QuickTableRow>
|
|
69
|
+
{/* … */}
|
|
70
|
+
</QuickTable>
|
|
71
|
+
|
|
72
|
+
// Cells auto-wrap
|
|
73
|
+
<QuickTable>
|
|
74
|
+
<QuickTableRow>Alice{12000}</QuickTableRow>
|
|
75
|
+
{/* … */}
|
|
76
|
+
</QuickTable>
|
|
77
|
+
|
|
78
|
+
// Mix: one explicit cell (for align/width), one auto-wrapped
|
|
79
|
+
<QuickTable>
|
|
80
|
+
<QuickTableRow>
|
|
81
|
+
Alice
|
|
82
|
+
<QuickTableCell align="right">12000</QuickTableCell>
|
|
83
|
+
</QuickTableRow>
|
|
84
|
+
{/* … */}
|
|
85
|
+
</QuickTable>
|
|
86
|
+
|
|
87
|
+
// Single-cell rows via table-level auto-wrap (each child becomes a 1-cell row)
|
|
88
|
+
<QuickTable>{['Alice', 'Bob', 'Charlie']}</QuickTable>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## React.Children.map flattening caveat
|
|
92
|
+
|
|
93
|
+
`wrapList` uses `React.Children.map`, which flattens nested arrays and fragments. Consequence:
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
// DOES NOT produce a row of [Alice, 12000]. The outer array flattens.
|
|
97
|
+
<QuickTable>{[['Alice', 12000], ['Bob', 8500]]}</QuickTable>
|
|
98
|
+
// Renders 4 single-cell rows: Alice / 12000 / Bob / 8500
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
To get multi-cell rows from raw data, wrap each row in `QuickTableRow` explicitly (as in the verbose / sugar forms above). This is a deliberate limitation — if you're already writing nested arrays, you've likely outgrown `QuickTable`.
|
|
102
|
+
|
|
103
|
+
## What you can add safely
|
|
104
|
+
|
|
105
|
+
Stay in the "reads like DOM" spirit. Props that describe one cell / one row / the table as a whole are fine:
|
|
106
|
+
|
|
107
|
+
- Per-cell: `width`, `align`, `colSpan`, `rowSpan` (if needed).
|
|
108
|
+
- Per-row: `onClick`, `isSelected`, `tone` (if needed).
|
|
109
|
+
- Table-level: styling knobs (`background` via `BackgroundToken`, `density`) — but only if a concrete use case demands them. Already shipped: `title` (rendered as `<caption>`), `hasColumnDividers` (border-left between cells via adjacent-sibling CSS).
|
|
110
|
+
|
|
111
|
+
What's NOT safe to add: anything shaped like "configure N columns in one place" (`columnAlign: ('left' | 'right')[]`, `columnWidths: number[]`, `columnRenderers: {…}`). Those betray the premise. If a caller needs column-level control, they can set `align` / `width` on each `QuickTableHeaderCell` and each `QuickTableCell` directly — verbose, but honest.
|
|
112
|
+
|
|
113
|
+
## Style rules (inherited from ui-components)
|
|
114
|
+
|
|
115
|
+
- No `className` prop, no HTML attribute pass-through, no `aria-*` at call sites.
|
|
116
|
+
- Booleans use `is*` / `can*` / `has*`.
|
|
117
|
+
- CSS module only; colors via `--ui-*` tokens.
|
|
118
|
+
- Barrel is `src/Table/QuickTable/index.tsx`. External imports go through `@teamwork/ui-components` (or `../Table/QuickTable` within the package); siblings inside this folder import each other directly, not through the barrel.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import {
|
|
3
|
+
QuickTable,
|
|
4
|
+
QuickTableCell,
|
|
5
|
+
QuickTableHeaderCell,
|
|
6
|
+
QuickTableHeaderRow,
|
|
7
|
+
QuickTableRow,
|
|
8
|
+
} from './index';
|
|
9
|
+
|
|
10
|
+
const meta: Meta<typeof QuickTable> = {
|
|
11
|
+
title: 'Tables/QuickTable',
|
|
12
|
+
component: QuickTable,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof QuickTable>;
|
|
17
|
+
|
|
18
|
+
const rows = [
|
|
19
|
+
{ id: 'a', name: 'Alice', role: 'Engineer', amount: 12_000 },
|
|
20
|
+
{ id: 'b', name: 'Bob', role: 'Designer', amount: 8_500 },
|
|
21
|
+
{ id: 'c', name: 'Charlie', role: 'PM', amount: 15_250 },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const Verbose: Story = {
|
|
25
|
+
name: 'Verbose — one cell per td',
|
|
26
|
+
render: () => (
|
|
27
|
+
<QuickTable
|
|
28
|
+
headerRow={
|
|
29
|
+
<QuickTableHeaderRow>
|
|
30
|
+
<QuickTableHeaderCell>Name</QuickTableHeaderCell>
|
|
31
|
+
<QuickTableHeaderCell>Role</QuickTableHeaderCell>
|
|
32
|
+
<QuickTableHeaderCell align="right">Amount</QuickTableHeaderCell>
|
|
33
|
+
</QuickTableHeaderRow>
|
|
34
|
+
}
|
|
35
|
+
>
|
|
36
|
+
{rows.map((r) => (
|
|
37
|
+
<QuickTableRow key={r.id}>
|
|
38
|
+
<QuickTableCell>{r.name}</QuickTableCell>
|
|
39
|
+
<QuickTableCell>{r.role}</QuickTableCell>
|
|
40
|
+
<QuickTableCell align="right">{r.amount.toLocaleString()}</QuickTableCell>
|
|
41
|
+
</QuickTableRow>
|
|
42
|
+
))}
|
|
43
|
+
</QuickTable>
|
|
44
|
+
),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const HeaderCellsSugar: Story = {
|
|
48
|
+
name: 'headerCells sugar + cell auto-wrap',
|
|
49
|
+
render: () => (
|
|
50
|
+
<QuickTable headerCells={['Name', 'Role', 'Amount']}>
|
|
51
|
+
{rows.map((r) => (
|
|
52
|
+
<QuickTableRow key={r.id}>
|
|
53
|
+
{r.name}
|
|
54
|
+
{r.role}
|
|
55
|
+
{r.amount.toLocaleString()}
|
|
56
|
+
</QuickTableRow>
|
|
57
|
+
))}
|
|
58
|
+
</QuickTable>
|
|
59
|
+
),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const AutoWrapEverything: Story = {
|
|
63
|
+
name: 'Fully terse — row auto-wrap and cell auto-wrap',
|
|
64
|
+
render: () => (
|
|
65
|
+
<QuickTable headerCells={['id', 'name']}>
|
|
66
|
+
{rows.map((r) => (
|
|
67
|
+
<QuickTableRow key={r.id}>
|
|
68
|
+
{r.id}
|
|
69
|
+
{r.name}
|
|
70
|
+
</QuickTableRow>
|
|
71
|
+
))}
|
|
72
|
+
</QuickTable>
|
|
73
|
+
),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const MixedLiteralAndWrapper: Story = {
|
|
77
|
+
name: 'Mix: some cells wrapped explicitly, others auto-wrapped',
|
|
78
|
+
render: () => (
|
|
79
|
+
<QuickTable headerCells={['Name', 'Role', 'Amount']}>
|
|
80
|
+
{rows.map((r) => (
|
|
81
|
+
<QuickTableRow key={r.id}>
|
|
82
|
+
{r.name}
|
|
83
|
+
<QuickTableCell>
|
|
84
|
+
<em>{r.role}</em>
|
|
85
|
+
</QuickTableCell>
|
|
86
|
+
<QuickTableCell align="right">
|
|
87
|
+
${r.amount.toLocaleString()}
|
|
88
|
+
</QuickTableCell>
|
|
89
|
+
</QuickTableRow>
|
|
90
|
+
))}
|
|
91
|
+
</QuickTable>
|
|
92
|
+
),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const SingleCellRow: Story = {
|
|
96
|
+
name: 'Single-cell rows via direct table children',
|
|
97
|
+
render: () => (
|
|
98
|
+
<QuickTable headerCells={['Notice']}>
|
|
99
|
+
{rows.map((r) => r.name)}
|
|
100
|
+
</QuickTable>
|
|
101
|
+
),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const TitleAndColumnDividers: Story = {
|
|
105
|
+
name: 'title + hasColumnDividers',
|
|
106
|
+
render: () => (
|
|
107
|
+
<QuickTable
|
|
108
|
+
title="Q3 totals"
|
|
109
|
+
hasColumnDividers
|
|
110
|
+
headerCells={['Name', 'Role', 'Amount']}
|
|
111
|
+
>
|
|
112
|
+
{rows.map((r) => (
|
|
113
|
+
<QuickTableRow key={r.id}>
|
|
114
|
+
{r.name}
|
|
115
|
+
{r.role}
|
|
116
|
+
<QuickTableCell align="right">{r.amount.toLocaleString()}</QuickTableCell>
|
|
117
|
+
</QuickTableRow>
|
|
118
|
+
))}
|
|
119
|
+
</QuickTable>
|
|
120
|
+
),
|
|
121
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import { brandQuickTableType, wrapList } from './internal';
|
|
3
|
+
import type {
|
|
4
|
+
QuickTableCellAlign,
|
|
5
|
+
QuickTableCellProps,
|
|
6
|
+
QuickTableHeaderCellProps,
|
|
7
|
+
QuickTableHeaderRowProps,
|
|
8
|
+
QuickTableProps,
|
|
9
|
+
QuickTableRowProps,
|
|
10
|
+
} from './types';
|
|
11
|
+
import { cx } from '../../../utils';
|
|
12
|
+
import styles from './styles.module.css';
|
|
13
|
+
|
|
14
|
+
const alignClass = (align: QuickTableCellAlign | undefined): string | undefined => {
|
|
15
|
+
if (align === 'right') return styles.alignRight;
|
|
16
|
+
if (align === 'center') return styles.alignCenter;
|
|
17
|
+
return undefined;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const widthStyle = (
|
|
21
|
+
width: string | number | undefined,
|
|
22
|
+
): CSSProperties | undefined => {
|
|
23
|
+
if (width == null) return undefined;
|
|
24
|
+
return { width: typeof width === 'number' ? `${width}px` : width };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const QuickTableCell = (props: QuickTableCellProps) => {
|
|
28
|
+
const { children, width, align } = props;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<td className={cx(styles.cell, alignClass(align))} style={widthStyle(width)}>
|
|
32
|
+
{children}
|
|
33
|
+
</td>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
brandQuickTableType(QuickTableCell, 'QuickTableCell');
|
|
37
|
+
|
|
38
|
+
export const QuickTableRow = (props: QuickTableRowProps) => {
|
|
39
|
+
const { children } = props;
|
|
40
|
+
|
|
41
|
+
return <tr className={styles.row}>{wrapList(children, QuickTableCell)}</tr>;
|
|
42
|
+
};
|
|
43
|
+
brandQuickTableType(QuickTableRow, 'QuickTableRow');
|
|
44
|
+
|
|
45
|
+
export const QuickTableHeaderCell = (props: QuickTableHeaderCellProps) => {
|
|
46
|
+
const { children, width, align } = props;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<th className={cx(styles.headerCell, alignClass(align))} style={widthStyle(width)}>
|
|
50
|
+
{children}
|
|
51
|
+
</th>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
brandQuickTableType(QuickTableHeaderCell, 'QuickTableHeaderCell');
|
|
55
|
+
|
|
56
|
+
export const QuickTableHeaderRow = (props: QuickTableHeaderRowProps) => {
|
|
57
|
+
const { children } = props;
|
|
58
|
+
|
|
59
|
+
return <tr className={styles.headerRow}>{wrapList(children, QuickTableHeaderCell)}</tr>;
|
|
60
|
+
};
|
|
61
|
+
brandQuickTableType(QuickTableHeaderRow, 'QuickTableHeaderRow');
|
|
62
|
+
|
|
63
|
+
export const QuickTable = (props: QuickTableProps) => {
|
|
64
|
+
const { children, headerCells, headerRow, title, hasColumnDividers } = props;
|
|
65
|
+
|
|
66
|
+
const header =
|
|
67
|
+
headerRow ??
|
|
68
|
+
(headerCells ? <QuickTableHeaderRow>{headerCells}</QuickTableHeaderRow> : undefined);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<table className={cx(styles.table, hasColumnDividers && styles.tableWithColumnDividers)}>
|
|
72
|
+
{title ? <caption className={styles.caption}>{title}</caption> : null}
|
|
73
|
+
{header ? <thead>{header}</thead> : null}
|
|
74
|
+
<tbody>{wrapList(children, QuickTableRow)}</tbody>
|
|
75
|
+
</table>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type {
|
|
80
|
+
QuickTableProps,
|
|
81
|
+
QuickTableRowProps,
|
|
82
|
+
QuickTableCellProps,
|
|
83
|
+
QuickTableHeaderRowProps,
|
|
84
|
+
QuickTableHeaderCellProps,
|
|
85
|
+
QuickTableCellAlign,
|
|
86
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Children, isValidElement } from 'react';
|
|
2
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
// Each wrapper component carries a string tag at this symbol. Auto-wrap
|
|
5
|
+
// uses either reference equality on the component type (the common case)
|
|
6
|
+
// or tag equality (survives barrel re-export / HOC wrapping that breaks
|
|
7
|
+
// reference identity).
|
|
8
|
+
const BRAND = Symbol('ui-components.QuickTable.brand');
|
|
9
|
+
|
|
10
|
+
export const brandQuickTableType = <T,>(component: T, tag: string): T => {
|
|
11
|
+
if (component) {
|
|
12
|
+
(component as unknown as Record<symbol, string>)[BRAND] = tag;
|
|
13
|
+
}
|
|
14
|
+
return component;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const readBrand = (value: unknown): string | undefined => {
|
|
18
|
+
if (!value || (typeof value !== 'function' && typeof value !== 'object')) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return (value as Record<symbol, string | undefined>)[BRAND];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const isAlready = (
|
|
25
|
+
element: unknown,
|
|
26
|
+
Wrapper: ComponentType<{ children: ReactNode }>,
|
|
27
|
+
): boolean => {
|
|
28
|
+
if (!isValidElement(element)) return false;
|
|
29
|
+
if (element.type === Wrapper) return true;
|
|
30
|
+
const elementBrand = readBrand(element.type);
|
|
31
|
+
if (!elementBrand) return false;
|
|
32
|
+
return elementBrand === readBrand(Wrapper);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Iterates `children` and wraps any node that isn't already a `Wrapper` into
|
|
37
|
+
* one. This is what lets callers write `<QuickTableRow>{a}{b}</QuickTableRow>`
|
|
38
|
+
* instead of `<QuickTableRow><QuickTableCell>{a}</QuickTableCell>...`.
|
|
39
|
+
*/
|
|
40
|
+
export const wrapList = (
|
|
41
|
+
children: ReactNode,
|
|
42
|
+
Wrapper: ComponentType<{ children: ReactNode }>,
|
|
43
|
+
): ReactNode => {
|
|
44
|
+
return Children.map(children, (child, index) => {
|
|
45
|
+
if (isAlready(child, Wrapper)) return child;
|
|
46
|
+
return <Wrapper key={index}>{child}</Wrapper>;
|
|
47
|
+
});
|
|
48
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
.table {
|
|
2
|
+
width: 100%;
|
|
3
|
+
border-collapse: collapse;
|
|
4
|
+
font-size: var(--ui-text-small);
|
|
5
|
+
color: var(--ui-foreground);
|
|
6
|
+
background-color: var(--ui-background-0);
|
|
7
|
+
border: 1px solid var(--ui-border-subtle);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.headerRow {
|
|
11
|
+
background-color: var(--ui-background-1);
|
|
12
|
+
border-bottom: 1px solid var(--ui-border);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.headerCell {
|
|
16
|
+
text-align: left;
|
|
17
|
+
vertical-align: top;
|
|
18
|
+
font-weight: 500;
|
|
19
|
+
color: var(--ui-muted);
|
|
20
|
+
font-size: var(--ui-text-xsmall);
|
|
21
|
+
text-transform: uppercase;
|
|
22
|
+
letter-spacing: 0.03em;
|
|
23
|
+
padding: var(--ui-space-1) var(--ui-space-2);
|
|
24
|
+
white-space: nowrap;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.row {
|
|
28
|
+
border-bottom: 1px solid var(--ui-border-subtle);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.row:nth-child(even) {
|
|
32
|
+
background-color: var(--ui-background-0-offset);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.row:last-child {
|
|
36
|
+
border-bottom: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.cell {
|
|
40
|
+
text-align: left;
|
|
41
|
+
vertical-align: top;
|
|
42
|
+
padding: var(--ui-space-1) var(--ui-space-2);
|
|
43
|
+
line-height: var(--ui-line-height-tight);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.alignRight {
|
|
47
|
+
text-align: right;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.alignCenter {
|
|
51
|
+
text-align: center;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.caption {
|
|
55
|
+
caption-side: top;
|
|
56
|
+
text-align: left;
|
|
57
|
+
padding: var(--ui-space-1) var(--ui-space-2);
|
|
58
|
+
color: var(--ui-foreground);
|
|
59
|
+
font-weight: 500;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.tableWithColumnDividers .cell + .cell,
|
|
63
|
+
.tableWithColumnDividers .headerCell + .headerCell {
|
|
64
|
+
border-left: 1px solid var(--ui-border-subtle);
|
|
65
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type QuickTableCellAlign = 'left' | 'right' | 'center';
|
|
4
|
+
|
|
5
|
+
export type QuickTableProps = {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
/**
|
|
8
|
+
* Sugar for the most common header shape: an array of cell contents. Each
|
|
9
|
+
* entry is wrapped into a `QuickTableHeaderCell` inside a
|
|
10
|
+
* `QuickTableHeaderRow`. For a custom row (column spans, per-cell widths,
|
|
11
|
+
* actions), pass `headerRow` instead.
|
|
12
|
+
*/
|
|
13
|
+
headerCells?: ReactNode[];
|
|
14
|
+
/** Full control over the header row. Overrides `headerCells` when both are set. */
|
|
15
|
+
headerRow?: ReactNode;
|
|
16
|
+
/** Optional title rendered above the table as a `<caption>` element. */
|
|
17
|
+
title?: ReactNode;
|
|
18
|
+
/** Draw vertical divider lines between columns. Default: false. */
|
|
19
|
+
hasColumnDividers?: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type QuickTableRowProps = {
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type QuickTableCellProps = {
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
width?: string | number;
|
|
29
|
+
align?: QuickTableCellAlign;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type QuickTableHeaderRowProps = {
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type QuickTableHeaderCellProps = {
|
|
37
|
+
children: ReactNode;
|
|
38
|
+
width?: string | number;
|
|
39
|
+
align?: QuickTableCellAlign;
|
|
40
|
+
};
|
package/src/env.d.ts
ADDED