@planningcenter/tapestry 3.8.0-qa-1049.1 → 3.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.
Files changed (131) hide show
  1. package/dist/componentRegistration.js +7 -1
  2. package/dist/componentRegistration.js.map +1 -1
  3. package/dist/components/DataTable/DataTable.d.ts +39 -39
  4. package/dist/components/DataTable/DataTable.d.ts.map +1 -1
  5. package/dist/components/DataTable/DataTable.js +145 -24
  6. package/dist/components/DataTable/DataTable.js.map +1 -1
  7. package/dist/components/DataTable/DataTableColumn.d.ts +112 -0
  8. package/dist/components/DataTable/DataTableColumn.d.ts.map +1 -0
  9. package/dist/components/DataTable/DataTableColumnTypes.d.ts +60 -0
  10. package/dist/components/DataTable/DataTableColumnTypes.d.ts.map +1 -0
  11. package/dist/components/DataTable/DataTableColumnTypes.js +167 -0
  12. package/dist/components/DataTable/DataTableColumnTypes.js.map +1 -0
  13. package/dist/components/DataTable/DataTableRow.d.ts +2 -0
  14. package/dist/components/DataTable/DataTableRow.d.ts.map +1 -0
  15. package/dist/components/DataTable/DataTableRow.js +9 -0
  16. package/dist/components/DataTable/DataTableRow.js.map +1 -0
  17. package/dist/components/DataTable/index.d.ts +2 -1
  18. package/dist/components/DataTable/index.d.ts.map +1 -1
  19. package/dist/components/button-group/ButtonGroup.d.ts +35 -0
  20. package/dist/components/button-group/ButtonGroup.d.ts.map +1 -0
  21. package/dist/components/button-group/ButtonGroup.js +24 -0
  22. package/dist/components/button-group/ButtonGroup.js.map +1 -0
  23. package/dist/components/button-group/index.d.ts +4 -0
  24. package/dist/components/button-group/index.d.ts.map +1 -0
  25. package/dist/components/checkbox/Checkbox.d.ts +2 -2
  26. package/dist/components/checkbox/Checkbox.d.ts.map +1 -1
  27. package/dist/components/checkbox/Checkbox.js.map +1 -1
  28. package/dist/components/checkbox-group/CheckboxGroup.d.ts +1 -1
  29. package/dist/components/checkbox-group/CheckboxGroup.d.ts.map +1 -1
  30. package/dist/components/checkbox-group/CheckboxGroup.js.map +1 -1
  31. package/dist/components/combo-box/ComboBox.d.ts +1 -1
  32. package/dist/components/combo-box/ComboBox.d.ts.map +1 -1
  33. package/dist/components/combo-box/ComboBox.js.map +1 -1
  34. package/dist/components/date-picker/DatePicker.d.ts +2 -2
  35. package/dist/components/date-picker/DatePicker.d.ts.map +1 -1
  36. package/dist/components/date-picker/DatePicker.js +39 -39
  37. package/dist/components/date-picker/DatePicker.js.map +1 -1
  38. package/dist/components/dropdown/Dropdown.d.ts +9 -8
  39. package/dist/components/dropdown/Dropdown.d.ts.map +1 -1
  40. package/dist/components/dropdown/Dropdown.js +2 -2
  41. package/dist/components/dropdown/Dropdown.js.map +1 -1
  42. package/dist/components/input/Input.d.ts +1 -1
  43. package/dist/components/input/Input.d.ts.map +1 -1
  44. package/dist/components/input/Input.js.map +1 -1
  45. package/dist/components/input-text-base/InputTextBase.d.ts +1 -1
  46. package/dist/components/input-text-base/InputTextBase.d.ts.map +1 -1
  47. package/dist/components/input-text-base/InputTextBase.js.map +1 -1
  48. package/dist/components/modal/Modal.d.ts +20 -0
  49. package/dist/components/modal/Modal.d.ts.map +1 -0
  50. package/dist/components/modal/Modal.js +113 -0
  51. package/dist/components/modal/Modal.js.map +1 -0
  52. package/dist/components/modal/ModalBody.d.ts +21 -0
  53. package/dist/components/modal/ModalBody.d.ts.map +1 -0
  54. package/dist/components/modal/ModalBody.js +18 -0
  55. package/dist/components/modal/ModalBody.js.map +1 -0
  56. package/dist/components/modal/ModalContext.d.ts +9 -0
  57. package/dist/components/modal/ModalContext.d.ts.map +1 -0
  58. package/dist/components/modal/ModalContext.js +6 -0
  59. package/dist/components/modal/ModalContext.js.map +1 -0
  60. package/dist/components/modal/ModalFooter.d.ts +22 -0
  61. package/dist/components/modal/ModalFooter.d.ts.map +1 -0
  62. package/dist/components/modal/ModalFooter.js +21 -0
  63. package/dist/components/modal/ModalFooter.js.map +1 -0
  64. package/dist/components/modal/ModalHeader.d.ts +23 -0
  65. package/dist/components/modal/ModalHeader.d.ts.map +1 -0
  66. package/dist/components/modal/ModalHeader.js +32 -0
  67. package/dist/components/modal/ModalHeader.js.map +1 -0
  68. package/dist/components/modal/index.d.ts +10 -0
  69. package/dist/components/modal/index.d.ts.map +1 -0
  70. package/dist/components/number-stepper/NumberStepper.d.ts +1 -1
  71. package/dist/components/number-stepper/NumberStepper.d.ts.map +1 -1
  72. package/dist/components/number-stepper/NumberStepper.js +11 -11
  73. package/dist/components/number-stepper/NumberStepper.js.map +1 -1
  74. package/dist/components/radio/Radio.d.ts +2 -2
  75. package/dist/components/radio/Radio.d.ts.map +1 -1
  76. package/dist/components/radio/Radio.js.map +1 -1
  77. package/dist/components/radio-group/RadioGroup.d.ts +2 -2
  78. package/dist/components/radio-group/RadioGroup.d.ts.map +1 -1
  79. package/dist/components/radio-group/RadioGroup.js.map +1 -1
  80. package/dist/components/select/Select.d.ts +1 -1
  81. package/dist/components/select/Select.d.ts.map +1 -1
  82. package/dist/components/select/Select.js +6 -6
  83. package/dist/components/select/Select.js.map +1 -1
  84. package/dist/components/select/SelectNative.js +6 -6
  85. package/dist/components/select/SelectNative.js.map +1 -1
  86. package/dist/components/select/SelectOptions.js +9 -9
  87. package/dist/components/select/SelectOptions.js.map +1 -1
  88. package/dist/components/select/SelectPopover.js +14 -14
  89. package/dist/components/select/SelectPopover.js.map +1 -1
  90. package/dist/components/text-area/TextArea.d.ts +1 -1
  91. package/dist/components/text-area/TextArea.d.ts.map +1 -1
  92. package/dist/components/text-area/TextArea.js.map +1 -1
  93. package/dist/components/time-field/TimeField.d.ts +1 -1
  94. package/dist/components/time-field/TimeField.d.ts.map +1 -1
  95. package/dist/components/time-field/TimeField.js.map +1 -1
  96. package/dist/components/toggle-switch/ToggleSwitch.d.ts +2 -2
  97. package/dist/components/toggle-switch/ToggleSwitch.d.ts.map +1 -1
  98. package/dist/components/toggle-switch/ToggleSwitch.js.map +1 -1
  99. package/dist/ext/@internationalized/date/dist/private/DateFormatter.js +139 -0
  100. package/dist/ext/@internationalized/date/dist/private/DateFormatter.js.map +1 -0
  101. package/dist/index.css +159 -4
  102. package/dist/index.css.map +1 -1
  103. package/dist/index.d.ts +2 -0
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +5 -0
  106. package/dist/index.js.map +1 -1
  107. package/dist/packages/tapestry-render/dist/buildComponent-Dr2UOgFa.js.map +1 -0
  108. package/dist/packages/tapestry-render/dist/index.js.map +1 -0
  109. package/dist/packages/tapestry-render/dist/legacy.js.map +1 -0
  110. package/dist/packages/tapestry-render/dist/registry.js.map +1 -0
  111. package/dist/reactRender.css +4454 -4114
  112. package/dist/reactRender.css.map +1 -1
  113. package/dist/reactRender.js +2 -2
  114. package/dist/reactRenderLegacy.css +4454 -4114
  115. package/dist/reactRenderLegacy.css.map +1 -1
  116. package/dist/reactRenderLegacy.js +2 -2
  117. package/dist/unstable.css +374 -34
  118. package/dist/unstable.css.map +1 -1
  119. package/dist/unstable.js +5 -0
  120. package/dist/unstable.js.map +1 -1
  121. package/dist/utilities/Icon.js +2 -2
  122. package/dist/utilities/Icon.js.map +1 -1
  123. package/package.json +5 -5
  124. package/dist/tapestry-render/dist/buildComponent-Dr2UOgFa.js.map +0 -1
  125. package/dist/tapestry-render/dist/index.js.map +0 -1
  126. package/dist/tapestry-render/dist/legacy.js.map +0 -1
  127. package/dist/tapestry-render/dist/registry.js.map +0 -1
  128. /package/dist/{tapestry-render → packages/tapestry-render}/dist/buildComponent-Dr2UOgFa.js +0 -0
  129. /package/dist/{tapestry-render → packages/tapestry-render}/dist/index.js +0 -0
  130. /package/dist/{tapestry-render → packages/tapestry-render}/dist/legacy.js +0 -0
  131. /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":";;;;;;AAOAA,CAAQ,CAAC,YAAY,EAAE,UAAU,CAAC;AAClCA,CAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;AAC5BA,CAAQ,CAAC,aAAa,EAAE,WAAW,CAAC;AACpCA,CAAQ,CAAC,gBAAgB,EAAE,cAAc,CAAC"}
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,50 +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
- export type DataTableColumnType = "boolean" | "currency" | "custom" | "date" | "enum" | "number" | "string";
6
- interface BaseColumn {
7
- /** Determines how items align in the table. */
8
- align?: "center" | "end" | "start";
9
- /** Human-readable column header label. */
10
- headerLabel: string;
5
+ import type { DataTableColumn } from "./DataTableColumn";
6
+ export type Sort = {
7
+ column: string;
8
+ direction: "asc" | "desc";
9
+ };
10
+ export type DataTableLoadingState = "error" | "idle" | "loading";
11
+ export interface DataTableProps {
11
12
  /**
12
- * Visually hides the header label. The text is still rendered (visually
13
- * hidden via CSS) so it remains announced to assistive tech.
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.
14
16
  */
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 `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
- /** Determines rendering and filter behavior. */
27
- type: DataTableColumnType;
28
- }
29
- export interface EnumColumn extends BaseColumn {
30
- enumValues: string[];
31
- type: "enum";
32
- }
33
- export interface NumberColumn extends BaseColumn {
34
- max?: number;
35
- min?: number;
36
- step?: number;
37
- type: "number";
38
- }
39
- export type DataTableColumn = BaseColumn | EnumColumn | NumberColumn;
40
- export interface DataTableProps {
41
- /** Columns define what data is displayed and how. */
42
17
  columns: DataTableColumn[];
43
18
  /** The array of rows to display. */
44
19
  data: unknown[];
45
20
  /** Rendered in place of the table body when there is no data. */
46
21
  emptyState?: ReactNode;
22
+ /** Rendered in place of the table body when `loadingState` is `"error"`. */
23
+ errorState?: ReactNode;
47
24
  /** Returns a stable identity for each row. Defaults to `row.id`. */
48
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;
35
+ /** Callback invoked when the sort state changes. */
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;
41
+ /** The current sort state for the table. */
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;
49
45
  }
50
46
  type AriaTablePropsToOmit = "children" | "slot";
51
47
  type AriaTablePropsToInclude = never;
@@ -53,14 +49,18 @@ export type DataTableElementProps = CombineAriaPropsWithCustomProps<AriaTablePro
53
49
  /**
54
50
  * A table for displaying tabular data. Built on React Aria's `Table`.
55
51
  *
56
- * This is an early skeleton: only column headers and string cell values are
57
- * rendered. Sorting, filtering, pagination, selection, and other RFC
58
- * features are not yet implemented.
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.
59
59
  *
60
60
  * @component
61
61
  */
62
62
  export declare const DataTable: {
63
- ({ className, columns, data, emptyState, getRowId, ...restProps }: DataTableElementProps): React.JSX.Element;
63
+ ({ className, columns, data, emptyState, errorState, getRowId, loadingState, scrollContainerRef, scrollContainerSelector, stickyHeader, sortBy, onSortChange, ...restProps }: DataTableElementProps): React.JSX.Element;
64
64
  displayName: string;
65
65
  };
66
66
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/DataTable.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,2BAA2B,CAAA;AAEhF,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EAOL,KAAK,UAAU,IAAI,cAAc,EAClC,MAAM,6BAA6B,CAAA;AAEpC,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,UAAU,GACV,QAAQ,GACR,MAAM,GACN,MAAM,GACN,QAAQ,GACR,QAAQ,CAAA;AAEZ,UAAU,UAAU;IAClB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAA;IAClC,0CAA0C;IAC1C,WAAW,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,uDAAuD;IACvD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,4DAA4D;IAC5D,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;IAClB,gDAAgD;IAChD,IAAI,EAAE,mBAAmB,CAAA;CAC1B;AAED,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC5C,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,QAAQ,CAAA;CACf;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,CAAA;AAEpE,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,oCAAoC;IACpC,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,iEAAiE;IACjE,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,MAAM,CAAA;CAC7C;AAED,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;AAkBD;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS;uEAOnB,qBAAqB;;CA4CvB,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;;CAwGvB,CAAA"}
@@ -1,39 +1,160 @@
1
+ import LoadingSpinner from '../internal/LoadingSpinner.js';
2
+ import Icon from '../../utilities/Icon.js';
1
3
  import classNames from 'classnames';
2
- import React__default from 'react';
4
+ import React__default, { useMemo, useRef, useState, useLayoutEffect } from 'react';
3
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';
4
8
 
5
- function getCellValue(row, column) {
6
- if (column.render)
7
- return column.render(row);
8
- const value = row?.[column.name];
9
- return value === undefined || value === null ? "" : String(value);
10
- }
11
- function getRowKey(row, index, getRowId) {
12
- if (getRowId)
13
- return getRowId(row);
14
- const id = row?.id;
15
- return typeof id === "number" || typeof id === "string" ? id : index;
16
- }
9
+ const DEFAULT_ERROR_STATE = "Something went wrong.";
17
10
  /**
18
11
  * A table for displaying tabular data. Built on React Aria's `Table`.
19
12
  *
20
- * This is an early skeleton: only column headers and string cell values are
21
- * rendered. Sorting, filtering, pagination, selection, and other RFC
22
- * features are not yet implemented.
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.
23
20
  *
24
21
  * @component
25
22
  */
26
- const DataTable = ({ className, columns, data, emptyState, getRowId, ...restProps }) => {
23
+ const DataTable = ({ className, columns, data, emptyState, errorState, getRowId, loadingState = "idle", scrollContainerRef, scrollContainerSelector = ".content-wrap", stickyHeader, sortBy, onSortChange, ...restProps }) => {
27
24
  const combinedClassName = classNames("tds-data-table", className);
28
- return (React__default.createElement("div", { className: "tds-data-table-wrapper" },
29
- React__default.createElement(Table, { ...restProps, className: combinedClassName },
30
- React__default.createElement(TableHeader, { className: "tds-data-table-header" }, columns.map((column, index) => (React__default.createElement(Column, { key: column.name, id: column.name, isRowHeader: index === 0, className: "tds-data-table-column" }, column.hideLabel ? (React__default.createElement("span", { className: "tds-data-table-column-label--hidden" }, column.headerLabel)) : (column.headerLabel))))),
31
- React__default.createElement(TableBody, { className: "tds-data-table-body", renderEmptyState: emptyState ? () => emptyState : undefined }, data.map((row, rowIndex) => {
32
- const rowKey = getRowKey(row, rowIndex, getRowId);
33
- return (React__default.createElement(Row, { key: rowKey, id: rowKey, className: "tds-data-table-row" }, columns.map((column) => (React__default.createElement(Cell, { key: column.name, className: "tds-data-table-cell" }, getCellValue(row, column))))));
34
- })))));
25
+ const sortDescriptor = useSortDescriptor(sortBy);
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
+ }) },
48
+ React__default.createElement("div", { className: "tds-data-table-scroll" },
49
+ React__default.createElement(Table, { ...restProps, className: combinedClassName, sortDescriptor: sortDescriptor, onSortChange: (sort) => {
50
+ onSortChange?.(serializeSort(sort));
51
+ } },
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 },
55
+ column.hideLabel ? (React__default.createElement("span", { className: "tds-data-table-column-label--hidden" }, column.headerLabel)) : (column.headerLabel),
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 })))))),
57
+ React__default.createElement(TableBody, { className: "tds-data-table-body", renderEmptyState: renderEmptyState }, data.map((row, rowIndex) => {
58
+ const rowKey = getRowKey(row, rowIndex, getRowId);
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, "data-pii": prepared.column.pii || undefined, 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))))));
63
+ }))))));
35
64
  };
36
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
+ }
84
+ function useSortDescriptor(sortBy) {
85
+ return useMemo(() => {
86
+ if (!sortBy)
87
+ return undefined;
88
+ return deserializeSort(sortBy);
89
+ }, [sortBy]);
90
+ }
91
+ function deserializeSort(sort) {
92
+ if (sort.direction === "desc")
93
+ return { column: sort.column, direction: "descending" };
94
+ return { column: sort.column, direction: "ascending" };
95
+ }
96
+ function serializeSort(sort) {
97
+ if (sort.direction === "descending")
98
+ return { column: sort.column, direction: "desc" };
99
+ return { column: sort.column, direction: "asc" };
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
+ }
37
158
 
38
159
  export { DataTable };
39
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 type { CombineAriaPropsWithCustomProps } from \"@utilities/reactAriaProps\"\nimport classNames from \"classnames\"\nimport React, { type ReactNode } from \"react\"\nimport {\n Cell,\n Column,\n Row,\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\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}\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 ...restProps\n}: DataTableElementProps) => {\n const combinedClassName = classNames(\"tds-data-table\", className)\n\n return (\n <div className=\"tds-data-table-wrapper\">\n <Table {...restProps} className={combinedClassName}>\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 >\n {column.hideLabel ? (\n <span className=\"tds-data-table-column-label--hidden\">\n {column.headerLabel}\n </span>\n ) : (\n column.headerLabel\n )}\n </Column>\n ))}\n </TableHeader>\n <TableBody\n className=\"tds-data-table-body\"\n renderEmptyState={emptyState ? () => emptyState : undefined}\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 )\n}\n\nDataTable.displayName = \"DataTable\"\n"],"names":["React"],"mappings":";;;;AAoFA,SAAS,YAAY,CAAC,GAAY,EAAE,MAAuB,EAAA;IACzD,IAAI,MAAM,CAAC,MAAM;AAAE,QAAA,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;IAC5C,MAAM,KAAK,GAAI,GAA+B,GAAG,MAAM,CAAC,IAAI,CAAC;AAC7D,IAAA,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,GAAG,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC;AACnE;AAEA,SAAS,SAAS,CAChB,GAAY,EACZ,KAAa,EACb,QAA4C,EAAA;AAE5C,IAAA,IAAI,QAAQ;AAAE,QAAA,OAAO,QAAQ,CAAC,GAAG,CAAC;AAClC,IAAA,MAAM,EAAE,GAAI,GAA+B,EAAE,EAAE;AAC/C,IAAA,OAAO,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,GAAG,EAAE,GAAG,KAAK;AACtE;AAEA;;;;;;;;AAQG;MACU,SAAS,GAAG,CAAC,EACxB,SAAS,EACT,OAAO,EACP,IAAI,EACJ,UAAU,EACV,QAAQ,EACR,GAAG,SAAS,EACU,KAAI;IAC1B,MAAM,iBAAiB,GAAG,UAAU,CAAC,gBAAgB,EAAE,SAAS,CAAC;AAEjE,IAAA,QACEA,cAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,wBAAwB,EAAA;AACrC,QAAAA,cAAA,CAAA,aAAA,CAAC,KAAK,EAAA,EAAA,GAAK,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAA;AAChD,YAAAA,cAAA,CAAA,aAAA,CAAC,WAAW,EAAA,EAAC,SAAS,EAAC,uBAAuB,EAAA,EAC3C,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,MACzBA,cAAA,CAAA,aAAA,CAAC,MAAM,EAAA,EACL,GAAG,EAAE,MAAM,CAAC,IAAI,EAChB,EAAE,EAAE,MAAM,CAAC,IAAI,EACf,WAAW,EAAE,KAAK,KAAK,CAAC,EACxB,SAAS,EAAC,uBAAuB,EAAA,EAEhC,MAAM,CAAC,SAAS,IACfA,cAAA,CAAA,aAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,qCAAqC,EAAA,EAClD,MAAM,CAAC,WAAW,CACd,KAEP,MAAM,CAAC,WAAW,CACnB,CACM,CACV,CAAC,CACU;AACd,YAAAA,cAAA,CAAA,aAAA,CAAC,SAAS,EAAA,EACR,SAAS,EAAC,qBAAqB,EAC/B,gBAAgB,EAAE,UAAU,GAAG,MAAM,UAAU,GAAG,SAAS,EAAA,EAE1D,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,QAAQ,KAAI;gBAC1B,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC;gBACjD,QACEA,6BAAC,GAAG,EAAA,EAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAC,oBAAoB,EAAA,EACzD,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,MAClBA,cAAA,CAAA,aAAA,CAAC,IAAI,IAAC,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,SAAS,EAAC,qBAAqB,IACpD,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CACrB,CACR,CAAC,CACE;AAEV,YAAA,CAAC,CAAC,CACQ,CACN,CACJ;AAEV;AAEA,SAAS,CAAC,WAAW,GAAG,WAAW;;;;"}
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 data-pii={prepared.column.pii || undefined}\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,112 @@
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
+ /**
19
+ * Marks this column as containing personally identifiable information. When
20
+ * set, every body cell in the column renders a `data-pii` attribute. Note
21
+ * the first column's body cells render as row headers (`th`), so target
22
+ * `[data-pii]` rather than `td[data-pii]`.
23
+ */
24
+ pii?: boolean;
25
+ /** Enables filtering on this column. See Filtering. */
26
+ queryable?: boolean;
27
+ /** Custom cell renderer. Defaults to a type-aware format of `row[column.name]`. */
28
+ render?: (row: unknown) => ReactNode;
29
+ /** Enables the resizing of that column. */
30
+ resizable?: boolean;
31
+ /** Enables sorting on this column. */
32
+ sortable?: boolean;
33
+ }
34
+ /** Renders the raw value as text. */
35
+ export interface StringColumn extends BaseColumn {
36
+ type: "string";
37
+ }
38
+ /** Formats numeric values with `Intl.NumberFormat`. Aligns to the end by default. */
39
+ export interface NumberColumn extends BaseColumn {
40
+ /** Maximum value. Used by numeric filters. */
41
+ max?: number;
42
+ /** Minimum value. Used by numeric filters. */
43
+ min?: number;
44
+ /** Overrides the `Intl.NumberFormat` options used to display values. */
45
+ numberFormatOptions?: Intl.NumberFormatOptions;
46
+ /** Step increment. Used by numeric filters. */
47
+ step?: number;
48
+ type: "number";
49
+ }
50
+ /**
51
+ * @experimental Unstable — subject to change. Currency support is in beta and
52
+ * is currently hardwired to USD; the API may change without notice.
53
+ *
54
+ * Formats numeric values as currency. Aligns to the end by default.
55
+ */
56
+ export interface CurrencyColumn extends BaseColumn {
57
+ /** Overrides the `Intl.NumberFormat` options used to display values. */
58
+ numberFormatOptions?: Intl.NumberFormatOptions;
59
+ type: "currency";
60
+ }
61
+ /**
62
+ * Formats date values (without a time) with the `@internationalized/date`
63
+ * `DateFormatter` (a locale-aware wrapper around `Intl.DateTimeFormat`).
64
+ * Accepts `DateValue` objects from `@internationalized/date` (`CalendarDate`,
65
+ * `CalendarDateTime`, `ZonedDateTime`), `Date` objects, timestamps, or date
66
+ * strings (ISO `YYYY-MM-DD` strings are parsed as local dates to avoid
67
+ * time-zone drift). Defaults to a `medium` date style.
68
+ */
69
+ export interface DateColumn extends BaseColumn {
70
+ /** Overrides the `Intl.DateTimeFormatOptions` used by the `DateFormatter`. */
71
+ dateFormatOptions?: Intl.DateTimeFormatOptions;
72
+ type: "date";
73
+ }
74
+ /**
75
+ * Formats date values with both a date and a time, displaying the time after
76
+ * the date. Uses the `@internationalized/date` `DateFormatter` (a locale-aware
77
+ * wrapper around `Intl.DateTimeFormat`) and accepts the same value types as
78
+ * {@link DateColumn}. Defaults to a `medium` date style with a `short` time
79
+ * style.
80
+ */
81
+ export interface DateTimeColumn extends BaseColumn {
82
+ /** Overrides the `Intl.DateTimeFormatOptions` used by the `DateFormatter`. */
83
+ dateFormatOptions?: Intl.DateTimeFormatOptions;
84
+ type: "datetime";
85
+ }
86
+ /**
87
+ * Renders a boolean value as a check icon for truthy values and nothing for
88
+ * falsy ones. The meaning stays available to assistive tech: `trueLabel`
89
+ * becomes the icon's accessible name, and `falseLabel` renders as
90
+ * visually-hidden text.
91
+ */
92
+ export interface BooleanColumn extends BaseColumn {
93
+ /** Accessible label for falsy values. Defaults to `"No"`. */
94
+ falseLabel?: string;
95
+ /** Accessible label for truthy values. Defaults to `"Yes"`. */
96
+ trueLabel?: string;
97
+ type: "boolean";
98
+ }
99
+ /** Renders one of a fixed set of values. */
100
+ export interface EnumColumn extends BaseColumn {
101
+ /** The set of allowed values. Used by enum filters. */
102
+ enumValues: string[];
103
+ type: "enum";
104
+ }
105
+ /** Renders entirely through a required `render` function. */
106
+ export interface CustomColumn extends BaseColumn {
107
+ render: (row: unknown) => ReactNode;
108
+ type: "custom";
109
+ }
110
+ export type DataTableColumn = BooleanColumn | CurrencyColumn | CustomColumn | DateColumn | DateTimeColumn | EnumColumn | NumberColumn | StringColumn;
111
+ export {};
112
+ //# 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;;;;;OAKG;IACH,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,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"}