@servicetitan/navigation 11.0.0-canary.237.c4efaad.0 → 11.0.0-canary.237.dd4db7e.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 (82) hide show
  1. package/dist/components/badge-tag.d.ts +1 -1
  2. package/dist/components/badge-tag.d.ts.map +1 -1
  3. package/dist/components/header-navigation/header-navigation-stacked.stories.js +1 -1
  4. package/dist/components/header-navigation/header-navigation-stacked.stories.js.map +1 -1
  5. package/dist/components/header-navigation/header-navigation.stories.js +1 -1
  6. package/dist/components/header-navigation/header-navigation.stories.js.map +1 -1
  7. package/dist/components/left-navigation/header-navigation-tiny.stories.js +2 -2
  8. package/dist/components/left-navigation/header-navigation-tiny.stories.js.map +1 -1
  9. package/dist/components/logo/logo-titan-text.d.ts +1 -1
  10. package/dist/components/logo/logo-titan-text.d.ts.map +1 -1
  11. package/dist/components/profile-dropdown/profile-dropdown.d.ts +10 -7
  12. package/dist/components/profile-dropdown/profile-dropdown.d.ts.map +1 -1
  13. package/dist/components/profile-dropdown/profile-dropdown.js +2 -2
  14. package/dist/components/profile-dropdown/profile-dropdown.js.map +1 -1
  15. package/dist/components/profile-dropdown/profile-dropdown.module.less +2 -0
  16. package/dist/components/titan-layout/layout-header.d.ts +2 -0
  17. package/dist/components/titan-layout/layout-header.d.ts.map +1 -1
  18. package/dist/components/titan-layout/layout-header.js +3 -4
  19. package/dist/components/titan-layout/layout-header.js.map +1 -1
  20. package/dist/components/titan-layout/layout-header.module.less +62 -16
  21. package/dist/components/titan-layout/layout-logo.d.ts.map +1 -1
  22. package/dist/components/titan-layout/layout-logo.js +2 -1
  23. package/dist/components/titan-layout/layout-logo.js.map +1 -1
  24. package/dist/components/titan-layout/layout-profile.d.ts.map +1 -1
  25. package/dist/components/titan-layout/layout-profile.js +32 -8
  26. package/dist/components/titan-layout/layout-profile.js.map +1 -1
  27. package/dist/components/titan-layout/layout-profile.stories.d.ts.map +1 -1
  28. package/dist/components/titan-layout/layout-profile.stories.js +1 -1
  29. package/dist/components/titan-layout/layout-profile.stories.js.map +1 -1
  30. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts +2 -2
  31. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts.map +1 -1
  32. package/dist/components/titan-layout/layout-sidebar-links-internal.js +4 -4
  33. package/dist/components/titan-layout/layout-sidebar-links-internal.js.map +1 -1
  34. package/dist/components/titan-layout/layout-sidebar-links.d.ts.map +1 -1
  35. package/dist/components/titan-layout/layout-sidebar-links.js +11 -4
  36. package/dist/components/titan-layout/layout-sidebar-links.js.map +1 -1
  37. package/dist/components/titan-layout/layout-sidebar.d.ts +2 -0
  38. package/dist/components/titan-layout/layout-sidebar.d.ts.map +1 -1
  39. package/dist/components/titan-layout/layout-sidebar.js +6 -4
  40. package/dist/components/titan-layout/layout-sidebar.js.map +1 -1
  41. package/dist/components/titan-layout/layout-sidebar.module.less +29 -6
  42. package/dist/components/titan-layout/notifications-context.d.ts +13 -0
  43. package/dist/components/titan-layout/notifications-context.d.ts.map +1 -0
  44. package/dist/components/titan-layout/notifications-context.js +23 -0
  45. package/dist/components/titan-layout/notifications-context.js.map +1 -0
  46. package/dist/components/titan-layout/titan-layout.d.ts +10 -6
  47. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -1
  48. package/dist/components/titan-layout/titan-layout.js +73 -21
  49. package/dist/components/titan-layout/titan-layout.js.map +1 -1
  50. package/dist/components/titan-layout/titan-layout.module.less +53 -20
  51. package/dist/components/titan-layout/titan-layout.stories.d.ts +15 -11
  52. package/dist/components/titan-layout/titan-layout.stories.d.ts.map +1 -1
  53. package/dist/components/titan-layout/titan-layout.stories.js +35 -14
  54. package/dist/components/titan-layout/titan-layout.stories.js.map +1 -1
  55. package/dist/test/data.d.ts +4 -1
  56. package/dist/test/data.d.ts.map +1 -1
  57. package/dist/test/data.js +2 -3
  58. package/dist/test/data.js.map +1 -1
  59. package/package.json +2 -2
  60. package/src/components/badge-tag.tsx +1 -1
  61. package/src/components/header-navigation/header-navigation-stacked.stories.tsx +1 -1
  62. package/src/components/header-navigation/header-navigation.stories.tsx +1 -1
  63. package/src/components/left-navigation/header-navigation-tiny.stories.tsx +2 -2
  64. package/src/components/logo/logo-titan-text.tsx +1 -1
  65. package/src/components/profile-dropdown/profile-dropdown.module.less +2 -0
  66. package/src/components/profile-dropdown/profile-dropdown.tsx +13 -6
  67. package/src/components/titan-layout/layout-header.module.less +62 -16
  68. package/src/components/titan-layout/layout-header.tsx +12 -5
  69. package/src/components/titan-layout/layout-logo.tsx +13 -6
  70. package/src/components/titan-layout/layout-profile.stories.tsx +10 -1
  71. package/src/components/titan-layout/layout-profile.tsx +60 -25
  72. package/src/components/titan-layout/layout-sidebar-links-internal.tsx +18 -5
  73. package/src/components/titan-layout/layout-sidebar-links.tsx +16 -4
  74. package/src/components/titan-layout/layout-sidebar.module.less +29 -6
  75. package/src/components/titan-layout/layout-sidebar.module.less.d.ts +1 -0
  76. package/src/components/titan-layout/layout-sidebar.tsx +15 -6
  77. package/src/components/titan-layout/notifications-context.tsx +44 -0
  78. package/src/components/titan-layout/titan-layout.module.less +53 -20
  79. package/src/components/titan-layout/titan-layout.module.less.d.ts +3 -0
  80. package/src/components/titan-layout/titan-layout.stories.tsx +166 -19
  81. package/src/components/titan-layout/titan-layout.tsx +193 -77
  82. package/src/test/data.tsx +2 -3
@@ -11,6 +11,7 @@ import {
11
11
  useCallback,
12
12
  useEffect,
13
13
  useMemo,
14
+ useRef,
14
15
  useState,
15
16
  } from 'react';
16
17
  import { NavigationItemData } from '../../utils/navigation';
@@ -28,33 +29,38 @@ import { TitanLayoutLogo, TitanLayoutLogoProps } from './layout-logo';
28
29
  import { LayoutSidebar } from './layout-sidebar';
29
30
  import { TitanLayoutSidebarLink, TitanLayoutSidebarTrigger } from './layout-sidebar-links';
30
31
  import { InternalSideNavigationTrigger } from './layout-sidebar-links-internal';
32
+ import { useNotificationsState } from './notifications-context';
31
33
  import * as Styles from './titan-layout.module.less';
32
34
 
33
35
  type TitanLayoutChild = ReactElement<TitanLayoutContentProps> | ReactElement<TitanLayoutLogoProps>;
34
36
 
35
37
  export type TitanLayoutProps = Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'style'> & {
36
- empty?: boolean;
37
-
38
+ /** layout appearance */
38
39
  appearance?: 'legacy' | 'anvil1' | 'anvil2';
39
40
 
41
+ /** layout's content */
42
+ children?: TitanLayoutChild | TitanLayoutChild[];
43
+
44
+ /** show only content without side and top bars */
45
+ contentOnly?: boolean;
46
+
40
47
  /** component used for navigation */
41
48
  navigationComponent?: FC<NavLinkComponentProps>;
42
49
 
43
50
  /** data for main navigation links */
44
51
  navigationMainItems?: NavigationItemData[];
45
52
 
46
- /** layout's content */
47
- children?: TitanLayoutChild | TitanLayoutChild[];
48
-
49
53
  state?: TitanLayoutState;
50
54
  onStateChange?: (state: TitanLayoutState) => void;
51
55
 
52
56
  header?: ReactElement;
57
+ top?: ReactElement;
58
+ sideTop?: ReactElement[];
53
59
  profile?: ReactElement;
54
60
  extraLinks?: ReactElement;
55
61
  extraLinksTop?: ReactElement;
56
62
  extraText?: string;
57
- sidebarTop?: ReactElement[];
63
+ minContentWidth?: number;
58
64
  };
59
65
 
60
66
  const defaultSidebarContext: TitanLayoutSidebarContextType = {
@@ -113,8 +119,10 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
113
119
  appearance = 'anvil2',
114
120
  id,
115
121
  children,
122
+ contentOnly,
116
123
  navigationComponent,
117
124
  header,
125
+ top,
118
126
  profile,
119
127
  state,
120
128
  onStateChange,
@@ -122,10 +130,10 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
122
130
  extraLinks,
123
131
  extraLinksTop,
124
132
  extraText,
125
- sidebarTop,
133
+ minContentWidth,
134
+ sideTop,
126
135
  }) => {
127
136
  const breakpoint = useTitanBreakpoint();
128
- const isMobile = breakpoint.isMobile;
129
137
  const context: TitanLayoutContextType = useMemo(
130
138
  () => ({
131
139
  NavigationComponent: navigationComponent ?? DefaultNavLinkComponent,
@@ -138,21 +146,19 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
138
146
  const variant = useVariant(appearance);
139
147
  const [mobileDrawerOpened, setMobileDrawerOpened] = useState(false);
140
148
  const { content, logo } = useLayoutChildren(children);
149
+ const { hasNotifications, NotificationsContextProvider } = useNotificationsState();
150
+
151
+ const isMobile = breakpoint.isMobile;
152
+ const hasSideBar = !contentOnly && (!!navigationMainItems?.length || !!sideTop?.length);
153
+ const hasTopBar = !contentOnly;
141
154
 
142
155
  useEffect(() => {
143
- if (!isMobile) {
144
- setMobileDrawerOpened(false);
145
- return;
156
+ if (variant.isAnvil1) {
157
+ const bodyClassName = 'of-hidden-i';
158
+ document.body.classList.add(bodyClassName);
159
+ return () => document.body.classList.remove(bodyClassName);
146
160
  }
147
-
148
- const listener = () => {
149
- setMobileDrawerOpened(false);
150
- };
151
-
152
- document.addEventListener('click', listener);
153
-
154
- return () => document.removeEventListener('click', listener);
155
- }, [isMobile]);
161
+ }, [variant.isAnvil1]);
156
162
 
157
163
  const onBurgerClick = useCallback((e: MouseEvent) => {
158
164
  setMobileDrawerOpened(true);
@@ -178,8 +184,24 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
178
184
  },
179
185
  [state, onStateChange]
180
186
  );
181
-
182
- const [layoutStyles] = useState<object>({});
187
+ const hasMenuNotifications = useMemo(() => {
188
+ try {
189
+ return (
190
+ navigationMainItems?.some(item => {
191
+ if (item.counter || item.tag?.value) {
192
+ return true;
193
+ } else if (item.submenu) {
194
+ return item.submenu.groups.some(group =>
195
+ group.links.some(link => !!link.counter || !!link.tag?.value)
196
+ );
197
+ }
198
+ return false;
199
+ }) ?? false
200
+ );
201
+ } catch {
202
+ return false;
203
+ }
204
+ }, [navigationMainItems]);
183
205
 
184
206
  const layoutClass = variant.isLegacy
185
207
  ? Styles.layoutLegacy
@@ -194,79 +216,173 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
194
216
  id={id}
195
217
  className={classNames(
196
218
  Styles.layout,
197
- isMobile
198
- ? Styles.layoutMobile
199
- : state?.navCollapsed
200
- ? Styles.layoutNavSlim
201
- : Styles.layoutNavWide,
219
+ isMobile ? Styles.layoutMobile : Styles.layoutDesktop,
220
+ {
221
+ [Styles.layoutTop]: hasTopBar,
222
+ [Styles.layoutNavSlim]: !isMobile && hasSideBar && state?.navCollapsed,
223
+ [Styles.layoutNavWide]: !isMobile && hasSideBar && !state?.navCollapsed,
224
+ },
202
225
  layoutClass
203
226
  )}
204
- style={layoutStyles}
205
227
  >
206
228
  {variant.isSequent && <div className={Styles.topPlaceholder} />}
207
- <LayoutHeader
208
- className={Styles.top}
209
- logo={logo}
210
- profile={isMobile ? undefined : profile}
211
- center={header}
212
- rightText={isMobile ? undefined : extraText}
213
- right={
214
- <Fragment>
215
- {extraLinksTop}
216
- {!isMobile && extraLinks}
217
- </Fragment>
218
- }
219
- onBurgerClick={onBurgerClick}
220
- />
221
-
222
- <LayoutSidebar
223
- className={Styles.side}
224
- mobile={breakpoint.isMobile}
225
- barExpanded={isMobile ? mobileDrawerOpened : !state?.navCollapsed}
226
- submenuExpanded={state?.submenuExpanded}
227
- onBarExpandChange={onBarExpandChange}
228
- onSubmenuExpandChange={onSubmenuExpandChange}
229
- top={sidebarTop}
230
- mainItems={navigationMainItems}
231
- navigationComponent={context.NavigationComponent}
232
- bottom={
233
- isMobile ? (
229
+ {hasTopBar && (
230
+ <LayoutHeader
231
+ className={Styles.top}
232
+ logo={logo}
233
+ profile={isMobile ? undefined : profile}
234
+ center={top}
235
+ rightText={isMobile ? undefined : extraText}
236
+ right={
234
237
  <Fragment>
235
- {profile}
236
- {extraLinks}
237
- {!!extraText && (
238
- <InternalSideNavigationTrigger
239
- id="__extra_text"
240
- title={extraText}
241
- submenuExpanded={undefined}
242
- dataPrefix="navigation-extra-text"
243
- tag={undefined}
244
- icon={undefined}
245
- iconActive={undefined}
246
- />
247
- )}
238
+ {extraLinksTop}
239
+ {!isMobile && extraLinks}
248
240
  </Fragment>
249
- ) : undefined
250
- }
251
- />
241
+ }
242
+ isMobile={isMobile}
243
+ hasNotifications={hasNotifications || hasMenuNotifications}
244
+ onBurgerClick={onBurgerClick}
245
+ />
246
+ )}
252
247
 
253
- {content}
248
+ {hasSideBar && (
249
+ <NotificationsContextProvider>
250
+ <LayoutSidebar
251
+ className={Styles.side}
252
+ mobile={breakpoint.isMobile}
253
+ barExpanded={!state?.navCollapsed}
254
+ onBarExpandChange={onBarExpandChange}
255
+ submenuExpanded={state?.submenuExpanded}
256
+ onSubmenuExpandChange={onSubmenuExpandChange}
257
+ drawerOpened={mobileDrawerOpened}
258
+ onDrawerOpenChange={setMobileDrawerOpened}
259
+ top={sideTop}
260
+ mainItems={navigationMainItems}
261
+ navigationComponent={context.NavigationComponent}
262
+ bottom={
263
+ isMobile ? (
264
+ <Fragment>
265
+ {profile}
266
+ {extraLinks}
267
+ {!!extraText && (
268
+ <InternalSideNavigationTrigger
269
+ id="__extra_text"
270
+ title={extraText}
271
+ submenuExpanded={undefined}
272
+ dataPrefix="navigation-extra-text"
273
+ tag={undefined}
274
+ icon={undefined}
275
+ iconActive={undefined}
276
+ />
277
+ )}
278
+ </Fragment>
279
+ ) : undefined
280
+ }
281
+ />
282
+ </NotificationsContextProvider>
283
+ )}
284
+ <LayoutContent
285
+ header={header}
286
+ anvil2={variant.isAnvil2}
287
+ anvil1={variant.isAnvil1}
288
+ minWidth={minContentWidth}
289
+ >
290
+ {content}
291
+ </LayoutContent>
254
292
  </div>
255
293
  </LayoutPlacementContext.Provider>
256
294
  </LayoutContext.Provider>
257
295
  );
258
296
  };
259
297
 
298
+ const TitanLayoutHeaderObserved: FC<{
299
+ children: ReactNode;
300
+ heightChange?(value: number): void;
301
+ }> = ({ children, heightChange }) => {
302
+ const ref = useRef<HTMLDivElement>(null);
303
+
304
+ useEffect(() => {
305
+ if (ref.current) {
306
+ const updatePosition = () => {
307
+ if (ref.current && heightChange) {
308
+ const pos = ref.current.getBoundingClientRect();
309
+ heightChange(pos.height);
310
+ }
311
+ };
312
+
313
+ const observer = new ResizeObserver(updatePosition);
314
+ observer.observe(ref.current);
315
+
316
+ updatePosition();
317
+ return () => observer.disconnect();
318
+ }
319
+ }, [heightChange]);
320
+
321
+ useEffect(() => {
322
+ return () => {
323
+ heightChange?.(0);
324
+ };
325
+ }, [heightChange]);
326
+ return (
327
+ <div ref={ref} className={Styles.contentHeader} data-cy="layout-content-header">
328
+ {children}
329
+ </div>
330
+ );
331
+ };
332
+
260
333
  export interface TitanLayoutContentProps {
261
334
  children: ReactNode;
262
335
  }
263
- const TitanLayoutContent: FC<TitanLayoutContentProps> = ({ children }) => (
264
- <div className={Styles.content}>{children}</div>
265
- );
336
+ const TitanLayoutContent: FC<TitanLayoutContentProps> = ({ children }) => children;
337
+
338
+ const LayoutContent: FC<{
339
+ children: ReactNode;
340
+ header?: ReactNode;
341
+ anvil1: boolean;
342
+ anvil2: boolean;
343
+ minWidth: number | undefined;
344
+ }> = ({ anvil1, anvil2, children, header, minWidth }) => {
345
+ const [anvil2Styles, setAnvil2Styles] = useState<object>({});
346
+ const updateIndicatorsHeight = useCallback((offset: number) => {
347
+ setAnvil2Styles({ '--offset': `calc(var(--nav-offset-top) + ${offset}px)` });
348
+ }, []);
349
+
350
+ const contentStyles = useMemo(
351
+ () => ({
352
+ ...(minWidth ? { minWidth: `${minWidth}px` } : {}),
353
+ ...(anvil2 ? anvil2Styles : {}),
354
+ }),
355
+ [anvil2, minWidth, anvil2Styles]
356
+ );
357
+
358
+ return (
359
+ <Fragment>
360
+ {!!header &&
361
+ (anvil2 ? (
362
+ <TitanLayoutHeaderObserved heightChange={updateIndicatorsHeight}>
363
+ {header}
364
+ </TitanLayoutHeaderObserved>
365
+ ) : (
366
+ <div className={Styles.contentHeader} data-cy="layout-content-header">
367
+ {header}
368
+ </div>
369
+ ))}
370
+ <div className={Styles.content} style={contentStyles} data-cy="layout-content">
371
+ {anvil1 ? (
372
+ <div className="position-relative d-f flex-grow-1 flex-basis-0 of-hidden">
373
+ {children}
374
+ </div>
375
+ ) : (
376
+ children
377
+ )}
378
+ </div>
379
+ </Fragment>
380
+ );
381
+ };
266
382
 
267
383
  export const TitanLayout = Object.assign(TitanLayoutComponent, {
268
384
  Content: TitanLayoutContent,
269
385
  Logo: TitanLayoutLogo,
270
- SidebarLink: TitanLayoutSidebarLink,
271
- SidebarTrigger: TitanLayoutSidebarTrigger,
386
+ Link: TitanLayoutSidebarLink,
387
+ Trigger: TitanLayoutSidebarTrigger,
272
388
  });
package/src/test/data.tsx CHANGED
@@ -66,7 +66,6 @@ export const NavLinkMock = forwardRef<any, NavLinkComponentProps>(
66
66
  {...rest}
67
67
  onClick={e => {
68
68
  e.preventDefault();
69
- e.stopPropagation();
70
69
 
71
70
  if (!to.startsWith('http')) {
72
71
  history.replace(to);
@@ -82,10 +81,10 @@ export const NavLinkMock = forwardRef<any, NavLinkComponentProps>(
82
81
  }
83
82
  );
84
83
 
85
- export const LocationInfo = () => {
84
+ export const LocationInfo: FC<{ className?: string }> = ({ className }) => {
86
85
  const location = useLocation();
87
86
 
88
- return <BodyText>current location - {location.pathname}</BodyText>;
87
+ return <BodyText className={className}>current location - {location.pathname}</BodyText>;
89
88
  };
90
89
 
91
90
  const LocationProvider: FC<{ children: any }> = ({ children }) => {