@servicetitan/navigation 8.1.6 → 8.2.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.
@@ -1,4 +1,4 @@
1
- import { withAnvil, withMemoryRouter } from '../test/data';
1
+ import { LocationInfo, withAnvil, withMemoryRouter } from '../test/data';
2
2
  import {
3
3
  WithAllMonolithData,
4
4
  WithAllMonolithDataCommercial,
@@ -6,6 +6,7 @@ import {
6
6
  import {
7
7
  DefaultSideNavigation,
8
8
  SideNavigationLinksOnly,
9
+ SideNavigationWithSubmenu,
9
10
  } from './left-navigation/side-navigation.stories';
10
11
 
11
12
  export default {
@@ -49,3 +50,17 @@ export const LeftNavLayoutOnlyLinks = () => {
49
50
  </div>
50
51
  );
51
52
  };
53
+
54
+ export const LeftNavLayoutSubmenu = () => {
55
+ return (
56
+ <div className="d-f border flex-column" style={{ height: '800px' }}>
57
+ <WithAllMonolithDataCommercial />
58
+ <div className="flex-grow-1 flex-basis-0 d-f">
59
+ <SideNavigationWithSubmenu />
60
+ <div className="flex-grow-1 flex-basis-0 p-5">
61
+ <LocationInfo />
62
+ </div>
63
+ </div>
64
+ </div>
65
+ );
66
+ };
@@ -75,7 +75,7 @@
75
75
 
76
76
  .navigation-item-icon-wrapper {
77
77
  flex: 1;
78
- padding: @spacing-1;
78
+ padding: @spacing-1 @spacing-half;
79
79
  }
80
80
 
81
81
  .navigation-item-text {
@@ -160,7 +160,7 @@
160
160
  }
161
161
 
162
162
  &.navigation-item-icon-switch {
163
- &.navigation-icon-active {
163
+ &.navigation-item-active {
164
164
  .navigation-icon-inactive[data-anv][data-anv] {
165
165
  display: none;
166
166
  }
@@ -168,7 +168,7 @@
168
168
  display: block;
169
169
  }
170
170
  }
171
- &:not(.navigation-icon-active) {
171
+ &:not(.navigation-item-active) {
172
172
  .navigation-icon-inactive[data-anv][data-anv] {
173
173
  display: block;
174
174
  }
@@ -187,6 +187,11 @@
187
187
  font-weight: @font-weight-semibold;
188
188
  }
189
189
 
190
+ .navigation-item-group-toggle[data-anv][data-anv] {
191
+ color: inherit;
192
+ font-weight: @font-weight-semibold;
193
+ }
194
+
190
195
  .navigation-icon[data-anv][data-anv] {
191
196
  height: 24px;
192
197
  width: 24px;
@@ -199,6 +204,114 @@
199
204
  }
200
205
  }
201
206
 
207
+ .submenu {
208
+ margin-left: @spacing-3;
209
+ padding-left: @spacing-1;
210
+ padding-right: @spacing-1;
211
+ margin-bottom: @spacing-1;
212
+ position: relative;
213
+
214
+ &:before {
215
+ content: '';
216
+ position: absolute;
217
+ border-left: 1px solid @color-neutral-100;
218
+ width: 1px;
219
+ top: @spacing-2;
220
+ bottom: @spacing-1;
221
+ left: 0;
222
+ }
223
+
224
+ .submenu-group-header[data-anv][data-anv] {
225
+ padding-top: @spacing-2;
226
+ padding-bottom: @spacing-half;
227
+ }
228
+
229
+ .submenu-link {
230
+ padding: @spacing-1;
231
+ }
232
+
233
+ .submenu-link-active {
234
+ position: relative;
235
+ }
236
+
237
+ .submenu-link-active:before {
238
+ content: '';
239
+ position: absolute;
240
+ background-color: @text-color-active;
241
+ width: 3px;
242
+ top: @spacing-1;
243
+ bottom: @spacing-1;
244
+ left: -12px;
245
+ }
246
+
247
+ .submenu-link:before:not(.submenu-link-active) {
248
+ background-color: @bg-color-hover;
249
+ }
250
+
251
+ > *,
252
+ > *[data-anv][data-anv] {
253
+ border-left: 3px solid transparent;
254
+ padding-left: @spacing-1;
255
+ }
256
+
257
+ > *:last-child {
258
+ margin-bottom: @spacing-0;
259
+ }
260
+ }
261
+
262
+ .submenu-popover {
263
+ margin-left: -@spacing-1;
264
+ margin-right: -@spacing-1;
265
+ min-width: 240px;
266
+
267
+ .submenu-group-header[data-anv][data-anv] {
268
+ margin-top: @spacing-2;
269
+ padding-bottom: @spacing-half;
270
+ }
271
+
272
+ .submenu-link {
273
+ padding-top: @spacing-1;
274
+ padding-bottom: @spacing-1;
275
+ }
276
+
277
+ .submenu-link-active {
278
+ background-color: @bg-color-active;
279
+ }
280
+ .submenu-link:hover:not(.submenu-link-active) {
281
+ background-color: @bg-color-hover;
282
+ }
283
+
284
+ > *,
285
+ > *[data-anv][data-anv] {
286
+ padding-left: @spacing-1;
287
+ padding-right: @spacing-1;
288
+ }
289
+ }
290
+
291
+ .submenu,
292
+ .submenu-popover {
293
+ display: flex;
294
+ flex-direction: column;
295
+
296
+ .submenu-group-header[data-anv][data-anv] {
297
+ color: @color-neutral-70;
298
+ }
299
+
300
+ .submenu-link {
301
+ color: @text-color;
302
+ font-size: @typescale-2;
303
+ border-radius: @border-radius-2;
304
+ }
305
+
306
+ .submenu-link-active {
307
+ color: @text-color-active;
308
+ }
309
+
310
+ .submenu-link:hover:not(.submenu-link-active) {
311
+ background-color: @bg-color-hover;
312
+ }
313
+ }
314
+
202
315
  .options-item {
203
316
  font-family: @base-font-family;
204
317
  color: @text-color;
@@ -6,6 +6,7 @@ export const navigationIconInactive: string;
6
6
  export const navigationItem: string;
7
7
  export const navigationItemActive: string;
8
8
  export const navigationItemCounter: string;
9
+ export const navigationItemGroupToggle: string;
9
10
  export const navigationItemIconSwitch: string;
10
11
  export const navigationItemIconWrapper: string;
11
12
  export const navigationItemText: string;
@@ -19,4 +20,9 @@ export const sideNavContent: string;
19
20
  export const sideNavExpanded: string;
20
21
  export const sideNavSlim: string;
21
22
  export const sideNavTop: string;
23
+ export const submenu: string;
24
+ export const submenuGroupHeader: string;
25
+ export const submenuLink: string;
26
+ export const submenuLinkActive: string;
27
+ export const submenuPopover: string;
22
28
 
@@ -1,12 +1,14 @@
1
1
  import { ComponentType, useState } from 'react';
2
- import { items, withAnvil, withMemoryRouter } from '../../test/data';
2
+ import { LocationInfo, items, withAnvil, withMemoryRouter } from '../../test/data';
3
3
  import { SideNavigation } from './';
4
4
 
5
5
  const layout = (Story: ComponentType) => {
6
6
  return (
7
7
  <div className="d-f border" style={{ height: '800px' }}>
8
8
  <Story />
9
- <div className="flex-grow-1 flex-basis-0" />
9
+ <div className="flex-grow-1 flex-basis-0 p-5">
10
+ <LocationInfo />
11
+ </div>
10
12
  </div>
11
13
  );
12
14
  };
@@ -69,3 +71,29 @@ export const SideNavigationLinksOnly = () => {
69
71
  />
70
72
  );
71
73
  };
74
+
75
+ export const SideNavigationWithSubmenu = () => {
76
+ const [expanded, setExpanded] = useState(false);
77
+ return (
78
+ <SideNavigation
79
+ expanded={expanded}
80
+ onExpandedChange={setExpanded}
81
+ items={[
82
+ items.dashboard,
83
+ items.calls,
84
+ items.schedule,
85
+ items.dispatch,
86
+
87
+ items.accountingWithSubmenu,
88
+ items.purchasingWithSubmenu,
89
+
90
+ items.followUps,
91
+ items.reports,
92
+ items.marketing,
93
+ items.priceBook,
94
+
95
+ items.projects,
96
+ ]}
97
+ />
98
+ );
99
+ };
@@ -1,9 +1,27 @@
1
- import { Icon } from '@servicetitan/anvil2';
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';
2
4
  import SvgCollapse from '@servicetitan/anvil2/assets/icons/st/gnav_menu_collapse.svg';
3
5
  import SvgExpand from '@servicetitan/anvil2/assets/icons/st/gnav_menu_expand.svg';
6
+ import { Collapsible, Headline } from '@servicetitan/design-system';
7
+
4
8
  import classNames from 'classnames';
5
- import { FC, Fragment, useContext } from 'react';
6
- import { HeaderNavigationItemData, NavLinkComponentProps } from '../../utils/navigation';
9
+ import {
10
+ CSSProperties,
11
+ FC,
12
+ Fragment,
13
+ MouseEvent,
14
+ ReactElement,
15
+ useCallback,
16
+ useContext,
17
+ useState,
18
+ } from 'react';
19
+ import {
20
+ HeaderNavigationItemData,
21
+ HeaderNavigationItemLinkProps,
22
+ HeaderNavigationItemSubmenu,
23
+ NavLinkComponentProps,
24
+ } from '../../utils/navigation';
7
25
  import { NavigationComponentContext } from '../../utils/navigation-context';
8
26
  import { CounterTag } from '../counter-tag';
9
27
  import * as Styles from './side-navigation.module.less';
@@ -59,14 +77,23 @@ export const SideNavigation: FC<SideNavigationProps> = ({
59
77
  </Fragment>
60
78
  )}
61
79
  <div className={Styles.sideNavContent} data-cy="navigation-items">
62
- {items?.map(item => (
63
- <SideNavigationItem
64
- key={item.id}
65
- expanded={expanded}
66
- navigationComponent={NavigationComponent}
67
- {...item}
68
- />
69
- ))}
80
+ {items?.map(item =>
81
+ item.submenu ? (
82
+ <SideNavigationGroupItem
83
+ key={item.id}
84
+ expanded={expanded}
85
+ navigationComponent={NavigationComponent}
86
+ {...item}
87
+ />
88
+ ) : (
89
+ <SideNavigationItem
90
+ key={item.id}
91
+ expanded={expanded}
92
+ navigationComponent={NavigationComponent}
93
+ {...item}
94
+ />
95
+ )
96
+ )}
70
97
  </div>
71
98
  <div className={Styles.divider} />
72
99
  <div className={Styles.sideNavBottom}>
@@ -79,14 +106,19 @@ export const SideNavigation: FC<SideNavigationProps> = ({
79
106
  );
80
107
  };
81
108
 
82
- interface SideNavigationItemProps extends HeaderNavigationItemData {
109
+ interface NavigationComponentProps {
83
110
  navigationComponent: FC<NavLinkComponentProps>;
111
+ }
112
+
113
+ interface SideNavigationItemProps extends HeaderNavigationItemData, NavigationComponentProps {
84
114
  expanded?: boolean;
115
+ submenuExpanded?: boolean;
85
116
  }
86
117
 
87
118
  /** Side Navigation menu item */
88
119
  const SideNavigationItem: FC<SideNavigationItemProps> = ({
89
120
  id,
121
+ submenuExpanded,
90
122
  to,
91
123
  title,
92
124
  hint,
@@ -101,6 +133,7 @@ const SideNavigationItem: FC<SideNavigationItemProps> = ({
101
133
  expanded,
102
134
  }) => {
103
135
  const iconSwitch = !!icon && !!iconActive && !IconComponent;
136
+ const hasSubmenu = submenuExpanded === true || submenuExpanded === false;
104
137
 
105
138
  return (
106
139
  <NavigationComponent
@@ -150,6 +183,12 @@ const SideNavigationItem: FC<SideNavigationItemProps> = ({
150
183
  {!!counter && (
151
184
  <CounterTag data={counter} className={Styles.navigationItemCounter} />
152
185
  )}
186
+ {hasSubmenu && !!expanded && (
187
+ <Icon
188
+ svg={submenuExpanded ? SvgGroupCollapse : SvgGroupExpand}
189
+ className={Styles.navigationItemGroupToggle}
190
+ />
191
+ )}
153
192
  </div>
154
193
 
155
194
  {!expanded && <div className={Styles.navigationItemText}>{title}</div>}
@@ -157,6 +196,119 @@ const SideNavigationItem: FC<SideNavigationItemProps> = ({
157
196
  );
158
197
  };
159
198
 
199
+ const submenuPopoverStyles = { '--background-color-strong': '#24323C' } as CSSProperties;
200
+
201
+ /** Side Navigation menu item */
202
+ const SideNavigationGroupItem: FC<SideNavigationItemProps> = ({ ...props }) => {
203
+ const [submenuExpanded, setSubmenuExpanded] = useState(false);
204
+ const triggerClick = useCallback(
205
+ (e: MouseEvent<HTMLDivElement>) => {
206
+ e.stopPropagation();
207
+ e.preventDefault();
208
+
209
+ if (props.expanded) {
210
+ setSubmenuExpanded(exp => !exp);
211
+ }
212
+ },
213
+ [props.expanded]
214
+ );
215
+
216
+ return props.expanded ? (
217
+ <Fragment>
218
+ <div onClickCapture={triggerClick}>
219
+ <SideNavigationItem {...props} submenuExpanded={submenuExpanded} />
220
+ </div>
221
+ <Collapsible open={submenuExpanded} animate>
222
+ <div className={Styles.submenu}>
223
+ <SideNavigationGroupContent
224
+ groups={props.submenu?.groups ?? []}
225
+ navigationComponent={props.navigationComponent}
226
+ />
227
+ </div>
228
+ </Collapsible>
229
+ </Fragment>
230
+ ) : (
231
+ <Popover placement="right-start" openOnHover>
232
+ <Popover.Trigger>
233
+ {(triggerProps: PopoverTriggerProps) => (
234
+ <div {...triggerProps} onClickCapture={triggerClick}>
235
+ <SideNavigationItem {...props} />
236
+ </div>
237
+ )}
238
+ </Popover.Trigger>
239
+ <Popover.Content style={submenuPopoverStyles}>
240
+ <div className={Styles.submenuPopover}>
241
+ <Headline size="small" className="c-white m-b-half-i m-t-1">
242
+ {props.title}
243
+ </Headline>
244
+ <SideNavigationGroupContent
245
+ groups={props.submenu?.groups ?? []}
246
+ navigationComponent={props.navigationComponent}
247
+ />
248
+ </div>
249
+ </Popover.Content>
250
+ </Popover>
251
+ );
252
+ };
253
+ const SideNavigationGroupContent: FC<HeaderNavigationItemSubmenu & NavigationComponentProps> = ({
254
+ groups,
255
+ navigationComponent,
256
+ }) => {
257
+ return (
258
+ <Fragment>
259
+ {groups.reduce((out, group) => {
260
+ if (!group.links.length) {
261
+ return out;
262
+ }
263
+
264
+ out.push(
265
+ <Text
266
+ key=":group:title"
267
+ variant="eyebrow"
268
+ className={Styles.submenuGroupHeader}
269
+ >
270
+ {group.title}
271
+ </Text>
272
+ );
273
+
274
+ out.push(
275
+ ...group.links.map(link => (
276
+ <SideNavigationGroupLink
277
+ key={link.id}
278
+ {...link}
279
+ navigationComponent={navigationComponent}
280
+ />
281
+ ))
282
+ );
283
+ return out;
284
+ }, [] as ReactElement[])}
285
+ </Fragment>
286
+ );
287
+ };
288
+ const SideNavigationGroupLink: FC<HeaderNavigationItemLinkProps & NavigationComponentProps> = ({
289
+ id,
290
+ title,
291
+ to,
292
+ isActive,
293
+ navigationComponent: NavigationComponent,
294
+ }) => {
295
+ return (
296
+ <NavigationComponent
297
+ data-cy={`navigation-item-${id}`}
298
+ data-pendo={`navigation-item-${id}`}
299
+ key={id}
300
+ to={to}
301
+ className={classNames(Styles.submenuLink, {
302
+ [Styles.submenuLinkActive]: isActive === true,
303
+ })}
304
+ isActive={typeof isActive === 'function' ? isActive : undefined}
305
+ activeClassName={Styles.submenuLinkActive}
306
+ >
307
+ {title}
308
+ </NavigationComponent>
309
+ );
310
+ };
311
+
160
312
  /** Side Navigation options toggle */
161
313
  export const SideNavigationOptionsToggle: FC<{
162
314
  expanded?: boolean;
package/src/test/data.tsx CHANGED
@@ -30,7 +30,7 @@ import SvgSchedule from '@servicetitan/anvil2/assets/icons/st/gnav_schedule_inac
30
30
  import SvgTasksActive from '@servicetitan/anvil2/assets/icons/st/gnav_tasks_active.svg';
31
31
  import SvgTasks from '@servicetitan/anvil2/assets/icons/st/gnav_tasks_inactive.svg';
32
32
 
33
- import { Popover } from '@servicetitan/design-system';
33
+ import { BodyText, Popover } from '@servicetitan/design-system';
34
34
 
35
35
  import classNames from 'classnames';
36
36
  import { forwardRef, useState } from 'react';
@@ -38,7 +38,12 @@ import { forwardRef, useState } from 'react';
38
38
  // eslint-disable-next-line import/no-extraneous-dependencies
39
39
  import { MemoryRouter, useHistory, useLocation } from 'react-router-dom';
40
40
  import { HeaderNavigationTrigger } from '../components/links';
41
- import { HeaderNavigationItemData, NavLinkComponentProps } from '../utils/navigation';
41
+ import {
42
+ HeaderNavigationItemData,
43
+ HeaderNavigationItemLinkProps,
44
+ HeaderNavigationItemSubmenuGroup,
45
+ NavLinkComponentProps,
46
+ } from '../utils/navigation';
42
47
  import { NavigationComponentContext } from '../utils/navigation-context';
43
48
  import * as Styles from './data-stories.module.less';
44
49
 
@@ -46,20 +51,21 @@ export const NavLinkMock = forwardRef<any, NavLinkComponentProps>(
46
51
  ({ to, children, activeClassName, className, isActive, ...rest }, ref) => {
47
52
  const history = useHistory();
48
53
  const location = useLocation();
49
- const linkActive = location.pathname.replace('/', '') === to;
54
+ const linkActive = location.pathname.startsWith(to);
50
55
 
51
56
  return (
52
57
  <a
53
58
  {...rest}
54
- className={classNames(className, linkActive ? activeClassName : '')}
55
- href={to}
56
59
  onClick={e => {
57
60
  e.preventDefault();
61
+ e.stopPropagation();
58
62
 
59
63
  if (!to.startsWith('http')) {
60
- history.push(to);
64
+ history.replace(to);
61
65
  }
62
66
  }}
67
+ className={classNames(className, linkActive ? activeClassName : '')}
68
+ href={to}
63
69
  ref={ref}
64
70
  >
65
71
  {children}
@@ -68,6 +74,12 @@ export const NavLinkMock = forwardRef<any, NavLinkComponentProps>(
68
74
  }
69
75
  );
70
76
 
77
+ export const LocationInfo = () => {
78
+ const location = useLocation();
79
+
80
+ return <BodyText>current location - {location.pathname}</BodyText>;
81
+ };
82
+
71
83
  export const withMemoryRouter = (Story: any) => (
72
84
  <MemoryRouter>
73
85
  <NavigationComponentContext.Provider value={NavLinkMock}>
@@ -111,12 +123,42 @@ const getItem = (
111
123
  data: Partial<HeaderNavigationItemData>
112
124
  ): HeaderNavigationItemData => ({
113
125
  id,
114
- to: id,
126
+ to: '/' + id,
115
127
  title: id[0].toUpperCase() + id.substring(1),
116
128
  hint: id,
117
129
  icon: undefined,
118
130
  iconActive: undefined,
119
131
  ...(data ?? {}),
132
+ submenu: data.submenu
133
+ ? {
134
+ ...data.submenu,
135
+ groups: data.submenu.groups.map(group => ({
136
+ ...group,
137
+ links: group.links.map(link => ({
138
+ ...link,
139
+ to: `/${id}/${link.to}`,
140
+ })),
141
+ })),
142
+ }
143
+ : undefined,
144
+ });
145
+
146
+ const getSubItem = (
147
+ id: string,
148
+ data: Partial<HeaderNavigationItemLinkProps>
149
+ ): HeaderNavigationItemLinkProps => ({
150
+ id,
151
+ to: id,
152
+ title: id[0].toUpperCase() + id.substring(1),
153
+ ...(data ?? {}),
154
+ });
155
+
156
+ const getGroup = (
157
+ title: string,
158
+ links: HeaderNavigationItemLinkProps[]
159
+ ): HeaderNavigationItemSubmenuGroup => ({
160
+ title,
161
+ links,
120
162
  });
121
163
 
122
164
  export const items = {
@@ -165,11 +207,45 @@ export const items = {
165
207
  iconActive: SvgInventoryActive,
166
208
  }),
167
209
  purchasing: getItem('purchasing', { iconComponent: InventoryIcon }),
210
+ purchasingWithSubmenu: getItem('purchasing', {
211
+ iconName: 'toys',
212
+ icon: SvgInventory,
213
+ iconActive: SvgInventoryActive,
214
+ submenu: {
215
+ groups: [
216
+ getGroup('Purchase', [
217
+ getSubItem('repl', { title: 'Replenishment' }),
218
+ getSubItem('orders', { title: 'Purchase Orders' }),
219
+ getSubItem('receipts', { title: 'Receipts' }),
220
+ getSubItem('returns', { title: 'Returns' }),
221
+ ]),
222
+ ],
223
+ },
224
+ }),
168
225
  accounting: getItem('accounting', {
169
226
  iconName: 'assignment',
170
227
  icon: SvgAccounting,
171
228
  iconActive: SvgAccountingActive,
172
229
  }),
230
+ accountingWithSubmenu: getItem('accounting', {
231
+ iconName: 'assignment',
232
+ icon: SvgAccounting,
233
+ iconActive: SvgAccountingActive,
234
+ submenu: {
235
+ groups: [
236
+ getGroup('Accounts Receivable', [
237
+ getSubItem('ar', { title: 'AR Management' }),
238
+ getSubItem('export', { title: 'Batch/Export Transactions' }),
239
+ getSubItem('invoices', { title: 'Invoices' }),
240
+ getSubItem('payments', { title: 'Customer Payments' }),
241
+ getSubItem('deposits', { title: 'Bank Deposits' }),
242
+ ]),
243
+ getGroup('Accounts Payable', [getSubItem('bills', { title: 'Bills' })]),
244
+ getGroup('Financing', [getSubItem('dashboard', { title: 'Dashboard' })]),
245
+ getGroup('Others', [getSubItem('at', { title: 'Accounting Audit Trail' })]),
246
+ ],
247
+ },
248
+ }),
173
249
  marketing: getItem('marketing', {
174
250
  iconName: 'bullhorn',
175
251
  icon: SvgMarketing,
@@ -3,22 +3,10 @@ import { IconPropsStrict } from '@servicetitan/design-system';
3
3
  import { FC, HTMLAttributeAnchorTarget, ReactNode } from 'react';
4
4
  import { CounterTagPropsType } from '../components/counter-tag';
5
5
 
6
- export interface HeaderNavigationItemData {
7
- /** link id */
8
- id: string;
9
-
10
- /** link href */
11
- to: string;
12
-
13
- /** link title */
14
- title: string;
15
-
6
+ export interface HeaderNavigationItemData extends HeaderNavigationItemLinkProps {
16
7
  /** link description */
17
8
  hint: string;
18
9
 
19
- /** callback to return active state. By default, it compares link href with current pathname */
20
- isActive?: boolean | ((pathname: string) => boolean);
21
-
22
10
  /** flag if the link is not shown (based on FG and/or user permissions) */
23
11
  isHidden?: boolean;
24
12
 
@@ -42,6 +30,36 @@ export interface HeaderNavigationItemData {
42
30
 
43
31
  /** class name of link item */
44
32
  className?: string;
33
+
34
+ /** optional submenu of link item */
35
+ submenu?: HeaderNavigationItemSubmenu;
36
+ }
37
+
38
+ export interface HeaderNavigationItemLinkProps {
39
+ /** link id */
40
+ id: string;
41
+
42
+ /** link href */
43
+ to: string;
44
+
45
+ /** link title */
46
+ title: string;
47
+
48
+ /** callback to return active state. By default, it compares link href with current pathname */
49
+ isActive?: boolean | ((pathname: string) => boolean);
50
+ }
51
+
52
+ export interface HeaderNavigationItemSubmenu {
53
+ /** submenu groups */
54
+ groups: HeaderNavigationItemSubmenuGroup[];
55
+ }
56
+
57
+ export interface HeaderNavigationItemSubmenuGroup {
58
+ /** submenu group title */
59
+ title: string;
60
+
61
+ /** submenu group links */
62
+ links: HeaderNavigationItemLinkProps[];
45
63
  }
46
64
 
47
65
  export interface NavLinkComponentPropsStrict {