@terpjs/react-core 0.7.0 → 0.8.0
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/package.json +2 -2
- package/src/AppShell.test.tsx +33 -12
- package/src/AppShell.tsx +69 -249
- package/src/Breadcrumbs.test.tsx +24 -0
- package/src/Breadcrumbs.tsx +9 -32
- package/src/ConfirmDialog.tsx +13 -44
- package/src/EmptyState.tsx +8 -36
- package/src/ErrorState.tsx +8 -36
- package/src/Field.test.tsx +57 -0
- package/src/Field.tsx +46 -22
- package/src/HubPage.test.tsx +22 -13
- package/src/HubPage.tsx +25 -97
- package/src/LoadingState.tsx +3 -24
- package/src/PageActions.tsx +5 -10
- package/src/UserMenu.test.tsx +12 -5
- package/src/UserMenu.tsx +33 -62
- package/src/dataview/DataView.test.tsx +109 -5
- package/src/dataview/DataView.tsx +41 -23
- package/src/dataview/DataViewCardList.tsx +14 -60
- package/src/dataview/DataViewColumnSettings.tsx +46 -51
- package/src/dataview/DataViewExpandableRow.tsx +2 -17
- package/src/dataview/DataViewPagination.tsx +2 -32
- package/src/dataview/DataViewRowActions.tsx +13 -33
- package/src/dataview/DataViewTable.tsx +16 -103
- package/src/dataview/DataViewToolbar.tsx +53 -76
- package/src/dataview/README.md +6 -0
- package/src/dataview/index.ts +1 -0
- package/src/dataview/internal.tsx +4 -1
- package/src/dataview/types.ts +13 -0
- package/src/feedback.test.tsx +26 -0
- package/src/files.test.tsx +18 -0
- package/src/files.tsx +13 -4
- package/src/icons.test.tsx +10 -6
- package/src/icons.tsx +9 -37
- package/src/index.ts +0 -3
- package/src/layout.test.tsx +24 -9
- package/src/layout.tsx +24 -21
- package/src/layoutContract.test.tsx +95 -0
- package/src/locale.tsx +24 -4
- package/src/markers.test.ts +242 -19
- package/src/router.tsx +6 -9
- package/src/ssr.test.tsx +1 -3
- package/src/styles.test.ts +823 -6
- package/src/styles.ts +2699 -153
- package/src/theme.tsx +24 -3
- package/src/toast.tsx +35 -71
- package/src/ui/Alert.test.tsx +12 -0
- package/src/ui/Alert.tsx +15 -43
- package/src/ui/Badge.test.tsx +14 -3
- package/src/ui/Badge.tsx +9 -28
- package/src/ui/Button.test.tsx +17 -4
- package/src/ui/Button.tsx +10 -63
- package/src/ui/Card.test.tsx +6 -2
- package/src/ui/Card.tsx +11 -39
- package/src/ui/Checkbox.tsx +2 -19
- package/src/ui/Combobox.test.tsx +22 -0
- package/src/ui/Combobox.tsx +31 -80
- package/src/ui/DatePicker.test.tsx +131 -4
- package/src/ui/DatePicker.tsx +158 -106
- package/src/ui/Input.tsx +6 -19
- package/src/ui/Markdown.test.tsx +26 -0
- package/src/ui/Markdown.tsx +28 -2
- package/src/ui/Menu.test.tsx +38 -4
- package/src/ui/Menu.tsx +50 -52
- package/src/ui/Popover.tsx +53 -19
- package/src/ui/Radio.tsx +5 -30
- package/src/ui/Select.tsx +7 -30
- package/src/ui/Switch.tsx +2 -20
- package/src/ui/Tabs.tsx +4 -28
- package/src/ui/Textarea.tsx +6 -17
- package/src/ui/Tooltip.tsx +9 -21
- package/src/ui/controlStyles.ts +0 -9
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { useId } from "react";
|
|
2
|
+
|
|
3
|
+
import { Popover } from "../ui/Popover";
|
|
1
4
|
import { ArrowDownGlyph, ArrowUpGlyph, ColumnsGlyph } from "./glyphs";
|
|
2
|
-
import {
|
|
5
|
+
import { useDataViewText } from "./internal";
|
|
3
6
|
import type { DataViewColumn } from "./types";
|
|
4
7
|
|
|
5
8
|
export interface DataViewColumnSettingsProps<T> {
|
|
@@ -8,68 +11,72 @@ export interface DataViewColumnSettingsProps<T> {
|
|
|
8
11
|
columnVisibility: Record<string, boolean>;
|
|
9
12
|
onColumnVisibleChange: (columnId: string, visible: boolean) => void;
|
|
10
13
|
onMoveColumn: (columnId: string, direction: -1 | 1) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Render the panel open on mount — the same escape the menu primitive already exposes,
|
|
16
|
+
* and the only way a portalled panel reaches a per-specimen visual lane.
|
|
17
|
+
*/
|
|
18
|
+
defaultOpen?: boolean;
|
|
11
19
|
}
|
|
12
20
|
|
|
13
21
|
/**
|
|
14
|
-
* The "view options"
|
|
22
|
+
* The "view options" panel: per-column show/hide checkboxes and up/down reordering.
|
|
15
23
|
* Only user columns appear — the pinned system columns (select/expand/actions) are
|
|
16
24
|
* never hideable or reorderable, and are skipped when computing reorder targets
|
|
17
25
|
* because this list simply does not contain them.
|
|
26
|
+
*
|
|
27
|
+
* A `Popover` rather than a `Menu`, and the distinction is a correctness one rather than
|
|
28
|
+
* a preference. `Menu` stamps `role="menu"`, which ARIA requires to own only
|
|
29
|
+
* `menuitem` / `menuitemradio` / `menuitemcheckbox` / `group` children — and this panel's
|
|
30
|
+
* content is a heading, labelled checkboxes and paired reorder buttons, which is a small
|
|
31
|
+
* FORM. Feeding it to a menu produced a critical `aria-required-children` violation in
|
|
32
|
+
* every theme, and there is no arrangement that fixes it while keeping the interaction:
|
|
33
|
+
* reorder buttons inside a `menuitemcheckbox` would be interactive descendants of a menu
|
|
34
|
+
* item, which ARIA forbids in turn. The panel is therefore a group labelled by its own
|
|
35
|
+
* heading, navigated with Tab the way a form is, and `Popover` supplies the Escape,
|
|
36
|
+
* outside-click and focus-return behaviour `Menu` was being used for.
|
|
37
|
+
*
|
|
38
|
+
* Nothing had rendered this panel open before, which is why a shipped critical defect was
|
|
39
|
+
* invisible: the screenshot lane clips to the specimen and the panel portals to the body,
|
|
40
|
+
* and no specimen opened it at all.
|
|
18
41
|
*/
|
|
19
42
|
export function DataViewColumnSettings<T>({
|
|
20
43
|
columns,
|
|
21
44
|
columnVisibility,
|
|
22
45
|
onColumnVisibleChange,
|
|
23
46
|
onMoveColumn,
|
|
47
|
+
defaultOpen,
|
|
24
48
|
}: DataViewColumnSettingsProps<T>) {
|
|
25
49
|
const { strings, resolve } = useDataViewText();
|
|
50
|
+
const headingId = useId();
|
|
26
51
|
|
|
27
52
|
return (
|
|
28
|
-
<
|
|
53
|
+
<Popover
|
|
54
|
+
defaultOpen={defaultOpen}
|
|
55
|
+
focusOnOpen
|
|
56
|
+
data-owner="dataview-column-settings"
|
|
29
57
|
trigger={
|
|
30
|
-
|
|
58
|
+
// The trigger keeps the menu-trigger marker, which is styling vocabulary rather than
|
|
59
|
+
// a semantic claim — the sheet describes it as the package's outlined control, and
|
|
60
|
+
// reusing it is what keeps this control's appearance identical. What it does NOT keep
|
|
61
|
+
// is aria-haspopup="menu", because the panel is no longer a menu; Popover supplies
|
|
62
|
+
// aria-expanded and aria-controls, which is the whole contract for a disclosure.
|
|
63
|
+
<button type="button" data-terp="menu-trigger">
|
|
31
64
|
<ColumnsGlyph />
|
|
32
|
-
<span
|
|
33
|
-
|
|
65
|
+
<span>{resolve(strings.viewOptions)}</span>
|
|
66
|
+
</button>
|
|
34
67
|
}
|
|
35
|
-
triggerLabel={resolve(strings.viewOptions)}
|
|
36
68
|
>
|
|
37
69
|
{() => (
|
|
38
|
-
<div
|
|
39
|
-
<div
|
|
40
|
-
style={{
|
|
41
|
-
padding: "var(--space-1) var(--space-2)",
|
|
42
|
-
fontSize: "var(--font-size-sm)",
|
|
43
|
-
fontWeight: "var(--font-weight-medium)" as never,
|
|
44
|
-
color: "var(--color-neutral-500)",
|
|
45
|
-
}}
|
|
46
|
-
>
|
|
70
|
+
<div data-terp="dataview-column-settings" role="group" aria-labelledby={headingId}>
|
|
71
|
+
<div id={headingId} data-terp="dataview-column-settings-title">
|
|
47
72
|
{resolve(strings.columns)}
|
|
48
73
|
</div>
|
|
49
74
|
{columns.map((column, index) => {
|
|
50
75
|
const label = resolve(column.meta?.label ?? column.header);
|
|
51
76
|
const visible = columnVisibility[column.id] !== false;
|
|
52
77
|
return (
|
|
53
|
-
<div
|
|
54
|
-
|
|
55
|
-
style={{
|
|
56
|
-
display: "flex",
|
|
57
|
-
alignItems: "center",
|
|
58
|
-
gap: "var(--space-2)",
|
|
59
|
-
padding: "var(--space-1) var(--space-2)",
|
|
60
|
-
borderRadius: "var(--radius-sm)",
|
|
61
|
-
}}
|
|
62
|
-
>
|
|
63
|
-
<label
|
|
64
|
-
style={{
|
|
65
|
-
display: "flex",
|
|
66
|
-
alignItems: "center",
|
|
67
|
-
gap: "var(--space-2)",
|
|
68
|
-
flex: 1,
|
|
69
|
-
cursor: "pointer",
|
|
70
|
-
fontSize: "var(--font-size-sm)",
|
|
71
|
-
}}
|
|
72
|
-
>
|
|
78
|
+
<div key={column.id} data-terp="dataview-column-option">
|
|
79
|
+
<label>
|
|
73
80
|
<input
|
|
74
81
|
type="checkbox"
|
|
75
82
|
checked={visible}
|
|
@@ -80,19 +87,19 @@ export function DataViewColumnSettings<T>({
|
|
|
80
87
|
</label>
|
|
81
88
|
<button
|
|
82
89
|
type="button"
|
|
90
|
+
data-terp="iconbutton"
|
|
83
91
|
aria-label={`${resolve(strings.moveUp)}: ${label}`}
|
|
84
92
|
disabled={index === 0}
|
|
85
93
|
onClick={() => onMoveColumn(column.id, -1)}
|
|
86
|
-
style={reorderButtonStyle(index === 0)}
|
|
87
94
|
>
|
|
88
95
|
<ArrowUpGlyph size={14} />
|
|
89
96
|
</button>
|
|
90
97
|
<button
|
|
91
98
|
type="button"
|
|
99
|
+
data-terp="iconbutton"
|
|
92
100
|
aria-label={`${resolve(strings.moveDown)}: ${label}`}
|
|
93
101
|
disabled={index === columns.length - 1}
|
|
94
102
|
onClick={() => onMoveColumn(column.id, 1)}
|
|
95
|
-
style={reorderButtonStyle(index === columns.length - 1)}
|
|
96
103
|
>
|
|
97
104
|
<ArrowDownGlyph size={14} />
|
|
98
105
|
</button>
|
|
@@ -101,18 +108,6 @@ export function DataViewColumnSettings<T>({
|
|
|
101
108
|
})}
|
|
102
109
|
</div>
|
|
103
110
|
)}
|
|
104
|
-
</
|
|
111
|
+
</Popover>
|
|
105
112
|
);
|
|
106
113
|
}
|
|
107
|
-
|
|
108
|
-
function reorderButtonStyle(disabled: boolean) {
|
|
109
|
-
return {
|
|
110
|
-
display: "inline-flex",
|
|
111
|
-
padding: "var(--space-1)",
|
|
112
|
-
background: "transparent",
|
|
113
|
-
border: "none",
|
|
114
|
-
borderRadius: "var(--radius-sm)",
|
|
115
|
-
cursor: disabled ? "not-allowed" : "pointer",
|
|
116
|
-
color: disabled ? "var(--color-neutral-300)" : "var(--color-neutral-700)",
|
|
117
|
-
} as const;
|
|
118
|
-
}
|
|
@@ -20,19 +20,11 @@ export function DataViewExpandToggle({
|
|
|
20
20
|
type="button"
|
|
21
21
|
aria-label={resolve(expanded ? strings.collapseRow : strings.expandRow)}
|
|
22
22
|
aria-expanded={expanded}
|
|
23
|
+
data-terp="iconbutton"
|
|
23
24
|
onClick={(event) => {
|
|
24
25
|
event.stopPropagation();
|
|
25
26
|
onToggle();
|
|
26
27
|
}}
|
|
27
|
-
style={{
|
|
28
|
-
display: "inline-flex",
|
|
29
|
-
padding: "var(--space-1)",
|
|
30
|
-
background: "transparent",
|
|
31
|
-
border: "none",
|
|
32
|
-
borderRadius: "var(--radius-sm)",
|
|
33
|
-
cursor: "pointer",
|
|
34
|
-
color: "var(--color-neutral-500)",
|
|
35
|
-
}}
|
|
36
28
|
>
|
|
37
29
|
{expanded ? <ChevronDownGlyph /> : <ChevronRightGlyph />}
|
|
38
30
|
</button>
|
|
@@ -52,14 +44,7 @@ export function DataViewExpandableRow({
|
|
|
52
44
|
}) {
|
|
53
45
|
return (
|
|
54
46
|
<tr>
|
|
55
|
-
<td
|
|
56
|
-
colSpan={colSpan}
|
|
57
|
-
style={{
|
|
58
|
-
padding: "var(--space-3) var(--space-4)",
|
|
59
|
-
background: "var(--color-neutral-50)",
|
|
60
|
-
borderBottom: "1px solid var(--color-neutral-200)",
|
|
61
|
-
}}
|
|
62
|
-
>
|
|
47
|
+
<td colSpan={colSpan} data-terp="dataview-expanded-cell">
|
|
63
48
|
{children}
|
|
64
49
|
</td>
|
|
65
50
|
</tr>
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { CSSProperties } from "react";
|
|
2
|
-
|
|
3
1
|
import { useDataViewText } from "./internal";
|
|
4
2
|
import {
|
|
5
3
|
PageFirstGlyph,
|
|
@@ -15,18 +13,6 @@ export interface DataViewPaginationProps {
|
|
|
15
13
|
onPaginationChange: (pagination: DataViewPaginationState) => void;
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
const pagerButtonStyle = (disabled: boolean): CSSProperties => ({
|
|
19
|
-
display: "inline-flex",
|
|
20
|
-
alignItems: "center",
|
|
21
|
-
minHeight: "2rem",
|
|
22
|
-
padding: "var(--space-1) var(--space-2)",
|
|
23
|
-
background: "var(--color-neutral-0)",
|
|
24
|
-
border: "1px solid var(--color-neutral-300)",
|
|
25
|
-
borderRadius: "var(--radius-md)",
|
|
26
|
-
cursor: disabled ? "not-allowed" : "pointer",
|
|
27
|
-
color: disabled ? "var(--color-neutral-300)" : "var(--color-neutral-700)",
|
|
28
|
-
});
|
|
29
|
-
|
|
30
16
|
/**
|
|
31
17
|
* The footer pagination bar: "X–Y of Z results", the current page / page count, and
|
|
32
18
|
* first / prev / next / last controls (disabled at bounds; page controls hidden when
|
|
@@ -49,22 +35,10 @@ export function DataViewPagination({
|
|
|
49
35
|
const atLast = pageIndex >= pageCount - 1;
|
|
50
36
|
|
|
51
37
|
return (
|
|
52
|
-
<div
|
|
53
|
-
style={{
|
|
54
|
-
display: "flex",
|
|
55
|
-
alignItems: "center",
|
|
56
|
-
justifyContent: "space-between",
|
|
57
|
-
gap: "var(--space-3)",
|
|
58
|
-
flexWrap: "wrap",
|
|
59
|
-
padding: "var(--space-2) var(--space-3)",
|
|
60
|
-
borderTop: "1px solid var(--color-neutral-200)",
|
|
61
|
-
fontSize: "var(--font-size-sm)",
|
|
62
|
-
color: "var(--color-neutral-500)",
|
|
63
|
-
}}
|
|
64
|
-
>
|
|
38
|
+
<div data-terp="dataview-pagination">
|
|
65
39
|
<span>{format(strings.resultsRange, { from, to, total: totalCount })}</span>
|
|
66
40
|
{pageCount > 1 && (
|
|
67
|
-
<span
|
|
41
|
+
<span data-terp="dataview-pager">
|
|
68
42
|
<span>{format(strings.pageOf, { page: pageIndex + 1, pages: pageCount })}</span>
|
|
69
43
|
<button
|
|
70
44
|
type="button"
|
|
@@ -72,7 +46,6 @@ export function DataViewPagination({
|
|
|
72
46
|
disabled={atFirst}
|
|
73
47
|
onClick={() => goTo(0)}
|
|
74
48
|
data-terp="iconbutton"
|
|
75
|
-
style={pagerButtonStyle(atFirst)}
|
|
76
49
|
>
|
|
77
50
|
<PageFirstGlyph />
|
|
78
51
|
</button>
|
|
@@ -82,7 +55,6 @@ export function DataViewPagination({
|
|
|
82
55
|
disabled={atFirst}
|
|
83
56
|
onClick={() => goTo(pageIndex - 1)}
|
|
84
57
|
data-terp="iconbutton"
|
|
85
|
-
style={pagerButtonStyle(atFirst)}
|
|
86
58
|
>
|
|
87
59
|
<PagePrevGlyph />
|
|
88
60
|
</button>
|
|
@@ -92,7 +64,6 @@ export function DataViewPagination({
|
|
|
92
64
|
disabled={atLast}
|
|
93
65
|
onClick={() => goTo(pageIndex + 1)}
|
|
94
66
|
data-terp="iconbutton"
|
|
95
|
-
style={pagerButtonStyle(atLast)}
|
|
96
67
|
>
|
|
97
68
|
<PageNextGlyph />
|
|
98
69
|
</button>
|
|
@@ -102,7 +73,6 @@ export function DataViewPagination({
|
|
|
102
73
|
disabled={atLast}
|
|
103
74
|
onClick={() => goTo(pageCount - 1)}
|
|
104
75
|
data-terp="iconbutton"
|
|
105
|
-
style={pagerButtonStyle(atLast)}
|
|
106
76
|
>
|
|
107
77
|
<PageLastGlyph />
|
|
108
78
|
</button>
|
|
@@ -14,6 +14,12 @@ export interface DataViewRowActionsProps<T> {
|
|
|
14
14
|
layout: DataViewRowActionsLayout;
|
|
15
15
|
/** On mobile all standard actions collapse into the menu regardless of flags. */
|
|
16
16
|
isMobile: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Render the overflow menu open on mount — the same escape `Menu`, `Popover` and both
|
|
19
|
+
* date pickers already expose, and for the same reason: a portalled panel is invisible
|
|
20
|
+
* to a per-specimen visual lane unless something can name its open state.
|
|
21
|
+
*/
|
|
22
|
+
defaultOpen?: boolean;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
function InlineActionButton<T>({ action, row }: { action: DataViewRowAction<T>; row: T }) {
|
|
@@ -23,30 +29,12 @@ function InlineActionButton<T>({ action, row }: { action: DataViewRowAction<T>;
|
|
|
23
29
|
return (
|
|
24
30
|
<button
|
|
25
31
|
type="button"
|
|
32
|
+
data-terp="dataview-inline-action"
|
|
33
|
+
data-destructive={destructive || undefined}
|
|
26
34
|
disabled={disabled}
|
|
27
35
|
onClick={() => action.onClick?.(row)}
|
|
28
|
-
style={{
|
|
29
|
-
font: "inherit",
|
|
30
|
-
fontSize: "var(--font-size-sm)",
|
|
31
|
-
display: "inline-flex",
|
|
32
|
-
alignItems: "center",
|
|
33
|
-
minHeight: "2rem",
|
|
34
|
-
gap: "var(--space-1)",
|
|
35
|
-
padding: "var(--space-1) var(--space-2)",
|
|
36
|
-
background: "transparent",
|
|
37
|
-
border: "1px solid var(--color-neutral-300)",
|
|
38
|
-
borderRadius: "var(--radius-md)",
|
|
39
|
-
cursor: disabled ? "not-allowed" : "pointer",
|
|
40
|
-
color: disabled
|
|
41
|
-
? "var(--color-neutral-300)"
|
|
42
|
-
: destructive
|
|
43
|
-
? "var(--color-status-danger)"
|
|
44
|
-
: "var(--color-neutral-700)",
|
|
45
|
-
}}
|
|
46
36
|
>
|
|
47
|
-
{action.icon !== undefined &&
|
|
48
|
-
<span aria-hidden style={{ display: "inline-flex" }}>{action.icon}</span>
|
|
49
|
-
)}
|
|
37
|
+
{action.icon !== undefined && <span aria-hidden>{action.icon}</span>}
|
|
50
38
|
{resolve(action.label)}
|
|
51
39
|
</button>
|
|
52
40
|
);
|
|
@@ -62,6 +50,7 @@ export function DataViewRowActions<T>({
|
|
|
62
50
|
actions,
|
|
63
51
|
layout,
|
|
64
52
|
isMobile,
|
|
53
|
+
defaultOpen,
|
|
65
54
|
}: DataViewRowActionsProps<T>) {
|
|
66
55
|
const { strings, resolve } = useDataViewText();
|
|
67
56
|
|
|
@@ -83,21 +72,11 @@ export function DataViewRowActions<T>({
|
|
|
83
72
|
const stop = (event: MouseEvent) => event.stopPropagation();
|
|
84
73
|
|
|
85
74
|
const renderCustom = (action: DataViewRowAction<T>, index: number): ReactNode => (
|
|
86
|
-
<span key={`custom-${index}`}
|
|
87
|
-
{action.render?.(row)}
|
|
88
|
-
</span>
|
|
75
|
+
<span key={`custom-${index}`}>{action.render?.(row)}</span>
|
|
89
76
|
);
|
|
90
77
|
|
|
91
78
|
return (
|
|
92
|
-
<span
|
|
93
|
-
onClick={stop}
|
|
94
|
-
style={{
|
|
95
|
-
display: "inline-flex",
|
|
96
|
-
alignItems: "center",
|
|
97
|
-
justifyContent: "flex-end",
|
|
98
|
-
gap: "var(--space-1)",
|
|
99
|
-
}}
|
|
100
|
-
>
|
|
79
|
+
<span data-terp="dataview-row-actions" onClick={stop}>
|
|
101
80
|
{custom.map(renderCustom)}
|
|
102
81
|
{inline.map((action, index) => (
|
|
103
82
|
<InlineActionButton key={`inline-${index}`} action={action} row={row} />
|
|
@@ -106,6 +85,7 @@ export function DataViewRowActions<T>({
|
|
|
106
85
|
<DataViewMenu
|
|
107
86
|
trigger={<EllipsisGlyph />}
|
|
108
87
|
triggerLabel={resolve(strings.moreActions)}
|
|
88
|
+
defaultOpen={defaultOpen}
|
|
109
89
|
>
|
|
110
90
|
{(close) => (
|
|
111
91
|
<>
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
import { injectTerpStyles } from "../styles";
|
|
5
5
|
import type { BadgeTone } from "../ui/Badge";
|
|
6
|
-
import { toneSoftColors } from "../ui/Badge";
|
|
7
6
|
import type { UiText } from "../uiText";
|
|
8
7
|
import { DataViewExpandToggle, DataViewExpandableRow } from "./DataViewExpandableRow";
|
|
9
8
|
import { DataViewRowActions } from "./DataViewRowActions";
|
|
@@ -46,41 +45,6 @@ export interface DataViewTableProps<T> {
|
|
|
46
45
|
rowActionsLayout: DataViewRowActionsLayout;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
const headerCellStyle: CSSProperties = {
|
|
50
|
-
position: "relative",
|
|
51
|
-
padding: "var(--space-2) var(--space-3)",
|
|
52
|
-
textAlign: "left",
|
|
53
|
-
fontSize: "var(--font-size-xs)",
|
|
54
|
-
fontWeight: "var(--font-weight-semibold)" as never,
|
|
55
|
-
color: "var(--color-neutral-500)",
|
|
56
|
-
textTransform: "uppercase",
|
|
57
|
-
letterSpacing: "0.04em",
|
|
58
|
-
borderBottom: "1px solid var(--color-neutral-200)",
|
|
59
|
-
whiteSpace: "nowrap",
|
|
60
|
-
background: "var(--color-neutral-0)",
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const bodyCellStyle: CSSProperties = {
|
|
64
|
-
padding: "var(--space-3)",
|
|
65
|
-
borderBottom: "1px solid var(--color-neutral-100)",
|
|
66
|
-
fontSize: "var(--font-size-sm)",
|
|
67
|
-
color: "var(--color-neutral-900)",
|
|
68
|
-
overflow: "hidden",
|
|
69
|
-
textOverflow: "ellipsis",
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const recordButtonStyle: CSSProperties = {
|
|
73
|
-
position: "absolute",
|
|
74
|
-
width: 1,
|
|
75
|
-
height: 1,
|
|
76
|
-
padding: 0,
|
|
77
|
-
margin: -1,
|
|
78
|
-
overflow: "hidden",
|
|
79
|
-
clip: "rect(0 0 0 0)",
|
|
80
|
-
whiteSpace: "nowrap",
|
|
81
|
-
border: 0,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
48
|
/**
|
|
85
49
|
* The table layout of {@link DataView}: sortable, resizable headers; system columns
|
|
86
50
|
* (expand → select → user columns → actions); row click, selection, expansion.
|
|
@@ -166,17 +130,13 @@ export function DataViewTable<T>(props: DataViewTableProps<T>) {
|
|
|
166
130
|
<table
|
|
167
131
|
ref={tableRef}
|
|
168
132
|
data-terp="dataview-table"
|
|
169
|
-
|
|
170
|
-
width: "100%",
|
|
171
|
-
borderCollapse: "collapse",
|
|
172
|
-
tableLayout: liveSizing !== null ? "fixed" : "auto",
|
|
173
|
-
}}
|
|
133
|
+
data-resizing={liveSizing !== null || undefined}
|
|
174
134
|
>
|
|
175
135
|
<thead>
|
|
176
136
|
<tr>
|
|
177
|
-
{hasExpand && <th
|
|
137
|
+
{hasExpand && <th data-terp="dataview-expand-cell" aria-hidden />}
|
|
178
138
|
{props.selectionEnabled && (
|
|
179
|
-
<th
|
|
139
|
+
<th data-terp="dataview-select-cell">
|
|
180
140
|
<input
|
|
181
141
|
ref={selectAllRef}
|
|
182
142
|
type="checkbox"
|
|
@@ -197,27 +157,17 @@ export function DataViewTable<T>(props: DataViewTableProps<T>) {
|
|
|
197
157
|
aria-sort={
|
|
198
158
|
sort === undefined ? undefined : sort.desc ? "descending" : "ascending"
|
|
199
159
|
}
|
|
200
|
-
style={{
|
|
160
|
+
style={width === undefined ? undefined : { width }}
|
|
201
161
|
>
|
|
202
162
|
{sortable ? (
|
|
203
163
|
<button
|
|
204
164
|
type="button"
|
|
165
|
+
data-terp="dataview-column-sort"
|
|
205
166
|
onClick={() => props.onToggleSort(column.id)}
|
|
206
|
-
style={{
|
|
207
|
-
font: "inherit",
|
|
208
|
-
color: "inherit",
|
|
209
|
-
display: "inline-flex",
|
|
210
|
-
alignItems: "center",
|
|
211
|
-
gap: "var(--space-1)",
|
|
212
|
-
background: "transparent",
|
|
213
|
-
border: "none",
|
|
214
|
-
padding: 0,
|
|
215
|
-
cursor: "pointer",
|
|
216
|
-
}}
|
|
217
167
|
>
|
|
218
168
|
{resolve(column.header)}
|
|
219
169
|
{sort === undefined ? (
|
|
220
|
-
<SortNoneGlyph size={12}
|
|
170
|
+
<SortNoneGlyph size={12} />
|
|
221
171
|
) : sort.desc ? (
|
|
222
172
|
<SortDescGlyph size={12} />
|
|
223
173
|
) : (
|
|
@@ -231,44 +181,18 @@ export function DataViewTable<T>(props: DataViewTableProps<T>) {
|
|
|
231
181
|
role="separator"
|
|
232
182
|
aria-orientation="vertical"
|
|
233
183
|
aria-label={`${resolve(strings.resizeColumn)}: ${resolve(column.meta?.label ?? column.header)}`}
|
|
184
|
+
data-terp="dataview-column-resizer"
|
|
234
185
|
onPointerDown={(event) => {
|
|
235
186
|
event.preventDefault();
|
|
236
187
|
startResize(column.id, event.clientX);
|
|
237
188
|
}}
|
|
238
|
-
style={{
|
|
239
|
-
position: "absolute",
|
|
240
|
-
top: 0,
|
|
241
|
-
// Sit flush with the cell's right edge — a negative offset would
|
|
242
|
-
// let the last column's handle spill past the table and trip the
|
|
243
|
-
// scroll container's overflow-x, adding a spurious scrollbar.
|
|
244
|
-
right: 0,
|
|
245
|
-
width: 7,
|
|
246
|
-
height: "100%",
|
|
247
|
-
cursor: "col-resize",
|
|
248
|
-
zIndex: 1,
|
|
249
|
-
touchAction: "none",
|
|
250
|
-
}}
|
|
251
189
|
/>
|
|
252
190
|
</th>
|
|
253
191
|
);
|
|
254
192
|
})}
|
|
255
193
|
{hasActions && (
|
|
256
|
-
<th
|
|
257
|
-
<span
|
|
258
|
-
style={{
|
|
259
|
-
position: "absolute",
|
|
260
|
-
width: 1,
|
|
261
|
-
height: 1,
|
|
262
|
-
padding: 0,
|
|
263
|
-
margin: -1,
|
|
264
|
-
overflow: "hidden",
|
|
265
|
-
clip: "rect(0 0 0 0)",
|
|
266
|
-
whiteSpace: "nowrap",
|
|
267
|
-
border: 0,
|
|
268
|
-
}}
|
|
269
|
-
>
|
|
270
|
-
{resolve(strings.actions)}
|
|
271
|
-
</span>
|
|
194
|
+
<th data-terp="dataview-actions-cell">
|
|
195
|
+
<span>{resolve(strings.actions)}</span>
|
|
272
196
|
</th>
|
|
273
197
|
)}
|
|
274
198
|
</tr>
|
|
@@ -283,23 +207,13 @@ export function DataViewTable<T>(props: DataViewTableProps<T>) {
|
|
|
283
207
|
<RowGroup key={rowId}>
|
|
284
208
|
<tr
|
|
285
209
|
onClick={clickable ? () => props.onRowClick?.(row) : undefined}
|
|
286
|
-
data-terp=
|
|
210
|
+
data-terp="dataview-row"
|
|
211
|
+
data-clickable={clickable || undefined}
|
|
287
212
|
data-selected={props.isSelected(rowId) || undefined}
|
|
288
213
|
data-tone={tone ?? undefined}
|
|
289
|
-
style={{
|
|
290
|
-
cursor: clickable ? "pointer" : undefined,
|
|
291
|
-
// A row's own state outranks the selection tint — selection still
|
|
292
|
-
// shows via the checkbox and data-selected.
|
|
293
|
-
background:
|
|
294
|
-
tone !== null
|
|
295
|
-
? toneSoftColors[tone]
|
|
296
|
-
: props.isSelected(rowId)
|
|
297
|
-
? "var(--color-neutral-50)"
|
|
298
|
-
: undefined,
|
|
299
|
-
}}
|
|
300
214
|
>
|
|
301
215
|
{hasExpand && (
|
|
302
|
-
<td
|
|
216
|
+
<td data-terp="dataview-expand-cell">
|
|
303
217
|
<DataViewExpandToggle
|
|
304
218
|
expanded={expanded}
|
|
305
219
|
onToggle={() => props.onToggleExpanded(rowId)}
|
|
@@ -307,7 +221,7 @@ export function DataViewTable<T>(props: DataViewTableProps<T>) {
|
|
|
307
221
|
</td>
|
|
308
222
|
)}
|
|
309
223
|
{props.selectionEnabled && (
|
|
310
|
-
<td
|
|
224
|
+
<td data-terp="dataview-select-cell" onClick={(event) => event.stopPropagation()}>
|
|
311
225
|
<input
|
|
312
226
|
type="checkbox"
|
|
313
227
|
aria-label={resolve(strings.selectRow)}
|
|
@@ -317,7 +231,7 @@ export function DataViewTable<T>(props: DataViewTableProps<T>) {
|
|
|
317
231
|
</td>
|
|
318
232
|
)}
|
|
319
233
|
{props.columns.map((column) => (
|
|
320
|
-
<td key={column.id}
|
|
234
|
+
<td key={column.id}>
|
|
321
235
|
{clickable && column === props.columns[0] && (
|
|
322
236
|
<button
|
|
323
237
|
type="button"
|
|
@@ -329,7 +243,6 @@ export function DataViewTable<T>(props: DataViewTableProps<T>) {
|
|
|
329
243
|
event.stopPropagation();
|
|
330
244
|
props.onRowClick?.(row);
|
|
331
245
|
}}
|
|
332
|
-
style={recordButtonStyle}
|
|
333
246
|
/>
|
|
334
247
|
)}
|
|
335
248
|
{column.cell !== undefined
|
|
@@ -338,7 +251,7 @@ export function DataViewTable<T>(props: DataViewTableProps<T>) {
|
|
|
338
251
|
</td>
|
|
339
252
|
))}
|
|
340
253
|
{hasActions && (
|
|
341
|
-
<td
|
|
254
|
+
<td data-terp="dataview-actions-cell">
|
|
342
255
|
<DataViewRowActions
|
|
343
256
|
row={row}
|
|
344
257
|
actions={props.rowActions?.(row) ?? []}
|