@planningcenter/tapestry 3.8.0-rc.0 → 3.8.1-qa-1046.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/dist/componentRegistration.js +7 -1
- package/dist/componentRegistration.js.map +1 -1
- package/dist/components/DataTable/DataTable.d.ts +31 -39
- package/dist/components/DataTable/DataTable.d.ts.map +1 -1
- package/dist/components/DataTable/DataTable.js +118 -23
- package/dist/components/DataTable/DataTable.js.map +1 -1
- package/dist/components/DataTable/DataTableColumn.d.ts +105 -0
- package/dist/components/DataTable/DataTableColumn.d.ts.map +1 -0
- package/dist/components/DataTable/DataTableColumnTypes.d.ts +60 -0
- package/dist/components/DataTable/DataTableColumnTypes.d.ts.map +1 -0
- package/dist/components/DataTable/DataTableColumnTypes.js +167 -0
- package/dist/components/DataTable/DataTableColumnTypes.js.map +1 -0
- package/dist/components/DataTable/DataTableRow.d.ts +2 -0
- package/dist/components/DataTable/DataTableRow.d.ts.map +1 -0
- package/dist/components/DataTable/DataTableRow.js +9 -0
- package/dist/components/DataTable/DataTableRow.js.map +1 -0
- package/dist/components/DataTable/index.d.ts +2 -1
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/button-group/ButtonGroup.d.ts +35 -0
- package/dist/components/button-group/ButtonGroup.d.ts.map +1 -0
- package/dist/components/button-group/ButtonGroup.js +24 -0
- package/dist/components/button-group/ButtonGroup.js.map +1 -0
- package/dist/components/button-group/index.d.ts +4 -0
- package/dist/components/button-group/index.d.ts.map +1 -0
- package/dist/components/checkbox/Checkbox.d.ts +2 -2
- package/dist/components/checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/checkbox/Checkbox.js.map +1 -1
- package/dist/components/checkbox-group/CheckboxGroup.d.ts +1 -1
- package/dist/components/checkbox-group/CheckboxGroup.d.ts.map +1 -1
- package/dist/components/checkbox-group/CheckboxGroup.js.map +1 -1
- package/dist/components/combo-box/ComboBox.d.ts +1 -1
- package/dist/components/combo-box/ComboBox.d.ts.map +1 -1
- package/dist/components/combo-box/ComboBox.js.map +1 -1
- package/dist/components/date-picker/DatePicker.d.ts +2 -2
- package/dist/components/date-picker/DatePicker.d.ts.map +1 -1
- package/dist/components/date-picker/DatePicker.js +39 -39
- package/dist/components/date-picker/DatePicker.js.map +1 -1
- package/dist/components/input/Input.d.ts +1 -1
- package/dist/components/input/Input.d.ts.map +1 -1
- package/dist/components/input/Input.js.map +1 -1
- package/dist/components/input-text-base/InputTextBase.d.ts +1 -1
- package/dist/components/input-text-base/InputTextBase.d.ts.map +1 -1
- package/dist/components/input-text-base/InputTextBase.js.map +1 -1
- package/dist/components/modal/Modal.d.ts +20 -0
- package/dist/components/modal/Modal.d.ts.map +1 -0
- package/dist/components/modal/Modal.js +113 -0
- package/dist/components/modal/Modal.js.map +1 -0
- package/dist/components/modal/ModalBody.d.ts +21 -0
- package/dist/components/modal/ModalBody.d.ts.map +1 -0
- package/dist/components/modal/ModalBody.js +18 -0
- package/dist/components/modal/ModalBody.js.map +1 -0
- package/dist/components/modal/ModalContext.d.ts +9 -0
- package/dist/components/modal/ModalContext.d.ts.map +1 -0
- package/dist/components/modal/ModalContext.js +6 -0
- package/dist/components/modal/ModalContext.js.map +1 -0
- package/dist/components/modal/ModalFooter.d.ts +22 -0
- package/dist/components/modal/ModalFooter.d.ts.map +1 -0
- package/dist/components/modal/ModalFooter.js +21 -0
- package/dist/components/modal/ModalFooter.js.map +1 -0
- package/dist/components/modal/ModalHeader.d.ts +23 -0
- package/dist/components/modal/ModalHeader.d.ts.map +1 -0
- package/dist/components/modal/ModalHeader.js +32 -0
- package/dist/components/modal/ModalHeader.js.map +1 -0
- package/dist/components/modal/index.d.ts +10 -0
- package/dist/components/modal/index.d.ts.map +1 -0
- package/dist/components/number-stepper/NumberStepper.d.ts +1 -1
- package/dist/components/number-stepper/NumberStepper.d.ts.map +1 -1
- package/dist/components/number-stepper/NumberStepper.js +11 -11
- package/dist/components/number-stepper/NumberStepper.js.map +1 -1
- package/dist/components/radio/Radio.d.ts +2 -2
- package/dist/components/radio/Radio.d.ts.map +1 -1
- package/dist/components/radio/Radio.js.map +1 -1
- package/dist/components/radio-group/RadioGroup.d.ts +2 -2
- package/dist/components/radio-group/RadioGroup.d.ts.map +1 -1
- package/dist/components/radio-group/RadioGroup.js.map +1 -1
- package/dist/components/select/Select.d.ts +1 -1
- package/dist/components/select/Select.d.ts.map +1 -1
- package/dist/components/select/Select.js +6 -6
- package/dist/components/select/Select.js.map +1 -1
- package/dist/components/select/SelectNative.js +6 -6
- package/dist/components/select/SelectNative.js.map +1 -1
- package/dist/components/select/SelectOptions.js +9 -9
- package/dist/components/select/SelectOptions.js.map +1 -1
- package/dist/components/select/SelectPopover.js +14 -14
- package/dist/components/select/SelectPopover.js.map +1 -1
- package/dist/components/text-area/TextArea.d.ts +1 -1
- package/dist/components/text-area/TextArea.d.ts.map +1 -1
- package/dist/components/text-area/TextArea.js.map +1 -1
- package/dist/components/time-field/TimeField.d.ts +1 -1
- package/dist/components/time-field/TimeField.d.ts.map +1 -1
- package/dist/components/time-field/TimeField.js.map +1 -1
- package/dist/components/toggle-switch/ToggleSwitch.d.ts +2 -2
- package/dist/components/toggle-switch/ToggleSwitch.d.ts.map +1 -1
- package/dist/components/toggle-switch/ToggleSwitch.js.map +1 -1
- package/dist/ext/@internationalized/date/dist/private/DateFormatter.js +139 -0
- package/dist/ext/@internationalized/date/dist/private/DateFormatter.js.map +1 -0
- package/dist/index.css +159 -4
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/packages/tapestry-render/dist/buildComponent-Dr2UOgFa.js.map +1 -0
- package/dist/packages/tapestry-render/dist/index.js.map +1 -0
- package/dist/packages/tapestry-render/dist/legacy.js.map +1 -0
- package/dist/packages/tapestry-render/dist/registry.js.map +1 -0
- package/dist/reactRender.css +4310 -4066
- package/dist/reactRender.css.map +1 -1
- package/dist/reactRender.js +2 -2
- package/dist/reactRenderLegacy.css +4310 -4066
- package/dist/reactRenderLegacy.css.map +1 -1
- package/dist/reactRenderLegacy.js +2 -2
- package/dist/unstable.css +272 -28
- package/dist/unstable.css.map +1 -1
- package/dist/unstable.js +5 -0
- package/dist/unstable.js.map +1 -1
- package/dist/utilities/Icon.js +2 -2
- package/dist/utilities/Icon.js.map +1 -1
- package/package.json +5 -5
- package/dist/tapestry-render/dist/buildComponent-Dr2UOgFa.js.map +0 -1
- package/dist/tapestry-render/dist/index.js.map +0 -1
- package/dist/tapestry-render/dist/legacy.js.map +0 -1
- package/dist/tapestry-render/dist/registry.js.map +0 -1
- /package/dist/{tapestry-render → packages/tapestry-render}/dist/buildComponent-Dr2UOgFa.js +0 -0
- /package/dist/{tapestry-render → packages/tapestry-render}/dist/index.js +0 -0
- /package/dist/{tapestry-render → packages/tapestry-render}/dist/legacy.js +0 -0
- /package/dist/{tapestry-render → packages/tapestry-render}/dist/registry.js +0 -0
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import { register as r } from './tapestry-render/dist/registry.js';
|
|
1
|
+
import { register as r } from './packages/tapestry-render/dist/registry.js';
|
|
2
|
+
import { DatePicker } from './components/date-picker/DatePicker.js';
|
|
3
|
+
import { NumberStepper } from './components/number-stepper/NumberStepper.js';
|
|
2
4
|
import { PageHeader } from './components/page-header/PageHeader.js';
|
|
5
|
+
import { Select } from './components/select/Select.js';
|
|
3
6
|
import { Sidenav } from './components/sidenav/Sidenav.js';
|
|
4
7
|
import { SidenavItem } from './components/sidenav/SidenavItem.js';
|
|
5
8
|
import { SidenavSection } from './components/sidenav/SidenavSection.js';
|
|
6
9
|
|
|
10
|
+
r("DatePicker", DatePicker);
|
|
11
|
+
r("NumberStepper", NumberStepper);
|
|
7
12
|
r("PageHeader", PageHeader);
|
|
13
|
+
r("Select", Select);
|
|
8
14
|
r("Sidenav", Sidenav);
|
|
9
15
|
r("SidenavItem", SidenavItem);
|
|
10
16
|
r("SidenavSection", SidenavSection);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"componentRegistration.js","sources":["../src/componentRegistration.ts"],"sourcesContent":["import { register } from \"@planningcenter/tapestry-render/registry\"\n\nimport { PageHeader } from \"./components/page-header/PageHeader\"\nimport { Sidenav } from \"./components/sidenav/Sidenav\"\nimport { SidenavItem } from \"./components/sidenav/SidenavItem\"\nimport { SidenavSection } from \"./components/sidenav/SidenavSection\"\n\nregister(\"PageHeader\", PageHeader)\nregister(\"Sidenav\", Sidenav)\nregister(\"SidenavItem\", SidenavItem)\nregister(\"SidenavSection\", SidenavSection)\n"],"names":["register"],"mappings":"
|
|
1
|
+
{"version":3,"file":"componentRegistration.js","sources":["../src/componentRegistration.ts"],"sourcesContent":["import { register } from \"@planningcenter/tapestry-render/registry\"\n\nimport { DatePicker } from \"./components/date-picker/DatePicker\"\nimport { NumberStepper } from \"./components/number-stepper/NumberStepper\"\nimport { PageHeader } from \"./components/page-header/PageHeader\"\nimport { Select } from \"./components/select/Select\"\nimport { Sidenav } from \"./components/sidenav/Sidenav\"\nimport { SidenavItem } from \"./components/sidenav/SidenavItem\"\nimport { SidenavSection } from \"./components/sidenav/SidenavSection\"\n\nregister(\"DatePicker\", DatePicker)\nregister(\"NumberStepper\", NumberStepper)\nregister(\"PageHeader\", PageHeader)\nregister(\"Select\", Select)\nregister(\"Sidenav\", Sidenav)\nregister(\"SidenavItem\", SidenavItem)\nregister(\"SidenavSection\", SidenavSection)\n"],"names":["register"],"mappings":";;;;;;;;;AAUAA,CAAQ,CAAC,YAAY,EAAE,UAAU,CAAC;AAClCA,CAAQ,CAAC,eAAe,EAAE,aAAa,CAAC;AACxCA,CAAQ,CAAC,YAAY,EAAE,UAAU,CAAC;AAClCA,CAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;AAC1BA,CAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;AAC5BA,CAAQ,CAAC,aAAa,EAAE,WAAW,CAAC;AACpCA,CAAQ,CAAC,gBAAgB,EAAE,cAAc,CAAC"}
|
|
@@ -2,58 +2,46 @@ import "./index.css";
|
|
|
2
2
|
import type { CombineAriaPropsWithCustomProps } from "../../utilities/reactAriaProps";
|
|
3
3
|
import React, { type ReactNode } from "react";
|
|
4
4
|
import { type TableProps as AriaTableProps } from "react-aria-components/Table";
|
|
5
|
-
|
|
5
|
+
import type { DataTableColumn } from "./DataTableColumn";
|
|
6
6
|
export type Sort = {
|
|
7
7
|
column: string;
|
|
8
8
|
direction: "asc" | "desc";
|
|
9
9
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
align?: "center" | "end" | "start";
|
|
13
|
-
/** Human-readable column header label. */
|
|
14
|
-
headerLabel: string;
|
|
10
|
+
export type DataTableLoadingState = "error" | "idle" | "loading";
|
|
11
|
+
export interface DataTableProps {
|
|
15
12
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* Columns define what data is displayed and how. Pass a stable reference
|
|
14
|
+
* (e.g. a module constant or memoized value); columns are prepared per array
|
|
15
|
+
* identity, so an inline array rebuilds each column's formatter every render.
|
|
18
16
|
*/
|
|
19
|
-
hideLabel?: boolean;
|
|
20
|
-
/** Key used to look up the value in each row by default. */
|
|
21
|
-
name: string;
|
|
22
|
-
/** Enables filtering on this column. See Filtering. */
|
|
23
|
-
queryable?: boolean;
|
|
24
|
-
/** Custom cell renderer. Defaults to `row[column.name]`. */
|
|
25
|
-
render?: (row: unknown) => ReactNode;
|
|
26
|
-
/** Enables the resizing of that column. */
|
|
27
|
-
resizable?: boolean;
|
|
28
|
-
/** Enables sorting on this column. */
|
|
29
|
-
sortable?: boolean;
|
|
30
|
-
/** Determines rendering and filter behavior. */
|
|
31
|
-
type: DataTableColumnType;
|
|
32
|
-
}
|
|
33
|
-
export interface EnumColumn extends BaseColumn {
|
|
34
|
-
enumValues: string[];
|
|
35
|
-
type: "enum";
|
|
36
|
-
}
|
|
37
|
-
export interface NumberColumn extends BaseColumn {
|
|
38
|
-
max?: number;
|
|
39
|
-
min?: number;
|
|
40
|
-
step?: number;
|
|
41
|
-
type: "number";
|
|
42
|
-
}
|
|
43
|
-
export type DataTableColumn = BaseColumn | EnumColumn | NumberColumn;
|
|
44
|
-
export interface DataTableProps {
|
|
45
|
-
/** Columns define what data is displayed and how. */
|
|
46
17
|
columns: DataTableColumn[];
|
|
47
18
|
/** The array of rows to display. */
|
|
48
19
|
data: unknown[];
|
|
49
20
|
/** Rendered in place of the table body when there is no data. */
|
|
50
21
|
emptyState?: ReactNode;
|
|
22
|
+
/** Rendered in place of the table body when `loadingState` is `"error"`. */
|
|
23
|
+
errorState?: ReactNode;
|
|
51
24
|
/** Returns a stable identity for each row. Defaults to `row.id`. */
|
|
52
25
|
getRowId?: (row: unknown) => number | string;
|
|
26
|
+
/**
|
|
27
|
+
* Async loading state for the table body.
|
|
28
|
+
* - `"loading"`: While `data` is empty, replaces the empty state with a
|
|
29
|
+
* centered spinner.
|
|
30
|
+
* - `"error"`: While `data` is empty, replaces the empty state with
|
|
31
|
+
* `errorState` (or a default error message).
|
|
32
|
+
* - `"idle"` (default): Normal rendering.
|
|
33
|
+
*/
|
|
34
|
+
loadingState?: DataTableLoadingState;
|
|
53
35
|
/** Callback invoked when the sort state changes. */
|
|
54
36
|
onSortChange?: (sort: Sort) => void;
|
|
37
|
+
/** Ref to the scroll container the sticky header should track. Takes priority over `scrollContainerSelector`. Only used when `stickyHeader` is true. */
|
|
38
|
+
scrollContainerRef?: React.RefObject<HTMLElement>;
|
|
39
|
+
/** CSS selector for the scroll container the sticky header should track. Only used when `stickyHeader` is true. Defaults to ".content-wrap" */
|
|
40
|
+
scrollContainerSelector?: string;
|
|
55
41
|
/** The current sort state for the table. */
|
|
56
42
|
sortBy?: Sort;
|
|
43
|
+
/** Enables a sticky header that tracks an outer scroll container. Progressive enhancement: Relies on `animation-timeline: scroll()`, which isn't supported in all browsers (e.g. Firefox) — the header scrolls with the table body instead of sticking in those cases. */
|
|
44
|
+
stickyHeader?: boolean;
|
|
57
45
|
}
|
|
58
46
|
type AriaTablePropsToOmit = "children" | "slot";
|
|
59
47
|
type AriaTablePropsToInclude = never;
|
|
@@ -61,14 +49,18 @@ export type DataTableElementProps = CombineAriaPropsWithCustomProps<AriaTablePro
|
|
|
61
49
|
/**
|
|
62
50
|
* A table for displaying tabular data. Built on React Aria's `Table`.
|
|
63
51
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
52
|
+
* Each column declares a `type` that controls how its cell values are
|
|
53
|
+
* formatted: `string`, `number`, `currency`, `date`, `datetime`, `boolean`,
|
|
54
|
+
* `enum`, and `custom`. A column's `render` function always takes precedence
|
|
55
|
+
* over the built-in type formatting.
|
|
56
|
+
*
|
|
57
|
+
* Sorting, filtering, pagination, selection, and other RFC features are not yet
|
|
58
|
+
* implemented.
|
|
67
59
|
*
|
|
68
60
|
* @component
|
|
69
61
|
*/
|
|
70
62
|
export declare const DataTable: {
|
|
71
|
-
({ className, columns, data, emptyState, getRowId, sortBy, onSortChange, ...restProps }: DataTableElementProps): React.JSX.Element;
|
|
63
|
+
({ className, columns, data, emptyState, errorState, getRowId, loadingState, scrollContainerRef, scrollContainerSelector, stickyHeader, sortBy, onSortChange, ...restProps }: DataTableElementProps): React.JSX.Element;
|
|
72
64
|
displayName: string;
|
|
73
65
|
};
|
|
74
66
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/DataTable.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/DataTable.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAIpB,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,2BAA2B,CAAA;AAEhF,OAAO,KAAK,EAAE,EACZ,KAAK,SAAS,EAKf,MAAM,OAAO,CAAA;AACd,OAAO,EAQL,KAAK,UAAU,IAAI,cAAc,EAClC,MAAM,6BAA6B,CAAA;AAEpC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAIxD,MAAM,MAAM,IAAI,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;CAAE,CAAA;AAEhE,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;AAEhE,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,oCAAoC;IACpC,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,iEAAiE;IACjE,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,MAAM,CAAA;IAC5C;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,oDAAoD;IACpD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACnC,wJAAwJ;IACxJ,kBAAkB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,CAAA;IACjD,+IAA+I;IAC/I,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,4CAA4C;IAC5C,MAAM,CAAC,EAAE,IAAI,CAAA;IACb,0QAA0Q;IAC1Q,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAID,KAAK,oBAAoB,GAAG,UAAU,GAAG,MAAM,CAAA;AAE/C,KAAK,uBAAuB,GAAG,KAAK,CAAA;AAEpC,MAAM,MAAM,qBAAqB,GAAG,+BAA+B,CACjE,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,uBAAuB,CACxB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,SAAS;kLAcnB,qBAAqB;;CAuGvB,CAAA"}
|
|
@@ -1,48 +1,86 @@
|
|
|
1
|
+
import LoadingSpinner from '../internal/LoadingSpinner.js';
|
|
1
2
|
import Icon from '../../utilities/Icon.js';
|
|
2
3
|
import classNames from 'classnames';
|
|
3
|
-
import React__default, { useMemo } from 'react';
|
|
4
|
+
import React__default, { useMemo, useRef, useState, useLayoutEffect } from 'react';
|
|
4
5
|
import { Table, TableHeader, Column, TableBody, Row, Cell } from 'react-aria-components/Table';
|
|
6
|
+
import { prepareColumn } from './DataTableColumnTypes.js';
|
|
7
|
+
import { getRowKey } from './DataTableRow.js';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
if (column.render)
|
|
8
|
-
return column.render(row);
|
|
9
|
-
const value = row?.[column.name];
|
|
10
|
-
return value === undefined || value === null ? "" : String(value);
|
|
11
|
-
}
|
|
12
|
-
function getRowKey(row, index, getRowId) {
|
|
13
|
-
if (getRowId)
|
|
14
|
-
return getRowId(row);
|
|
15
|
-
const id = row?.id;
|
|
16
|
-
return typeof id === "number" || typeof id === "string" ? id : index;
|
|
17
|
-
}
|
|
9
|
+
const DEFAULT_ERROR_STATE = "Something went wrong.";
|
|
18
10
|
/**
|
|
19
11
|
* A table for displaying tabular data. Built on React Aria's `Table`.
|
|
20
12
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
13
|
+
* Each column declares a `type` that controls how its cell values are
|
|
14
|
+
* formatted: `string`, `number`, `currency`, `date`, `datetime`, `boolean`,
|
|
15
|
+
* `enum`, and `custom`. A column's `render` function always takes precedence
|
|
16
|
+
* over the built-in type formatting.
|
|
17
|
+
*
|
|
18
|
+
* Sorting, filtering, pagination, selection, and other RFC features are not yet
|
|
19
|
+
* implemented.
|
|
24
20
|
*
|
|
25
21
|
* @component
|
|
26
22
|
*/
|
|
27
|
-
const DataTable = ({ className, columns, data, emptyState, getRowId, sortBy, onSortChange, ...restProps }) => {
|
|
23
|
+
const DataTable = ({ className, columns, data, emptyState, errorState, getRowId, loadingState = "idle", scrollContainerRef, scrollContainerSelector = ".content-wrap", stickyHeader, sortBy, onSortChange, ...restProps }) => {
|
|
28
24
|
const combinedClassName = classNames("tds-data-table", className);
|
|
29
25
|
const sortDescriptor = useSortDescriptor(sortBy);
|
|
30
|
-
|
|
26
|
+
const { hasScrollContainer, wrapperRef } = useStickyHeader(stickyHeader, scrollContainerRef, scrollContainerSelector);
|
|
27
|
+
// React Aria only invokes `renderEmptyState` when the collection is empty, so
|
|
28
|
+
// the loading spinner and error message only take over the body while `data`
|
|
29
|
+
// is empty; once rows exist they always render.
|
|
30
|
+
const emptyStateContent = getEmptyStateContent({
|
|
31
|
+
emptyState,
|
|
32
|
+
errorState,
|
|
33
|
+
loadingState,
|
|
34
|
+
});
|
|
35
|
+
const renderEmptyState = emptyStateContent
|
|
36
|
+
? () => emptyStateContent
|
|
37
|
+
: undefined;
|
|
38
|
+
// Keyed on column names rather than the `columns` array identity so a fresh
|
|
39
|
+
// `columns` literal each render doesn't rebuild every formatter. `columns` is
|
|
40
|
+
// intentionally omitted from the deps.
|
|
41
|
+
const columnKey = columns.map((c) => `${c.name}/${c.type}`).join("|");
|
|
42
|
+
const preparedColumns = useMemo(() => columns.map(prepareColumn),
|
|
43
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
|
+
[columnKey]);
|
|
45
|
+
return (React__default.createElement("div", { ref: wrapperRef, className: classNames("tds-data-table-wrapper", {
|
|
46
|
+
"tds-data-table-wrapper--sticky-header": hasScrollContainer,
|
|
47
|
+
}) },
|
|
31
48
|
React__default.createElement("div", { className: "tds-data-table-scroll" },
|
|
32
49
|
React__default.createElement(Table, { ...restProps, className: combinedClassName, sortDescriptor: sortDescriptor, onSortChange: (sort) => {
|
|
33
50
|
onSortChange?.(serializeSort(sort));
|
|
34
51
|
} },
|
|
35
|
-
React__default.createElement(TableHeader, { className: "tds-data-table-header" },
|
|
52
|
+
React__default.createElement(TableHeader, { className: "tds-data-table-header" }, preparedColumns.map(({ align, column }, index) => (React__default.createElement(Column, { key: column.name, id: column.name, isRowHeader: index === 0, className: classNames("tds-data-table-column", {
|
|
53
|
+
[`tds-data-table-column--align-${align}`]: align !== "start",
|
|
54
|
+
}), allowsSorting: column.sortable },
|
|
36
55
|
column.hideLabel ? (React__default.createElement("span", { className: "tds-data-table-column-label--hidden" }, column.headerLabel)) : (column.headerLabel),
|
|
37
56
|
column.sortable && (React__default.createElement(Icon, { className: "tds-data-table-column-icon tds-data-table-column-icon--sort", symbol: "general#up-caret", "aria-hidden": true })))))),
|
|
38
|
-
React__default.createElement(TableBody, { className: "tds-data-table-body", renderEmptyState:
|
|
39
|
-
? () => React__default.createElement("div", { className: "tds-data-table-empty" }, emptyState)
|
|
40
|
-
: undefined }, data.map((row, rowIndex) => {
|
|
57
|
+
React__default.createElement(TableBody, { className: "tds-data-table-body", renderEmptyState: renderEmptyState }, data.map((row, rowIndex) => {
|
|
41
58
|
const rowKey = getRowKey(row, rowIndex, getRowId);
|
|
42
|
-
return (React__default.createElement(Row, { key: rowKey, id: rowKey, className: "tds-data-table-row" },
|
|
59
|
+
return (React__default.createElement(Row, { key: rowKey, id: rowKey, className: "tds-data-table-row" }, preparedColumns.map((prepared) => (React__default.createElement(Cell, { key: prepared.column.name, className: classNames("tds-data-table-cell", {
|
|
60
|
+
[`tds-data-table-cell--align-${prepared.align}`]: prepared.align !== "start",
|
|
61
|
+
"tds-data-table-cell--numeric": prepared.isNumeric,
|
|
62
|
+
}) }, prepared.renderCell(row))))));
|
|
43
63
|
}))))));
|
|
44
64
|
};
|
|
45
65
|
DataTable.displayName = "DataTable";
|
|
66
|
+
/**
|
|
67
|
+
* Resolves the content rendered in the table body's empty-state slot based on
|
|
68
|
+
* `loadingState`, falling back to `emptyState`. Returns `null` when nothing
|
|
69
|
+
* should render (idle with no `emptyState`).
|
|
70
|
+
*/
|
|
71
|
+
function getEmptyStateContent({ emptyState, errorState, loadingState, }) {
|
|
72
|
+
if (loadingState === "loading") {
|
|
73
|
+
return (React__default.createElement("div", { className: "tds-data-table-loading", role: "status" },
|
|
74
|
+
React__default.createElement("span", { className: "tds-data-table-cell-label--hidden" }, "Loading"),
|
|
75
|
+
React__default.createElement("span", { className: "tds-data-table-loading-spinner", "aria-hidden": "true" },
|
|
76
|
+
React__default.createElement(LoadingSpinner, null))));
|
|
77
|
+
}
|
|
78
|
+
// Error and empty share the same container; they differ only in content.
|
|
79
|
+
const content = loadingState === "error" ? (errorState ?? DEFAULT_ERROR_STATE) : emptyState;
|
|
80
|
+
if (content == null)
|
|
81
|
+
return null;
|
|
82
|
+
return React__default.createElement("div", { className: "tds-data-table-empty" }, content);
|
|
83
|
+
}
|
|
46
84
|
function useSortDescriptor(sortBy) {
|
|
47
85
|
return useMemo(() => {
|
|
48
86
|
if (!sortBy)
|
|
@@ -60,6 +98,63 @@ function serializeSort(sort) {
|
|
|
60
98
|
return { column: sort.column, direction: "desc" };
|
|
61
99
|
return { column: sort.column, direction: "asc" };
|
|
62
100
|
}
|
|
101
|
+
function useStickyHeader(stickyHeader, scrollContainerRef, scrollContainerSelector) {
|
|
102
|
+
const wrapperRef = useRef(null);
|
|
103
|
+
const [hasScrollContainer, setHasScrollContainer] = useState(false);
|
|
104
|
+
useLayoutEffect(() => {
|
|
105
|
+
setHasScrollContainer(false);
|
|
106
|
+
if (!stickyHeader)
|
|
107
|
+
return;
|
|
108
|
+
if (typeof ResizeObserver === "undefined")
|
|
109
|
+
return;
|
|
110
|
+
const wrapper = wrapperRef.current;
|
|
111
|
+
if (!wrapper)
|
|
112
|
+
return;
|
|
113
|
+
const scrollContainer = getScrollContainer(scrollContainerRef, scrollContainerSelector, wrapper);
|
|
114
|
+
if (!scrollContainer)
|
|
115
|
+
return;
|
|
116
|
+
setHasScrollContainer(true);
|
|
117
|
+
let thead = null;
|
|
118
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
119
|
+
if (!thead) {
|
|
120
|
+
thead = wrapper.querySelector(".tds-data-table-header");
|
|
121
|
+
if (!thead)
|
|
122
|
+
return;
|
|
123
|
+
scrollContainer.style.setProperty("scroll-timeline-name", "--_sticky-header-scroll");
|
|
124
|
+
}
|
|
125
|
+
const { top: wrapperTop, bottom: wrapperBottom } = wrapper.getBoundingClientRect();
|
|
126
|
+
const { top: scrollContainerTop } = scrollContainer.getBoundingClientRect();
|
|
127
|
+
// Derive headerTop from the untransformed wrapper to avoid reading the
|
|
128
|
+
// scroll-driven translateY that getBoundingClientRect() includes on thead.
|
|
129
|
+
const headerTop = wrapperTop + wrapper.clientTop;
|
|
130
|
+
const start = scrollContainer.scrollTop + (headerTop - scrollContainerTop);
|
|
131
|
+
const max = Math.max(0, wrapperBottom - headerTop - thead.offsetHeight);
|
|
132
|
+
wrapper.style.setProperty("--_sticky-header-scroll-start", `${start}px`);
|
|
133
|
+
wrapper.style.setProperty("--_sticky-header-scroll-max", `${max}px`);
|
|
134
|
+
});
|
|
135
|
+
resizeObserver.observe(wrapper);
|
|
136
|
+
resizeObserver.observe(scrollContainer);
|
|
137
|
+
return () => {
|
|
138
|
+
resizeObserver.disconnect();
|
|
139
|
+
wrapper.style.removeProperty("--_sticky-header-scroll-start");
|
|
140
|
+
wrapper.style.removeProperty("--_sticky-header-scroll-max");
|
|
141
|
+
};
|
|
142
|
+
}, [stickyHeader, scrollContainerRef, scrollContainerSelector]);
|
|
143
|
+
return { hasScrollContainer, wrapperRef };
|
|
144
|
+
}
|
|
145
|
+
function getScrollContainer(scrollContainerRef, scrollContainerSelector, wrapper) {
|
|
146
|
+
const element = scrollContainerRef?.current ??
|
|
147
|
+
getScrollContainerFromSelector(scrollContainerSelector, wrapper);
|
|
148
|
+
if (element instanceof Window)
|
|
149
|
+
return;
|
|
150
|
+
return element;
|
|
151
|
+
}
|
|
152
|
+
function getScrollContainerFromSelector(scrollContainerSelector, wrapper) {
|
|
153
|
+
return scrollContainerSelector
|
|
154
|
+
? (wrapper.parentElement?.closest(scrollContainerSelector) ??
|
|
155
|
+
undefined)
|
|
156
|
+
: undefined;
|
|
157
|
+
}
|
|
63
158
|
|
|
64
159
|
export { DataTable };
|
|
65
160
|
//# sourceMappingURL=DataTable.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTable.js","sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport Icon from \"@utilities/Icon\"\nimport type { CombineAriaPropsWithCustomProps } from \"@utilities/reactAriaProps\"\nimport classNames from \"classnames\"\nimport React, { type ReactNode, useMemo } from \"react\"\nimport {\n Cell,\n Column,\n Row,\n type SortDescriptor,\n Table,\n TableBody,\n TableHeader,\n type TableProps as AriaTableProps,\n} from \"react-aria-components/Table\"\n\nexport type DataTableColumnType =\n | \"boolean\"\n | \"currency\"\n | \"custom\"\n | \"date\"\n | \"enum\"\n | \"number\"\n | \"string\"\n\nexport type Sort = { column: string; direction: \"asc\" | \"desc\" }\n\ninterface BaseColumn {\n /** Determines how items align in the table. */\n align?: \"center\" | \"end\" | \"start\"\n /** Human-readable column header label. */\n headerLabel: string\n /**\n * Visually hides the header label. The text is still rendered (visually\n * hidden via CSS) so it remains announced to assistive tech.\n */\n hideLabel?: boolean\n /** Key used to look up the value in each row by default. */\n name: string\n /** Enables filtering on this column. See Filtering. */\n queryable?: boolean\n /** Custom cell renderer. Defaults to `row[column.name]`. */\n render?: (row: unknown) => ReactNode\n /** Enables the resizing of that column. */\n resizable?: boolean\n /** Enables sorting on this column. */\n sortable?: boolean\n /** Determines rendering and filter behavior. */\n type: DataTableColumnType\n}\n\nexport interface EnumColumn extends BaseColumn {\n enumValues: string[]\n type: \"enum\"\n}\n\nexport interface NumberColumn extends BaseColumn {\n max?: number\n min?: number\n step?: number\n type: \"number\"\n}\n\nexport type DataTableColumn = BaseColumn | EnumColumn | NumberColumn\n\nexport interface DataTableProps {\n /** Columns define what data is displayed and how. */\n columns: DataTableColumn[]\n /** The array of rows to display. */\n data: unknown[]\n /** Rendered in place of the table body when there is no data. */\n emptyState?: ReactNode\n /** Returns a stable identity for each row. Defaults to `row.id`. */\n getRowId?: (row: unknown) => number | string\n /** Callback invoked when the sort state changes. */\n onSortChange?: (sort: Sort) => void\n /** The current sort state for the table. */\n sortBy?: Sort\n}\n\ntype AriaTablePropsToOmit = \"children\" | \"slot\"\n\ntype AriaTablePropsToInclude = never\n\nexport type DataTableElementProps = CombineAriaPropsWithCustomProps<\n AriaTableProps,\n DataTableProps,\n AriaTablePropsToOmit,\n AriaTablePropsToInclude\n>\n\nfunction getCellValue(row: unknown, column: DataTableColumn): ReactNode {\n if (column.render) return column.render(row)\n const value = (row as Record<string, unknown>)?.[column.name]\n return value === undefined || value === null ? \"\" : String(value)\n}\n\nfunction getRowKey(\n row: unknown,\n index: number,\n getRowId?: (row: unknown) => number | string\n): number | string {\n if (getRowId) return getRowId(row)\n const id = (row as Record<string, unknown>)?.id\n return typeof id === \"number\" || typeof id === \"string\" ? id : index\n}\n\n/**\n * A table for displaying tabular data. Built on React Aria's `Table`.\n *\n * This is an early skeleton: only column headers and string cell values are\n * rendered. Sorting, filtering, pagination, selection, and other RFC\n * features are not yet implemented.\n *\n * @component\n */\nexport const DataTable = ({\n className,\n columns,\n data,\n emptyState,\n getRowId,\n sortBy,\n onSortChange,\n ...restProps\n}: DataTableElementProps) => {\n const combinedClassName = classNames(\"tds-data-table\", className)\n const sortDescriptor = useSortDescriptor(sortBy)\n\n return (\n <div className=\"tds-data-table-wrapper\">\n <div className=\"tds-data-table-scroll\">\n <Table\n {...restProps}\n className={combinedClassName}\n sortDescriptor={sortDescriptor}\n onSortChange={(sort) => {\n onSortChange?.(serializeSort(sort))\n }}\n >\n <TableHeader className=\"tds-data-table-header\">\n {columns.map((column, index) => (\n <Column\n key={column.name}\n id={column.name}\n isRowHeader={index === 0}\n className=\"tds-data-table-column\"\n allowsSorting={column.sortable}\n >\n {column.hideLabel ? (\n <span className=\"tds-data-table-column-label--hidden\">\n {column.headerLabel}\n </span>\n ) : (\n column.headerLabel\n )}\n {column.sortable && (\n <Icon\n className=\"tds-data-table-column-icon tds-data-table-column-icon--sort\"\n symbol=\"general#up-caret\"\n aria-hidden={true}\n />\n )}\n </Column>\n ))}\n </TableHeader>\n <TableBody\n className=\"tds-data-table-body\"\n renderEmptyState={\n emptyState\n ? () => <div className=\"tds-data-table-empty\">{emptyState}</div>\n : undefined\n }\n >\n {data.map((row, rowIndex) => {\n const rowKey = getRowKey(row, rowIndex, getRowId)\n return (\n <Row key={rowKey} id={rowKey} className=\"tds-data-table-row\">\n {columns.map((column) => (\n <Cell key={column.name} className=\"tds-data-table-cell\">\n {getCellValue(row, column)}\n </Cell>\n ))}\n </Row>\n )\n })}\n </TableBody>\n </Table>\n </div>\n </div>\n )\n}\n\nDataTable.displayName = \"DataTable\"\n\nfunction useSortDescriptor(\n sortBy: Sort | undefined\n): SortDescriptor | undefined {\n return useMemo(() => {\n if (!sortBy) return undefined\n return deserializeSort(sortBy)\n }, [sortBy])\n}\n\nfunction deserializeSort(sort: Sort): SortDescriptor {\n if (sort.direction === \"desc\")\n return { column: sort.column, direction: \"descending\" }\n return { column: sort.column, direction: \"ascending\" }\n}\n\nfunction serializeSort(sort: SortDescriptor): Sort {\n if (sort.direction === \"descending\")\n return { column: sort.column as string, direction: \"desc\" }\n return { column: sort.column as string, direction: \"asc\" }\n}\n"],"names":["React"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DataTable.js","sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport { LoadingSpinner } from \"@components/internal\"\nimport Icon from \"@utilities/Icon\"\nimport type { CombineAriaPropsWithCustomProps } from \"@utilities/reactAriaProps\"\nimport classNames from \"classnames\"\nimport React, {\n type ReactNode,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\"\nimport {\n Cell,\n Column,\n Row,\n type SortDescriptor,\n Table,\n TableBody,\n TableHeader,\n type TableProps as AriaTableProps,\n} from \"react-aria-components/Table\"\n\nimport type { DataTableColumn } from \"./DataTableColumn\"\nimport { prepareColumn, type PreparedColumn } from \"./DataTableColumnTypes\"\nimport { getRowKey } from \"./DataTableRow\"\n\nexport type Sort = { column: string; direction: \"asc\" | \"desc\" }\n\nexport type DataTableLoadingState = \"error\" | \"idle\" | \"loading\"\n\nexport interface DataTableProps {\n /**\n * Columns define what data is displayed and how. Pass a stable reference\n * (e.g. a module constant or memoized value); columns are prepared per array\n * identity, so an inline array rebuilds each column's formatter every render.\n */\n columns: DataTableColumn[]\n /** The array of rows to display. */\n data: unknown[]\n /** Rendered in place of the table body when there is no data. */\n emptyState?: ReactNode\n /** Rendered in place of the table body when `loadingState` is `\"error\"`. */\n errorState?: ReactNode\n /** Returns a stable identity for each row. Defaults to `row.id`. */\n getRowId?: (row: unknown) => number | string\n /**\n * Async loading state for the table body.\n * - `\"loading\"`: While `data` is empty, replaces the empty state with a\n * centered spinner.\n * - `\"error\"`: While `data` is empty, replaces the empty state with\n * `errorState` (or a default error message).\n * - `\"idle\"` (default): Normal rendering.\n */\n loadingState?: DataTableLoadingState\n /** Callback invoked when the sort state changes. */\n onSortChange?: (sort: Sort) => void\n /** Ref to the scroll container the sticky header should track. Takes priority over `scrollContainerSelector`. Only used when `stickyHeader` is true. */\n scrollContainerRef?: React.RefObject<HTMLElement>\n /** CSS selector for the scroll container the sticky header should track. Only used when `stickyHeader` is true. Defaults to \".content-wrap\" */\n scrollContainerSelector?: string\n /** The current sort state for the table. */\n sortBy?: Sort\n /** Enables a sticky header that tracks an outer scroll container. Progressive enhancement: Relies on `animation-timeline: scroll()`, which isn't supported in all browsers (e.g. Firefox) — the header scrolls with the table body instead of sticking in those cases. */\n stickyHeader?: boolean\n}\n\nconst DEFAULT_ERROR_STATE = \"Something went wrong.\"\n\ntype AriaTablePropsToOmit = \"children\" | \"slot\"\n\ntype AriaTablePropsToInclude = never\n\nexport type DataTableElementProps = CombineAriaPropsWithCustomProps<\n AriaTableProps,\n DataTableProps,\n AriaTablePropsToOmit,\n AriaTablePropsToInclude\n>\n\n/**\n * A table for displaying tabular data. Built on React Aria's `Table`.\n *\n * Each column declares a `type` that controls how its cell values are\n * formatted: `string`, `number`, `currency`, `date`, `datetime`, `boolean`,\n * `enum`, and `custom`. A column's `render` function always takes precedence\n * over the built-in type formatting.\n *\n * Sorting, filtering, pagination, selection, and other RFC features are not yet\n * implemented.\n *\n * @component\n */\nexport const DataTable = ({\n className,\n columns,\n data,\n emptyState,\n errorState,\n getRowId,\n loadingState = \"idle\",\n scrollContainerRef,\n scrollContainerSelector = \".content-wrap\",\n stickyHeader,\n sortBy,\n onSortChange,\n ...restProps\n}: DataTableElementProps) => {\n const combinedClassName = classNames(\"tds-data-table\", className)\n const sortDescriptor = useSortDescriptor(sortBy)\n const { hasScrollContainer, wrapperRef } = useStickyHeader(\n stickyHeader,\n scrollContainerRef,\n scrollContainerSelector\n )\n\n // React Aria only invokes `renderEmptyState` when the collection is empty, so\n // the loading spinner and error message only take over the body while `data`\n // is empty; once rows exist they always render.\n const emptyStateContent = getEmptyStateContent({\n emptyState,\n errorState,\n loadingState,\n })\n const renderEmptyState = emptyStateContent\n ? () => emptyStateContent\n : undefined\n\n // Keyed on column names rather than the `columns` array identity so a fresh\n // `columns` literal each render doesn't rebuild every formatter. `columns` is\n // intentionally omitted from the deps.\n const columnKey = columns.map((c) => `${c.name}/${c.type}`).join(\"|\")\n const preparedColumns = useMemo<PreparedColumn[]>(\n () => columns.map(prepareColumn),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [columnKey]\n )\n\n return (\n <div\n ref={wrapperRef}\n className={classNames(\"tds-data-table-wrapper\", {\n \"tds-data-table-wrapper--sticky-header\": hasScrollContainer,\n })}\n >\n <div className=\"tds-data-table-scroll\">\n <Table\n {...restProps}\n className={combinedClassName}\n sortDescriptor={sortDescriptor}\n onSortChange={(sort) => {\n onSortChange?.(serializeSort(sort))\n }}\n >\n <TableHeader className=\"tds-data-table-header\">\n {preparedColumns.map(({ align, column }, index) => (\n <Column\n key={column.name}\n id={column.name}\n isRowHeader={index === 0}\n className={classNames(\"tds-data-table-column\", {\n [`tds-data-table-column--align-${align}`]: align !== \"start\",\n })}\n allowsSorting={column.sortable}\n >\n {column.hideLabel ? (\n <span className=\"tds-data-table-column-label--hidden\">\n {column.headerLabel}\n </span>\n ) : (\n column.headerLabel\n )}\n {column.sortable && (\n <Icon\n className=\"tds-data-table-column-icon tds-data-table-column-icon--sort\"\n symbol=\"general#up-caret\"\n aria-hidden={true}\n />\n )}\n </Column>\n ))}\n </TableHeader>\n <TableBody\n className=\"tds-data-table-body\"\n renderEmptyState={renderEmptyState}\n >\n {data.map((row, rowIndex) => {\n const rowKey = getRowKey(row, rowIndex, getRowId)\n return (\n <Row key={rowKey} id={rowKey} className=\"tds-data-table-row\">\n {preparedColumns.map((prepared) => (\n <Cell\n key={prepared.column.name}\n className={classNames(\"tds-data-table-cell\", {\n [`tds-data-table-cell--align-${prepared.align}`]:\n prepared.align !== \"start\",\n \"tds-data-table-cell--numeric\": prepared.isNumeric,\n })}\n >\n {prepared.renderCell(row)}\n </Cell>\n ))}\n </Row>\n )\n })}\n </TableBody>\n </Table>\n </div>\n </div>\n )\n}\n\nDataTable.displayName = \"DataTable\"\n\n/**\n * Resolves the content rendered in the table body's empty-state slot based on\n * `loadingState`, falling back to `emptyState`. Returns `null` when nothing\n * should render (idle with no `emptyState`).\n */\nfunction getEmptyStateContent({\n emptyState,\n errorState,\n loadingState,\n}: {\n emptyState: ReactNode\n errorState: ReactNode\n loadingState: DataTableLoadingState\n}): ReactNode {\n if (loadingState === \"loading\") {\n return (\n <div className=\"tds-data-table-loading\" role=\"status\">\n <span className=\"tds-data-table-cell-label--hidden\">Loading</span>\n <span className=\"tds-data-table-loading-spinner\" aria-hidden=\"true\">\n <LoadingSpinner />\n </span>\n </div>\n )\n }\n\n // Error and empty share the same container; they differ only in content.\n const content =\n loadingState === \"error\" ? (errorState ?? DEFAULT_ERROR_STATE) : emptyState\n if (content == null) return null\n\n return <div className=\"tds-data-table-empty\">{content}</div>\n}\n\nfunction useSortDescriptor(\n sortBy: Sort | undefined\n): SortDescriptor | undefined {\n return useMemo(() => {\n if (!sortBy) return undefined\n return deserializeSort(sortBy)\n }, [sortBy])\n}\n\nfunction deserializeSort(sort: Sort): SortDescriptor {\n if (sort.direction === \"desc\")\n return { column: sort.column, direction: \"descending\" }\n return { column: sort.column, direction: \"ascending\" }\n}\n\nfunction serializeSort(sort: SortDescriptor): Sort {\n if (sort.direction === \"descending\")\n return { column: sort.column as string, direction: \"desc\" }\n return { column: sort.column as string, direction: \"asc\" }\n}\n\nfunction useStickyHeader(\n stickyHeader: boolean | undefined,\n scrollContainerRef: React.RefObject<HTMLElement> | undefined,\n scrollContainerSelector: string | undefined\n) {\n const wrapperRef = useRef<HTMLDivElement>(null)\n const [hasScrollContainer, setHasScrollContainer] = useState(false)\n\n useLayoutEffect(() => {\n setHasScrollContainer(false)\n if (!stickyHeader) return\n if (typeof ResizeObserver === \"undefined\") return\n const wrapper = wrapperRef.current\n if (!wrapper) return\n\n const scrollContainer = getScrollContainer(\n scrollContainerRef,\n scrollContainerSelector,\n wrapper\n )\n if (!scrollContainer) return\n\n setHasScrollContainer(true)\n\n let thead: HTMLElement | null = null\n\n const resizeObserver = new ResizeObserver(() => {\n if (!thead) {\n thead = wrapper.querySelector<HTMLElement>(\".tds-data-table-header\")\n if (!thead) return\n scrollContainer.style.setProperty(\n \"scroll-timeline-name\",\n \"--_sticky-header-scroll\"\n )\n }\n\n const { top: wrapperTop, bottom: wrapperBottom } =\n wrapper.getBoundingClientRect()\n const { top: scrollContainerTop } =\n scrollContainer.getBoundingClientRect()\n // Derive headerTop from the untransformed wrapper to avoid reading the\n // scroll-driven translateY that getBoundingClientRect() includes on thead.\n const headerTop = wrapperTop + wrapper.clientTop\n const start = scrollContainer.scrollTop + (headerTop - scrollContainerTop)\n const max = Math.max(0, wrapperBottom - headerTop - thead.offsetHeight)\n wrapper.style.setProperty(\"--_sticky-header-scroll-start\", `${start}px`)\n wrapper.style.setProperty(\"--_sticky-header-scroll-max\", `${max}px`)\n })\n\n resizeObserver.observe(wrapper)\n resizeObserver.observe(scrollContainer)\n\n return () => {\n resizeObserver.disconnect()\n wrapper.style.removeProperty(\"--_sticky-header-scroll-start\")\n wrapper.style.removeProperty(\"--_sticky-header-scroll-max\")\n }\n }, [stickyHeader, scrollContainerRef, scrollContainerSelector])\n\n return { hasScrollContainer, wrapperRef }\n}\n\nfunction getScrollContainer(\n scrollContainerRef: React.RefObject<HTMLElement> | undefined,\n scrollContainerSelector: string | undefined,\n wrapper: HTMLElement\n): HTMLElement | undefined {\n const element =\n scrollContainerRef?.current ??\n getScrollContainerFromSelector(scrollContainerSelector, wrapper)\n if (element instanceof Window) return\n return element\n}\n\nfunction getScrollContainerFromSelector(\n scrollContainerSelector: string | undefined,\n wrapper: HTMLElement\n): HTMLElement | undefined {\n return scrollContainerSelector\n ? (wrapper.parentElement?.closest<HTMLElement>(scrollContainerSelector) ??\n undefined)\n : undefined\n}\n"],"names":["React"],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type DataTableColumnType = "boolean"
|
|
3
|
+
/**
|
|
4
|
+
* @experimental Unstable — subject to change. Currency support is in beta
|
|
5
|
+
* and is currently hardwired to USD; the API may change without notice.
|
|
6
|
+
*/
|
|
7
|
+
| "currency" | "custom" | "date" | "datetime" | "enum" | "number" | "string";
|
|
8
|
+
export type DataTableColumnAlign = "center" | "end" | "start";
|
|
9
|
+
interface BaseColumn {
|
|
10
|
+
/** Determines how items align in the table. */
|
|
11
|
+
align?: DataTableColumnAlign;
|
|
12
|
+
/** Human-readable column header label. */
|
|
13
|
+
headerLabel: string;
|
|
14
|
+
/** Hides the visible header label (still announced to assistive tech). */
|
|
15
|
+
hideLabel?: boolean;
|
|
16
|
+
/** Key used to look up the value in each row by default. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Enables filtering on this column. See Filtering. */
|
|
19
|
+
queryable?: boolean;
|
|
20
|
+
/** Custom cell renderer. Defaults to a type-aware format of `row[column.name]`. */
|
|
21
|
+
render?: (row: unknown) => ReactNode;
|
|
22
|
+
/** Enables the resizing of that column. */
|
|
23
|
+
resizable?: boolean;
|
|
24
|
+
/** Enables sorting on this column. */
|
|
25
|
+
sortable?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Renders the raw value as text. */
|
|
28
|
+
export interface StringColumn extends BaseColumn {
|
|
29
|
+
type: "string";
|
|
30
|
+
}
|
|
31
|
+
/** Formats numeric values with `Intl.NumberFormat`. Aligns to the end by default. */
|
|
32
|
+
export interface NumberColumn extends BaseColumn {
|
|
33
|
+
/** Maximum value. Used by numeric filters. */
|
|
34
|
+
max?: number;
|
|
35
|
+
/** Minimum value. Used by numeric filters. */
|
|
36
|
+
min?: number;
|
|
37
|
+
/** Overrides the `Intl.NumberFormat` options used to display values. */
|
|
38
|
+
numberFormatOptions?: Intl.NumberFormatOptions;
|
|
39
|
+
/** Step increment. Used by numeric filters. */
|
|
40
|
+
step?: number;
|
|
41
|
+
type: "number";
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* @experimental Unstable — subject to change. Currency support is in beta and
|
|
45
|
+
* is currently hardwired to USD; the API may change without notice.
|
|
46
|
+
*
|
|
47
|
+
* Formats numeric values as currency. Aligns to the end by default.
|
|
48
|
+
*/
|
|
49
|
+
export interface CurrencyColumn extends BaseColumn {
|
|
50
|
+
/** Overrides the `Intl.NumberFormat` options used to display values. */
|
|
51
|
+
numberFormatOptions?: Intl.NumberFormatOptions;
|
|
52
|
+
type: "currency";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Formats date values (without a time) with the `@internationalized/date`
|
|
56
|
+
* `DateFormatter` (a locale-aware wrapper around `Intl.DateTimeFormat`).
|
|
57
|
+
* Accepts `DateValue` objects from `@internationalized/date` (`CalendarDate`,
|
|
58
|
+
* `CalendarDateTime`, `ZonedDateTime`), `Date` objects, timestamps, or date
|
|
59
|
+
* strings (ISO `YYYY-MM-DD` strings are parsed as local dates to avoid
|
|
60
|
+
* time-zone drift). Defaults to a `medium` date style.
|
|
61
|
+
*/
|
|
62
|
+
export interface DateColumn extends BaseColumn {
|
|
63
|
+
/** Overrides the `Intl.DateTimeFormatOptions` used by the `DateFormatter`. */
|
|
64
|
+
dateFormatOptions?: Intl.DateTimeFormatOptions;
|
|
65
|
+
type: "date";
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Formats date values with both a date and a time, displaying the time after
|
|
69
|
+
* the date. Uses the `@internationalized/date` `DateFormatter` (a locale-aware
|
|
70
|
+
* wrapper around `Intl.DateTimeFormat`) and accepts the same value types as
|
|
71
|
+
* {@link DateColumn}. Defaults to a `medium` date style with a `short` time
|
|
72
|
+
* style.
|
|
73
|
+
*/
|
|
74
|
+
export interface DateTimeColumn extends BaseColumn {
|
|
75
|
+
/** Overrides the `Intl.DateTimeFormatOptions` used by the `DateFormatter`. */
|
|
76
|
+
dateFormatOptions?: Intl.DateTimeFormatOptions;
|
|
77
|
+
type: "datetime";
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Renders a boolean value as a check icon for truthy values and nothing for
|
|
81
|
+
* falsy ones. The meaning stays available to assistive tech: `trueLabel`
|
|
82
|
+
* becomes the icon's accessible name, and `falseLabel` renders as
|
|
83
|
+
* visually-hidden text.
|
|
84
|
+
*/
|
|
85
|
+
export interface BooleanColumn extends BaseColumn {
|
|
86
|
+
/** Accessible label for falsy values. Defaults to `"No"`. */
|
|
87
|
+
falseLabel?: string;
|
|
88
|
+
/** Accessible label for truthy values. Defaults to `"Yes"`. */
|
|
89
|
+
trueLabel?: string;
|
|
90
|
+
type: "boolean";
|
|
91
|
+
}
|
|
92
|
+
/** Renders one of a fixed set of values. */
|
|
93
|
+
export interface EnumColumn extends BaseColumn {
|
|
94
|
+
/** The set of allowed values. Used by enum filters. */
|
|
95
|
+
enumValues: string[];
|
|
96
|
+
type: "enum";
|
|
97
|
+
}
|
|
98
|
+
/** Renders entirely through a required `render` function. */
|
|
99
|
+
export interface CustomColumn extends BaseColumn {
|
|
100
|
+
render: (row: unknown) => ReactNode;
|
|
101
|
+
type: "custom";
|
|
102
|
+
}
|
|
103
|
+
export type DataTableColumn = BooleanColumn | CurrencyColumn | CustomColumn | DateColumn | DateTimeColumn | EnumColumn | NumberColumn | StringColumn;
|
|
104
|
+
export {};
|
|
105
|
+
//# sourceMappingURL=DataTableColumn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataTableColumn.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/DataTableColumn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,MAAM,MAAM,mBAAmB,GAC3B,SAAS;AACX;;;GAGG;GACD,UAAU,GACV,QAAQ,GACR,MAAM,GACN,UAAU,GACV,MAAM,GACN,QAAQ,GACR,QAAQ,CAAA;AAEZ,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAA;AAE7D,UAAU,UAAU;IAClB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,oBAAoB,CAAA;IAC5B,0CAA0C;IAC1C,WAAW,EAAE,MAAM,CAAA;IACnB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,uDAAuD;IACvD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,mFAAmF;IACnF,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,SAAS,CAAA;IACpC,2CAA2C;IAC3C,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,IAAI,EAAE,QAAQ,CAAA;CACf;AAED,qFAAqF;AACrF,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAA;IAC9C,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,QAAQ,CAAA;CACf;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAA;IAC9C,IAAI,EAAE,UAAU,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC5C,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,IAAI,CAAC,qBAAqB,CAAA;IAC9C,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,IAAI,CAAC,qBAAqB,CAAA;IAC9C,IAAI,EAAE,UAAU,CAAA;CACjB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;CAChB;AAED,4CAA4C;AAC5C,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC5C,uDAAuD;IACvD,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,6DAA6D;AAC7D,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,SAAS,CAAA;IACnC,IAAI,EAAE,QAAQ,CAAA;CACf;AAED,MAAM,MAAM,eAAe,GACvB,aAAa,GACb,cAAc,GACd,YAAY,GACZ,UAAU,GACV,cAAc,GACd,UAAU,GACV,YAAY,GACZ,YAAY,CAAA"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { DataTableColumn, DataTableColumnAlign } from "./DataTableColumn";
|
|
3
|
+
/** Formats a single cell value. Produced per column by the registry. */
|
|
4
|
+
export type Formatter = (value: unknown) => ReactNode;
|
|
5
|
+
/** Renders a cell from the whole row. The unified shape a prepared column exposes. */
|
|
6
|
+
export type CellRenderer = (row: unknown) => ReactNode;
|
|
7
|
+
export declare function isEmpty(value: unknown): value is null | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Everything that varies per column `type`, co-located in one place: how the
|
|
10
|
+
* column's values are formatted, its default alignment, and whether it counts
|
|
11
|
+
* as numeric. Adding a column type is a single entry in
|
|
12
|
+
* {@link columnTypeDefinitions} below.
|
|
13
|
+
*/
|
|
14
|
+
export interface ColumnTypeDefinition<C extends DataTableColumn> {
|
|
15
|
+
/** Default alignment when the column doesn't set `align`. Omitted = "start". */
|
|
16
|
+
align?: DataTableColumnAlign;
|
|
17
|
+
/**
|
|
18
|
+
* Builds the per-column value formatter. Called once per column by
|
|
19
|
+
* {@link prepareColumn}, so the `Intl`/`DateFormatter` instances it closes
|
|
20
|
+
* over are constructed once per column rather than per render — provided the
|
|
21
|
+
* consumer passes a stable `columns` reference (see `prepareColumn`).
|
|
22
|
+
*/
|
|
23
|
+
createFormatter: (column: C) => Formatter;
|
|
24
|
+
/** Marks numeric types (drives the `--numeric` cell class). */
|
|
25
|
+
isNumeric?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Maps each column discriminant to a definition typed to that column's
|
|
29
|
+
* interface, so `createFormatter` receives the correctly-narrowed column (e.g.
|
|
30
|
+
* the `number` entry's `column` is `NumberColumn`). The mapped type also makes
|
|
31
|
+
* a missing entry a compile error.
|
|
32
|
+
*/
|
|
33
|
+
type ColumnTypeRegistry = {
|
|
34
|
+
[C in DataTableColumn as C["type"]]: ColumnTypeDefinition<C>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The single source of truth for per-type behavior. To add a new column type:
|
|
38
|
+
* declare its literal + interface in `DataTableColumn.ts`, then add one entry
|
|
39
|
+
* here.
|
|
40
|
+
*/
|
|
41
|
+
export declare const columnTypeDefinitions: ColumnTypeRegistry;
|
|
42
|
+
/** A column with everything the table needs to render its header and cells. */
|
|
43
|
+
export interface PreparedColumn {
|
|
44
|
+
align: DataTableColumnAlign;
|
|
45
|
+
column: DataTableColumn;
|
|
46
|
+
isNumeric: boolean;
|
|
47
|
+
renderCell: CellRenderer;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Turns a column into the everything-resolved shape the table renders from.
|
|
51
|
+
* This is the single place `render` and the type formatter are reconciled: a
|
|
52
|
+
* `render` prop and a value formatter both become one `renderCell(row)`, with
|
|
53
|
+
* `render` taking precedence. The type formatter is built at most once here
|
|
54
|
+
* (and only when there is no `render`), so callers should prepare each column
|
|
55
|
+
* once — `DataTable` does this in a `useMemo` keyed on the `columns` array, so
|
|
56
|
+
* pass a stable `columns` reference to avoid rebuilding formatters every render.
|
|
57
|
+
*/
|
|
58
|
+
export declare function prepareColumn(column: DataTableColumn): PreparedColumn;
|
|
59
|
+
export {};
|
|
60
|
+
//# sourceMappingURL=DataTableColumnTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataTableColumnTypes.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/DataTableColumnTypes.tsx"],"names":[],"mappings":"AAOA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE7C,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAW9E,wEAAwE;AACxE,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,CAAA;AAErD,sFAAsF;AACtF,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,SAAS,CAAA;AAEtD,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,SAAS,CAEjE;AAwCD;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,eAAe;IAC7D,gFAAgF;IAChF,KAAK,CAAC,EAAE,oBAAoB,CAAA;IAC5B;;;;;OAKG;IACH,eAAe,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,SAAS,CAAA;IACzC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;;GAKG;AACH,KAAK,kBAAkB,GAAG;KACvB,CAAC,IAAI,eAAe,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC;CAC7D,CAAA;AAID;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,kBAmFnC,CAAA;AAWD,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,oBAAoB,CAAA;IAC3B,MAAM,EAAE,eAAe,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,EAAE,YAAY,CAAA;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,GAAG,cAAc,CAoBrE"}
|