@okta/odyssey-react-mui 1.26.0 → 1.27.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 (79) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/Surface.js +10 -2
  3. package/dist/Surface.js.map +1 -1
  4. package/dist/index.scss +1 -1
  5. package/dist/labs/DataView/DataView.js +6 -0
  6. package/dist/labs/DataView/DataView.js.map +1 -1
  7. package/dist/labs/DataView/componentTypes.js.map +1 -1
  8. package/dist/labs/SideNav/NavAccordion.js +4 -5
  9. package/dist/labs/SideNav/NavAccordion.js.map +1 -1
  10. package/dist/labs/SideNav/SideNav.js +167 -93
  11. package/dist/labs/SideNav/SideNav.js.map +1 -1
  12. package/dist/labs/SideNav/SideNavItemContent.js +97 -57
  13. package/dist/labs/SideNav/SideNavItemContent.js.map +1 -1
  14. package/dist/labs/SideNav/SideNavItemContentContext.js +1 -0
  15. package/dist/labs/SideNav/SideNavItemContentContext.js.map +1 -1
  16. package/dist/labs/SideNav/SideNavItemLinkContent.js +2 -2
  17. package/dist/labs/SideNav/SideNavItemLinkContent.js.map +1 -1
  18. package/dist/labs/SideNav/SideNavToggleButton.js +5 -5
  19. package/dist/labs/SideNav/SideNavToggleButton.js.map +1 -1
  20. package/dist/labs/SideNav/SortableList/SortableItem.js +162 -0
  21. package/dist/labs/SideNav/SortableList/SortableItem.js.map +1 -0
  22. package/dist/labs/SideNav/SortableList/SortableList.js +118 -0
  23. package/dist/labs/SideNav/SortableList/SortableList.js.map +1 -0
  24. package/dist/labs/SideNav/SortableList/SortableOverlay.js +30 -0
  25. package/dist/labs/SideNav/SortableList/SortableOverlay.js.map +1 -0
  26. package/dist/labs/SideNav/types.js.map +1 -1
  27. package/dist/labs/TopNav/TopNav.js +1 -1
  28. package/dist/labs/TopNav/TopNav.js.map +1 -1
  29. package/dist/labs/UiShell/UiShellContent.js +1 -1
  30. package/dist/labs/UiShell/UiShellContent.js.map +1 -1
  31. package/dist/properties/ts/odyssey-react-mui.js +7 -0
  32. package/dist/properties/ts/odyssey-react-mui.js.map +1 -1
  33. package/dist/src/OdysseyTranslationProvider.d.ts +1 -1
  34. package/dist/src/OdysseyTranslationProvider.d.ts.map +1 -1
  35. package/dist/src/Surface.d.ts.map +1 -1
  36. package/dist/src/labs/DataView/DataView.d.ts +1 -1
  37. package/dist/src/labs/DataView/DataView.d.ts.map +1 -1
  38. package/dist/src/labs/DataView/componentTypes.d.ts +3 -2
  39. package/dist/src/labs/DataView/componentTypes.d.ts.map +1 -1
  40. package/dist/src/labs/SideNav/NavAccordion.d.ts +2 -6
  41. package/dist/src/labs/SideNav/NavAccordion.d.ts.map +1 -1
  42. package/dist/src/labs/SideNav/SideNav.d.ts +1 -1
  43. package/dist/src/labs/SideNav/SideNav.d.ts.map +1 -1
  44. package/dist/src/labs/SideNav/SideNavItemContent.d.ts +37 -1
  45. package/dist/src/labs/SideNav/SideNavItemContent.d.ts.map +1 -1
  46. package/dist/src/labs/SideNav/SideNavItemContentContext.d.ts +1 -0
  47. package/dist/src/labs/SideNav/SideNavItemContentContext.d.ts.map +1 -1
  48. package/dist/src/labs/SideNav/SideNavToggleButton.d.ts.map +1 -1
  49. package/dist/src/labs/SideNav/SortableList/SortableItem.d.ts +26 -0
  50. package/dist/src/labs/SideNav/SortableList/SortableItem.d.ts.map +1 -0
  51. package/dist/src/labs/SideNav/SortableList/SortableList.d.ts +36 -0
  52. package/dist/src/labs/SideNav/SortableList/SortableList.d.ts.map +1 -0
  53. package/dist/src/labs/SideNav/SortableList/SortableOverlay.d.ts +17 -0
  54. package/dist/src/labs/SideNav/SortableList/SortableOverlay.d.ts.map +1 -0
  55. package/dist/src/labs/SideNav/types.d.ts +16 -6
  56. package/dist/src/labs/SideNav/types.d.ts.map +1 -1
  57. package/dist/src/properties/ts/odyssey-react-mui.d.ts +7 -0
  58. package/dist/src/properties/ts/odyssey-react-mui.d.ts.map +1 -1
  59. package/dist/tsconfig.production.tsbuildinfo +1 -1
  60. package/i18n.config.json +2 -1
  61. package/package.json +6 -3
  62. package/src/Surface.tsx +16 -4
  63. package/src/labs/DataView/DataView.tsx +6 -0
  64. package/src/labs/DataView/componentTypes.ts +6 -2
  65. package/src/labs/SideNav/NavAccordion.tsx +5 -10
  66. package/src/labs/SideNav/SideNav.test.tsx +8 -8
  67. package/src/labs/SideNav/SideNav.tsx +232 -119
  68. package/src/labs/SideNav/SideNavItemContent.tsx +114 -61
  69. package/src/labs/SideNav/SideNavItemContentContext.tsx +2 -0
  70. package/src/labs/SideNav/SideNavItemLinkContent.tsx +2 -2
  71. package/src/labs/SideNav/SideNavToggleButton.tsx +5 -9
  72. package/src/labs/SideNav/SortableList/SortableItem.tsx +202 -0
  73. package/src/labs/SideNav/SortableList/SortableList.tsx +122 -0
  74. package/src/labs/SideNav/SortableList/SortableOverlay.tsx +34 -0
  75. package/src/labs/SideNav/types.ts +16 -6
  76. package/src/labs/TopNav/TopNav.tsx +1 -1
  77. package/src/labs/UiShell/UiShellContent.tsx +1 -1
  78. package/src/properties/odyssey-react-mui.properties +7 -0
  79. package/src/properties/ts/odyssey-react-mui.ts +1 -1
@@ -38,6 +38,9 @@ import {
38
38
  import { SideNavFooterContent } from "./SideNavFooterContent";
39
39
  import { SideNavItemContentContext } from "./SideNavItemContentContext";
40
40
  import { SideNavToggleButton } from "./SideNavToggleButton";
41
+ import { SortableList } from "./SortableList/SortableList";
42
+ // eslint-disable-next-line import/no-extraneous-dependencies
43
+ import { arrayMove } from "@dnd-kit/sortable";
41
44
 
42
45
  export const DEFAULT_SIDE_NAV_WIDTH = "300px";
43
46
 
@@ -58,14 +61,41 @@ const StyledCollapsibleContent = styled("div", {
58
61
  }) => ({
59
62
  position: "relative",
60
63
  display: "inline-grid",
61
- gridTemplateColumns: isSideNavCollapsed ? 0 : DEFAULT_SIDE_NAV_WIDTH,
62
- gridTemplateRows: "max-content 1fr max-content",
63
- minWidth: isSideNavCollapsed ? 0 : DEFAULT_SIDE_NAV_WIDTH,
64
+ gridTemplateColumns: DEFAULT_SIDE_NAV_WIDTH,
65
+ // gridTemplateRows: "max-content 1fr max-content",
64
66
  height: "100%",
65
67
  transition: `grid-template-columns ${odysseyDesignTokens.TransitionDurationMain}, opacity 300ms`,
66
68
  transitionTimingFunction: odysseyDesignTokens.TransitionTimingMain,
67
69
  overflow: "hidden",
68
- opacity: isSideNavCollapsed ? 0 : 1,
70
+
71
+ ...(isSideNavCollapsed && {
72
+ gridTemplateColumns: 0,
73
+ opacity: 0,
74
+ }),
75
+ }),
76
+ );
77
+
78
+ const StyledOpacityTransitionContainer = styled("div", {
79
+ shouldForwardProp: (prop) =>
80
+ prop !== "odysseyDesignTokens" && prop !== "isSideNavCollapsed",
81
+ })(
82
+ ({
83
+ odysseyDesignTokens,
84
+ isSideNavCollapsed,
85
+ }: {
86
+ odysseyDesignTokens: DesignTokens;
87
+ isSideNavCollapsed: boolean;
88
+ }) => ({
89
+ display: "inline-grid",
90
+ gridTemplateRows: "max-content 1fr max-content",
91
+ height: "100%",
92
+ transition: `opacity 50ms`,
93
+ transitionTimingFunction: odysseyDesignTokens.TransitionTimingMain,
94
+ overflow: "hidden",
95
+
96
+ ...(isSideNavCollapsed && {
97
+ opacity: 0,
98
+ }),
69
99
  }),
70
100
  );
71
101
 
@@ -96,6 +126,7 @@ const StyledSideNav = styled("nav", {
96
126
  transform: `translateX(0)`,
97
127
  transition: `opacity ${odysseyDesignTokens.TransitionDurationMain}, transform ${odysseyDesignTokens.TransitionDurationMain}`,
98
128
  width: odysseyDesignTokens.Spacing2,
129
+ zIndex: 2,
99
130
  },
100
131
 
101
132
  "&:has([data-sidenav-toggle='true']:hover), &:has([data-sidenav-toggle='true']:focus)":
@@ -159,7 +190,7 @@ const SideNavScrollableContainer = styled("div", {
159
190
  const SectionHeaderContainer = styled("li", {
160
191
  shouldForwardProp: (prop) => prop !== "odysseyDesignTokens",
161
192
  })(({ odysseyDesignTokens }: { odysseyDesignTokens: DesignTokens }) => ({
162
- paddingBlock: odysseyDesignTokens.Spacing3,
193
+ paddingBlock: odysseyDesignTokens.Spacing1,
163
194
  paddingInline: odysseyDesignTokens.Spacing4,
164
195
  }));
165
196
 
@@ -257,6 +288,7 @@ const SideNav = ({
257
288
  logoProps,
258
289
  onCollapse,
259
290
  onExpand,
291
+ onSort,
260
292
  sideNavItems,
261
293
  }: SideNavProps) => {
262
294
  const [isSideNavCollapsed, setSideNavCollapsed] = useState(false);
@@ -267,6 +299,7 @@ const SideNav = ({
267
299
  const intersectionObserverRef = useRef<IntersectionObserver | null>(null);
268
300
  const odysseyDesignTokens: DesignTokens = useOdysseyDesignTokens();
269
301
  const { t } = useTranslation();
302
+ const [sideNavItemsList, updateSideNavItemsList] = useState(sideNavItems);
270
303
 
271
304
  useEffect(() => {
272
305
  const updateIsContentScrollable = () => {
@@ -358,8 +391,8 @@ const SideNav = ({
358
391
  */
359
392
  const firstSideNavItemIdWithIsSelected = useMemo(() => {
360
393
  const flattenedItems = sideNavItems.flatMap((sideNavItem) =>
361
- sideNavItem.children
362
- ? [sideNavItem, ...sideNavItem.children]
394
+ sideNavItem.nestedNavItems
395
+ ? [sideNavItem, ...sideNavItem.nestedNavItems]
363
396
  : sideNavItem,
364
397
  );
365
398
  const firstItemWithIsSelected = flattenedItems.find(
@@ -396,31 +429,67 @@ const SideNav = ({
396
429
  [isCompact],
397
430
  );
398
431
 
399
- const processedSideNavItems = useMemo(
400
- () =>
401
- sideNavItems.map((item) => ({
402
- ...item,
403
- children: item.children?.map((childProps) => {
404
- return (
432
+ const setSelectedItem = useCallback(
433
+ (selectedItemId: string) => {
434
+ const updatedSideNavItems = sideNavItemsList.map((item) => {
435
+ if (item.id === selectedItemId) {
436
+ item.isSelected = true;
437
+ } else if (item.isSelected) {
438
+ delete item.isSelected;
439
+ }
440
+
441
+ return item.nestedNavItems
442
+ ? {
443
+ ...item,
444
+ nestedNavItems: item.nestedNavItems.map((childItem) => {
445
+ if (childItem.id === selectedItemId) {
446
+ childItem.isSelected = true;
447
+ } else if (childItem.isSelected) {
448
+ delete childItem.isSelected;
449
+ }
450
+ return childItem;
451
+ }),
452
+ }
453
+ : item;
454
+ });
455
+ updateSideNavItemsList(updatedSideNavItems);
456
+ },
457
+ [sideNavItemsList],
458
+ );
459
+
460
+ const processedSideNavItems = useMemo(() => {
461
+ return sideNavItemsList?.map((item) => ({
462
+ ...item,
463
+ childNavItems: item.nestedNavItems?.map((childProps) => {
464
+ return {
465
+ id: childProps.id,
466
+ isSelected: childProps.isSelected,
467
+ isDisabled: childProps.isDisabled,
468
+ navItem: (
405
469
  <SideNavItemContentContext.Provider
406
- value={{ ...sideNavItemContentProviderValue, depth: 2 }}
470
+ value={{
471
+ ...sideNavItemContentProviderValue,
472
+ depth: 2,
473
+ isSortable: item.isSortable,
474
+ }}
407
475
  key={childProps.id}
408
476
  >
409
477
  <SideNavItemContent
410
478
  {...childProps}
411
- key={childProps.id}
412
479
  scrollRef={getRefIfThisIsFirstNodeWithIsSelected(childProps.id)}
480
+ onItemSelected={setSelectedItem}
413
481
  />
414
482
  </SideNavItemContentContext.Provider>
415
- );
416
- }),
417
- })),
418
- [
419
- getRefIfThisIsFirstNodeWithIsSelected,
420
- sideNavItems,
421
- sideNavItemContentProviderValue,
422
- ],
423
- );
483
+ ),
484
+ };
485
+ }),
486
+ }));
487
+ }, [
488
+ getRefIfThisIsFirstNodeWithIsSelected,
489
+ sideNavItemsList,
490
+ sideNavItemContentProviderValue,
491
+ setSelectedItem,
492
+ ]);
424
493
 
425
494
  const sideNavExpandClickHandler = useCallback(() => {
426
495
  isSideNavCollapsed ? onExpand?.() : onCollapse?.();
@@ -439,6 +508,26 @@ const SideNav = ({
439
508
  [sideNavExpandClickHandler],
440
509
  );
441
510
 
511
+ const setSortedItems = useCallback(
512
+ (parentId: string, activeIndex: number, overIndex: number) => {
513
+ const sortedSideNavItems = sideNavItemsList.map((item) =>
514
+ item.id === parentId && item.nestedNavItems
515
+ ? {
516
+ ...item,
517
+ nestedNavItems: arrayMove(
518
+ item.nestedNavItems,
519
+ activeIndex,
520
+ overIndex,
521
+ ),
522
+ }
523
+ : item,
524
+ );
525
+ updateSideNavItemsList(sortedSideNavItems);
526
+ onSort?.(sortedSideNavItems);
527
+ },
528
+ [onSort, sideNavItemsList],
529
+ );
530
+
442
531
  return (
443
532
  <StyledSideNav
444
533
  aria-label={t("navigation.label")}
@@ -460,107 +549,131 @@ const SideNav = ({
460
549
  isSideNavCollapsed={isSideNavCollapsed}
461
550
  odysseyDesignTokens={odysseyDesignTokens}
462
551
  >
463
- <SideNavHeaderContainer
552
+ <StyledOpacityTransitionContainer
553
+ isSideNavCollapsed={isSideNavCollapsed}
464
554
  odysseyDesignTokens={odysseyDesignTokens}
465
- hasContentScrolled={hasContentScrolled}
466
555
  >
467
- <SideNavHeader
468
- appName={appName}
469
- isLoading={isLoading}
470
- logoProps={logoProps}
471
- />
472
- </SideNavHeaderContainer>
473
- <SideNavScrollableContainer
474
- odysseyDesignTokens={odysseyDesignTokens}
475
- data-se="scrollable-region"
476
- >
477
- <SideNavListContainer role="list" ref={scrollableContentRef}>
478
- {isLoading
479
- ? [...Array(6)].map((_, index) => <LoadingItem key={index} />)
480
- : processedSideNavItems?.map((item) => {
481
- const {
482
- id,
483
- label,
484
- isSectionHeader,
485
- startIcon,
486
- children,
487
- isDefaultExpanded,
488
- isDisabled,
489
- isExpanded,
490
- } = item;
491
-
492
- if (isSectionHeader) {
493
- return (
494
- <SectionHeaderContainer
495
- id={id}
496
- key={id}
497
- odysseyDesignTokens={odysseyDesignTokens}
498
- >
499
- <SectionHeader
556
+ <SideNavHeaderContainer
557
+ odysseyDesignTokens={odysseyDesignTokens}
558
+ hasContentScrolled={hasContentScrolled}
559
+ >
560
+ <SideNavHeader
561
+ appName={appName}
562
+ isLoading={isLoading}
563
+ logoProps={logoProps}
564
+ />
565
+ </SideNavHeaderContainer>
566
+ <SideNavScrollableContainer
567
+ odysseyDesignTokens={odysseyDesignTokens}
568
+ data-se="scrollable-region"
569
+ >
570
+ <SideNavListContainer role="none" ref={scrollableContentRef}>
571
+ {isLoading
572
+ ? [...Array(6)].map((_, index) => <LoadingItem key={index} />)
573
+ : processedSideNavItems?.map((item) => {
574
+ const {
575
+ id,
576
+ label,
577
+ isSectionHeader,
578
+ startIcon,
579
+ childNavItems,
580
+ isSortable,
581
+ isDefaultExpanded,
582
+ isDisabled,
583
+ isExpanded,
584
+ } = item;
585
+
586
+ if (isSectionHeader) {
587
+ return (
588
+ <SectionHeaderContainer
589
+ id={id}
590
+ key={id}
500
591
  odysseyDesignTokens={odysseyDesignTokens}
501
592
  >
502
- {label}
503
- </SectionHeader>
504
- </SectionHeaderContainer>
505
- );
506
- } else if (children) {
507
- return (
508
- <StyledSideNavListItem
509
- id={id}
510
- key={id}
511
- odysseyDesignTokens={odysseyDesignTokens}
512
- disabled={isDisabled}
513
- aria-disabled={isDisabled}
514
- >
515
- <NavAccordion
516
- label={label}
517
- isCompact={isCompact}
518
- isDefaultExpanded={isDefaultExpanded}
519
- isExpanded={isExpanded}
520
- startIcon={startIcon}
521
- isDisabled={isDisabled}
593
+ <SectionHeader
594
+ odysseyDesignTokens={odysseyDesignTokens}
595
+ >
596
+ {label}
597
+ </SectionHeader>
598
+ </SectionHeaderContainer>
599
+ );
600
+ } else if (childNavItems) {
601
+ return (
602
+ <StyledSideNavListItem
603
+ id={id}
604
+ key={id}
605
+ odysseyDesignTokens={odysseyDesignTokens}
606
+ disabled={isDisabled}
607
+ aria-disabled={isDisabled}
522
608
  >
523
- <SideNavListContainer id={`${id}-list`} role="list">
524
- {children}
525
- </SideNavListContainer>
526
- </NavAccordion>
527
- </StyledSideNavListItem>
528
- );
529
- } else {
530
- return (
531
- <SideNavItemContentContext.Provider
532
- key={item.id}
533
- value={sideNavItemContentProviderValue}
534
- >
535
- <SideNavItemContent
536
- {...item}
609
+ <NavAccordion
610
+ label={label}
611
+ isCompact={isCompact}
612
+ isDefaultExpanded={isDefaultExpanded}
613
+ isExpanded={isExpanded}
614
+ startIcon={startIcon}
615
+ isDisabled={isDisabled}
616
+ >
617
+ <SideNavListContainer role="none">
618
+ {isSortable ? (
619
+ <SortableList
620
+ parentId={item.id}
621
+ items={childNavItems}
622
+ onChange={setSortedItems}
623
+ renderItem={(sortableItem) => (
624
+ <SortableList.Item
625
+ id={sortableItem.id}
626
+ isDisabled={sortableItem.isDisabled}
627
+ isSelected={sortableItem.isSelected}
628
+ >
629
+ {sortableItem.navItem}
630
+ </SortableList.Item>
631
+ )}
632
+ />
633
+ ) : (
634
+ childNavItems.map((item) => item.navItem)
635
+ )}
636
+ </SideNavListContainer>
637
+ </NavAccordion>
638
+ </StyledSideNavListItem>
639
+ );
640
+ } else {
641
+ return (
642
+ <SideNavItemContentContext.Provider
537
643
  key={item.id}
538
- scrollRef={getRefIfThisIsFirstNodeWithIsSelected(
539
- item.id,
540
- )}
541
- />
542
- </SideNavItemContentContext.Provider>
543
- );
544
- }
545
- })}
546
- </SideNavListContainer>
547
- </SideNavScrollableContainer>
548
- {!isLoading && (footerItems || hasCustomFooter) && (
549
- <SideNavFooter
550
- odysseyDesignTokens={odysseyDesignTokens}
551
- isContentScrollable={isContentScrollable}
552
- >
553
- {hasCustomFooter
554
- ? footerComponent
555
- : footerItems && (
556
- <SideNavFooterItemsContainer
557
- odysseyDesignTokens={odysseyDesignTokens}
558
- >
559
- <SideNavFooterContent footerItems={footerItems} />
560
- </SideNavFooterItemsContainer>
561
- )}
562
- </SideNavFooter>
563
- )}
644
+ value={sideNavItemContentProviderValue}
645
+ >
646
+ <SideNavItemContent
647
+ {...item}
648
+ key={item.id}
649
+ scrollRef={getRefIfThisIsFirstNodeWithIsSelected(
650
+ item.id,
651
+ )}
652
+ onItemSelected={setSelectedItem}
653
+ />
654
+ </SideNavItemContentContext.Provider>
655
+ );
656
+ }
657
+ })}
658
+ </SideNavListContainer>
659
+ </SideNavScrollableContainer>
660
+ {!isLoading && (footerItems || hasCustomFooter) && (
661
+ <SideNavFooter
662
+ odysseyDesignTokens={odysseyDesignTokens}
663
+ isContentScrollable={isContentScrollable}
664
+ >
665
+ {hasCustomFooter
666
+ ? footerComponent
667
+ : footerItems && (
668
+ <SideNavFooterItemsContainer
669
+ odysseyDesignTokens={odysseyDesignTokens}
670
+ >
671
+ <SideNavFooterContent footerItems={footerItems} />
672
+ </SideNavFooterItemsContainer>
673
+ )}
674
+ </SideNavFooter>
675
+ )}
676
+ </StyledOpacityTransitionContainer>
564
677
  </StyledCollapsibleContent>
565
678
  </OdysseyThemeProvider>
566
679
  </StyledSideNav>