@servicetitan/navigation 10.7.0 → 11.0.0-canary.237.4d902dc.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 (149) 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/links.d.ts.map +1 -1
  19. package/dist/components/links.js +7 -7
  20. package/dist/components/links.js.map +1 -1
  21. package/dist/components/logo/logo-company-title.d.ts +1 -0
  22. package/dist/components/logo/logo-company-title.d.ts.map +1 -1
  23. package/dist/components/logo/logo-company-title.js +2 -2
  24. package/dist/components/logo/logo-company-title.js.map +1 -1
  25. package/dist/components/profile-dropdown/profile-dropdown.d.ts +6 -3
  26. package/dist/components/profile-dropdown/profile-dropdown.d.ts.map +1 -1
  27. package/dist/components/profile-dropdown/profile-dropdown.js +7 -8
  28. package/dist/components/profile-dropdown/profile-dropdown.js.map +1 -1
  29. package/dist/components/profile-dropdown/profile-dropdown.module.less +4 -0
  30. package/dist/components/profile-dropdown/profile-dropdown.stories.js +2 -2
  31. package/dist/components/profile-dropdown/profile-dropdown.stories.js.map +1 -1
  32. package/dist/components/titan-layout/index.d.ts +6 -0
  33. package/dist/components/titan-layout/index.d.ts.map +1 -0
  34. package/dist/components/titan-layout/index.js +6 -0
  35. package/dist/components/titan-layout/index.js.map +1 -0
  36. package/dist/components/titan-layout/interface-internal.d.ts +6 -0
  37. package/dist/components/titan-layout/interface-internal.d.ts.map +1 -0
  38. package/dist/components/titan-layout/interface-internal.js +2 -0
  39. package/dist/components/titan-layout/interface-internal.js.map +1 -0
  40. package/dist/components/titan-layout/interface.d.ts +21 -0
  41. package/dist/components/titan-layout/interface.d.ts.map +1 -0
  42. package/dist/components/titan-layout/interface.js +2 -0
  43. package/dist/components/titan-layout/interface.js.map +1 -0
  44. package/dist/components/titan-layout/layout-context.d.ts +20 -0
  45. package/dist/components/titan-layout/layout-context.d.ts.map +1 -0
  46. package/dist/components/titan-layout/layout-context.js +12 -0
  47. package/dist/components/titan-layout/layout-context.js.map +1 -0
  48. package/dist/components/titan-layout/layout-header-links.d.ts +7 -0
  49. package/dist/components/titan-layout/layout-header-links.d.ts.map +1 -0
  50. package/dist/components/titan-layout/layout-header-links.js +32 -0
  51. package/dist/components/titan-layout/layout-header-links.js.map +1 -0
  52. package/dist/components/titan-layout/layout-header.d.ts +20 -0
  53. package/dist/components/titan-layout/layout-header.d.ts.map +1 -0
  54. package/dist/components/titan-layout/layout-header.js +11 -0
  55. package/dist/components/titan-layout/layout-header.js.map +1 -0
  56. package/dist/components/titan-layout/layout-header.module.less +153 -0
  57. package/dist/components/titan-layout/layout-logo.d.ts +12 -0
  58. package/dist/components/titan-layout/layout-logo.d.ts.map +1 -0
  59. package/dist/components/titan-layout/layout-logo.js +15 -0
  60. package/dist/components/titan-layout/layout-logo.js.map +1 -0
  61. package/dist/components/titan-layout/layout-logo.stories.d.ts +13 -0
  62. package/dist/components/titan-layout/layout-logo.stories.d.ts.map +1 -0
  63. package/dist/components/titan-layout/layout-logo.stories.js +17 -0
  64. package/dist/components/titan-layout/layout-logo.stories.js.map +1 -0
  65. package/dist/components/titan-layout/layout-profile.d.ts +9 -0
  66. package/dist/components/titan-layout/layout-profile.d.ts.map +1 -0
  67. package/dist/components/titan-layout/layout-profile.js +44 -0
  68. package/dist/components/titan-layout/layout-profile.js.map +1 -0
  69. package/dist/components/titan-layout/layout-profile.stories.d.ts +13 -0
  70. package/dist/components/titan-layout/layout-profile.stories.d.ts.map +1 -0
  71. package/dist/components/titan-layout/layout-profile.stories.js +13 -0
  72. package/dist/components/titan-layout/layout-profile.stories.js.map +1 -0
  73. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts +46 -0
  74. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts.map +1 -0
  75. package/dist/components/titan-layout/layout-sidebar-links-internal.js +61 -0
  76. package/dist/components/titan-layout/layout-sidebar-links-internal.js.map +1 -0
  77. package/dist/components/titan-layout/layout-sidebar-links.d.ts +6 -0
  78. package/dist/components/titan-layout/layout-sidebar-links.d.ts.map +1 -0
  79. package/dist/components/titan-layout/layout-sidebar-links.js +21 -0
  80. package/dist/components/titan-layout/layout-sidebar-links.js.map +1 -0
  81. package/dist/components/titan-layout/layout-sidebar.d.ts +18 -0
  82. package/dist/components/titan-layout/layout-sidebar.d.ts.map +1 -0
  83. package/dist/components/titan-layout/layout-sidebar.js +65 -0
  84. package/dist/components/titan-layout/layout-sidebar.js.map +1 -0
  85. package/dist/components/titan-layout/layout-sidebar.module.less +528 -0
  86. package/dist/components/titan-layout/titan-layout.d.ts +36 -0
  87. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -0
  88. package/dist/components/titan-layout/titan-layout.js +109 -0
  89. package/dist/components/titan-layout/titan-layout.js.map +1 -0
  90. package/dist/components/titan-layout/titan-layout.module.less +53 -0
  91. package/dist/components/titan-layout/titan-layout.stories.d.ts +18 -0
  92. package/dist/components/titan-layout/titan-layout.stories.d.ts.map +1 -0
  93. package/dist/components/titan-layout/titan-layout.stories.js +62 -0
  94. package/dist/components/titan-layout/titan-layout.stories.js.map +1 -0
  95. package/dist/components/titan-layout/with-tooltip.d.ts +4 -0
  96. package/dist/components/titan-layout/with-tooltip.d.ts.map +1 -0
  97. package/dist/components/titan-layout/with-tooltip.js +4 -0
  98. package/dist/components/titan-layout/with-tooltip.js.map +1 -0
  99. package/dist/index.d.ts +2 -1
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +2 -1
  102. package/dist/index.js.map +1 -1
  103. package/dist/test/data.d.ts.map +1 -1
  104. package/dist/test/data.js +3 -3
  105. package/dist/test/data.js.map +1 -1
  106. package/dist/utils/navigation-legacy.d.ts +3 -1
  107. package/dist/utils/navigation-legacy.d.ts.map +1 -1
  108. package/dist/utils/use-breakpoint.d.ts +7 -0
  109. package/dist/utils/use-breakpoint.d.ts.map +1 -0
  110. package/dist/utils/use-breakpoint.js +13 -0
  111. package/dist/utils/use-breakpoint.js.map +1 -0
  112. package/package.json +5 -6
  113. package/src/components/header-navigation/header-navigation-extra.stories.tsx +7 -0
  114. package/src/components/header-navigation/header-navigation-links.tsx +2 -0
  115. package/src/components/header-navigation/header-navigation-stacked.stories.tsx +4 -0
  116. package/src/components/header-navigation/header-navigation.stories.tsx +5 -0
  117. package/src/components/left-navigation/header-navigation-tiny.stories.tsx +6 -0
  118. package/src/components/left-navigation/side-navigation-links.tsx +1 -1
  119. package/src/components/links.tsx +33 -13
  120. package/src/components/logo/logo-company-title.tsx +8 -6
  121. package/src/components/profile-dropdown/profile-dropdown.module.less +4 -0
  122. package/src/components/profile-dropdown/profile-dropdown.stories.tsx +4 -4
  123. package/src/components/profile-dropdown/profile-dropdown.tsx +43 -46
  124. package/src/components/titan-layout/index.ts +5 -0
  125. package/src/components/titan-layout/interface-internal.ts +6 -0
  126. package/src/components/titan-layout/interface.ts +26 -0
  127. package/src/components/titan-layout/layout-context.tsx +30 -0
  128. package/src/components/titan-layout/layout-header-links.tsx +144 -0
  129. package/src/components/titan-layout/layout-header.module.less +153 -0
  130. package/src/components/titan-layout/layout-header.module.less.d.ts +16 -0
  131. package/src/components/titan-layout/layout-header.tsx +86 -0
  132. package/src/components/titan-layout/layout-logo.stories.tsx +31 -0
  133. package/src/components/titan-layout/layout-logo.tsx +57 -0
  134. package/src/components/titan-layout/layout-profile.stories.tsx +37 -0
  135. package/src/components/titan-layout/layout-profile.tsx +116 -0
  136. package/src/components/titan-layout/layout-sidebar-links-internal.tsx +265 -0
  137. package/src/components/titan-layout/layout-sidebar-links.tsx +56 -0
  138. package/src/components/titan-layout/layout-sidebar.module.less +528 -0
  139. package/src/components/titan-layout/layout-sidebar.module.less.d.ts +50 -0
  140. package/src/components/titan-layout/layout-sidebar.tsx +298 -0
  141. package/src/components/titan-layout/titan-layout.module.less +53 -0
  142. package/src/components/titan-layout/titan-layout.module.less.d.ts +11 -0
  143. package/src/components/titan-layout/titan-layout.stories.tsx +194 -0
  144. package/src/components/titan-layout/titan-layout.tsx +270 -0
  145. package/src/components/titan-layout/with-tooltip.tsx +16 -0
  146. package/src/index.ts +2 -1
  147. package/src/test/data.tsx +3 -2
  148. package/src/utils/navigation-legacy.ts +3 -1
  149. package/src/utils/use-breakpoint.ts +19 -0
@@ -0,0 +1,86 @@
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, useTitanLayoutContext } 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
+ onBurgerClick?: (e: MouseEvent) => void;
29
+ };
30
+
31
+ export const LayoutHeader: FC<LayoutHeaderProps> = ({
32
+ className,
33
+ right,
34
+ rightText,
35
+ rightClassName,
36
+ center,
37
+ centerClassName,
38
+ logo,
39
+ profile,
40
+ onBurgerClick,
41
+ ...rest
42
+ }) => {
43
+ const { breakpoint } = useTitanLayoutContext();
44
+
45
+ return (
46
+ <LayoutPlacementContext.Provider value="top">
47
+ <div
48
+ className={classNames(Styles.header, className)}
49
+ {...rest}
50
+ data-cy="header-navigation"
51
+ >
52
+ <div className={classNames(Styles.heTopLeft)} data-cy="navigation-left">
53
+ {breakpoint.isMobile && (
54
+ <LayoutHeaderNavigationTrigger
55
+ id="burger"
56
+ title=""
57
+ icon={SvgBurgerMenu}
58
+ iconActive={SvgBurgerMenu}
59
+ className="m-r-1"
60
+ onClick={onBurgerClick}
61
+ />
62
+ )}
63
+ {logo}
64
+ </div>
65
+ <div
66
+ className={classNames(Styles.heTopCenter, centerClassName)}
67
+ data-cy="navigation-center"
68
+ >
69
+ {center}
70
+ </div>
71
+ <div
72
+ className={classNames(
73
+ 'd-f flex-row justify-content-end align-items-center',
74
+ Styles.heTopRight,
75
+ rightClassName
76
+ )}
77
+ data-cy="navigation-right"
78
+ >
79
+ {!!rightText && <div className={Styles.heTopRightText}>{rightText}</div>}
80
+ {right}
81
+ {profile}
82
+ </div>
83
+ </div>
84
+ </LayoutPlacementContext.Provider>
85
+ );
86
+ };
@@ -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,57 @@
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
+
37
+ return (
38
+ <div className={classNames('d-f align-items-center', className)}>
39
+ {typeof title === 'string' ? (
40
+ <Fragment>
41
+ <LogoTitan size={logoSize} mantleFill={mantleFill} logoWrapper={Wrapper} />
42
+ {!isMobile && (
43
+ <LogoTitanTitle className="c-inherit m-l-1">{title}</LogoTitanTitle>
44
+ )}
45
+ </Fragment>
46
+ ) : title === true && !isMobile ? (
47
+ <Wrapper className="">
48
+ <LogoCompanyTitle height={logoCompanySize} />
49
+ </Wrapper>
50
+ ) : (
51
+ <LogoTitan size={logoSize} mantleFill={mantleFill} logoWrapper={Wrapper} />
52
+ )}
53
+
54
+ {!isMobile && postfix}
55
+ </div>
56
+ );
57
+ };
@@ -0,0 +1,37 @@
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">
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>
37
+ );
@@ -0,0 +1,116 @@
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, useState } from 'react';
5
+ import { 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
+
21
+ export type {
22
+ ProfileDropdownProps,
23
+ ProfileDropdownSectionProps,
24
+ ProfileDropdownLinkProps,
25
+ } from '../profile-dropdown/profile-dropdown';
26
+
27
+ const ProfileDropdownContent: FC<ProfileDropdownProps> = props => {
28
+ const { breakpoint, NavigationComponent } = useTitanLayoutContext();
29
+ return breakpoint.isMobile ? (
30
+ <MobileProfileDropdown {...props} navigationComponent={NavigationComponent} />
31
+ ) : (
32
+ <NavigationComponentContext.Provider value={NavigationComponent}>
33
+ <DesktopProfileDropdown {...props} />
34
+ </NavigationComponentContext.Provider>
35
+ );
36
+ };
37
+ ProfileDropdownContent.displayName = 'ProfileDropdown';
38
+
39
+ const MobileProfileDropdown: FC<ProfileDropdownProps & NavigationComponentProps> = ({
40
+ children,
41
+ ...props
42
+ }) => {
43
+ const [expanded, setExpanded] = useState(false);
44
+ const onExpandToggle = () => setExpanded(!expanded);
45
+ return (
46
+ <InternalSideNavigationGroup
47
+ id="__profile"
48
+ to={undefined}
49
+ title="Profile"
50
+ icon={SvgAccountInactive}
51
+ iconActive={SvgAccountActive}
52
+ isActive={expanded}
53
+ {...props}
54
+ submenuExpanded={expanded}
55
+ onExpandToggle={onExpandToggle}
56
+ onClick={onExpandToggle}
57
+ tag={undefined}
58
+ >
59
+ {children}
60
+ </InternalSideNavigationGroup>
61
+ );
62
+ };
63
+
64
+ const ProfileDropdownDivider: FC = () => {
65
+ const { breakpoint } = useTitanLayoutContext();
66
+ return breakpoint.isMobile ? (
67
+ <InternalSideNavigationGroupDivider />
68
+ ) : (
69
+ <DesktopProfileDropdown.Divider />
70
+ );
71
+ };
72
+
73
+ const ProfileDropdownSection: FC<ProfileDropdownSectionProps> = props => {
74
+ const { breakpoint } = useTitanLayoutContext();
75
+ return breakpoint.isMobile ? (
76
+ <MobileProfileDropdownSection {...props} />
77
+ ) : (
78
+ <DesktopProfileDropdown.Section {...props} />
79
+ );
80
+ };
81
+ const MobileProfileDropdownSection: FC<ProfileDropdownSectionProps> = props => {
82
+ const title = typeof props.children === 'string' ? props.children : undefined;
83
+ return title ? (
84
+ <InternalSideNavigationGroupTrigger id={props.id} title={title} onClick={props.onClick} />
85
+ ) : null;
86
+ };
87
+
88
+ const ProfileDropdownLink: FC<ProfileDropdownLinkProps> = props => {
89
+ const { breakpoint, NavigationComponent } = useTitanLayoutContext();
90
+ return breakpoint.isMobile ? (
91
+ <MobileProfileDropdownLink {...props} navigationComponent={NavigationComponent} />
92
+ ) : (
93
+ <DesktopProfileDropdown.Link {...props} />
94
+ );
95
+ };
96
+ const MobileProfileDropdownLink: FC<ProfileDropdownLinkProps & NavigationComponentProps> = ({
97
+ to,
98
+ navigationComponent,
99
+ ...props
100
+ }) => {
101
+ const title = typeof props.children === 'string' ? props.children : undefined;
102
+ return title ? (
103
+ <InternalSideNavigationGroupLink
104
+ {...props}
105
+ to={to}
106
+ title={title}
107
+ navigationComponent={navigationComponent}
108
+ />
109
+ ) : null;
110
+ };
111
+
112
+ export const ProfileDropdown = Object.assign(ProfileDropdownContent, {
113
+ Divider: ProfileDropdownDivider,
114
+ Link: ProfileDropdownLink,
115
+ Section: ProfileDropdownSection,
116
+ });
@@ -0,0 +1,265 @@
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'> & { onClick?: () => void }
160
+ > = ({ className, dataPrefix, isActive, submenuExpanded, onExpandToggle, onClick, ...props }) => {
161
+ return (
162
+ <div
163
+ {...internalNavigationContentContainerProps({
164
+ ...props,
165
+ prefix: dataPrefix ?? 'navigation-item',
166
+ className,
167
+ isActive,
168
+ isLink: !!onClick,
169
+ })}
170
+ onClick={onClick}
171
+ >
172
+ <InternalSideNavigationItemContent
173
+ submenuExpanded={submenuExpanded}
174
+ onExpandToggle={onExpandToggle}
175
+ {...props}
176
+ />
177
+ </div>
178
+ );
179
+ };
180
+
181
+ export const InternalSideNavigationGroupLink: FC<
182
+ NavigationSubmenuItemData & NavigationComponentProps
183
+ > = ({ id, counter, tag, title, to, isActive, navigationComponent: NavigationComponent }) => {
184
+ return (
185
+ <NavigationComponent
186
+ data-cy={`navigation-item-${id}`}
187
+ data-pendo={`navigation-item-${id}`}
188
+ key={id}
189
+ to={to}
190
+ className={classNames(Styles.submenuItem, Styles.submenuLink, {
191
+ [Styles.submenuLinkActive]: isActive === true,
192
+ })}
193
+ isActive={typeof isActive === 'function' ? isActive : undefined}
194
+ activeClassName={Styles.submenuLinkActive}
195
+ >
196
+ <span>{title}</span>
197
+ <BadgeTag data={getCounterTag(counter, tag)} className={Styles.submenuLinkCounter} />
198
+ </NavigationComponent>
199
+ );
200
+ };
201
+
202
+ export const InternalSideNavigationGroupTrigger: FC<
203
+ Omit<NavigationSubmenuItemData, 'to'> & { onClick?: (e: MouseEvent<any>) => void }
204
+ > = ({ id, counter, onClick, tag, title, isActive }) => {
205
+ return (
206
+ <div
207
+ data-cy={`navigation-item-${id}`}
208
+ data-pendo={`navigation-item-${id}`}
209
+ key={id}
210
+ className={classNames(Styles.submenuItem, {
211
+ [Styles.submenuLink]: !!onClick,
212
+ [Styles.submenuLinkActive]: isActive === true,
213
+ })}
214
+ onClick={onClick}
215
+ >
216
+ <span>{title}</span>
217
+ <BadgeTag data={getCounterTag(counter, tag)} className={Styles.submenuLinkCounter} />
218
+ </div>
219
+ );
220
+ };
221
+
222
+ export const InternalSideNavigationGroupDivider = () => {
223
+ return <div className={Styles.divider} />;
224
+ };
225
+
226
+ export const InternalSideNavigationGroup: FC<
227
+ Omit<NavigationItemData, 'tag' | 'counter' | 'to'> &
228
+ NavigationComponentProps & {
229
+ children: ReactNode;
230
+ submenuExpanded: boolean;
231
+ onExpandToggle?: (e: MouseEvent<never>) => void;
232
+ tag: BadgeTagProps | undefined;
233
+ to: NavigationItemData['to'] | undefined;
234
+ onClick?: () => void;
235
+ }
236
+ > = ({ children, submenuExpanded, to, onExpandToggle, onClick, ...props }) => {
237
+ return (
238
+ <div className={classNames(Styles.navGroupWrapper)}>
239
+ <div className={Styles.navGroupItem}>
240
+ {to ? (
241
+ <InternalSideNavigationLink
242
+ {...props}
243
+ to={to}
244
+ submenuExpanded={submenuExpanded}
245
+ onExpandToggle={onExpandToggle}
246
+ />
247
+ ) : (
248
+ <InternalSideNavigationTrigger
249
+ {...props}
250
+ submenuExpanded={submenuExpanded}
251
+ onExpandToggle={onExpandToggle}
252
+ onClick={onClick}
253
+ />
254
+ )}
255
+ </div>
256
+ <div
257
+ className={classNames(Styles.submenuWrapper, {
258
+ [Styles.submenuWrapperCollapsed]: !submenuExpanded,
259
+ })}
260
+ >
261
+ <div className={Styles.submenu}>{children}</div>
262
+ </div>
263
+ </div>
264
+ );
265
+ };
@@ -0,0 +1,56 @@
1
+ import { FC, ReactElement } from 'react';
2
+ import { getCounterTag } from '../../utils/side-nav';
3
+ import { TitanLayoutSidebarLinkProps, TitanLayoutSidebarTriggerProps } from './interface';
4
+ import { useTitanLayoutContext } from './layout-context';
5
+ import {
6
+ InternalSideNavigationLink,
7
+ InternalSideNavigationTrigger,
8
+ } from './layout-sidebar-links-internal';
9
+
10
+ const WrappedLink: FC<{
11
+ children: ReactElement<any>;
12
+ wrapper: NonNullable<TitanLayoutSidebarLinkProps['wrapper']>;
13
+ }> = ({ children, wrapper: WrapperComponent }) => {
14
+ const { sidebar } = useTitanLayoutContext();
15
+ return <WrapperComponent context={sidebar}>{children}</WrapperComponent>;
16
+ };
17
+
18
+ /** Side Navigation menu link */
19
+ export function TitanLayoutSidebarLink({ wrapper, ...props }: TitanLayoutSidebarLinkProps) {
20
+ const { NavigationComponent } = useTitanLayoutContext();
21
+
22
+ const element = (
23
+ <InternalSideNavigationLink
24
+ {...props}
25
+ navigationComponent={NavigationComponent}
26
+ submenuExpanded={undefined}
27
+ dataPrefix="navigation-link"
28
+ tag={getCounterTag(props.counter, props.tag)}
29
+ />
30
+ );
31
+
32
+ return wrapper ? <WrappedLink wrapper={wrapper}>{element}</WrappedLink> : element;
33
+ }
34
+
35
+ /** Side Navigation menu trigger */
36
+ export function TitanLayoutSidebarTrigger({
37
+ wrapper,
38
+ onMobileClick,
39
+ onClick,
40
+ ...props
41
+ }: TitanLayoutSidebarTriggerProps) {
42
+ const {
43
+ breakpoint: { isMobile },
44
+ } = useTitanLayoutContext();
45
+
46
+ const element = (
47
+ <InternalSideNavigationTrigger
48
+ {...props}
49
+ submenuExpanded={undefined}
50
+ dataPrefix="navigation-trigger"
51
+ tag={getCounterTag(props.counter, props.tag)}
52
+ onClick={isMobile && !!onMobileClick ? onMobileClick : onClick}
53
+ />
54
+ );
55
+ return wrapper && !isMobile ? <WrappedLink wrapper={wrapper}>{element}</WrappedLink> : element;
56
+ }