@servicetitan/navigation 9.0.0 → 9.1.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.
- package/dist/components/counter-tag.d.ts +1 -4
- package/dist/components/counter-tag.d.ts.map +1 -1
- package/dist/components/counter-tag.js.map +1 -1
- package/dist/components/layout.stories.js +2 -2
- package/dist/components/layout.stories.js.map +1 -1
- package/dist/components/left-navigation/index.d.ts +2 -0
- package/dist/components/left-navigation/index.d.ts.map +1 -1
- package/dist/components/left-navigation/index.js +2 -0
- package/dist/components/left-navigation/index.js.map +1 -1
- package/dist/components/left-navigation/interface-internal.d.ts +10 -0
- package/dist/components/left-navigation/interface-internal.d.ts.map +1 -0
- package/dist/components/left-navigation/interface-internal.js +2 -0
- package/dist/components/left-navigation/interface-internal.js.map +1 -0
- package/dist/components/left-navigation/interface.d.ts +11 -0
- package/dist/components/left-navigation/interface.d.ts.map +1 -0
- package/dist/components/left-navigation/interface.js +2 -0
- package/dist/components/left-navigation/interface.js.map +1 -0
- package/dist/components/left-navigation/side-navigation-links-internal.d.ts +23 -0
- package/dist/components/left-navigation/side-navigation-links-internal.d.ts.map +1 -0
- package/dist/components/left-navigation/side-navigation-links-internal.js +29 -0
- package/dist/components/left-navigation/side-navigation-links-internal.js.map +1 -0
- package/dist/components/left-navigation/side-navigation-links.d.ts +7 -0
- package/dist/components/left-navigation/side-navigation-links.d.ts.map +1 -0
- package/dist/components/left-navigation/side-navigation-links.js +20 -0
- package/dist/components/left-navigation/side-navigation-links.js.map +1 -0
- package/dist/components/left-navigation/side-navigation.d.ts +3 -11
- package/dist/components/left-navigation/side-navigation.d.ts.map +1 -1
- package/dist/components/left-navigation/side-navigation.js +12 -27
- package/dist/components/left-navigation/side-navigation.js.map +1 -1
- package/dist/components/left-navigation/side-navigation.module.less +11 -4
- package/dist/components/left-navigation/side-navigation.stories.d.ts +1 -0
- package/dist/components/left-navigation/side-navigation.stories.d.ts.map +1 -1
- package/dist/components/left-navigation/side-navigation.stories.js +26 -7
- package/dist/components/left-navigation/side-navigation.stories.js.map +1 -1
- package/dist/test/data.d.ts +1 -0
- package/dist/test/data.d.ts.map +1 -1
- package/dist/test/data.js +3 -2
- package/dist/test/data.js.map +1 -1
- package/dist/utils/counter-tag.d.ts +8 -0
- package/dist/utils/counter-tag.d.ts.map +1 -0
- package/dist/utils/counter-tag.js +2 -0
- package/dist/utils/counter-tag.js.map +1 -0
- package/dist/utils/side-nav.d.ts +3 -0
- package/dist/utils/side-nav.d.ts.map +1 -0
- package/dist/utils/side-nav.js +27 -0
- package/dist/utils/side-nav.js.map +1 -0
- package/package.json +4 -4
- package/src/components/counter-tag.tsx +1 -5
- package/src/components/layout.stories.tsx +2 -2
- package/src/components/left-navigation/index.ts +2 -0
- package/src/components/left-navigation/interface-internal.ts +11 -0
- package/src/components/left-navigation/interface.ts +13 -0
- package/src/components/left-navigation/side-navigation-links-internal.tsx +128 -0
- package/src/components/left-navigation/side-navigation-links.tsx +39 -0
- package/src/components/left-navigation/side-navigation.module.less +11 -4
- package/src/components/left-navigation/side-navigation.module.less.d.ts +2 -1
- package/src/components/left-navigation/side-navigation.stories.tsx +66 -7
- package/src/components/left-navigation/side-navigation.tsx +28 -131
- package/src/test/data.tsx +13 -2
- package/src/utils/counter-tag.ts +11 -0
- package/src/utils/side-nav.ts +34 -0
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { Icon, Popover, PopoverTriggerProps, Text } 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
2
|
import SvgCollapse from '@servicetitan/anvil2/assets/icons/st/gnav_menu_collapse.svg';
|
|
5
3
|
import SvgExpand from '@servicetitan/anvil2/assets/icons/st/gnav_menu_expand.svg';
|
|
6
4
|
import { Collapsible, Headline } from '@servicetitan/design-system';
|
|
@@ -12,6 +10,7 @@ import {
|
|
|
12
10
|
Fragment,
|
|
13
11
|
MouseEvent,
|
|
14
12
|
ReactElement,
|
|
13
|
+
ReactNode,
|
|
15
14
|
useCallback,
|
|
16
15
|
useContext,
|
|
17
16
|
} from 'react';
|
|
@@ -19,18 +18,16 @@ import {
|
|
|
19
18
|
HeaderNavigationItemData,
|
|
20
19
|
HeaderNavigationItemSubmenu,
|
|
21
20
|
HeaderNavigationItemSubmenuLink,
|
|
22
|
-
NavLinkComponentProps,
|
|
23
21
|
} from '../../utils/navigation';
|
|
24
22
|
import { NavigationComponentContext } from '../../utils/navigation-context';
|
|
23
|
+
import { getSubmenuGroupTag } from '../../utils/side-nav';
|
|
25
24
|
import { CounterTag } from '../counter-tag';
|
|
25
|
+
import { SideNavigationExpandedState } from './interface';
|
|
26
|
+
import { NavigationComponentProps, SideNavigationExpandedProps } from './interface-internal';
|
|
27
|
+
import { InternalSideNavigationLink } from './side-navigation-links-internal';
|
|
26
28
|
import * as Styles from './side-navigation.module.less';
|
|
27
29
|
import { withTooltip } from './with-tooltip';
|
|
28
30
|
|
|
29
|
-
export interface SideNavigationExpandedState {
|
|
30
|
-
bar: boolean;
|
|
31
|
-
submenus?: string[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
31
|
export interface SideNavigationProps {
|
|
35
32
|
/** container class name */
|
|
36
33
|
className?: string;
|
|
@@ -39,7 +36,7 @@ export interface SideNavigationProps {
|
|
|
39
36
|
/** main navigation items */
|
|
40
37
|
items?: HeaderNavigationItemData[];
|
|
41
38
|
/** top navigation items */
|
|
42
|
-
itemsTop?:
|
|
39
|
+
itemsTop?: ReactNode;
|
|
43
40
|
/** is menu expanded */
|
|
44
41
|
expanded?: SideNavigationExpandedState;
|
|
45
42
|
/** expand change handler */
|
|
@@ -65,17 +62,10 @@ export const SideNavigation: FC<SideNavigationProps> = ({
|
|
|
65
62
|
id={id}
|
|
66
63
|
data-cy="side-navigation"
|
|
67
64
|
>
|
|
68
|
-
{!!itemsTop
|
|
65
|
+
{!!itemsTop && (
|
|
69
66
|
<Fragment>
|
|
70
67
|
<div className={Styles.sideNavTop} data-cy="navigation-items-top">
|
|
71
|
-
{itemsTop
|
|
72
|
-
<SideNavigationItem
|
|
73
|
-
key={item.id}
|
|
74
|
-
expanded={expanded}
|
|
75
|
-
navigationComponent={NavigationComponent}
|
|
76
|
-
{...item}
|
|
77
|
-
/>
|
|
78
|
-
))}
|
|
68
|
+
{itemsTop}
|
|
79
69
|
</div>
|
|
80
70
|
<div className={Styles.divider} />
|
|
81
71
|
</Fragment>
|
|
@@ -91,9 +81,9 @@ export const SideNavigation: FC<SideNavigationProps> = ({
|
|
|
91
81
|
{...item}
|
|
92
82
|
/>
|
|
93
83
|
) : (
|
|
94
|
-
<
|
|
84
|
+
<InternalSideNavigationLink
|
|
95
85
|
key={item.id}
|
|
96
|
-
|
|
86
|
+
submenuExpanded={undefined}
|
|
97
87
|
navigationComponent={NavigationComponent}
|
|
98
88
|
{...item}
|
|
99
89
|
/>
|
|
@@ -111,117 +101,22 @@ export const SideNavigation: FC<SideNavigationProps> = ({
|
|
|
111
101
|
);
|
|
112
102
|
};
|
|
113
103
|
|
|
114
|
-
interface NavigationComponentProps {
|
|
115
|
-
navigationComponent: FC<NavLinkComponentProps>;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
interface SideNavigationItemProps extends HeaderNavigationItemData, NavigationComponentProps {
|
|
119
|
-
expanded?: SideNavigationExpandedState;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Side Navigation menu item */
|
|
123
|
-
const SideNavigationItem: FC<SideNavigationItemProps> = ({
|
|
124
|
-
id,
|
|
125
|
-
to,
|
|
126
|
-
title,
|
|
127
|
-
className,
|
|
128
|
-
iconClassName,
|
|
129
|
-
iconComponent: IconComponent,
|
|
130
|
-
icon,
|
|
131
|
-
iconActive,
|
|
132
|
-
isActive,
|
|
133
|
-
navigationComponent: NavigationComponent,
|
|
134
|
-
tag,
|
|
135
|
-
expanded,
|
|
136
|
-
submenu,
|
|
137
|
-
}) => {
|
|
138
|
-
const iconSwitch = !!icon && !!iconActive && !IconComponent;
|
|
139
|
-
const hasSubmenu = !!submenu;
|
|
140
|
-
|
|
141
|
-
return (
|
|
142
|
-
<NavigationComponent
|
|
143
|
-
data-cy={`navigation-item-${id}`}
|
|
144
|
-
data-pendo={`navigation-item-${id}`}
|
|
145
|
-
key={id}
|
|
146
|
-
to={to}
|
|
147
|
-
className={classNames(Styles.navigationItem, className, {
|
|
148
|
-
[Styles.navigationItemActive]: isActive === true,
|
|
149
|
-
[Styles.navigationItemIconSwitch]: iconSwitch,
|
|
150
|
-
})}
|
|
151
|
-
isActive={typeof isActive === 'function' ? isActive : undefined}
|
|
152
|
-
activeClassName={Styles.navigationItemActive}
|
|
153
|
-
>
|
|
154
|
-
<div className={Styles.navigationItemIconWrapper}>
|
|
155
|
-
{IconComponent ? (
|
|
156
|
-
<i className={classNames(Styles.navigationIcon, iconClassName)}>
|
|
157
|
-
<IconComponent />
|
|
158
|
-
</i>
|
|
159
|
-
) : (
|
|
160
|
-
<Fragment>
|
|
161
|
-
{icon && (
|
|
162
|
-
<Icon
|
|
163
|
-
svg={icon}
|
|
164
|
-
className={classNames(
|
|
165
|
-
Styles.navigationIcon,
|
|
166
|
-
Styles.navigationIconInactive,
|
|
167
|
-
iconClassName
|
|
168
|
-
)}
|
|
169
|
-
/>
|
|
170
|
-
)}
|
|
171
|
-
{iconActive && (
|
|
172
|
-
<Icon
|
|
173
|
-
svg={iconActive}
|
|
174
|
-
className={classNames(
|
|
175
|
-
Styles.navigationIcon,
|
|
176
|
-
Styles.navigationIconActive,
|
|
177
|
-
iconClassName
|
|
178
|
-
)}
|
|
179
|
-
/>
|
|
180
|
-
)}
|
|
181
|
-
</Fragment>
|
|
182
|
-
)}
|
|
183
|
-
|
|
184
|
-
{!!expanded?.bar && <div className={Styles.navigationItemText}>{title}</div>}
|
|
185
|
-
{!!tag && <CounterTag data={tag} className={Styles.navigationItemCounter} />}
|
|
186
|
-
{hasSubmenu && !!expanded?.bar && (
|
|
187
|
-
<Icon
|
|
188
|
-
svg={expanded?.submenus?.includes(id) ? SvgGroupCollapse : SvgGroupExpand}
|
|
189
|
-
className={Styles.navigationItemGroupToggle}
|
|
190
|
-
/>
|
|
191
|
-
)}
|
|
192
|
-
</div>
|
|
193
|
-
|
|
194
|
-
{!expanded?.bar && (
|
|
195
|
-
<div
|
|
196
|
-
className={classNames(Styles.navigationItemText, {
|
|
197
|
-
[Styles.navigationItemTextSmall]: title.length >= 10,
|
|
198
|
-
})}
|
|
199
|
-
>
|
|
200
|
-
{title}
|
|
201
|
-
</div>
|
|
202
|
-
)}
|
|
203
|
-
</NavigationComponent>
|
|
204
|
-
);
|
|
205
|
-
};
|
|
206
|
-
|
|
207
104
|
const submenuPopoverStyles = { '--background-color-strong': '#24323C' } as CSSProperties;
|
|
208
|
-
const getFirstSubmenuLinkTo = (
|
|
209
|
-
submenu: HeaderNavigationItemSubmenu | undefined,
|
|
210
|
-
defaultTo: string
|
|
211
|
-
) => submenu?.groups[0]?.links?.[0].to ?? defaultTo;
|
|
212
105
|
|
|
213
106
|
/** Side Navigation menu item */
|
|
214
107
|
const SideNavigationGroupItem: FC<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
108
|
+
HeaderNavigationItemData &
|
|
109
|
+
SideNavigationExpandedProps &
|
|
110
|
+
NavigationComponentProps & {
|
|
111
|
+
onExpandedChange: undefined | ((expanded: SideNavigationExpandedState) => void);
|
|
112
|
+
}
|
|
218
113
|
> = ({ onExpandedChange, ...props }) => {
|
|
114
|
+
const isSubmenuExpanded = props.expanded?.submenus?.includes(props.id) ?? false;
|
|
219
115
|
const triggerClick = useCallback(
|
|
220
116
|
(e: MouseEvent<HTMLDivElement>) => {
|
|
221
117
|
e.stopPropagation();
|
|
222
118
|
e.preventDefault();
|
|
223
119
|
|
|
224
|
-
const isSubmenuExpanded = props.expanded?.submenus?.includes(props.id);
|
|
225
120
|
onExpandedChange?.({
|
|
226
121
|
bar: !!props.expanded?.bar,
|
|
227
122
|
submenus: [
|
|
@@ -230,17 +125,19 @@ const SideNavigationGroupItem: FC<
|
|
|
230
125
|
],
|
|
231
126
|
});
|
|
232
127
|
},
|
|
233
|
-
[props.id, props.expanded, onExpandedChange]
|
|
128
|
+
[props.id, props.expanded, isSubmenuExpanded, onExpandedChange]
|
|
234
129
|
);
|
|
235
130
|
|
|
236
|
-
const tag = props.submenu
|
|
237
|
-
? true
|
|
238
|
-
: props.tag;
|
|
131
|
+
const tag = getSubmenuGroupTag(props.submenu, props.tag);
|
|
239
132
|
|
|
240
133
|
return props.expanded?.bar ? (
|
|
241
134
|
<Fragment>
|
|
242
135
|
<div onClickCapture={triggerClick}>
|
|
243
|
-
<
|
|
136
|
+
<InternalSideNavigationLink
|
|
137
|
+
{...props}
|
|
138
|
+
submenuExpanded={isSubmenuExpanded}
|
|
139
|
+
tag={tag}
|
|
140
|
+
/>
|
|
244
141
|
</div>
|
|
245
142
|
<Collapsible open={props.expanded?.submenus?.includes(props.id)} animate>
|
|
246
143
|
<div className={Styles.submenu}>
|
|
@@ -256,9 +153,9 @@ const SideNavigationGroupItem: FC<
|
|
|
256
153
|
<Popover.Trigger>
|
|
257
154
|
{(triggerProps: PopoverTriggerProps) => (
|
|
258
155
|
<div {...triggerProps}>
|
|
259
|
-
<
|
|
156
|
+
<InternalSideNavigationLink
|
|
260
157
|
{...props}
|
|
261
|
-
|
|
158
|
+
submenuExpanded={undefined}
|
|
262
159
|
tag={tag}
|
|
263
160
|
/>
|
|
264
161
|
</div>
|
|
@@ -337,7 +234,7 @@ const SideNavigationGroupLink: FC<HeaderNavigationItemSubmenuLink & NavigationCo
|
|
|
337
234
|
};
|
|
338
235
|
|
|
339
236
|
/** Side Navigation options toggle */
|
|
340
|
-
|
|
237
|
+
const SideNavigationOptionsToggle: FC<{
|
|
341
238
|
expanded?: SideNavigationExpandedState;
|
|
342
239
|
onExpandedChange?(expanded: SideNavigationExpandedState): void;
|
|
343
240
|
}> = ({ expanded, onExpandedChange }) =>
|
|
@@ -354,8 +251,8 @@ export const SideNavigationOptionsToggle: FC<{
|
|
|
354
251
|
<Icon className={Styles.optionsIcon} svg={expanded ? SvgCollapse : SvgExpand} />
|
|
355
252
|
</div>
|
|
356
253
|
|
|
357
|
-
{!!expanded && <span className={Styles.optionsItemText}>Collapse Menu</span>}
|
|
254
|
+
{!!expanded?.bar && <span className={Styles.optionsItemText}>Collapse Menu</span>}
|
|
358
255
|
</div>,
|
|
359
|
-
expanded ? undefined : 'Expand Menu',
|
|
256
|
+
expanded?.bar ? undefined : 'Expand Menu',
|
|
360
257
|
'right'
|
|
361
258
|
);
|
package/src/test/data.tsx
CHANGED
|
@@ -33,10 +33,10 @@ import SvgTasks from '@servicetitan/anvil2/assets/icons/st/gnav_tasks_inactive.s
|
|
|
33
33
|
import { BodyText, Popover } from '@servicetitan/design-system';
|
|
34
34
|
|
|
35
35
|
import classNames from 'classnames';
|
|
36
|
-
import { forwardRef, useState } from 'react';
|
|
36
|
+
import { Fragment, forwardRef, useState } from 'react';
|
|
37
37
|
// needed only for storybook and added in root dependencies
|
|
38
38
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
39
|
-
import { MemoryRouter, useHistory, useLocation } from 'react-router-dom';
|
|
39
|
+
import { MemoryRouter, Redirect, Switch, useHistory, useLocation } from 'react-router-dom';
|
|
40
40
|
import { HeaderNavigationTrigger } from '../components/links';
|
|
41
41
|
import {
|
|
42
42
|
HeaderNavigationItemData,
|
|
@@ -319,3 +319,14 @@ export const CallsNavigationTrigger = () => {
|
|
|
319
319
|
</Popover>
|
|
320
320
|
);
|
|
321
321
|
};
|
|
322
|
+
|
|
323
|
+
export const withDefaultRedirects = (Story: any) => (
|
|
324
|
+
<Fragment>
|
|
325
|
+
<Switch>
|
|
326
|
+
<Redirect from="/accounting" exact to="/accounting/export" />
|
|
327
|
+
<Redirect from="/purchasing" exact to="/purchasing/repl" />
|
|
328
|
+
<Redirect from="/followUps" exact to="/followUps/sold" />
|
|
329
|
+
</Switch>
|
|
330
|
+
<Story />
|
|
331
|
+
</Fragment>
|
|
332
|
+
);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CounterTagProps } from '../components/counter-tag';
|
|
2
|
+
|
|
3
|
+
export interface CounterTagPropsStrict {
|
|
4
|
+
value: number | boolean;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type CounterTagType = boolean | number | CounterTagProps;
|
|
9
|
+
|
|
10
|
+
export const isCounterPropsObject = (tag?: CounterTagType): tag is CounterTagProps =>
|
|
11
|
+
!!tag && (tag as any).value !== undefined;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { isCounterPropsObject } from './counter-tag';
|
|
2
|
+
import { HeaderNavigationItemData, HeaderNavigationItemSubmenu } from './navigation';
|
|
3
|
+
|
|
4
|
+
export function getSubmenuGroupTag(
|
|
5
|
+
submenu: HeaderNavigationItemSubmenu | undefined,
|
|
6
|
+
defaultTag: HeaderNavigationItemData['tag']
|
|
7
|
+
): HeaderNavigationItemData['tag'] {
|
|
8
|
+
if (!submenu) {
|
|
9
|
+
return defaultTag;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let tagValue: number | boolean | undefined = undefined;
|
|
13
|
+
|
|
14
|
+
for (const group of submenu.groups) {
|
|
15
|
+
for (const link of group.links) {
|
|
16
|
+
const ltv: number | boolean | undefined = isCounterPropsObject(link.tag)
|
|
17
|
+
? link.tag.value
|
|
18
|
+
: link.tag;
|
|
19
|
+
|
|
20
|
+
if (ltv) {
|
|
21
|
+
if (typeof ltv === 'number') {
|
|
22
|
+
if (typeof tagValue !== 'number') {
|
|
23
|
+
tagValue = 0;
|
|
24
|
+
}
|
|
25
|
+
tagValue += ltv;
|
|
26
|
+
} else if (typeof tagValue !== 'number') {
|
|
27
|
+
tagValue = true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return tagValue ?? defaultTag;
|
|
34
|
+
}
|