@servicetitan/navigation 11.0.0-canary.237.6ce8e81.0 → 11.0.0-canary.237.7e21f65.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 (70) 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/logo/logo-titan-text.d.ts +1 -1
  4. package/dist/components/logo/logo-titan-text.d.ts.map +1 -1
  5. package/dist/components/profile-dropdown/profile-dropdown.d.ts +1 -0
  6. package/dist/components/profile-dropdown/profile-dropdown.d.ts.map +1 -1
  7. package/dist/components/profile-dropdown/profile-dropdown.js +2 -2
  8. package/dist/components/profile-dropdown/profile-dropdown.js.map +1 -1
  9. package/dist/components/titan-layout/layout-header.d.ts +2 -0
  10. package/dist/components/titan-layout/layout-header.d.ts.map +1 -1
  11. package/dist/components/titan-layout/layout-header.js +3 -4
  12. package/dist/components/titan-layout/layout-header.js.map +1 -1
  13. package/dist/components/titan-layout/layout-header.module.less +26 -2
  14. package/dist/components/titan-layout/layout-logo.d.ts.map +1 -1
  15. package/dist/components/titan-layout/layout-logo.js +2 -1
  16. package/dist/components/titan-layout/layout-logo.js.map +1 -1
  17. package/dist/components/titan-layout/layout-profile.d.ts.map +1 -1
  18. package/dist/components/titan-layout/layout-profile.js +20 -5
  19. package/dist/components/titan-layout/layout-profile.js.map +1 -1
  20. package/dist/components/titan-layout/layout-profile.stories.js +1 -1
  21. package/dist/components/titan-layout/layout-profile.stories.js.map +1 -1
  22. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts +2 -2
  23. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts.map +1 -1
  24. package/dist/components/titan-layout/layout-sidebar-links-internal.js +4 -4
  25. package/dist/components/titan-layout/layout-sidebar-links-internal.js.map +1 -1
  26. package/dist/components/titan-layout/layout-sidebar-links.d.ts.map +1 -1
  27. package/dist/components/titan-layout/layout-sidebar-links.js +9 -2
  28. package/dist/components/titan-layout/layout-sidebar-links.js.map +1 -1
  29. package/dist/components/titan-layout/layout-sidebar.d.ts +2 -0
  30. package/dist/components/titan-layout/layout-sidebar.d.ts.map +1 -1
  31. package/dist/components/titan-layout/layout-sidebar.js +6 -4
  32. package/dist/components/titan-layout/layout-sidebar.js.map +1 -1
  33. package/dist/components/titan-layout/layout-sidebar.module.less +16 -3
  34. package/dist/components/titan-layout/notifications-context.d.ts +13 -0
  35. package/dist/components/titan-layout/notifications-context.d.ts.map +1 -0
  36. package/dist/components/titan-layout/notifications-context.js +23 -0
  37. package/dist/components/titan-layout/notifications-context.js.map +1 -0
  38. package/dist/components/titan-layout/titan-layout.d.ts +6 -3
  39. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -1
  40. package/dist/components/titan-layout/titan-layout.js +42 -18
  41. package/dist/components/titan-layout/titan-layout.js.map +1 -1
  42. package/dist/components/titan-layout/titan-layout.module.less +30 -7
  43. package/dist/components/titan-layout/titan-layout.stories.d.ts +2 -0
  44. package/dist/components/titan-layout/titan-layout.stories.d.ts.map +1 -1
  45. package/dist/components/titan-layout/titan-layout.stories.js +8 -5
  46. package/dist/components/titan-layout/titan-layout.stories.js.map +1 -1
  47. package/dist/test/data.d.ts +4 -1
  48. package/dist/test/data.d.ts.map +1 -1
  49. package/dist/test/data.js +2 -3
  50. package/dist/test/data.js.map +1 -1
  51. package/package.json +2 -2
  52. package/src/components/badge-tag.tsx +1 -1
  53. package/src/components/logo/logo-titan-text.tsx +1 -1
  54. package/src/components/profile-dropdown/profile-dropdown.tsx +5 -1
  55. package/src/components/titan-layout/layout-header.module.less +26 -2
  56. package/src/components/titan-layout/layout-header.tsx +7 -4
  57. package/src/components/titan-layout/layout-logo.tsx +13 -6
  58. package/src/components/titan-layout/layout-profile.stories.tsx +1 -1
  59. package/src/components/titan-layout/layout-profile.tsx +42 -19
  60. package/src/components/titan-layout/layout-sidebar-links-internal.tsx +18 -5
  61. package/src/components/titan-layout/layout-sidebar-links.tsx +11 -2
  62. package/src/components/titan-layout/layout-sidebar.module.less +16 -3
  63. package/src/components/titan-layout/layout-sidebar.module.less.d.ts +1 -0
  64. package/src/components/titan-layout/layout-sidebar.tsx +14 -5
  65. package/src/components/titan-layout/notifications-context.tsx +44 -0
  66. package/src/components/titan-layout/titan-layout.module.less +30 -7
  67. package/src/components/titan-layout/titan-layout.module.less.d.ts +2 -1
  68. package/src/components/titan-layout/titan-layout.stories.tsx +13 -4
  69. package/src/components/titan-layout/titan-layout.tsx +122 -76
  70. package/src/test/data.tsx +2 -3
@@ -27,6 +27,8 @@ interface LayoutContentArgs {
27
27
  search: boolean;
28
28
  longContent: boolean;
29
29
  wideContent: boolean;
30
+ minWidth: boolean;
31
+ emptyNav: boolean;
30
32
  }
31
33
 
32
34
  export default {
@@ -41,6 +43,8 @@ export default {
41
43
  search: true,
42
44
  longContent: true,
43
45
  wideContent: false,
46
+ minWidth: false,
47
+ emptyNav: false,
44
48
  } as LayoutContentArgs,
45
49
  };
46
50
 
@@ -68,6 +72,7 @@ const profile = (
68
72
  to="https://google.com"
69
73
  tooltip="Google it"
70
74
  target="_blank"
75
+ tag={{ value: true }}
71
76
  >
72
77
  first link
73
78
  </ProfileDropdown.Link>
@@ -180,7 +185,9 @@ const ContentHeader = () => {
180
185
  </Fragment>
181
186
  );
182
187
  };
183
- const SearchBar = () => <TextField size="small" placeholder="Search" className="w-100-i" />;
188
+ const SearchBar = () => (
189
+ <TextField size="small" placeholder="Search" className="w-100-i m-x-half-i" />
190
+ );
184
191
 
185
192
  const useLayoutProps = (args: LayoutContentArgs): TitanLayoutProps => {
186
193
  const [state, setState] = useState<TitanLayoutState | undefined>(undefined);
@@ -190,7 +197,7 @@ const useLayoutProps = (args: LayoutContentArgs): TitanLayoutProps => {
190
197
  onStateChange: setState,
191
198
 
192
199
  navigationComponent: NavLinkMock,
193
- navigationMainItems: mainNavItems,
200
+ navigationMainItems: args.emptyNav ? [] : mainNavItems,
194
201
 
195
202
  profile,
196
203
  top: args.search ? <SearchBar /> : undefined,
@@ -200,14 +207,16 @@ const useLayoutProps = (args: LayoutContentArgs): TitanLayoutProps => {
200
207
  extraLinksTop,
201
208
  extraText: args.extraText ? 'EST (-8 hrs)' : undefined,
202
209
 
203
- sideTop: args.sideTop ? sidebarTop() : undefined,
210
+ sideTop: args.sideTop && !args.emptyNav ? sidebarTop() : undefined,
211
+
212
+ minContentWidth: args.minWidth ? 900 : undefined,
204
213
  };
205
214
  };
206
215
 
207
216
  const Content = (args: LayoutContentArgs) => {
208
217
  return (
209
218
  <Fragment>
210
- <LocationInfo />
219
+ <LocationInfo className="m-b-3" />
211
220
  {args.wideContent && (
212
221
  <div style={{ width: '1200px' }}>
213
222
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
@@ -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,19 @@ 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
+
151
+ const isMobile = breakpoint.isMobile;
152
+ const hasSideBar = !contentOnly && (!!navigationMainItems?.length || !!sideTop?.length);
153
+ const hasTopBar = !contentOnly;
144
154
 
145
155
  useEffect(() => {
146
- if (!isMobile) {
147
- setMobileDrawerOpened(false);
148
- return;
156
+ if (variant.isAnvil1) {
157
+ const bodyClassName = 'of-hidden-i';
158
+ document.body.classList.add(bodyClassName);
159
+ return () => document.body.classList.remove(bodyClassName);
149
160
  }
150
-
151
- const listener = () => {
152
- setMobileDrawerOpened(false);
153
- };
154
-
155
- document.addEventListener('click', listener);
156
-
157
- return () => document.removeEventListener('click', listener);
158
- }, [isMobile]);
161
+ }, [variant.isAnvil1]);
159
162
 
160
163
  const onBurgerClick = useCallback((e: MouseEvent) => {
161
164
  setMobileDrawerOpened(true);
@@ -181,6 +184,24 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
181
184
  },
182
185
  [state, onStateChange]
183
186
  );
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]);
184
205
 
185
206
  const layoutClass = variant.isLegacy
186
207
  ? Styles.layoutLegacy
@@ -196,63 +217,75 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
196
217
  className={classNames(
197
218
  Styles.layout,
198
219
  isMobile ? Styles.layoutMobile : Styles.layoutDesktop,
199
- !isMobile && state?.navCollapsed
200
- ? Styles.layoutNavSlim
201
- : Styles.layoutNavWide,
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
227
  >
205
228
  {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 ? (
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={
233
237
  <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
- )}
238
+ {extraLinksTop}
239
+ {!isMobile && extraLinks}
247
240
  </Fragment>
248
- ) : undefined
249
- }
250
- />
241
+ }
242
+ isMobile={isMobile}
243
+ hasNotifications={hasNotifications || hasMenuNotifications}
244
+ onBurgerClick={onBurgerClick}
245
+ />
246
+ )}
251
247
 
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
+ )}
252
284
  <LayoutContent
253
285
  header={header}
254
286
  anvil2={variant.isAnvil2}
255
287
  anvil1={variant.isAnvil1}
288
+ minWidth={minContentWidth}
256
289
  >
257
290
  {content}
258
291
  </LayoutContent>
@@ -291,7 +324,7 @@ const TitanLayoutHeaderObserved: FC<{
291
324
  };
292
325
  }, [heightChange]);
293
326
  return (
294
- <div ref={ref} className={Styles.header}>
327
+ <div ref={ref} className={Styles.contentHeader} data-cy="layout-content-header">
295
328
  {children}
296
329
  </div>
297
330
  );
@@ -307,30 +340,43 @@ const LayoutContent: FC<{
307
340
  header?: ReactNode;
308
341
  anvil1: boolean;
309
342
  anvil2: boolean;
310
- }> = ({ anvil1, anvil2, children, header }) => {
343
+ minWidth: number | undefined;
344
+ }> = ({ anvil1, anvil2, children, header, minWidth }) => {
311
345
  const [anvil2Styles, setAnvil2Styles] = useState<object>({});
312
346
  const updateIndicatorsHeight = useCallback((offset: number) => {
313
347
  setAnvil2Styles({ '--offset': `calc(var(--nav-offset-top) + ${offset}px)` });
314
348
  }, []);
315
349
 
350
+ const contentStyles = useMemo(
351
+ () => ({
352
+ ...(minWidth ? { minWidth: `${minWidth}px` } : {}),
353
+ ...(anvil2 ? anvil2Styles : {}),
354
+ }),
355
+ [anvil2, minWidth, anvil2Styles]
356
+ );
357
+
316
358
  return (
317
- <div className={Styles.content} style={anvil2Styles}>
359
+ <Fragment>
318
360
  {!!header &&
319
361
  (anvil2 ? (
320
362
  <TitanLayoutHeaderObserved heightChange={updateIndicatorsHeight}>
321
363
  {header}
322
364
  </TitanLayoutHeaderObserved>
323
365
  ) : (
324
- header
366
+ <div className={Styles.contentHeader} data-cy="layout-content-header">
367
+ {header}
368
+ </div>
325
369
  ))}
326
- {anvil1 ? (
327
- <div className="position-relative d-f flex-grow-1 flex-basis-0 of-hidden">
328
- {children}
329
- </div>
330
- ) : (
331
- children
332
- )}
333
- </div>
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>
334
380
  );
335
381
  };
336
382
 
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 }) => {