@okta/odyssey-react-mui 1.14.4 → 1.14.6
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/CHANGELOG.md +8 -0
- package/dist/Badge.js +1 -1
- package/dist/Badge.js.map +1 -1
- package/dist/DataTable/DataTable.js +178 -58
- package/dist/DataTable/DataTable.js.map +1 -1
- package/dist/DataTable/DataTableEmptyState.js +55 -0
- package/dist/DataTable/DataTableEmptyState.js.map +1 -0
- package/dist/DataTable/DataTablePagination.js +221 -0
- package/dist/DataTable/DataTablePagination.js.map +1 -0
- package/dist/DataTable/DataTableRowActions.js +34 -24
- package/dist/DataTable/DataTableRowActions.js.map +1 -1
- package/dist/DataTable/DataTableSettings.js +22 -10
- package/dist/DataTable/DataTableSettings.js.map +1 -1
- package/dist/DataTable/constants.js +1 -0
- package/dist/DataTable/constants.js.map +1 -1
- package/dist/DataTable/index.js +1 -0
- package/dist/DataTable/index.js.map +1 -1
- package/dist/DataTable/useRowReordering.js +3 -3
- package/dist/DataTable/useRowReordering.js.map +1 -1
- package/dist/DataTable/useScrollIndication.js +70 -0
- package/dist/DataTable/useScrollIndication.js.map +1 -0
- package/dist/Field.js.map +1 -1
- package/dist/Fieldset.js +17 -14
- package/dist/Fieldset.js.map +1 -1
- package/dist/Form.js +33 -23
- package/dist/Form.js.map +1 -1
- package/dist/MenuButton.js +1 -1
- package/dist/MenuButton.js.map +1 -1
- package/dist/SearchField.js +2 -2
- package/dist/SearchField.js.map +1 -1
- package/dist/labs/DataFilters.js +6 -2
- package/dist/labs/DataFilters.js.map +1 -1
- package/dist/labs/DataTable.js +3 -3
- package/dist/labs/DataTable.js.map +1 -1
- package/dist/labs/FileUpload.js +195 -0
- package/dist/labs/FileUpload.js.map +1 -0
- package/dist/labs/FileUploadIllustration.js +54 -0
- package/dist/labs/FileUploadIllustration.js.map +1 -0
- package/dist/labs/FileUploadPreview.js +109 -0
- package/dist/labs/FileUploadPreview.js.map +1 -0
- package/dist/labs/index.js +1 -0
- package/dist/labs/index.js.map +1 -1
- package/dist/properties/ts/odyssey-react-mui.js +12 -0
- package/dist/properties/ts/odyssey-react-mui.js.map +1 -1
- package/dist/src/DataTable/DataTable.d.ts +36 -18
- package/dist/src/DataTable/DataTable.d.ts.map +1 -1
- package/dist/src/DataTable/DataTableEmptyState.d.ts +21 -0
- package/dist/src/DataTable/DataTableEmptyState.d.ts.map +1 -0
- package/dist/src/DataTable/DataTablePagination.d.ts +33 -0
- package/dist/src/DataTable/DataTablePagination.d.ts.map +1 -0
- package/dist/src/DataTable/DataTableRowActions.d.ts.map +1 -1
- package/dist/src/DataTable/DataTableSettings.d.ts.map +1 -1
- package/dist/src/DataTable/constants.d.ts +1 -0
- package/dist/src/DataTable/constants.d.ts.map +1 -1
- package/dist/src/DataTable/index.d.ts +2 -1
- package/dist/src/DataTable/index.d.ts.map +1 -1
- package/dist/src/DataTable/useRowReordering.d.ts.map +1 -1
- package/dist/src/DataTable/useScrollIndication.d.ts +22 -0
- package/dist/src/DataTable/useScrollIndication.d.ts.map +1 -0
- package/dist/src/Field.d.ts +8 -7
- package/dist/src/Field.d.ts.map +1 -1
- package/dist/src/Fieldset.d.ts.map +1 -1
- package/dist/src/Form.d.ts.map +1 -1
- package/dist/src/OdysseyTranslationProvider.d.ts +1 -1
- package/dist/src/OdysseyTranslationProvider.d.ts.map +1 -1
- package/dist/src/SearchField.d.ts.map +1 -1
- package/dist/src/labs/DataFilters.d.ts +5 -1
- package/dist/src/labs/DataFilters.d.ts.map +1 -1
- package/dist/src/labs/DataTable.d.ts.map +1 -1
- package/dist/src/labs/FileUpload.d.ts +40 -0
- package/dist/src/labs/FileUpload.d.ts.map +1 -0
- package/dist/src/labs/FileUploadIllustration.d.ts +15 -0
- package/dist/src/labs/FileUploadIllustration.d.ts.map +1 -0
- package/dist/src/labs/FileUploadPreview.d.ts +21 -0
- package/dist/src/labs/FileUploadPreview.d.ts.map +1 -0
- package/dist/src/labs/index.d.ts +4 -0
- package/dist/src/labs/index.d.ts.map +1 -1
- package/dist/src/properties/ts/odyssey-react-mui.d.ts +12 -0
- package/dist/src/properties/ts/odyssey-react-mui.d.ts.map +1 -1
- package/dist/src/theme/components.d.ts.map +1 -1
- package/dist/theme/components.js +10 -1
- package/dist/theme/components.js.map +1 -1
- package/dist/tsconfig.production.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/Badge.tsx +1 -1
- package/src/DataTable/DataTable.tsx +293 -85
- package/src/DataTable/DataTableEmptyState.tsx +62 -0
- package/src/DataTable/DataTablePagination.tsx +289 -0
- package/src/DataTable/DataTableRowActions.tsx +35 -37
- package/src/DataTable/DataTableSettings.tsx +43 -17
- package/src/DataTable/constants.ts +1 -0
- package/src/DataTable/index.tsx +7 -1
- package/src/DataTable/useRowReordering.tsx +5 -3
- package/src/DataTable/useScrollIndication.tsx +118 -0
- package/src/Field.tsx +9 -7
- package/src/Fieldset.tsx +24 -18
- package/src/Form.tsx +43 -27
- package/src/MenuButton.tsx +1 -1
- package/src/SearchField.tsx +1 -2
- package/src/labs/DataFilters.tsx +9 -0
- package/src/labs/DataTable.tsx +5 -9
- package/src/labs/FileUpload.tsx +301 -0
- package/src/labs/FileUploadIllustration.tsx +66 -0
- package/src/labs/FileUploadPreview.tsx +150 -0
- package/src/labs/index.ts +4 -2
- package/src/properties/odyssey-react-mui.properties +18 -0
- package/src/properties/ts/odyssey-react-mui.ts +1 -1
- package/src/theme/components.tsx +9 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.
|
|
3
|
+
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
|
|
4
|
+
*
|
|
5
|
+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
|
6
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
7
|
+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
8
|
+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9
|
+
*
|
|
10
|
+
* See the License for the specific language governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { InputBase } from "@mui/material";
|
|
14
|
+
import {
|
|
15
|
+
Dispatch,
|
|
16
|
+
SetStateAction,
|
|
17
|
+
memo,
|
|
18
|
+
useCallback,
|
|
19
|
+
useEffect,
|
|
20
|
+
useMemo,
|
|
21
|
+
useRef,
|
|
22
|
+
useState,
|
|
23
|
+
} from "react";
|
|
24
|
+
import { Paragraph } from "../Typography";
|
|
25
|
+
import { Button } from "../Button";
|
|
26
|
+
import { ArrowLeftIcon, ArrowRightIcon } from "../icons.generated";
|
|
27
|
+
import styled from "@emotion/styled";
|
|
28
|
+
import {
|
|
29
|
+
DesignTokens,
|
|
30
|
+
useOdysseyDesignTokens,
|
|
31
|
+
} from "../OdysseyDesignTokensContext";
|
|
32
|
+
import { Box } from "../Box";
|
|
33
|
+
import { Trans, useTranslation } from "react-i18next";
|
|
34
|
+
import { paginationTypeValues } from "./constants";
|
|
35
|
+
|
|
36
|
+
const PaginationContainer = styled("div")({
|
|
37
|
+
display: "flex",
|
|
38
|
+
alignItems: "center",
|
|
39
|
+
justifyContent: "space-between",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const PaginationSegment = styled("div", {
|
|
43
|
+
shouldForwardProp: (prop) => prop !== "odysseyDesignTokens",
|
|
44
|
+
})(({ odysseyDesignTokens }: { odysseyDesignTokens: DesignTokens }) => ({
|
|
45
|
+
display: "flex",
|
|
46
|
+
alignItems: "center",
|
|
47
|
+
gap: odysseyDesignTokens.Spacing4,
|
|
48
|
+
"& > div": {
|
|
49
|
+
display: "flex",
|
|
50
|
+
alignItems: "center",
|
|
51
|
+
gap: odysseyDesignTokens.Spacing2,
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
const PaginationInput = styled(InputBase, {
|
|
56
|
+
shouldForwardProp: (prop) => prop !== "odysseyDesignTokens",
|
|
57
|
+
})(({ odysseyDesignTokens }: { odysseyDesignTokens: DesignTokens }) => ({
|
|
58
|
+
borderColor: odysseyDesignTokens.HueNeutral200,
|
|
59
|
+
borderRadius: odysseyDesignTokens.BorderRadiusTight,
|
|
60
|
+
height: odysseyDesignTokens.Spacing6,
|
|
61
|
+
width: "4.5714285714rem", // This is a hardcoded value, keep as string
|
|
62
|
+
"&:hover": {
|
|
63
|
+
borderColor: odysseyDesignTokens.HueNeutral400,
|
|
64
|
+
},
|
|
65
|
+
"&.Mui-focused:hover": {
|
|
66
|
+
borderColor: odysseyDesignTokens.PalettePrimaryMain,
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
const PaginationButtonContainer = styled("div")({
|
|
71
|
+
"& > *": {
|
|
72
|
+
marginInlineStart: `0 !important`,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export type DataTablePaginationProps = {
|
|
77
|
+
pagination: {
|
|
78
|
+
pageIndex: number;
|
|
79
|
+
pageSize: number;
|
|
80
|
+
};
|
|
81
|
+
setPagination: Dispatch<
|
|
82
|
+
SetStateAction<{ pageIndex: number; pageSize: number }>
|
|
83
|
+
>;
|
|
84
|
+
totalRows?: number;
|
|
85
|
+
isDisabled?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* The type of pagination controls shown. Defaults to next/prev buttons, but can be
|
|
88
|
+
* set to a simple "Load more" button by setting to "loadMore".
|
|
89
|
+
*/
|
|
90
|
+
variant?: (typeof paginationTypeValues)[number];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const DataTablePagination = ({
|
|
94
|
+
pagination,
|
|
95
|
+
setPagination,
|
|
96
|
+
totalRows,
|
|
97
|
+
isDisabled,
|
|
98
|
+
variant,
|
|
99
|
+
}: DataTablePaginationProps) => {
|
|
100
|
+
const odysseyDesignTokens = useOdysseyDesignTokens();
|
|
101
|
+
const { t } = useTranslation();
|
|
102
|
+
|
|
103
|
+
const [page, setPage] = useState<number>(pagination.pageIndex);
|
|
104
|
+
const [rowsPerPage, setRowsPerPage] = useState<number>(pagination.pageSize);
|
|
105
|
+
const initialRowsPerPage = useRef<number>(pagination.pageSize);
|
|
106
|
+
|
|
107
|
+
const firstRow = pagination.pageSize * (pagination.pageIndex - 1) + 1;
|
|
108
|
+
let lastRow = firstRow + (pagination.pageSize - 1);
|
|
109
|
+
// If the last eligible row is greater than the number of total rows,
|
|
110
|
+
// show the number of total rows instead (ie, if we're showing rows
|
|
111
|
+
// 180-200 but there are only 190 rows, show 180-190 instead)
|
|
112
|
+
lastRow = totalRows && lastRow > totalRows ? totalRows : lastRow;
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
setPage(pagination.pageIndex);
|
|
116
|
+
setRowsPerPage(pagination.pageSize);
|
|
117
|
+
}, [pagination]);
|
|
118
|
+
|
|
119
|
+
const handlePaginationChange = useCallback(() => {
|
|
120
|
+
const updatedPage =
|
|
121
|
+
totalRows && page * totalRows > lastRow
|
|
122
|
+
? Math.ceil(totalRows / rowsPerPage)
|
|
123
|
+
: page;
|
|
124
|
+
const updatedRowsPerPage =
|
|
125
|
+
totalRows && rowsPerPage > totalRows ? totalRows : rowsPerPage;
|
|
126
|
+
|
|
127
|
+
setPagination({
|
|
128
|
+
pageIndex: updatedPage,
|
|
129
|
+
pageSize: updatedRowsPerPage,
|
|
130
|
+
});
|
|
131
|
+
}, [page, rowsPerPage, lastRow, setPagination, totalRows]);
|
|
132
|
+
|
|
133
|
+
// The following handlers use React.KeyboardEvent (rather than just KeyboardEvent) becuase React.KeyboardEvent
|
|
134
|
+
// is generic, while plain KeyboardEvent is not. We need this generic so we can specify the HTMLInputElement,
|
|
135
|
+
// which allows us to use currentTarget.value
|
|
136
|
+
const handlePageSubmit = useCallback(
|
|
137
|
+
(event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
138
|
+
if (event.key === "Enter") {
|
|
139
|
+
setPagination({
|
|
140
|
+
pageIndex: parseInt(event.currentTarget.value),
|
|
141
|
+
pageSize: rowsPerPage,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
[rowsPerPage, setPagination],
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const handleRowsPerPageSubmit = useCallback(
|
|
149
|
+
(event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
150
|
+
if (event.key === "Enter") {
|
|
151
|
+
setPagination({
|
|
152
|
+
pageIndex: page,
|
|
153
|
+
pageSize: parseInt(event.currentTarget.value),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
[page, setPagination],
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const setPageFromEvent = useCallback(
|
|
161
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
162
|
+
setPage(parseInt(event.target.value));
|
|
163
|
+
},
|
|
164
|
+
[setPage],
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const setRowsPerPageFromEvent = useCallback(
|
|
168
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
169
|
+
setRowsPerPage(parseInt(event.target.value));
|
|
170
|
+
},
|
|
171
|
+
[setRowsPerPage],
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const handleLoadMore = useCallback(() => {
|
|
175
|
+
setPagination({
|
|
176
|
+
pageIndex: 1,
|
|
177
|
+
pageSize: rowsPerPage + initialRowsPerPage.current,
|
|
178
|
+
});
|
|
179
|
+
}, [rowsPerPage, setPagination]);
|
|
180
|
+
|
|
181
|
+
const handleNextButton = useCallback(() => {
|
|
182
|
+
setPagination({ pageIndex: page + 1, pageSize: rowsPerPage });
|
|
183
|
+
}, [setPagination, page, rowsPerPage]);
|
|
184
|
+
|
|
185
|
+
const handlePreviousButton = useCallback(() => {
|
|
186
|
+
setPagination({ pageIndex: page - 1, pageSize: rowsPerPage });
|
|
187
|
+
}, [setPagination, page, rowsPerPage]);
|
|
188
|
+
|
|
189
|
+
const loadMoreIsDisabled = useMemo(() => {
|
|
190
|
+
return totalRows ? rowsPerPage >= totalRows : false;
|
|
191
|
+
}, [rowsPerPage, totalRows]);
|
|
192
|
+
|
|
193
|
+
const nextButtonDisabled = useMemo(
|
|
194
|
+
() => (totalRows ? lastRow >= totalRows : false) || isDisabled,
|
|
195
|
+
[totalRows, lastRow, isDisabled],
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const previousButtonDisabled = useMemo(
|
|
199
|
+
() => pagination.pageIndex <= 1 || isDisabled,
|
|
200
|
+
[pagination, isDisabled],
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
return variant === "paged" ? (
|
|
204
|
+
<PaginationContainer>
|
|
205
|
+
<PaginationSegment odysseyDesignTokens={odysseyDesignTokens}>
|
|
206
|
+
<Box>
|
|
207
|
+
<Paragraph component="span" color="textSecondary">
|
|
208
|
+
{t("table.pagination.rowsperpage")}
|
|
209
|
+
</Paragraph>
|
|
210
|
+
<PaginationInput
|
|
211
|
+
odysseyDesignTokens={odysseyDesignTokens}
|
|
212
|
+
type="number"
|
|
213
|
+
value={rowsPerPage}
|
|
214
|
+
onChange={setRowsPerPageFromEvent}
|
|
215
|
+
onBlur={handlePaginationChange}
|
|
216
|
+
onKeyDown={handleRowsPerPageSubmit}
|
|
217
|
+
disabled={isDisabled}
|
|
218
|
+
inputProps={{
|
|
219
|
+
"aria-label": t("table.pagination.rowsperpage"),
|
|
220
|
+
}}
|
|
221
|
+
/>
|
|
222
|
+
</Box>
|
|
223
|
+
<Paragraph component="span" color="textSecondary">
|
|
224
|
+
{totalRows ? (
|
|
225
|
+
<Trans
|
|
226
|
+
i18nKey="table.pagination.rowswithtotal"
|
|
227
|
+
values={{ firstRow, lastRow, totalRows }}
|
|
228
|
+
/>
|
|
229
|
+
) : (
|
|
230
|
+
<Trans
|
|
231
|
+
i18nKey="table.pagination.rowswithouttotal"
|
|
232
|
+
values={{ firstRow, lastRow }}
|
|
233
|
+
/>
|
|
234
|
+
)}
|
|
235
|
+
</Paragraph>
|
|
236
|
+
</PaginationSegment>
|
|
237
|
+
|
|
238
|
+
<PaginationSegment odysseyDesignTokens={odysseyDesignTokens}>
|
|
239
|
+
{totalRows && (
|
|
240
|
+
<Box>
|
|
241
|
+
<Paragraph component="span" color="textSecondary">
|
|
242
|
+
{t("table.pagination.page")}
|
|
243
|
+
</Paragraph>
|
|
244
|
+
<PaginationInput
|
|
245
|
+
odysseyDesignTokens={odysseyDesignTokens}
|
|
246
|
+
type="number"
|
|
247
|
+
value={page}
|
|
248
|
+
onChange={setPageFromEvent}
|
|
249
|
+
onBlur={handlePaginationChange}
|
|
250
|
+
onKeyDown={handlePageSubmit}
|
|
251
|
+
disabled={isDisabled}
|
|
252
|
+
inputProps={{
|
|
253
|
+
"aria-label": t("table.pagination.page"),
|
|
254
|
+
}}
|
|
255
|
+
/>
|
|
256
|
+
</Box>
|
|
257
|
+
)}
|
|
258
|
+
<PaginationButtonContainer>
|
|
259
|
+
<Button
|
|
260
|
+
startIcon={<ArrowLeftIcon />}
|
|
261
|
+
variant="floating"
|
|
262
|
+
size="small"
|
|
263
|
+
ariaLabel={t("table.pagination.previous")}
|
|
264
|
+
onClick={handlePreviousButton}
|
|
265
|
+
isDisabled={previousButtonDisabled}
|
|
266
|
+
/>
|
|
267
|
+
<Button
|
|
268
|
+
endIcon={<ArrowRightIcon />}
|
|
269
|
+
variant="floating"
|
|
270
|
+
size="small"
|
|
271
|
+
ariaLabel={t("table.pagination.next")}
|
|
272
|
+
onClick={handleNextButton}
|
|
273
|
+
isDisabled={nextButtonDisabled}
|
|
274
|
+
/>
|
|
275
|
+
</PaginationButtonContainer>
|
|
276
|
+
</PaginationSegment>
|
|
277
|
+
</PaginationContainer>
|
|
278
|
+
) : (
|
|
279
|
+
<Button
|
|
280
|
+
variant="secondary"
|
|
281
|
+
label={t("table.pagination.loadmore")}
|
|
282
|
+
onClick={handleLoadMore}
|
|
283
|
+
isDisabled={loadMoreIsDisabled}
|
|
284
|
+
/>
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const MemoizedDataTablePagination = memo(DataTablePagination);
|
|
289
|
+
export { MemoizedDataTablePagination as DataTablePagination };
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { MRT_Row, MRT_RowData } from "material-react-table";
|
|
14
|
-
import { Fragment, ReactElement, memo } from "react";
|
|
14
|
+
import { Fragment, ReactElement, memo, useCallback } from "react";
|
|
15
15
|
import { Button } from "../Button";
|
|
16
16
|
import { MenuItem } from "../MenuItem";
|
|
17
17
|
import { Box as MuiBox } from "@mui/material";
|
|
@@ -52,6 +52,29 @@ const DataTableRowActions = ({
|
|
|
52
52
|
updateRowOrder,
|
|
53
53
|
}: DataTableRowActionsProps) => {
|
|
54
54
|
const { t } = useTranslation();
|
|
55
|
+
|
|
56
|
+
const handleToFrontClick = useCallback(() => {
|
|
57
|
+
updateRowOrder && updateRowOrder({ rowId: row.id, newRowIndex: 0 });
|
|
58
|
+
}, [row.id, updateRowOrder]);
|
|
59
|
+
|
|
60
|
+
const handleForwardClick = useCallback(() => {
|
|
61
|
+
updateRowOrder &&
|
|
62
|
+
updateRowOrder({ rowId: row.id, newRowIndex: Math.max(0, rowIndex - 1) });
|
|
63
|
+
}, [row.id, rowIndex, updateRowOrder]);
|
|
64
|
+
|
|
65
|
+
const handleBackwardClick = useCallback(() => {
|
|
66
|
+
updateRowOrder &&
|
|
67
|
+
updateRowOrder({ rowId: row.id, newRowIndex: rowIndex + 1 });
|
|
68
|
+
}, [row.id, rowIndex, updateRowOrder]);
|
|
69
|
+
|
|
70
|
+
const handleToBackClick = useCallback(() => {
|
|
71
|
+
updateRowOrder &&
|
|
72
|
+
updateRowOrder({
|
|
73
|
+
rowId: row.id,
|
|
74
|
+
newRowIndex: totalRows ? totalRows - 1 : rowIndex,
|
|
75
|
+
});
|
|
76
|
+
}, [row.id, rowIndex, totalRows, updateRowOrder]);
|
|
77
|
+
|
|
55
78
|
return (
|
|
56
79
|
<MuiBox display="flex">
|
|
57
80
|
{rowActionButtons?.(row)}
|
|
@@ -67,51 +90,26 @@ const DataTableRowActions = ({
|
|
|
67
90
|
{rowActionMenuItems && updateRowOrder && <hr />}
|
|
68
91
|
{updateRowOrder && (
|
|
69
92
|
<>
|
|
70
|
-
<MenuItem
|
|
71
|
-
isDisabled={rowIndex <= 0}
|
|
72
|
-
onClick={() =>
|
|
73
|
-
updateRowOrder({ rowId: row.id, newRowIndex: 0 })
|
|
74
|
-
}
|
|
75
|
-
>
|
|
93
|
+
<MenuItem isDisabled={rowIndex <= 0} onClick={handleToFrontClick}>
|
|
76
94
|
<ArrowTopIcon /> <Trans i18nKey="table.reorder.tofront" />
|
|
77
95
|
</MenuItem>
|
|
78
|
-
<MenuItem
|
|
79
|
-
isDisabled={rowIndex <= 0}
|
|
80
|
-
onClick={() =>
|
|
81
|
-
updateRowOrder({
|
|
82
|
-
rowId: row.id,
|
|
83
|
-
newRowIndex: rowIndex <= 0 ? 0 : rowIndex - 1,
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
>
|
|
96
|
+
<MenuItem isDisabled={rowIndex <= 0} onClick={handleForwardClick}>
|
|
87
97
|
<ArrowUpIcon /> <Trans i18nKey="table.reorder.forward" />
|
|
88
98
|
</MenuItem>
|
|
89
99
|
<MenuItem
|
|
90
100
|
isDisabled={totalRows ? rowIndex >= totalRows - 1 : false}
|
|
91
|
-
onClick={
|
|
92
|
-
updateRowOrder({
|
|
93
|
-
rowId: row.id,
|
|
94
|
-
newRowIndex: rowIndex + 1,
|
|
95
|
-
})
|
|
96
|
-
}
|
|
101
|
+
onClick={handleBackwardClick}
|
|
97
102
|
>
|
|
98
103
|
<ArrowDownIcon /> <Trans i18nKey="table.reorder.backward" />
|
|
99
104
|
</MenuItem>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
>
|
|
111
|
-
<ArrowBottomIcon /> <Trans i18nKey="table.reorder.toback" />
|
|
112
|
-
</MenuItem>
|
|
113
|
-
)}
|
|
114
|
-
</>
|
|
105
|
+
{totalRows && (
|
|
106
|
+
<MenuItem
|
|
107
|
+
isDisabled={rowIndex >= totalRows - 1}
|
|
108
|
+
onClick={handleToBackClick}
|
|
109
|
+
>
|
|
110
|
+
<ArrowBottomIcon /> <Trans i18nKey="table.reorder.toback" />
|
|
111
|
+
</MenuItem>
|
|
112
|
+
)}
|
|
115
113
|
</>
|
|
116
114
|
)}
|
|
117
115
|
</MenuButton>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* See the License for the specific language governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { Dispatch, SetStateAction, memo } from "react";
|
|
13
|
+
import { Dispatch, SetStateAction, memo, useCallback, useMemo } from "react";
|
|
14
14
|
import { Checkbox as MuiCheckbox } from "@mui/material";
|
|
15
15
|
import { MenuButton } from "../MenuButton";
|
|
16
16
|
import { MenuItem } from "../MenuItem";
|
|
@@ -42,6 +42,44 @@ const DataTableSettings = ({
|
|
|
42
42
|
setColumnVisibility,
|
|
43
43
|
}: DataTableSettingsProps) => {
|
|
44
44
|
const { t } = useTranslation();
|
|
45
|
+
|
|
46
|
+
const changeRowDensity = useCallback(
|
|
47
|
+
(value: (typeof densityValues)[number]) =>
|
|
48
|
+
(_event: React.MouseEvent<HTMLLIElement>) => {
|
|
49
|
+
// This is necessary to avoid linter errors, while the _event is necessary to satisfy the onClick type
|
|
50
|
+
if (process.env.NODE_ENV === "development") console.debug(_event);
|
|
51
|
+
|
|
52
|
+
setRowDensity(value);
|
|
53
|
+
},
|
|
54
|
+
[setRowDensity],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const changeColumnVisibility = useCallback(
|
|
58
|
+
(columnId: string) => (_event: React.MouseEvent<HTMLLIElement>) => {
|
|
59
|
+
// This is necessary to avoid linter errors, while the _event is necessary to satisfy the onClick type
|
|
60
|
+
if (process.env.NODE_ENV === "development") console.debug(_event);
|
|
61
|
+
|
|
62
|
+
setColumnVisibility((prevVisibility) => ({
|
|
63
|
+
...prevVisibility,
|
|
64
|
+
[columnId]: prevVisibility ? prevVisibility[columnId] === false : false,
|
|
65
|
+
}));
|
|
66
|
+
},
|
|
67
|
+
[setColumnVisibility],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const isColumnVisibilityChecked = useMemo(() => {
|
|
71
|
+
return columns.reduce(
|
|
72
|
+
(acc, column) => {
|
|
73
|
+
const isChecked = columnVisibility
|
|
74
|
+
? columnVisibility[column.accessorKey!] !== false
|
|
75
|
+
: true;
|
|
76
|
+
acc[column.accessorKey!] = isChecked;
|
|
77
|
+
return acc;
|
|
78
|
+
},
|
|
79
|
+
{} as Record<string, boolean>,
|
|
80
|
+
);
|
|
81
|
+
}, [columns, columnVisibility]);
|
|
82
|
+
|
|
45
83
|
return (
|
|
46
84
|
<>
|
|
47
85
|
{hasChangeableDensity && (
|
|
@@ -56,7 +94,7 @@ const DataTableSettings = ({
|
|
|
56
94
|
<MenuItem
|
|
57
95
|
key={value}
|
|
58
96
|
isSelected={rowDensity === value}
|
|
59
|
-
onClick={(
|
|
97
|
+
onClick={changeRowDensity(value)}
|
|
60
98
|
>
|
|
61
99
|
{`${value.charAt(0).toUpperCase()}${value.slice(1)}`}
|
|
62
100
|
</MenuItem>
|
|
@@ -78,23 +116,10 @@ const DataTableSettings = ({
|
|
|
78
116
|
.map((column) => (
|
|
79
117
|
<MenuItem
|
|
80
118
|
key={column.accessorKey}
|
|
81
|
-
onClick={(
|
|
82
|
-
const columnId = column.id as string;
|
|
83
|
-
setColumnVisibility((prevVisibility) => ({
|
|
84
|
-
...prevVisibility,
|
|
85
|
-
[columnId]: prevVisibility
|
|
86
|
-
? prevVisibility[columnId] === false
|
|
87
|
-
: false,
|
|
88
|
-
}));
|
|
89
|
-
}}
|
|
119
|
+
onClick={changeColumnVisibility(column.id as string)}
|
|
90
120
|
>
|
|
91
121
|
<MuiCheckbox
|
|
92
|
-
checked={
|
|
93
|
-
columnVisibility
|
|
94
|
-
? columnVisibility[column.accessorKey as string] !==
|
|
95
|
-
false
|
|
96
|
-
: true
|
|
97
|
-
}
|
|
122
|
+
checked={isColumnVisibilityChecked[column.accessorKey!]}
|
|
98
123
|
/>
|
|
99
124
|
{column.header}
|
|
100
125
|
</MenuItem>
|
|
@@ -105,5 +130,6 @@ const DataTableSettings = ({
|
|
|
105
130
|
</>
|
|
106
131
|
);
|
|
107
132
|
};
|
|
133
|
+
|
|
108
134
|
const MemoizedDataTableSettings = memo(DataTableSettings);
|
|
109
135
|
export { MemoizedDataTableSettings as DataTableSettings };
|
package/src/DataTable/index.tsx
CHANGED
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
* See the License for the specific language governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
export {
|
|
13
|
+
export {
|
|
14
|
+
DataTable,
|
|
15
|
+
type DataTableProps,
|
|
16
|
+
type DataTableGetDataType,
|
|
17
|
+
type DataTableOnReorderRowsType,
|
|
18
|
+
} from "./DataTable";
|
|
19
|
+
export { DataTableEmptyState } from "./DataTableEmptyState";
|
|
14
20
|
export { densityValues } from "./constants";
|
|
15
21
|
export type {
|
|
16
22
|
MRT_ColumnFiltersState as DataTableFiltersState,
|
|
@@ -48,6 +48,8 @@ export const useRowReordering = ({
|
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Needs to include the totalRows check because totalRows might not
|
|
52
|
+
// be set. If it isn't set, this whole check doesn't matter.
|
|
51
53
|
if (totalRows && newRowIndex > totalRows) {
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
@@ -104,7 +106,7 @@ export const useRowReordering = ({
|
|
|
104
106
|
return undefined;
|
|
105
107
|
};
|
|
106
108
|
|
|
107
|
-
const
|
|
109
|
+
const setHoveredRow = (
|
|
108
110
|
table: MRT_TableInstance<MRT_RowData>,
|
|
109
111
|
id: MRT_RowData["id"],
|
|
110
112
|
) => {
|
|
@@ -175,12 +177,12 @@ export const useRowReordering = ({
|
|
|
175
177
|
|
|
176
178
|
if (isArrowDown || isArrowUp) {
|
|
177
179
|
const nextIndex = isArrowDown ? index + 1 : index - 1;
|
|
178
|
-
|
|
180
|
+
setHoveredRow(table, data[nextIndex]?.id);
|
|
179
181
|
}
|
|
180
182
|
} else {
|
|
181
183
|
if (isArrowDown || isArrowUp) {
|
|
182
184
|
const nextIndex = isArrowDown ? row.index + 1 : row.index - 1;
|
|
183
|
-
|
|
185
|
+
setHoveredRow(table, data[nextIndex]?.id);
|
|
184
186
|
}
|
|
185
187
|
}
|
|
186
188
|
} else {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.
|
|
3
|
+
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
|
|
4
|
+
*
|
|
5
|
+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
|
6
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
7
|
+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
8
|
+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9
|
+
*
|
|
10
|
+
* See the License for the specific language governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Dispatch,
|
|
15
|
+
SetStateAction,
|
|
16
|
+
useCallback,
|
|
17
|
+
useEffect,
|
|
18
|
+
useRef,
|
|
19
|
+
} from "react";
|
|
20
|
+
|
|
21
|
+
type UseScrollIndicationProps = {
|
|
22
|
+
tableOuterContainer: HTMLDivElement | null;
|
|
23
|
+
tableInnerContainer: HTMLDivElement | null;
|
|
24
|
+
setIsTableContainerScrolledToStart: Dispatch<SetStateAction<boolean>>;
|
|
25
|
+
setIsTableContainerScrolledToEnd: Dispatch<SetStateAction<boolean>>;
|
|
26
|
+
setTableInnerContainerWidth: Dispatch<SetStateAction<string>>;
|
|
27
|
+
};
|
|
28
|
+
export const useScrollIndication = ({
|
|
29
|
+
tableOuterContainer,
|
|
30
|
+
tableInnerContainer,
|
|
31
|
+
setIsTableContainerScrolledToStart,
|
|
32
|
+
setIsTableContainerScrolledToEnd,
|
|
33
|
+
setTableInnerContainerWidth,
|
|
34
|
+
}: UseScrollIndicationProps) => {
|
|
35
|
+
const animationFrameIdRef = useRef<number | null>(null);
|
|
36
|
+
|
|
37
|
+
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
38
|
+
|
|
39
|
+
const checkScrollIndicators = useCallback(() => {
|
|
40
|
+
if (!tableOuterContainer || !tableInnerContainer) return;
|
|
41
|
+
|
|
42
|
+
const containerWidth = tableOuterContainer.clientWidth;
|
|
43
|
+
const contentWidth = tableInnerContainer.scrollWidth;
|
|
44
|
+
const containerStartScrollPosition = tableInnerContainer.scrollLeft;
|
|
45
|
+
const containerEndScrollPosition =
|
|
46
|
+
containerStartScrollPosition + containerWidth;
|
|
47
|
+
|
|
48
|
+
setIsTableContainerScrolledToStart(containerStartScrollPosition <= 16);
|
|
49
|
+
setIsTableContainerScrolledToEnd(
|
|
50
|
+
containerEndScrollPosition >= contentWidth - 16,
|
|
51
|
+
);
|
|
52
|
+
}, [
|
|
53
|
+
tableInnerContainer,
|
|
54
|
+
tableOuterContainer,
|
|
55
|
+
setIsTableContainerScrolledToEnd,
|
|
56
|
+
setIsTableContainerScrolledToStart,
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (resizeObserverRef.current) return; // Avoid creating multiple observers
|
|
61
|
+
|
|
62
|
+
let debounceTimer: ReturnType<typeof setTimeout>;
|
|
63
|
+
|
|
64
|
+
resizeObserverRef.current = new ResizeObserver(() => {
|
|
65
|
+
clearTimeout(debounceTimer);
|
|
66
|
+
debounceTimer = setTimeout(() => {
|
|
67
|
+
if (!animationFrameIdRef.current) {
|
|
68
|
+
animationFrameIdRef.current = requestAnimationFrame(() => {
|
|
69
|
+
checkScrollIndicators();
|
|
70
|
+
animationFrameIdRef.current = null;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
setTableInnerContainerWidth(
|
|
75
|
+
tableInnerContainer?.clientWidth
|
|
76
|
+
? `${tableInnerContainer.clientWidth}px`
|
|
77
|
+
: "100%",
|
|
78
|
+
);
|
|
79
|
+
}, 100); // debounce delay
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (tableOuterContainer) {
|
|
83
|
+
resizeObserverRef.current.observe(tableOuterContainer);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return () => {
|
|
87
|
+
if (resizeObserverRef.current) {
|
|
88
|
+
resizeObserverRef.current.disconnect();
|
|
89
|
+
resizeObserverRef.current = null;
|
|
90
|
+
}
|
|
91
|
+
clearTimeout(debounceTimer); // Ensure timer is cleared on component unmount
|
|
92
|
+
if (animationFrameIdRef.current) {
|
|
93
|
+
cancelAnimationFrame(animationFrameIdRef.current);
|
|
94
|
+
animationFrameIdRef.current = null;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}, [
|
|
98
|
+
checkScrollIndicators,
|
|
99
|
+
setTableInnerContainerWidth,
|
|
100
|
+
tableOuterContainer,
|
|
101
|
+
tableInnerContainer,
|
|
102
|
+
setIsTableContainerScrolledToStart,
|
|
103
|
+
setIsTableContainerScrolledToEnd,
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
tableInnerContainer?.addEventListener("scroll", checkScrollIndicators);
|
|
108
|
+
return () =>
|
|
109
|
+
tableInnerContainer?.removeEventListener("scroll", checkScrollIndicators);
|
|
110
|
+
}, [tableInnerContainer, checkScrollIndicators]); // Re-run when innerContainerRef changes
|
|
111
|
+
|
|
112
|
+
// Initial check to set state correctly on mount
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
checkScrollIndicators();
|
|
115
|
+
|
|
116
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
117
|
+
}, []);
|
|
118
|
+
};
|
package/src/Field.tsx
CHANGED
|
@@ -29,6 +29,14 @@ import { useUniqueId } from "./useUniqueId";
|
|
|
29
29
|
|
|
30
30
|
export const fieldTypeValues = ["single", "group"] as const;
|
|
31
31
|
|
|
32
|
+
export type RenderFieldComponentProps = {
|
|
33
|
+
ariaDescribedBy?: string;
|
|
34
|
+
dataSe?: string;
|
|
35
|
+
errorMessageElementId?: string;
|
|
36
|
+
id: string;
|
|
37
|
+
labelElementId: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
32
40
|
export type FieldProps = {
|
|
33
41
|
/**
|
|
34
42
|
* If `error` is not undefined, the `input` will indicate an error.
|
|
@@ -67,13 +75,7 @@ export type FieldProps = {
|
|
|
67
75
|
errorMessageElementId,
|
|
68
76
|
id,
|
|
69
77
|
labelElementId,
|
|
70
|
-
}:
|
|
71
|
-
ariaDescribedBy?: string;
|
|
72
|
-
dataSe?: string;
|
|
73
|
-
errorMessageElementId?: string;
|
|
74
|
-
id: string;
|
|
75
|
-
labelElementId: string;
|
|
76
|
-
}) => ReactElement;
|
|
78
|
+
}: RenderFieldComponentProps) => ReactElement;
|
|
77
79
|
};
|
|
78
80
|
|
|
79
81
|
const Field = ({
|