@servicetitan/navigation 11.0.0-canary.237.fef17f5.0 → 11.0.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 (96) 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 +12 -7
  12. package/dist/components/profile-dropdown/profile-dropdown.d.ts.map +1 -1
  13. package/dist/components/profile-dropdown/profile-dropdown.js +10 -7
  14. package/dist/components/profile-dropdown/profile-dropdown.js.map +1 -1
  15. package/dist/components/profile-dropdown/profile-dropdown.module.less +20 -6
  16. package/dist/components/titan-layout/layout-context.js +1 -1
  17. package/dist/components/titan-layout/layout-context.js.map +1 -1
  18. package/dist/components/titan-layout/layout-header-links.d.ts.map +1 -1
  19. package/dist/components/titan-layout/layout-header-links.js +1 -1
  20. package/dist/components/titan-layout/layout-header-links.js.map +1 -1
  21. package/dist/components/titan-layout/layout-header.d.ts +2 -0
  22. package/dist/components/titan-layout/layout-header.d.ts.map +1 -1
  23. package/dist/components/titan-layout/layout-header.js +3 -4
  24. package/dist/components/titan-layout/layout-header.js.map +1 -1
  25. package/dist/components/titan-layout/layout-header.module.less +56 -33
  26. package/dist/components/titan-layout/layout-logo.d.ts.map +1 -1
  27. package/dist/components/titan-layout/layout-logo.js +2 -1
  28. package/dist/components/titan-layout/layout-logo.js.map +1 -1
  29. package/dist/components/titan-layout/layout-profile.d.ts.map +1 -1
  30. package/dist/components/titan-layout/layout-profile.js +39 -11
  31. package/dist/components/titan-layout/layout-profile.js.map +1 -1
  32. package/dist/components/titan-layout/layout-profile.stories.d.ts.map +1 -1
  33. package/dist/components/titan-layout/layout-profile.stories.js +1 -1
  34. package/dist/components/titan-layout/layout-profile.stories.js.map +1 -1
  35. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts +2 -2
  36. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts.map +1 -1
  37. package/dist/components/titan-layout/layout-sidebar-links-internal.js +4 -4
  38. package/dist/components/titan-layout/layout-sidebar-links-internal.js.map +1 -1
  39. package/dist/components/titan-layout/layout-sidebar-links.d.ts.map +1 -1
  40. package/dist/components/titan-layout/layout-sidebar-links.js +10 -3
  41. package/dist/components/titan-layout/layout-sidebar-links.js.map +1 -1
  42. package/dist/components/titan-layout/layout-sidebar.d.ts +2 -0
  43. package/dist/components/titan-layout/layout-sidebar.d.ts.map +1 -1
  44. package/dist/components/titan-layout/layout-sidebar.js +6 -4
  45. package/dist/components/titan-layout/layout-sidebar.js.map +1 -1
  46. package/dist/components/titan-layout/layout-sidebar.module.less +25 -5
  47. package/dist/components/titan-layout/notifications-context.d.ts +13 -0
  48. package/dist/components/titan-layout/notifications-context.d.ts.map +1 -0
  49. package/dist/components/titan-layout/notifications-context.js +23 -0
  50. package/dist/components/titan-layout/notifications-context.js.map +1 -0
  51. package/dist/components/titan-layout/titan-layout.d.ts +6 -3
  52. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -1
  53. package/dist/components/titan-layout/titan-layout.js +77 -22
  54. package/dist/components/titan-layout/titan-layout.js.map +1 -1
  55. package/dist/components/titan-layout/titan-layout.module.less +44 -20
  56. package/dist/components/titan-layout/titan-layout.stories.d.ts +4 -0
  57. package/dist/components/titan-layout/titan-layout.stories.d.ts.map +1 -1
  58. package/dist/components/titan-layout/titan-layout.stories.js +15 -7
  59. package/dist/components/titan-layout/titan-layout.stories.js.map +1 -1
  60. package/dist/test/data.d.ts +4 -1
  61. package/dist/test/data.d.ts.map +1 -1
  62. package/dist/test/data.js +2 -3
  63. package/dist/test/data.js.map +1 -1
  64. package/dist/utils/use-breakpoint.d.ts +1 -0
  65. package/dist/utils/use-breakpoint.d.ts.map +1 -1
  66. package/dist/utils/use-breakpoint.js +3 -2
  67. package/dist/utils/use-breakpoint.js.map +1 -1
  68. package/package.json +2 -2
  69. package/src/components/badge-tag.tsx +1 -1
  70. package/src/components/header-navigation/header-navigation-stacked.stories.tsx +1 -1
  71. package/src/components/header-navigation/header-navigation.stories.tsx +1 -1
  72. package/src/components/left-navigation/header-navigation-tiny.stories.tsx +2 -2
  73. package/src/components/logo/logo-titan-text.tsx +1 -1
  74. package/src/components/profile-dropdown/profile-dropdown.module.less +20 -6
  75. package/src/components/profile-dropdown/profile-dropdown.module.less.d.ts +2 -0
  76. package/src/components/profile-dropdown/profile-dropdown.tsx +50 -15
  77. package/src/components/titan-layout/layout-context.tsx +1 -1
  78. package/src/components/titan-layout/layout-header-links.tsx +2 -1
  79. package/src/components/titan-layout/layout-header.module.less +56 -33
  80. package/src/components/titan-layout/layout-header.module.less.d.ts +2 -0
  81. package/src/components/titan-layout/layout-header.tsx +12 -5
  82. package/src/components/titan-layout/layout-logo.tsx +13 -6
  83. package/src/components/titan-layout/layout-profile.stories.tsx +10 -1
  84. package/src/components/titan-layout/layout-profile.tsx +92 -28
  85. package/src/components/titan-layout/layout-sidebar-links-internal.tsx +18 -5
  86. package/src/components/titan-layout/layout-sidebar-links.tsx +16 -3
  87. package/src/components/titan-layout/layout-sidebar.module.less +25 -5
  88. package/src/components/titan-layout/layout-sidebar.module.less.d.ts +1 -0
  89. package/src/components/titan-layout/layout-sidebar.tsx +14 -5
  90. package/src/components/titan-layout/notifications-context.tsx +44 -0
  91. package/src/components/titan-layout/titan-layout.module.less +44 -20
  92. package/src/components/titan-layout/titan-layout.module.less.d.ts +2 -1
  93. package/src/components/titan-layout/titan-layout.stories.tsx +118 -6
  94. package/src/components/titan-layout/titan-layout.tsx +196 -87
  95. package/src/test/data.tsx +2 -3
  96. package/src/utils/use-breakpoint.ts +3 -1
@@ -29,24 +29,27 @@ import { TitanLayoutLogo, TitanLayoutLogoProps } from './layout-logo';
29
29
  import { LayoutSidebar } from './layout-sidebar';
30
30
  import { TitanLayoutSidebarLink, TitanLayoutSidebarTrigger } from './layout-sidebar-links';
31
31
  import { InternalSideNavigationTrigger } from './layout-sidebar-links-internal';
32
+ import { useNotificationsState } from './notifications-context';
32
33
  import * as Styles from './titan-layout.module.less';
33
34
 
34
35
  type TitanLayoutChild = ReactElement<TitanLayoutContentProps> | ReactElement<TitanLayoutLogoProps>;
35
36
 
36
37
  export type TitanLayoutProps = Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'style'> & {
37
- empty?: boolean;
38
-
38
+ /** layout appearance */
39
39
  appearance?: 'legacy' | 'anvil1' | 'anvil2';
40
40
 
41
+ /** layout's content */
42
+ children?: TitanLayoutChild | TitanLayoutChild[];
43
+
44
+ /** show only content without side and top bars */
45
+ contentOnly?: boolean;
46
+
41
47
  /** component used for navigation */
42
48
  navigationComponent?: FC<NavLinkComponentProps>;
43
49
 
44
50
  /** data for main navigation links */
45
51
  navigationMainItems?: NavigationItemData[];
46
52
 
47
- /** layout's content */
48
- children?: TitanLayoutChild | TitanLayoutChild[];
49
-
50
53
  state?: TitanLayoutState;
51
54
  onStateChange?: (state: TitanLayoutState) => void;
52
55
 
@@ -57,6 +60,7 @@ export type TitanLayoutProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'
57
60
  extraLinks?: ReactElement;
58
61
  extraLinksTop?: ReactElement;
59
62
  extraText?: string;
63
+ minContentWidth?: number;
60
64
  };
61
65
 
62
66
  const defaultSidebarContext: TitanLayoutSidebarContextType = {
@@ -115,6 +119,7 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
115
119
  appearance = 'anvil2',
116
120
  id,
117
121
  children,
122
+ contentOnly,
118
123
  navigationComponent,
119
124
  header,
120
125
  top,
@@ -125,10 +130,10 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
125
130
  extraLinks,
126
131
  extraLinksTop,
127
132
  extraText,
133
+ minContentWidth,
128
134
  sideTop,
129
135
  }) => {
130
136
  const breakpoint = useTitanBreakpoint();
131
- const isMobile = breakpoint.isMobile;
132
137
  const context: TitanLayoutContextType = useMemo(
133
138
  () => ({
134
139
  NavigationComponent: navigationComponent ?? DefaultNavLinkComponent,
@@ -141,21 +146,25 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
141
146
  const variant = useVariant(appearance);
142
147
  const [mobileDrawerOpened, setMobileDrawerOpened] = useState(false);
143
148
  const { content, logo } = useLayoutChildren(children);
149
+ const { hasNotifications, NotificationsContextProvider } = useNotificationsState();
150
+ const [offsetTopStyles, setOffsetTopStyles] = useState<object>({});
151
+ const updateIndicatorsHeight = useCallback((offset: number) => {
152
+ setOffsetTopStyles({
153
+ '--content-offset-top': `calc(var(--nav-offset-top) + ${offset}px)`,
154
+ });
155
+ }, []);
156
+
157
+ const isMobile = breakpoint.isMobile;
158
+ const hasSideBar = !contentOnly && (!!navigationMainItems?.length || !!sideTop?.length);
159
+ const hasTopBar = !contentOnly;
144
160
 
145
161
  useEffect(() => {
146
- if (!isMobile) {
147
- setMobileDrawerOpened(false);
148
- return;
162
+ if (variant.isAnvil1) {
163
+ const bodyClassName = 'of-hidden-i';
164
+ document.body.classList.add(bodyClassName);
165
+ return () => document.body.classList.remove(bodyClassName);
149
166
  }
150
-
151
- const listener = () => {
152
- setMobileDrawerOpened(false);
153
- };
154
-
155
- document.addEventListener('click', listener);
156
-
157
- return () => document.removeEventListener('click', listener);
158
- }, [isMobile]);
167
+ }, [variant.isAnvil1]);
159
168
 
160
169
  const onBurgerClick = useCallback((e: MouseEvent) => {
161
170
  setMobileDrawerOpened(true);
@@ -181,6 +190,49 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
181
190
  },
182
191
  [state, onStateChange]
183
192
  );
193
+ const hasMenuNotifications = useMemo(() => {
194
+ try {
195
+ return (
196
+ navigationMainItems?.some(item => {
197
+ if (item.counter || item.tag?.value) {
198
+ return true;
199
+ } else if (item.submenu) {
200
+ return item.submenu.groups.some(group =>
201
+ group.links.some(link => !!link.counter || !!link.tag?.value)
202
+ );
203
+ }
204
+ return false;
205
+ }) ?? false
206
+ );
207
+ } catch {
208
+ return false;
209
+ }
210
+ }, [navigationMainItems]);
211
+
212
+ const limitContentWidth = useMemo(() => {
213
+ if (variant.isAnvil2 || !minContentWidth) {
214
+ return undefined;
215
+ }
216
+
217
+ if (breakpoint.width < minContentWidth) {
218
+ return minContentWidth;
219
+ }
220
+ }, [variant, minContentWidth, breakpoint.width]);
221
+
222
+ const contentStyles = useMemo(() => {
223
+ if (variant.isAnvil2) {
224
+ return offsetTopStyles;
225
+ }
226
+
227
+ if (variant.isLegacy) {
228
+ return {
229
+ ...(limitContentWidth
230
+ ? { display: 'flex', flexDirection: 'column', minHeight: '100vh' }
231
+ : {}),
232
+ ...offsetTopStyles,
233
+ };
234
+ }
235
+ }, [variant, offsetTopStyles, limitContentWidth]);
184
236
 
185
237
  const layoutClass = variant.isLegacy
186
238
  ? Styles.layoutLegacy
@@ -196,62 +248,88 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
196
248
  className={classNames(
197
249
  Styles.layout,
198
250
  isMobile ? Styles.layoutMobile : Styles.layoutDesktop,
199
- !isMobile && state?.navCollapsed
200
- ? Styles.layoutNavSlim
201
- : Styles.layoutNavWide,
251
+ {
252
+ [Styles.layoutTop]: hasTopBar,
253
+ [Styles.layoutNavSlim]: !isMobile && hasSideBar && state?.navCollapsed,
254
+ [Styles.layoutNavWide]: !isMobile && hasSideBar && !state?.navCollapsed,
255
+ },
202
256
  layoutClass
203
257
  )}
258
+ style={contentStyles}
204
259
  >
205
260
  {variant.isSequent && <div className={Styles.topPlaceholder} />}
206
- <LayoutHeader
207
- className={Styles.top}
208
- logo={logo}
209
- profile={isMobile ? undefined : profile}
210
- center={top}
211
- rightText={isMobile ? undefined : extraText}
212
- right={
213
- <Fragment>
214
- {extraLinksTop}
215
- {!isMobile && extraLinks}
216
- </Fragment>
217
- }
218
- onBurgerClick={onBurgerClick}
219
- />
220
-
221
- <LayoutSidebar
222
- className={Styles.side}
223
- mobile={breakpoint.isMobile}
224
- barExpanded={isMobile ? mobileDrawerOpened : !state?.navCollapsed}
225
- submenuExpanded={state?.submenuExpanded}
226
- onBarExpandChange={onBarExpandChange}
227
- onSubmenuExpandChange={onSubmenuExpandChange}
228
- top={sideTop}
229
- mainItems={navigationMainItems}
230
- navigationComponent={context.NavigationComponent}
231
- bottom={
232
- isMobile ? (
261
+ {hasTopBar && (
262
+ <LayoutHeader
263
+ className={Styles.top}
264
+ logo={logo}
265
+ profile={isMobile ? undefined : profile}
266
+ center={top}
267
+ rightText={isMobile ? undefined : extraText}
268
+ right={
233
269
  <Fragment>
234
- {profile}
235
- {extraLinks}
236
- {!!extraText && (
237
- <InternalSideNavigationTrigger
238
- id="__extra_text"
239
- title={extraText}
240
- submenuExpanded={undefined}
241
- dataPrefix="navigation-extra-text"
242
- tag={undefined}
243
- icon={undefined}
244
- iconActive={undefined}
245
- />
246
- )}
270
+ {extraLinksTop}
271
+ {!isMobile && extraLinks}
247
272
  </Fragment>
248
- ) : undefined
249
- }
250
- />
273
+ }
274
+ isMobile={isMobile}
275
+ hasNotifications={hasNotifications || hasMenuNotifications}
276
+ onBurgerClick={onBurgerClick}
277
+ />
278
+ )}
251
279
 
252
- <LayoutContent header={header} anvil2={variant.isAnvil2}>
253
- {content}
254
- </LayoutContent>
280
+ {hasSideBar && (
281
+ <NotificationsContextProvider>
282
+ <LayoutSidebar
283
+ className={Styles.side}
284
+ mobile={breakpoint.isMobile}
285
+ barExpanded={!state?.navCollapsed}
286
+ onBarExpandChange={onBarExpandChange}
287
+ submenuExpanded={state?.submenuExpanded}
288
+ onSubmenuExpandChange={onSubmenuExpandChange}
289
+ drawerOpened={mobileDrawerOpened}
290
+ onDrawerOpenChange={setMobileDrawerOpened}
291
+ top={sideTop}
292
+ mainItems={navigationMainItems}
293
+ navigationComponent={context.NavigationComponent}
294
+ bottom={
295
+ isMobile ? (
296
+ <Fragment>
297
+ {profile}
298
+ {extraLinks}
299
+ {!!extraText && (
300
+ <InternalSideNavigationTrigger
301
+ id="__extra_text"
302
+ title={extraText}
303
+ submenuExpanded={undefined}
304
+ dataPrefix="navigation-extra-text"
305
+ tag={undefined}
306
+ icon={undefined}
307
+ iconActive={undefined}
308
+ />
309
+ )}
310
+ </Fragment>
311
+ ) : undefined
312
+ }
313
+ />
314
+ </NotificationsContextProvider>
315
+ )}
316
+
317
+ {variant.isSequent && (
318
+ <TitanLayoutHeaderObserved heightChange={updateIndicatorsHeight}>
319
+ {header}
320
+ </TitanLayoutHeaderObserved>
321
+ )}
322
+ {variant.isAnvil1 ? (
323
+ <LayoutContentAnvil1 header={header} minWidth={limitContentWidth}>
324
+ {content}
325
+ </LayoutContentAnvil1>
326
+ ) : variant.isLegacy ? (
327
+ <LayoutContentLegacy minWidth={limitContentWidth}>
328
+ {content}
329
+ </LayoutContentLegacy>
330
+ ) : (
331
+ children
332
+ )}
255
333
  </div>
256
334
  </LayoutPlacementContext.Provider>
257
335
  </LayoutContext.Provider>
@@ -287,7 +365,14 @@ const TitanLayoutHeaderObserved: FC<{
287
365
  };
288
366
  }, [heightChange]);
289
367
  return (
290
- <div ref={ref} className={Styles.header}>
368
+ <div ref={ref} className={Styles.contentHeader} data-cy="layout-content-header">
369
+ {children}
370
+ </div>
371
+ );
372
+ };
373
+ const TitanLayoutHeader: FC<{ children: ReactNode }> = ({ children }) => {
374
+ return (
375
+ <div className={Styles.contentHeader} data-cy="layout-content-header">
291
376
  {children}
292
377
  </div>
293
378
  );
@@ -298,27 +383,51 @@ export interface TitanLayoutContentProps {
298
383
  }
299
384
  const TitanLayoutContent: FC<TitanLayoutContentProps> = ({ children }) => children;
300
385
 
301
- const LayoutContent: FC<{ children: ReactNode; header?: ReactNode; anvil2: boolean }> = ({
302
- anvil2,
303
- children,
304
- header,
305
- }) => {
306
- const [anvil2Styles, setAnvil2Styles] = useState<object>({});
307
- const updateIndicatorsHeight = useCallback((offset: number) => {
308
- setAnvil2Styles({ '--offset': `calc(var(--nav-offset-top) + ${offset}px)` });
309
- }, []);
386
+ const LayoutContentAnvil1: FC<{
387
+ children: ReactNode;
388
+ header?: ReactNode;
389
+ minWidth?: number;
390
+ }> = ({ children, header, minWidth }) => {
391
+ const innerContentStyles: CSSProperties = useMemo(
392
+ () => ({
393
+ ...(minWidth ? { minWidth: `${minWidth}px` } : {}),
394
+ }),
395
+ [minWidth]
396
+ );
310
397
 
311
398
  return (
312
- <div className={Styles.content} style={anvil2Styles}>
313
- {!!header &&
314
- (anvil2 ? (
315
- <TitanLayoutHeaderObserved heightChange={updateIndicatorsHeight}>
316
- {header}
317
- </TitanLayoutHeaderObserved>
318
- ) : (
319
- header
320
- ))}
321
- {children}
399
+ <Fragment>
400
+ <TitanLayoutHeader>{header}</TitanLayoutHeader>
401
+ <div
402
+ className={classNames(Styles.content, { 'of-x-auto': !!minWidth })}
403
+ data-cy="layout-content"
404
+ >
405
+ <div
406
+ className="position-relative d-f flex-grow-1 flex-basis-0 of-hidden"
407
+ style={innerContentStyles}
408
+ >
409
+ {children}
410
+ </div>
411
+ </div>
412
+ </Fragment>
413
+ );
414
+ };
415
+
416
+ const LayoutContentLegacy: FC<{
417
+ children: ReactNode;
418
+ minWidth: number | undefined;
419
+ }> = ({ children, minWidth }) => {
420
+ const innerContentStyles: CSSProperties = useMemo(
421
+ () => ({
422
+ position: 'relative',
423
+ minWidth: `${minWidth}px`,
424
+ }),
425
+ [minWidth]
426
+ );
427
+
428
+ return (
429
+ <div className={minWidth ? 'of-x-auto flex-basis-0 flex-grow-1' : undefined}>
430
+ <div style={minWidth ? innerContentStyles : undefined}>{children}</div>
322
431
  </div>
323
432
  );
324
433
  };
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 }) => {
@@ -4,6 +4,7 @@ import { useMemo } from 'react';
4
4
  export interface TitanBreakpoint {
5
5
  name: BreakpointReturnProps['name'];
6
6
  isMobile: boolean;
7
+ width: number;
7
8
  }
8
9
 
9
10
  export const useTitanBreakpoint = (): TitanBreakpoint => {
@@ -12,7 +13,8 @@ export const useTitanBreakpoint = (): TitanBreakpoint => {
12
13
  return useMemo(
13
14
  () => ({
14
15
  name: breakpoint?.name ?? 'xl',
15
- isMobile: breakpoint ? breakpoint.innerWidth <= 768 : false,
16
+ isMobile: breakpoint ? breakpoint.innerWidth < 768 : false,
17
+ width: breakpoint?.innerWidth ?? 0,
16
18
  }),
17
19
  [breakpoint]
18
20
  );