@okta/odyssey-react-mui 1.22.0 → 1.23.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 (230) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/@types/i18next.d.js.map +1 -1
  3. package/dist/Autocomplete.js +30 -0
  4. package/dist/Autocomplete.js.map +1 -1
  5. package/dist/Callout.js +12 -24
  6. package/dist/Callout.js.map +1 -1
  7. package/dist/DataTable/useScrollIndication.js +7 -3
  8. package/dist/DataTable/useScrollIndication.js.map +1 -1
  9. package/dist/FileUploader/FileUploadIllustration.js.map +1 -0
  10. package/dist/FileUploader/FileUploadPreview.js.map +1 -0
  11. package/dist/{labs/FileUpload.js → FileUploader/FileUploader.js} +6 -5
  12. package/dist/FileUploader/FileUploader.js.map +1 -0
  13. package/dist/FileUploader/index.js +13 -0
  14. package/dist/FileUploader/index.js.map +1 -0
  15. package/dist/OdysseyProvider.js +4 -0
  16. package/dist/OdysseyProvider.js.map +1 -1
  17. package/dist/Radio.js +2 -2
  18. package/dist/Radio.js.map +1 -1
  19. package/dist/Select.js +36 -0
  20. package/dist/Select.js.map +1 -1
  21. package/dist/{labs/Switch.js → Switch.js} +7 -7
  22. package/dist/Switch.js.map +1 -0
  23. package/dist/Tabs.js +7 -9
  24. package/dist/Tabs.js.map +1 -1
  25. package/dist/Tag.js +102 -4
  26. package/dist/Tag.js.map +1 -1
  27. package/dist/TextField.js +16 -39
  28. package/dist/TextField.js.map +1 -1
  29. package/dist/Toast.js +2 -2
  30. package/dist/Toast.js.map +1 -1
  31. package/dist/createShadowDomElements.js +1 -0
  32. package/dist/createShadowDomElements.js.map +1 -1
  33. package/dist/i18n.js +1 -1
  34. package/dist/i18n.js.map +1 -1
  35. package/dist/index.js +2 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/index.scss +92 -4
  38. package/dist/labs/DataView/DataView.js +64 -25
  39. package/dist/labs/DataView/DataView.js.map +1 -1
  40. package/dist/labs/DataView/TableLayoutContent.js +17 -3
  41. package/dist/labs/DataView/TableLayoutContent.js.map +1 -1
  42. package/dist/labs/DataView/componentTypes.js.map +1 -1
  43. package/dist/labs/DateField.js +2 -0
  44. package/dist/labs/DateField.js.map +1 -1
  45. package/dist/labs/DatePicker.js +5 -1
  46. package/dist/labs/DatePicker.js.map +1 -1
  47. package/dist/labs/SideNav/OktaLogo.js +36 -0
  48. package/dist/labs/SideNav/OktaLogo.js.map +1 -0
  49. package/dist/labs/SideNav/SideNav.js +125 -36
  50. package/dist/labs/SideNav/SideNav.js.map +1 -1
  51. package/dist/labs/SideNav/SideNavHeader.js +33 -10
  52. package/dist/labs/SideNav/SideNavHeader.js.map +1 -1
  53. package/dist/labs/SideNav/types.js.map +1 -1
  54. package/dist/labs/TopNav.js +2 -1
  55. package/dist/labs/TopNav.js.map +1 -1
  56. package/dist/labs/index.js +0 -2
  57. package/dist/labs/index.js.map +1 -1
  58. package/dist/labs/useDateFieldsTranslations.js +1 -1
  59. package/dist/labs/useDateFieldsTranslations.js.map +1 -1
  60. package/dist/properties/ts/odyssey-react-mui.js +3 -1
  61. package/dist/properties/ts/odyssey-react-mui.js.map +1 -1
  62. package/dist/src/Autocomplete.d.ts +30 -0
  63. package/dist/src/Autocomplete.d.ts.map +1 -1
  64. package/dist/src/Callout.d.ts +11 -23
  65. package/dist/src/Callout.d.ts.map +1 -1
  66. package/dist/src/DataTable/useScrollIndication.d.ts.map +1 -1
  67. package/dist/src/FileUploader/FileUploadIllustration.d.ts.map +1 -0
  68. package/dist/src/{labs → FileUploader}/FileUploadPreview.d.ts +2 -2
  69. package/dist/src/FileUploader/FileUploadPreview.d.ts.map +1 -0
  70. package/dist/src/{labs/FileUpload.d.ts → FileUploader/FileUploader.d.ts} +5 -4
  71. package/dist/src/FileUploader/FileUploader.d.ts.map +1 -0
  72. package/dist/src/FileUploader/index.d.ts +13 -0
  73. package/dist/src/FileUploader/index.d.ts.map +1 -0
  74. package/dist/src/NativeSelect.d.ts +1 -1
  75. package/dist/src/OdysseyProvider.d.ts.map +1 -1
  76. package/dist/src/OdysseyTranslationProvider.d.ts +1 -1
  77. package/dist/src/OdysseyTranslationProvider.d.ts.map +1 -1
  78. package/dist/src/PasswordField.d.ts +1 -1
  79. package/dist/src/SearchField.d.ts +1 -1
  80. package/dist/src/Select.d.ts +36 -0
  81. package/dist/src/Select.d.ts.map +1 -1
  82. package/dist/src/{labs/Switch.d.ts → Switch.d.ts} +3 -3
  83. package/dist/src/Switch.d.ts.map +1 -0
  84. package/dist/src/Tabs.d.ts +6 -8
  85. package/dist/src/Tabs.d.ts.map +1 -1
  86. package/dist/src/Tag.d.ts +7 -1
  87. package/dist/src/Tag.d.ts.map +1 -1
  88. package/dist/src/TextField.d.ts +17 -40
  89. package/dist/src/TextField.d.ts.map +1 -1
  90. package/dist/src/createShadowDomElements.d.ts.map +1 -1
  91. package/dist/src/i18n.d.ts +2 -2
  92. package/dist/src/i18n.d.ts.map +1 -1
  93. package/dist/src/index.d.ts +2 -1
  94. package/dist/src/index.d.ts.map +1 -1
  95. package/dist/src/labs/DataView/DataView.d.ts +1 -1
  96. package/dist/src/labs/DataView/DataView.d.ts.map +1 -1
  97. package/dist/src/labs/DataView/TableLayoutContent.d.ts +2 -1
  98. package/dist/src/labs/DataView/TableLayoutContent.d.ts.map +1 -1
  99. package/dist/src/labs/DataView/componentTypes.d.ts +10 -0
  100. package/dist/src/labs/DataView/componentTypes.d.ts.map +1 -1
  101. package/dist/src/labs/DateField.d.ts +2 -2
  102. package/dist/src/labs/DateField.d.ts.map +1 -1
  103. package/dist/src/labs/DatePicker.d.ts +2 -2
  104. package/dist/src/labs/DatePicker.d.ts.map +1 -1
  105. package/dist/{test-selectors/odysseyTestSelectors.js → src/labs/SideNav/OktaLogo.d.ts} +3 -9
  106. package/dist/src/labs/SideNav/OktaLogo.d.ts.map +1 -0
  107. package/dist/src/labs/SideNav/SideNav.d.ts +2 -1
  108. package/dist/src/labs/SideNav/SideNav.d.ts.map +1 -1
  109. package/dist/src/labs/SideNav/SideNavHeader.d.ts +1 -1
  110. package/dist/src/labs/SideNav/SideNavHeader.d.ts.map +1 -1
  111. package/dist/src/labs/SideNav/types.d.ts +28 -5
  112. package/dist/src/labs/SideNav/types.d.ts.map +1 -1
  113. package/dist/src/labs/TopNav.d.ts +1 -0
  114. package/dist/src/labs/TopNav.d.ts.map +1 -1
  115. package/dist/src/labs/index.d.ts +0 -2
  116. package/dist/src/labs/index.d.ts.map +1 -1
  117. package/dist/src/properties/ts/odyssey-react-mui.d.ts +2 -0
  118. package/dist/src/properties/ts/odyssey-react-mui.d.ts.map +1 -1
  119. package/dist/src/test-selectors/getByQuerySelector.d.ts +148 -0
  120. package/dist/src/test-selectors/getByQuerySelector.d.ts.map +1 -0
  121. package/{src/test-selectors/odysseyTestSelectors.ts → dist/src/test-selectors/getComputedAccessibleErrorMessageText.d.ts} +3 -11
  122. package/dist/src/test-selectors/getComputedAccessibleErrorMessageText.d.ts.map +1 -0
  123. package/dist/src/test-selectors/{featureTestSelector.d.ts → getComputedAccessibleText.d.ts} +11 -19
  124. package/dist/src/test-selectors/getComputedAccessibleText.d.ts.map +1 -0
  125. package/dist/src/test-selectors/index.d.ts +2 -2
  126. package/dist/src/test-selectors/index.d.ts.map +1 -1
  127. package/dist/src/test-selectors/interpolateString.d.ts +15 -0
  128. package/dist/src/test-selectors/interpolateString.d.ts.map +1 -0
  129. package/dist/src/test-selectors/linkedHtmlSelectors.d.ts +24 -0
  130. package/dist/src/test-selectors/linkedHtmlSelectors.d.ts.map +1 -0
  131. package/dist/src/test-selectors/queryOdysseySelector.d.ts +5755 -0
  132. package/dist/src/test-selectors/queryOdysseySelector.d.ts.map +1 -0
  133. package/dist/src/test-selectors/querySelector.d.ts +59 -3613
  134. package/dist/src/test-selectors/querySelector.d.ts.map +1 -1
  135. package/dist/src/test-selectors/sanityChecks.d.ts +18 -0
  136. package/dist/src/test-selectors/sanityChecks.d.ts.map +1 -0
  137. package/dist/src/test-selectors/testSelector.d.ts +46 -0
  138. package/dist/src/test-selectors/testSelector.d.ts.map +1 -0
  139. package/dist/src/theme/components.d.ts.map +1 -1
  140. package/dist/test-selectors/getByQuerySelector.js +64 -0
  141. package/dist/test-selectors/getByQuerySelector.js.map +1 -0
  142. package/dist/test-selectors/getComputedAccessibleErrorMessageText.js +25 -0
  143. package/dist/test-selectors/getComputedAccessibleErrorMessageText.js.map +1 -0
  144. package/dist/test-selectors/getComputedAccessibleText.js +24 -0
  145. package/dist/test-selectors/getComputedAccessibleText.js.map +1 -0
  146. package/dist/test-selectors/index.js +2 -2
  147. package/dist/test-selectors/index.js.map +1 -1
  148. package/{src/test-selectors/featureTestSelector.ts → dist/test-selectors/interpolateString.js} +11 -27
  149. package/dist/test-selectors/interpolateString.js.map +1 -0
  150. package/dist/test-selectors/linkedHtmlSelectors.js +34 -0
  151. package/dist/test-selectors/linkedHtmlSelectors.js.map +1 -0
  152. package/dist/test-selectors/queryOdysseySelector.js +26 -0
  153. package/dist/test-selectors/queryOdysseySelector.js.map +1 -0
  154. package/dist/test-selectors/querySelector.js +82 -58
  155. package/dist/test-selectors/querySelector.js.map +1 -1
  156. package/dist/test-selectors/sanityChecks.js +33 -0
  157. package/dist/test-selectors/sanityChecks.js.map +1 -0
  158. package/dist/test-selectors/testSelector.js +2 -0
  159. package/dist/test-selectors/testSelector.js.map +1 -0
  160. package/dist/test-selectors/testSelectors.json +1 -1
  161. package/dist/theme/components.js +0 -1
  162. package/dist/theme/components.js.map +1 -1
  163. package/dist/tsconfig.production.tsbuildinfo +1 -1
  164. package/dist/tsconfig.tsbuildinfo +1 -1
  165. package/jest.setup.js +3 -0
  166. package/package.json +5 -4
  167. package/scripts/generateTestSelectorsJson.ts +1 -1
  168. package/src/@types/i18next.d.ts +1 -1
  169. package/src/Autocomplete.tsx +32 -0
  170. package/src/Callout.tsx +13 -25
  171. package/src/DataTable/useScrollIndication.tsx +9 -2
  172. package/src/{labs → FileUploader}/FileUploadPreview.tsx +3 -3
  173. package/src/{labs/FileUpload.tsx → FileUploader/FileUploader.tsx} +7 -6
  174. package/src/FileUploader/index.ts +13 -0
  175. package/src/OdysseyCacheProvider.test.tsx +1 -0
  176. package/src/OdysseyProvider.tsx +6 -1
  177. package/src/Radio.tsx +2 -2
  178. package/src/Select.tsx +38 -0
  179. package/src/{labs/Switch.tsx → Switch.tsx} +10 -10
  180. package/src/Tabs.tsx +8 -10
  181. package/src/Tag.tsx +134 -3
  182. package/src/TextField.tsx +18 -41
  183. package/src/Toast.tsx +1 -1
  184. package/src/createShadowDomElements.ts +3 -0
  185. package/src/i18n.ts +3 -3
  186. package/src/index.ts +6 -1
  187. package/src/labs/DataView/DataView.test.tsx +158 -0
  188. package/src/labs/DataView/DataView.tsx +98 -50
  189. package/src/labs/DataView/TableLayoutContent.tsx +28 -1
  190. package/src/labs/DataView/componentTypes.ts +13 -0
  191. package/src/labs/DateField.tsx +3 -0
  192. package/src/labs/DatePicker.tsx +12 -1
  193. package/src/labs/SideNav/OktaLogo.tsx +39 -0
  194. package/src/labs/SideNav/SideNav.tsx +187 -51
  195. package/src/labs/SideNav/SideNavHeader.tsx +30 -7
  196. package/src/labs/SideNav/types.ts +32 -5
  197. package/src/labs/TopNav.tsx +3 -1
  198. package/src/labs/index.ts +0 -3
  199. package/src/labs/useDateFieldsTranslations.ts +1 -1
  200. package/src/properties/odyssey-react-mui.properties +2 -1
  201. package/src/properties/ts/odyssey-react-mui.ts +1 -1
  202. package/src/test-selectors/getByQuerySelector.ts +176 -0
  203. package/src/test-selectors/getComputedAccessibleErrorMessageText.ts +52 -0
  204. package/src/test-selectors/getComputedAccessibleText.ts +36 -0
  205. package/src/test-selectors/index.ts +2 -2
  206. package/src/test-selectors/interpolateString.ts +41 -0
  207. package/src/test-selectors/linkedHtmlSelectors.ts +73 -0
  208. package/src/test-selectors/queryOdysseySelector.ts +36 -0
  209. package/src/test-selectors/querySelector.ts +221 -170
  210. package/src/test-selectors/sanityChecks.ts +53 -0
  211. package/src/test-selectors/testSelector.ts +143 -0
  212. package/src/theme/components.tsx +0 -2
  213. package/dist/labs/FileUpload.js.map +0 -1
  214. package/dist/labs/FileUploadIllustration.js.map +0 -1
  215. package/dist/labs/FileUploadPreview.js.map +0 -1
  216. package/dist/labs/Switch.js.map +0 -1
  217. package/dist/src/labs/FileUpload.d.ts.map +0 -1
  218. package/dist/src/labs/FileUploadIllustration.d.ts.map +0 -1
  219. package/dist/src/labs/FileUploadPreview.d.ts.map +0 -1
  220. package/dist/src/labs/Switch.d.ts.map +0 -1
  221. package/dist/src/test-selectors/featureTestSelector.d.ts.map +0 -1
  222. package/dist/src/test-selectors/odysseyTestSelectors.d.ts +0 -120
  223. package/dist/src/test-selectors/odysseyTestSelectors.d.ts.map +0 -1
  224. package/dist/test-selectors/featureTestSelector.js +0 -2
  225. package/dist/test-selectors/featureTestSelector.js.map +0 -1
  226. package/dist/test-selectors/odysseyTestSelectors.js.map +0 -1
  227. /package/dist/{labs → FileUploader}/FileUploadIllustration.js +0 -0
  228. /package/dist/{labs → FileUploader}/FileUploadPreview.js +0 -0
  229. /package/dist/src/{labs → FileUploader}/FileUploadIllustration.d.ts +0 -0
  230. /package/src/{labs → FileUploader}/FileUploadIllustration.tsx +0 -0
@@ -21,7 +21,6 @@ import {
21
21
  useEffect,
22
22
  } from "react";
23
23
 
24
- import { Box } from "../../Box";
25
24
  import { ExpandLeftIcon } from "../../icons.generated";
26
25
  import { NavAccordion } from "../NavAccordion";
27
26
  import {
@@ -29,6 +28,7 @@ import {
29
28
  useOdysseyDesignTokens,
30
29
  } from "../../OdysseyDesignTokensContext";
31
30
  import type { SideNavProps } from "./types";
31
+ import { OktaLogo } from "./OktaLogo";
32
32
  import { SideNavHeader } from "./SideNavHeader";
33
33
  import {
34
34
  SideNavItemContent,
@@ -36,6 +36,17 @@ import {
36
36
  } from "./SideNavItemContent";
37
37
  import { SideNavFooterContent } from "./SideNavFooterContent";
38
38
 
39
+ export const DEFAULT_SIDE_NAV_WIDTH = "300px";
40
+
41
+ const SideNavContainer = styled("div", {
42
+ shouldForwardProp: (prop) => prop !== "expandedWidth",
43
+ })(({ expandedWidth }: { expandedWidth: SideNavProps["expandedWidth"] }) => ({
44
+ display: "flex",
45
+ height: "100%",
46
+ maxWidth: expandedWidth,
47
+ overflow: "hidden",
48
+ }));
49
+
39
50
  const SideNavCollapsedContainer = styled("div", {
40
51
  shouldForwardProp: (prop) =>
41
52
  prop !== "odysseyDesignTokens" && prop !== "isSideNavCollapsed",
@@ -59,26 +70,51 @@ const SideNavCollapsedContainer = styled("div", {
59
70
  }),
60
71
  );
61
72
 
62
- const SideNavExpandContainer = styled("div", {
73
+ const SideNavExpandContainer = styled("nav", {
63
74
  shouldForwardProp: (prop) =>
64
- prop !== "odysseyDesignTokens" && prop !== "isSideNavCollapsed",
75
+ prop !== "odysseyDesignTokens" &&
76
+ prop !== "isSideNavCollapsed" &&
77
+ prop !== "expandedWidth",
65
78
  })(
66
79
  ({
67
80
  odysseyDesignTokens,
68
81
  isSideNavCollapsed,
82
+ expandedWidth,
69
83
  }: {
70
84
  odysseyDesignTokens: DesignTokens;
71
85
  isSideNavCollapsed: boolean;
86
+ expandedWidth: string;
72
87
  }) => ({
73
88
  backgroundColor: odysseyDesignTokens.HueNeutralWhite,
74
89
  flexDirection: "column",
75
90
  display: "flex",
76
91
  opacity: isSideNavCollapsed ? 0 : 1,
77
92
  visibility: isSideNavCollapsed ? "hidden" : "visible",
78
- width: isSideNavCollapsed ? "0" : "100%",
93
+ width: isSideNavCollapsed ? "0" : expandedWidth,
79
94
  transitionProperty: "opacity, width",
80
95
  transitionDuration: odysseyDesignTokens.TransitionDurationMain,
81
96
  transitionTimingFunction: odysseyDesignTokens.TransitionTimingMain,
97
+ borderRight: `${odysseyDesignTokens.BorderWidthMain} ${odysseyDesignTokens.BorderStyleMain} ${odysseyDesignTokens.HueNeutral50}`,
98
+ }),
99
+ );
100
+
101
+ const SideNavHeaderContainer = styled("div", {
102
+ shouldForwardProp: (prop) =>
103
+ prop !== "hasContentScrolled" && prop !== "odysseyDesignTokens",
104
+ })(
105
+ ({
106
+ hasContentScrolled,
107
+ odysseyDesignTokens,
108
+ }: {
109
+ hasContentScrolled: boolean;
110
+ odysseyDesignTokens: DesignTokens;
111
+ }) => ({
112
+ position: "sticky",
113
+ top: 0,
114
+ // The bottom border should appear only if the scrollable region has been scrolled
115
+ ...(hasContentScrolled && {
116
+ borderBottom: `${odysseyDesignTokens.BorderWidthMain} ${odysseyDesignTokens.BorderStyleMain} ${odysseyDesignTokens.HueNeutral50}`,
117
+ }),
82
118
  }),
83
119
  );
84
120
 
@@ -88,9 +124,15 @@ const SideNavListContainer = styled.ul({
88
124
  listStyleType: "none",
89
125
  });
90
126
 
127
+ const SideNavScrollableContainer = styled.div({
128
+ flex: 1,
129
+ overflowY: "auto",
130
+ });
131
+
91
132
  const SectionHeader = styled("li", {
92
133
  shouldForwardProp: (prop) => prop !== "odysseyDesignTokens",
93
134
  })(({ odysseyDesignTokens }: { odysseyDesignTokens: DesignTokens }) => ({
135
+ fontFamily: odysseyDesignTokens.TypographyFamilyHeading,
94
136
  fontSize: odysseyDesignTokens.TypographySizeOverline,
95
137
  fontWeight: odysseyDesignTokens.TypographyWeightHeadingBold,
96
138
  color: odysseyDesignTokens.HueNeutral600,
@@ -100,7 +142,31 @@ const SectionHeader = styled("li", {
100
142
  textTransform: "uppercase",
101
143
  }));
102
144
 
103
- const SideNavFooterContainer = styled("div", {
145
+ const SideNavFooter = styled("div", {
146
+ shouldForwardProp: (prop) =>
147
+ prop !== "isContentScrollable" && prop !== "odysseyDesignTokens",
148
+ })(
149
+ ({
150
+ isContentScrollable,
151
+ odysseyDesignTokens,
152
+ }: {
153
+ isContentScrollable: boolean;
154
+ odysseyDesignTokens: DesignTokens;
155
+ }) => ({
156
+ position: "sticky",
157
+ bottom: 0,
158
+ paddingTop: odysseyDesignTokens.Spacing2,
159
+ transitionProperty: "box-shadow",
160
+ transitionDuration: odysseyDesignTokens.TransitionDurationMain,
161
+ transitionTiming: odysseyDesignTokens.TransitionTimingMain,
162
+ // The box shadow should appear above the footer only if the scrollable region has overflow
163
+ ...(isContentScrollable && {
164
+ boxShadow: odysseyDesignTokens.DepthHigh,
165
+ }),
166
+ }),
167
+ );
168
+
169
+ const SideNavFooterItemsContainer = styled("div", {
104
170
  shouldForwardProp: (prop) => prop !== "odysseyDesignTokens",
105
171
  })(({ odysseyDesignTokens }: { odysseyDesignTokens: DesignTokens }) => ({
106
172
  paddingTop: odysseyDesignTokens.Spacing2,
@@ -121,16 +187,105 @@ const SideNavFooterContainer = styled("div", {
121
187
  },
122
188
  }));
123
189
 
190
+ const getHasScrollableContent = (scrollableContainer: HTMLElement) =>
191
+ scrollableContainer.scrollHeight > scrollableContainer.clientHeight;
192
+
124
193
  const SideNav = ({
125
194
  navHeaderText,
126
195
  isCollapsible,
127
196
  onCollapse,
128
197
  onExpand,
129
198
  sideNavItems,
199
+ expandedWidth = DEFAULT_SIDE_NAV_WIDTH,
130
200
  footerItems,
201
+ footerComponent,
202
+ logo,
131
203
  }: SideNavProps) => {
132
204
  const [isSideNavCollapsed, setSideNavCollapsed] = useState(false);
133
205
  const odysseyDesignTokens = useOdysseyDesignTokens();
206
+ const [isContentScrollable, setIsContentScrollable] = useState(false);
207
+ const [hasContentScrolled, setHasContentScrolled] = useState(false);
208
+ const scrollableContentRef = useRef<HTMLUListElement>(null);
209
+ const resizeObserverRef = useRef<ResizeObserver | null>(null);
210
+ const intersectionObserverRef = useRef<IntersectionObserver | null>(null);
211
+
212
+ useEffect(() => {
213
+ const updateIsContentScrollable = () => {
214
+ if (
215
+ scrollableContentRef.current &&
216
+ scrollableContentRef.current.parentElement
217
+ ) {
218
+ setIsContentScrollable(
219
+ getHasScrollableContent(scrollableContentRef.current.parentElement),
220
+ );
221
+ }
222
+ };
223
+
224
+ // If the window is resized, we may need to re-determine if the scrollable container has overflow
225
+ // Setup a ResizeObserver to know if the size of the scrollableContent changes
226
+ let resizeObserverDebounceTimer: ReturnType<typeof requestAnimationFrame>;
227
+ if (!resizeObserverRef.current) {
228
+ resizeObserverRef.current = new ResizeObserver(() => {
229
+ cancelAnimationFrame(resizeObserverDebounceTimer);
230
+ resizeObserverDebounceTimer = requestAnimationFrame(
231
+ updateIsContentScrollable,
232
+ );
233
+ });
234
+ }
235
+
236
+ if (resizeObserverRef.current && scrollableContentRef.current) {
237
+ // Observe the <ul> itself (in case it changes size due to the content expanding)
238
+ resizeObserverRef.current.observe(scrollableContentRef.current);
239
+ if (scrollableContentRef.current.parentElement) {
240
+ // ALSO observe the parent (<SideNavScrollableContainer>) in case the window resizes
241
+ resizeObserverRef.current.observe(
242
+ scrollableContentRef.current.parentElement,
243
+ );
244
+ }
245
+ }
246
+
247
+ // Determine if the scrollable container has overflow or not on load
248
+ updateIsContentScrollable();
249
+
250
+ // Finally, we only want to have the border on the bottom of the header iff the user has scrolled
251
+ // the scrollable container
252
+ if (!intersectionObserverRef.current && scrollableContentRef.current) {
253
+ intersectionObserverRef.current = new IntersectionObserver(
254
+ (entries) => {
255
+ // If isIntersecting is true, then we're at the top of the scroll container
256
+ // If isIntersecting is false, some scrolling has occurred.
257
+ // The entries must be sorted by time and we only really need to look at the latest one
258
+ const isIntersecting = entries
259
+ .slice()
260
+ .sort((a, b) => a.time - b.time)
261
+ .at(0)?.isIntersecting;
262
+ setHasContentScrolled(!isIntersecting);
263
+ },
264
+ {
265
+ root: scrollableContentRef.current.parentElement,
266
+ threshold: 1.0,
267
+ },
268
+ );
269
+ }
270
+ if (intersectionObserverRef.current && scrollableContentRef.current) {
271
+ const ul = scrollableContentRef.current;
272
+ const li = ul?.firstChild;
273
+ intersectionObserverRef.current.observe(li as HTMLElement);
274
+ }
275
+
276
+ // Cleanup when unmounted:
277
+ return () => {
278
+ if (resizeObserverRef.current) {
279
+ resizeObserverRef.current.disconnect();
280
+ resizeObserverRef.current = null;
281
+ }
282
+ if (intersectionObserverRef.current) {
283
+ intersectionObserverRef.current.disconnect();
284
+ intersectionObserverRef.current = null;
285
+ }
286
+ cancelAnimationFrame(resizeObserverDebounceTimer); // Ensure timer is cleared on component unmount
287
+ };
288
+ }, []);
134
289
 
135
290
  const scrollIntoViewRef = useRef<HTMLLIElement>(null);
136
291
  /**
@@ -209,39 +364,6 @@ const SideNav = ({
209
364
  [isSideNavCollapsed, setSideNavCollapsed, onExpand],
210
365
  );
211
366
 
212
- const sideNavStyles = useMemo(
213
- () => ({
214
- display: "flex",
215
- height: "100vh",
216
- }),
217
- [],
218
- );
219
-
220
- const sideNavHeaderContainerStyles = useMemo(
221
- () => ({
222
- position: "sticky",
223
- top: 0,
224
- }),
225
- [],
226
- );
227
-
228
- const sideNavListContainerStyles = useMemo(
229
- () => ({
230
- flex: 1,
231
- overflowY: "auto",
232
- }),
233
- [],
234
- );
235
-
236
- const sideNavFooterContainerStyles = useMemo(
237
- () => ({
238
- position: "sticky",
239
- bottom: 0,
240
- paddingTop: odysseyDesignTokens.Spacing2,
241
- }),
242
- [odysseyDesignTokens],
243
- );
244
-
245
367
  const expandLeftIconStyles = useMemo(
246
368
  () => ({
247
369
  fontSize: "1em",
@@ -251,7 +373,7 @@ const SideNav = ({
251
373
  );
252
374
 
253
375
  return (
254
- <Box sx={sideNavStyles}>
376
+ <SideNavContainer expandedWidth={expandedWidth}>
255
377
  <SideNavCollapsedContainer
256
378
  tabIndex={0}
257
379
  role="button"
@@ -268,16 +390,22 @@ const SideNav = ({
268
390
  odysseyDesignTokens={odysseyDesignTokens}
269
391
  isSideNavCollapsed={isSideNavCollapsed}
270
392
  data-se="expanded-region"
393
+ expandedWidth={expandedWidth}
394
+ aria-label={navHeaderText}
271
395
  >
272
- <Box sx={sideNavHeaderContainerStyles}>
396
+ <SideNavHeaderContainer
397
+ odysseyDesignTokens={odysseyDesignTokens}
398
+ hasContentScrolled={hasContentScrolled}
399
+ >
273
400
  <SideNavHeader
401
+ logo={logo || <OktaLogo />}
274
402
  navHeaderText={navHeaderText}
275
403
  isCollapsible={isCollapsible}
276
404
  onCollapse={sideNavCollapseHandler}
277
405
  />
278
- </Box>
279
- <Box sx={sideNavListContainerStyles} testId="scrollable-region">
280
- <SideNavListContainer>
406
+ </SideNavHeaderContainer>
407
+ <SideNavScrollableContainer data-se="scrollable-region">
408
+ <SideNavListContainer ref={scrollableContentRef}>
281
409
  {processedSideNavItems?.map((item) => {
282
410
  const {
283
411
  id,
@@ -331,16 +459,24 @@ const SideNav = ({
331
459
  }
332
460
  })}
333
461
  </SideNavListContainer>
334
- </Box>
335
- {footerItems && (
336
- <Box sx={sideNavFooterContainerStyles}>
337
- <SideNavFooterContainer odysseyDesignTokens={odysseyDesignTokens}>
338
- <SideNavFooterContent footerItems={footerItems} />
339
- </SideNavFooterContainer>
340
- </Box>
462
+ </SideNavScrollableContainer>
463
+ {(footerItems || footerComponent) && (
464
+ <SideNavFooter
465
+ odysseyDesignTokens={odysseyDesignTokens}
466
+ isContentScrollable={isContentScrollable}
467
+ >
468
+ {footerComponent}
469
+ {footerItems && !footerComponent && (
470
+ <SideNavFooterItemsContainer
471
+ odysseyDesignTokens={odysseyDesignTokens}
472
+ >
473
+ <SideNavFooterContent footerItems={footerItems} />
474
+ </SideNavFooterItemsContainer>
475
+ )}
476
+ </SideNavFooter>
341
477
  )}
342
478
  </SideNavExpandContainer>
343
- </Box>
479
+ </SideNavContainer>
344
480
  );
345
481
  };
346
482
 
@@ -20,6 +20,18 @@ import { Box } from "../../Box";
20
20
  import { Heading6 } from "../../Typography";
21
21
  import { CollapseIcon } from "./CollapseIcon";
22
22
  import type { SideNavProps } from "./types";
23
+ import { TOP_NAV_HEIGHT_TOKEN } from "../TopNav";
24
+
25
+ const SideNavLogoContainer = styled("div", {
26
+ shouldForwardProp: (prop) => prop !== "odysseyDesignTokens",
27
+ })(({ odysseyDesignTokens }: { odysseyDesignTokens: DesignTokens }) => ({
28
+ height: odysseyDesignTokens[TOP_NAV_HEIGHT_TOKEN],
29
+ padding: odysseyDesignTokens.Spacing3,
30
+ borderColor: odysseyDesignTokens.HueNeutral50,
31
+ borderStyle: odysseyDesignTokens.BorderStyleMain,
32
+ borderWidth: 0,
33
+ borderBottomWidth: odysseyDesignTokens.BorderWidthMain,
34
+ }));
23
35
 
24
36
  const SideNavHeaderContainer = styled("div", {
25
37
  shouldForwardProp: (prop) => prop !== "odysseyDesignTokens",
@@ -37,9 +49,10 @@ const SideNavHeader = ({
37
49
  navHeaderText,
38
50
  isCollapsible,
39
51
  onCollapse,
52
+ logo,
40
53
  }: Pick<
41
54
  SideNavProps,
42
- "navHeaderText" | "isCollapsible" | "onCollapse"
55
+ "navHeaderText" | "isCollapsible" | "onCollapse" | "logo"
43
56
  >): ReactNode => {
44
57
  const odysseyDesignTokens = useOdysseyDesignTokens();
45
58
 
@@ -51,12 +64,22 @@ const SideNavHeader = ({
51
64
  );
52
65
 
53
66
  return (
54
- <SideNavHeaderContainer odysseyDesignTokens={odysseyDesignTokens}>
55
- <Box sx={sideNavHeaderStyles}>
56
- <Heading6>{navHeaderText}</Heading6>
57
- </Box>
58
- {isCollapsible && <CollapseIcon onClick={onCollapse} />}
59
- </SideNavHeaderContainer>
67
+ <Box
68
+ sx={{
69
+ display: "flex",
70
+ flexDirection: "column",
71
+ }}
72
+ >
73
+ <SideNavLogoContainer odysseyDesignTokens={odysseyDesignTokens}>
74
+ {logo}
75
+ </SideNavLogoContainer>
76
+ <SideNavHeaderContainer odysseyDesignTokens={odysseyDesignTokens}>
77
+ <Box sx={sideNavHeaderStyles}>
78
+ <Heading6>{navHeaderText}</Heading6>
79
+ </Box>
80
+ {isCollapsible && <CollapseIcon onClick={onCollapse} />}
81
+ </SideNavHeaderContainer>
82
+ </Box>
60
83
  );
61
84
  };
62
85
  const MemoizedSideNavHeader = memo(SideNavHeader);
@@ -23,10 +23,6 @@ export type SideNavProps = {
23
23
  * Determines whether the side nav is collapsible
24
24
  */
25
25
  isCollapsible?: boolean;
26
- /**
27
- * Footer items in the side nav
28
- */
29
- footerItems?: SideNavFooterItem[];
30
26
  /**
31
27
  * Triggers when the side nav is collapsed
32
28
  */
@@ -39,7 +35,38 @@ export type SideNavProps = {
39
35
  * Nav items in the side nav
40
36
  */
41
37
  sideNavItems: SideNavItem[];
42
- } & Pick<HtmlProps, "testId">;
38
+ /**
39
+ * A CSS length string indicating the customizable expanded width of the SideNav container.
40
+ * (it will be smaller if isCollapsible and collapsed)
41
+ */
42
+ expandedWidth?: string;
43
+ /**
44
+ * An optional logo to display in the header. If not provided, will default to the Okta logo
45
+ */
46
+ logo?: ReactElement;
47
+ } & (
48
+ | {
49
+ /**
50
+ * Footer items in the side nav
51
+ */
52
+ footerItems?: SideNavFooterItem[];
53
+ /**
54
+ * footerComponent cannot be used if footerItems are defined
55
+ */
56
+ footerComponent?: never;
57
+ }
58
+ | {
59
+ /**
60
+ * footerItems cannot be used if footerComponent is defined
61
+ */
62
+ footerItems?: never;
63
+ /**
64
+ * The component to display as the footer; if present the `footerItems` are ignored and not rendered.
65
+ */
66
+ footerComponent?: ReactElement;
67
+ }
68
+ ) &
69
+ Pick<HtmlProps, "testId">;
43
70
 
44
71
  export type SideNavItem = {
45
72
  id: string;
@@ -29,6 +29,8 @@ import {
29
29
  } from "../OdysseyDesignTokensContext";
30
30
  import { Subordinate } from "../Typography";
31
31
 
32
+ export const TOP_NAV_HEIGHT_TOKEN = "Spacing9";
33
+
32
34
  export type TopNavLinkItem = {
33
35
  id: string;
34
36
  label: string;
@@ -306,7 +308,7 @@ const TopNavContainer = styled("div", {
306
308
  display: "flex",
307
309
  alignItems: "center",
308
310
  backgroundColor: odysseyDesignTokens.HueNeutralWhite,
309
- height: odysseyDesignTokens.Spacing9,
311
+ height: odysseyDesignTokens[TOP_NAV_HEIGHT_TOKEN],
310
312
  }));
311
313
 
312
314
  const SearchFieldContainer = styled("div", {
package/src/labs/index.ts CHANGED
@@ -24,7 +24,6 @@ export * from "./DataView";
24
24
  /** @deprecated Will be removed in a future Odyssey version in lieu of the one shipping with DataTable */
25
25
  export * from "./DataTablePagination";
26
26
  export * from "./DataFilters";
27
- export * from "./FileUpload";
28
27
  export * from "./Layout";
29
28
  export * from "./materialReactTableTypes";
30
29
  /** @deprecated Will be removed in a future Odyssey version in lieu of DataTable */
@@ -35,8 +34,6 @@ export * from "./PaginatedTable";
35
34
 
36
35
  export * from "./GroupPicker";
37
36
 
38
- export * from "./Switch";
39
-
40
37
  export * from "./NavAccordion";
41
38
  export * from "./SideNav";
42
39
  export * from "./TopNav";
@@ -46,7 +46,7 @@ export const useDateFieldsTranslations = (): DateFieldsTranslations => {
46
46
  empty: `${t("picker.labels.empty")}`,
47
47
  end: `${t("picker.labels.range.end")}`,
48
48
  endDate: `${t("picker.labels.range.enddate")}`,
49
- endTime: `${t("picker.labels.range.endTime")}`,
49
+ endTime: `${t("picker.labels.range.endtime")}`,
50
50
  fieldClearLabel: `${t("picker.labels.field.clear")}`,
51
51
  fieldDayPlaceholder: () => `${t("picker.field.placeholder.day")}`,
52
52
  fieldMonthPlaceholder: () => `${t("picker.field.placeholder.month")}`,
@@ -31,7 +31,7 @@ picker.labels.action.today = Today
31
31
  picker.labels.clock.empty = No time selected
32
32
  picker.labels.clock.hours = hours
33
33
  picker.labels.clock.minutes = minutes
34
- picker.labels.clock.minutes = seconds
34
+ picker.labels.clock.seconds = seconds
35
35
  picker.labels.clock.selected = Selected time is
36
36
  picker.labels.date.choose = Choose date
37
37
  picker.labels.date.selected = Selected date is
@@ -47,6 +47,7 @@ picker.labels.select = Select
47
47
  picker.labels.table.date = pick date
48
48
  picker.labels.table.time = pick time
49
49
  picker.labels.time.choose = Choose time
50
+ picker.labels.time.selected = selected time is
50
51
  picker.time.toolbar.title = Select time
51
52
  picker.view.name.day = Day
52
53
  picker.view.name.hours = Hours
@@ -1 +1 @@
1
- export const translation = {"breadcrumbs.home.text":"Home","breadcrumbs.label.text":"Breadcrumbs","close.text":"Close","clear.text":"Clear","open.text":"Open","picker.calendar.navigation.nextmonth":"Next month","picker.calendar.navigation.previousmonth":"Previous month","picker.date.toolbar.title":"Selected date","picker.daterange.toolbar.title":"Select date range","picker.datetime.toolbar.title":"Select date & time","picker.error.invalid":"Correct the date format or select the date from the calendar.","picker.error.mindate":"Date entered is earlier than allowed dates. Select a date from the range available in the calendar.","picker.error.maxdate":"Date entered is later than the allowed dates. Select a date from the range available in the calendar.","picker.field.placeholder.day":"DD","picker.field.placeholder.hours":"hh","picker.field.placeholder.meridiem":"aa","picker.field.placeholder.minutes":"mm","picker.field.placeholder.month":"MM","picker.field.placeholder.seconds":"ss","picker.field.placeholder.year":"YYYY","picker.labels.action.apply":"Apply","picker.labels.action.cancel":"Cancel","picker.labels.action.today":"Today","picker.labels.clock.empty":"No time selected","picker.labels.clock.hours":"hours","picker.labels.clock.minutes":"seconds","picker.labels.clock.selected":"Selected time is","picker.labels.date.choose":"Choose date","picker.labels.date.selected":"Selected date is","picker.labels.empty":"Empty","picker.labels.field.clear":"Clear value","picker.labels.range.end":"End","picker.labels.range.enddate":"End date","picker.labels.range.endtime":"End time","picker.labels.range.start":"Start","picker.labels.range.startdate":"Start date","picker.labels.range.starttime":"Start time","picker.labels.select":"Select","picker.labels.table.date":"pick date","picker.labels.table.time":"pick time","picker.labels.time.choose":"Choose time","picker.time.toolbar.title":"Select time","picker.view.name.day":"Day","picker.view.name.hours":"Hours","picker.view.name.meridiem":"Meridiem","picker.view.name.minutes":"Minutes","picker.view.name.month":"Month","picker.view.name.seconds":"Seconds","picker.view.name.weekday":"Week day","picker.view.name.year":"Year","picker.view.navigation.open.nextview":"Open next view","picker.view.navigation.open.previousview":"Open previous view","picker.view.navigation.switch.calendarview":"year view is open, switch to calendar view","picker.view.navigation.switch.yearview":"calendar view is open, switch to year view","fielderror.screenreader.text":"Error","fieldlabel.optional.text":"Optional","fieldlabel.required.text":"Required","filters.clear.label":"Clear filters","filters.filter.any":"Any","filters.filter.clear":"Clear filter","filters.filters.arialabel":"Filters","filters.menuitem.any":"Any {{label}}","filters.menuitem.selected":"{{selected}} selected","filters.search.label":"Search","fileupload.button.text":"Add files","fileupload.prompt.text":"Drag and drop files here or click to add files","fileupload.removefile.text":"Remove file","passwordfield.icon.label.hide":"Hide password","passwordfield.icon.label.show":"Show password","severity.error":"error","severity.info":"info","severity.success":"success","severity.warning":"warning","switch.active":"Active","switch.inactive":"Inactive","table.columnvisibility.arialabel":"Show/hide columns","table.density.arialabel":"Table density","table.draghandle.arialabel":"Drag row to reorder. Or, press space or enter to start and stop reordering and esc to cancel.","table.draghandle.tooltip":"Drag row or press space/enter key to start and stop reordering","table.actions":"Actions","table.error":"Error loading data.","table.fetchedrows.text":"Fetched {{totalRows}} row","table.fetchedrows.text_plural":"Fetched {{totalRows}} total rows","table.moreactions.arialabel":"More actions","table.noresults.heading":"There are no results.","table.noresults.text":"Try a different query.","table.reorder.backward":"Send backward","table.reorder.forward":"Bring forward","table.reorder.toback":"Send to back","table.reorder.tofront":"Bring to front","table.rows.text":"{{totalRows}} row","table.rows.text_plural":"{{totalRows}} rows","pagination.loadmore":"Show more","pagination.next":"Next page","pagination.previous":"Previous page","pagination.page":"Page","pagination.rowsperpage":"Rows per page","pagination.rowswithtotal":"{{firstRow}}-{{lastRow}} of {{totalRows}} rows","pagination.rowswithouttotal":"{{firstRow}}-{{lastRow}} rows","table.actions.selectall":"Select all","table.actions.selectnone":"Select none","table.actions.selectsome":"{{selectedRowCount}} selected","table.rowexpansion.expand":"Expand","table.rowexpansion.expandall":"Expand all","table.rowexpansion.collapse":"Collapse","table.rowexpansion.collapseall":"Collapse all","dataview.layout.table":"Table","dataview.layout.grid":"Grid","dataview.layout.list":"List"};
1
+ export const translation = {"breadcrumbs.home.text":"Home","breadcrumbs.label.text":"Breadcrumbs","close.text":"Close","clear.text":"Clear","open.text":"Open","picker.calendar.navigation.nextmonth":"Next month","picker.calendar.navigation.previousmonth":"Previous month","picker.date.toolbar.title":"Selected date","picker.daterange.toolbar.title":"Select date range","picker.datetime.toolbar.title":"Select date & time","picker.error.invalid":"Correct the date format or select the date from the calendar.","picker.error.mindate":"Date entered is earlier than allowed dates. Select a date from the range available in the calendar.","picker.error.maxdate":"Date entered is later than the allowed dates. Select a date from the range available in the calendar.","picker.field.placeholder.day":"DD","picker.field.placeholder.hours":"hh","picker.field.placeholder.meridiem":"aa","picker.field.placeholder.minutes":"mm","picker.field.placeholder.month":"MM","picker.field.placeholder.seconds":"ss","picker.field.placeholder.year":"YYYY","picker.labels.action.apply":"Apply","picker.labels.action.cancel":"Cancel","picker.labels.action.today":"Today","picker.labels.clock.empty":"No time selected","picker.labels.clock.hours":"hours","picker.labels.clock.minutes":"minutes","picker.labels.clock.seconds":"seconds","picker.labels.clock.selected":"Selected time is","picker.labels.date.choose":"Choose date","picker.labels.date.selected":"Selected date is","picker.labels.empty":"Empty","picker.labels.field.clear":"Clear value","picker.labels.range.end":"End","picker.labels.range.enddate":"End date","picker.labels.range.endtime":"End time","picker.labels.range.start":"Start","picker.labels.range.startdate":"Start date","picker.labels.range.starttime":"Start time","picker.labels.select":"Select","picker.labels.table.date":"pick date","picker.labels.table.time":"pick time","picker.labels.time.choose":"Choose time","picker.labels.time.selected":"selected time is","picker.time.toolbar.title":"Select time","picker.view.name.day":"Day","picker.view.name.hours":"Hours","picker.view.name.meridiem":"Meridiem","picker.view.name.minutes":"Minutes","picker.view.name.month":"Month","picker.view.name.seconds":"Seconds","picker.view.name.weekday":"Week day","picker.view.name.year":"Year","picker.view.navigation.open.nextview":"Open next view","picker.view.navigation.open.previousview":"Open previous view","picker.view.navigation.switch.calendarview":"year view is open, switch to calendar view","picker.view.navigation.switch.yearview":"calendar view is open, switch to year view","fielderror.screenreader.text":"Error","fieldlabel.optional.text":"Optional","fieldlabel.required.text":"Required","filters.clear.label":"Clear filters","filters.filter.any":"Any","filters.filter.clear":"Clear filter","filters.filters.arialabel":"Filters","filters.menuitem.any":"Any {{label}}","filters.menuitem.selected":"{{selected}} selected","filters.search.label":"Search","fileupload.button.text":"Add files","fileupload.prompt.text":"Drag and drop files here or click to add files","fileupload.removefile.text":"Remove file","passwordfield.icon.label.hide":"Hide password","passwordfield.icon.label.show":"Show password","severity.error":"error","severity.info":"info","severity.success":"success","severity.warning":"warning","switch.active":"Active","switch.inactive":"Inactive","table.columnvisibility.arialabel":"Show/hide columns","table.density.arialabel":"Table density","table.draghandle.arialabel":"Drag row to reorder. Or, press space or enter to start and stop reordering and esc to cancel.","table.draghandle.tooltip":"Drag row or press space/enter key to start and stop reordering","table.actions":"Actions","table.error":"Error loading data.","table.fetchedrows.text":"Fetched {{totalRows}} row","table.fetchedrows.text_plural":"Fetched {{totalRows}} total rows","table.moreactions.arialabel":"More actions","table.noresults.heading":"There are no results.","table.noresults.text":"Try a different query.","table.reorder.backward":"Send backward","table.reorder.forward":"Bring forward","table.reorder.toback":"Send to back","table.reorder.tofront":"Bring to front","table.rows.text":"{{totalRows}} row","table.rows.text_plural":"{{totalRows}} rows","pagination.loadmore":"Show more","pagination.next":"Next page","pagination.previous":"Previous page","pagination.page":"Page","pagination.rowsperpage":"Rows per page","pagination.rowswithtotal":"{{firstRow}}-{{lastRow}} of {{totalRows}} rows","pagination.rowswithouttotal":"{{firstRow}}-{{lastRow}} rows","table.actions.selectall":"Select all","table.actions.selectnone":"Select none","table.actions.selectsome":"{{selectedRowCount}} selected","table.rowexpansion.expand":"Expand","table.rowexpansion.expandall":"Expand all","table.rowexpansion.collapse":"Collapse","table.rowexpansion.collapseall":"Collapse all","dataview.layout.table":"Table","dataview.layout.grid":"Grid","dataview.layout.list":"List"};