@servicetitan/navigation 11.0.0-canary.237.0c461af.0 → 11.0.0-canary.237.1127df0.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 (73) 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.module.less +2 -0
  6. package/dist/components/titan-layout/layout-context.js +1 -1
  7. package/dist/components/titan-layout/layout-context.js.map +1 -1
  8. package/dist/components/titan-layout/layout-header.d.ts +2 -0
  9. package/dist/components/titan-layout/layout-header.d.ts.map +1 -1
  10. package/dist/components/titan-layout/layout-header.js +3 -4
  11. package/dist/components/titan-layout/layout-header.js.map +1 -1
  12. package/dist/components/titan-layout/layout-header.module.less +55 -36
  13. package/dist/components/titan-layout/layout-logo.d.ts.map +1 -1
  14. package/dist/components/titan-layout/layout-logo.js +2 -1
  15. package/dist/components/titan-layout/layout-logo.js.map +1 -1
  16. package/dist/components/titan-layout/layout-profile.d.ts.map +1 -1
  17. package/dist/components/titan-layout/layout-profile.js +29 -15
  18. package/dist/components/titan-layout/layout-profile.js.map +1 -1
  19. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts +2 -2
  20. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts.map +1 -1
  21. package/dist/components/titan-layout/layout-sidebar-links-internal.js +2 -2
  22. package/dist/components/titan-layout/layout-sidebar-links-internal.js.map +1 -1
  23. package/dist/components/titan-layout/layout-sidebar-links.d.ts.map +1 -1
  24. package/dist/components/titan-layout/layout-sidebar-links.js +10 -3
  25. package/dist/components/titan-layout/layout-sidebar-links.js.map +1 -1
  26. package/dist/components/titan-layout/layout-sidebar.d.ts +2 -0
  27. package/dist/components/titan-layout/layout-sidebar.d.ts.map +1 -1
  28. package/dist/components/titan-layout/layout-sidebar.js +6 -4
  29. package/dist/components/titan-layout/layout-sidebar.js.map +1 -1
  30. package/dist/components/titan-layout/layout-sidebar.module.less +25 -5
  31. package/dist/components/titan-layout/notifications-context.d.ts +13 -0
  32. package/dist/components/titan-layout/notifications-context.d.ts.map +1 -0
  33. package/dist/components/titan-layout/notifications-context.js +23 -0
  34. package/dist/components/titan-layout/notifications-context.js.map +1 -0
  35. package/dist/components/titan-layout/titan-layout.d.ts +5 -3
  36. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -1
  37. package/dist/components/titan-layout/titan-layout.js +73 -28
  38. package/dist/components/titan-layout/titan-layout.js.map +1 -1
  39. package/dist/components/titan-layout/titan-layout.module.less +9 -6
  40. package/dist/components/titan-layout/titan-layout.stories.d.ts +2 -0
  41. package/dist/components/titan-layout/titan-layout.stories.d.ts.map +1 -1
  42. package/dist/components/titan-layout/titan-layout.stories.js +8 -5
  43. package/dist/components/titan-layout/titan-layout.stories.js.map +1 -1
  44. package/dist/test/data.d.ts +4 -1
  45. package/dist/test/data.d.ts.map +1 -1
  46. package/dist/test/data.js +2 -3
  47. package/dist/test/data.js.map +1 -1
  48. package/dist/utils/use-breakpoint.d.ts +1 -0
  49. package/dist/utils/use-breakpoint.d.ts.map +1 -1
  50. package/dist/utils/use-breakpoint.js +3 -2
  51. package/dist/utils/use-breakpoint.js.map +1 -1
  52. package/package.json +2 -2
  53. package/src/components/badge-tag.tsx +1 -1
  54. package/src/components/logo/logo-titan-text.tsx +1 -1
  55. package/src/components/profile-dropdown/profile-dropdown.module.less +2 -0
  56. package/src/components/titan-layout/layout-context.tsx +1 -1
  57. package/src/components/titan-layout/layout-header.module.less +55 -36
  58. package/src/components/titan-layout/layout-header.module.less.d.ts +2 -0
  59. package/src/components/titan-layout/layout-header.tsx +12 -5
  60. package/src/components/titan-layout/layout-logo.tsx +13 -6
  61. package/src/components/titan-layout/layout-profile.tsx +71 -32
  62. package/src/components/titan-layout/layout-sidebar-links-internal.tsx +6 -3
  63. package/src/components/titan-layout/layout-sidebar-links.tsx +16 -3
  64. package/src/components/titan-layout/layout-sidebar.module.less +25 -5
  65. package/src/components/titan-layout/layout-sidebar.module.less.d.ts +1 -0
  66. package/src/components/titan-layout/layout-sidebar.tsx +14 -5
  67. package/src/components/titan-layout/notifications-context.tsx +44 -0
  68. package/src/components/titan-layout/titan-layout.module.less +9 -6
  69. package/src/components/titan-layout/titan-layout.module.less.d.ts +1 -0
  70. package/src/components/titan-layout/titan-layout.stories.tsx +13 -4
  71. package/src/components/titan-layout/titan-layout.tsx +203 -107
  72. package/src/test/data.tsx +2 -3
  73. package/src/utils/use-breakpoint.ts +3 -1
@@ -33,9 +33,11 @@ export interface LayoutSidebarProps {
33
33
  mainItems?: NavigationItemData[];
34
34
  barExpanded: boolean;
35
35
  submenuExpanded: string | undefined;
36
+ drawerOpened: boolean;
36
37
  mobile: boolean;
37
38
  navigationComponent: FC<NavLinkComponentProps>;
38
39
  onBarExpandChange(expanded: boolean): void;
40
+ onDrawerOpenChange(expanded: boolean): void;
39
41
  onSubmenuExpandChange(id: string, expanded: boolean): void;
40
42
  }
41
43
 
@@ -44,29 +46,36 @@ export const LayoutSidebar: FC<LayoutSidebarProps> = ({
44
46
  mobile,
45
47
  barExpanded,
46
48
  submenuExpanded,
49
+ drawerOpened,
47
50
  onBarExpandChange,
48
51
  onSubmenuExpandChange,
52
+ onDrawerOpenChange,
49
53
  mainItems,
50
54
  top,
51
55
  bottom,
52
56
  navigationComponent,
53
57
  }) => {
54
- const handleClick = (e: MouseEvent<never>) => {
55
- e.stopPropagation();
58
+ const closeDrawer = () => {
59
+ if (mobile) {
60
+ onDrawerOpenChange?.(false);
61
+ }
56
62
  };
57
63
 
58
64
  return (
59
65
  <LayoutPlacementContext.Provider value="side">
66
+ {mobile && drawerOpened && (
67
+ <div className={Styles.navDrawerBackdrop} onClick={closeDrawer} />
68
+ )}
60
69
  <div
61
70
  className={classNames(
62
71
  Styles.nav,
63
72
  mobile && Styles.navDrawer,
64
- mobile && barExpanded && Styles.navDrawerOpened,
73
+ mobile && drawerOpened && Styles.navDrawerOpened,
65
74
  !mobile && (barExpanded ? Styles.navWide : Styles.navSlim),
66
75
  className
67
76
  )}
68
77
  data-cy="side-navigation"
69
- onClick={handleClick}
78
+ onClick={closeDrawer}
70
79
  >
71
80
  <ThemeProvider mode="dark" className={Styles.navMain}>
72
81
  {mobile && (
@@ -86,7 +95,7 @@ export const LayoutSidebar: FC<LayoutSidebarProps> = ({
86
95
  item.submenu ? (
87
96
  <SideNavigationGroupItem
88
97
  key={item.id}
89
- barExpanded={barExpanded}
98
+ barExpanded={mobile ? drawerOpened : barExpanded}
90
99
  submenuExpanded={!!item.id && submenuExpanded === item.id}
91
100
  onSubmenuExpand={onSubmenuExpandChange}
92
101
  navigationComponent={navigationComponent}
@@ -0,0 +1,44 @@
1
+ import {
2
+ FC,
3
+ PropsWithChildren,
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+
11
+ interface NotificationsContextType {
12
+ onNotificationsUpdate: (id: string, hasNotifications: boolean) => void;
13
+ }
14
+
15
+ const NotificationsContext = createContext<NotificationsContextType>({
16
+ onNotificationsUpdate: () => {},
17
+ });
18
+
19
+ export const useNotificationsContext = () => useContext(NotificationsContext);
20
+
21
+ export const useNotificationsState = () => {
22
+ const [hasNotifications, setHasNotifications] = useState(false);
23
+ const items = useRef(new Set<string>());
24
+ const onNotificationsUpdate = useCallback((id: string, hasNotifications: boolean) => {
25
+ if (hasNotifications && !items.current.has(id)) {
26
+ items.current.add(id);
27
+ setHasNotifications(!!items.current.size);
28
+ } else if (!hasNotifications && items.current.has(id)) {
29
+ items.current.delete(id);
30
+ setHasNotifications(!!items.current.size);
31
+ }
32
+ }, []);
33
+
34
+ const NotificationsContextProvider: FC<PropsWithChildren> = useCallback(
35
+ ({ children }) => (
36
+ <NotificationsContext.Provider value={{ onNotificationsUpdate }}>
37
+ {children}
38
+ </NotificationsContext.Provider>
39
+ ),
40
+ [onNotificationsUpdate]
41
+ );
42
+
43
+ return { NotificationsContextProvider, hasNotifications };
44
+ };
@@ -53,18 +53,21 @@
53
53
 
54
54
  .layout-legacy,
55
55
  .layout-anvil2 {
56
- overflow-x: auto;
57
56
  .top-placeholder {
58
57
  height: var(--nav-offset-top);
59
58
  }
60
59
  }
61
60
 
62
61
  .layout {
63
- --nav-offset-top: 49px;
62
+ --nav-offset-top: 0px;
63
+ --nav-offset-left: 0px;
64
64
 
65
- &.layout-mobile {
66
- --nav-offset-top: 61px;
67
- --nav-offset-left: 0;
65
+ &.layout-desktop.layout-top {
66
+ --nav-offset-top: 48px;
67
+ }
68
+
69
+ &.layout-mobile.layout-top {
70
+ --nav-offset-top: 72px;
68
71
  }
69
72
 
70
73
  &.layout-desktop.layout-nav-slim {
@@ -77,9 +80,9 @@
77
80
 
78
81
  .top {
79
82
  position: fixed;
83
+ width: 100vw;
80
84
  top: 0;
81
85
  left: 0;
82
- right: 0;
83
86
  z-index: 991;
84
87
  }
85
88
 
@@ -9,6 +9,7 @@ export const layoutLegacy: string;
9
9
  export const layoutMobile: string;
10
10
  export const layoutNavSlim: string;
11
11
  export const layoutNavWide: string;
12
+ export const layoutTop: string;
12
13
  export const side: string;
13
14
  export const top: string;
14
15
  export const topPlaceholder: string;
@@ -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
 
@@ -116,6 +119,7 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
116
119
  appearance = 'anvil2',
117
120
  id,
118
121
  children,
122
+ contentOnly,
119
123
  navigationComponent,
120
124
  header,
121
125
  top,
@@ -130,7 +134,6 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
130
134
  sideTop,
131
135
  }) => {
132
136
  const breakpoint = useTitanBreakpoint();
133
- const isMobile = breakpoint.isMobile;
134
137
  const context: TitanLayoutContextType = useMemo(
135
138
  () => ({
136
139
  NavigationComponent: navigationComponent ?? DefaultNavLinkComponent,
@@ -143,26 +146,21 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
143
146
  const variant = useVariant(appearance);
144
147
  const [mobileDrawerOpened, setMobileDrawerOpened] = useState(false);
145
148
  const { content, logo } = useLayoutChildren(children);
149
+ const { hasNotifications, NotificationsContextProvider } = useNotificationsState();
150
+ const [anvil2Styles, setAnvil2Styles] = useState<object>({});
151
+ const updateIndicatorsHeight = useCallback((offset: number) => {
152
+ setAnvil2Styles({ '--offset': `calc(var(--nav-offset-top) + ${offset}px)` });
153
+ }, []);
146
154
 
147
- useEffect(() => {
148
- if (!isMobile) {
149
- setMobileDrawerOpened(false);
150
- return;
151
- }
152
-
153
- const listener = () => {
154
- setMobileDrawerOpened(false);
155
- };
156
-
157
- document.addEventListener('click', listener);
158
-
159
- return () => document.removeEventListener('click', listener);
160
- }, [isMobile]);
155
+ const isMobile = breakpoint.isMobile;
156
+ const hasSideBar = !contentOnly && (!!navigationMainItems?.length || !!sideTop?.length);
157
+ const hasTopBar = !contentOnly;
161
158
 
162
159
  useEffect(() => {
163
160
  if (variant.isAnvil1) {
164
- document.body.classList.add('of-hidden-i');
165
- return () => document.body.classList.remove('of-hidden');
161
+ const bodyClassName = 'of-hidden-i';
162
+ document.body.classList.add(bodyClassName);
163
+ return () => document.body.classList.remove(bodyClassName);
166
164
  }
167
165
  }, [variant.isAnvil1]);
168
166
 
@@ -190,6 +188,48 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
190
188
  },
191
189
  [state, onStateChange]
192
190
  );
191
+ const hasMenuNotifications = useMemo(() => {
192
+ try {
193
+ return (
194
+ navigationMainItems?.some(item => {
195
+ if (item.counter || item.tag?.value) {
196
+ return true;
197
+ } else if (item.submenu) {
198
+ return item.submenu.groups.some(group =>
199
+ group.links.some(link => !!link.counter || !!link.tag?.value)
200
+ );
201
+ }
202
+ return false;
203
+ }) ?? false
204
+ );
205
+ } catch {
206
+ return false;
207
+ }
208
+ }, [navigationMainItems]);
209
+
210
+ const limitContentWidth = useMemo(() => {
211
+ if (variant.isAnvil2 || !minContentWidth) {
212
+ return undefined;
213
+ }
214
+
215
+ if (breakpoint.width < minContentWidth) {
216
+ return minContentWidth;
217
+ }
218
+ }, [variant, minContentWidth, breakpoint.width]);
219
+
220
+ const contentStyles = useMemo(() => {
221
+ if (variant.isAnvil2) {
222
+ return anvil2Styles;
223
+ }
224
+
225
+ if (variant.isLegacy && limitContentWidth) {
226
+ return {
227
+ display: 'flex',
228
+ flexDirection: 'column',
229
+ minHeight: '100vh',
230
+ };
231
+ }
232
+ }, [variant, anvil2Styles, limitContentWidth]);
193
233
 
194
234
  const layoutClass = variant.isLegacy
195
235
  ? Styles.layoutLegacy
@@ -205,67 +245,88 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
205
245
  className={classNames(
206
246
  Styles.layout,
207
247
  isMobile ? Styles.layoutMobile : Styles.layoutDesktop,
208
- !isMobile && state?.navCollapsed
209
- ? Styles.layoutNavSlim
210
- : Styles.layoutNavWide,
248
+ {
249
+ [Styles.layoutTop]: hasTopBar,
250
+ [Styles.layoutNavSlim]: !isMobile && hasSideBar && state?.navCollapsed,
251
+ [Styles.layoutNavWide]: !isMobile && hasSideBar && !state?.navCollapsed,
252
+ },
211
253
  layoutClass
212
254
  )}
255
+ style={contentStyles}
213
256
  >
214
257
  {variant.isSequent && <div className={Styles.topPlaceholder} />}
215
- <LayoutHeader
216
- className={Styles.top}
217
- logo={logo}
218
- profile={isMobile ? undefined : profile}
219
- center={top}
220
- rightText={isMobile ? undefined : extraText}
221
- right={
222
- <Fragment>
223
- {extraLinksTop}
224
- {!isMobile && extraLinks}
225
- </Fragment>
226
- }
227
- onBurgerClick={onBurgerClick}
228
- />
229
-
230
- <LayoutSidebar
231
- className={Styles.side}
232
- mobile={breakpoint.isMobile}
233
- barExpanded={isMobile ? mobileDrawerOpened : !state?.navCollapsed}
234
- submenuExpanded={state?.submenuExpanded}
235
- onBarExpandChange={onBarExpandChange}
236
- onSubmenuExpandChange={onSubmenuExpandChange}
237
- top={sideTop}
238
- mainItems={navigationMainItems}
239
- navigationComponent={context.NavigationComponent}
240
- bottom={
241
- isMobile ? (
258
+ {hasTopBar && (
259
+ <LayoutHeader
260
+ className={Styles.top}
261
+ logo={logo}
262
+ profile={isMobile ? undefined : profile}
263
+ center={top}
264
+ rightText={isMobile ? undefined : extraText}
265
+ right={
242
266
  <Fragment>
243
- {profile}
244
- {extraLinks}
245
- {!!extraText && (
246
- <InternalSideNavigationTrigger
247
- id="__extra_text"
248
- title={extraText}
249
- submenuExpanded={undefined}
250
- dataPrefix="navigation-extra-text"
251
- tag={undefined}
252
- icon={undefined}
253
- iconActive={undefined}
254
- />
255
- )}
267
+ {extraLinksTop}
268
+ {!isMobile && extraLinks}
256
269
  </Fragment>
257
- ) : undefined
258
- }
259
- />
260
-
261
- <LayoutContent
262
- header={header}
263
- anvil2={variant.isAnvil2}
264
- anvil1={variant.isAnvil1}
265
- minWidth={minContentWidth}
266
- >
267
- {content}
268
- </LayoutContent>
270
+ }
271
+ isMobile={isMobile}
272
+ hasNotifications={hasNotifications || hasMenuNotifications}
273
+ onBurgerClick={onBurgerClick}
274
+ />
275
+ )}
276
+
277
+ {hasSideBar && (
278
+ <NotificationsContextProvider>
279
+ <LayoutSidebar
280
+ className={Styles.side}
281
+ mobile={breakpoint.isMobile}
282
+ barExpanded={!state?.navCollapsed}
283
+ onBarExpandChange={onBarExpandChange}
284
+ submenuExpanded={state?.submenuExpanded}
285
+ onSubmenuExpandChange={onSubmenuExpandChange}
286
+ drawerOpened={mobileDrawerOpened}
287
+ onDrawerOpenChange={setMobileDrawerOpened}
288
+ top={sideTop}
289
+ mainItems={navigationMainItems}
290
+ navigationComponent={context.NavigationComponent}
291
+ bottom={
292
+ isMobile ? (
293
+ <Fragment>
294
+ {profile}
295
+ {extraLinks}
296
+ {!!extraText && (
297
+ <InternalSideNavigationTrigger
298
+ id="__extra_text"
299
+ title={extraText}
300
+ submenuExpanded={undefined}
301
+ dataPrefix="navigation-extra-text"
302
+ tag={undefined}
303
+ icon={undefined}
304
+ iconActive={undefined}
305
+ />
306
+ )}
307
+ </Fragment>
308
+ ) : undefined
309
+ }
310
+ />
311
+ </NotificationsContextProvider>
312
+ )}
313
+
314
+ {variant.isAnvil1 ? (
315
+ <LayoutContentAnvil1 header={header} minWidth={limitContentWidth}>
316
+ {content}
317
+ </LayoutContentAnvil1>
318
+ ) : variant.isLegacy ? (
319
+ <LayoutContentLegacy header={header} minWidth={limitContentWidth}>
320
+ {content}
321
+ </LayoutContentLegacy>
322
+ ) : (
323
+ <LayoutContentAnvil2
324
+ header={header}
325
+ onHeaderHeightChange={updateIndicatorsHeight}
326
+ >
327
+ {content}
328
+ </LayoutContentAnvil2>
329
+ )}
269
330
  </div>
270
331
  </LayoutPlacementContext.Provider>
271
332
  </LayoutContext.Provider>
@@ -306,57 +367,92 @@ const TitanLayoutHeaderObserved: FC<{
306
367
  </div>
307
368
  );
308
369
  };
370
+ const TitanLayoutHeader: FC<{ children: ReactNode }> = ({ children }) => {
371
+ return (
372
+ <div className={Styles.contentHeader} data-cy="layout-content-header">
373
+ {children}
374
+ </div>
375
+ );
376
+ };
309
377
 
310
378
  export interface TitanLayoutContentProps {
311
379
  children: ReactNode;
312
380
  }
313
381
  const TitanLayoutContent: FC<TitanLayoutContentProps> = ({ children }) => children;
314
382
 
315
- const LayoutContent: FC<{
383
+ const LayoutContentAnvil2: FC<{
316
384
  children: ReactNode;
317
385
  header?: ReactNode;
318
- anvil1: boolean;
319
- anvil2: boolean;
320
- minWidth: number | undefined;
321
- }> = ({ anvil1, anvil2, children, header, minWidth }) => {
322
- const [anvil2Styles, setAnvil2Styles] = useState<object>({});
323
- const updateIndicatorsHeight = useCallback((offset: number) => {
324
- setAnvil2Styles({ '--offset': `calc(var(--nav-offset-top) + ${offset}px)` });
325
- }, []);
386
+ onHeaderHeightChange?: (height: number) => void;
387
+ }> = ({ children, header, onHeaderHeightChange }) => {
388
+ return (
389
+ <Fragment>
390
+ <TitanLayoutHeaderObserved heightChange={onHeaderHeightChange}>
391
+ {header}
392
+ </TitanLayoutHeaderObserved>
393
+ {children}
394
+ </Fragment>
395
+ );
396
+ };
326
397
 
327
- const contentStyles = useMemo(
398
+ const LayoutContentAnvil1: FC<{
399
+ children: ReactNode;
400
+ header?: ReactNode;
401
+ minWidth?: number;
402
+ }> = ({ children, header, minWidth }) => {
403
+ const innerContentStyles: CSSProperties = useMemo(
328
404
  () => ({
329
405
  ...(minWidth ? { minWidth: `${minWidth}px` } : {}),
330
- ...(anvil2 ? anvil2Styles : {}),
331
406
  }),
332
- [anvil2, minWidth, anvil2Styles]
407
+ [minWidth]
333
408
  );
334
409
 
335
410
  return (
336
411
  <Fragment>
337
- {!!header &&
338
- (anvil2 ? (
339
- <TitanLayoutHeaderObserved heightChange={updateIndicatorsHeight}>
340
- {header}
341
- </TitanLayoutHeaderObserved>
342
- ) : (
343
- <div className={Styles.contentHeader} data-cy="layout-content-header">
344
- {header}
345
- </div>
346
- ))}
347
- <div className={Styles.content} style={contentStyles} data-cy="layout-content">
348
- {anvil1 ? (
349
- <div className="position-relative d-f flex-grow-1 flex-basis-0 of-hidden">
350
- {children}
351
- </div>
352
- ) : (
353
- children
354
- )}
412
+ <TitanLayoutHeader>{header}</TitanLayoutHeader>
413
+ <div
414
+ className={classNames(Styles.content, { 'of-x-auto': !!minWidth })}
415
+ data-cy="layout-content"
416
+ >
417
+ <div
418
+ className="position-relative d-f flex-grow-1 flex-basis-0 of-hidden"
419
+ style={innerContentStyles}
420
+ >
421
+ {children}
422
+ </div>
355
423
  </div>
356
424
  </Fragment>
357
425
  );
358
426
  };
359
427
 
428
+ const LayoutContentLegacy: FC<{
429
+ children: ReactNode;
430
+ header?: ReactNode;
431
+ minWidth: number | undefined;
432
+ }> = ({ children, header, minWidth }) => {
433
+ const innerContentStyles: CSSProperties = useMemo(
434
+ () => ({
435
+ position: 'relative',
436
+ minWidth: `${minWidth}px`,
437
+ }),
438
+ [minWidth]
439
+ );
440
+
441
+ return (
442
+ <Fragment>
443
+ <TitanLayoutHeader>{header}</TitanLayoutHeader>
444
+
445
+ {minWidth ? (
446
+ <div className="of-x-auto flex-basis-0 flex-grow-1">
447
+ <div style={innerContentStyles}>{children}</div>
448
+ </div>
449
+ ) : (
450
+ children
451
+ )}
452
+ </Fragment>
453
+ );
454
+ };
455
+
360
456
  export const TitanLayout = Object.assign(TitanLayoutComponent, {
361
457
  Content: TitanLayoutContent,
362
458
  Logo: TitanLayoutLogo,
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
  );