@servicetitan/navigation 14.2.0 → 14.3.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 (89) hide show
  1. package/dist/components/titan-layout/__tests__/layout-sidebar-links-internal.test.d.ts +2 -0
  2. package/dist/components/titan-layout/__tests__/layout-sidebar-links-internal.test.d.ts.map +1 -0
  3. package/dist/components/titan-layout/__tests__/navigation-catalog-reset.test.d.ts +2 -0
  4. package/dist/components/titan-layout/__tests__/navigation-catalog-reset.test.d.ts.map +1 -0
  5. package/dist/components/titan-layout/interface.d.ts +66 -0
  6. package/dist/components/titan-layout/interface.d.ts.map +1 -1
  7. package/dist/components/titan-layout/interface.js.map +1 -1
  8. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts +3 -0
  9. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts.map +1 -1
  10. package/dist/components/titan-layout/layout-sidebar-links-internal.js +58 -13
  11. package/dist/components/titan-layout/layout-sidebar-links-internal.js.map +1 -1
  12. package/dist/components/titan-layout/layout-sidebar.d.ts +14 -0
  13. package/dist/components/titan-layout/layout-sidebar.d.ts.map +1 -1
  14. package/dist/components/titan-layout/layout-sidebar.js +169 -97
  15. package/dist/components/titan-layout/layout-sidebar.js.map +1 -1
  16. package/dist/components/titan-layout/layout-sidebar.module.less +230 -144
  17. package/dist/components/titan-layout/layout-sidebar.module.less.d.ts +16 -5
  18. package/dist/components/titan-layout/navigation-catalog/index.d.ts +3 -0
  19. package/dist/components/titan-layout/navigation-catalog/index.d.ts.map +1 -0
  20. package/dist/components/titan-layout/navigation-catalog/index.js +4 -0
  21. package/dist/components/titan-layout/navigation-catalog/index.js.map +1 -0
  22. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-footer.d.ts +8 -0
  23. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-footer.d.ts.map +1 -0
  24. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-footer.js +88 -0
  25. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-footer.js.map +1 -0
  26. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.d.ts +21 -0
  27. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.d.ts.map +1 -0
  28. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.js +138 -0
  29. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.js.map +1 -0
  30. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.module.less +89 -0
  31. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.module.less.d.ts +9 -0
  32. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.d.ts +14 -0
  33. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.d.ts.map +1 -0
  34. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.js +382 -0
  35. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.js.map +1 -0
  36. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.module.less +436 -0
  37. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.module.less.d.ts +24 -0
  38. package/dist/components/titan-layout/navigation-scroll-indicators.d.ts +12 -0
  39. package/dist/components/titan-layout/navigation-scroll-indicators.d.ts.map +1 -0
  40. package/dist/components/titan-layout/navigation-scroll-indicators.js +48 -0
  41. package/dist/components/titan-layout/navigation-scroll-indicators.js.map +1 -0
  42. package/dist/components/titan-layout/titan-layout.d.ts +29 -1
  43. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -1
  44. package/dist/components/titan-layout/titan-layout.js +46 -6
  45. package/dist/components/titan-layout/titan-layout.js.map +1 -1
  46. package/dist/components/titan-layout/titan-layout.module.less +2 -1
  47. package/dist/components/titan-layout/use-navigation-scroll.d.ts +20 -0
  48. package/dist/components/titan-layout/use-navigation-scroll.d.ts.map +1 -0
  49. package/dist/components/titan-layout/use-navigation-scroll.js +140 -0
  50. package/dist/components/titan-layout/use-navigation-scroll.js.map +1 -0
  51. package/dist/utils/__tests__/navigation-notifications.test.d.ts +2 -0
  52. package/dist/utils/__tests__/navigation-notifications.test.d.ts.map +1 -0
  53. package/dist/utils/__tests__/submenu-state.test.d.ts +2 -0
  54. package/dist/utils/__tests__/submenu-state.test.d.ts.map +1 -0
  55. package/dist/utils/navigation-notifications.d.ts +21 -0
  56. package/dist/utils/navigation-notifications.d.ts.map +1 -0
  57. package/dist/utils/navigation-notifications.js +39 -0
  58. package/dist/utils/navigation-notifications.js.map +1 -0
  59. package/dist/utils/side-nav.js +3 -1
  60. package/dist/utils/side-nav.js.map +1 -1
  61. package/dist/utils/submenu-state.d.ts +3 -0
  62. package/dist/utils/submenu-state.d.ts.map +1 -0
  63. package/dist/utils/submenu-state.js +15 -0
  64. package/dist/utils/submenu-state.js.map +1 -0
  65. package/package.json +2 -2
  66. package/src/components/titan-layout/__tests__/layout-sidebar-links-internal.test.tsx +225 -0
  67. package/src/components/titan-layout/__tests__/navigation-catalog-reset.test.tsx +186 -0
  68. package/src/components/titan-layout/interface.ts +73 -0
  69. package/src/components/titan-layout/layout-sidebar-links-internal.tsx +86 -14
  70. package/src/components/titan-layout/layout-sidebar.module.less +230 -144
  71. package/src/components/titan-layout/layout-sidebar.module.less.d.ts +16 -5
  72. package/src/components/titan-layout/layout-sidebar.tsx +193 -117
  73. package/src/components/titan-layout/navigation-catalog/index.ts +2 -0
  74. package/src/components/titan-layout/navigation-catalog/navigation-catalog-footer.tsx +88 -0
  75. package/src/components/titan-layout/navigation-catalog/navigation-catalog-trigger.module.less +89 -0
  76. package/src/components/titan-layout/navigation-catalog/navigation-catalog-trigger.module.less.d.ts +9 -0
  77. package/src/components/titan-layout/navigation-catalog/navigation-catalog-trigger.tsx +170 -0
  78. package/src/components/titan-layout/navigation-catalog/navigation-catalog.module.less +436 -0
  79. package/src/components/titan-layout/navigation-catalog/navigation-catalog.module.less.d.ts +24 -0
  80. package/src/components/titan-layout/navigation-catalog/navigation-catalog.tsx +458 -0
  81. package/src/components/titan-layout/navigation-scroll-indicators.tsx +80 -0
  82. package/src/components/titan-layout/titan-layout.module.less +2 -1
  83. package/src/components/titan-layout/titan-layout.tsx +81 -5
  84. package/src/components/titan-layout/use-navigation-scroll.ts +145 -0
  85. package/src/utils/__tests__/navigation-notifications.test.ts +70 -0
  86. package/src/utils/__tests__/submenu-state.test.ts +39 -0
  87. package/src/utils/navigation-notifications.ts +59 -0
  88. package/src/utils/side-nav.ts +1 -1
  89. package/src/utils/submenu-state.ts +16 -0
@@ -2,7 +2,16 @@ import { Button, Icon, IconProps } from '@servicetitan/anvil2';
2
2
  import SvgGroupCollapse from '@servicetitan/anvil2/assets/icons/material/round/expand_less.svg';
3
3
  import SvgGroupExpand from '@servicetitan/anvil2/assets/icons/material/round/expand_more.svg';
4
4
  import classNames from 'classnames';
5
- import { ComponentPropsWithoutRef, FC, Fragment, MouseEvent, ReactNode } from 'react';
5
+ import {
6
+ ComponentPropsWithoutRef,
7
+ FC,
8
+ Fragment,
9
+ MouseEvent,
10
+ ReactNode,
11
+ SyntheticEvent,
12
+ useId,
13
+ } from 'react';
14
+ import { getNotificationCount } from '../../utils/navigation-notifications';
6
15
 
7
16
  import { BadgeTag, BadgeTagProps } from '../badge-tag';
8
17
  import { NavigationComponentProps } from './interface-internal';
@@ -46,12 +55,7 @@ const InternalSideNavigationItemContent: FC<InternalSideNavigationItemContentPro
46
55
  )}
47
56
  </div>
48
57
 
49
- <div
50
- className={classNames(Styles.navItemTextCollapsed, {
51
- [Styles.navItemTextSmall]: !!title && title.length >= 10,
52
- })}
53
- data-cy="nav-item-label"
54
- >
58
+ <div className={Styles.navItemTextCollapsed} data-cy="nav-item-label">
55
59
  {title}
56
60
  </div>
57
61
  </Fragment>
@@ -68,6 +72,7 @@ export const InternalSideNavigationLink: FC<
68
72
  iconActive: IconProps['svg'] | undefined;
69
73
  className: string | undefined;
70
74
  tag: BadgeTagProps | undefined;
75
+ notificationCount?: number;
71
76
  } & Omit<ComponentPropsWithoutRef<'a'>, 'children' | 'title'>
72
77
  > = ({
73
78
  id,
@@ -78,6 +83,7 @@ export const InternalSideNavigationLink: FC<
78
83
  iconActive,
79
84
  className,
80
85
  tag,
86
+ notificationCount = getNotificationCount(tag?.value),
81
87
 
82
88
  navigationComponent: NavigationComponent,
83
89
  ...rest
@@ -86,6 +92,7 @@ export const InternalSideNavigationLink: FC<
86
92
  <NavigationComponent
87
93
  data-cy={`navigation-item-${id}`}
88
94
  data-pendo={`navigation-item-${id}`}
95
+ data-navigation-notification-count={notificationCount || undefined}
89
96
  {...rest}
90
97
  className={classNames(Styles.navItem, Styles.navLink, className, {
91
98
  [Styles.navItemActive]: isActive === true,
@@ -117,6 +124,7 @@ export const InternalSideNavigationTrigger: FC<
117
124
  className: string | undefined;
118
125
 
119
126
  tag: BadgeTagProps | undefined;
127
+ notificationCount?: number;
120
128
  onClick?: (e: MouseEvent<never>) => void;
121
129
  } & Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'title'>
122
130
  > = ({
@@ -128,12 +136,14 @@ export const InternalSideNavigationTrigger: FC<
128
136
  className,
129
137
 
130
138
  tag,
139
+ notificationCount = getNotificationCount(tag?.value),
131
140
  ...rest
132
141
  }) => {
133
142
  return (
134
143
  <div
135
144
  data-cy={`navigation-item-${id}`}
136
145
  data-pendo={`navigation-item-${id}`}
146
+ data-navigation-notification-count={notificationCount || undefined}
137
147
  tabIndex={rest.onClick ? 0 : -1}
138
148
  {...rest}
139
149
  className={classNames(Styles.navItem, className, {
@@ -186,6 +196,7 @@ export const InternalSideNavigationGroupLink: FC<
186
196
  key={id}
187
197
  data-cy={`navigation-item-${parentId}--${id}`}
188
198
  data-pendo={`navigation-item-${parentId}--${id}`}
199
+ data-navigation-notification-count={getNotificationCount(tag?.value) || undefined}
189
200
  {...rest}
190
201
  to={to}
191
202
  className={classNames(Styles.submenuItem, Styles.submenuLink, className, {
@@ -221,6 +232,7 @@ export const InternalSideNavigationGroupTrigger: FC<{
221
232
  return (
222
233
  <div
223
234
  data-cy={`navigation-item-${id}`}
235
+ data-navigation-notification-count={getNotificationCount(tag?.value) || undefined}
224
236
  data-pendo={`navigation-item-${id}`}
225
237
  key={id}
226
238
  {...rest}
@@ -250,6 +262,7 @@ export const InternalSideNavigationGroup: FC<
250
262
  className: string | undefined;
251
263
  tag: BadgeTagProps | undefined;
252
264
 
265
+ notificationCount?: number;
253
266
  children: ReactNode;
254
267
  submenuOpened: boolean;
255
268
  onClick?: (e: MouseEvent<never>) => void;
@@ -265,6 +278,7 @@ export const InternalSideNavigationGroup: FC<
265
278
  className,
266
279
  tag,
267
280
 
281
+ notificationCount,
268
282
  children,
269
283
  submenuOpened,
270
284
  onSubmenuOpen,
@@ -272,16 +286,62 @@ export const InternalSideNavigationGroup: FC<
272
286
  navigationComponent,
273
287
  ...rest
274
288
  }) => {
275
- const onToggleClick = (e: MouseEvent<never>) => {
276
- onSubmenuOpen(id, !submenuOpened);
289
+ const submenuId = useId();
290
+ const onToggleClick = (e: SyntheticEvent) => {
277
291
  e.preventDefault();
278
292
  e.stopPropagation();
293
+ onSubmenuOpen(id, !submenuOpened);
279
294
  };
280
295
 
281
296
  return (
282
- <div className={classNames(Styles.navGroupWrapper)}>
297
+ <div
298
+ className={classNames(Styles.navGroupWrapper, {
299
+ [Styles.navGroupOpened]: submenuOpened,
300
+ })}
301
+ >
283
302
  <div className={Styles.navGroupItem}>
284
- <div>
303
+ <div
304
+ // Update expansion before the host link handles navigation to its destination.
305
+ onClickCapture={event => {
306
+ if (!to) {
307
+ onToggleClick(event);
308
+ } else if (
309
+ /*
310
+ * A module name only ever opens. It navigates into the module, so
311
+ * collapsing would hide the very page the click is about to land on —
312
+ * the destination is `to`, whether that is a page itself or an entry
313
+ * route the host redirects to the module's default page. Collapsing an
314
+ * open module is the chevron's job.
315
+ */
316
+ !submenuOpened &&
317
+ event.button === 0 &&
318
+ !event.altKey &&
319
+ !event.ctrlKey &&
320
+ !event.metaKey &&
321
+ !event.shiftKey
322
+ ) {
323
+ onSubmenuOpen(id, true);
324
+ }
325
+ }}
326
+ onClick={event => event.stopPropagation()}
327
+ onKeyDownCapture={event => {
328
+ if (
329
+ (event.key === 'Enter' || event.key === ' ') &&
330
+ !event.altKey &&
331
+ !event.ctrlKey &&
332
+ !event.metaKey &&
333
+ !event.shiftKey
334
+ ) {
335
+ if (to) {
336
+ event.preventDefault();
337
+ event.stopPropagation();
338
+ event.currentTarget.querySelector('a')?.click();
339
+ } else {
340
+ onToggleClick(event);
341
+ }
342
+ }
343
+ }}
344
+ >
285
345
  {to ? (
286
346
  <InternalSideNavigationLink
287
347
  id={id}
@@ -291,8 +351,11 @@ export const InternalSideNavigationGroup: FC<
291
351
  icon={icon}
292
352
  iconActive={iconActive}
293
353
  tag={tag}
354
+ notificationCount={notificationCount}
294
355
  className={className}
295
356
  {...rest}
357
+ aria-expanded={submenuOpened}
358
+ aria-controls={submenuId}
296
359
  navigationComponent={navigationComponent}
297
360
  />
298
361
  ) : (
@@ -303,8 +366,14 @@ export const InternalSideNavigationGroup: FC<
303
366
  icon={icon}
304
367
  iconActive={iconActive}
305
368
  tag={tag}
369
+ notificationCount={notificationCount}
306
370
  className={className}
307
371
  {...rest}
372
+ role="button"
373
+ tabIndex={0}
374
+ onClick={onToggleClick}
375
+ aria-expanded={submenuOpened}
376
+ aria-controls={submenuId}
308
377
  />
309
378
  )}
310
379
  </div>
@@ -312,12 +381,14 @@ export const InternalSideNavigationGroup: FC<
312
381
  <div className={Styles.navItemGroupToggleWrapper}>
313
382
  {withTooltip(
314
383
  <Button
315
- appearance="secondary"
316
- size="small"
384
+ appearance="ghost"
385
+ size="xsmall"
317
386
  icon={submenuOpened ? SvgGroupCollapse : SvgGroupExpand}
318
387
  className={Styles.navItemGroupToggle}
319
388
  onClick={onToggleClick}
320
- aria-label="Toggle submenu"
389
+ aria-label={`${submenuOpened ? 'Collapse' : 'Expand'} ${title} submenu`}
390
+ aria-expanded={submenuOpened}
391
+ aria-controls={submenuId}
321
392
  data-cy="nav-item-group-expand"
322
393
  />,
323
394
  // hide submenu tooltip for now
@@ -328,6 +399,7 @@ export const InternalSideNavigationGroup: FC<
328
399
  </div>
329
400
  {submenuOpened && (
330
401
  <div
402
+ id={submenuId}
331
403
  className={classNames(Styles.submenuWrapper, {
332
404
  [Styles.submenuWrapperCollapsed]: !submenuOpened,
333
405
  })}