@servicetitan/navigation 10.6.1 → 11.0.0-canary.237.0505ce7.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 (177) 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-extra.stories.d.ts.map +1 -1
  4. package/dist/components/header-navigation/header-navigation-extra.stories.js +5 -5
  5. package/dist/components/header-navigation/header-navigation-extra.stories.js.map +1 -1
  6. package/dist/components/header-navigation/header-navigation-links.d.ts.map +1 -1
  7. package/dist/components/header-navigation/header-navigation-links.js +2 -2
  8. package/dist/components/header-navigation/header-navigation-links.js.map +1 -1
  9. package/dist/components/header-navigation/header-navigation-stacked.stories.d.ts.map +1 -1
  10. package/dist/components/header-navigation/header-navigation-stacked.stories.js +1 -1
  11. package/dist/components/header-navigation/header-navigation-stacked.stories.js.map +1 -1
  12. package/dist/components/header-navigation/header-navigation.stories.d.ts.map +1 -1
  13. package/dist/components/header-navigation/header-navigation.stories.js +2 -2
  14. package/dist/components/header-navigation/header-navigation.stories.js.map +1 -1
  15. package/dist/components/header-navigation/with-tooltip.d.ts +1 -1
  16. package/dist/components/header-navigation/with-tooltip.d.ts.map +1 -1
  17. package/dist/components/left-navigation/header-navigation-tiny.stories.d.ts.map +1 -1
  18. package/dist/components/left-navigation/header-navigation-tiny.stories.js +2 -2
  19. package/dist/components/left-navigation/header-navigation-tiny.stories.js.map +1 -1
  20. package/dist/components/left-navigation/interface.d.ts +1 -1
  21. package/dist/components/left-navigation/interface.d.ts.map +1 -1
  22. package/dist/components/left-navigation/side-navigation-links-internal.d.ts +3 -1
  23. package/dist/components/left-navigation/side-navigation-links-internal.d.ts.map +1 -1
  24. package/dist/components/left-navigation/side-navigation-links-internal.js +3 -3
  25. package/dist/components/left-navigation/side-navigation-links-internal.js.map +1 -1
  26. package/dist/components/left-navigation/side-navigation.d.ts.map +1 -1
  27. package/dist/components/left-navigation/side-navigation.js +8 -7
  28. package/dist/components/left-navigation/side-navigation.js.map +1 -1
  29. package/dist/components/left-navigation/side-navigation.module.less +21 -19
  30. package/dist/components/links.d.ts.map +1 -1
  31. package/dist/components/links.js +7 -7
  32. package/dist/components/links.js.map +1 -1
  33. package/dist/components/logo/logo-company-title.d.ts +1 -0
  34. package/dist/components/logo/logo-company-title.d.ts.map +1 -1
  35. package/dist/components/logo/logo-company-title.js +2 -2
  36. package/dist/components/logo/logo-company-title.js.map +1 -1
  37. package/dist/components/logo/logo-titan-text.d.ts +1 -1
  38. package/dist/components/logo/logo-titan-text.d.ts.map +1 -1
  39. package/dist/components/profile-dropdown/profile-dropdown.d.ts +17 -9
  40. package/dist/components/profile-dropdown/profile-dropdown.d.ts.map +1 -1
  41. package/dist/components/profile-dropdown/profile-dropdown.js +11 -9
  42. package/dist/components/profile-dropdown/profile-dropdown.js.map +1 -1
  43. package/dist/components/profile-dropdown/profile-dropdown.module.less +24 -6
  44. package/dist/components/profile-dropdown/profile-dropdown.stories.js +2 -2
  45. package/dist/components/profile-dropdown/profile-dropdown.stories.js.map +1 -1
  46. package/dist/components/titan-layout/index.d.ts +6 -0
  47. package/dist/components/titan-layout/index.d.ts.map +1 -0
  48. package/dist/components/titan-layout/index.js +6 -0
  49. package/dist/components/titan-layout/index.js.map +1 -0
  50. package/dist/components/titan-layout/interface-internal.d.ts +6 -0
  51. package/dist/components/titan-layout/interface-internal.d.ts.map +1 -0
  52. package/dist/components/titan-layout/interface-internal.js +2 -0
  53. package/dist/components/titan-layout/interface-internal.js.map +1 -0
  54. package/dist/components/titan-layout/interface.d.ts +21 -0
  55. package/dist/components/titan-layout/interface.d.ts.map +1 -0
  56. package/dist/components/titan-layout/interface.js +2 -0
  57. package/dist/components/titan-layout/interface.js.map +1 -0
  58. package/dist/components/titan-layout/layout-context.d.ts +20 -0
  59. package/dist/components/titan-layout/layout-context.d.ts.map +1 -0
  60. package/dist/components/titan-layout/layout-context.js +12 -0
  61. package/dist/components/titan-layout/layout-context.js.map +1 -0
  62. package/dist/components/titan-layout/layout-header-links.d.ts +7 -0
  63. package/dist/components/titan-layout/layout-header-links.d.ts.map +1 -0
  64. package/dist/components/titan-layout/layout-header-links.js +32 -0
  65. package/dist/components/titan-layout/layout-header-links.js.map +1 -0
  66. package/dist/components/titan-layout/layout-header.d.ts +22 -0
  67. package/dist/components/titan-layout/layout-header.d.ts.map +1 -0
  68. package/dist/components/titan-layout/layout-header.js +10 -0
  69. package/dist/components/titan-layout/layout-header.js.map +1 -0
  70. package/dist/components/titan-layout/layout-header.module.less +193 -0
  71. package/dist/components/titan-layout/layout-logo.d.ts +12 -0
  72. package/dist/components/titan-layout/layout-logo.d.ts.map +1 -0
  73. package/dist/components/titan-layout/layout-logo.js +16 -0
  74. package/dist/components/titan-layout/layout-logo.js.map +1 -0
  75. package/dist/components/titan-layout/layout-logo.stories.d.ts +13 -0
  76. package/dist/components/titan-layout/layout-logo.stories.d.ts.map +1 -0
  77. package/dist/components/titan-layout/layout-logo.stories.js +17 -0
  78. package/dist/components/titan-layout/layout-logo.stories.js.map +1 -0
  79. package/dist/components/titan-layout/layout-profile.d.ts +9 -0
  80. package/dist/components/titan-layout/layout-profile.d.ts.map +1 -0
  81. package/dist/components/titan-layout/layout-profile.js +72 -0
  82. package/dist/components/titan-layout/layout-profile.js.map +1 -0
  83. package/dist/components/titan-layout/layout-profile.stories.d.ts +13 -0
  84. package/dist/components/titan-layout/layout-profile.stories.d.ts.map +1 -0
  85. package/dist/components/titan-layout/layout-profile.stories.js +13 -0
  86. package/dist/components/titan-layout/layout-profile.stories.js.map +1 -0
  87. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts +46 -0
  88. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts.map +1 -0
  89. package/dist/components/titan-layout/layout-sidebar-links-internal.js +61 -0
  90. package/dist/components/titan-layout/layout-sidebar-links-internal.js.map +1 -0
  91. package/dist/components/titan-layout/layout-sidebar-links.d.ts +6 -0
  92. package/dist/components/titan-layout/layout-sidebar-links.d.ts.map +1 -0
  93. package/dist/components/titan-layout/layout-sidebar-links.js +28 -0
  94. package/dist/components/titan-layout/layout-sidebar-links.js.map +1 -0
  95. package/dist/components/titan-layout/layout-sidebar.d.ts +19 -0
  96. package/dist/components/titan-layout/layout-sidebar.d.ts.map +1 -0
  97. package/dist/components/titan-layout/layout-sidebar.js +67 -0
  98. package/dist/components/titan-layout/layout-sidebar.js.map +1 -0
  99. package/dist/components/titan-layout/layout-sidebar.module.less +536 -0
  100. package/dist/components/titan-layout/notifications-context.d.ts +13 -0
  101. package/dist/components/titan-layout/notifications-context.d.ts.map +1 -0
  102. package/dist/components/titan-layout/notifications-context.js +23 -0
  103. package/dist/components/titan-layout/notifications-context.js.map +1 -0
  104. package/dist/components/titan-layout/titan-layout.d.ts +40 -0
  105. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -0
  106. package/dist/components/titan-layout/titan-layout.js +192 -0
  107. package/dist/components/titan-layout/titan-layout.js.map +1 -0
  108. package/dist/components/titan-layout/titan-layout.module.less +106 -0
  109. package/dist/components/titan-layout/titan-layout.stories.d.ts +22 -0
  110. package/dist/components/titan-layout/titan-layout.stories.d.ts.map +1 -0
  111. package/dist/components/titan-layout/titan-layout.stories.js +83 -0
  112. package/dist/components/titan-layout/titan-layout.stories.js.map +1 -0
  113. package/dist/components/titan-layout/with-tooltip.d.ts +4 -0
  114. package/dist/components/titan-layout/with-tooltip.d.ts.map +1 -0
  115. package/dist/components/titan-layout/with-tooltip.js +4 -0
  116. package/dist/components/titan-layout/with-tooltip.js.map +1 -0
  117. package/dist/index.d.ts +2 -1
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +2 -1
  120. package/dist/index.js.map +1 -1
  121. package/dist/test/data.d.ts +4 -1
  122. package/dist/test/data.d.ts.map +1 -1
  123. package/dist/test/data.js +5 -6
  124. package/dist/test/data.js.map +1 -1
  125. package/dist/utils/navigation-legacy.d.ts +3 -1
  126. package/dist/utils/navigation-legacy.d.ts.map +1 -1
  127. package/dist/utils/use-breakpoint.d.ts +8 -0
  128. package/dist/utils/use-breakpoint.d.ts.map +1 -0
  129. package/dist/utils/use-breakpoint.js +14 -0
  130. package/dist/utils/use-breakpoint.js.map +1 -0
  131. package/package.json +5 -6
  132. package/src/components/badge-tag.tsx +1 -1
  133. package/src/components/header-navigation/header-navigation-extra.stories.tsx +7 -0
  134. package/src/components/header-navigation/header-navigation-links.tsx +2 -0
  135. package/src/components/header-navigation/header-navigation-stacked.stories.tsx +5 -1
  136. package/src/components/header-navigation/header-navigation.stories.tsx +6 -1
  137. package/src/components/left-navigation/header-navigation-tiny.stories.tsx +8 -2
  138. package/src/components/left-navigation/interface.ts +2 -2
  139. package/src/components/left-navigation/side-navigation-links-internal.tsx +21 -6
  140. package/src/components/left-navigation/side-navigation-links.tsx +1 -1
  141. package/src/components/left-navigation/side-navigation.module.less +21 -19
  142. package/src/components/left-navigation/side-navigation.module.less.d.ts +2 -1
  143. package/src/components/left-navigation/side-navigation.tsx +15 -8
  144. package/src/components/links.tsx +33 -13
  145. package/src/components/logo/logo-company-title.tsx +8 -6
  146. package/src/components/logo/logo-titan-text.tsx +1 -1
  147. package/src/components/profile-dropdown/profile-dropdown.module.less +24 -6
  148. package/src/components/profile-dropdown/profile-dropdown.module.less.d.ts +2 -0
  149. package/src/components/profile-dropdown/profile-dropdown.stories.tsx +4 -4
  150. package/src/components/profile-dropdown/profile-dropdown.tsx +87 -55
  151. package/src/components/titan-layout/index.ts +5 -0
  152. package/src/components/titan-layout/interface-internal.ts +6 -0
  153. package/src/components/titan-layout/interface.ts +26 -0
  154. package/src/components/titan-layout/layout-context.tsx +30 -0
  155. package/src/components/titan-layout/layout-header-links.tsx +144 -0
  156. package/src/components/titan-layout/layout-header.module.less +193 -0
  157. package/src/components/titan-layout/layout-header.module.less.d.ts +18 -0
  158. package/src/components/titan-layout/layout-header.tsx +97 -0
  159. package/src/components/titan-layout/layout-logo.stories.tsx +31 -0
  160. package/src/components/titan-layout/layout-logo.tsx +64 -0
  161. package/src/components/titan-layout/layout-profile.stories.tsx +46 -0
  162. package/src/components/titan-layout/layout-profile.tsx +178 -0
  163. package/src/components/titan-layout/layout-sidebar-links-internal.tsx +278 -0
  164. package/src/components/titan-layout/layout-sidebar-links.tsx +72 -0
  165. package/src/components/titan-layout/layout-sidebar.module.less +536 -0
  166. package/src/components/titan-layout/layout-sidebar.module.less.d.ts +49 -0
  167. package/src/components/titan-layout/layout-sidebar.tsx +304 -0
  168. package/src/components/titan-layout/notifications-context.tsx +44 -0
  169. package/src/components/titan-layout/titan-layout.module.less +106 -0
  170. package/src/components/titan-layout/titan-layout.module.less.d.ts +16 -0
  171. package/src/components/titan-layout/titan-layout.stories.tsx +342 -0
  172. package/src/components/titan-layout/titan-layout.tsx +461 -0
  173. package/src/components/titan-layout/with-tooltip.tsx +16 -0
  174. package/src/index.ts +2 -1
  175. package/src/test/data.tsx +5 -5
  176. package/src/utils/navigation-legacy.ts +3 -1
  177. package/src/utils/use-breakpoint.ts +21 -0
@@ -0,0 +1,461 @@
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 { useNotificationsState } from './notifications-context';
33
+ import * as Styles from './titan-layout.module.less';
34
+
35
+ type TitanLayoutChild = ReactElement<TitanLayoutContentProps> | ReactElement<TitanLayoutLogoProps>;
36
+
37
+ export type TitanLayoutProps = Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'style'> & {
38
+ /** layout appearance */
39
+ appearance?: 'legacy' | 'anvil1' | 'anvil2';
40
+
41
+ /** layout's content */
42
+ children?: TitanLayoutChild | TitanLayoutChild[];
43
+
44
+ /** show only content without side and top bars */
45
+ contentOnly?: boolean;
46
+
47
+ /** component used for navigation */
48
+ navigationComponent?: FC<NavLinkComponentProps>;
49
+
50
+ /** data for main navigation links */
51
+ navigationMainItems?: NavigationItemData[];
52
+
53
+ state?: TitanLayoutState;
54
+ onStateChange?: (state: TitanLayoutState) => void;
55
+
56
+ header?: ReactElement;
57
+ top?: ReactElement;
58
+ sideTop?: ReactElement[];
59
+ profile?: ReactElement;
60
+ extraLinks?: ReactElement;
61
+ extraLinksTop?: ReactElement;
62
+ extraText?: string;
63
+ minContentWidth?: number;
64
+ };
65
+
66
+ const defaultSidebarContext: TitanLayoutSidebarContextType = {
67
+ styles: {
68
+ popoverContent: {
69
+ '--background-color-strong': '#24323C',
70
+ 'color': 'var(--color-white)',
71
+ } as CSSProperties,
72
+ },
73
+ };
74
+
75
+ const useVariant = (appearance: TitanLayoutProps['appearance']) =>
76
+ useMemo(() => {
77
+ const isLegacy = appearance === 'legacy';
78
+ const isAnvil1 = appearance === 'anvil1';
79
+ const isAnvil2 = appearance === 'anvil2';
80
+
81
+ return {
82
+ isLegacy,
83
+ isAnvil1,
84
+ isAnvil2,
85
+ isSequent: isLegacy || isAnvil2,
86
+ };
87
+ }, [appearance]);
88
+
89
+ const useLayoutChildren = (children: TitanLayoutProps['children']) =>
90
+ useMemo(
91
+ () =>
92
+ Children.toArray(children).reduce(
93
+ (out, item) => {
94
+ if (
95
+ item &&
96
+ isValidElement(item) &&
97
+ item.type &&
98
+ typeof item.type !== 'string'
99
+ ) {
100
+ if (item.type.name === TitanLayoutContent.name) {
101
+ out.content = item as any;
102
+ } else if (item.type.name === TitanLayoutLogo.name) {
103
+ out.logo = item as any;
104
+ }
105
+ }
106
+ return out;
107
+ },
108
+ {
109
+ logo: <TitanLayoutLogo />,
110
+ } as {
111
+ content?: ReactElement<TitanLayoutContentProps>;
112
+ logo: ReactElement<TitanLayoutLogoProps>;
113
+ }
114
+ ),
115
+ [children]
116
+ );
117
+
118
+ const TitanLayoutComponent: FC<TitanLayoutProps> = ({
119
+ appearance = 'anvil2',
120
+ id,
121
+ children,
122
+ contentOnly,
123
+ navigationComponent,
124
+ header,
125
+ top,
126
+ profile,
127
+ state,
128
+ onStateChange,
129
+ navigationMainItems,
130
+ extraLinks,
131
+ extraLinksTop,
132
+ extraText,
133
+ minContentWidth,
134
+ sideTop,
135
+ }) => {
136
+ const breakpoint = useTitanBreakpoint();
137
+ const context: TitanLayoutContextType = useMemo(
138
+ () => ({
139
+ NavigationComponent: navigationComponent ?? DefaultNavLinkComponent,
140
+ breakpoint,
141
+ isTitanLayout: true,
142
+ sidebar: defaultSidebarContext,
143
+ }),
144
+ [navigationComponent, breakpoint]
145
+ );
146
+ const variant = useVariant(appearance);
147
+ const [mobileDrawerOpened, setMobileDrawerOpened] = useState(false);
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
+ }, []);
154
+
155
+ const isMobile = breakpoint.isMobile;
156
+ const hasSideBar = !contentOnly && (!!navigationMainItems?.length || !!sideTop?.length);
157
+ const hasTopBar = !contentOnly;
158
+
159
+ useEffect(() => {
160
+ if (variant.isAnvil1) {
161
+ const bodyClassName = 'of-hidden-i';
162
+ document.body.classList.add(bodyClassName);
163
+ return () => document.body.classList.remove(bodyClassName);
164
+ }
165
+ }, [variant.isAnvil1]);
166
+
167
+ const onBurgerClick = useCallback((e: MouseEvent) => {
168
+ setMobileDrawerOpened(true);
169
+ e.stopPropagation();
170
+ }, []);
171
+
172
+ const onBarExpandChange = useCallback(
173
+ (expanded: boolean) => {
174
+ if (isMobile) {
175
+ setMobileDrawerOpened(false);
176
+ } else {
177
+ onStateChange?.({ navCollapsed: !expanded });
178
+ }
179
+ },
180
+ [onStateChange, isMobile]
181
+ );
182
+ const onSubmenuExpandChange = useCallback(
183
+ (id: string, expanded: boolean) => {
184
+ onStateChange?.({
185
+ navCollapsed: state?.navCollapsed ?? false,
186
+ submenuExpanded: expanded ? id : undefined,
187
+ });
188
+ },
189
+ [state, onStateChange]
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]);
233
+
234
+ const layoutClass = variant.isLegacy
235
+ ? Styles.layoutLegacy
236
+ : variant.isAnvil1
237
+ ? Styles.layoutAnvil1
238
+ : Styles.layoutAnvil2;
239
+
240
+ return (
241
+ <LayoutContext.Provider value={context}>
242
+ <LayoutPlacementContext.Provider value="unset">
243
+ <div
244
+ id={id}
245
+ className={classNames(
246
+ Styles.layout,
247
+ isMobile ? Styles.layoutMobile : Styles.layoutDesktop,
248
+ {
249
+ [Styles.layoutTop]: hasTopBar,
250
+ [Styles.layoutNavSlim]: !isMobile && hasSideBar && state?.navCollapsed,
251
+ [Styles.layoutNavWide]: !isMobile && hasSideBar && !state?.navCollapsed,
252
+ },
253
+ layoutClass
254
+ )}
255
+ style={contentStyles}
256
+ >
257
+ {variant.isSequent && <div className={Styles.topPlaceholder} />}
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={
266
+ <Fragment>
267
+ {extraLinksTop}
268
+ {!isMobile && extraLinks}
269
+ </Fragment>
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
+ )}
330
+ </div>
331
+ </LayoutPlacementContext.Provider>
332
+ </LayoutContext.Provider>
333
+ );
334
+ };
335
+
336
+ const TitanLayoutHeaderObserved: FC<{
337
+ children: ReactNode;
338
+ heightChange?(value: number): void;
339
+ }> = ({ children, heightChange }) => {
340
+ const ref = useRef<HTMLDivElement>(null);
341
+
342
+ useEffect(() => {
343
+ if (ref.current) {
344
+ const updatePosition = () => {
345
+ if (ref.current && heightChange) {
346
+ const pos = ref.current.getBoundingClientRect();
347
+ heightChange(pos.height);
348
+ }
349
+ };
350
+
351
+ const observer = new ResizeObserver(updatePosition);
352
+ observer.observe(ref.current);
353
+
354
+ updatePosition();
355
+ return () => observer.disconnect();
356
+ }
357
+ }, [heightChange]);
358
+
359
+ useEffect(() => {
360
+ return () => {
361
+ heightChange?.(0);
362
+ };
363
+ }, [heightChange]);
364
+ return (
365
+ <div ref={ref} className={Styles.contentHeader} data-cy="layout-content-header">
366
+ {children}
367
+ </div>
368
+ );
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
+ };
377
+
378
+ export interface TitanLayoutContentProps {
379
+ children: ReactNode;
380
+ }
381
+ const TitanLayoutContent: FC<TitanLayoutContentProps> = ({ children }) => children;
382
+
383
+ const LayoutContentAnvil2: FC<{
384
+ children: ReactNode;
385
+ header?: ReactNode;
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
+ };
397
+
398
+ const LayoutContentAnvil1: FC<{
399
+ children: ReactNode;
400
+ header?: ReactNode;
401
+ minWidth?: number;
402
+ }> = ({ children, header, minWidth }) => {
403
+ const innerContentStyles: CSSProperties = useMemo(
404
+ () => ({
405
+ ...(minWidth ? { minWidth: `${minWidth}px` } : {}),
406
+ }),
407
+ [minWidth]
408
+ );
409
+
410
+ return (
411
+ <Fragment>
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>
423
+ </div>
424
+ </Fragment>
425
+ );
426
+ };
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
+
456
+ export const TitanLayout = Object.assign(TitanLayoutComponent, {
457
+ Content: TitanLayoutContent,
458
+ Logo: TitanLayoutLogo,
459
+ Link: TitanLayoutSidebarLink,
460
+ Trigger: TitanLayoutSidebarTrigger,
461
+ });
@@ -0,0 +1,16 @@
1
+ import { Tooltip, TooltipProps } from '@servicetitan/anvil2';
2
+ import { ReactElement } from 'react';
3
+
4
+ export const withTooltip = (
5
+ element: ReactElement,
6
+ tooltip: string | undefined,
7
+ placement: TooltipProps['placement'] = 'bottom'
8
+ ) =>
9
+ tooltip ? (
10
+ <Tooltip placement={placement}>
11
+ <Tooltip.Content>{tooltip}</Tooltip.Content>
12
+ <Tooltip.Trigger>{element}</Tooltip.Trigger>
13
+ </Tooltip>
14
+ ) : (
15
+ element
16
+ );
package/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  export * from './components/header-navigation';
2
- export * from './components/profile-dropdown/profile-dropdown';
3
2
  export * from './components/logo/logo-company-title';
4
3
  export * from './components/logo/logo-titan';
5
4
  export * from './components/logo/logo-titan-text';
6
5
  export * from './components/counter-tag';
7
6
  export * from './components/left-navigation';
7
+ export * from './components/titan-layout';
8
8
  export * from './components/links';
9
9
  export * from './utils/navigation';
10
10
  export {
@@ -17,3 +17,4 @@ export {
17
17
  NavigationActiveLinkMatcher,
18
18
  } from './utils/navigation-context';
19
19
  export * from './utils/navigation-legacy';
20
+ export * from './utils/use-breakpoint';
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 }) => {
@@ -154,7 +153,7 @@ const getItem = (
154
153
  hint: id,
155
154
  icon: undefined,
156
155
  iconActive: undefined,
157
- ...(data ?? {}),
156
+ ...data,
158
157
  submenu: data.submenu
159
158
  ? {
160
159
  ...data.submenu,
@@ -176,7 +175,7 @@ const getSubItem = (
176
175
  id,
177
176
  to: id,
178
177
  title: id[0].toUpperCase() + id.substring(1),
179
- ...(data ?? {}),
178
+ ...data,
180
179
  });
181
180
 
182
181
  const getGroup = (
@@ -339,6 +338,7 @@ export const CallsNavigationTrigger = () => {
339
338
  id="dialpad"
340
339
  iconName="phone"
341
340
  counter={2}
341
+ title="Calls"
342
342
  icon={SvgPhone}
343
343
  iconActive={SvgPhoneActive}
344
344
  onClick={() => setOpen(!open)}
@@ -54,6 +54,8 @@ export interface HeaderNavigationItemLinkProps {
54
54
  export interface HeaderNavigationTriggerPropsStrict {
55
55
  /** unique identifier */
56
56
  id: string;
57
+ /** item title (used for mobile) */
58
+ title: string;
57
59
  /** tooltip text */
58
60
  tooltip?: string;
59
61
  /** item description */
@@ -80,7 +82,7 @@ export interface HeaderNavigationTriggerPropsStrict {
80
82
  /** svg icon (anvil2) of inactive item */
81
83
  icon: IconProps['svg'] | undefined;
82
84
  /** svg icon (anvil2) of active item */
83
- iconActive: IconProps['svg'] | undefined;
85
+ iconActive?: IconProps['svg'];
84
86
  }
85
87
 
86
88
  export interface HeaderNavigationTriggerProps extends HeaderNavigationTriggerPropsStrict {
@@ -0,0 +1,21 @@
1
+ import { BreakpointReturnProps, useBreakpoint } from '@servicetitan/anvil2';
2
+ import { useMemo } from 'react';
3
+
4
+ export interface TitanBreakpoint {
5
+ name: BreakpointReturnProps['name'];
6
+ isMobile: boolean;
7
+ width: number;
8
+ }
9
+
10
+ export const useTitanBreakpoint = (): TitanBreakpoint => {
11
+ const breakpoint = useBreakpoint();
12
+
13
+ return useMemo(
14
+ () => ({
15
+ name: breakpoint?.name ?? 'xl',
16
+ isMobile: breakpoint ? breakpoint.innerWidth < 768 : false,
17
+ width: breakpoint?.innerWidth ?? 0,
18
+ }),
19
+ [breakpoint]
20
+ );
21
+ };