@servicetitan/navigation 0.1.0 → 0.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.
- package/dist/components/header-navigation/header-navigation.d.ts +25 -1
- package/dist/components/header-navigation/header-navigation.d.ts.map +1 -1
- package/dist/components/header-navigation/header-navigation.js +19 -14
- package/dist/components/header-navigation/header-navigation.js.map +1 -1
- package/dist/components/header-navigation/header-navigation.module.less +32 -1
- package/dist/components/header-navigation/header-navigation.stories.d.ts +1 -0
- package/dist/components/header-navigation/header-navigation.stories.d.ts.map +1 -1
- package/dist/components/header-navigation/header-navigation.stories.js +34 -5
- package/dist/components/header-navigation/header-navigation.stories.js.map +1 -1
- package/dist/components/logo/logo.stories.js +1 -1
- package/dist/components/logo/logo.stories.js.map +1 -1
- package/dist/components/profile-dropdown/profile-dropdown.d.ts +53 -0
- package/dist/components/profile-dropdown/profile-dropdown.d.ts.map +1 -0
- package/dist/components/profile-dropdown/profile-dropdown.js +60 -0
- package/dist/components/profile-dropdown/profile-dropdown.js.map +1 -0
- package/dist/components/profile-dropdown/profile-dropdown.module.less +136 -0
- package/dist/components/profile-dropdown/profile-dropdown.stories.d.ts +16 -0
- package/dist/components/profile-dropdown/profile-dropdown.stories.d.ts.map +1 -0
- package/dist/components/profile-dropdown/profile-dropdown.stories.js +47 -0
- package/dist/components/profile-dropdown/profile-dropdown.stories.js.map +1 -0
- package/dist/components/profile-dropdown/profile-icon.d.ts +5 -0
- package/dist/components/profile-dropdown/profile-icon.d.ts.map +1 -0
- package/dist/components/profile-dropdown/profile-icon.js +3 -0
- package/dist/components/profile-dropdown/profile-icon.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/navigation-context.d.ts +5 -0
- package/dist/utils/navigation-context.d.ts.map +1 -0
- package/dist/utils/navigation-context.js +6 -0
- package/dist/utils/navigation-context.js.map +1 -0
- package/dist/utils/navigation.d.ts +3 -2
- package/dist/utils/navigation.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/header-navigation/header-navigation.module.less +32 -1
- package/src/components/header-navigation/header-navigation.module.less.d.ts +2 -0
- package/src/components/header-navigation/header-navigation.stories.tsx +105 -5
- package/src/components/header-navigation/header-navigation.tsx +141 -71
- package/src/components/logo/logo.stories.tsx +1 -1
- package/src/components/profile-dropdown/profile-dropdown.module.less +136 -0
- package/src/components/profile-dropdown/profile-dropdown.module.less.d.ts +18 -0
- package/src/components/profile-dropdown/profile-dropdown.stories.tsx +127 -0
- package/src/components/profile-dropdown/profile-dropdown.tsx +258 -0
- package/src/components/profile-dropdown/profile-icon.tsx +52 -0
- package/src/index.ts +4 -0
- package/src/utils/navigation-context.tsx +12 -0
- package/src/utils/navigation.ts +3 -2
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/* stylelint-disable no-descending-specificity */
|
|
2
|
+
@import (reference) '@servicetitan/tokens/core/tokens.less';
|
|
3
|
+
|
|
4
|
+
.trigger-container {
|
|
5
|
+
height: 40px;
|
|
6
|
+
position: relative;
|
|
7
|
+
|
|
8
|
+
.profile-image {
|
|
9
|
+
height: 32px;
|
|
10
|
+
width: 32px;
|
|
11
|
+
border-radius: @border-radius-circular;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.info {
|
|
15
|
+
margin-left: @spacing-1;
|
|
16
|
+
max-width: 80px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.avatar-badge {
|
|
20
|
+
position: absolute;
|
|
21
|
+
top: 8px;
|
|
22
|
+
left: 0;
|
|
23
|
+
height: 12px;
|
|
24
|
+
width: 12px;
|
|
25
|
+
border-radius: @border-radius-circular;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.badge {
|
|
29
|
+
position: absolute;
|
|
30
|
+
right: 16px;
|
|
31
|
+
color: @color-white;
|
|
32
|
+
font-size: @typescale-1;
|
|
33
|
+
display: flex;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
align-items: center;
|
|
36
|
+
font-weight: @font-weight-semibold;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.badge-no-content {
|
|
40
|
+
top: 8px;
|
|
41
|
+
height: 12px;
|
|
42
|
+
width: 12px;
|
|
43
|
+
border-radius: @border-radius-2;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.badge-with-content {
|
|
47
|
+
top: 4px;
|
|
48
|
+
height: 16px;
|
|
49
|
+
min-width: 12px;
|
|
50
|
+
padding-left: 2px;
|
|
51
|
+
padding-right: 2px;
|
|
52
|
+
border-radius: 8px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&.trigger-container-hint-arrow:first-child:before {
|
|
56
|
+
display: none;
|
|
57
|
+
content: '';
|
|
58
|
+
position: absolute;
|
|
59
|
+
background-color: @color-white;
|
|
60
|
+
height: 12px;
|
|
61
|
+
width: 12px;
|
|
62
|
+
bottom: -11px;
|
|
63
|
+
left: 14px;
|
|
64
|
+
z-index: (@z-index-popover + 2);
|
|
65
|
+
transform: rotateY(0deg) rotate(45deg);
|
|
66
|
+
border-left: 1px solid var(--colorsBorderGrey, @color-neutral-60);
|
|
67
|
+
border-top: 1px solid var(--colorsBorderGrey, @color-neutral-60);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.dropdown {
|
|
72
|
+
:global(.Popover__content) {
|
|
73
|
+
padding: @spacing-0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
:global(.Popover__divider) {
|
|
77
|
+
margin: @spacing-half @spacing-0;
|
|
78
|
+
width: 100%;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
:global(.Popover__content).dropdown-content {
|
|
82
|
+
padding: @spacing-1 @spacing-0;
|
|
83
|
+
font-size: @typescale-2;
|
|
84
|
+
position: relative;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.dropdown-content-bottom-left {
|
|
88
|
+
.dropdown-content-wrapper::before {
|
|
89
|
+
content: '';
|
|
90
|
+
position: absolute;
|
|
91
|
+
top: -8px;
|
|
92
|
+
right: 8px;
|
|
93
|
+
height: 8px;
|
|
94
|
+
width: 8px;
|
|
95
|
+
background-color: coral;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.dropdown-section {
|
|
100
|
+
padding: @spacing-1 @spacing-2;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.dropdown-link {
|
|
104
|
+
display: block;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
color: @color-black;
|
|
107
|
+
|
|
108
|
+
&:hover {
|
|
109
|
+
background-color: rgba(0, 0, 0, 0.1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.hint {
|
|
115
|
+
background-color: @color-white;
|
|
116
|
+
padding: @spacing-0 !important;
|
|
117
|
+
|
|
118
|
+
.hint-content {
|
|
119
|
+
padding: @spacing-1 @spacing-2;
|
|
120
|
+
background-color: inherit;
|
|
121
|
+
|
|
122
|
+
&::before {
|
|
123
|
+
content: '';
|
|
124
|
+
position: absolute;
|
|
125
|
+
top: -6px;
|
|
126
|
+
right: 29px;
|
|
127
|
+
height: 12px;
|
|
128
|
+
width: 12px;
|
|
129
|
+
background-color: inherit;
|
|
130
|
+
transform: rotateY(0deg) rotate(45deg);
|
|
131
|
+
border-left: 1px solid @color-neutral-60;
|
|
132
|
+
border-top: 1px solid @color-neutral-60;
|
|
133
|
+
z-index: (@z-index-popover + 2);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const __esModule: true;
|
|
2
|
+
export const triggerContainer: string;
|
|
3
|
+
export const profileImage: string;
|
|
4
|
+
export const info: string;
|
|
5
|
+
export const avatarBadge: string;
|
|
6
|
+
export const badge: string;
|
|
7
|
+
export const badgeNoContent: string;
|
|
8
|
+
export const badgeWithContent: string;
|
|
9
|
+
export const triggerContainerHintArrow: string;
|
|
10
|
+
export const dropdown: string;
|
|
11
|
+
export const dropdownContent: string;
|
|
12
|
+
export const dropdownContentBottomLeft: string;
|
|
13
|
+
export const dropdownContentWrapper: string;
|
|
14
|
+
export const dropdownSection: string;
|
|
15
|
+
export const dropdownLink: string;
|
|
16
|
+
export const hint: string;
|
|
17
|
+
export const hintContent: string;
|
|
18
|
+
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { HeaderNavigationComponentProps } from '../../utils/navigation';
|
|
3
|
+
import { HeaderNavigation } from '../header-navigation/header-navigation';
|
|
4
|
+
import { ProfileDropdown } from './profile-dropdown';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: 'Navigation/ProfileDropdown',
|
|
8
|
+
component: ProfileDropdown,
|
|
9
|
+
parameters: {},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const NavLinkMock: FC<HeaderNavigationComponentProps> = props => (
|
|
13
|
+
<a
|
|
14
|
+
href={props.to}
|
|
15
|
+
target={props.target}
|
|
16
|
+
onClick={e => {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
}}
|
|
19
|
+
className={props.className}
|
|
20
|
+
>
|
|
21
|
+
{props.children}
|
|
22
|
+
</a>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const profileDropdownDefault = () => (
|
|
26
|
+
<HeaderNavigation navigationComponent={NavLinkMock}>
|
|
27
|
+
<ProfileDropdown>
|
|
28
|
+
<ProfileDropdown.Link to="https://google.com">first link</ProfileDropdown.Link>
|
|
29
|
+
<ProfileDropdown.Link onClick={() => alert('second click')}>
|
|
30
|
+
second link
|
|
31
|
+
</ProfileDropdown.Link>
|
|
32
|
+
<ProfileDropdown.Divider />
|
|
33
|
+
<ProfileDropdown.Section>some content</ProfileDropdown.Section>
|
|
34
|
+
<ProfileDropdown.Divider />
|
|
35
|
+
<ProfileDropdown.Link to="https://google.com">third link</ProfileDropdown.Link>
|
|
36
|
+
</ProfileDropdown>
|
|
37
|
+
</HeaderNavigation>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const profileDropdownWithLogo = () => (
|
|
41
|
+
<HeaderNavigation>
|
|
42
|
+
<ProfileDropdown
|
|
43
|
+
trigger={{
|
|
44
|
+
imageSrc: 'https://upload.wikimedia.org/wikipedia/en/1/11/Milhouse_Van_Houten.png',
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
</HeaderNavigation>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
export const profileDropdownWithErrorLogo = () => (
|
|
51
|
+
<HeaderNavigation>
|
|
52
|
+
<ProfileDropdown
|
|
53
|
+
trigger={{
|
|
54
|
+
imageSrc: 'https://some.incorrect.url/logo.png',
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
</HeaderNavigation>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export const profileDropdownWithInfo = () => (
|
|
61
|
+
<HeaderNavigation>
|
|
62
|
+
<ProfileDropdown
|
|
63
|
+
trigger={{
|
|
64
|
+
info: { text: 'first', title: 'tenant user' },
|
|
65
|
+
avatarBadge: true,
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
</HeaderNavigation>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
export const profileDropdownWithCounter = () => (
|
|
72
|
+
<HeaderNavigation>
|
|
73
|
+
<ProfileDropdown
|
|
74
|
+
trigger={{
|
|
75
|
+
info: { text: 'first', title: 'tenant user' },
|
|
76
|
+
avatarBadge: true,
|
|
77
|
+
badge: { content: 3, className: 'bg-red-500' },
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
</HeaderNavigation>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
export const profileDropdownWithBothBadges = () => (
|
|
84
|
+
<HeaderNavigation>
|
|
85
|
+
<ProfileDropdown
|
|
86
|
+
trigger={{
|
|
87
|
+
avatarBadge: 'yellow-500',
|
|
88
|
+
badge: { className: 'bg-red-400' },
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
</HeaderNavigation>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export const profileDropdownWithHintPopup = () => (
|
|
95
|
+
<HeaderNavigation navigationComponent={NavLinkMock}>
|
|
96
|
+
<ProfileDropdown
|
|
97
|
+
trigger={{
|
|
98
|
+
avatarBadge: 'yellow-500',
|
|
99
|
+
badge: { className: 'bg-red-400' },
|
|
100
|
+
}}
|
|
101
|
+
hintPopup={{
|
|
102
|
+
className: 'bg-blue-500-i c-white',
|
|
103
|
+
content: <div>hello</div>,
|
|
104
|
+
width: 's',
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
<ProfileDropdown.Link to="https://google.com">first item</ProfileDropdown.Link>
|
|
108
|
+
<ProfileDropdown.Divider />
|
|
109
|
+
<ProfileDropdown.Section>second item</ProfileDropdown.Section>
|
|
110
|
+
</ProfileDropdown>
|
|
111
|
+
</HeaderNavigation>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
export const profileDropdownWithHintAndInfoPopup = () => (
|
|
115
|
+
<HeaderNavigation navigationComponent={NavLinkMock}>
|
|
116
|
+
<ProfileDropdown
|
|
117
|
+
trigger={{
|
|
118
|
+
avatarBadge: 'yellow-500',
|
|
119
|
+
badge: { className: 'bg-red-400' },
|
|
120
|
+
info: { title: 'some text', text: 'qq' },
|
|
121
|
+
}}
|
|
122
|
+
hintPopup={{
|
|
123
|
+
content: <div>hello</div>,
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
</HeaderNavigation>
|
|
127
|
+
);
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { BodyText, Icon, Popover, PopoverPropsStrict } from '@servicetitan/design-system';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import {
|
|
4
|
+
FC,
|
|
5
|
+
HTMLAttributeAnchorTarget,
|
|
6
|
+
MouseEvent,
|
|
7
|
+
MouseEventHandler,
|
|
8
|
+
ReactNode,
|
|
9
|
+
useCallback,
|
|
10
|
+
useContext,
|
|
11
|
+
useEffect,
|
|
12
|
+
useState,
|
|
13
|
+
} from 'react';
|
|
14
|
+
|
|
15
|
+
import { NavigationContext } from '../../utils/navigation-context';
|
|
16
|
+
import * as Styles from './profile-dropdown.module.less';
|
|
17
|
+
import { ProfileLogo } from './profile-icon';
|
|
18
|
+
|
|
19
|
+
interface ProfileDropdownTriggerProps {
|
|
20
|
+
className?: string;
|
|
21
|
+
info?: { title: string; text: string };
|
|
22
|
+
imageSrc?: string | null;
|
|
23
|
+
avatarBadge?: boolean | string;
|
|
24
|
+
badge?: { content?: number | string; className: string };
|
|
25
|
+
hintArrow?: boolean;
|
|
26
|
+
onClick?(e: MouseEvent): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ProfileDropdownTrigger: FC<ProfileDropdownTriggerProps> = ({
|
|
30
|
+
avatarBadge,
|
|
31
|
+
badge,
|
|
32
|
+
className,
|
|
33
|
+
hintArrow,
|
|
34
|
+
imageSrc,
|
|
35
|
+
info,
|
|
36
|
+
onClick,
|
|
37
|
+
}) => {
|
|
38
|
+
const [avatarSource, setAvatarSource] = useState(imageSrc ?? '');
|
|
39
|
+
const [avatarSourceError, setAvatarSourceError] = useState(false);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const src = imageSrc ?? '';
|
|
43
|
+
|
|
44
|
+
if (src === avatarSource) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setAvatarSource(src);
|
|
49
|
+
setAvatarSourceError(false);
|
|
50
|
+
}, [imageSrc, avatarSource]);
|
|
51
|
+
|
|
52
|
+
const onAvatarError = useCallback(() => {
|
|
53
|
+
setAvatarSourceError(true);
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
className={classNames(
|
|
59
|
+
'd-f align-items-center cursor-pointer position-relative p-y-1 p-x-1',
|
|
60
|
+
Styles.triggerContainer,
|
|
61
|
+
{ [Styles.triggerContainerHintArrow]: hintArrow },
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
onClick={onClick}
|
|
65
|
+
>
|
|
66
|
+
{avatarSource && !avatarSourceError ? (
|
|
67
|
+
<img
|
|
68
|
+
src={avatarSource}
|
|
69
|
+
className={Styles.profileImage}
|
|
70
|
+
onError={onAvatarError}
|
|
71
|
+
alt="user dropdown menu"
|
|
72
|
+
/>
|
|
73
|
+
) : (
|
|
74
|
+
<ProfileLogo size={24} />
|
|
75
|
+
)}
|
|
76
|
+
|
|
77
|
+
{!!info && (
|
|
78
|
+
<div className={Styles.info}>
|
|
79
|
+
<BodyText bold title={info.title} className="t-truncate c-white" size="xsmall">
|
|
80
|
+
{info.title}
|
|
81
|
+
</BodyText>
|
|
82
|
+
<BodyText
|
|
83
|
+
title={info.text}
|
|
84
|
+
className="t-truncate c-neutral-70 tt-uppercase"
|
|
85
|
+
size="xsmall"
|
|
86
|
+
>
|
|
87
|
+
{info.text}
|
|
88
|
+
</BodyText>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
<Icon className="m-l-half" name="expand_more" size={12} />
|
|
93
|
+
|
|
94
|
+
{!!avatarBadge && (
|
|
95
|
+
<div
|
|
96
|
+
className={classNames(
|
|
97
|
+
Styles.avatarBadge,
|
|
98
|
+
avatarBadge === true ? 'bg-blue-500' : `bg-${avatarBadge}`
|
|
99
|
+
)}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
{!!badge && (
|
|
103
|
+
<span
|
|
104
|
+
className={classNames(
|
|
105
|
+
Styles.badge,
|
|
106
|
+
badge.content ? Styles.badgeWithContent : Styles.badgeNoContent,
|
|
107
|
+
badge.className
|
|
108
|
+
)}
|
|
109
|
+
>
|
|
110
|
+
{badge.content}
|
|
111
|
+
</span>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
interface ProfileDropdownSectionProps {
|
|
118
|
+
children: ReactNode;
|
|
119
|
+
className?: string;
|
|
120
|
+
onClick?(e: MouseEvent): void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const ProfileDropdownSection: FC<ProfileDropdownSectionProps> = ({
|
|
124
|
+
children,
|
|
125
|
+
className,
|
|
126
|
+
onClick,
|
|
127
|
+
}) => {
|
|
128
|
+
const clickHandler: MouseEventHandler<never> = e => {
|
|
129
|
+
if (onClick) {
|
|
130
|
+
onClick(e);
|
|
131
|
+
} else {
|
|
132
|
+
e.stopPropagation();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div className={classNames(Styles.dropdownSection, className)} onClick={clickHandler}>
|
|
138
|
+
{children}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
type ProfileDropdownLinkProps = {
|
|
144
|
+
children: ReactNode;
|
|
145
|
+
} & (
|
|
146
|
+
| { target?: HTMLAttributeAnchorTarget; to: string; onClick?: () => void }
|
|
147
|
+
| { target?: never; to?: never; onClick(): void }
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const ProfileDropdownLink: FC<ProfileDropdownLinkProps> = ({
|
|
151
|
+
children,
|
|
152
|
+
target,
|
|
153
|
+
to,
|
|
154
|
+
onClick,
|
|
155
|
+
}: ProfileDropdownLinkProps) => {
|
|
156
|
+
const NavigationComponent = useContext(NavigationContext);
|
|
157
|
+
|
|
158
|
+
const clickHandler = (e: MouseEvent<any>) => {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
onClick?.();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return to ? (
|
|
164
|
+
<NavigationComponent
|
|
165
|
+
className={classNames(Styles.dropdownSection, Styles.dropdownLink)}
|
|
166
|
+
target={target}
|
|
167
|
+
to={to}
|
|
168
|
+
>
|
|
169
|
+
{children}
|
|
170
|
+
</NavigationComponent>
|
|
171
|
+
) : (
|
|
172
|
+
<a
|
|
173
|
+
className={classNames(Styles.dropdownSection, Styles.dropdownLink)}
|
|
174
|
+
onClick={clickHandler}
|
|
175
|
+
>
|
|
176
|
+
{children}
|
|
177
|
+
</a>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export interface ProfileDropdownProps {
|
|
182
|
+
children?: ReactNode;
|
|
183
|
+
trigger?: Omit<ProfileDropdownTriggerProps, 'onClick'>;
|
|
184
|
+
hintPopup?: {
|
|
185
|
+
className?: string;
|
|
186
|
+
content: ReactNode;
|
|
187
|
+
width?: PopoverPropsStrict['width'];
|
|
188
|
+
onClose?: () => void;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface ProfileDropdownType extends FC<ProfileDropdownProps> {
|
|
193
|
+
Divider: typeof Popover.Divider;
|
|
194
|
+
Link: typeof ProfileDropdownLink;
|
|
195
|
+
Section: typeof ProfileDropdownSection;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const ProfileDropdown: ProfileDropdownType = (({ children, hintPopup, trigger }) => {
|
|
199
|
+
const [open, setOpen] = useState(false);
|
|
200
|
+
const onClose = useCallback(() => {
|
|
201
|
+
setOpen(false);
|
|
202
|
+
}, []);
|
|
203
|
+
const onTriggerClick = useCallback(
|
|
204
|
+
(e: MouseEvent) => {
|
|
205
|
+
e.stopPropagation();
|
|
206
|
+
setOpen(!open);
|
|
207
|
+
},
|
|
208
|
+
[open]
|
|
209
|
+
);
|
|
210
|
+
const hintShown = !!hintPopup && !open;
|
|
211
|
+
|
|
212
|
+
const triggerElement = (
|
|
213
|
+
<ProfileDropdownTrigger
|
|
214
|
+
{...trigger}
|
|
215
|
+
onClick={children ? onTriggerClick : undefined}
|
|
216
|
+
hintArrow={hintShown}
|
|
217
|
+
/>
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<div className="position-relative">
|
|
222
|
+
{!!hintPopup && hintShown ? (
|
|
223
|
+
<Popover
|
|
224
|
+
direction="bl"
|
|
225
|
+
width={hintPopup.width ?? 'xs'}
|
|
226
|
+
trigger={triggerElement}
|
|
227
|
+
popoverContentClassName={Styles.hint}
|
|
228
|
+
open
|
|
229
|
+
>
|
|
230
|
+
<div className={classNames(Styles.hintContent, hintPopup.className)}>
|
|
231
|
+
{hintPopup.content}
|
|
232
|
+
</div>
|
|
233
|
+
</Popover>
|
|
234
|
+
) : (
|
|
235
|
+
<Popover
|
|
236
|
+
direction="bl"
|
|
237
|
+
width="xs"
|
|
238
|
+
trigger={triggerElement}
|
|
239
|
+
open={open}
|
|
240
|
+
onClickOutside={onClose}
|
|
241
|
+
className={Styles.dropdown}
|
|
242
|
+
popoverContentClassName={classNames(
|
|
243
|
+
Styles.dropdownContent,
|
|
244
|
+
Styles.dropdownContentBottomLeft
|
|
245
|
+
)}
|
|
246
|
+
>
|
|
247
|
+
<div className={Styles.dropdownContentWrapper} onClick={onClose}>
|
|
248
|
+
{children}
|
|
249
|
+
</div>
|
|
250
|
+
</Popover>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
}) as ProfileDropdownType;
|
|
255
|
+
|
|
256
|
+
ProfileDropdown.Divider = Popover.Divider;
|
|
257
|
+
ProfileDropdown.Link = ProfileDropdownLink;
|
|
258
|
+
ProfileDropdown.Section = ProfileDropdownSection;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
export const ProfileLogo: FC<{ size: number }> = ({ size }) => (
|
|
4
|
+
<svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
version="1.1"
|
|
7
|
+
width={size}
|
|
8
|
+
height={size}
|
|
9
|
+
viewBox="0 0 256 256"
|
|
10
|
+
>
|
|
11
|
+
<g
|
|
12
|
+
stroke="none"
|
|
13
|
+
strokeWidth="0"
|
|
14
|
+
strokeDasharray="none"
|
|
15
|
+
strokeLinecap="butt"
|
|
16
|
+
strokeLinejoin="miter"
|
|
17
|
+
strokeMiterlimit="10"
|
|
18
|
+
fill="none"
|
|
19
|
+
fillRule="nonzero"
|
|
20
|
+
opacity="1"
|
|
21
|
+
transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)"
|
|
22
|
+
>
|
|
23
|
+
<path
|
|
24
|
+
d="M 45 53.718 c -10.022 0 -18.175 -8.153 -18.175 -18.175 S 34.978 17.368 45 17.368 c 10.021 0 18.175 8.153 18.175 18.175 S 55.021 53.718 45 53.718 z"
|
|
25
|
+
stroke="none"
|
|
26
|
+
strokeWidth="1"
|
|
27
|
+
strokeDasharray="none"
|
|
28
|
+
strokeLinecap="round"
|
|
29
|
+
strokeLinejoin="miter"
|
|
30
|
+
strokeMiterlimit="10"
|
|
31
|
+
fill="rgb(255,255,255)"
|
|
32
|
+
fillRule="nonzero"
|
|
33
|
+
opacity="1"
|
|
34
|
+
transform=" matrix(1 0 0 1 0 0) "
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
<path
|
|
38
|
+
d="M 45 0 C 20.187 0 0 20.187 0 45 c 0 24.813 20.187 45 45 45 c 24.813 0 45 -20.187 45 -45 C 90 20.187 69.813 0 45 0 z M 74.821 70.096 c -3.543 -5.253 -8.457 -9.568 -14.159 -12.333 c -2.261 -1.096 -4.901 -1.08 -7.247 0.047 c -2.638 1.268 -5.47 1.91 -8.415 1.91 c -2.945 0 -5.776 -0.643 -8.415 -1.91 c -2.343 -1.125 -4.984 -1.143 -7.247 -0.047 c -5.702 2.765 -10.616 7.08 -14.16 12.333 C 9.457 63.308 6 54.552 6 45 C 6 23.495 23.495 6 45 6 s 39 17.495 39 39 C 84 54.552 80.543 63.308 74.821 70.096 z"
|
|
39
|
+
stroke="none"
|
|
40
|
+
strokeWidth="1"
|
|
41
|
+
strokeDasharray="none"
|
|
42
|
+
strokeLinecap="round"
|
|
43
|
+
strokeLinejoin="miter"
|
|
44
|
+
strokeMiterlimit="10"
|
|
45
|
+
fill="rgb(255,255,255)"
|
|
46
|
+
fillRule="nonzero"
|
|
47
|
+
opacity="1"
|
|
48
|
+
transform=" matrix(1 0 0 1 0 0) "
|
|
49
|
+
/>
|
|
50
|
+
</g>
|
|
51
|
+
</svg>
|
|
52
|
+
);
|
package/src/index.ts
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
1
|
export * from './components/header-navigation/header-navigation';
|
|
2
|
+
export * from './components/profile-dropdown/profile-dropdown';
|
|
3
|
+
export * from './components/logo/logo-company-title';
|
|
4
|
+
export * from './components/logo/logo-titan';
|
|
5
|
+
export * from './components/logo/logo-titan-text';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FC, createContext } from 'react';
|
|
2
|
+
import { NavLink } from 'react-router-dom';
|
|
3
|
+
import { HeaderNavigationComponentProps } from './navigation';
|
|
4
|
+
|
|
5
|
+
export const DefaultNavigationComponent: FC<HeaderNavigationComponentProps> = props => (
|
|
6
|
+
<NavLink
|
|
7
|
+
{...props}
|
|
8
|
+
isActive={props.isActive && ((_, { pathname }) => props.isActive!(pathname))}
|
|
9
|
+
/>
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export const NavigationContext = createContext(DefaultNavigationComponent);
|
package/src/utils/navigation.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IconPropsStrict } from '@servicetitan/design-system';
|
|
2
|
-
import { FC, ReactNode } from 'react';
|
|
2
|
+
import { FC, HTMLAttributeAnchorTarget, ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
export interface HeaderNavigationLinkData {
|
|
5
5
|
/** link href */
|
|
@@ -38,11 +38,12 @@ export interface HeaderNavigationLinkData {
|
|
|
38
38
|
|
|
39
39
|
export interface HeaderNavigationComponentPropsStrict {
|
|
40
40
|
to: string;
|
|
41
|
-
title
|
|
41
|
+
title?: string;
|
|
42
42
|
className?: string;
|
|
43
43
|
activeClassName?: string;
|
|
44
44
|
children: ReactNode;
|
|
45
45
|
isActive?: (pathname: string) => boolean;
|
|
46
|
+
target?: HTMLAttributeAnchorTarget;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export interface HeaderNavigationComponentProps extends HeaderNavigationComponentPropsStrict {
|