@superdispatch/ui-lab 0.50.3 → 0.50.5

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 (124) hide show
  1. package/.babelrc.js +5 -0
  2. package/package.json +38 -13
  3. package/pkg/README.md +10 -0
  4. package/{dist-node → pkg/dist-node}/index.js +52 -40
  5. package/pkg/dist-node/index.js.map +1 -0
  6. package/{dist-src → pkg/dist-src}/email-autocomplate/EmailAutocomplete.js +3 -3
  7. package/{dist-src → pkg/dist-src}/file-drop-zone/FileDropZone.js +2 -2
  8. package/{dist-src → pkg/dist-src}/flag-list/FlagListItem.js +11 -19
  9. package/{dist-src → pkg/dist-src}/index.js +1 -0
  10. package/{dist-src → pkg/dist-src}/navbar/Navbar.js +13 -4
  11. package/{dist-src → pkg/dist-src}/navbar/NavbarAccordion.js +18 -9
  12. package/{dist-src → pkg/dist-src}/navbar/NavbarList.js +1 -1
  13. package/{dist-src → pkg/dist-src}/sidebar/SidebarBackButton.js +1 -1
  14. package/{dist-src → pkg/dist-src}/sidebar/SidebarMenuItem.js +1 -1
  15. package/{dist-src → pkg/dist-src}/sidebar/SidebarMenuItemAvatar.js +1 -1
  16. package/{dist-types → pkg/dist-types}/index.d.ts +8 -2
  17. package/{dist-web → pkg/dist-web}/index.js +52 -42
  18. package/pkg/dist-web/index.js.map +1 -0
  19. package/pkg/package.json +41 -0
  20. package/playroom.ts +24 -0
  21. package/src/alert/Alert.stories.tsx +162 -0
  22. package/src/alert/Alert.tsx +135 -0
  23. package/src/banner/Banner.stories.tsx +64 -0
  24. package/src/banner/Banner.tsx +120 -0
  25. package/src/box/Box.stories.tsx +20 -0
  26. package/src/box/Box.tsx +257 -0
  27. package/src/button/Button.stories.tsx +739 -0
  28. package/src/button/Button.tsx +498 -0
  29. package/src/button-area/ButtonArea.stories.tsx +65 -0
  30. package/src/button-area/ButtonArea.tsx +90 -0
  31. package/src/chat/Chat.stories.tsx +130 -0
  32. package/src/chat/Chat.tsx +57 -0
  33. package/src/chat/ChatMessage.tsx +45 -0
  34. package/src/chat/README.MD +7 -0
  35. package/src/chat/__tests__/Chat.spec.tsx +29 -0
  36. package/src/chat/__tests__/ChatMessage.spec.tsx +39 -0
  37. package/src/container/Container.tsx +48 -0
  38. package/src/description-item/DescriptionItem.stories.tsx +163 -0
  39. package/src/description-item/DescriptionItem.tsx +104 -0
  40. package/src/description-line-item/DescriptionLineItem.stories.tsx +23 -0
  41. package/src/description-line-item/DescriptionLineItem.tsx +29 -0
  42. package/src/email-autocomplate/CloseIcon.tsx +20 -0
  43. package/src/email-autocomplate/EmailAutocomplete.stories.tsx +47 -0
  44. package/src/email-autocomplate/EmailAutocomplete.tsx +138 -0
  45. package/src/file-drop-zone/FileDropZone.stories.tsx +44 -0
  46. package/src/file-drop-zone/FileDropZone.tsx +170 -0
  47. package/src/file-list-item/FileListItem.stories.tsx +37 -0
  48. package/src/file-list-item/FileListItem.tsx +148 -0
  49. package/src/file-list-item/__tests__/FileListItem.spec.tsx +339 -0
  50. package/src/flag-list/FlagList.stories.tsx +72 -0
  51. package/src/flag-list/FlagList.tsx +54 -0
  52. package/src/flag-list/FlagListItem.tsx +126 -0
  53. package/src/index.spec.ts +53 -0
  54. package/src/index.ts +36 -0
  55. package/src/linked-text/LinkeText.stories.tsx +42 -0
  56. package/src/linked-text/LinkedText.tsx +47 -0
  57. package/src/multiline-text/MultilineText.stories.tsx +30 -0
  58. package/src/multiline-text/MultilineText.ts +16 -0
  59. package/src/navbar/Navbar.stories.tsx +137 -0
  60. package/src/navbar/Navbar.tsx +132 -0
  61. package/src/navbar/NavbarAccordion.tsx +195 -0
  62. package/src/navbar/NavbarAvatar.tsx +51 -0
  63. package/src/navbar/NavbarBottomBar.tsx +135 -0
  64. package/src/navbar/NavbarContext.tsx +22 -0
  65. package/src/navbar/NavbarItem.tsx +125 -0
  66. package/src/navbar/NavbarList.tsx +247 -0
  67. package/src/navbar/NavbarMenu.tsx +102 -0
  68. package/src/passwordStepper/PasswordStrength.stories.tsx +95 -0
  69. package/src/passwordStepper/PasswordStrength.tsx +107 -0
  70. package/src/passwordStepper/PasswordUtils.tsx +42 -0
  71. package/src/passwordStepper/PasswordValidationComponents.tsx +95 -0
  72. package/src/sidebar/Sidebar.stories.tsx +376 -0
  73. package/src/sidebar/Sidebar.tsx +75 -0
  74. package/src/sidebar/SidebarBackButton.tsx +33 -0
  75. package/src/sidebar/SidebarContainer.tsx +114 -0
  76. package/src/sidebar/SidebarContent.tsx +119 -0
  77. package/src/sidebar/SidebarDivider.tsx +15 -0
  78. package/src/sidebar/SidebarMenuItem.tsx +196 -0
  79. package/src/sidebar/SidebarMenuItemAction.tsx +27 -0
  80. package/src/sidebar/SidebarMenuItemAvatar.tsx +59 -0
  81. package/src/sidebar/SidebarMenuItemContext.tsx +33 -0
  82. package/src/sidebar/SidebarSubheader.tsx +38 -0
  83. package/src/styled.d.ts +12 -0
  84. package/src/text-box/TextBox.stories.tsx +114 -0
  85. package/src/text-box/TextBox.tsx +238 -0
  86. package/src/utils/RuleNormalizer.ts +24 -0
  87. package/src/utils/mergeStyles.ts +28 -0
  88. package/tsconfig.json +19 -0
  89. package/LICENSE +0 -21
  90. package/dist-node/index.js.map +0 -1
  91. package/dist-web/index.js.map +0 -1
  92. /package/{dist-src → pkg/dist-src}/alert/Alert.js +0 -0
  93. /package/{dist-src → pkg/dist-src}/banner/Banner.js +0 -0
  94. /package/{dist-src → pkg/dist-src}/box/Box.js +0 -0
  95. /package/{dist-src → pkg/dist-src}/button/Button.js +0 -0
  96. /package/{dist-src → pkg/dist-src}/button-area/ButtonArea.js +0 -0
  97. /package/{dist-src → pkg/dist-src}/chat/Chat.js +0 -0
  98. /package/{dist-src → pkg/dist-src}/chat/ChatMessage.js +0 -0
  99. /package/{dist-src → pkg/dist-src}/container/Container.js +0 -0
  100. /package/{dist-src → pkg/dist-src}/description-item/DescriptionItem.js +0 -0
  101. /package/{dist-src → pkg/dist-src}/description-line-item/DescriptionLineItem.js +0 -0
  102. /package/{dist-src → pkg/dist-src}/email-autocomplate/CloseIcon.js +0 -0
  103. /package/{dist-src → pkg/dist-src}/file-list-item/FileListItem.js +0 -0
  104. /package/{dist-src → pkg/dist-src}/flag-list/FlagList.js +0 -0
  105. /package/{dist-src → pkg/dist-src}/linked-text/LinkedText.js +0 -0
  106. /package/{dist-src → pkg/dist-src}/multiline-text/MultilineText.js +0 -0
  107. /package/{dist-src → pkg/dist-src}/navbar/NavbarAvatar.js +0 -0
  108. /package/{dist-src → pkg/dist-src}/navbar/NavbarBottomBar.js +0 -0
  109. /package/{dist-src → pkg/dist-src}/navbar/NavbarContext.js +0 -0
  110. /package/{dist-src → pkg/dist-src}/navbar/NavbarItem.js +0 -0
  111. /package/{dist-src → pkg/dist-src}/navbar/NavbarMenu.js +0 -0
  112. /package/{dist-src → pkg/dist-src}/passwordStepper/PasswordStrength.js +0 -0
  113. /package/{dist-src → pkg/dist-src}/passwordStepper/PasswordUtils.js +0 -0
  114. /package/{dist-src → pkg/dist-src}/passwordStepper/PasswordValidationComponents.js +0 -0
  115. /package/{dist-src → pkg/dist-src}/sidebar/Sidebar.js +0 -0
  116. /package/{dist-src → pkg/dist-src}/sidebar/SidebarContainer.js +0 -0
  117. /package/{dist-src → pkg/dist-src}/sidebar/SidebarContent.js +0 -0
  118. /package/{dist-src → pkg/dist-src}/sidebar/SidebarDivider.js +0 -0
  119. /package/{dist-src → pkg/dist-src}/sidebar/SidebarMenuItemAction.js +0 -0
  120. /package/{dist-src → pkg/dist-src}/sidebar/SidebarMenuItemContext.js +0 -0
  121. /package/{dist-src → pkg/dist-src}/sidebar/SidebarSubheader.js +0 -0
  122. /package/{dist-src → pkg/dist-src}/text-box/TextBox.js +0 -0
  123. /package/{dist-src → pkg/dist-src}/utils/RuleNormalizer.js +0 -0
  124. /package/{dist-src → pkg/dist-src}/utils/mergeStyles.js +0 -0
@@ -0,0 +1,135 @@
1
+ import { BottomNavigation, BottomNavigationAction } from '@material-ui/core';
2
+ import { Menu } from '@material-ui/icons';
3
+ import { ColorDynamic } from '@superdispatch/ui';
4
+ import {
5
+ ComponentType,
6
+ HTMLAttributes,
7
+ ReactElement,
8
+ ReactNode,
9
+ useMemo,
10
+ } from 'react';
11
+ import styled from 'styled-components';
12
+ import { useNavbarContext } from './NavbarContext';
13
+
14
+ const StyledBottomNavigation = styled(BottomNavigation)`
15
+ background: ${ColorDynamic.Dark500};
16
+ `;
17
+
18
+ const StyledBottomNavigationAction = styled(BottomNavigationAction)`
19
+ && {
20
+ background: #1b2638;
21
+ color: ${ColorDynamic.Silver500};
22
+ padding: 6px 0 8px;
23
+ line-height: 20px;
24
+ }
25
+
26
+ &:first-child {
27
+ padding-left: 12px;
28
+ }
29
+
30
+ &:last-child {
31
+ padding-right: 12px;
32
+ }
33
+
34
+ &.Mui-selected {
35
+ color: ${ColorDynamic.White};
36
+
37
+ .MuiBottomNavigationAction-label {
38
+ font-size: 0.75rem;
39
+ }
40
+ }
41
+ `;
42
+
43
+ const IconWrapper = styled.div`
44
+ position: relative;
45
+ `;
46
+
47
+ const IconLabel = styled.div`
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+
52
+ font-size: 12px;
53
+ border-radius: 50%;
54
+ color: ${ColorDynamic.White};
55
+ background: ${ColorDynamic.Red300};
56
+
57
+ position: absolute;
58
+ top: 0;
59
+ right: 0;
60
+
61
+ width: 8px;
62
+ height: 8px;
63
+ `;
64
+
65
+ export interface NavbarBottomBarItem {
66
+ active?: boolean;
67
+ hasBadge?: boolean;
68
+ value: string;
69
+ label: ReactNode;
70
+ icon: ReactNode;
71
+ onClick?: () => void;
72
+ component?: ComponentType<HTMLAttributes<HTMLElement>>;
73
+ }
74
+
75
+ interface NavbarBottomBarProps {
76
+ items: NavbarBottomBarItem[];
77
+ hasMenuBadge?: boolean;
78
+ }
79
+
80
+ export function NavbarBottomBar({
81
+ items,
82
+ hasMenuBadge,
83
+ }: NavbarBottomBarProps): ReactElement {
84
+ const { isDrawerOpen, setDrawerOpen } = useNavbarContext();
85
+
86
+ const activeItem = useMemo(() => items.find((item) => item.active), [items]);
87
+
88
+ return (
89
+ <StyledBottomNavigation
90
+ value={activeItem?.value}
91
+ showLabels={true}
92
+ onChange={(_event, newValue) => {
93
+ if (newValue) {
94
+ if (newValue === 'menu') {
95
+ setDrawerOpen(!isDrawerOpen);
96
+ } else {
97
+ setDrawerOpen(false);
98
+ }
99
+ }
100
+ }}
101
+ >
102
+ {items.map(({ active, hasBadge, ...item }) => (
103
+ <StyledBottomNavigationAction
104
+ {...item}
105
+ key={item.value}
106
+ icon={
107
+ hasBadge ? (
108
+ <IconWrapper>
109
+ <IconLabel />
110
+ {item.icon}
111
+ </IconWrapper>
112
+ ) : (
113
+ item.icon
114
+ )
115
+ }
116
+ />
117
+ ))}
118
+
119
+ <StyledBottomNavigationAction
120
+ value="menu"
121
+ label="Menu"
122
+ icon={
123
+ hasMenuBadge ? (
124
+ <IconWrapper>
125
+ <IconLabel />
126
+ <Menu fontSize="small" />
127
+ </IconWrapper>
128
+ ) : (
129
+ <Menu fontSize="small" />
130
+ )
131
+ }
132
+ />
133
+ </StyledBottomNavigation>
134
+ );
135
+ }
@@ -0,0 +1,22 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ export interface NavbarContextType {
4
+ isDrawerOpen: boolean;
5
+ isMenuExpanded: boolean;
6
+ isNavbarExpanded: boolean;
7
+
8
+ setDrawerOpen: (value: boolean) => void;
9
+ setMenuExpanded: (value: boolean) => void;
10
+ }
11
+
12
+ export const NavbarContext = createContext<NavbarContextType>({
13
+ isDrawerOpen: false,
14
+ isMenuExpanded: false,
15
+ isNavbarExpanded: false,
16
+ setMenuExpanded: () => void 0,
17
+ setDrawerOpen: () => void 0,
18
+ });
19
+
20
+ export function useNavbarContext(): NavbarContextType {
21
+ return useContext(NavbarContext);
22
+ }
@@ -0,0 +1,125 @@
1
+ import { alpha } from '@material-ui/core';
2
+ import { Color, ColorDynamic, useUID } from '@superdispatch/ui';
3
+ import {
4
+ ComponentType,
5
+ HTMLAttributes,
6
+ MouseEvent,
7
+ ReactElement,
8
+ ReactNode,
9
+ } from 'react';
10
+ import styled from 'styled-components';
11
+
12
+ export const NavbarBadge = styled.span`
13
+ flex-shrink: 0;
14
+ padding: 4px 6px;
15
+ min-width: 20px;
16
+ line-height: 12px;
17
+ font-size: 12px;
18
+ font-weight: 400;
19
+ border-radius: 10px;
20
+ text-align: center;
21
+ background: ${alpha(Color.White, 0.05)};
22
+
23
+ &[data-variant='primary'] {
24
+ color: ${Color.White};
25
+ background: ${ColorDynamic.Blue300};
26
+ }
27
+
28
+ &[data-variant='danger'] {
29
+ color: ${Color.White};
30
+ background: ${ColorDynamic.Red300};
31
+ }
32
+ `;
33
+
34
+ export const NavbarLabel = styled.span`
35
+ flex-grow: 1;
36
+ overflow: hidden;
37
+ white-space: nowrap;
38
+ text-overflow: ellipsis;
39
+ `;
40
+
41
+ const NavbarItemRoot = styled.div`
42
+ width: 100%;
43
+
44
+ display: flex;
45
+ align-items: center;
46
+ color: #c2c4c9;
47
+ font-size: 14px;
48
+ font-weight: 400;
49
+ line-height: 20px;
50
+ text-decoration: none;
51
+ outline: none;
52
+ cursor: pointer;
53
+ padding: 8px 16px;
54
+ border-left: 4px solid transparent;
55
+
56
+ &[aria-current],
57
+ &[data-active='true'] {
58
+ color: ${Color.White};
59
+ background-color: ${Color.White10};
60
+ border-left-color: ${ColorDynamic.Blue300};
61
+ }
62
+
63
+ &:hover {
64
+ background-color: ${Color.White08};
65
+ color: ${Color.White};
66
+ border-left-color: ${ColorDynamic.Blue300};
67
+ }
68
+ `;
69
+
70
+ const IconWrapper = styled.div`
71
+ width: 24px;
72
+ margin-right: 8px;
73
+
74
+ & svg {
75
+ font-size: 24px;
76
+ }
77
+ `;
78
+
79
+ export interface NavbarItemProps {
80
+ onClick?: (event: MouseEvent<HTMLElement>) => void;
81
+ component?: ComponentType<HTMLAttributes<HTMLElement>>;
82
+
83
+ label: ReactNode;
84
+ icon?: ReactNode;
85
+ badge?: ReactNode;
86
+ ident?: number;
87
+ gutter?: boolean;
88
+ variant?: 'danger' | 'primary';
89
+ active?: boolean;
90
+ }
91
+
92
+ export function NavbarItem({
93
+ label,
94
+ gutter,
95
+ badge,
96
+ icon,
97
+ component,
98
+ onClick,
99
+ variant,
100
+ ident = 0,
101
+ active,
102
+ }: NavbarItemProps): ReactElement {
103
+ const uid = useUID();
104
+
105
+ return (
106
+ <NavbarItemRoot
107
+ as={component}
108
+ onClick={onClick}
109
+ aria-labelledby={uid}
110
+ data-active={active}
111
+ style={{
112
+ marginTop: gutter ? '16px' : '0',
113
+ paddingLeft: (ident + 1) * 20,
114
+ }}
115
+ >
116
+ <IconWrapper>{icon}</IconWrapper>
117
+
118
+ <NavbarLabel id={uid}>{label}</NavbarLabel>
119
+
120
+ {badge != null && (
121
+ <NavbarBadge data-variant={variant}>{badge}</NavbarBadge>
122
+ )}
123
+ </NavbarItemRoot>
124
+ );
125
+ }
@@ -0,0 +1,247 @@
1
+ import { IconButton } from '@material-ui/core';
2
+ import { Menu as MenuIcon, MenuOpen } from '@material-ui/icons';
3
+ import {
4
+ Color,
5
+ ColorDark,
6
+ ColorDynamic,
7
+ Inline,
8
+ useResponsiveValue,
9
+ } from '@superdispatch/ui';
10
+ import {
11
+ ComponentType,
12
+ HTMLAttributes,
13
+ Key,
14
+ MouseEvent,
15
+ ReactElement,
16
+ ReactNode,
17
+ useMemo,
18
+ } from 'react';
19
+ import styled from 'styled-components';
20
+ import { NavbarAccordion, NavbarAccordionProps } from './NavbarAccordion';
21
+ import { useNavbarContext } from './NavbarContext';
22
+ import {
23
+ NavbarBadge,
24
+ NavbarItem,
25
+ NavbarItemProps,
26
+ NavbarLabel,
27
+ } from './NavbarItem';
28
+
29
+ const Header = styled.div`
30
+ margin: 0 16px 8px;
31
+
32
+ display: flex;
33
+ flex-shrink: 0;
34
+ align-items: center;
35
+ justify-content: space-between;
36
+ position: sticky;
37
+ `;
38
+
39
+ const Wrapper = styled.div<{ isMobile: boolean }>`
40
+ display: flex;
41
+ flex: 1;
42
+ flex-direction: column;
43
+
44
+ overflow: hidden;
45
+ padding-top: 16px;
46
+ background: ${({ theme }) =>
47
+ theme.palette.type === 'dark' ? ColorDark.White : Color.Dark500};
48
+ transition: width 0.3s linear;
49
+
50
+ height: 100%;
51
+ width: ${({ isMobile }) => (isMobile ? '280px' : 'initial')};
52
+
53
+ &[data-expanded='true'] {
54
+ width: ${({ isMobile }) => (isMobile ? '280px' : '240px')};
55
+ }
56
+
57
+ &[data-expanded='false'] {
58
+ width: ${({ isMobile }) => (isMobile ? '280px' : '72px')};
59
+
60
+ & > ${Header} {
61
+ justify-content: center;
62
+ }
63
+ }
64
+ `;
65
+
66
+ const ExpandIconButton = styled(IconButton)`
67
+ color: ${ColorDynamic.Dark100};
68
+
69
+ &&:focus {
70
+ background-color: inherit;
71
+ }
72
+ `;
73
+
74
+ const Footer = styled.div`
75
+ flex-grow: 1;
76
+ display: flex;
77
+ align-items: flex-end;
78
+ margin: 16px 0 8px;
79
+ position: sticky;
80
+ `;
81
+
82
+ const Root = styled.div`
83
+ color: inherit;
84
+ background-color: unset;
85
+ border-left: unset;
86
+ font-size: 14px;
87
+ font-weight: 400;
88
+ line-height: 20px;
89
+ text-decoration: none;
90
+ padding: 8px 16px;
91
+
92
+ svg {
93
+ font-size: 24px;
94
+ color: ${ColorDynamic.Dark100};
95
+ }
96
+ `;
97
+
98
+ interface NavbarMenuItemProps {
99
+ icon?: ReactNode;
100
+ label: ReactNode;
101
+ onClick?: (event: MouseEvent<HTMLElement>) => void;
102
+ component?: ComponentType<HTMLAttributes<HTMLElement>>;
103
+ }
104
+
105
+ export function NavbarMenuItem({
106
+ label,
107
+ icon,
108
+ onClick,
109
+ component,
110
+ }: NavbarMenuItemProps): ReactElement {
111
+ return (
112
+ <Root as={component} onClick={onClick}>
113
+ <Inline space="xsmall" verticalAlign="center">
114
+ {icon}
115
+
116
+ <NavbarLabel>{label}</NavbarLabel>
117
+ </Inline>
118
+ </Root>
119
+ );
120
+ }
121
+
122
+ const Content = styled.div`
123
+ height: 100%;
124
+ min-height: 50px;
125
+ overflow-y: auto;
126
+ overflow-x: hidden;
127
+
128
+ &[aria-expanded='false'] {
129
+ ${NavbarBadge}, ${NavbarLabel} {
130
+ display: none;
131
+ }
132
+ }
133
+
134
+ &::-webkit-scrollbar {
135
+ width: 10px;
136
+ height: 10px;
137
+ }
138
+
139
+ &::-webkit-scrollbar-track {
140
+ background-color: transparent;
141
+ }
142
+
143
+ &::-webkit-scrollbar-thumb {
144
+ background-color: ${ColorDark.Silver500};
145
+ border-radius: 100vw;
146
+ margin-bottom: 100px;
147
+ }
148
+ `;
149
+
150
+ export interface NavbarAccordionOptions extends NavbarAccordionProps {
151
+ key: Key;
152
+ groupKey?: Key;
153
+ hide?: boolean;
154
+ }
155
+ export interface NavbarItemOptions extends NavbarItemProps {
156
+ key: Key;
157
+ groupKey?: Key;
158
+ hide?: boolean;
159
+ }
160
+
161
+ interface NavbarListProps {
162
+ header: ReactNode;
163
+ items: Array<NavbarItemOptions | NavbarAccordionOptions>;
164
+ footer?: ReactNode;
165
+ }
166
+
167
+ export function NavbarList({
168
+ header,
169
+ items,
170
+ footer,
171
+ }: NavbarListProps): ReactElement {
172
+ const platform = useResponsiveValue('mobile', 'tablet', 'desktop');
173
+ const isMobile = platform === 'mobile';
174
+
175
+ const { isMenuExpanded, isDrawerOpen, setDrawerOpen, setMenuExpanded } =
176
+ useNavbarContext();
177
+
178
+ const isSidebarOpened = isMobile ? isDrawerOpen : isMenuExpanded;
179
+
180
+ const filteredItems: Array<NavbarItemOptions | NavbarAccordionOptions> =
181
+ useMemo(
182
+ () =>
183
+ items
184
+ .filter((item) => {
185
+ return !item.hide && (isSidebarOpened || !!item.icon);
186
+ })
187
+ .map((item) => ({
188
+ ...item,
189
+ menuGroupKey: item.groupKey,
190
+ })),
191
+ [items, isSidebarOpened],
192
+ );
193
+
194
+ return (
195
+ <Wrapper isMobile={isMobile} data-expanded={isSidebarOpened}>
196
+ <Header>
197
+ {isSidebarOpened && header}
198
+
199
+ {!isMobile && (
200
+ <ExpandIconButton
201
+ disableRipple={true}
202
+ style={isMenuExpanded ? { paddingRight: 0 } : {}}
203
+ onClick={() => {
204
+ setMenuExpanded(!isMenuExpanded);
205
+ }}
206
+ >
207
+ {isMenuExpanded ? <MenuOpen /> : <MenuIcon />}
208
+ </ExpandIconButton>
209
+ )}
210
+ </Header>
211
+
212
+ <Content aria-expanded={isSidebarOpened}>
213
+ {filteredItems.map((item) => {
214
+ const index = filteredItems.indexOf(item);
215
+ const prev = filteredItems[index - 1];
216
+
217
+ if ('items' in item) {
218
+ return (
219
+ <NavbarAccordion
220
+ {...item}
221
+ key={item.key}
222
+ gutter={prev && prev.groupKey !== item.groupKey}
223
+ onClick={item.onClick}
224
+ />
225
+ );
226
+ }
227
+ return (
228
+ <NavbarItem
229
+ {...item}
230
+ key={item.key}
231
+ gutter={prev && prev.groupKey !== item.groupKey}
232
+ onClick={(event) => {
233
+ item.onClick?.(event);
234
+
235
+ if (!event.isDefaultPrevented()) {
236
+ setDrawerOpen(false);
237
+ }
238
+ }}
239
+ />
240
+ );
241
+ })}
242
+ </Content>
243
+
244
+ <Footer>{footer}</Footer>
245
+ </Wrapper>
246
+ );
247
+ }
@@ -0,0 +1,102 @@
1
+ import { Menu, MenuItem, Typography } from '@material-ui/core';
2
+ import { Color, ColorDynamic, Inline } from '@superdispatch/ui';
3
+ import { Key, MouseEvent, ReactElement, ReactNode, useState } from 'react';
4
+ import styled from 'styled-components';
5
+ import { useNavbarContext } from './NavbarContext';
6
+
7
+ const Divider = styled.div`
8
+ border-bottom: 1px solid ${ColorDynamic.Silver400};
9
+ margin: 8px -16px;
10
+ `;
11
+
12
+ const StyledMenuItem = styled(MenuItem)`
13
+ & svg {
14
+ font-size: 24px;
15
+ color: ${ColorDynamic.Dark100};
16
+ }
17
+ `;
18
+
19
+ const Wrapper = styled.div<{ active?: boolean }>`
20
+ width: 100%;
21
+ padding: 8px 16px;
22
+ cursor: pointer;
23
+ background: ${Color.White10};
24
+
25
+ border-left: 4px solid
26
+ ${({ active }) => (active ? ColorDynamic.Blue300 : 'transparent')};
27
+
28
+ &:hover {
29
+ border-left-color: ${ColorDynamic.Blue300};
30
+ background: ${Color.White08};
31
+ }
32
+ `;
33
+
34
+ export interface NavbarMenuItemOption {
35
+ key: Key;
36
+ icon: ReactNode;
37
+ label: ReactNode;
38
+ visible?: boolean;
39
+ onClick: () => void;
40
+ }
41
+
42
+ interface NavbarMenuProps {
43
+ children: ReactNode;
44
+ items: NavbarMenuItemOption[];
45
+ }
46
+
47
+ export function NavbarMenu({ items, children }: NavbarMenuProps): ReactElement {
48
+ const { setDrawerOpen } = useNavbarContext();
49
+ const [anchor, setAnchor] = useState<HTMLElement | null>(null);
50
+
51
+ function showProfileMenu(event: MouseEvent<HTMLElement>): void {
52
+ setAnchor(event.currentTarget);
53
+ }
54
+
55
+ function hideProfileMenu(): void {
56
+ setAnchor(null);
57
+ setDrawerOpen(false);
58
+ }
59
+
60
+ return (
61
+ <>
62
+ <Wrapper onClick={showProfileMenu} active={!!anchor}>
63
+ {children}
64
+ </Wrapper>
65
+
66
+ <Menu
67
+ open={!!anchor}
68
+ anchorEl={anchor}
69
+ onClose={hideProfileMenu}
70
+ onClick={hideProfileMenu}
71
+ anchorOrigin={{
72
+ vertical: 'top',
73
+ horizontal: 'center',
74
+ }}
75
+ transformOrigin={{
76
+ vertical: 'bottom',
77
+ horizontal: 'center',
78
+ }}
79
+ >
80
+ {items
81
+ .filter((item) => item.visible !== false)
82
+ .flatMap((item, index, arr) => {
83
+ return [
84
+ <StyledMenuItem key={item.key} onClick={item.onClick}>
85
+ <Inline space="small" verticalAlign="center">
86
+ {item.icon}
87
+
88
+ <Typography style={{ color: ColorDynamic.Dark500 }}>
89
+ {item.label}
90
+ </Typography>
91
+ </Inline>
92
+ </StyledMenuItem>,
93
+
94
+ index !== arr.length - 1 && (
95
+ <Divider key={`${item.key}-divider`} />
96
+ ),
97
+ ];
98
+ })}
99
+ </Menu>
100
+ </>
101
+ );
102
+ }