@cdx-ui/components 0.3.12 → 0.3.14
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/coverage.svg +1 -1
- package/docs/components/alert-dialog/content.md +359 -0
- package/docs/components/bar-chart/content.md +108 -2
- package/docs/components/data-grid/content.md +551 -5
- package/docs/components/dialog/content.md +468 -13
- package/docs/components/line-chart/content.md +1 -0
- package/docs/components/list-item/content.md +1 -1
- package/docs/components/table/content.md +84 -0
- package/docs/foundations/expressions/icons/content.md +1 -1
- package/docs/llms.txt +2 -0
- package/docs/resources/release-notes/0.3.12/content.md +15 -0
- package/docs/resources/release-notes/0.3.13/content.md +21 -0
- package/docs/resources/release-notes/content.md +2 -0
- package/lib/commonjs/components/BarChart/geometry.js +95 -10
- package/lib/commonjs/components/BarChart/geometry.js.map +1 -1
- package/lib/commonjs/components/BarChart/index.js +8 -6
- package/lib/commonjs/components/BarChart/index.js.map +1 -1
- package/lib/commonjs/components/CurrencyInput/styles.js +1 -1
- package/lib/commonjs/components/ListItem/styles.js +1 -1
- package/lib/commonjs/components/ListItem/styles.js.map +1 -1
- package/lib/commonjs/figma/CurrencyInput.figma.js +4 -4
- package/lib/commonjs/figma/CurrencyInput.figma.js.map +1 -1
- package/lib/commonjs/figma/ProgressBar.figma.js +8 -5
- package/lib/commonjs/figma/ProgressBar.figma.js.map +1 -1
- package/lib/commonjs/figma/ProgressCircle.figma.js +8 -2
- package/lib/commonjs/figma/ProgressCircle.figma.js.map +1 -1
- package/lib/module/components/BarChart/geometry.js +93 -9
- package/lib/module/components/BarChart/geometry.js.map +1 -1
- package/lib/module/components/BarChart/index.js +9 -7
- package/lib/module/components/BarChart/index.js.map +1 -1
- package/lib/module/components/CurrencyInput/styles.js +1 -1
- package/lib/module/components/ListItem/styles.js +1 -1
- package/lib/module/components/ListItem/styles.js.map +1 -1
- package/lib/module/figma/CurrencyInput.figma.js +4 -4
- package/lib/module/figma/CurrencyInput.figma.js.map +1 -1
- package/lib/module/figma/ProgressBar.figma.js +8 -5
- package/lib/module/figma/ProgressBar.figma.js.map +1 -1
- package/lib/module/figma/ProgressCircle.figma.js +8 -2
- package/lib/module/figma/ProgressCircle.figma.js.map +1 -1
- package/lib/typescript/components/BarChart/geometry.d.ts +19 -2
- package/lib/typescript/components/BarChart/geometry.d.ts.map +1 -1
- package/lib/typescript/components/BarChart/index.d.ts +4 -2
- package/lib/typescript/components/BarChart/index.d.ts.map +1 -1
- package/lib/typescript/components/ListItem/index.d.ts +2 -2
- package/lib/typescript/components/chart/types.d.ts +7 -0
- package/lib/typescript/components/chart/types.d.ts.map +1 -1
- package/lib/typescript/figma/CurrencyInput.figma.d.ts.map +1 -1
- package/lib/typescript/figma/ProgressBar.figma.d.ts.map +1 -1
- package/lib/typescript/figma/ProgressCircle.figma.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/components/BarChart/geometry.ts +126 -10
- package/src/components/BarChart/index.tsx +24 -16
- package/src/components/CurrencyInput/styles.ts +1 -1
- package/src/components/ListItem/index.tsx +2 -2
- package/src/components/ListItem/styles.ts +1 -1
- package/src/components/chart/types.ts +7 -0
|
@@ -107,10 +107,10 @@ type Product = {
|
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
const columns: GridColDef<Product>[] = [
|
|
110
|
-
{ field: 'name', headerName: 'Name', type: 'string', flex: 1, minWidth:
|
|
110
|
+
{ field: 'name', headerName: 'Name', type: 'string', flex: 1, minWidth: 160 },
|
|
111
111
|
{ field: 'price', headerName: 'Price', type: 'number', width: 120 },
|
|
112
112
|
{ field: 'listed', headerName: 'Listed', type: 'date', width: 140 },
|
|
113
|
-
{ field: 'updated', headerName: 'Updated', type: 'dateTime', width:
|
|
113
|
+
{ field: 'updated', headerName: 'Updated', type: 'dateTime', width: 200 },
|
|
114
114
|
{ field: 'active', headerName: 'Active', type: 'boolean', width: 100 },
|
|
115
115
|
{
|
|
116
116
|
field: 'status',
|
|
@@ -193,10 +193,10 @@ type Product = {
|
|
|
193
193
|
};
|
|
194
194
|
|
|
195
195
|
const columns: GridColDef<Product>[] = [
|
|
196
|
-
{ field: 'name', headerName: 'Name', type: 'string', flex: 1, minWidth:
|
|
196
|
+
{ field: 'name', headerName: 'Name', type: 'string', flex: 1, minWidth: 160 },
|
|
197
197
|
{ field: 'price', headerName: 'Price', type: 'number', width: 120 },
|
|
198
198
|
{ field: 'listed', headerName: 'Listed', type: 'date', width: 140 },
|
|
199
|
-
{ field: 'updated', headerName: 'Updated', type: 'dateTime', width:
|
|
199
|
+
{ field: 'updated', headerName: 'Updated', type: 'dateTime', width: 200 },
|
|
200
200
|
{ field: 'active', headerName: 'Active', type: 'boolean', width: 100 },
|
|
201
201
|
{
|
|
202
202
|
field: 'status',
|
|
@@ -808,6 +808,550 @@ export default function DataGridCheckboxCellsDemo() {
|
|
|
808
808
|
}
|
|
809
809
|
```
|
|
810
810
|
|
|
811
|
+
## Column visibility
|
|
812
|
+
|
|
813
|
+
Set a field to `false` in `columnVisibilityModel` to hide its column while keeping it in `columns`. A hidden column still sorts, filters, and matches search, and the filter panel still lists it, so a single visible column can show a rich cell while other fields drive the toolbar controls. With `treeData`, the first visible column becomes the tree column. The grid has no built-in control for showing or hiding columns, so keep the model in your own state. The API picker in [Tree data](#tree-data) uses it.
|
|
814
|
+
|
|
815
|
+
## Tree data
|
|
816
|
+
|
|
817
|
+
Set `treeData` and return each row's path from `getTreeDataPath`, such as `['Statements', '2026', 'Checking 4821 Oct.pdf']`. The first column becomes the tree column: an expand toggle, then the row content, indented by depth. Pass `groupingColDef` to override that column's header, width, or cell. A row attaches to its nearest ancestor that exists in `rows`, so a path does not need a row for every level.
|
|
818
|
+
|
|
819
|
+
Every other column renders, sorts, and filters as it does in a flat grid, so parents and children share the same columns. Leave a value empty where it does not apply to a level, such as a folder's size. Sorting reorders siblings at every level and keeps children under their parent.
|
|
820
|
+
|
|
821
|
+
Groups start collapsed. `defaultGroupingExpansionDepth` expands groups down to a depth (`-1` expands everything), and `isGroupExpandedByDefault` decides per row. While search, header filters, or the filter panel are active, a row stays visible when it or a descendant matches, and every group on the way to a match opens. Clearing the filter restores the groups the user had open.
|
|
822
|
+
|
|
823
|
+
Pagination counts top-level rows, so a page never splits a group. Tree data sorts and filters on the client. It cannot be combined with server pagination, sorting, or filtering.
|
|
824
|
+
|
|
825
|
+
This document library uses `groupingColDef` to draw a file-type icon and each folder's file count in the tree column, and opens the current year's statements with `isGroupExpandedByDefault`.
|
|
826
|
+
|
|
827
|
+
```tsx
|
|
828
|
+
import { HStack, Icon, type IconColor, Text } from '@cdx-ui/components';
|
|
829
|
+
import {
|
|
830
|
+
DataGrid,
|
|
831
|
+
type DataGridProps,
|
|
832
|
+
type GridColDef,
|
|
833
|
+
type GridTreeNodeParams,
|
|
834
|
+
} from '@cdx-ui/data-grid';
|
|
835
|
+
import { Csv, Description, Folder, type ForgeIcon, PictureAsPdf, TableChart } from '@cdx-ui/icons';
|
|
836
|
+
|
|
837
|
+
type DocumentType = 'Folder' | 'PDF' | 'CSV' | 'Spreadsheet' | 'Document';
|
|
838
|
+
|
|
839
|
+
// GridValidRowModel's index signature rejects otherwise equivalent interfaces.
|
|
840
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
841
|
+
type BankDocument = {
|
|
842
|
+
id: string;
|
|
843
|
+
path: string[];
|
|
844
|
+
type: DocumentType;
|
|
845
|
+
modified: Date;
|
|
846
|
+
size?: number;
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
const typeIcons: Record<DocumentType, { icon: ForgeIcon; color: IconColor }> = {
|
|
850
|
+
Folder: { icon: Folder, color: 'action' },
|
|
851
|
+
PDF: { icon: PictureAsPdf, color: 'danger' },
|
|
852
|
+
CSV: { icon: Csv, color: 'success' },
|
|
853
|
+
Spreadsheet: { icon: TableChart, color: 'success' },
|
|
854
|
+
Document: { icon: Description, color: 'info' },
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
const documents: BankDocument[] = [
|
|
858
|
+
{ id: 'statements', path: ['Statements'], type: 'Folder', modified: new Date(2026, 9, 31) },
|
|
859
|
+
{
|
|
860
|
+
id: 'statements-2026',
|
|
861
|
+
path: ['Statements', '2026'],
|
|
862
|
+
type: 'Folder',
|
|
863
|
+
modified: new Date(2026, 9, 31),
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
id: 'checking-2026-10',
|
|
867
|
+
path: ['Statements', '2026', 'Checking 4821 Oct.pdf'],
|
|
868
|
+
type: 'PDF',
|
|
869
|
+
modified: new Date(2026, 9, 31),
|
|
870
|
+
size: 184_320,
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
id: 'checking-2026-09',
|
|
874
|
+
path: ['Statements', '2026', 'Checking 4821 Sep.pdf'],
|
|
875
|
+
type: 'PDF',
|
|
876
|
+
modified: new Date(2026, 8, 30),
|
|
877
|
+
size: 176_128,
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
id: 'savings-2026-q3',
|
|
881
|
+
path: ['Statements', '2026', 'Savings 9310 Q3.pdf'],
|
|
882
|
+
type: 'PDF',
|
|
883
|
+
modified: new Date(2026, 8, 30),
|
|
884
|
+
size: 98_304,
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
id: 'statements-2025',
|
|
888
|
+
path: ['Statements', '2025'],
|
|
889
|
+
type: 'Folder',
|
|
890
|
+
modified: new Date(2025, 11, 31),
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
id: 'checking-2025-12',
|
|
894
|
+
path: ['Statements', '2025', 'Checking 4821 Dec.pdf'],
|
|
895
|
+
type: 'PDF',
|
|
896
|
+
modified: new Date(2025, 11, 31),
|
|
897
|
+
size: 192_512,
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
id: 'savings-2025-q4',
|
|
901
|
+
path: ['Statements', '2025', 'Savings 9310 Q4.pdf'],
|
|
902
|
+
type: 'PDF',
|
|
903
|
+
modified: new Date(2025, 11, 31),
|
|
904
|
+
size: 101_376,
|
|
905
|
+
},
|
|
906
|
+
{ id: 'tax', path: ['Tax documents'], type: 'Folder', modified: new Date(2026, 0, 31) },
|
|
907
|
+
{
|
|
908
|
+
id: 'tax-1099-int',
|
|
909
|
+
path: ['Tax documents', '1099-INT 2025.pdf'],
|
|
910
|
+
type: 'PDF',
|
|
911
|
+
modified: new Date(2026, 0, 31),
|
|
912
|
+
size: 64_512,
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
id: 'tax-1098',
|
|
916
|
+
path: ['Tax documents', '1098 Mortgage 2025.pdf'],
|
|
917
|
+
type: 'PDF',
|
|
918
|
+
modified: new Date(2026, 0, 29),
|
|
919
|
+
size: 71_680,
|
|
920
|
+
},
|
|
921
|
+
{ id: 'loans', path: ['Loans'], type: 'Folder', modified: new Date(2026, 8, 15) },
|
|
922
|
+
{
|
|
923
|
+
id: 'mortgage',
|
|
924
|
+
path: ['Loans', 'Home mortgage 7702'],
|
|
925
|
+
type: 'Folder',
|
|
926
|
+
modified: new Date(2026, 8, 15),
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
id: 'mortgage-closing',
|
|
930
|
+
path: ['Loans', 'Home mortgage 7702', 'Closing disclosure.pdf'],
|
|
931
|
+
type: 'PDF',
|
|
932
|
+
modified: new Date(2024, 4, 22),
|
|
933
|
+
size: 1_258_291,
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
id: 'mortgage-amortization',
|
|
937
|
+
path: ['Loans', 'Home mortgage 7702', 'Amortization.xlsx'],
|
|
938
|
+
type: 'Spreadsheet',
|
|
939
|
+
modified: new Date(2024, 4, 22),
|
|
940
|
+
size: 48_128,
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
id: 'mortgage-escrow',
|
|
944
|
+
path: ['Loans', 'Home mortgage 7702', 'Escrow analysis.pdf'],
|
|
945
|
+
type: 'PDF',
|
|
946
|
+
modified: new Date(2026, 8, 15),
|
|
947
|
+
size: 215_040,
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
id: 'auto-loan',
|
|
951
|
+
path: ['Loans', 'Auto loan 3318'],
|
|
952
|
+
type: 'Folder',
|
|
953
|
+
modified: new Date(2026, 6, 2),
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
id: 'auto-payoff',
|
|
957
|
+
path: ['Loans', 'Auto loan 3318', 'Payoff quote.pdf'],
|
|
958
|
+
type: 'PDF',
|
|
959
|
+
modified: new Date(2026, 6, 2),
|
|
960
|
+
size: 52_224,
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
id: 'transactions-export',
|
|
964
|
+
path: ['Transactions Oct 2026.csv'],
|
|
965
|
+
type: 'CSV',
|
|
966
|
+
modified: new Date(2026, 10, 2),
|
|
967
|
+
size: 23_552,
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
id: 'direct-deposit',
|
|
971
|
+
path: ['Direct deposit form.pdf'],
|
|
972
|
+
type: 'PDF',
|
|
973
|
+
modified: new Date(2026, 2, 9),
|
|
974
|
+
size: 88_064,
|
|
975
|
+
},
|
|
976
|
+
{
|
|
977
|
+
id: 'beneficiary',
|
|
978
|
+
path: ['Beneficiaries.docx'],
|
|
979
|
+
type: 'Document',
|
|
980
|
+
modified: new Date(2025, 10, 18),
|
|
981
|
+
size: 36_864,
|
|
982
|
+
},
|
|
983
|
+
];
|
|
984
|
+
|
|
985
|
+
const fileCountOf = new Map(
|
|
986
|
+
documents
|
|
987
|
+
.filter((folder) => folder.type === 'Folder')
|
|
988
|
+
.map((folder) => [
|
|
989
|
+
folder.id,
|
|
990
|
+
documents.filter(
|
|
991
|
+
(doc) =>
|
|
992
|
+
doc.type !== 'Folder' &&
|
|
993
|
+
folder.path.every((segment, index) => doc.path[index] === segment),
|
|
994
|
+
).length,
|
|
995
|
+
]),
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
const sizeFormatter = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 });
|
|
999
|
+
|
|
1000
|
+
function formatSize(bytes: number) {
|
|
1001
|
+
if (bytes < 1024) {
|
|
1002
|
+
return `${sizeFormatter.format(bytes)} B`;
|
|
1003
|
+
}
|
|
1004
|
+
if (bytes < 1024 * 1024) {
|
|
1005
|
+
return `${sizeFormatter.format(bytes / 1024)} KB`;
|
|
1006
|
+
}
|
|
1007
|
+
return `${sizeFormatter.format(bytes / (1024 * 1024))} MB`;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const columns: GridColDef<BankDocument>[] = [
|
|
1011
|
+
{ field: 'name', headerName: 'Name', valueGetter: (_, row) => row.path.at(-1) },
|
|
1012
|
+
{ field: 'type', headerName: 'Type', width: 120 },
|
|
1013
|
+
{ field: 'modified', headerName: 'Modified', type: 'date', width: 130 },
|
|
1014
|
+
{
|
|
1015
|
+
field: 'size',
|
|
1016
|
+
headerName: 'Size',
|
|
1017
|
+
type: 'number',
|
|
1018
|
+
width: 100,
|
|
1019
|
+
valueFormatter: (value) => (typeof value === 'number' ? formatSize(value) : ''),
|
|
1020
|
+
},
|
|
1021
|
+
];
|
|
1022
|
+
|
|
1023
|
+
const groupingColDef: DataGridProps<BankDocument>['groupingColDef'] = {
|
|
1024
|
+
flex: 1,
|
|
1025
|
+
minWidth: 240,
|
|
1026
|
+
renderCell: ({ row }) => {
|
|
1027
|
+
const { icon, color } = typeIcons[row.type];
|
|
1028
|
+
const isFolder = row.type === 'Folder';
|
|
1029
|
+
return (
|
|
1030
|
+
<HStack space="sm" className="min-w-0 flex-1 items-center">
|
|
1031
|
+
<Icon as={icon} color={color} />
|
|
1032
|
+
<Text numberOfLines={1} color={isFolder ? 'action' : 'primary'}>
|
|
1033
|
+
{isFolder
|
|
1034
|
+
? `${String(row.path.at(-1))} (${String(fileCountOf.get(row.id) ?? 0)})`
|
|
1035
|
+
: row.path.at(-1)}
|
|
1036
|
+
</Text>
|
|
1037
|
+
</HStack>
|
|
1038
|
+
);
|
|
1039
|
+
},
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
const getTreeDataPath = (row: BankDocument) => row.path;
|
|
1043
|
+
const isGroupExpandedByDefault = ({ id }: GridTreeNodeParams<BankDocument>) =>
|
|
1044
|
+
id === 'statements' || id === 'statements-2026';
|
|
1045
|
+
|
|
1046
|
+
export default function DataGridTreeDataColumnsDemo() {
|
|
1047
|
+
return (
|
|
1048
|
+
<DataGrid
|
|
1049
|
+
rows={documents}
|
|
1050
|
+
columns={columns}
|
|
1051
|
+
treeData
|
|
1052
|
+
getTreeDataPath={getTreeDataPath}
|
|
1053
|
+
groupingColDef={groupingColDef}
|
|
1054
|
+
isGroupExpandedByDefault={isGroupExpandedByDefault}
|
|
1055
|
+
aria-label="Documents"
|
|
1056
|
+
className="h-130"
|
|
1057
|
+
/>
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
With `checkboxSelection`, the checkbox sits in the tree column. By default, selecting a group selects its descendants, and a group is checked when all of its children are and shows a mixed state when only some are. Set `rowSelectionPropagation` to `{ descendants: false, parents: false }` to select rows independently. Selection is kept for rows that a filter hides, unlike MUI X, and select-all applies to the rows that match the filter.
|
|
1063
|
+
|
|
1064
|
+
This picker shows each API as one rich row. `description` and `method` are hidden with `columnVisibilityModel` (see [Column visibility](#column-visibility)), so search also matches descriptions. The filter panel still lists the hidden `method` column, so filtering by method uses the panel that already exists.
|
|
1065
|
+
|
|
1066
|
+
```tsx
|
|
1067
|
+
import { useState } from 'react';
|
|
1068
|
+
import { Chip, HStack, Text, VStack } from '@cdx-ui/components';
|
|
1069
|
+
import { DataGrid, type GridColDef, type GridRowSelectionModel } from '@cdx-ui/data-grid';
|
|
1070
|
+
|
|
1071
|
+
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
1072
|
+
|
|
1073
|
+
// GridValidRowModel's index signature rejects otherwise equivalent interfaces.
|
|
1074
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
1075
|
+
type Api = {
|
|
1076
|
+
id: string;
|
|
1077
|
+
path: string[];
|
|
1078
|
+
description: string;
|
|
1079
|
+
method?: Method;
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
const methodColors: Record<Method, 'info' | 'success' | 'warning' | 'danger'> = {
|
|
1083
|
+
GET: 'info',
|
|
1084
|
+
POST: 'success',
|
|
1085
|
+
PUT: 'warning',
|
|
1086
|
+
DELETE: 'danger',
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
const apis: Api[] = [
|
|
1090
|
+
{ id: 'accounts', path: ['Accounts'], description: 'Account information and balances' },
|
|
1091
|
+
{ id: 'oauth', path: ['Accounts', 'OAuth V1'], description: 'Token-scoped account APIs' },
|
|
1092
|
+
{
|
|
1093
|
+
id: 'list-accounts',
|
|
1094
|
+
path: ['Accounts', 'OAuth V1', 'List customer accounts'],
|
|
1095
|
+
description: 'Returns every account the customer can see',
|
|
1096
|
+
method: 'GET',
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
id: 'close-account',
|
|
1100
|
+
path: ['Accounts', 'OAuth V1', 'Close account'],
|
|
1101
|
+
description: 'Closes an account with a zero balance',
|
|
1102
|
+
method: 'DELETE',
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
id: 'open-account',
|
|
1106
|
+
path: ['Accounts', 'OAuth V1', 'Open account'],
|
|
1107
|
+
description: 'Opens a new deposit account',
|
|
1108
|
+
method: 'POST',
|
|
1109
|
+
},
|
|
1110
|
+
{ id: 'balances', path: ['Accounts', 'Balances'], description: 'Real-time balance APIs' },
|
|
1111
|
+
{
|
|
1112
|
+
id: 'get-balance',
|
|
1113
|
+
path: ['Accounts', 'Balances', 'Get balance'],
|
|
1114
|
+
description: 'Returns the available and ledger balance',
|
|
1115
|
+
method: 'GET',
|
|
1116
|
+
},
|
|
1117
|
+
{ id: 'payments', path: ['Payments'], description: 'Transfers and bill pay' },
|
|
1118
|
+
{
|
|
1119
|
+
id: 'create-transfer',
|
|
1120
|
+
path: ['Payments', 'Create transfer'],
|
|
1121
|
+
description: 'Moves money between two accounts',
|
|
1122
|
+
method: 'POST',
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
id: 'update-payee',
|
|
1126
|
+
path: ['Payments', 'Update payee'],
|
|
1127
|
+
description: 'Changes a bill pay payee',
|
|
1128
|
+
method: 'PUT',
|
|
1129
|
+
},
|
|
1130
|
+
];
|
|
1131
|
+
|
|
1132
|
+
const columns: GridColDef<Api>[] = [
|
|
1133
|
+
{
|
|
1134
|
+
field: 'name',
|
|
1135
|
+
headerName: 'APIs',
|
|
1136
|
+
flex: 1,
|
|
1137
|
+
minWidth: 280,
|
|
1138
|
+
valueGetter: (_, row) => row.path.at(-1),
|
|
1139
|
+
renderCell: ({ row }) => (
|
|
1140
|
+
<HStack space="sm" className="flex-1 items-center">
|
|
1141
|
+
<VStack className="min-w-0 flex-1">
|
|
1142
|
+
<Text numberOfLines={1} weight="medium">
|
|
1143
|
+
{row.path.at(-1)}
|
|
1144
|
+
</Text>
|
|
1145
|
+
<Text numberOfLines={1} color="secondary" size="sm">
|
|
1146
|
+
{row.description}
|
|
1147
|
+
</Text>
|
|
1148
|
+
</VStack>
|
|
1149
|
+
{row.method ? (
|
|
1150
|
+
<Chip color={methodColors[row.method]} size="small">
|
|
1151
|
+
<Chip.Label>{row.method}</Chip.Label>
|
|
1152
|
+
</Chip>
|
|
1153
|
+
) : null}
|
|
1154
|
+
</HStack>
|
|
1155
|
+
),
|
|
1156
|
+
},
|
|
1157
|
+
{ field: 'description', headerName: 'Description' },
|
|
1158
|
+
{ field: 'method', headerName: 'Method' },
|
|
1159
|
+
];
|
|
1160
|
+
|
|
1161
|
+
const columnVisibilityModel = { description: false, method: false };
|
|
1162
|
+
const endpointCount = apis.filter((api) => api.method).length;
|
|
1163
|
+
|
|
1164
|
+
export default function DataGridTreeDataSelectionDemo() {
|
|
1165
|
+
const [selection, setSelection] = useState<GridRowSelectionModel>({
|
|
1166
|
+
type: 'include',
|
|
1167
|
+
ids: new Set(['list-accounts', 'create-transfer']),
|
|
1168
|
+
});
|
|
1169
|
+
const selectedEndpoints = apis.filter((api) => api.method && selection.ids.has(api.id)).length;
|
|
1170
|
+
|
|
1171
|
+
return (
|
|
1172
|
+
<DataGrid
|
|
1173
|
+
rows={apis}
|
|
1174
|
+
columns={columns}
|
|
1175
|
+
columnVisibilityModel={columnVisibilityModel}
|
|
1176
|
+
treeData
|
|
1177
|
+
getTreeDataPath={(row) => row.path}
|
|
1178
|
+
defaultGroupingExpansionDepth={1}
|
|
1179
|
+
checkboxSelection
|
|
1180
|
+
rowSelectionModel={selection}
|
|
1181
|
+
onRowSelectionModelChange={setSelection}
|
|
1182
|
+
showToolbar
|
|
1183
|
+
toolbarActions={
|
|
1184
|
+
<Text color="secondary" size="sm">
|
|
1185
|
+
{`${selectedEndpoints.toString()} of ${endpointCount.toString()} endpoints selected`}
|
|
1186
|
+
</Text>
|
|
1187
|
+
}
|
|
1188
|
+
hideFooter
|
|
1189
|
+
aria-label="APIs"
|
|
1190
|
+
className="h-130"
|
|
1191
|
+
/>
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
## Detail panel
|
|
1197
|
+
|
|
1198
|
+
Return content from `getDetailPanelContent` to add a toggle column at the start of the grid. Opening a row shows a panel under it that spans every column. Return `null` for rows that have no details, and those rows get no toggle. Use `defaultDetailPanelExpandedRowIds`, or control `detailPanelExpandedRowIds` with `onDetailPanelExpandedRowIdsChange`, to choose which panels are open. The panel grows to fit its content.
|
|
1199
|
+
|
|
1200
|
+
The panel scrolls with the grid when it is wider than the viewport, so keep its content narrow enough to read without horizontal scrolling. When any column is pinned to start, the toggle column pins after the selection column.
|
|
1201
|
+
|
|
1202
|
+
```tsx
|
|
1203
|
+
import { Button, Chip, HStack, Text, VStack } from '@cdx-ui/components';
|
|
1204
|
+
import { DataGrid, type GridColDef } from '@cdx-ui/data-grid';
|
|
1205
|
+
import { Check, Close, ContentCopy } from '@cdx-ui/icons';
|
|
1206
|
+
|
|
1207
|
+
// GridValidRowModel's index signature rejects otherwise equivalent interfaces.
|
|
1208
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
1209
|
+
type AuditEvent = {
|
|
1210
|
+
id: string;
|
|
1211
|
+
time: string;
|
|
1212
|
+
user: string;
|
|
1213
|
+
category: 'Access' | 'Change';
|
|
1214
|
+
domain: string;
|
|
1215
|
+
eventType: string;
|
|
1216
|
+
status: 'Success' | 'Failed';
|
|
1217
|
+
details: [label: string, value: string][];
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
const events: AuditEvent[] = [
|
|
1221
|
+
{
|
|
1222
|
+
id: 'evt_8f2a6c91',
|
|
1223
|
+
time: 'Jan 12, 2026, 11:51:06 AM',
|
|
1224
|
+
user: 'Mary Jane',
|
|
1225
|
+
category: 'Change',
|
|
1226
|
+
domain: 'Track and publish',
|
|
1227
|
+
eventType: 'Login',
|
|
1228
|
+
status: 'Failed',
|
|
1229
|
+
details: [
|
|
1230
|
+
['Event ID', 'evt_8f2a6c91'],
|
|
1231
|
+
['Organisation ID', 'org_acme'],
|
|
1232
|
+
['Category', 'change'],
|
|
1233
|
+
['Environment ID', 'env_production'],
|
|
1234
|
+
['Occurred at', '2026-01-12T11:51:06Z'],
|
|
1235
|
+
['Target collection', 'articles'],
|
|
1236
|
+
['Actor type', 'user'],
|
|
1237
|
+
['Version before', '12'],
|
|
1238
|
+
['User ID', 'usr_10482'],
|
|
1239
|
+
['Version after', '13'],
|
|
1240
|
+
['User email', 'mary.jane@example.com'],
|
|
1241
|
+
['Patch path', '/title'],
|
|
1242
|
+
['Correlation ID', 'req_7d4b9e23'],
|
|
1243
|
+
['HTTP status code', '403'],
|
|
1244
|
+
],
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
id: 'evt_51ab07e4',
|
|
1248
|
+
time: 'Jan 12, 2026, 10:05:41 AM',
|
|
1249
|
+
user: 'Peter Parker',
|
|
1250
|
+
category: 'Access',
|
|
1251
|
+
domain: 'Branding and theming',
|
|
1252
|
+
eventType: 'History',
|
|
1253
|
+
status: 'Success',
|
|
1254
|
+
details: [
|
|
1255
|
+
['Event ID', 'evt_51ab07e4'],
|
|
1256
|
+
['Organisation ID', 'org_acme'],
|
|
1257
|
+
['Category', 'access'],
|
|
1258
|
+
['Environment ID', 'env_production'],
|
|
1259
|
+
['User ID', 'usr_20311'],
|
|
1260
|
+
['HTTP status code', '200'],
|
|
1261
|
+
],
|
|
1262
|
+
},
|
|
1263
|
+
{
|
|
1264
|
+
id: 'evt_c3d94f10',
|
|
1265
|
+
time: 'Jan 11, 2026, 4:18:22 PM',
|
|
1266
|
+
user: 'Jane Cooper',
|
|
1267
|
+
category: 'Access',
|
|
1268
|
+
domain: 'Track and publish',
|
|
1269
|
+
eventType: 'Login',
|
|
1270
|
+
status: 'Success',
|
|
1271
|
+
details: [
|
|
1272
|
+
['Event ID', 'evt_c3d94f10'],
|
|
1273
|
+
['Organisation ID', 'org_acme'],
|
|
1274
|
+
['User ID', 'usr_10077'],
|
|
1275
|
+
['HTTP status code', '200'],
|
|
1276
|
+
],
|
|
1277
|
+
},
|
|
1278
|
+
];
|
|
1279
|
+
|
|
1280
|
+
const columns: GridColDef<AuditEvent>[] = [
|
|
1281
|
+
{ field: 'time', headerName: 'Time', width: 220 },
|
|
1282
|
+
{ field: 'user', headerName: 'User', width: 140 },
|
|
1283
|
+
{
|
|
1284
|
+
field: 'category',
|
|
1285
|
+
headerName: 'Category',
|
|
1286
|
+
width: 120,
|
|
1287
|
+
renderCell: ({ row }) => (
|
|
1288
|
+
<Chip size="small" color={row.category === 'Access' ? 'info' : 'neutral'}>
|
|
1289
|
+
<Chip.Label>{row.category}</Chip.Label>
|
|
1290
|
+
</Chip>
|
|
1291
|
+
),
|
|
1292
|
+
},
|
|
1293
|
+
{ field: 'domain', headerName: 'Domain', flex: 1, minWidth: 180 },
|
|
1294
|
+
{ field: 'eventType', headerName: 'Event type', width: 130 },
|
|
1295
|
+
{
|
|
1296
|
+
field: 'status',
|
|
1297
|
+
headerName: 'Status',
|
|
1298
|
+
width: 120,
|
|
1299
|
+
renderCell: ({ row }) => (
|
|
1300
|
+
<Chip size="small" color={row.status === 'Success' ? 'success' : 'danger'}>
|
|
1301
|
+
<Chip.Icon as={row.status === 'Success' ? Check : Close} />
|
|
1302
|
+
<Chip.Label>{row.status}</Chip.Label>
|
|
1303
|
+
</Chip>
|
|
1304
|
+
),
|
|
1305
|
+
},
|
|
1306
|
+
];
|
|
1307
|
+
|
|
1308
|
+
function AuditEventDetails({ event }: { event: AuditEvent }) {
|
|
1309
|
+
return (
|
|
1310
|
+
<VStack space="md" className="rounded-lg bg-surface-secondary p-4">
|
|
1311
|
+
<HStack className="flex-wrap">
|
|
1312
|
+
{event.details.map(([label, value]) => (
|
|
1313
|
+
<HStack key={label} space="md" className="w-1/2 py-1">
|
|
1314
|
+
<Text color="secondary" size="sm" className="w-40">
|
|
1315
|
+
{label}:
|
|
1316
|
+
</Text>
|
|
1317
|
+
<Text size="sm" className="flex-1">
|
|
1318
|
+
{value}
|
|
1319
|
+
</Text>
|
|
1320
|
+
</HStack>
|
|
1321
|
+
))}
|
|
1322
|
+
</HStack>
|
|
1323
|
+
<Button
|
|
1324
|
+
variant="outline"
|
|
1325
|
+
size="small"
|
|
1326
|
+
className="self-end"
|
|
1327
|
+
onPress={() => {
|
|
1328
|
+
void navigator.clipboard.writeText(
|
|
1329
|
+
JSON.stringify(Object.fromEntries(event.details), null, 2),
|
|
1330
|
+
);
|
|
1331
|
+
}}
|
|
1332
|
+
>
|
|
1333
|
+
<Button.Icon as={ContentCopy} position="leading" />
|
|
1334
|
+
<Button.Label>Copy</Button.Label>
|
|
1335
|
+
</Button>
|
|
1336
|
+
</VStack>
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
export default function DataGridDetailPanelDemo() {
|
|
1341
|
+
return (
|
|
1342
|
+
<DataGrid
|
|
1343
|
+
rows={events}
|
|
1344
|
+
columns={columns}
|
|
1345
|
+
getDetailPanelContent={({ row }) => <AuditEventDetails event={row} />}
|
|
1346
|
+
defaultDetailPanelExpandedRowIds={new Set(['evt_8f2a6c91'])}
|
|
1347
|
+
hideFooter
|
|
1348
|
+
aria-label="Audit log"
|
|
1349
|
+
className="h-130"
|
|
1350
|
+
/>
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
```
|
|
1354
|
+
|
|
811
1355
|
## Column pinning
|
|
812
1356
|
|
|
813
1357
|
Pass `pinnedColumns` with `start` and/or `end` field lists so key columns stay visible while the grid scrolls horizontally. Deprecated `left` / `right` keys are aliases for `start` / `end` (same mapping as `align`). MUI X `initialState.pinnedColumns` moves to the `pinnedColumns` prop.
|
|
@@ -1138,4 +1682,6 @@ sort. `valueOptions` is required when `type` is `singleSelect`.
|
|
|
1138
1682
|
|
|
1139
1683
|
## Accessibility
|
|
1140
1684
|
|
|
1141
|
-
The grid renders a semantic HTML table on web. Sortable headers expose `aria-sort`. Select-all and row checkboxes are labelled.
|
|
1685
|
+
The grid renders a semantic HTML table on web. Sortable headers expose `aria-sort`. Select-all and row checkboxes are labelled.
|
|
1686
|
+
|
|
1687
|
+
Tree and detail panel toggles are buttons with `aria-expanded`, labelled with the row they open, such as "Expand OAuth V1, Accounts". A partly selected tree group exposes `aria-checked="mixed"`. Expanding or collapsing never moves focus. When a filter removes the row that had focus, focus moves to the toolbar search field if `showToolbar` is on. Closing the filter panel with Escape, Remove all, or by removing the last filter returns focus to the control that opened it. A press outside the panel closes it and leaves focus where it went. The grid keeps table semantics rather than `treegrid`, so screen readers do not announce the tree level; the toggle label names the row's ancestors instead.
|