@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.
Files changed (61) hide show
  1. package/dist/components/counter-tag.d.ts +1 -4
  2. package/dist/components/counter-tag.d.ts.map +1 -1
  3. package/dist/components/counter-tag.js.map +1 -1
  4. package/dist/components/layout.stories.js +2 -2
  5. package/dist/components/layout.stories.js.map +1 -1
  6. package/dist/components/left-navigation/index.d.ts +2 -0
  7. package/dist/components/left-navigation/index.d.ts.map +1 -1
  8. package/dist/components/left-navigation/index.js +2 -0
  9. package/dist/components/left-navigation/index.js.map +1 -1
  10. package/dist/components/left-navigation/interface-internal.d.ts +10 -0
  11. package/dist/components/left-navigation/interface-internal.d.ts.map +1 -0
  12. package/dist/components/left-navigation/interface-internal.js +2 -0
  13. package/dist/components/left-navigation/interface-internal.js.map +1 -0
  14. package/dist/components/left-navigation/interface.d.ts +11 -0
  15. package/dist/components/left-navigation/interface.d.ts.map +1 -0
  16. package/dist/components/left-navigation/interface.js +2 -0
  17. package/dist/components/left-navigation/interface.js.map +1 -0
  18. package/dist/components/left-navigation/side-navigation-links-internal.d.ts +23 -0
  19. package/dist/components/left-navigation/side-navigation-links-internal.d.ts.map +1 -0
  20. package/dist/components/left-navigation/side-navigation-links-internal.js +29 -0
  21. package/dist/components/left-navigation/side-navigation-links-internal.js.map +1 -0
  22. package/dist/components/left-navigation/side-navigation-links.d.ts +7 -0
  23. package/dist/components/left-navigation/side-navigation-links.d.ts.map +1 -0
  24. package/dist/components/left-navigation/side-navigation-links.js +20 -0
  25. package/dist/components/left-navigation/side-navigation-links.js.map +1 -0
  26. package/dist/components/left-navigation/side-navigation.d.ts +3 -11
  27. package/dist/components/left-navigation/side-navigation.d.ts.map +1 -1
  28. package/dist/components/left-navigation/side-navigation.js +12 -27
  29. package/dist/components/left-navigation/side-navigation.js.map +1 -1
  30. package/dist/components/left-navigation/side-navigation.module.less +11 -4
  31. package/dist/components/left-navigation/side-navigation.stories.d.ts +1 -0
  32. package/dist/components/left-navigation/side-navigation.stories.d.ts.map +1 -1
  33. package/dist/components/left-navigation/side-navigation.stories.js +26 -7
  34. package/dist/components/left-navigation/side-navigation.stories.js.map +1 -1
  35. package/dist/test/data.d.ts +1 -0
  36. package/dist/test/data.d.ts.map +1 -1
  37. package/dist/test/data.js +3 -2
  38. package/dist/test/data.js.map +1 -1
  39. package/dist/utils/counter-tag.d.ts +8 -0
  40. package/dist/utils/counter-tag.d.ts.map +1 -0
  41. package/dist/utils/counter-tag.js +2 -0
  42. package/dist/utils/counter-tag.js.map +1 -0
  43. package/dist/utils/side-nav.d.ts +3 -0
  44. package/dist/utils/side-nav.d.ts.map +1 -0
  45. package/dist/utils/side-nav.js +27 -0
  46. package/dist/utils/side-nav.js.map +1 -0
  47. package/package.json +4 -4
  48. package/src/components/counter-tag.tsx +1 -5
  49. package/src/components/layout.stories.tsx +2 -2
  50. package/src/components/left-navigation/index.ts +2 -0
  51. package/src/components/left-navigation/interface-internal.ts +11 -0
  52. package/src/components/left-navigation/interface.ts +13 -0
  53. package/src/components/left-navigation/side-navigation-links-internal.tsx +128 -0
  54. package/src/components/left-navigation/side-navigation-links.tsx +39 -0
  55. package/src/components/left-navigation/side-navigation.module.less +11 -4
  56. package/src/components/left-navigation/side-navigation.module.less.d.ts +2 -1
  57. package/src/components/left-navigation/side-navigation.stories.tsx +66 -7
  58. package/src/components/left-navigation/side-navigation.tsx +28 -131
  59. package/src/test/data.tsx +13 -2
  60. package/src/utils/counter-tag.ts +11 -0
  61. 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?: HeaderNavigationItemData[];
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?.length && (
65
+ {!!itemsTop && (
69
66
  <Fragment>
70
67
  <div className={Styles.sideNavTop} data-cy="navigation-items-top">
71
- {itemsTop.map(item => (
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
- <SideNavigationItem
84
+ <InternalSideNavigationLink
95
85
  key={item.id}
96
- expanded={expanded}
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
- SideNavigationItemProps & {
216
- onExpandedChange: undefined | ((expanded: SideNavigationExpandedState) => void);
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?.groups.some(group => group.links.some(link => !!link.tag))
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
- <SideNavigationItem {...props} tag={tag} />
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
- <SideNavigationItem
156
+ <InternalSideNavigationLink
260
157
  {...props}
261
- to={getFirstSubmenuLinkTo(props.submenu, props.to)}
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
- export const SideNavigationOptionsToggle: FC<{
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
+ }