@okta/odyssey-react-mui 1.9.13 → 1.9.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okta/odyssey-react-mui",
3
- "version": "1.9.13",
3
+ "version": "1.9.15",
4
4
  "description": "React MUI components for Odyssey, Okta's design system",
5
5
  "author": "Okta, Inc.",
6
6
  "license": "Apache-2.0",
@@ -51,7 +51,7 @@
51
51
  "@mui/system": "^5.14.9",
52
52
  "@mui/utils": "^5.11.2",
53
53
  "@mui/x-date-pickers": "^5.0.15",
54
- "@okta/odyssey-design-tokens": "1.9.13",
54
+ "@okta/odyssey-design-tokens": "1.9.15",
55
55
  "date-fns": "^2.30.0",
56
56
  "i18next": "^23.5.1",
57
57
  "material-react-table": "^2.0.2",
@@ -63,5 +63,5 @@
63
63
  "react": ">=17 <19",
64
64
  "react-dom": ">=17 <19"
65
65
  },
66
- "gitHead": "747861bf33f9c65b901782e306c16b0ccf26a942"
66
+ "gitHead": "6c4dcd20f0d97c07740c0f6132fe76347a6e7f18"
67
67
  }
@@ -66,7 +66,7 @@ export const OdysseyTranslationProvider = <SupportedLanguages extends string>({
66
66
  };
67
67
  // Defaults to the browser's language if available otherwise `en` will be used
68
68
  i18n.changeLanguage(
69
- normalizedLanguageCode || window.navigator.language,
69
+ languageCode || window.navigator.language,
70
70
  changeHtmlElementLanguageAttribute
71
71
  );
72
72
  }, [languageCode]);
@@ -23,6 +23,7 @@ import {
23
23
  MRT_RowSelectionState,
24
24
  MRT_Row,
25
25
  MRT_ColumnDef,
26
+ MRT_TableInstance,
26
27
  } from "material-react-table";
27
28
  import {
28
29
  Fragment,
@@ -33,6 +34,7 @@ import {
33
34
  useMemo,
34
35
  useRef,
35
36
  useState,
37
+ KeyboardEvent,
36
38
  } from "react";
37
39
  import {
38
40
  ArrowTopIcon,
@@ -45,6 +47,7 @@ import {
45
47
  MoreIcon,
46
48
  } from "../icons.generated";
47
49
  import { Checkbox as MuiCheckbox } from "@mui/material";
50
+ import { useOdysseyDesignTokens } from "../OdysseyDesignTokensContext";
48
51
  import {
49
52
  DataTablePagination,
50
53
  paginationTypeValues,
@@ -254,6 +257,32 @@ export type DataTableProps = {
254
257
  ) => ReactElement<typeof MenuItem | typeof Fragment>;
255
258
  };
256
259
 
260
+ type TableType = MRT_TableInstance<MRT_RowData>;
261
+
262
+ const reorderDataRowsLocally = ({
263
+ currentData,
264
+ rowId,
265
+ newIndex,
266
+ }: {
267
+ currentData: MRT_TableOptions<MRT_RowData>["data"];
268
+ rowId: string;
269
+ newIndex: number;
270
+ }) => {
271
+ const updatedData = [...currentData];
272
+
273
+ const rowIndex = updatedData.findIndex((row) => row.id === rowId);
274
+
275
+ if (rowIndex !== -1) {
276
+ // Remove the row from its current position
277
+ const [removedRow] = updatedData.splice(rowIndex, 1);
278
+
279
+ // Insert the row at the new index
280
+ updatedData.splice(newIndex, 0, removedRow);
281
+ }
282
+
283
+ return updatedData;
284
+ };
285
+
257
286
  const DataTable = ({
258
287
  columns,
259
288
  data: dataProp,
@@ -280,6 +309,7 @@ const DataTable = ({
280
309
  hasSearch,
281
310
  hasSorting,
282
311
  }: DataTableProps) => {
312
+ const odysseyDesignTokens = useOdysseyDesignTokens();
283
313
  const [draggingRow, setDraggingRow] = useState<MRT_Row<MRT_RowData> | null>();
284
314
  const [showSkeletons, setShowSkeletons] = useState<boolean>(true);
285
315
  const [data, setData] =
@@ -305,6 +335,14 @@ const DataTable = ({
305
335
  const [globalFilter, setGlobalFilter] = useState<string>("");
306
336
  const [filters, setFilters] = useState<Array<DataFilter>>();
307
337
 
338
+ useEffect(() => {
339
+ setShowSkeletons(false);
340
+ }, [data]);
341
+
342
+ useEffect(() => {
343
+ onRowSelectionChange?.(rowSelection);
344
+ }, [rowSelection, onRowSelectionChange]);
345
+
308
346
  const refreshData = useCallback(async () => {
309
347
  setShowSkeletons(true);
310
348
  try {
@@ -318,11 +356,14 @@ const DataTable = ({
318
356
  setData(newData);
319
357
  setShowSkeletons(false);
320
358
  } catch (error) {
321
- console.log(error);
322
359
  setShowSkeletons(false);
323
360
  }
324
361
  }, [page, resultsPerPage, sorting, globalFilter, filters, fetchDataFn]);
325
362
 
363
+ useEffect(() => {
364
+ refreshData();
365
+ }, [refreshData, page, resultsPerPage, sorting, globalFilter, filters]);
366
+
326
367
  const handleSortingChange = useCallback(
327
368
  (updater: MRT_Updater<MRT_SortingState>) => {
328
369
  setSorting((prevSorting) =>
@@ -359,7 +400,7 @@ const DataTable = ({
359
400
  [rowSelection]
360
401
  );
361
402
 
362
- const handleReordering = useCallback(
403
+ const updateRowOrder = useCallback(
363
404
  ({ rowId, newIndex }: { rowId: string; newIndex: number }) => {
364
405
  if (newIndex < 0) {
365
406
  return;
@@ -369,26 +410,145 @@ const DataTable = ({
369
410
  return;
370
411
  }
371
412
 
413
+ const newData = reorderDataRowsLocally({
414
+ currentData: data,
415
+ rowId,
416
+ newIndex,
417
+ });
418
+
419
+ setData(newData);
372
420
  reorderDataFn?.({ rowId, newIndex });
373
421
  refreshData();
374
422
  },
375
- [totalRows, reorderDataFn, refreshData]
423
+ [data, totalRows, reorderDataFn, refreshData]
376
424
  );
377
425
 
378
- useEffect(() => {
379
- setShowSkeletons(false);
380
- }, [data]);
426
+ const rowVirtualizerInstanceRef =
427
+ useRef<MRT_Virtualizer<HTMLDivElement, HTMLTableRowElement>>(null);
381
428
 
382
- useEffect(() => {
383
- refreshData();
384
- }, [refreshData, page, resultsPerPage, sorting, globalFilter, filters]);
429
+ const getRowFromTableAndSetHovered = (
430
+ table: TableType,
431
+ id: MRT_RowData["id"]
432
+ ) => {
433
+ if (id) {
434
+ const nextRow: MRT_RowData = table.getRow(id);
385
435
 
386
- useEffect(() => {
387
- onRowSelectionChange?.(rowSelection);
388
- }, [rowSelection, onRowSelectionChange]);
436
+ if (nextRow) {
437
+ table.setHoveredRow(nextRow);
438
+ }
439
+ }
440
+ };
441
+
442
+ const resetDraggingAndHoveredRow = (table: TableType) => {
443
+ setDraggingRow(null);
444
+ table.setHoveredRow(null);
445
+ };
446
+
447
+ type HandleDragHandleKeyDownArgs = {
448
+ table: TableType;
449
+ row: MRT_Row<MRT_RowData>;
450
+ event: KeyboardEvent<HTMLButtonElement>;
451
+ };
452
+
453
+ const handleDragHandleKeyDown = useCallback(
454
+ ({ table, row, event }: HandleDragHandleKeyDownArgs) => {
455
+ const { hoveredRow } = table.getState();
456
+
457
+ const { key } = event;
458
+
459
+ const isSpaceKey = key === " ";
460
+ const isEnterKey = key === "Enter";
461
+ const isEscapeKey = key === "Escape";
462
+ const isArrowDown = key === "ArrowDown";
463
+ const isArrowUp = key === "ArrowUp";
464
+ const isSpaceOrEnter = isSpaceKey || isEnterKey;
465
+ const zeroIndexedPageNumber = page - 1;
466
+ const currentIndex = row.index + zeroIndexedPageNumber * resultsPerPage;
467
+
468
+ if (isEscapeKey) {
469
+ resetDraggingAndHoveredRow(table);
470
+ return;
471
+ }
389
472
 
390
- const rowVirtualizerInstanceRef =
391
- useRef<MRT_Virtualizer<HTMLDivElement, HTMLTableRowElement>>(null);
473
+ if (isSpaceOrEnter) {
474
+ event.preventDefault();
475
+ event.stopPropagation();
476
+ }
477
+
478
+ if (draggingRow) {
479
+ if (typeof hoveredRow?.index === "number") {
480
+ const { index } = hoveredRow;
481
+
482
+ if (isSpaceOrEnter) {
483
+ const pageRelativeIndex =
484
+ index + zeroIndexedPageNumber * resultsPerPage;
485
+
486
+ if (pageRelativeIndex !== currentIndex) {
487
+ updateRowOrder({
488
+ rowId: row.id,
489
+ newIndex: pageRelativeIndex,
490
+ });
491
+
492
+ // Can't transition CSS hover effect. Use timeout to delay hovered row effect removal
493
+ setTimeout(() => {
494
+ resetDraggingAndHoveredRow(table);
495
+ }, odysseyDesignTokens.TransitionDurationMainAsNumber);
496
+ return;
497
+ }
498
+ }
499
+
500
+ if (isArrowDown || isArrowUp) {
501
+ const nextIndex = isArrowDown ? index + 1 : index - 1;
502
+ getRowFromTableAndSetHovered(table, data[nextIndex]?.id);
503
+ }
504
+ } else {
505
+ if (isArrowDown || isArrowUp) {
506
+ const nextIndex = isArrowDown ? row.index + 1 : row.index - 1;
507
+ getRowFromTableAndSetHovered(table, data[nextIndex]?.id);
508
+ }
509
+ }
510
+ } else {
511
+ if (isSpaceOrEnter) {
512
+ setDraggingRow(row);
513
+ }
514
+ }
515
+ },
516
+ [
517
+ data,
518
+ draggingRow,
519
+ odysseyDesignTokens,
520
+ page,
521
+ resultsPerPage,
522
+ updateRowOrder,
523
+ ]
524
+ );
525
+
526
+ const handleDragHandleOnDragEnd = useCallback(
527
+ (table: TableType) => {
528
+ const cols = table.getAllColumns();
529
+ cols[0].toggleVisibility();
530
+
531
+ const { draggingRow, hoveredRow } = table.getState();
532
+ if (draggingRow) {
533
+ updateRowOrder({
534
+ rowId: draggingRow.id,
535
+ newIndex: (hoveredRow as MRT_RowData).index,
536
+ });
537
+ }
538
+
539
+ setDraggingRow(null);
540
+ },
541
+ [updateRowOrder]
542
+ );
543
+
544
+ const handleDragHandleOnDragCapture = useCallback(
545
+ (table: TableType) => {
546
+ if (!draggingRow && table.getState().draggingRow?.id) {
547
+ setDraggingRow(table.getState().draggingRow);
548
+ }
549
+ },
550
+ [draggingRow]
551
+ );
392
552
 
393
553
  const table = useMaterialReactTable({
394
554
  columns: columns,
@@ -440,13 +600,13 @@ const DataTable = ({
440
600
  muiTableBodyCellProps: {
441
601
  sx: {
442
602
  minWidth: 0,
443
- width: 32,
603
+ width: "auto",
444
604
  },
445
605
  },
446
606
  muiTableHeadCellProps: {
447
607
  sx: {
448
608
  minWidth: 0,
449
- width: 32,
609
+ width: "auto",
450
610
  },
451
611
  },
452
612
  },
@@ -487,29 +647,27 @@ const DataTable = ({
487
647
  : undefined,
488
648
  }),
489
649
 
490
- muiRowDragHandleProps: {
491
- tabIndex: -1,
492
- onDragEnd: () => {
493
- const cols = table.getAllColumns();
494
- cols[0].toggleVisibility();
495
-
496
- const { draggingRow, hoveredRow } = table.getState();
497
- if (draggingRow) {
498
- handleReordering({
499
- rowId: draggingRow.id,
500
- newIndex: (hoveredRow as MRT_RowData).index,
501
- });
502
- }
503
-
504
- setDraggingRow(null);
650
+ muiRowDragHandleProps: ({ table, row }) => ({
651
+ title: "Drag row or press space/enter key to start and stop reordering",
652
+ "aria-label":
653
+ "Drag row to reorder. Or, press space or enter to start and stop reordering and esc to cancel.",
654
+ onKeyDown: (event) => handleDragHandleKeyDown({ table, row, event }),
655
+ onBlur: () => {
656
+ resetDraggingAndHoveredRow(table);
505
657
  },
506
-
507
- onDragCapture: () => {
508
- if (!draggingRow && table.getState().draggingRow?.id) {
509
- setDraggingRow(table.getState().draggingRow);
510
- }
658
+ onDragEnd: () => handleDragHandleOnDragEnd(table),
659
+ onDragCapture: () => handleDragHandleOnDragCapture(table),
660
+ sx: {
661
+ padding: odysseyDesignTokens.Spacing1,
662
+ borderRadius: odysseyDesignTokens.BorderRadiusMain,
663
+
664
+ "&:focus-visible": {
665
+ boxShadow: `0 0 0 2px ${odysseyDesignTokens.HueNeutralWhite}, 0 0 0 4px ${odysseyDesignTokens.PalettePrimaryMain}`,
666
+ outline: "2px solid transparent",
667
+ outlineOffset: "1px",
668
+ },
511
669
  },
512
- },
670
+ }),
513
671
 
514
672
  renderRowActions: ({ row }) => {
515
673
  const currentIndex = row.index + (page - 1) * resultsPerPage;
@@ -533,14 +691,14 @@ const DataTable = ({
533
691
  )}
534
692
  <MenuItem
535
693
  isDisabled={currentIndex <= 0}
536
- onClick={() => handleReordering({ rowId: row.id, newIndex: 0 })}
694
+ onClick={() => updateRowOrder({ rowId: row.id, newIndex: 0 })}
537
695
  >
538
696
  <ArrowTopIcon /> Bring to front
539
697
  </MenuItem>
540
698
  <MenuItem
541
699
  isDisabled={currentIndex <= 0}
542
700
  onClick={() =>
543
- handleReordering({
701
+ updateRowOrder({
544
702
  rowId: row.id,
545
703
  newIndex: currentIndex <= 0 ? 0 : currentIndex - 1,
546
704
  })
@@ -551,7 +709,7 @@ const DataTable = ({
551
709
  <MenuItem
552
710
  isDisabled={totalRows ? currentIndex >= totalRows - 1 : false}
553
711
  onClick={() =>
554
- handleReordering({
712
+ updateRowOrder({
555
713
  rowId: row.id,
556
714
  newIndex: currentIndex + 1,
557
715
  })
@@ -564,7 +722,7 @@ const DataTable = ({
564
722
  <MenuItem
565
723
  isDisabled={currentIndex >= totalRows - 1}
566
724
  onClick={() =>
567
- handleReordering({
725
+ updateRowOrder({
568
726
  rowId: row.id,
569
727
  newIndex: totalRows,
570
728
  })