@servicetitan/navigation 11.0.0-canary.237.0ce6038.0 → 11.0.0-canary.237.4786284.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 (52) hide show
  1. package/dist/components/header-navigation/header-navigation-stacked.stories.js +1 -1
  2. package/dist/components/header-navigation/header-navigation-stacked.stories.js.map +1 -1
  3. package/dist/components/header-navigation/header-navigation.stories.js +1 -1
  4. package/dist/components/header-navigation/header-navigation.stories.js.map +1 -1
  5. package/dist/components/left-navigation/header-navigation-tiny.stories.js +2 -2
  6. package/dist/components/left-navigation/header-navigation-tiny.stories.js.map +1 -1
  7. package/dist/components/profile-dropdown/profile-dropdown.d.ts +10 -7
  8. package/dist/components/profile-dropdown/profile-dropdown.d.ts.map +1 -1
  9. package/dist/components/profile-dropdown/profile-dropdown.js +2 -2
  10. package/dist/components/profile-dropdown/profile-dropdown.js.map +1 -1
  11. package/dist/components/titan-layout/layout-header.d.ts.map +1 -1
  12. package/dist/components/titan-layout/layout-header.js +1 -1
  13. package/dist/components/titan-layout/layout-header.js.map +1 -1
  14. package/dist/components/titan-layout/layout-header.module.less +36 -16
  15. package/dist/components/titan-layout/layout-profile.d.ts.map +1 -1
  16. package/dist/components/titan-layout/layout-profile.js +14 -4
  17. package/dist/components/titan-layout/layout-profile.js.map +1 -1
  18. package/dist/components/titan-layout/layout-profile.stories.d.ts.map +1 -1
  19. package/dist/components/titan-layout/layout-profile.stories.js +1 -1
  20. package/dist/components/titan-layout/layout-profile.stories.js.map +1 -1
  21. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts.map +1 -1
  22. package/dist/components/titan-layout/layout-sidebar-links-internal.js +2 -2
  23. package/dist/components/titan-layout/layout-sidebar-links-internal.js.map +1 -1
  24. package/dist/components/titan-layout/layout-sidebar-links.d.ts.map +1 -1
  25. package/dist/components/titan-layout/layout-sidebar-links.js +2 -2
  26. package/dist/components/titan-layout/layout-sidebar-links.js.map +1 -1
  27. package/dist/components/titan-layout/layout-sidebar.module.less +4 -1
  28. package/dist/components/titan-layout/titan-layout.d.ts +5 -3
  29. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -1
  30. package/dist/components/titan-layout/titan-layout.js +45 -11
  31. package/dist/components/titan-layout/titan-layout.js.map +1 -1
  32. package/dist/components/titan-layout/titan-layout.module.less +56 -16
  33. package/dist/components/titan-layout/titan-layout.stories.d.ts +13 -11
  34. package/dist/components/titan-layout/titan-layout.stories.d.ts.map +1 -1
  35. package/dist/components/titan-layout/titan-layout.stories.js +30 -12
  36. package/dist/components/titan-layout/titan-layout.stories.js.map +1 -1
  37. package/package.json +2 -2
  38. package/src/components/header-navigation/header-navigation-stacked.stories.tsx +1 -1
  39. package/src/components/header-navigation/header-navigation.stories.tsx +1 -1
  40. package/src/components/left-navigation/header-navigation-tiny.stories.tsx +2 -2
  41. package/src/components/profile-dropdown/profile-dropdown.tsx +13 -6
  42. package/src/components/titan-layout/layout-header.module.less +36 -16
  43. package/src/components/titan-layout/layout-header.tsx +5 -1
  44. package/src/components/titan-layout/layout-profile.stories.tsx +10 -1
  45. package/src/components/titan-layout/layout-profile.tsx +24 -8
  46. package/src/components/titan-layout/layout-sidebar-links-internal.tsx +12 -2
  47. package/src/components/titan-layout/layout-sidebar-links.tsx +5 -2
  48. package/src/components/titan-layout/layout-sidebar.module.less +4 -1
  49. package/src/components/titan-layout/titan-layout.module.less +56 -16
  50. package/src/components/titan-layout/titan-layout.module.less.d.ts +3 -0
  51. package/src/components/titan-layout/titan-layout.stories.tsx +156 -18
  52. package/src/components/titan-layout/titan-layout.tsx +101 -18
@@ -2,7 +2,7 @@ import SvgAccountActive from '@servicetitan/anvil2/assets/icons/st/gnav_account_
2
2
  import SvgAccountInactive from '@servicetitan/anvil2/assets/icons/st/gnav_account_inactive.svg';
3
3
 
4
4
  import { FC, useState } from 'react';
5
- import { NavigationComponentContext } from '../../utils/navigation-context';
5
+ import { NavLinkComponentProps, NavigationComponentContext } from '../../utils/navigation-context';
6
6
  import {
7
7
  ProfileDropdown as DesktopProfileDropdown,
8
8
  ProfileDropdownLinkProps,
@@ -24,6 +24,10 @@ export type {
24
24
  ProfileDropdownLinkProps,
25
25
  } from '../profile-dropdown/profile-dropdown';
26
26
 
27
+ const ExternalNavComponent: FC<NavLinkComponentProps> = ({ children, isActive, ...props }) => (
28
+ <a {...props}>{children}</a>
29
+ );
30
+
27
31
  const ProfileDropdownContent: FC<ProfileDropdownProps> = props => {
28
32
  const { breakpoint, NavigationComponent } = useTitanLayoutContext();
29
33
  return breakpoint.isMobile ? (
@@ -70,6 +74,18 @@ const ProfileDropdownDivider: FC = () => {
70
74
  );
71
75
  };
72
76
 
77
+ const getText = (props: any): string | undefined => {
78
+ if (typeof props.children === 'string') {
79
+ return props.children;
80
+ }
81
+
82
+ if (typeof props.text === 'string') {
83
+ return props.text;
84
+ }
85
+
86
+ return undefined;
87
+ };
88
+
73
89
  const ProfileDropdownSection: FC<ProfileDropdownSectionProps> = props => {
74
90
  const { breakpoint } = useTitanLayoutContext();
75
91
  return breakpoint.isMobile ? (
@@ -79,9 +95,9 @@ const ProfileDropdownSection: FC<ProfileDropdownSectionProps> = props => {
79
95
  );
80
96
  };
81
97
  const MobileProfileDropdownSection: FC<ProfileDropdownSectionProps> = props => {
82
- const title = typeof props.children === 'string' ? props.children : undefined;
83
- return title ? (
84
- <InternalSideNavigationGroupTrigger id={props.id} title={title} onClick={props.onClick} />
98
+ const text = getText(props);
99
+ return text ? (
100
+ <InternalSideNavigationGroupTrigger id={props.id} title={text} onClick={props.onClick} />
85
101
  ) : null;
86
102
  };
87
103
 
@@ -98,13 +114,13 @@ const MobileProfileDropdownLink: FC<ProfileDropdownLinkProps & NavigationCompone
98
114
  navigationComponent,
99
115
  ...props
100
116
  }) => {
101
- const title = typeof props.children === 'string' ? props.children : undefined;
102
- return title ? (
117
+ const text = getText(props);
118
+ return text ? (
103
119
  <InternalSideNavigationGroupLink
104
120
  {...props}
105
121
  to={to}
106
- title={title}
107
- navigationComponent={navigationComponent}
122
+ title={text}
123
+ navigationComponent={props.external ? ExternalNavComponent : navigationComponent}
108
124
  />
109
125
  ) : null;
110
126
  };
@@ -180,12 +180,22 @@ export const InternalSideNavigationTrigger: FC<
180
180
 
181
181
  export const InternalSideNavigationGroupLink: FC<
182
182
  NavigationSubmenuItemData & NavigationComponentProps
183
- > = ({ id, counter, tag, title, to, isActive, navigationComponent: NavigationComponent }) => {
183
+ > = ({
184
+ id,
185
+ counter,
186
+ tag,
187
+ title,
188
+ to,
189
+ isActive,
190
+ navigationComponent: NavigationComponent,
191
+ ...rest
192
+ }) => {
184
193
  return (
185
194
  <NavigationComponent
195
+ key={id}
186
196
  data-cy={`navigation-item-${id}`}
187
197
  data-pendo={`navigation-item-${id}`}
188
- key={id}
198
+ {...rest}
189
199
  to={to}
190
200
  className={classNames(Styles.submenuItem, Styles.submenuLink, {
191
201
  [Styles.submenuLinkActive]: isActive === true,
@@ -17,7 +17,10 @@ const WrappedLink: FC<{
17
17
 
18
18
  /** Side Navigation menu link */
19
19
  export function TitanLayoutSidebarLink({ wrapper, ...props }: TitanLayoutSidebarLinkProps) {
20
- const { NavigationComponent } = useTitanLayoutContext();
20
+ const {
21
+ NavigationComponent,
22
+ breakpoint: { isMobile },
23
+ } = useTitanLayoutContext();
21
24
 
22
25
  const element = (
23
26
  <InternalSideNavigationLink
@@ -29,7 +32,7 @@ export function TitanLayoutSidebarLink({ wrapper, ...props }: TitanLayoutSidebar
29
32
  />
30
33
  );
31
34
 
32
- return wrapper ? <WrappedLink wrapper={wrapper}>{element}</WrappedLink> : element;
35
+ return wrapper && !isMobile ? <WrappedLink wrapper={wrapper}>{element}</WrappedLink> : element;
33
36
  }
34
37
 
35
38
  /** Side Navigation menu trigger */
@@ -141,7 +141,6 @@
141
141
  // desktop version expanded
142
142
  .nav-wide {
143
143
  width: var(--nav-offset-left);
144
- padding-top: @spacing-2;
145
144
 
146
145
  .toggle {
147
146
  .toggle-content {
@@ -162,6 +161,10 @@
162
161
  margin-top: @spacing-1;
163
162
  margin-bottom: @spacing-1;
164
163
  }
164
+
165
+ .nav-main {
166
+ padding-top: @spacing-1;
167
+ }
165
168
  }
166
169
 
167
170
  .nav-drawer,
@@ -13,7 +13,7 @@
13
13
  margin-top: var(--nav-offset-top);
14
14
 
15
15
  display: flex;
16
- flex-direction: row;
16
+ flex-direction: column;
17
17
 
18
18
  .side {
19
19
  flex-basis: var(--nav-offset-left);
@@ -26,27 +26,45 @@
26
26
 
27
27
  display: flex;
28
28
  flex-direction: column;
29
- overflow: hidden;
29
+ overflow-y: hidden;
30
+ overflow-x: auto;
31
+
32
+ .content-page {
33
+ position: relative;
34
+ display: flex;
35
+ flex-direction: column;
36
+ flex-grow: 1;
37
+ flex-basis: 0;
38
+ overflow-y: hidden;
39
+ }
30
40
  }
31
41
  }
32
42
 
33
- .layout-legacy,
34
43
  .layout-anvil2 {
44
+ .header {
45
+ position: sticky;
46
+ top: var(--nav-offset-top);
47
+ z-index: 989;
48
+ }
49
+ }
50
+
51
+ .layout-desktop {
35
52
  padding-left: var(--nav-offset-left);
36
53
 
37
- .top-placeholder {
38
- height: var(--nav-offset-top);
54
+ .side {
55
+ position: fixed;
56
+ top: var(--nav-offset-top);
57
+ bottom: 0;
58
+ left: 0;
59
+ right: 0;
60
+ z-index: @z-index-global-nav;
39
61
  }
62
+ }
40
63
 
41
- &:not(.layout-mobile) {
42
- .side {
43
- position: fixed;
44
- top: var(--nav-offset-top);
45
- bottom: 0;
46
- left: 0;
47
- right: 0;
48
- z-index: @z-index-global-nav;
49
- }
64
+ .layout-legacy,
65
+ .layout-anvil2 {
66
+ .top-placeholder {
67
+ height: var(--nav-offset-top);
50
68
  }
51
69
  }
52
70
 
@@ -58,11 +76,11 @@
58
76
  --nav-offset-left: 0;
59
77
  }
60
78
 
61
- &.layout-nav-slim {
79
+ &.layout-desktop.layout-nav-slim {
62
80
  --nav-offset-left: 64px;
63
81
  }
64
82
 
65
- &.layout-nav-wide {
83
+ &.layout-desktop.layout-nav-wide {
66
84
  --nav-offset-left: 212px;
67
85
  }
68
86
 
@@ -73,4 +91,26 @@
73
91
  right: 0;
74
92
  z-index: 991;
75
93
  }
94
+
95
+ .content {
96
+ overflow-x: auto;
97
+
98
+ .header {
99
+ position: sticky;
100
+ left: 0;
101
+ right: 0;
102
+ }
103
+ }
104
+ }
105
+
106
+ @media print {
107
+ .layout {
108
+ --nav-offset-left: 0px !important;
109
+ --nav-offset-right: 0px !important;
110
+
111
+ .side,
112
+ .top {
113
+ display: none !important;
114
+ }
115
+ }
76
116
  }
@@ -1,8 +1,11 @@
1
1
  export const __esModule: true;
2
2
  export const content: string;
3
+ export const contentPage: string;
4
+ export const header: string;
3
5
  export const layout: string;
4
6
  export const layoutAnvil1: string;
5
7
  export const layoutAnvil2: string;
8
+ export const layoutDesktop: string;
6
9
  export const layoutLegacy: string;
7
10
  export const layoutMobile: string;
8
11
  export const layoutNavSlim: string;
@@ -1,4 +1,4 @@
1
- import { Page as Anvil2Page, Popover } from '@servicetitan/anvil2';
1
+ import { Announcement, Page as Anvil2Page, Button, Popover, TextField } from '@servicetitan/anvil2';
2
2
  import SvgSearch from '@servicetitan/anvil2/assets/icons/material/round/search.svg';
3
3
  import SvgAtlas from '@servicetitan/anvil2/assets/icons/st/atlas_logo.svg';
4
4
  import SvgSettingsActive from '@servicetitan/anvil2/assets/icons/st/gnav_settings_active.svg';
@@ -20,11 +20,28 @@ import { SideNavigationLinkWrapperProps } from '../left-navigation';
20
20
  import { HeaderNavigationLink, HeaderNavigationTrigger } from '../links';
21
21
  import { ProfileDropdown, TitanLayout, TitanLayoutProps, TitanLayoutState } from './';
22
22
 
23
+ interface LayoutContentArgs {
24
+ header: boolean;
25
+ sideTop: boolean;
26
+ extraText: boolean;
27
+ search: boolean;
28
+ longContent: boolean;
29
+ wideContent: boolean;
30
+ }
31
+
23
32
  export default {
24
33
  title: 'Navigation/TitanLayout',
25
- component: TitanLayout,
26
34
  decorators: [withDefaultRedirects, withMemoryRouter, withAnvil],
27
35
  parameters: {},
36
+ argTypes: {},
37
+ args: {
38
+ header: true,
39
+ sideTop: true,
40
+ extraText: true,
41
+ search: true,
42
+ longContent: true,
43
+ wideContent: false,
44
+ } as LayoutContentArgs,
28
45
  };
29
46
 
30
47
  const mainNavItems = [
@@ -131,9 +148,9 @@ const SideLinkPopoverWrapper: FC<SideNavigationLinkWrapperProps> = ({ children,
131
148
  };
132
149
 
133
150
  const sidebarTop = () => [
134
- <TitanLayout.SidebarLink key="tasks" {...items.tasks} />,
135
- <TitanLayout.SidebarLink key="calls" {...items.calls} />,
136
- <TitanLayout.SidebarTrigger
151
+ <TitanLayout.Link key="tasks" {...items.tasks} />,
152
+ <TitanLayout.Link key="calls" {...items.calls} />,
153
+ <TitanLayout.Trigger
137
154
  key="marketing"
138
155
  {...items.marketing}
139
156
  isActive={false}
@@ -142,51 +159,172 @@ const sidebarTop = () => [
142
159
  counter={50}
143
160
  />,
144
161
  ];
145
- const useLayoutProps = (): TitanLayoutProps => {
162
+ const ContentHeader = () => {
163
+ const [longInfo, setLongInfo] = useState(false);
164
+
165
+ return (
166
+ <Fragment>
167
+ <Announcement title="Some info" status="info" />
168
+ <Announcement title="Some warning" status="warning" />
169
+ <div
170
+ className="d-f justify-content-center align-items-center bg-purple-100-i"
171
+ style={{ height: longInfo ? '120px' : '48px' }}
172
+ >
173
+ <div className="d-f align-items-center gap-1">
174
+ custom content{' '}
175
+ <Button onClick={() => setLongInfo(!longInfo)} size="small" aria-label="test">
176
+ {longInfo ? '↑' : '↓'}
177
+ </Button>
178
+ </div>
179
+ </div>
180
+ </Fragment>
181
+ );
182
+ };
183
+ const SearchBar = () => <TextField size="small" placeholder="Search" className="w-100-i" />;
184
+
185
+ const useLayoutProps = (args: LayoutContentArgs): TitanLayoutProps => {
146
186
  const [state, setState] = useState<TitanLayoutState | undefined>(undefined);
147
187
 
148
188
  return {
149
189
  state,
150
190
  onStateChange: setState,
151
191
 
192
+ navigationComponent: NavLinkMock,
152
193
  navigationMainItems: mainNavItems,
194
+
153
195
  profile,
196
+ top: args.search ? <SearchBar /> : undefined,
197
+ header: args.header ? <ContentHeader /> : undefined,
198
+
154
199
  extraLinks,
155
200
  extraLinksTop,
201
+ extraText: args.extraText ? 'EST (-8 hrs)' : undefined,
156
202
 
157
- sidebarTop: sidebarTop(),
158
- navigationComponent: NavLinkMock,
159
- extraText: 'EST (-8 hrs)',
203
+ sideTop: args.sideTop ? sidebarTop() : undefined,
160
204
  };
161
205
  };
162
206
 
163
- export const TitanLayoutLegacy = () => (
164
- <TitanLayout {...useLayoutProps()} appearance="legacy">
207
+ const Content = (args: LayoutContentArgs) => {
208
+ return (
209
+ <Fragment>
210
+ <LocationInfo />
211
+ {args.wideContent && (
212
+ <div style={{ width: '1200px' }}>
213
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
214
+ incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
215
+ nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
216
+ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
217
+ fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
218
+ culpa qui officia deserunt mollit anim id est laborum.
219
+ </div>
220
+ )}
221
+
222
+ {args.longContent && (
223
+ <div>
224
+ <p>Lorem</p>
225
+ <p>ipsum</p>
226
+ <p>dolor</p>
227
+ <p>sit</p>
228
+ <p>amet,</p>
229
+ <p>consectetur</p>
230
+ <p>adipiscing</p>
231
+ <p>elit,</p>
232
+ <p>sed</p>
233
+ <p>do</p>
234
+ <p>eiusmod</p>
235
+ <p>tempor</p>
236
+ <p>incididunt</p>
237
+ <p>ut</p>
238
+ <p>labore</p>
239
+ <p>et</p>
240
+ <p>dolore</p>
241
+ <p>magna</p>
242
+ <p>aliqua.</p>
243
+ <p>Ut</p>
244
+ <p>enim</p>
245
+ <p>ad</p>
246
+ <p>minim</p>
247
+ <p>veniam,</p>
248
+ <p>quis</p>
249
+ <p>nostrud</p>
250
+ <p>exercitation</p>
251
+ <p>ullamco</p>
252
+ <p>laboris</p>
253
+ <p>nisi</p>
254
+ <p>ut</p>
255
+ <p>aliquip</p>
256
+ <p>ex</p>
257
+ <p>ea</p>
258
+ <p>commodo</p>
259
+ <p>consequat.</p>
260
+ <p>Duis</p>
261
+ <p>aute</p>
262
+ <p>irure</p>
263
+ <p>dolor</p>
264
+ <p>in</p>
265
+ <p>reprehenderit</p>
266
+ <p>in</p>
267
+ <p>voluptate</p>
268
+ <p>velit</p>
269
+ <p>esse</p>
270
+ <p>cillum</p>
271
+ <p>dolore</p>
272
+ <p>eu</p>
273
+ <p>fugiat</p>
274
+ <p>nulla</p>
275
+ <p>pariatur.</p>
276
+ <p>Excepteur</p>
277
+ <p>sint</p>
278
+ <p>occaecat</p>
279
+ <p>cupidatat</p>
280
+ <p>non</p>
281
+ <p>proident,</p>
282
+ <p>sunt</p>
283
+ <p>in</p>
284
+ <p>culpa</p>
285
+ <p>qui</p>
286
+ <p>officia</p>
287
+ <p>deserunt</p>
288
+ <p>mollit</p>
289
+ <p>anim</p>
290
+ <p>id</p>
291
+ <p>est</p>
292
+ <p>laborum.</p>
293
+ </div>
294
+ )}
295
+ </Fragment>
296
+ );
297
+ };
298
+
299
+ export const TitanLayoutLegacy = (args: LayoutContentArgs) => (
300
+ <TitanLayout {...useLayoutProps(args)} appearance="legacy">
165
301
  <TitanLayout.Logo title />
166
302
  <TitanLayout.Content>
167
- <LocationInfo />
303
+ <div className="p-3">
304
+ <Content {...args} />
305
+ </div>
168
306
  </TitanLayout.Content>
169
307
  </TitanLayout>
170
308
  );
171
309
 
172
- export const TitanLayoutAnvil1 = () => (
173
- <TitanLayout {...useLayoutProps()} appearance="anvil1">
310
+ export const TitanLayoutAnvil1 = (args: LayoutContentArgs) => (
311
+ <TitanLayout {...useLayoutProps(args)} appearance="anvil1">
174
312
  <TitanLayout.Logo title />
175
313
  <TitanLayout.Content>
176
314
  <Anvil1Page>
177
- <LocationInfo />
315
+ <Content {...args} />
178
316
  </Anvil1Page>
179
317
  </TitanLayout.Content>
180
318
  </TitanLayout>
181
319
  );
182
320
 
183
- export const TitanLayoutAnvil2 = () => (
184
- <TitanLayout {...useLayoutProps()} appearance="anvil2">
321
+ export const TitanLayoutAnvil2 = (args: LayoutContentArgs) => (
322
+ <TitanLayout {...useLayoutProps(args)} appearance="anvil2">
185
323
  <TitanLayout.Logo title />
186
324
  <TitanLayout.Content>
187
325
  <Anvil2Page>
188
326
  <Anvil2Page.Content>
189
- <LocationInfo />
327
+ <Content {...args} />
190
328
  </Anvil2Page.Content>
191
329
  </Anvil2Page>
192
330
  </TitanLayout.Content>
@@ -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';
@@ -50,11 +51,13 @@ export type TitanLayoutProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'
50
51
  onStateChange?: (state: TitanLayoutState) => void;
51
52
 
52
53
  header?: ReactElement;
54
+ top?: ReactElement;
55
+ sideTop?: ReactElement[];
53
56
  profile?: ReactElement;
54
57
  extraLinks?: ReactElement;
55
58
  extraLinksTop?: ReactElement;
56
59
  extraText?: string;
57
- sidebarTop?: ReactElement[];
60
+ minContentWidth?: number;
58
61
  };
59
62
 
60
63
  const defaultSidebarContext: TitanLayoutSidebarContextType = {
@@ -115,6 +118,7 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
115
118
  children,
116
119
  navigationComponent,
117
120
  header,
121
+ top,
118
122
  profile,
119
123
  state,
120
124
  onStateChange,
@@ -122,7 +126,8 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
122
126
  extraLinks,
123
127
  extraLinksTop,
124
128
  extraText,
125
- sidebarTop,
129
+ minContentWidth,
130
+ sideTop,
126
131
  }) => {
127
132
  const breakpoint = useTitanBreakpoint();
128
133
  const isMobile = breakpoint.isMobile;
@@ -154,6 +159,13 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
154
159
  return () => document.removeEventListener('click', listener);
155
160
  }, [isMobile]);
156
161
 
162
+ useEffect(() => {
163
+ if (variant.isAnvil1) {
164
+ document.body.classList.add('of-hidden-i');
165
+ return () => document.body.classList.remove('of-hidden');
166
+ }
167
+ }, [variant.isAnvil1]);
168
+
157
169
  const onBurgerClick = useCallback((e: MouseEvent) => {
158
170
  setMobileDrawerOpened(true);
159
171
  e.stopPropagation();
@@ -179,8 +191,6 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
179
191
  [state, onStateChange]
180
192
  );
181
193
 
182
- const [layoutStyles] = useState<object>({});
183
-
184
194
  const layoutClass = variant.isLegacy
185
195
  ? Styles.layoutLegacy
186
196
  : variant.isAnvil1
@@ -194,21 +204,19 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
194
204
  id={id}
195
205
  className={classNames(
196
206
  Styles.layout,
197
- isMobile
198
- ? Styles.layoutMobile
199
- : state?.navCollapsed
200
- ? Styles.layoutNavSlim
201
- : Styles.layoutNavWide,
207
+ isMobile ? Styles.layoutMobile : Styles.layoutDesktop,
208
+ !isMobile && state?.navCollapsed
209
+ ? Styles.layoutNavSlim
210
+ : Styles.layoutNavWide,
202
211
  layoutClass
203
212
  )}
204
- style={layoutStyles}
205
213
  >
206
214
  {variant.isSequent && <div className={Styles.topPlaceholder} />}
207
215
  <LayoutHeader
208
216
  className={Styles.top}
209
217
  logo={logo}
210
218
  profile={isMobile ? undefined : profile}
211
- center={header}
219
+ center={top}
212
220
  rightText={isMobile ? undefined : extraText}
213
221
  right={
214
222
  <Fragment>
@@ -226,7 +234,7 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
226
234
  submenuExpanded={state?.submenuExpanded}
227
235
  onBarExpandChange={onBarExpandChange}
228
236
  onSubmenuExpandChange={onSubmenuExpandChange}
229
- top={sidebarTop}
237
+ top={sideTop}
230
238
  mainItems={navigationMainItems}
231
239
  navigationComponent={context.NavigationComponent}
232
240
  bottom={
@@ -250,23 +258,98 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
250
258
  }
251
259
  />
252
260
 
253
- {content}
261
+ <LayoutContent
262
+ header={header}
263
+ anvil2={variant.isAnvil2}
264
+ anvil1={variant.isAnvil1}
265
+ minWidth={minContentWidth}
266
+ >
267
+ {content}
268
+ </LayoutContent>
254
269
  </div>
255
270
  </LayoutPlacementContext.Provider>
256
271
  </LayoutContext.Provider>
257
272
  );
258
273
  };
259
274
 
275
+ const TitanLayoutHeaderObserved: FC<{
276
+ children: ReactNode;
277
+ heightChange?(value: number): void;
278
+ }> = ({ children, heightChange }) => {
279
+ const ref = useRef<HTMLDivElement>(null);
280
+
281
+ useEffect(() => {
282
+ if (ref.current) {
283
+ const updatePosition = () => {
284
+ if (ref.current && heightChange) {
285
+ const pos = ref.current.getBoundingClientRect();
286
+ heightChange(pos.height);
287
+ }
288
+ };
289
+
290
+ const observer = new ResizeObserver(updatePosition);
291
+ observer.observe(ref.current);
292
+
293
+ updatePosition();
294
+ return () => observer.disconnect();
295
+ }
296
+ }, [heightChange]);
297
+
298
+ useEffect(() => {
299
+ return () => {
300
+ heightChange?.(0);
301
+ };
302
+ }, [heightChange]);
303
+ return (
304
+ <div ref={ref} className={Styles.header}>
305
+ {children}
306
+ </div>
307
+ );
308
+ };
309
+
260
310
  export interface TitanLayoutContentProps {
261
311
  children: ReactNode;
262
312
  }
263
- const TitanLayoutContent: FC<TitanLayoutContentProps> = ({ children }) => (
264
- <div className={Styles.content}>{children}</div>
265
- );
313
+ const TitanLayoutContent: FC<TitanLayoutContentProps> = ({ children }) => children;
314
+
315
+ const LayoutContent: FC<{
316
+ children: ReactNode;
317
+ header?: ReactNode;
318
+ anvil1: boolean;
319
+ anvil2: boolean;
320
+ minWidth: number | undefined;
321
+ }> = ({ anvil1, anvil2, children, header }) => {
322
+ const [anvil2Styles, setAnvil2Styles] = useState<object>({});
323
+ const updateIndicatorsHeight = useCallback((offset: number) => {
324
+ setAnvil2Styles({ '--offset': `calc(var(--nav-offset-top) + ${offset}px)` });
325
+ }, []);
326
+
327
+ return (
328
+ <Fragment>
329
+ {!!header &&
330
+ (anvil2 ? (
331
+ <TitanLayoutHeaderObserved heightChange={updateIndicatorsHeight}>
332
+ {header}
333
+ </TitanLayoutHeaderObserved>
334
+ ) : (
335
+ <div className={Styles.header}>{header}</div>
336
+ ))}
337
+ <div className={Styles.content} style={anvil2Styles} data-cy="layout-content">
338
+ {anvil1 ? (
339
+ <div className="position-relative d-f flex-grow-1 flex-basis-0 of-hidden">
340
+ {children}
341
+ </div>
342
+ ) : (
343
+ children
344
+ )}
345
+ </div>
346
+ </Fragment>
347
+ );
348
+ };
266
349
 
267
350
  export const TitanLayout = Object.assign(TitanLayoutComponent, {
268
351
  Content: TitanLayoutContent,
269
352
  Logo: TitanLayoutLogo,
270
- SidebarLink: TitanLayoutSidebarLink,
271
- SidebarTrigger: TitanLayoutSidebarTrigger,
353
+ Link: TitanLayoutSidebarLink,
354
+ Trigger: TitanLayoutSidebarTrigger,
272
355
  });