@servicetitan/navigation 10.6.1 → 11.0.0-canary.237.0c461af.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 (164) hide show
  1. package/dist/components/header-navigation/header-navigation-extra.stories.d.ts.map +1 -1
  2. package/dist/components/header-navigation/header-navigation-extra.stories.js +5 -5
  3. package/dist/components/header-navigation/header-navigation-extra.stories.js.map +1 -1
  4. package/dist/components/header-navigation/header-navigation-links.d.ts.map +1 -1
  5. package/dist/components/header-navigation/header-navigation-links.js +2 -2
  6. package/dist/components/header-navigation/header-navigation-links.js.map +1 -1
  7. package/dist/components/header-navigation/header-navigation-stacked.stories.d.ts.map +1 -1
  8. package/dist/components/header-navigation/header-navigation-stacked.stories.js +1 -1
  9. package/dist/components/header-navigation/header-navigation-stacked.stories.js.map +1 -1
  10. package/dist/components/header-navigation/header-navigation.stories.d.ts.map +1 -1
  11. package/dist/components/header-navigation/header-navigation.stories.js +2 -2
  12. package/dist/components/header-navigation/header-navigation.stories.js.map +1 -1
  13. package/dist/components/header-navigation/with-tooltip.d.ts +1 -1
  14. package/dist/components/header-navigation/with-tooltip.d.ts.map +1 -1
  15. package/dist/components/left-navigation/header-navigation-tiny.stories.d.ts.map +1 -1
  16. package/dist/components/left-navigation/header-navigation-tiny.stories.js +2 -2
  17. package/dist/components/left-navigation/header-navigation-tiny.stories.js.map +1 -1
  18. package/dist/components/left-navigation/interface.d.ts +1 -1
  19. package/dist/components/left-navigation/interface.d.ts.map +1 -1
  20. package/dist/components/left-navigation/side-navigation-links-internal.d.ts +3 -1
  21. package/dist/components/left-navigation/side-navigation-links-internal.d.ts.map +1 -1
  22. package/dist/components/left-navigation/side-navigation-links-internal.js +3 -3
  23. package/dist/components/left-navigation/side-navigation-links-internal.js.map +1 -1
  24. package/dist/components/left-navigation/side-navigation.d.ts.map +1 -1
  25. package/dist/components/left-navigation/side-navigation.js +8 -7
  26. package/dist/components/left-navigation/side-navigation.js.map +1 -1
  27. package/dist/components/left-navigation/side-navigation.module.less +21 -19
  28. package/dist/components/links.d.ts.map +1 -1
  29. package/dist/components/links.js +7 -7
  30. package/dist/components/links.js.map +1 -1
  31. package/dist/components/logo/logo-company-title.d.ts +1 -0
  32. package/dist/components/logo/logo-company-title.d.ts.map +1 -1
  33. package/dist/components/logo/logo-company-title.js +2 -2
  34. package/dist/components/logo/logo-company-title.js.map +1 -1
  35. package/dist/components/profile-dropdown/profile-dropdown.d.ts +15 -9
  36. package/dist/components/profile-dropdown/profile-dropdown.d.ts.map +1 -1
  37. package/dist/components/profile-dropdown/profile-dropdown.js +7 -8
  38. package/dist/components/profile-dropdown/profile-dropdown.js.map +1 -1
  39. package/dist/components/profile-dropdown/profile-dropdown.module.less +4 -0
  40. package/dist/components/profile-dropdown/profile-dropdown.stories.js +2 -2
  41. package/dist/components/profile-dropdown/profile-dropdown.stories.js.map +1 -1
  42. package/dist/components/titan-layout/index.d.ts +6 -0
  43. package/dist/components/titan-layout/index.d.ts.map +1 -0
  44. package/dist/components/titan-layout/index.js +6 -0
  45. package/dist/components/titan-layout/index.js.map +1 -0
  46. package/dist/components/titan-layout/interface-internal.d.ts +6 -0
  47. package/dist/components/titan-layout/interface-internal.d.ts.map +1 -0
  48. package/dist/components/titan-layout/interface-internal.js +2 -0
  49. package/dist/components/titan-layout/interface-internal.js.map +1 -0
  50. package/dist/components/titan-layout/interface.d.ts +21 -0
  51. package/dist/components/titan-layout/interface.d.ts.map +1 -0
  52. package/dist/components/titan-layout/interface.js +2 -0
  53. package/dist/components/titan-layout/interface.js.map +1 -0
  54. package/dist/components/titan-layout/layout-context.d.ts +20 -0
  55. package/dist/components/titan-layout/layout-context.d.ts.map +1 -0
  56. package/dist/components/titan-layout/layout-context.js +12 -0
  57. package/dist/components/titan-layout/layout-context.js.map +1 -0
  58. package/dist/components/titan-layout/layout-header-links.d.ts +7 -0
  59. package/dist/components/titan-layout/layout-header-links.d.ts.map +1 -0
  60. package/dist/components/titan-layout/layout-header-links.js +32 -0
  61. package/dist/components/titan-layout/layout-header-links.js.map +1 -0
  62. package/dist/components/titan-layout/layout-header.d.ts +20 -0
  63. package/dist/components/titan-layout/layout-header.d.ts.map +1 -0
  64. package/dist/components/titan-layout/layout-header.js +11 -0
  65. package/dist/components/titan-layout/layout-header.js.map +1 -0
  66. package/dist/components/titan-layout/layout-header.module.less +174 -0
  67. package/dist/components/titan-layout/layout-logo.d.ts +12 -0
  68. package/dist/components/titan-layout/layout-logo.d.ts.map +1 -0
  69. package/dist/components/titan-layout/layout-logo.js +15 -0
  70. package/dist/components/titan-layout/layout-logo.js.map +1 -0
  71. package/dist/components/titan-layout/layout-logo.stories.d.ts +13 -0
  72. package/dist/components/titan-layout/layout-logo.stories.d.ts.map +1 -0
  73. package/dist/components/titan-layout/layout-logo.stories.js +17 -0
  74. package/dist/components/titan-layout/layout-logo.stories.js.map +1 -0
  75. package/dist/components/titan-layout/layout-profile.d.ts +9 -0
  76. package/dist/components/titan-layout/layout-profile.d.ts.map +1 -0
  77. package/dist/components/titan-layout/layout-profile.js +54 -0
  78. package/dist/components/titan-layout/layout-profile.js.map +1 -0
  79. package/dist/components/titan-layout/layout-profile.stories.d.ts +13 -0
  80. package/dist/components/titan-layout/layout-profile.stories.d.ts.map +1 -0
  81. package/dist/components/titan-layout/layout-profile.stories.js +13 -0
  82. package/dist/components/titan-layout/layout-profile.stories.js.map +1 -0
  83. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts +46 -0
  84. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts.map +1 -0
  85. package/dist/components/titan-layout/layout-sidebar-links-internal.js +61 -0
  86. package/dist/components/titan-layout/layout-sidebar-links-internal.js.map +1 -0
  87. package/dist/components/titan-layout/layout-sidebar-links.d.ts +6 -0
  88. package/dist/components/titan-layout/layout-sidebar-links.d.ts.map +1 -0
  89. package/dist/components/titan-layout/layout-sidebar-links.js +21 -0
  90. package/dist/components/titan-layout/layout-sidebar-links.js.map +1 -0
  91. package/dist/components/titan-layout/layout-sidebar.d.ts +17 -0
  92. package/dist/components/titan-layout/layout-sidebar.d.ts.map +1 -0
  93. package/dist/components/titan-layout/layout-sidebar.js +65 -0
  94. package/dist/components/titan-layout/layout-sidebar.js.map +1 -0
  95. package/dist/components/titan-layout/layout-sidebar.module.less +516 -0
  96. package/dist/components/titan-layout/titan-layout.d.ts +38 -0
  97. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -0
  98. package/dist/components/titan-layout/titan-layout.js +147 -0
  99. package/dist/components/titan-layout/titan-layout.js.map +1 -0
  100. package/dist/components/titan-layout/titan-layout.module.less +103 -0
  101. package/dist/components/titan-layout/titan-layout.stories.d.ts +20 -0
  102. package/dist/components/titan-layout/titan-layout.stories.d.ts.map +1 -0
  103. package/dist/components/titan-layout/titan-layout.stories.js +80 -0
  104. package/dist/components/titan-layout/titan-layout.stories.js.map +1 -0
  105. package/dist/components/titan-layout/with-tooltip.d.ts +4 -0
  106. package/dist/components/titan-layout/with-tooltip.d.ts.map +1 -0
  107. package/dist/components/titan-layout/with-tooltip.js +4 -0
  108. package/dist/components/titan-layout/with-tooltip.js.map +1 -0
  109. package/dist/index.d.ts +2 -1
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +2 -1
  112. package/dist/index.js.map +1 -1
  113. package/dist/test/data.d.ts.map +1 -1
  114. package/dist/test/data.js +3 -3
  115. package/dist/test/data.js.map +1 -1
  116. package/dist/utils/navigation-legacy.d.ts +3 -1
  117. package/dist/utils/navigation-legacy.d.ts.map +1 -1
  118. package/dist/utils/use-breakpoint.d.ts +7 -0
  119. package/dist/utils/use-breakpoint.d.ts.map +1 -0
  120. package/dist/utils/use-breakpoint.js +13 -0
  121. package/dist/utils/use-breakpoint.js.map +1 -0
  122. package/package.json +5 -6
  123. package/src/components/header-navigation/header-navigation-extra.stories.tsx +7 -0
  124. package/src/components/header-navigation/header-navigation-links.tsx +2 -0
  125. package/src/components/header-navigation/header-navigation-stacked.stories.tsx +5 -1
  126. package/src/components/header-navigation/header-navigation.stories.tsx +6 -1
  127. package/src/components/left-navigation/header-navigation-tiny.stories.tsx +8 -2
  128. package/src/components/left-navigation/interface.ts +2 -2
  129. package/src/components/left-navigation/side-navigation-links-internal.tsx +21 -6
  130. package/src/components/left-navigation/side-navigation-links.tsx +1 -1
  131. package/src/components/left-navigation/side-navigation.module.less +21 -19
  132. package/src/components/left-navigation/side-navigation.module.less.d.ts +2 -1
  133. package/src/components/left-navigation/side-navigation.tsx +15 -8
  134. package/src/components/links.tsx +33 -13
  135. package/src/components/logo/logo-company-title.tsx +8 -6
  136. package/src/components/profile-dropdown/profile-dropdown.module.less +4 -0
  137. package/src/components/profile-dropdown/profile-dropdown.stories.tsx +4 -4
  138. package/src/components/profile-dropdown/profile-dropdown.tsx +55 -51
  139. package/src/components/titan-layout/index.ts +5 -0
  140. package/src/components/titan-layout/interface-internal.ts +6 -0
  141. package/src/components/titan-layout/interface.ts +26 -0
  142. package/src/components/titan-layout/layout-context.tsx +30 -0
  143. package/src/components/titan-layout/layout-header-links.tsx +144 -0
  144. package/src/components/titan-layout/layout-header.module.less +174 -0
  145. package/src/components/titan-layout/layout-header.module.less.d.ts +16 -0
  146. package/src/components/titan-layout/layout-header.tsx +90 -0
  147. package/src/components/titan-layout/layout-logo.stories.tsx +31 -0
  148. package/src/components/titan-layout/layout-logo.tsx +57 -0
  149. package/src/components/titan-layout/layout-profile.stories.tsx +46 -0
  150. package/src/components/titan-layout/layout-profile.tsx +132 -0
  151. package/src/components/titan-layout/layout-sidebar-links-internal.tsx +275 -0
  152. package/src/components/titan-layout/layout-sidebar-links.tsx +59 -0
  153. package/src/components/titan-layout/layout-sidebar.module.less +516 -0
  154. package/src/components/titan-layout/layout-sidebar.module.less.d.ts +48 -0
  155. package/src/components/titan-layout/layout-sidebar.tsx +295 -0
  156. package/src/components/titan-layout/titan-layout.module.less +103 -0
  157. package/src/components/titan-layout/titan-layout.module.less.d.ts +15 -0
  158. package/src/components/titan-layout/titan-layout.stories.tsx +332 -0
  159. package/src/components/titan-layout/titan-layout.tsx +365 -0
  160. package/src/components/titan-layout/with-tooltip.tsx +16 -0
  161. package/src/index.ts +2 -1
  162. package/src/test/data.tsx +3 -2
  163. package/src/utils/navigation-legacy.ts +3 -1
  164. package/src/utils/use-breakpoint.ts +19 -0
@@ -0,0 +1,332 @@
1
+ import { Announcement, Page as Anvil2Page, Button, Popover, TextField } from '@servicetitan/anvil2';
2
+ import SvgSearch from '@servicetitan/anvil2/assets/icons/material/round/search.svg';
3
+ import SvgAtlas from '@servicetitan/anvil2/assets/icons/st/atlas_logo.svg';
4
+ import SvgSettingsActive from '@servicetitan/anvil2/assets/icons/st/gnav_settings_active.svg';
5
+ import SvgSettings from '@servicetitan/anvil2/assets/icons/st/gnav_settings_inactive.svg';
6
+ import SvgRocketActive from '@servicetitan/anvil2/assets/icons/st/gnav_titan_advisor_active.svg';
7
+ import SvgRocket from '@servicetitan/anvil2/assets/icons/st/gnav_titan_advisor_inactive.svg';
8
+ import { Page as Anvil1Page } from '@servicetitan/design-system';
9
+ import { FC, Fragment, useState } from 'react';
10
+ import {
11
+ CallsNavigationTrigger,
12
+ LocationInfo,
13
+ NavLinkMock,
14
+ items,
15
+ withAnvil,
16
+ withDefaultRedirects,
17
+ withMemoryRouter,
18
+ } from '../../test/data';
19
+ import { SideNavigationLinkWrapperProps } from '../left-navigation';
20
+ import { HeaderNavigationLink, HeaderNavigationTrigger } from '../links';
21
+ import { ProfileDropdown, TitanLayout, TitanLayoutProps, TitanLayoutState } from './';
22
+
23
+ interface LayoutContentArgs {
24
+ header: boolean;
25
+ sideTop: boolean;
26
+ extraText: boolean;
27
+ search: boolean;
28
+ longContent: boolean;
29
+ wideContent: boolean;
30
+ }
31
+
32
+ export default {
33
+ title: 'Navigation/TitanLayout',
34
+ decorators: [withDefaultRedirects, withMemoryRouter, withAnvil],
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,
45
+ };
46
+
47
+ const mainNavItems = [
48
+ items.dashboard,
49
+ items.calendar,
50
+ items.calls,
51
+ items.accountingWithSubmenu,
52
+ items.dispatch,
53
+
54
+ items.fleet,
55
+ items.followUps,
56
+ items.inventory,
57
+
58
+ items.marketing,
59
+ items.priceBook,
60
+ items.pointOfSale,
61
+ items.reports,
62
+ ];
63
+
64
+ const profile = (
65
+ <ProfileDropdown>
66
+ <ProfileDropdown.Link
67
+ id="first"
68
+ to="https://google.com"
69
+ tooltip="Google it"
70
+ target="_blank"
71
+ >
72
+ first link
73
+ </ProfileDropdown.Link>
74
+ <ProfileDropdown.Section
75
+ id="second"
76
+ onClick={() => alert('second click')}
77
+ tooltip="Second hint"
78
+ >
79
+ second link
80
+ </ProfileDropdown.Section>
81
+ <ProfileDropdown.Divider />
82
+ <ProfileDropdown.Section id="content">some content</ProfileDropdown.Section>
83
+ <ProfileDropdown.Divider />
84
+ <ProfileDropdown.Divider />
85
+ <ProfileDropdown.Divider />
86
+ <ProfileDropdown.Link id="third" to="/third">
87
+ third link
88
+ </ProfileDropdown.Link>
89
+ <ProfileDropdown.Divider />
90
+ </ProfileDropdown>
91
+ );
92
+
93
+ const extraLinks = (
94
+ <Fragment>
95
+ <HeaderNavigationLink
96
+ id="search"
97
+ to="https://google.com"
98
+ target="_blank"
99
+ title="Search"
100
+ hint="Search: for all the questions"
101
+ tooltip="Search"
102
+ icon={SvgSearch}
103
+ iconActive={SvgSearch}
104
+ />
105
+
106
+ <CallsNavigationTrigger />
107
+
108
+ <HeaderNavigationLink
109
+ id="titanAdvisor"
110
+ to="/titanAdvisor"
111
+ title="Titan Advisor"
112
+ icon={SvgRocket}
113
+ iconActive={SvgRocketActive}
114
+ />
115
+
116
+ <HeaderNavigationLink
117
+ id="settings"
118
+ to="/settings"
119
+ title="Settings"
120
+ target="_blank"
121
+ aria-label="search"
122
+ hint="Settings"
123
+ icon={SvgSettings}
124
+ iconActive={SvgSettingsActive}
125
+ />
126
+ </Fragment>
127
+ );
128
+
129
+ const extraLinksTop = (
130
+ <HeaderNavigationTrigger
131
+ id="atlas"
132
+ title="Atlas"
133
+ icon={SvgAtlas}
134
+ iconActive={SvgAtlas}
135
+ data-pendo="atlas-chat-button"
136
+ data-cy="atlas-chat-button"
137
+ data-atlas-chat-button
138
+ />
139
+ );
140
+
141
+ const SideLinkPopoverWrapper: FC<SideNavigationLinkWrapperProps> = ({ children, context }) => {
142
+ return (
143
+ <Popover placement="right" openOnHover>
144
+ <Popover.Trigger>{(props: any) => <div {...props}>{children}</div>}</Popover.Trigger>
145
+ <Popover.Content style={context.styles.popoverContent}>popover content</Popover.Content>
146
+ </Popover>
147
+ );
148
+ };
149
+
150
+ const sidebarTop = () => [
151
+ <TitanLayout.Link key="tasks" {...items.tasks} />,
152
+ <TitanLayout.Link key="calls" {...items.calls} />,
153
+ <TitanLayout.Trigger
154
+ key="marketing"
155
+ {...items.marketing}
156
+ isActive={false}
157
+ wrapper={SideLinkPopoverWrapper}
158
+ onMobileClick={() => alert('clicked')}
159
+ counter={50}
160
+ />,
161
+ ];
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 => {
186
+ const [state, setState] = useState<TitanLayoutState | undefined>(undefined);
187
+
188
+ return {
189
+ state,
190
+ onStateChange: setState,
191
+
192
+ navigationComponent: NavLinkMock,
193
+ navigationMainItems: mainNavItems,
194
+
195
+ profile,
196
+ top: args.search ? <SearchBar /> : undefined,
197
+ header: args.header ? <ContentHeader /> : undefined,
198
+
199
+ extraLinks,
200
+ extraLinksTop,
201
+ extraText: args.extraText ? 'EST (-8 hrs)' : undefined,
202
+
203
+ sideTop: args.sideTop ? sidebarTop() : undefined,
204
+ };
205
+ };
206
+
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">
301
+ <TitanLayout.Logo title />
302
+ <TitanLayout.Content>
303
+ <div className="p-3">
304
+ <Content {...args} />
305
+ </div>
306
+ </TitanLayout.Content>
307
+ </TitanLayout>
308
+ );
309
+
310
+ export const TitanLayoutAnvil1 = (args: LayoutContentArgs) => (
311
+ <TitanLayout {...useLayoutProps(args)} appearance="anvil1">
312
+ <TitanLayout.Logo title />
313
+ <TitanLayout.Content>
314
+ <Anvil1Page>
315
+ <Content {...args} />
316
+ </Anvil1Page>
317
+ </TitanLayout.Content>
318
+ </TitanLayout>
319
+ );
320
+
321
+ export const TitanLayoutAnvil2 = (args: LayoutContentArgs) => (
322
+ <TitanLayout {...useLayoutProps(args)} appearance="anvil2">
323
+ <TitanLayout.Logo title />
324
+ <TitanLayout.Content>
325
+ <Anvil2Page>
326
+ <Anvil2Page.Content>
327
+ <Content {...args} />
328
+ </Anvil2Page.Content>
329
+ </Anvil2Page>
330
+ </TitanLayout.Content>
331
+ </TitanLayout>
332
+ );
@@ -0,0 +1,365 @@
1
+ import classNames from 'classnames';
2
+ import {
3
+ CSSProperties,
4
+ Children,
5
+ ComponentPropsWithoutRef,
6
+ FC,
7
+ Fragment,
8
+ ReactElement,
9
+ ReactNode,
10
+ isValidElement,
11
+ useCallback,
12
+ useEffect,
13
+ useMemo,
14
+ useRef,
15
+ useState,
16
+ } from 'react';
17
+ import { NavigationItemData } from '../../utils/navigation';
18
+ import { DefaultNavLinkComponent, NavLinkComponentProps } from '../../utils/navigation-context';
19
+ import { useTitanBreakpoint } from '../../utils/use-breakpoint';
20
+ import { TitanLayoutState } from './interface';
21
+ import {
22
+ LayoutContext,
23
+ LayoutPlacementContext,
24
+ TitanLayoutContextType,
25
+ TitanLayoutSidebarContextType,
26
+ } from './layout-context';
27
+ import { LayoutHeader } from './layout-header';
28
+ import { TitanLayoutLogo, TitanLayoutLogoProps } from './layout-logo';
29
+ import { LayoutSidebar } from './layout-sidebar';
30
+ import { TitanLayoutSidebarLink, TitanLayoutSidebarTrigger } from './layout-sidebar-links';
31
+ import { InternalSideNavigationTrigger } from './layout-sidebar-links-internal';
32
+ import * as Styles from './titan-layout.module.less';
33
+
34
+ type TitanLayoutChild = ReactElement<TitanLayoutContentProps> | ReactElement<TitanLayoutLogoProps>;
35
+
36
+ export type TitanLayoutProps = Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'style'> & {
37
+ empty?: boolean;
38
+
39
+ appearance?: 'legacy' | 'anvil1' | 'anvil2';
40
+
41
+ /** component used for navigation */
42
+ navigationComponent?: FC<NavLinkComponentProps>;
43
+
44
+ /** data for main navigation links */
45
+ navigationMainItems?: NavigationItemData[];
46
+
47
+ /** layout's content */
48
+ children?: TitanLayoutChild | TitanLayoutChild[];
49
+
50
+ state?: TitanLayoutState;
51
+ onStateChange?: (state: TitanLayoutState) => void;
52
+
53
+ header?: ReactElement;
54
+ top?: ReactElement;
55
+ sideTop?: ReactElement[];
56
+ profile?: ReactElement;
57
+ extraLinks?: ReactElement;
58
+ extraLinksTop?: ReactElement;
59
+ extraText?: string;
60
+ minContentWidth?: number;
61
+ };
62
+
63
+ const defaultSidebarContext: TitanLayoutSidebarContextType = {
64
+ styles: {
65
+ popoverContent: {
66
+ '--background-color-strong': '#24323C',
67
+ 'color': 'var(--color-white)',
68
+ } as CSSProperties,
69
+ },
70
+ };
71
+
72
+ const useVariant = (appearance: TitanLayoutProps['appearance']) =>
73
+ useMemo(() => {
74
+ const isLegacy = appearance === 'legacy';
75
+ const isAnvil1 = appearance === 'anvil1';
76
+ const isAnvil2 = appearance === 'anvil2';
77
+
78
+ return {
79
+ isLegacy,
80
+ isAnvil1,
81
+ isAnvil2,
82
+ isSequent: isLegacy || isAnvil2,
83
+ };
84
+ }, [appearance]);
85
+
86
+ const useLayoutChildren = (children: TitanLayoutProps['children']) =>
87
+ useMemo(
88
+ () =>
89
+ Children.toArray(children).reduce(
90
+ (out, item) => {
91
+ if (
92
+ item &&
93
+ isValidElement(item) &&
94
+ item.type &&
95
+ typeof item.type !== 'string'
96
+ ) {
97
+ if (item.type.name === TitanLayoutContent.name) {
98
+ out.content = item as any;
99
+ } else if (item.type.name === TitanLayoutLogo.name) {
100
+ out.logo = item as any;
101
+ }
102
+ }
103
+ return out;
104
+ },
105
+ {
106
+ logo: <TitanLayoutLogo />,
107
+ } as {
108
+ content?: ReactElement<TitanLayoutContentProps>;
109
+ logo: ReactElement<TitanLayoutLogoProps>;
110
+ }
111
+ ),
112
+ [children]
113
+ );
114
+
115
+ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
116
+ appearance = 'anvil2',
117
+ id,
118
+ children,
119
+ navigationComponent,
120
+ header,
121
+ top,
122
+ profile,
123
+ state,
124
+ onStateChange,
125
+ navigationMainItems,
126
+ extraLinks,
127
+ extraLinksTop,
128
+ extraText,
129
+ minContentWidth,
130
+ sideTop,
131
+ }) => {
132
+ const breakpoint = useTitanBreakpoint();
133
+ const isMobile = breakpoint.isMobile;
134
+ const context: TitanLayoutContextType = useMemo(
135
+ () => ({
136
+ NavigationComponent: navigationComponent ?? DefaultNavLinkComponent,
137
+ breakpoint,
138
+ isTitanLayout: true,
139
+ sidebar: defaultSidebarContext,
140
+ }),
141
+ [navigationComponent, breakpoint]
142
+ );
143
+ const variant = useVariant(appearance);
144
+ const [mobileDrawerOpened, setMobileDrawerOpened] = useState(false);
145
+ const { content, logo } = useLayoutChildren(children);
146
+
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]);
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
+
169
+ const onBurgerClick = useCallback((e: MouseEvent) => {
170
+ setMobileDrawerOpened(true);
171
+ e.stopPropagation();
172
+ }, []);
173
+
174
+ const onBarExpandChange = useCallback(
175
+ (expanded: boolean) => {
176
+ if (isMobile) {
177
+ setMobileDrawerOpened(false);
178
+ } else {
179
+ onStateChange?.({ navCollapsed: !expanded });
180
+ }
181
+ },
182
+ [onStateChange, isMobile]
183
+ );
184
+ const onSubmenuExpandChange = useCallback(
185
+ (id: string, expanded: boolean) => {
186
+ onStateChange?.({
187
+ navCollapsed: state?.navCollapsed ?? false,
188
+ submenuExpanded: expanded ? id : undefined,
189
+ });
190
+ },
191
+ [state, onStateChange]
192
+ );
193
+
194
+ const layoutClass = variant.isLegacy
195
+ ? Styles.layoutLegacy
196
+ : variant.isAnvil1
197
+ ? Styles.layoutAnvil1
198
+ : Styles.layoutAnvil2;
199
+
200
+ return (
201
+ <LayoutContext.Provider value={context}>
202
+ <LayoutPlacementContext.Provider value="unset">
203
+ <div
204
+ id={id}
205
+ className={classNames(
206
+ Styles.layout,
207
+ isMobile ? Styles.layoutMobile : Styles.layoutDesktop,
208
+ !isMobile && state?.navCollapsed
209
+ ? Styles.layoutNavSlim
210
+ : Styles.layoutNavWide,
211
+ layoutClass
212
+ )}
213
+ >
214
+ {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 ? (
242
+ <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
+ )}
256
+ </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>
269
+ </div>
270
+ </LayoutPlacementContext.Provider>
271
+ </LayoutContext.Provider>
272
+ );
273
+ };
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.contentHeader} data-cy="layout-content-header">
305
+ {children}
306
+ </div>
307
+ );
308
+ };
309
+
310
+ export interface TitanLayoutContentProps {
311
+ children: ReactNode;
312
+ }
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, minWidth }) => {
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
+ const contentStyles = useMemo(
328
+ () => ({
329
+ ...(minWidth ? { minWidth: `${minWidth}px` } : {}),
330
+ ...(anvil2 ? anvil2Styles : {}),
331
+ }),
332
+ [anvil2, minWidth, anvil2Styles]
333
+ );
334
+
335
+ return (
336
+ <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
+ )}
355
+ </div>
356
+ </Fragment>
357
+ );
358
+ };
359
+
360
+ export const TitanLayout = Object.assign(TitanLayoutComponent, {
361
+ Content: TitanLayoutContent,
362
+ Logo: TitanLayoutLogo,
363
+ Link: TitanLayoutSidebarLink,
364
+ Trigger: TitanLayoutSidebarTrigger,
365
+ });