@superdispatch/ui-lab 0.21.11 → 0.21.12
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/.babelrc.js +5 -0
- package/.turbo/turbo-version.log +28 -0
- package/package.json +37 -12
- package/pkg/README.md +10 -0
- package/{dist-node → pkg/dist-node}/index.js +2 -4
- package/pkg/dist-node/index.js.map +1 -0
- package/{dist-src → pkg/dist-src}/alert/Alert.js +2 -4
- package/{dist-src → pkg/dist-src}/banner/Banner.js +0 -0
- package/{dist-src → pkg/dist-src}/box/Box.js +0 -0
- package/{dist-src → pkg/dist-src}/button/Button.js +0 -0
- package/{dist-src → pkg/dist-src}/button-area/ButtonArea.js +0 -0
- package/{dist-src → pkg/dist-src}/container/Container.js +0 -0
- package/{dist-src → pkg/dist-src}/description-item/DescriptionItem.js +0 -0
- package/{dist-src → pkg/dist-src}/file-drop-zone/FileDropZone.js +0 -0
- package/{dist-src → pkg/dist-src}/file-list-item/FileListItem.js +0 -0
- package/{dist-src → pkg/dist-src}/index.js +0 -0
- package/{dist-src → pkg/dist-src}/linked-text/LinkedText.js +0 -0
- package/{dist-src → pkg/dist-src}/multiline-text/MultilineText.js +0 -0
- package/{dist-src → pkg/dist-src}/navbar/Navbar.js +0 -0
- package/{dist-src → pkg/dist-src}/navbar/NavbarAccordion.js +0 -0
- package/{dist-src → pkg/dist-src}/navbar/NavbarAvatar.js +0 -0
- package/{dist-src → pkg/dist-src}/navbar/NavbarBottomBar.js +0 -0
- package/{dist-src → pkg/dist-src}/navbar/NavbarContext.js +0 -0
- package/{dist-src → pkg/dist-src}/navbar/NavbarItem.js +0 -0
- package/{dist-src → pkg/dist-src}/navbar/NavbarList.js +0 -0
- package/{dist-src → pkg/dist-src}/navbar/NavbarMenu.js +0 -0
- package/{dist-src → pkg/dist-src}/sidebar/Sidebar.js +0 -0
- package/{dist-src → pkg/dist-src}/sidebar/SidebarBackButton.js +0 -0
- package/{dist-src → pkg/dist-src}/sidebar/SidebarContainer.js +0 -0
- package/{dist-src → pkg/dist-src}/sidebar/SidebarContent.js +0 -0
- package/{dist-src → pkg/dist-src}/sidebar/SidebarDivider.js +0 -0
- package/{dist-src → pkg/dist-src}/sidebar/SidebarMenuItem.js +0 -0
- package/{dist-src → pkg/dist-src}/sidebar/SidebarMenuItemAction.js +0 -0
- package/{dist-src → pkg/dist-src}/sidebar/SidebarMenuItemAvatar.js +0 -0
- package/{dist-src → pkg/dist-src}/sidebar/SidebarMenuItemContext.js +0 -0
- package/{dist-src → pkg/dist-src}/sidebar/SidebarSubheader.js +0 -0
- package/{dist-src → pkg/dist-src}/text-box/TextBox.js +0 -0
- package/{dist-src → pkg/dist-src}/utils/RuleNormalizer.js +0 -0
- package/{dist-src → pkg/dist-src}/utils/mergeStyles.js +0 -0
- package/{dist-types → pkg/dist-types}/index.d.ts +0 -0
- package/{dist-web → pkg/dist-web}/index.js +2 -4
- package/pkg/dist-web/index.js.map +1 -0
- package/pkg/package.json +34 -0
- package/playroom.ts +23 -0
- package/src/alert/Alert.stories.tsx +105 -0
- package/src/alert/Alert.tsx +108 -0
- package/src/banner/Banner.stories.tsx +64 -0
- package/src/banner/Banner.tsx +120 -0
- package/src/box/Box.stories.tsx +20 -0
- package/src/box/Box.tsx +252 -0
- package/src/button/Button.stories.tsx +717 -0
- package/src/button/Button.tsx +460 -0
- package/src/button-area/ButtonArea.stories.tsx +65 -0
- package/src/button-area/ButtonArea.tsx +88 -0
- package/src/container/Container.tsx +48 -0
- package/src/description-item/DescriptionItem.stories.tsx +163 -0
- package/src/description-item/DescriptionItem.tsx +104 -0
- package/src/file-drop-zone/FileDropZone.stories.tsx +44 -0
- package/src/file-drop-zone/FileDropZone.tsx +170 -0
- package/src/file-list-item/FileListItem.stories.tsx +37 -0
- package/src/file-list-item/FileListItem.tsx +145 -0
- package/src/file-list-item/__tests__/FileListItem.spec.tsx +339 -0
- package/src/index.spec.ts +43 -0
- package/src/index.ts +28 -0
- package/src/linked-text/LinkeText.stories.tsx +42 -0
- package/src/linked-text/LinkedText.tsx +47 -0
- package/src/multiline-text/MultilineText.stories.tsx +30 -0
- package/src/multiline-text/MultilineText.ts +16 -0
- package/src/navbar/Navbar.stories.tsx +135 -0
- package/src/navbar/Navbar.tsx +111 -0
- package/src/navbar/NavbarAccordion.tsx +171 -0
- package/src/navbar/NavbarAvatar.tsx +51 -0
- package/src/navbar/NavbarBottomBar.tsx +135 -0
- package/src/navbar/NavbarContext.tsx +23 -0
- package/src/navbar/NavbarItem.tsx +119 -0
- package/src/navbar/NavbarList.tsx +225 -0
- package/src/navbar/NavbarMenu.tsx +102 -0
- package/src/sidebar/Sidebar.stories.tsx +363 -0
- package/src/sidebar/Sidebar.tsx +73 -0
- package/src/sidebar/SidebarBackButton.tsx +33 -0
- package/src/sidebar/SidebarContainer.tsx +114 -0
- package/src/sidebar/SidebarContent.tsx +119 -0
- package/src/sidebar/SidebarDivider.tsx +15 -0
- package/src/sidebar/SidebarMenuItem.tsx +211 -0
- package/src/sidebar/SidebarMenuItemAction.tsx +27 -0
- package/src/sidebar/SidebarMenuItemAvatar.tsx +59 -0
- package/src/sidebar/SidebarMenuItemContext.tsx +33 -0
- package/src/sidebar/SidebarSubheader.tsx +38 -0
- package/src/styled.d.ts +12 -0
- package/src/text-box/TextBox.stories.tsx +108 -0
- package/src/text-box/TextBox.tsx +229 -0
- package/src/utils/RuleNormalizer.ts +24 -0
- package/src/utils/mergeStyles.ts +28 -0
- package/tsconfig.json +19 -0
- package/LICENSE +0 -21
- package/dist-node/index.js.map +0 -1
- package/dist-web/index.js.map +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Color, Column, Columns, useUID } from '@superdispatch/ui';
|
|
2
|
+
import { forwardRef, ReactNode } from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { TextBox } from '../text-box/TextBox';
|
|
5
|
+
|
|
6
|
+
const SidebarRoot = styled.aside`
|
|
7
|
+
top: 0;
|
|
8
|
+
position: sticky;
|
|
9
|
+
overflow-y: auto;
|
|
10
|
+
overflow-x: hidden;
|
|
11
|
+
|
|
12
|
+
height: 100vh;
|
|
13
|
+
background-color: ${Color.White};
|
|
14
|
+
border-right: 1px solid ${Color.Silver400};
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const SidebarHeader = styled.div`
|
|
18
|
+
top: 0;
|
|
19
|
+
z-index: 2;
|
|
20
|
+
position: sticky;
|
|
21
|
+
padding-left: 24px;
|
|
22
|
+
padding-right: 24px;
|
|
23
|
+
padding-bottom: 8px;
|
|
24
|
+
background-color: ${Color.White};
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
const SidebarTitle = styled.div`
|
|
28
|
+
height: 64px;
|
|
29
|
+
max-height: 64px;
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
export interface SidebarProps {
|
|
35
|
+
id?: string;
|
|
36
|
+
title?: ReactNode;
|
|
37
|
+
count?: null | number;
|
|
38
|
+
header?: ReactNode;
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
|
|
43
|
+
({ title, count, header, children, id: idProp }, ref) => {
|
|
44
|
+
const id = useUID(idProp);
|
|
45
|
+
const titleID = `${id}-title`;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<SidebarRoot id={id} ref={ref}>
|
|
49
|
+
<SidebarHeader>
|
|
50
|
+
<SidebarTitle>
|
|
51
|
+
<Columns space="xsmall" align="center">
|
|
52
|
+
<Column width="fluid">
|
|
53
|
+
<TextBox variant="heading-2" noWrap={true} id={titleID}>
|
|
54
|
+
{title}
|
|
55
|
+
</TextBox>
|
|
56
|
+
</Column>
|
|
57
|
+
|
|
58
|
+
{!!count && (
|
|
59
|
+
<Column width="content">
|
|
60
|
+
<TextBox color="secondary">{count}</TextBox>
|
|
61
|
+
</Column>
|
|
62
|
+
)}
|
|
63
|
+
</Columns>
|
|
64
|
+
</SidebarTitle>
|
|
65
|
+
|
|
66
|
+
{header}
|
|
67
|
+
</SidebarHeader>
|
|
68
|
+
|
|
69
|
+
<div aria-labelledby={titleID}>{children}</div>
|
|
70
|
+
</SidebarRoot>
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { IconButton, IconButtonProps } from '@material-ui/core';
|
|
2
|
+
import { ArrowBack } from '@material-ui/icons';
|
|
3
|
+
import { useCollapseBreakpoint } from '@superdispatch/ui';
|
|
4
|
+
import { ReactElement } from 'react';
|
|
5
|
+
import { useSidebarContext } from './SidebarContainer';
|
|
6
|
+
|
|
7
|
+
export function SidebarBackButton({
|
|
8
|
+
onClick,
|
|
9
|
+
children = <ArrowBack fontSize="small" />,
|
|
10
|
+
...props
|
|
11
|
+
}: IconButtonProps): ReactElement | null {
|
|
12
|
+
const isCollapsed = useCollapseBreakpoint('sm');
|
|
13
|
+
const { openSidebar } = useSidebarContext();
|
|
14
|
+
|
|
15
|
+
if (!isCollapsed) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<IconButton
|
|
21
|
+
{...props}
|
|
22
|
+
size="small"
|
|
23
|
+
onClick={(event) => {
|
|
24
|
+
onClick?.(event);
|
|
25
|
+
if (!event.isDefaultPrevented()) {
|
|
26
|
+
openSidebar();
|
|
27
|
+
}
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</IconButton>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { useCollapseBreakpoint } from '@superdispatch/ui';
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
forwardRef,
|
|
5
|
+
ReactChild,
|
|
6
|
+
ReactNode,
|
|
7
|
+
useContext,
|
|
8
|
+
useMemo,
|
|
9
|
+
useState,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import styled from 'styled-components';
|
|
12
|
+
|
|
13
|
+
interface SidebarContext {
|
|
14
|
+
openSidebar: () => void;
|
|
15
|
+
openSidebarContent: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Context = createContext<SidebarContext | null>(null);
|
|
19
|
+
|
|
20
|
+
export function useSidebarContext(): SidebarContext {
|
|
21
|
+
const context = useContext(Context);
|
|
22
|
+
|
|
23
|
+
if (!context) {
|
|
24
|
+
throw new Error('SidebarContext is used outside Provider');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return context;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SidebarContainerSidebar = styled.div`
|
|
31
|
+
height: inherit;
|
|
32
|
+
max-height: inherit;
|
|
33
|
+
min-height: inherit;
|
|
34
|
+
|
|
35
|
+
width: 240px;
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
const SidebarContainerContent = styled.div`
|
|
39
|
+
height: inherit;
|
|
40
|
+
max-height: inherit;
|
|
41
|
+
min-height: inherit;
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const SidebarContainerRoot = styled.div`
|
|
45
|
+
display: flex;
|
|
46
|
+
|
|
47
|
+
height: inherit;
|
|
48
|
+
max-height: inherit;
|
|
49
|
+
min-height: inherit;
|
|
50
|
+
|
|
51
|
+
& ${SidebarContainerContent} {
|
|
52
|
+
width: calc(100% - 240px);
|
|
53
|
+
min-width: calc(100% - 240px);
|
|
54
|
+
max-width: calc(100% - 240px);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&[data-visibility='sidebar'] ${SidebarContainerSidebar} {
|
|
58
|
+
width: 100%;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&[data-visibility='sidebar'] ${SidebarContainerContent} {
|
|
62
|
+
display: none;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
&[data-visibility='content'] ${SidebarContainerSidebar} {
|
|
66
|
+
display: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&[data-visibility='content'] ${SidebarContainerContent} {
|
|
70
|
+
width: 100%;
|
|
71
|
+
max-width: unset;
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
export interface SidebarContainerProps {
|
|
76
|
+
sidebar: ReactChild;
|
|
77
|
+
children?: ReactNode;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type SidebarContainerVisibility = 'both' | 'sidebar' | 'content';
|
|
81
|
+
|
|
82
|
+
export const SidebarContainer = forwardRef<
|
|
83
|
+
HTMLDivElement,
|
|
84
|
+
SidebarContainerProps
|
|
85
|
+
>(({ sidebar, children }, ref) => {
|
|
86
|
+
const isCollapsed = useCollapseBreakpoint('sm');
|
|
87
|
+
const [visibilityState, setVisibilityState] =
|
|
88
|
+
useState<SidebarContainerVisibility>('sidebar');
|
|
89
|
+
|
|
90
|
+
const visibility: SidebarContainerVisibility = isCollapsed
|
|
91
|
+
? visibilityState
|
|
92
|
+
: 'both';
|
|
93
|
+
|
|
94
|
+
const context = useMemo<SidebarContext>(() => {
|
|
95
|
+
return {
|
|
96
|
+
openSidebar: () => {
|
|
97
|
+
setVisibilityState('sidebar');
|
|
98
|
+
},
|
|
99
|
+
openSidebarContent: () => {
|
|
100
|
+
setVisibilityState('content');
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Context.Provider value={context}>
|
|
107
|
+
<SidebarContainerRoot ref={ref} data-visibility={visibility}>
|
|
108
|
+
<SidebarContainerSidebar>{sidebar}</SidebarContainerSidebar>
|
|
109
|
+
|
|
110
|
+
<SidebarContainerContent>{children}</SidebarContainerContent>
|
|
111
|
+
</SidebarContainerRoot>
|
|
112
|
+
</Context.Provider>
|
|
113
|
+
);
|
|
114
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { AppBar, Toolbar } from '@material-ui/core';
|
|
2
|
+
import { Column, Columns, Stack, SuperDispatchTheme } from '@superdispatch/ui';
|
|
3
|
+
import {
|
|
4
|
+
MouseEvent,
|
|
5
|
+
ReactElement,
|
|
6
|
+
ReactNode,
|
|
7
|
+
useLayoutEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import styled, { css } from 'styled-components';
|
|
11
|
+
import { Box } from '../box/Box';
|
|
12
|
+
import { TextBox } from '../text-box/TextBox';
|
|
13
|
+
import { SidebarBackButton } from './SidebarBackButton';
|
|
14
|
+
import { useSidebarContext } from './SidebarContainer';
|
|
15
|
+
|
|
16
|
+
const SidebarAppBar = styled(AppBar)(
|
|
17
|
+
({ theme }: { theme: SuperDispatchTheme }) => {
|
|
18
|
+
return css`
|
|
19
|
+
${theme.breakpoints.up('sm')} {
|
|
20
|
+
&& {
|
|
21
|
+
border-left: transparent;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const ToolbarContent = styled.div`
|
|
29
|
+
flex: 1;
|
|
30
|
+
padding: 16px;
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export interface SidebarContentProps {
|
|
34
|
+
dense?: boolean;
|
|
35
|
+
title: ReactNode;
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
action?: ReactNode;
|
|
38
|
+
openOnMount?: boolean;
|
|
39
|
+
closeOnUnmount?: boolean;
|
|
40
|
+
onBack?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function SidebarContent({
|
|
44
|
+
dense,
|
|
45
|
+
action,
|
|
46
|
+
title,
|
|
47
|
+
children,
|
|
48
|
+
onBack,
|
|
49
|
+
openOnMount,
|
|
50
|
+
closeOnUnmount,
|
|
51
|
+
}: SidebarContentProps): ReactElement {
|
|
52
|
+
const isOpenedOnMount = useRef<boolean>(false);
|
|
53
|
+
const isClosedOnMount = useRef<boolean>(false);
|
|
54
|
+
|
|
55
|
+
const { openSidebarContent, openSidebar } = useSidebarContext();
|
|
56
|
+
|
|
57
|
+
useLayoutEffect(() => {
|
|
58
|
+
if (openOnMount) {
|
|
59
|
+
if (isOpenedOnMount.current) {
|
|
60
|
+
// eslint-disable-next-line no-console
|
|
61
|
+
console.warn(
|
|
62
|
+
'[SidebarContent]: "openOnMount" should not change during lifecycle of the component.',
|
|
63
|
+
);
|
|
64
|
+
} else {
|
|
65
|
+
isOpenedOnMount.current = true;
|
|
66
|
+
openSidebarContent();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}, [openOnMount, openSidebarContent]);
|
|
70
|
+
|
|
71
|
+
useLayoutEffect(() => {
|
|
72
|
+
return () => {
|
|
73
|
+
if (closeOnUnmount) {
|
|
74
|
+
if (isClosedOnMount.current) {
|
|
75
|
+
// eslint-disable-next-line no-console
|
|
76
|
+
console.warn(
|
|
77
|
+
'[SidebarContent]: "closeOnUnmount" should not change during lifecycle of the component.',
|
|
78
|
+
);
|
|
79
|
+
} else {
|
|
80
|
+
isClosedOnMount.current = true;
|
|
81
|
+
openSidebar();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}, [openSidebar, closeOnUnmount]);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Stack space="none">
|
|
89
|
+
<SidebarAppBar>
|
|
90
|
+
<Toolbar disableGutters={true}>
|
|
91
|
+
<ToolbarContent>
|
|
92
|
+
<Columns align={['top', 'center']} space="small">
|
|
93
|
+
<Column width="content">
|
|
94
|
+
<SidebarBackButton onClick={onBack} />
|
|
95
|
+
</Column>
|
|
96
|
+
|
|
97
|
+
<Column>
|
|
98
|
+
<Columns
|
|
99
|
+
space="small"
|
|
100
|
+
collapseBelow="tablet"
|
|
101
|
+
reverse={[true, false]}
|
|
102
|
+
align={['bottom', 'center']}
|
|
103
|
+
>
|
|
104
|
+
<Column>
|
|
105
|
+
<TextBox variant="heading-2">{title}</TextBox>
|
|
106
|
+
</Column>
|
|
107
|
+
|
|
108
|
+
{action && <Column width="content">{action}</Column>}
|
|
109
|
+
</Columns>
|
|
110
|
+
</Column>
|
|
111
|
+
</Columns>
|
|
112
|
+
</ToolbarContent>
|
|
113
|
+
</Toolbar>
|
|
114
|
+
</SidebarAppBar>
|
|
115
|
+
|
|
116
|
+
<Box padding={dense ? 'none' : ['small', 'medium']}>{children}</Box>
|
|
117
|
+
</Stack>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Divider } from '@material-ui/core';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
|
|
5
|
+
const SidebarDividerRoot = styled.div`
|
|
6
|
+
padding: 20px 24px;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
export const SidebarDivider = forwardRef<HTMLDivElement>((_, ref) => {
|
|
10
|
+
return (
|
|
11
|
+
<SidebarDividerRoot ref={ref}>
|
|
12
|
+
<Divider />
|
|
13
|
+
</SidebarDividerRoot>
|
|
14
|
+
);
|
|
15
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { ButtonBase } from '@material-ui/core';
|
|
2
|
+
import { OpenInNew } from '@material-ui/icons';
|
|
3
|
+
import { Color, Column, Columns, Inline, mergeRefs } from '@superdispatch/ui';
|
|
4
|
+
import {
|
|
5
|
+
forwardRef,
|
|
6
|
+
MouseEvent,
|
|
7
|
+
ReactNode,
|
|
8
|
+
useLayoutEffect,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import styled, { css } from 'styled-components';
|
|
13
|
+
import { TextBox } from '../text-box/TextBox';
|
|
14
|
+
import { useSidebarContext } from './SidebarContainer';
|
|
15
|
+
import { SidebarMenuItemContextProvider } from './SidebarMenuItemContext';
|
|
16
|
+
|
|
17
|
+
interface SidebarMenuItemRootProps {
|
|
18
|
+
hasAvatar: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SidebarMenuItemRoot = styled.div<SidebarMenuItemRootProps>(
|
|
22
|
+
({ hasAvatar }) => {
|
|
23
|
+
const height = hasAvatar ? 48 : 40;
|
|
24
|
+
|
|
25
|
+
return css`
|
|
26
|
+
position: relative;
|
|
27
|
+
|
|
28
|
+
& > .MuiButtonBase-root {
|
|
29
|
+
width: 100%;
|
|
30
|
+
display: flex;
|
|
31
|
+
justify-content: flex-start;
|
|
32
|
+
|
|
33
|
+
padding-left: 24px;
|
|
34
|
+
padding-right: 24px;
|
|
35
|
+
height: ${height}px;
|
|
36
|
+
max-height: ${height}px;
|
|
37
|
+
|
|
38
|
+
&[aria-current='true'] {
|
|
39
|
+
background-color: ${Color.Silver200};
|
|
40
|
+
box-shadow: inset 4px 0 0 ${Color.Blue300};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const SidebarMenuItemBadge = styled.div`
|
|
48
|
+
font-size: 12px;
|
|
49
|
+
line-height: 16px;
|
|
50
|
+
padding-left: 4px;
|
|
51
|
+
padding-right: 4px;
|
|
52
|
+
border-radius: 100px;
|
|
53
|
+
|
|
54
|
+
color: ${Color.Dark500};
|
|
55
|
+
background-color: ${Color.Silver300};
|
|
56
|
+
|
|
57
|
+
.MuiButtonBase-root[aria-current='true'] & {
|
|
58
|
+
color: ${Color.White};
|
|
59
|
+
background-color: ${Color.Dark450};
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const SidebarMenuItemSecondaryAction = styled.div`
|
|
64
|
+
top: 50%;
|
|
65
|
+
right: 24px;
|
|
66
|
+
position: absolute;
|
|
67
|
+
transform: translate3d(0, -50%, 0);
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
export interface SidebarMenuItemProps {
|
|
71
|
+
selected?: boolean;
|
|
72
|
+
external?: boolean;
|
|
73
|
+
disabled?: boolean;
|
|
74
|
+
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
75
|
+
|
|
76
|
+
badge?: null | number;
|
|
77
|
+
action?: ReactNode;
|
|
78
|
+
avatar?: ReactNode;
|
|
79
|
+
children?: ReactNode;
|
|
80
|
+
openContentOnClick?: boolean;
|
|
81
|
+
secondaryActions?: ReactNode;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const SidebarMenuItem = forwardRef<HTMLDivElement, SidebarMenuItemProps>(
|
|
85
|
+
(
|
|
86
|
+
{
|
|
87
|
+
action,
|
|
88
|
+
avatar,
|
|
89
|
+
onClick,
|
|
90
|
+
external,
|
|
91
|
+
children,
|
|
92
|
+
disabled,
|
|
93
|
+
selected,
|
|
94
|
+
secondaryActions,
|
|
95
|
+
badge: badgeProp,
|
|
96
|
+
openContentOnClick,
|
|
97
|
+
},
|
|
98
|
+
ref,
|
|
99
|
+
) => {
|
|
100
|
+
const [hovered, setHovered] = useState(false);
|
|
101
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
102
|
+
const actionsRef = useRef<HTMLDivElement>(null);
|
|
103
|
+
const actionsPlaceholderRef = useRef<HTMLDivElement>(null);
|
|
104
|
+
const { openSidebarContent } = useSidebarContext();
|
|
105
|
+
const { matches: isHoverSupported } = matchMedia('(hover: hover)');
|
|
106
|
+
|
|
107
|
+
useLayoutEffect(() => {
|
|
108
|
+
if (actionsRef.current && actionsPlaceholderRef.current) {
|
|
109
|
+
actionsPlaceholderRef.current.style.width = `${Math.max(
|
|
110
|
+
0,
|
|
111
|
+
actionsRef.current.offsetWidth - 8,
|
|
112
|
+
)}px`;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
useLayoutEffect(() => {
|
|
117
|
+
const rootNode = rootRef.current;
|
|
118
|
+
|
|
119
|
+
if (rootNode) {
|
|
120
|
+
if (isHoverSupported) {
|
|
121
|
+
rootNode.addEventListener('mouseenter', () => {
|
|
122
|
+
setHovered(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
rootNode.addEventListener('mouseleave', () => {
|
|
126
|
+
setHovered(false);
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
setHovered(true);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, [isHoverSupported]);
|
|
133
|
+
|
|
134
|
+
const badge =
|
|
135
|
+
!badgeProp || !Number.isFinite(badgeProp)
|
|
136
|
+
? null
|
|
137
|
+
: badgeProp > 999
|
|
138
|
+
? '999+'
|
|
139
|
+
: badgeProp;
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<SidebarMenuItemRoot ref={mergeRefs(ref, rootRef)} hasAvatar={!!avatar}>
|
|
143
|
+
<ButtonBase
|
|
144
|
+
disabled={disabled}
|
|
145
|
+
aria-current={selected}
|
|
146
|
+
onClick={(event) => {
|
|
147
|
+
onClick?.(event);
|
|
148
|
+
if (!event.isDefaultPrevented() && openContentOnClick) {
|
|
149
|
+
openSidebarContent();
|
|
150
|
+
}
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
<Columns align="center" space="xsmall">
|
|
154
|
+
<Column>
|
|
155
|
+
<Columns align="center" space="xsmall">
|
|
156
|
+
<Column width="fluid">
|
|
157
|
+
<Columns align="center" space="xsmall">
|
|
158
|
+
{!!avatar && (
|
|
159
|
+
<SidebarMenuItemContextProvider
|
|
160
|
+
hovered={hovered}
|
|
161
|
+
disabled={disabled}
|
|
162
|
+
>
|
|
163
|
+
<Column width="content">{avatar}</Column>
|
|
164
|
+
</SidebarMenuItemContextProvider>
|
|
165
|
+
)}
|
|
166
|
+
|
|
167
|
+
<Column width="adaptive">
|
|
168
|
+
<TextBox
|
|
169
|
+
variant={selected ? 'body-semibold' : 'body'}
|
|
170
|
+
noWrap={true}
|
|
171
|
+
>
|
|
172
|
+
{children}
|
|
173
|
+
</TextBox>
|
|
174
|
+
</Column>
|
|
175
|
+
|
|
176
|
+
{external && (
|
|
177
|
+
<Column width="content">
|
|
178
|
+
<OpenInNew color="action" fontSize="small" />
|
|
179
|
+
</Column>
|
|
180
|
+
)}
|
|
181
|
+
</Columns>
|
|
182
|
+
</Column>
|
|
183
|
+
</Columns>
|
|
184
|
+
</Column>
|
|
185
|
+
|
|
186
|
+
{!!badge && (
|
|
187
|
+
<Column width="content">
|
|
188
|
+
<SidebarMenuItemBadge>{badge}</SidebarMenuItemBadge>
|
|
189
|
+
</Column>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
{(!!action || !!secondaryActions) && (
|
|
193
|
+
<Column width="content">
|
|
194
|
+
<div ref={actionsPlaceholderRef} />
|
|
195
|
+
</Column>
|
|
196
|
+
)}
|
|
197
|
+
</Columns>
|
|
198
|
+
</ButtonBase>
|
|
199
|
+
|
|
200
|
+
{(!!action || !!secondaryActions) && (
|
|
201
|
+
<SidebarMenuItemSecondaryAction ref={actionsRef}>
|
|
202
|
+
<Inline>
|
|
203
|
+
{hovered && secondaryActions}
|
|
204
|
+
{action}
|
|
205
|
+
</Inline>
|
|
206
|
+
</SidebarMenuItemSecondaryAction>
|
|
207
|
+
)}
|
|
208
|
+
</SidebarMenuItemRoot>
|
|
209
|
+
);
|
|
210
|
+
},
|
|
211
|
+
);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { IconButton, Tooltip, TooltipProps } from '@material-ui/core';
|
|
2
|
+
import { forwardRef, ReactElement } from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
|
|
5
|
+
const SidebarMenuItemActionRoot = styled(IconButton)`
|
|
6
|
+
& .MuiSvgIcon-root {
|
|
7
|
+
font-size: 16px;
|
|
8
|
+
}
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
export interface SidebarMenuItemActionProps
|
|
12
|
+
extends Pick<TooltipProps, 'title' | 'placement'> {
|
|
13
|
+
children: ReactElement;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SidebarMenuItemAction = forwardRef<
|
|
17
|
+
HTMLButtonElement,
|
|
18
|
+
SidebarMenuItemActionProps
|
|
19
|
+
>(({ title, placement, children }, ref) => {
|
|
20
|
+
return (
|
|
21
|
+
<Tooltip title={title} placement={placement}>
|
|
22
|
+
<SidebarMenuItemActionRoot ref={ref} size="small" edge="end">
|
|
23
|
+
{children}
|
|
24
|
+
</SidebarMenuItemActionRoot>
|
|
25
|
+
</Tooltip>
|
|
26
|
+
);
|
|
27
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Avatar, Checkbox } from '@material-ui/core';
|
|
2
|
+
import { forwardRef, SyntheticEvent, useMemo } from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { useSidebarMenuItemContext } from './SidebarMenuItemContext';
|
|
5
|
+
|
|
6
|
+
function stopPropagation(event: SyntheticEvent): void {
|
|
7
|
+
event.stopPropagation();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const SidebarMenuItemAvatarCheckbox = styled.div`
|
|
11
|
+
margin: -5px;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
export interface SidebarMenuItemAvatarProps {
|
|
15
|
+
children: string;
|
|
16
|
+
|
|
17
|
+
value?: boolean;
|
|
18
|
+
onChange?: (checked: boolean) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const SidebarMenuItemAvatar = forwardRef<
|
|
22
|
+
HTMLDivElement,
|
|
23
|
+
SidebarMenuItemAvatarProps
|
|
24
|
+
>(({ children, value, onChange }, ref) => {
|
|
25
|
+
const { hovered, disabled } = useSidebarMenuItemContext();
|
|
26
|
+
|
|
27
|
+
const initials = useMemo(() => {
|
|
28
|
+
const matches = children.match(/\b\w/g) || [];
|
|
29
|
+
|
|
30
|
+
const first = matches.shift() || '';
|
|
31
|
+
const last = matches.pop() || '';
|
|
32
|
+
|
|
33
|
+
return (first + last).toUpperCase();
|
|
34
|
+
}, [children]);
|
|
35
|
+
|
|
36
|
+
if (value === true || (hovered && value != null)) {
|
|
37
|
+
return (
|
|
38
|
+
<SidebarMenuItemAvatarCheckbox>
|
|
39
|
+
<Checkbox
|
|
40
|
+
color="primary"
|
|
41
|
+
checked={value}
|
|
42
|
+
disabled={disabled}
|
|
43
|
+
onClick={stopPropagation}
|
|
44
|
+
onMouseDown={stopPropagation}
|
|
45
|
+
onTouchStart={stopPropagation}
|
|
46
|
+
onChange={(_, checked) => {
|
|
47
|
+
onChange?.(checked);
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
</SidebarMenuItemAvatarCheckbox>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Avatar ref={ref} aria-hidden={true}>
|
|
56
|
+
{initials}
|
|
57
|
+
</Avatar>
|
|
58
|
+
);
|
|
59
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
ReactElement,
|
|
4
|
+
ReactNode,
|
|
5
|
+
useContext,
|
|
6
|
+
useMemo,
|
|
7
|
+
} from 'react';
|
|
8
|
+
|
|
9
|
+
export interface SidebarMenuItemContext {
|
|
10
|
+
hovered?: boolean;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const context = createContext<SidebarMenuItemContext>({});
|
|
15
|
+
|
|
16
|
+
export function useSidebarMenuItemContext(): SidebarMenuItemContext {
|
|
17
|
+
return useContext(context);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SidebarMenuItemContextProviderProps
|
|
21
|
+
extends SidebarMenuItemContext {
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function SidebarMenuItemContextProvider({
|
|
26
|
+
children,
|
|
27
|
+
hovered = false,
|
|
28
|
+
disabled = false,
|
|
29
|
+
}: SidebarMenuItemContextProviderProps): ReactElement {
|
|
30
|
+
const ctx = useMemo(() => ({ hovered, disabled }), [hovered, disabled]);
|
|
31
|
+
|
|
32
|
+
return <context.Provider value={ctx}>{children}</context.Provider>;
|
|
33
|
+
}
|