@servicetitan/navigation 10.7.0 → 11.0.0-canary.237.0a4df1d.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 (162) 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/links.d.ts.map +1 -1
  21. package/dist/components/links.js +7 -7
  22. package/dist/components/links.js.map +1 -1
  23. package/dist/components/logo/logo-company-title.d.ts +1 -0
  24. package/dist/components/logo/logo-company-title.d.ts.map +1 -1
  25. package/dist/components/logo/logo-company-title.js +2 -2
  26. package/dist/components/logo/logo-company-title.js.map +1 -1
  27. package/dist/components/logo/logo-titan-text.d.ts +1 -1
  28. package/dist/components/logo/logo-titan-text.d.ts.map +1 -1
  29. package/dist/components/profile-dropdown/profile-dropdown.d.ts +17 -9
  30. package/dist/components/profile-dropdown/profile-dropdown.d.ts.map +1 -1
  31. package/dist/components/profile-dropdown/profile-dropdown.js +11 -9
  32. package/dist/components/profile-dropdown/profile-dropdown.js.map +1 -1
  33. package/dist/components/profile-dropdown/profile-dropdown.module.less +24 -6
  34. package/dist/components/profile-dropdown/profile-dropdown.stories.js +2 -2
  35. package/dist/components/profile-dropdown/profile-dropdown.stories.js.map +1 -1
  36. package/dist/components/titan-layout/index.d.ts +6 -0
  37. package/dist/components/titan-layout/index.d.ts.map +1 -0
  38. package/dist/components/titan-layout/index.js +6 -0
  39. package/dist/components/titan-layout/index.js.map +1 -0
  40. package/dist/components/titan-layout/interface-internal.d.ts +6 -0
  41. package/dist/components/titan-layout/interface-internal.d.ts.map +1 -0
  42. package/dist/components/titan-layout/interface-internal.js +2 -0
  43. package/dist/components/titan-layout/interface-internal.js.map +1 -0
  44. package/dist/components/titan-layout/interface.d.ts +21 -0
  45. package/dist/components/titan-layout/interface.d.ts.map +1 -0
  46. package/dist/components/titan-layout/interface.js +2 -0
  47. package/dist/components/titan-layout/interface.js.map +1 -0
  48. package/dist/components/titan-layout/layout-context.d.ts +20 -0
  49. package/dist/components/titan-layout/layout-context.d.ts.map +1 -0
  50. package/dist/components/titan-layout/layout-context.js +12 -0
  51. package/dist/components/titan-layout/layout-context.js.map +1 -0
  52. package/dist/components/titan-layout/layout-header-links.d.ts +7 -0
  53. package/dist/components/titan-layout/layout-header-links.d.ts.map +1 -0
  54. package/dist/components/titan-layout/layout-header-links.js +32 -0
  55. package/dist/components/titan-layout/layout-header-links.js.map +1 -0
  56. package/dist/components/titan-layout/layout-header.d.ts +22 -0
  57. package/dist/components/titan-layout/layout-header.d.ts.map +1 -0
  58. package/dist/components/titan-layout/layout-header.js +10 -0
  59. package/dist/components/titan-layout/layout-header.js.map +1 -0
  60. package/dist/components/titan-layout/layout-header.module.less +193 -0
  61. package/dist/components/titan-layout/layout-logo.d.ts +12 -0
  62. package/dist/components/titan-layout/layout-logo.d.ts.map +1 -0
  63. package/dist/components/titan-layout/layout-logo.js +16 -0
  64. package/dist/components/titan-layout/layout-logo.js.map +1 -0
  65. package/dist/components/titan-layout/layout-logo.stories.d.ts +13 -0
  66. package/dist/components/titan-layout/layout-logo.stories.d.ts.map +1 -0
  67. package/dist/components/titan-layout/layout-logo.stories.js +17 -0
  68. package/dist/components/titan-layout/layout-logo.stories.js.map +1 -0
  69. package/dist/components/titan-layout/layout-profile.d.ts +9 -0
  70. package/dist/components/titan-layout/layout-profile.d.ts.map +1 -0
  71. package/dist/components/titan-layout/layout-profile.js +72 -0
  72. package/dist/components/titan-layout/layout-profile.js.map +1 -0
  73. package/dist/components/titan-layout/layout-profile.stories.d.ts +13 -0
  74. package/dist/components/titan-layout/layout-profile.stories.d.ts.map +1 -0
  75. package/dist/components/titan-layout/layout-profile.stories.js +13 -0
  76. package/dist/components/titan-layout/layout-profile.stories.js.map +1 -0
  77. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts +46 -0
  78. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts.map +1 -0
  79. package/dist/components/titan-layout/layout-sidebar-links-internal.js +61 -0
  80. package/dist/components/titan-layout/layout-sidebar-links-internal.js.map +1 -0
  81. package/dist/components/titan-layout/layout-sidebar-links.d.ts +6 -0
  82. package/dist/components/titan-layout/layout-sidebar-links.d.ts.map +1 -0
  83. package/dist/components/titan-layout/layout-sidebar-links.js +28 -0
  84. package/dist/components/titan-layout/layout-sidebar-links.js.map +1 -0
  85. package/dist/components/titan-layout/layout-sidebar.d.ts +19 -0
  86. package/dist/components/titan-layout/layout-sidebar.d.ts.map +1 -0
  87. package/dist/components/titan-layout/layout-sidebar.js +67 -0
  88. package/dist/components/titan-layout/layout-sidebar.js.map +1 -0
  89. package/dist/components/titan-layout/layout-sidebar.module.less +536 -0
  90. package/dist/components/titan-layout/notifications-context.d.ts +13 -0
  91. package/dist/components/titan-layout/notifications-context.d.ts.map +1 -0
  92. package/dist/components/titan-layout/notifications-context.js +23 -0
  93. package/dist/components/titan-layout/notifications-context.js.map +1 -0
  94. package/dist/components/titan-layout/titan-layout.d.ts +40 -0
  95. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -0
  96. package/dist/components/titan-layout/titan-layout.js +192 -0
  97. package/dist/components/titan-layout/titan-layout.js.map +1 -0
  98. package/dist/components/titan-layout/titan-layout.module.less +106 -0
  99. package/dist/components/titan-layout/titan-layout.stories.d.ts +22 -0
  100. package/dist/components/titan-layout/titan-layout.stories.d.ts.map +1 -0
  101. package/dist/components/titan-layout/titan-layout.stories.js +83 -0
  102. package/dist/components/titan-layout/titan-layout.stories.js.map +1 -0
  103. package/dist/components/titan-layout/with-tooltip.d.ts +4 -0
  104. package/dist/components/titan-layout/with-tooltip.d.ts.map +1 -0
  105. package/dist/components/titan-layout/with-tooltip.js +4 -0
  106. package/dist/components/titan-layout/with-tooltip.js.map +1 -0
  107. package/dist/index.d.ts +2 -1
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +2 -1
  110. package/dist/index.js.map +1 -1
  111. package/dist/test/data.d.ts +4 -1
  112. package/dist/test/data.d.ts.map +1 -1
  113. package/dist/test/data.js +5 -6
  114. package/dist/test/data.js.map +1 -1
  115. package/dist/utils/navigation-legacy.d.ts +3 -1
  116. package/dist/utils/navigation-legacy.d.ts.map +1 -1
  117. package/dist/utils/use-breakpoint.d.ts +8 -0
  118. package/dist/utils/use-breakpoint.d.ts.map +1 -0
  119. package/dist/utils/use-breakpoint.js +14 -0
  120. package/dist/utils/use-breakpoint.js.map +1 -0
  121. package/package.json +5 -6
  122. package/src/components/badge-tag.tsx +1 -1
  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/side-navigation-links.tsx +1 -1
  129. package/src/components/links.tsx +33 -13
  130. package/src/components/logo/logo-company-title.tsx +8 -6
  131. package/src/components/logo/logo-titan-text.tsx +1 -1
  132. package/src/components/profile-dropdown/profile-dropdown.module.less +24 -6
  133. package/src/components/profile-dropdown/profile-dropdown.module.less.d.ts +2 -0
  134. package/src/components/profile-dropdown/profile-dropdown.stories.tsx +4 -4
  135. package/src/components/profile-dropdown/profile-dropdown.tsx +87 -55
  136. package/src/components/titan-layout/index.ts +5 -0
  137. package/src/components/titan-layout/interface-internal.ts +6 -0
  138. package/src/components/titan-layout/interface.ts +26 -0
  139. package/src/components/titan-layout/layout-context.tsx +30 -0
  140. package/src/components/titan-layout/layout-header-links.tsx +145 -0
  141. package/src/components/titan-layout/layout-header.module.less +193 -0
  142. package/src/components/titan-layout/layout-header.module.less.d.ts +18 -0
  143. package/src/components/titan-layout/layout-header.tsx +97 -0
  144. package/src/components/titan-layout/layout-logo.stories.tsx +31 -0
  145. package/src/components/titan-layout/layout-logo.tsx +64 -0
  146. package/src/components/titan-layout/layout-profile.stories.tsx +46 -0
  147. package/src/components/titan-layout/layout-profile.tsx +180 -0
  148. package/src/components/titan-layout/layout-sidebar-links-internal.tsx +278 -0
  149. package/src/components/titan-layout/layout-sidebar-links.tsx +72 -0
  150. package/src/components/titan-layout/layout-sidebar.module.less +536 -0
  151. package/src/components/titan-layout/layout-sidebar.module.less.d.ts +49 -0
  152. package/src/components/titan-layout/layout-sidebar.tsx +304 -0
  153. package/src/components/titan-layout/notifications-context.tsx +44 -0
  154. package/src/components/titan-layout/titan-layout.module.less +106 -0
  155. package/src/components/titan-layout/titan-layout.module.less.d.ts +16 -0
  156. package/src/components/titan-layout/titan-layout.stories.tsx +346 -0
  157. package/src/components/titan-layout/titan-layout.tsx +457 -0
  158. package/src/components/titan-layout/with-tooltip.tsx +16 -0
  159. package/src/index.ts +2 -1
  160. package/src/test/data.tsx +5 -5
  161. package/src/utils/navigation-legacy.ts +3 -1
  162. package/src/utils/use-breakpoint.ts +21 -0
@@ -0,0 +1,97 @@
1
+ import SvgBurgerMenu from '@servicetitan/anvil2/assets/icons/material/round/menu.svg';
2
+ import classNames from 'classnames';
3
+ import { ComponentPropsWithoutRef, FC, ReactElement, ReactNode } from 'react';
4
+ import { LayoutPlacementContext } from './layout-context';
5
+ import { LayoutHeaderNavigationTrigger } from './layout-header-links';
6
+ import * as Styles from './layout-header.module.less';
7
+ import { TitanLayoutLogoProps } from './layout-logo';
8
+
9
+ export interface TitanLayoutHeaderLogoProps {
10
+ title?: true | string;
11
+ mantleFill?: string;
12
+ to?: string;
13
+ }
14
+
15
+ export type LayoutHeaderProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'> & {
16
+ right?: ReactNode;
17
+ rightText?: string;
18
+ rightClassName?: string;
19
+
20
+ id?: string;
21
+
22
+ center?: ReactElement;
23
+ centerClassName?: string;
24
+
25
+ logo: ReactElement<TitanLayoutLogoProps>;
26
+ profile?: ReactElement;
27
+
28
+ isMobile: boolean;
29
+ hasNotifications: boolean;
30
+ onBurgerClick?: (e: MouseEvent) => void;
31
+ };
32
+
33
+ export const LayoutHeader: FC<LayoutHeaderProps> = ({
34
+ className,
35
+ right,
36
+ rightText,
37
+ rightClassName,
38
+ center,
39
+ centerClassName,
40
+ isMobile,
41
+ hasNotifications,
42
+ logo,
43
+ profile,
44
+ onBurgerClick,
45
+ ...rest
46
+ }) => {
47
+ return (
48
+ <LayoutPlacementContext.Provider value="top">
49
+ <div
50
+ className={classNames(
51
+ Styles.header,
52
+ isMobile ? Styles.headerMobile : Styles.headerDesktop,
53
+ className
54
+ )}
55
+ {...rest}
56
+ data-cy="header-navigation"
57
+ >
58
+ <div className={classNames(Styles.heTopLeft)} data-cy="navigation-left">
59
+ {isMobile && (
60
+ <LayoutHeaderNavigationTrigger
61
+ id="burger"
62
+ title=""
63
+ icon={SvgBurgerMenu}
64
+ iconActive={SvgBurgerMenu}
65
+ className="m-r-1"
66
+ onClick={onBurgerClick}
67
+ tag={{ value: hasNotifications }}
68
+ />
69
+ )}
70
+ {logo}
71
+ </div>
72
+ <div
73
+ className={classNames(
74
+ Styles.heTopCenter,
75
+ 'd-f align-items-center justify-content-center',
76
+ centerClassName
77
+ )}
78
+ data-cy="navigation-center"
79
+ >
80
+ {center}
81
+ </div>
82
+ <div
83
+ className={classNames(
84
+ 'd-f flex-row justify-content-end align-items-center',
85
+ Styles.heTopRight,
86
+ rightClassName
87
+ )}
88
+ data-cy="navigation-right"
89
+ >
90
+ {!!rightText && <div className={Styles.heTopRightText}>{rightText}</div>}
91
+ {right}
92
+ {profile}
93
+ </div>
94
+ </div>
95
+ </LayoutPlacementContext.Provider>
96
+ );
97
+ };
@@ -0,0 +1,31 @@
1
+ import { Chip } from '@servicetitan/anvil2';
2
+ import { ReactElement } from 'react';
3
+ import { withAnvil, withDefaultRedirects, withMemoryRouter } from '../../test/data';
4
+ import { TitanLayoutLogo, TitanLayoutLogoProps } from './layout-logo';
5
+ import { TitanLayout } from './titan-layout';
6
+
7
+ const withTitanLayout = (element: ReactElement<TitanLayoutLogoProps>) => () => (
8
+ <TitanLayout navigationMainItems={[]}>
9
+ {element}
10
+ <TitanLayout.Content>logo</TitanLayout.Content>
11
+ </TitanLayout>
12
+ );
13
+
14
+ export default {
15
+ title: 'Navigation/TitanLayoutLogo',
16
+ component: TitanLayoutLogo,
17
+ decorators: [withDefaultRedirects, withMemoryRouter, withAnvil],
18
+ parameters: {},
19
+ };
20
+
21
+ export const LogoDefault = withTitanLayout(<TitanLayoutLogo />);
22
+
23
+ export const LogoCompanyTitle = withTitanLayout(<TitanLayoutLogo title />);
24
+
25
+ export const LogoCommercial = withTitanLayout(
26
+ <TitanLayoutLogo title="Commercial" mantleFill="#2270EE" />
27
+ );
28
+
29
+ export const LogoWithPostfix = withTitanLayout(
30
+ <TitanLayoutLogo title postfix={<Chip className="m-l-2-i" label="demo" />} />
31
+ );
@@ -0,0 +1,64 @@
1
+ import classNames from 'classnames';
2
+ import { FC, Fragment, ReactNode } from 'react';
3
+ import { LogoCompanyTitle } from '../logo/logo-company-title';
4
+ import { LogoTitan, LogoTitanTitle, WrapperProps } from '../logo/logo-titan-text';
5
+ import { useTitanLayoutContext } from './layout-context';
6
+
7
+ export interface TitanLayoutLogoProps {
8
+ /** container class name */
9
+ className?: string;
10
+
11
+ title?: string | boolean;
12
+
13
+ postfix?: ReactNode;
14
+
15
+ logoWrapper?: WrapperProps;
16
+
17
+ mantleFill?: string;
18
+ }
19
+
20
+ const EmptyWrapper: FC<any> = ({ children }) => children;
21
+
22
+ export const TitanLayoutLogo: FC<TitanLayoutLogoProps> = ({
23
+ className,
24
+ mantleFill,
25
+ postfix,
26
+ title,
27
+ logoWrapper = EmptyWrapper,
28
+ }) => {
29
+ const {
30
+ breakpoint: { isMobile },
31
+ } = useTitanLayoutContext();
32
+
33
+ const Wrapper = logoWrapper;
34
+ const logoSize = isMobile ? 44 : 56;
35
+ const logoCompanySize = 48;
36
+ const showCompanyTitle = title === true && !isMobile;
37
+
38
+ return (
39
+ <div
40
+ className={classNames(
41
+ 'd-f align-items-center',
42
+ { 'p-t-half': showCompanyTitle },
43
+ className
44
+ )}
45
+ >
46
+ {showCompanyTitle ? (
47
+ <Wrapper>
48
+ <LogoCompanyTitle height={logoCompanySize} />
49
+ </Wrapper>
50
+ ) : typeof title === 'string' ? (
51
+ <Fragment>
52
+ <LogoTitan size={logoSize} mantleFill={mantleFill} logoWrapper={Wrapper} />
53
+ {!isMobile && (
54
+ <LogoTitanTitle className="c-inherit m-l-1">{title}</LogoTitanTitle>
55
+ )}
56
+ </Fragment>
57
+ ) : (
58
+ <LogoTitan size={logoSize} mantleFill={mantleFill} logoWrapper={Wrapper} />
59
+ )}
60
+
61
+ {!isMobile && postfix}
62
+ </div>
63
+ );
64
+ };
@@ -0,0 +1,46 @@
1
+ import { ReactElement } from 'react';
2
+ import { withAnvil, withDefaultRedirects, withMemoryRouter } from '../../test/data';
3
+ import { ProfileDropdown } from './layout-profile';
4
+ import { TitanLayout } from './titan-layout';
5
+
6
+ const withTitanLayout = (element: ReactElement) => () => (
7
+ <TitanLayout navigationMainItems={[]} profile={element}>
8
+ <TitanLayout.Content>profile</TitanLayout.Content>
9
+ </TitanLayout>
10
+ );
11
+
12
+ export default {
13
+ title: 'Navigation/TitanLayoutProfile',
14
+ component: ProfileDropdown,
15
+ decorators: [withDefaultRedirects, withMemoryRouter, withAnvil],
16
+ parameters: {},
17
+ };
18
+
19
+ export const ProfileDefault = withTitanLayout(
20
+ <ProfileDropdown>
21
+ <ProfileDropdown.Link id="first" to="https://google.com" external>
22
+ first link
23
+ </ProfileDropdown.Link>
24
+ <ProfileDropdown.Section id="second" onClick={() => alert('second click')}>
25
+ second link
26
+ </ProfileDropdown.Section>
27
+ <ProfileDropdown.Divider />
28
+ <ProfileDropdown.Section id="content">some content</ProfileDropdown.Section>
29
+ <ProfileDropdown.Divider />
30
+ <ProfileDropdown.Divider />
31
+ <ProfileDropdown.Divider />
32
+ <ProfileDropdown.Link id="third" to="third">
33
+ third link
34
+ </ProfileDropdown.Link>
35
+ <ProfileDropdown.Divider />
36
+ <ProfileDropdown.Section
37
+ id="forth"
38
+ onClick={() => alert('forth click')}
39
+ text="Sign Out user"
40
+ >
41
+ Sign Out
42
+ <span className="c-neutral-60 m-l-1">user</span>
43
+ </ProfileDropdown.Section>
44
+ <ProfileDropdown.Divider />
45
+ </ProfileDropdown>
46
+ );
@@ -0,0 +1,180 @@
1
+ import SvgAccountActive from '@servicetitan/anvil2/assets/icons/st/gnav_account_active.svg';
2
+ import SvgAccountInactive from '@servicetitan/anvil2/assets/icons/st/gnav_account_inactive.svg';
3
+
4
+ import { FC, MouseEvent, useEffect, useState } from 'react';
5
+ import { NavLinkComponentProps, NavigationComponentContext } from '../../utils/navigation-context';
6
+ import {
7
+ ProfileDropdown as DesktopProfileDropdown,
8
+ ProfileDropdownLinkProps,
9
+ ProfileDropdownProps,
10
+ ProfileDropdownSectionProps,
11
+ } from '../profile-dropdown/profile-dropdown';
12
+ import { NavigationComponentProps } from './interface-internal';
13
+ import { useTitanLayoutContext } from './layout-context';
14
+ import {
15
+ InternalSideNavigationGroup,
16
+ InternalSideNavigationGroupDivider,
17
+ InternalSideNavigationGroupLink,
18
+ InternalSideNavigationGroupTrigger,
19
+ } from './layout-sidebar-links-internal';
20
+ import { useNotificationsContext, useNotificationsState } from './notifications-context';
21
+
22
+ export type {
23
+ ProfileDropdownProps,
24
+ ProfileDropdownSectionProps,
25
+ ProfileDropdownLinkProps,
26
+ } from '../profile-dropdown/profile-dropdown';
27
+
28
+ const ExternalNavComponent: FC<NavLinkComponentProps> = ({
29
+ children,
30
+ isActive,
31
+ to,
32
+ activeClassName,
33
+ ...props
34
+ }) => (
35
+ <a {...props} href={to}>
36
+ {children}
37
+ </a>
38
+ );
39
+
40
+ const ProfileDropdownContent: FC<ProfileDropdownProps> = props => {
41
+ const { isTitanLayout, breakpoint, NavigationComponent } = useTitanLayoutContext();
42
+ return breakpoint.isMobile ? (
43
+ <MobileProfileDropdown {...props} navigationComponent={NavigationComponent} />
44
+ ) : isTitanLayout ? (
45
+ <NavigationComponentContext.Provider value={NavigationComponent}>
46
+ <DesktopProfileDropdown {...props} />
47
+ </NavigationComponentContext.Provider>
48
+ ) : (
49
+ <DesktopProfileDropdown {...props} />
50
+ );
51
+ };
52
+ ProfileDropdownContent.displayName = 'ProfileDropdown';
53
+
54
+ const MobileProfileDropdown: FC<ProfileDropdownProps & NavigationComponentProps> = ({
55
+ children,
56
+ ...props
57
+ }) => {
58
+ const id = '__profile';
59
+ const [expanded, setExpanded] = useState(false);
60
+ const { hasNotifications, NotificationsContextProvider } = useNotificationsState();
61
+ const { onNotificationsUpdate } = useNotificationsContext();
62
+ const onExpandToggle = (e: MouseEvent<never>) => {
63
+ e.stopPropagation();
64
+ setExpanded(!expanded);
65
+ };
66
+
67
+ useEffect(() => {
68
+ onNotificationsUpdate(id, hasNotifications);
69
+ }, [hasNotifications, onNotificationsUpdate]);
70
+
71
+ return (
72
+ <NotificationsContextProvider>
73
+ <InternalSideNavigationGroup
74
+ id={id}
75
+ to={undefined}
76
+ title="Profile"
77
+ icon={SvgAccountInactive}
78
+ iconActive={SvgAccountActive}
79
+ isActive={expanded}
80
+ {...props}
81
+ submenuExpanded={expanded}
82
+ onExpandToggle={onExpandToggle}
83
+ onClick={onExpandToggle}
84
+ tag={{ value: hasNotifications }}
85
+ >
86
+ {children}
87
+ </InternalSideNavigationGroup>
88
+ </NotificationsContextProvider>
89
+ );
90
+ };
91
+
92
+ const ProfileDropdownDivider: FC = () => {
93
+ const { breakpoint } = useTitanLayoutContext();
94
+ return breakpoint.isMobile ? (
95
+ <InternalSideNavigationGroupDivider />
96
+ ) : (
97
+ <DesktopProfileDropdown.Divider />
98
+ );
99
+ };
100
+
101
+ const getText = (children: any, text: any): string | undefined => {
102
+ if (typeof children === 'string') {
103
+ return children;
104
+ }
105
+
106
+ if (typeof text === 'string') {
107
+ return text;
108
+ }
109
+
110
+ return undefined;
111
+ };
112
+
113
+ const getTag = (
114
+ tag: ProfileDropdownLinkProps['tag'],
115
+ counter: ProfileDropdownLinkProps['counter']
116
+ ): boolean => {
117
+ return !!tag?.value || !!counter;
118
+ };
119
+
120
+ const ProfileDropdownSection: FC<ProfileDropdownSectionProps> = props => {
121
+ const { breakpoint } = useTitanLayoutContext();
122
+ return breakpoint.isMobile ? (
123
+ <MobileProfileDropdownSection {...props} />
124
+ ) : (
125
+ <DesktopProfileDropdown.Section {...props} />
126
+ );
127
+ };
128
+ const MobileProfileDropdownSection: FC<ProfileDropdownSectionProps> = ({
129
+ children,
130
+ text,
131
+ tooltip,
132
+ title,
133
+ ...props
134
+ }) => {
135
+ const sectionText = getText(children, text);
136
+ const { onNotificationsUpdate } = useNotificationsContext();
137
+ onNotificationsUpdate(props.id, getTag(props.tag, props.counter));
138
+
139
+ return sectionText ? (
140
+ <InternalSideNavigationGroupTrigger {...props} title={sectionText} />
141
+ ) : null;
142
+ };
143
+
144
+ const ProfileDropdownLink: FC<ProfileDropdownLinkProps> = props => {
145
+ const { breakpoint, NavigationComponent } = useTitanLayoutContext();
146
+ return breakpoint.isMobile ? (
147
+ <MobileProfileDropdownLink {...props} navigationComponent={NavigationComponent} />
148
+ ) : (
149
+ <DesktopProfileDropdown.Link {...props} />
150
+ );
151
+ };
152
+ const MobileProfileDropdownLink: FC<ProfileDropdownLinkProps & NavigationComponentProps> = ({
153
+ external,
154
+ to,
155
+ tooltip,
156
+ text,
157
+ children,
158
+ navigationComponent,
159
+ ...props
160
+ }) => {
161
+ const { onNotificationsUpdate } = useNotificationsContext();
162
+ const linkText = getText(children, text);
163
+ const isExternalLink = external ?? to?.startsWith('http');
164
+ onNotificationsUpdate(props.id, getTag(props.tag, props.counter));
165
+
166
+ return linkText ? (
167
+ <InternalSideNavigationGroupLink
168
+ {...props}
169
+ to={to}
170
+ title={linkText}
171
+ navigationComponent={isExternalLink ? ExternalNavComponent : navigationComponent}
172
+ />
173
+ ) : null;
174
+ };
175
+
176
+ export const ProfileDropdown = Object.assign(ProfileDropdownContent, {
177
+ Divider: ProfileDropdownDivider,
178
+ Link: ProfileDropdownLink,
179
+ Section: ProfileDropdownSection,
180
+ });
@@ -0,0 +1,278 @@
1
+ import { Icon } from '@servicetitan/anvil2';
2
+ import SvgGroupCollapse from '@servicetitan/anvil2/assets/icons/material/round/expand_less.svg';
3
+ import SvgGroupExpand from '@servicetitan/anvil2/assets/icons/material/round/expand_more.svg';
4
+
5
+ import classNames from 'classnames';
6
+ import { FC, Fragment, MouseEvent, ReactNode } from 'react';
7
+ import { NavigationItemData, NavigationSubmenuItemData } from '../../utils/navigation';
8
+ import { getCounterTag } from '../../utils/side-nav';
9
+ import { BadgeTag, BadgeTagProps } from '../badge-tag';
10
+ import { TitanLayoutSidebarTriggerProps } from './interface';
11
+ import { NavigationComponentProps } from './interface-internal';
12
+ import * as Styles from './layout-sidebar.module.less';
13
+
14
+ export interface InternalSideNavigationItemContentProps
15
+ extends Omit<NavigationItemData, 'iconName' | 'to' | 'counter' | 'tag'> {
16
+ submenuExpanded: boolean | undefined;
17
+ tag: BadgeTagProps | undefined;
18
+ onExpandToggle?: (e: MouseEvent<never>) => void;
19
+ }
20
+
21
+ export const InternalSideNavigationItemContent: FC<InternalSideNavigationItemContentProps> = ({
22
+ icon,
23
+ iconActive,
24
+ iconClassName,
25
+ iconComponent: IconComponent,
26
+ tag,
27
+ title,
28
+ submenuExpanded,
29
+ onExpandToggle,
30
+ }) => (
31
+ <Fragment>
32
+ <div className={Styles.navItemIconWrapper}>
33
+ {IconComponent ? (
34
+ <i className={classNames(Styles.navIcon, iconClassName)}>
35
+ <IconComponent />
36
+ </i>
37
+ ) : (
38
+ <Fragment>
39
+ {icon && (
40
+ <Icon
41
+ svg={icon}
42
+ className={classNames(
43
+ Styles.navIcon,
44
+ Styles.navIconInactive,
45
+ iconClassName
46
+ )}
47
+ />
48
+ )}
49
+ {iconActive && (
50
+ <Icon
51
+ svg={iconActive}
52
+ className={classNames(
53
+ Styles.navIcon,
54
+ Styles.navIconActive,
55
+ iconClassName
56
+ )}
57
+ />
58
+ )}
59
+ </Fragment>
60
+ )}
61
+
62
+ <div className={Styles.navItemTextExpanded}>{title}</div>
63
+ {!!tag && (
64
+ <BadgeTag
65
+ data={tag}
66
+ className={Styles.navItemCounter}
67
+ longClassName={Styles.navItemCounterLong}
68
+ />
69
+ )}
70
+ {typeof submenuExpanded === 'boolean' && (
71
+ <div className={Styles.navItemGroupToggleWrapper}>
72
+ <Icon
73
+ svg={submenuExpanded ? SvgGroupCollapse : SvgGroupExpand}
74
+ className={Styles.navItemGroupToggle}
75
+ onClick={onExpandToggle}
76
+ />
77
+ <div className={Styles.navItemGroupToggleClick} onClick={onExpandToggle} />
78
+ </div>
79
+ )}
80
+ </div>
81
+
82
+ <div
83
+ className={classNames(Styles.navItemTextCollapsed, {
84
+ [Styles.navItemTextSmall]: !!title && title.length >= 10,
85
+ })}
86
+ >
87
+ {title}
88
+ </div>
89
+ </Fragment>
90
+ );
91
+
92
+ export interface InternalSideNavigationLinkProps
93
+ extends Omit<NavigationItemData, 'iconName' | 'counter' | 'tag'>,
94
+ NavigationComponentProps {
95
+ submenuExpanded: boolean | undefined;
96
+ dataPrefix?: string;
97
+ tag: BadgeTagProps | undefined;
98
+ onExpandToggle?: (e: MouseEvent<never>) => void;
99
+ }
100
+
101
+ export const internalNavigationContentContainerProps = ({
102
+ className,
103
+ icon,
104
+ iconActive,
105
+ iconComponent,
106
+ id,
107
+ isActive,
108
+ prefix,
109
+ isLink,
110
+ }: Omit<TitanLayoutSidebarTriggerProps, 'isActive' | 'tag'> & {
111
+ prefix: string;
112
+ isActive?: any;
113
+ isLink: boolean;
114
+ }) => ({
115
+ 'data-cy': `${prefix}-${id}`,
116
+ 'data-pendo': `${prefix}-${id}`,
117
+ 'className': classNames(Styles.navItem, className, {
118
+ [Styles.navLink]: isLink,
119
+ [Styles.navItemActive]: isActive === true,
120
+ [Styles.navItemIconSwitch]: !!icon && !!iconActive && !iconComponent,
121
+ }),
122
+ });
123
+
124
+ /** Side Navigation menu item (for internal usage) */
125
+ export const InternalSideNavigationLink: FC<InternalSideNavigationLinkProps> = ({
126
+ to,
127
+ className,
128
+ dataPrefix,
129
+ isActive,
130
+ navigationComponent: NavigationComponent,
131
+ submenuExpanded,
132
+ onExpandToggle,
133
+ ...props
134
+ }) => {
135
+ return (
136
+ <NavigationComponent
137
+ {...internalNavigationContentContainerProps({
138
+ ...props,
139
+ prefix: dataPrefix ?? 'navigation-item',
140
+ className,
141
+ isActive,
142
+ isLink: true,
143
+ })}
144
+ to={to}
145
+ isActive={typeof isActive === 'function' ? isActive : undefined}
146
+ activeClassName={Styles.navItemActive}
147
+ >
148
+ <InternalSideNavigationItemContent
149
+ submenuExpanded={submenuExpanded}
150
+ onExpandToggle={onExpandToggle}
151
+ {...props}
152
+ />
153
+ </NavigationComponent>
154
+ );
155
+ };
156
+
157
+ /** Side Navigation menu trigger (for internal usage) */
158
+ export const InternalSideNavigationTrigger: FC<
159
+ Omit<InternalSideNavigationLinkProps, 'to' | 'navigationComponent'> & {
160
+ onClick?: (e: MouseEvent<never>) => void;
161
+ }
162
+ > = ({ className, dataPrefix, isActive, submenuExpanded, onExpandToggle, onClick, ...props }) => {
163
+ return (
164
+ <div
165
+ {...internalNavigationContentContainerProps({
166
+ ...props,
167
+ prefix: dataPrefix ?? 'navigation-item',
168
+ className,
169
+ isActive,
170
+ isLink: !!onClick,
171
+ })}
172
+ onClick={onClick}
173
+ >
174
+ <InternalSideNavigationItemContent
175
+ submenuExpanded={submenuExpanded}
176
+ onExpandToggle={onExpandToggle}
177
+ {...props}
178
+ />
179
+ </div>
180
+ );
181
+ };
182
+
183
+ export const InternalSideNavigationGroupLink: FC<
184
+ NavigationSubmenuItemData & NavigationComponentProps
185
+ > = ({
186
+ id,
187
+ counter,
188
+ tag,
189
+ title,
190
+ to,
191
+ isActive,
192
+ navigationComponent: NavigationComponent,
193
+ ...rest
194
+ }) => {
195
+ return (
196
+ <NavigationComponent
197
+ key={id}
198
+ data-cy={`navigation-item-${id}`}
199
+ data-pendo={`navigation-item-${id}`}
200
+ {...rest}
201
+ to={to}
202
+ className={classNames(Styles.submenuItem, Styles.submenuLink, {
203
+ [Styles.submenuLinkActive]: isActive === true,
204
+ })}
205
+ isActive={typeof isActive === 'function' ? isActive : undefined}
206
+ activeClassName={Styles.submenuLinkActive}
207
+ >
208
+ <span>{title}</span>
209
+ <BadgeTag data={getCounterTag(counter, tag)} className={Styles.submenuLinkCounter} />
210
+ </NavigationComponent>
211
+ );
212
+ };
213
+
214
+ export const InternalSideNavigationGroupTrigger: FC<
215
+ Omit<NavigationSubmenuItemData, 'to'> & { onClick?: (e: MouseEvent<any>) => void }
216
+ > = ({ id, counter, onClick, tag, title, isActive, ...rest }) => {
217
+ return (
218
+ <div
219
+ data-cy={`navigation-item-${id}`}
220
+ data-pendo={`navigation-item-${id}`}
221
+ key={id}
222
+ {...rest}
223
+ className={classNames(Styles.submenuItem, {
224
+ [Styles.submenuLink]: !!onClick,
225
+ [Styles.submenuLinkActive]: isActive === true,
226
+ })}
227
+ onClick={onClick}
228
+ >
229
+ <span>{title}</span>
230
+ <BadgeTag data={getCounterTag(counter, tag)} className={Styles.submenuLinkCounter} />
231
+ </div>
232
+ );
233
+ };
234
+
235
+ export const InternalSideNavigationGroupDivider = () => {
236
+ return <div className={Styles.divider} />;
237
+ };
238
+
239
+ export const InternalSideNavigationGroup: FC<
240
+ Omit<NavigationItemData, 'tag' | 'counter' | 'to'> &
241
+ NavigationComponentProps & {
242
+ children: ReactNode;
243
+ submenuExpanded: boolean;
244
+ onExpandToggle?: (e: MouseEvent<never>) => void;
245
+ tag: BadgeTagProps | undefined;
246
+ to: NavigationItemData['to'] | undefined;
247
+ onClick?: (e: MouseEvent<never>) => void;
248
+ }
249
+ > = ({ children, submenuExpanded, to, onExpandToggle, onClick, ...props }) => {
250
+ return (
251
+ <div className={classNames(Styles.navGroupWrapper)}>
252
+ <div className={Styles.navGroupItem}>
253
+ {to ? (
254
+ <InternalSideNavigationLink
255
+ {...props}
256
+ to={to}
257
+ submenuExpanded={submenuExpanded}
258
+ onExpandToggle={onExpandToggle}
259
+ />
260
+ ) : (
261
+ <InternalSideNavigationTrigger
262
+ {...props}
263
+ submenuExpanded={submenuExpanded}
264
+ onExpandToggle={onExpandToggle}
265
+ onClick={onClick}
266
+ />
267
+ )}
268
+ </div>
269
+ <div
270
+ className={classNames(Styles.submenuWrapper, {
271
+ [Styles.submenuWrapperCollapsed]: !submenuExpanded,
272
+ })}
273
+ >
274
+ <div className={Styles.submenu}>{children}</div>
275
+ </div>
276
+ </div>
277
+ );
278
+ };