@servicetitan/navigation 11.0.0-canary.237.4d902dc.0 → 11.0.0-canary.237.5f3f02d.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 +61 -21
  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 +12 -5
  41. package/dist/components/titan-layout/layout-sidebar-links.js.map +1 -1
  42. package/dist/components/titan-layout/layout-sidebar.d.ts +2 -1
  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 +29 -21
  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 +10 -6
  52. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -1
  53. package/dist/components/titan-layout/titan-layout.js +104 -21
  54. package/dist/components/titan-layout/titan-layout.js.map +1 -1
  55. package/dist/components/titan-layout/titan-layout.module.less +74 -19
  56. package/dist/components/titan-layout/titan-layout.stories.d.ts +15 -11
  57. package/dist/components/titan-layout/titan-layout.stories.d.ts.map +1 -1
  58. package/dist/components/titan-layout/titan-layout.stories.js +35 -14
  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 +61 -21
  80. package/src/components/titan-layout/layout-header.module.less.d.ts +2 -0
  81. package/src/components/titan-layout/layout-header.tsx +17 -6
  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 +21 -5
  87. package/src/components/titan-layout/layout-sidebar.module.less +29 -21
  88. package/src/components/titan-layout/layout-sidebar.module.less.d.ts +1 -2
  89. package/src/components/titan-layout/layout-sidebar.tsx +15 -9
  90. package/src/components/titan-layout/notifications-context.tsx +44 -0
  91. package/src/components/titan-layout/titan-layout.module.less +74 -19
  92. package/src/components/titan-layout/titan-layout.module.less.d.ts +5 -0
  93. package/src/components/titan-layout/titan-layout.stories.tsx +171 -19
  94. package/src/components/titan-layout/titan-layout.tsx +244 -74
  95. package/src/test/data.tsx +2 -3
  96. package/src/utils/use-breakpoint.ts +3 -1
@@ -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,32 @@ 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
+ minWidth: boolean;
31
+ emptyNav: boolean;
32
+ }
33
+
23
34
  export default {
24
35
  title: 'Navigation/TitanLayout',
25
- component: TitanLayout,
26
36
  decorators: [withDefaultRedirects, withMemoryRouter, withAnvil],
27
37
  parameters: {},
38
+ argTypes: {},
39
+ args: {
40
+ header: true,
41
+ sideTop: true,
42
+ extraText: true,
43
+ search: true,
44
+ longContent: true,
45
+ wideContent: false,
46
+ minWidth: false,
47
+ emptyNav: false,
48
+ } as LayoutContentArgs,
28
49
  };
29
50
 
30
51
  const mainNavItems = [
@@ -51,6 +72,7 @@ const profile = (
51
72
  to="https://google.com"
52
73
  tooltip="Google it"
53
74
  target="_blank"
75
+ tag={{ value: true }}
54
76
  >
55
77
  first link
56
78
  </ProfileDropdown.Link>
@@ -58,6 +80,7 @@ const profile = (
58
80
  id="second"
59
81
  onClick={() => alert('second click')}
60
82
  tooltip="Second hint"
83
+ counter
61
84
  >
62
85
  second link
63
86
  </ProfileDropdown.Section>
@@ -131,9 +154,9 @@ const SideLinkPopoverWrapper: FC<SideNavigationLinkWrapperProps> = ({ children,
131
154
  };
132
155
 
133
156
  const sidebarTop = () => [
134
- <TitanLayout.SidebarLink key="tasks" {...items.tasks} />,
135
- <TitanLayout.SidebarLink key="calls" {...items.calls} />,
136
- <TitanLayout.SidebarTrigger
157
+ <TitanLayout.Link key="tasks" {...items.tasks} />,
158
+ <TitanLayout.Link key="calls" {...items.calls} />,
159
+ <TitanLayout.Trigger
137
160
  key="marketing"
138
161
  {...items.marketing}
139
162
  isActive={false}
@@ -142,51 +165,180 @@ const sidebarTop = () => [
142
165
  counter={50}
143
166
  />,
144
167
  ];
145
- const useLayoutProps = (): TitanLayoutProps => {
168
+ const ContentHeader = () => {
169
+ const [longInfo, setLongInfo] = useState(false);
170
+
171
+ return (
172
+ <Fragment>
173
+ <Announcement title="Some info" status="info" />
174
+ <Announcement title="Some warning" status="warning" />
175
+ <div
176
+ className="d-f justify-content-center align-items-center bg-purple-100-i"
177
+ style={{ height: longInfo ? '120px' : '48px' }}
178
+ >
179
+ <div className="d-f align-items-center gap-1">
180
+ custom content{' '}
181
+ <Button onClick={() => setLongInfo(!longInfo)} size="small" aria-label="test">
182
+ {longInfo ? '↑' : '↓'}
183
+ </Button>
184
+ </div>
185
+ </div>
186
+ </Fragment>
187
+ );
188
+ };
189
+ const SearchBar = () => (
190
+ <TextField size="small" placeholder="Search" className="w-100-i m-x-half-i" />
191
+ );
192
+
193
+ const useLayoutProps = (args: LayoutContentArgs): TitanLayoutProps => {
146
194
  const [state, setState] = useState<TitanLayoutState | undefined>(undefined);
147
195
 
148
196
  return {
149
197
  state,
150
198
  onStateChange: setState,
151
199
 
152
- navigationMainItems: mainNavItems,
200
+ navigationComponent: NavLinkMock,
201
+ navigationMainItems: args.emptyNav ? [] : mainNavItems,
202
+
153
203
  profile,
204
+ top: args.search ? <SearchBar /> : undefined,
205
+ header: args.header ? <ContentHeader /> : undefined,
206
+
154
207
  extraLinks,
155
208
  extraLinksTop,
209
+ extraText: args.extraText ? 'EST (-8 hrs)' : undefined,
156
210
 
157
- sidebarTop: sidebarTop(),
158
- navigationComponent: NavLinkMock,
159
- extraText: 'EST (-8 hrs)',
211
+ sideTop: args.sideTop && !args.emptyNav ? sidebarTop() : undefined,
212
+
213
+ minContentWidth: args.minWidth ? 900 : undefined,
160
214
  };
161
215
  };
162
216
 
163
- export const TitanLayoutLegacy = () => (
164
- <TitanLayout {...useLayoutProps()} appearance="legacy">
217
+ const Content = (args: LayoutContentArgs) => {
218
+ return (
219
+ <Fragment>
220
+ <LocationInfo className="m-b-3" />
221
+ <div className="m-b-3">rendered - {new Date().toLocaleTimeString()}</div>
222
+ {args.wideContent && (
223
+ <div style={{ width: '1200px' }}>
224
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
225
+ incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
226
+ nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
227
+ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
228
+ fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
229
+ culpa qui officia deserunt mollit anim id est laborum.
230
+ </div>
231
+ )}
232
+
233
+ {args.longContent && (
234
+ <div>
235
+ <p>Lorem</p>
236
+ <p>ipsum</p>
237
+ <p>dolor</p>
238
+ <p>sit</p>
239
+ <p>amet,</p>
240
+ <p>consectetur</p>
241
+ <p>adipiscing</p>
242
+ <p>elit,</p>
243
+ <p>sed</p>
244
+ <p>do</p>
245
+ <p>eiusmod</p>
246
+ <p>tempor</p>
247
+ <p>incididunt</p>
248
+ <p>ut</p>
249
+ <p>labore</p>
250
+ <p>et</p>
251
+ <p>dolore</p>
252
+ <p>magna</p>
253
+ <p>aliqua.</p>
254
+ <p>Ut</p>
255
+ <p>enim</p>
256
+ <p>ad</p>
257
+ <p>minim</p>
258
+ <p>veniam,</p>
259
+ <p>quis</p>
260
+ <p>nostrud</p>
261
+ <p>exercitation</p>
262
+ <p>ullamco</p>
263
+ <p>laboris</p>
264
+ <p>nisi</p>
265
+ <p>ut</p>
266
+ <p>aliquip</p>
267
+ <p>ex</p>
268
+ <p>ea</p>
269
+ <p>commodo</p>
270
+ <p>consequat.</p>
271
+ <p>Duis</p>
272
+ <p>aute</p>
273
+ <p>irure</p>
274
+ <p>dolor</p>
275
+ <p>in</p>
276
+ <p>reprehenderit</p>
277
+ <p>in</p>
278
+ <p>voluptate</p>
279
+ <p>velit</p>
280
+ <p>esse</p>
281
+ <p>cillum</p>
282
+ <p>dolore</p>
283
+ <p>eu</p>
284
+ <p>fugiat</p>
285
+ <p>nulla</p>
286
+ <p>pariatur.</p>
287
+ <p>Excepteur</p>
288
+ <p>sint</p>
289
+ <p>occaecat</p>
290
+ <p>cupidatat</p>
291
+ <p>non</p>
292
+ <p>proident,</p>
293
+ <p>sunt</p>
294
+ <p>in</p>
295
+ <p>culpa</p>
296
+ <p>qui</p>
297
+ <p>officia</p>
298
+ <p>deserunt</p>
299
+ <p>mollit</p>
300
+ <p>anim</p>
301
+ <p>id</p>
302
+ <p>est</p>
303
+ <p>laborum.</p>
304
+ </div>
305
+ )}
306
+ </Fragment>
307
+ );
308
+ };
309
+
310
+ export const TitanLayoutLegacy = (args: LayoutContentArgs) => (
311
+ <TitanLayout {...useLayoutProps(args)} appearance="legacy">
165
312
  <TitanLayout.Logo title />
166
313
  <TitanLayout.Content>
167
- <LocationInfo />
314
+ <div
315
+ className="p-3"
316
+ style={{ position: 'absolute', width: 'calc(100% - var(--nav-offset-left));' }}
317
+ >
318
+ <Content {...args} />
319
+ </div>
168
320
  </TitanLayout.Content>
169
321
  </TitanLayout>
170
322
  );
171
323
 
172
- export const TitanLayoutAnvil1 = () => (
173
- <TitanLayout {...useLayoutProps()} appearance="anvil1">
324
+ export const TitanLayoutAnvil1 = (args: LayoutContentArgs) => (
325
+ <TitanLayout {...useLayoutProps(args)} appearance="anvil1">
174
326
  <TitanLayout.Logo title />
175
327
  <TitanLayout.Content>
176
328
  <Anvil1Page>
177
- <LocationInfo />
329
+ <Content {...args} />
178
330
  </Anvil1Page>
179
331
  </TitanLayout.Content>
180
332
  </TitanLayout>
181
333
  );
182
334
 
183
- export const TitanLayoutAnvil2 = () => (
184
- <TitanLayout {...useLayoutProps()} appearance="anvil2">
335
+ export const TitanLayoutAnvil2 = (args: LayoutContentArgs) => (
336
+ <TitanLayout {...useLayoutProps(args)} appearance="anvil2">
185
337
  <TitanLayout.Logo title />
186
338
  <TitanLayout.Content>
187
339
  <Anvil2Page>
188
340
  <Anvil2Page.Content>
189
- <LocationInfo />
341
+ <Content {...args} />
190
342
  </Anvil2Page.Content>
191
343
  </Anvil2Page>
192
344
  </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';
@@ -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 = {
@@ -76,7 +82,7 @@ const useVariant = (appearance: TitanLayoutProps['appearance']) =>
76
82
  isLegacy,
77
83
  isAnvil1,
78
84
  isAnvil2,
79
- isSequent: isLegacy || isAnvil1,
85
+ isSequent: isLegacy || isAnvil2,
80
86
  };
81
87
  }, [appearance]);
82
88
 
@@ -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,25 @@ 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
+ 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;
141
160
 
142
161
  useEffect(() => {
143
- if (!isMobile) {
144
- setMobileDrawerOpened(false);
145
- return;
162
+ if (variant.isAnvil1) {
163
+ const bodyClassName = 'of-hidden-i';
164
+ document.body.classList.add(bodyClassName);
165
+ return () => document.body.classList.remove(bodyClassName);
146
166
  }
147
-
148
- const listener = () => {
149
- setMobileDrawerOpened(false);
150
- };
151
-
152
- document.addEventListener('click', listener);
153
-
154
- return () => document.removeEventListener('click', listener);
155
- }, [isMobile]);
167
+ }, [variant.isAnvil1]);
156
168
 
157
169
  const onBurgerClick = useCallback((e: MouseEvent) => {
158
170
  setMobileDrawerOpened(true);
@@ -178,8 +190,49 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
178
190
  },
179
191
  [state, onStateChange]
180
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]);
181
211
 
182
- const [layoutStyles] = useState<object>({});
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]);
183
236
 
184
237
  const layoutClass = variant.isLegacy
185
238
  ? Styles.layoutLegacy
@@ -194,77 +247,194 @@ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
194
247
  id={id}
195
248
  className={classNames(
196
249
  Styles.layout,
197
- isMobile
198
- ? Styles.layoutMobile
199
- : state?.navCollapsed
200
- ? Styles.layoutNavSlim
201
- : Styles.layoutNavWide,
250
+ isMobile ? Styles.layoutMobile : Styles.layoutDesktop,
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
  )}
204
- style={layoutStyles}
258
+ style={contentStyles}
205
259
  >
206
260
  {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
- flex={!variant.isSequent}
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 ? (
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={
234
269
  <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
- )}
270
+ {extraLinksTop}
271
+ {!isMobile && extraLinks}
248
272
  </Fragment>
249
- ) : undefined
250
- }
251
- />
273
+ }
274
+ isMobile={isMobile}
275
+ hasNotifications={hasNotifications || hasMenuNotifications}
276
+ onBurgerClick={onBurgerClick}
277
+ />
278
+ )}
279
+
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
+ )}
252
316
 
253
- {content}
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
+ )}
254
333
  </div>
255
334
  </LayoutPlacementContext.Provider>
256
335
  </LayoutContext.Provider>
257
336
  );
258
337
  };
259
338
 
339
+ const TitanLayoutHeaderObserved: FC<{
340
+ children: ReactNode;
341
+ heightChange?(value: number): void;
342
+ }> = ({ children, heightChange }) => {
343
+ const ref = useRef<HTMLDivElement>(null);
344
+
345
+ useEffect(() => {
346
+ if (ref.current) {
347
+ const updatePosition = () => {
348
+ if (ref.current && heightChange) {
349
+ const pos = ref.current.getBoundingClientRect();
350
+ heightChange(pos.height);
351
+ }
352
+ };
353
+
354
+ const observer = new ResizeObserver(updatePosition);
355
+ observer.observe(ref.current);
356
+
357
+ updatePosition();
358
+ return () => observer.disconnect();
359
+ }
360
+ }, [heightChange]);
361
+
362
+ useEffect(() => {
363
+ return () => {
364
+ heightChange?.(0);
365
+ };
366
+ }, [heightChange]);
367
+ return (
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">
376
+ {children}
377
+ </div>
378
+ );
379
+ };
380
+
260
381
  export interface TitanLayoutContentProps {
261
382
  children: ReactNode;
262
383
  }
263
384
  const TitanLayoutContent: FC<TitanLayoutContentProps> = ({ children }) => children;
264
385
 
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
+ );
397
+
398
+ return (
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>
431
+ </div>
432
+ );
433
+ };
434
+
265
435
  export const TitanLayout = Object.assign(TitanLayoutComponent, {
266
436
  Content: TitanLayoutContent,
267
437
  Logo: TitanLayoutLogo,
268
- SidebarLink: TitanLayoutSidebarLink,
269
- SidebarTrigger: TitanLayoutSidebarTrigger,
438
+ Link: TitanLayoutSidebarLink,
439
+ Trigger: TitanLayoutSidebarTrigger,
270
440
  });