polpo 0.1.0 → 0.1.2
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/.storybook/theme.ts +2 -2
- package/.turbo/turbo-build.log +0 -77
- package/.turbo/turbo-lint.log +1 -1
- package/README.md +2 -5
- package/dist/chunk-CFYQBHH5.js +3 -0
- package/dist/chunk-CFYQBHH5.js.map +1 -0
- package/dist/chunk-MAWW6AA7.js +3 -0
- package/dist/chunk-MAWW6AA7.js.map +1 -0
- package/dist/get-modal-position-drle0OjP.d.cts +49 -0
- package/dist/get-modal-position-drle0OjP.d.ts +49 -0
- package/dist/helpers.cjs +1 -1
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +9 -2
- package/dist/helpers.d.ts +9 -2
- package/dist/helpers.js +1 -1
- package/dist/hooks.cjs +1 -1
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +59 -21
- package/dist/hooks.d.ts +59 -21
- package/dist/hooks.js +1 -1
- package/dist/ui.cjs +601 -389
- package/dist/ui.cjs.map +1 -1
- package/dist/ui.d.cts +97 -77
- package/dist/ui.d.ts +97 -77
- package/dist/ui.js +585 -373
- package/dist/ui.js.map +1 -1
- package/dist/use-modal-in-container-DiNW1PE_.d.cts +34 -0
- package/dist/use-modal-in-container-neGo-kMk.d.ts +34 -0
- package/package.json +5 -5
- package/src/components/buttons/button/button.stories.tsx +4 -4
- package/src/components/buttons/button/button.style.ts +10 -5
- package/src/components/buttons/button/button.tsx +7 -19
- package/src/components/cards/flip-card/flip-card.tsx +1 -1
- package/src/components/cursor/cursor.stories.tsx +35 -0
- package/src/components/cursor/cursor.style.ts +73 -0
- package/src/components/cursor/cursor.tsx +49 -0
- package/src/components/cursor/index.ts +1 -0
- package/src/components/form/checkbox/checkbox.stories.tsx +51 -0
- package/src/components/form/checkbox/checkbox.style.ts +73 -37
- package/src/components/form/checkbox/checkbox.tsx +38 -4
- package/src/components/form/field/field.stories.tsx +5 -1
- package/src/components/form/field/field.style.ts +12 -0
- package/src/components/form/field/field.tsx +3 -1
- package/src/components/form/field/field.types.ts +6 -0
- package/src/components/form/input-color/input-color.style.ts +5 -4
- package/src/components/form/input-color/input-color.tsx +41 -44
- package/src/components/form/radio/radio.stories.tsx +29 -5
- package/src/components/form/radio/radio.style.ts +45 -24
- package/src/components/form/radio/radio.tsx +22 -3
- package/src/components/form/select/options.tsx +119 -67
- package/src/components/form/select/select.stories.tsx +103 -42
- package/src/components/form/select/select.style.ts +10 -92
- package/src/components/form/select/select.tsx +19 -42
- package/src/components/form/select/select.types.ts +4 -21
- package/src/components/form/slider/slider.style.ts +2 -0
- package/src/components/icon/icons/social.tsx +17 -1
- package/src/components/index.ts +1 -0
- package/src/components/infinity-scroll/infinity-scroll.tsx +1 -1
- package/src/components/line/line.stories.tsx +3 -4
- package/src/components/modals/action-modal/action-modal.stories.tsx +58 -39
- package/src/components/modals/action-modal/action-modal.style.ts +13 -25
- package/src/components/modals/action-modal/action-modal.tsx +68 -70
- package/src/components/modals/aside-modal/aside-modal.stories.tsx +11 -15
- package/src/components/modals/aside-modal/aside-modal.style.ts +17 -37
- package/src/components/modals/aside-modal/aside-modal.tsx +41 -43
- package/src/components/modals/confirmation-modal/confirmation-modal.stories.tsx +21 -9
- package/src/components/modals/index.ts +2 -0
- package/src/components/modals/menu/index.ts +1 -0
- package/src/components/modals/menu/menu.stories.tsx +69 -0
- package/src/components/modals/menu/menu.style.ts +62 -0
- package/src/components/modals/menu/menu.tsx +142 -0
- package/src/components/modals/modal/backdrop.tsx +70 -0
- package/src/components/modals/modal/index.ts +1 -0
- package/src/components/modals/modal/modal.stories.tsx +325 -0
- package/src/components/modals/modal/modal.style.ts +62 -2
- package/src/components/modals/modal/modal.tsx +82 -123
- package/src/components/modals/portal/index.ts +1 -0
- package/src/components/modals/portal/portal.tsx +18 -0
- package/src/components/tabs/tabs-list.tsx +13 -10
- package/src/components/tabs/tabs.style.ts +48 -43
- package/src/components/tag/tag.stories.tsx +11 -12
- package/src/components/tag/tag.style.ts +9 -4
- package/src/components/tag/tag.tsx +2 -12
- package/src/components/tooltips/tooltip/tooltip.stories.tsx +5 -2
- package/src/components/tooltips/tooltip/tooltip.style.ts +37 -6
- package/src/components/tooltips/tooltip/tooltip.tsx +33 -19
- package/src/components/typography/typography.stories.tsx +3 -1
- package/src/components/typography/typography.tsx +21 -0
- package/src/contexts/theme-context/theme.animations.ts +91 -2
- package/src/contexts/theme-context/theme.defaults.ts +1 -1
- package/src/core/http-client.ts +49 -47
- package/src/core/variants/color.ts +3 -30
- package/src/core/variants/radius.ts +12 -41
- package/src/core/variants/size.ts +8 -33
- package/src/helpers/get-modal-position-relative-to-screen.ts +86 -0
- package/src/helpers/get-modal-position.ts +173 -28
- package/src/helpers/index.ts +1 -0
- package/src/hooks/index.ts +9 -3
- package/src/hooks/use-click-outside.ts +32 -0
- package/src/hooks/use-cookie.ts +124 -0
- package/src/hooks/use-dimensions.ts +11 -14
- package/src/hooks/use-dom-container.ts +32 -0
- package/src/hooks/use-event-listener.ts +4 -4
- package/src/hooks/use-geolocation.ts +63 -0
- package/src/hooks/use-in-view.ts +9 -11
- package/src/hooks/use-intersection-observer.ts +19 -0
- package/src/hooks/use-modal-in-container.ts +60 -52
- package/src/hooks/use-modal-transition.ts +54 -0
- package/src/hooks/use-modal.ts +21 -0
- package/src/hooks/use-mouse-position.ts +55 -7
- package/src/hooks/use-resize-observer.ts +18 -0
- package/src/stories/GettingStarted.mdx +2 -6
- package/svg/Name=npm, Category=social.svg +3 -0
- package/tsconfig.json +1 -0
- package/vite.config.ts +1 -0
- package/.turbo/daemon/f5c5c8fb195b01d0-turbo.log.2024-05-26 +0 -0
- package/.turbo/turbo-build$colon$watch.log +0 -96
- package/.turbo/turbo-build-storybook.log +0 -0
- package/.turbo/turbo-lint$colon$fix.log +0 -2
- package/dist/chunk-M4KRSYE7.js +0 -3
- package/dist/chunk-M4KRSYE7.js.map +0 -1
- package/dist/chunk-U5XSMSKZ.js +0 -3
- package/dist/chunk-U5XSMSKZ.js.map +0 -1
- package/dist/get-modal-position-DPftPoU2.d.cts +0 -28
- package/dist/get-modal-position-DPftPoU2.d.ts +0 -28
- package/src/components/form/select/select-option.tsx +0 -84
- package/src/hooks/use-observer.ts +0 -18
- package/src/hooks/use-on-click-outside-ref.ts +0 -17
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import styled from 'styled-components';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { Modal } from '@polpo/ui';
|
|
4
|
+
|
|
5
|
+
export const AsideModalStyle = styled(Modal)`
|
|
6
6
|
color: ${props => props.theme.colors.text.main};
|
|
7
|
+
overflow: auto;
|
|
8
|
+
height: 100%;
|
|
7
9
|
|
|
8
10
|
.aside-modal-content {
|
|
9
|
-
overflow: auto;
|
|
10
|
-
height: 100%;
|
|
11
11
|
padding: 2em;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -23,17 +23,12 @@ export const AsideModalStyle = styled.section`
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
&.left {
|
|
26
|
-
height: 100%;
|
|
27
|
-
top: 0;
|
|
28
|
-
left: 0;
|
|
29
26
|
border-right: 4px solid ${props => props.theme.colors.primary.main};
|
|
27
|
+
animation: slideIn-left 300ms ease;
|
|
30
28
|
|
|
31
|
-
&.
|
|
32
|
-
animation: slideIn-left 300ms ease;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
&.close-animation {
|
|
29
|
+
&.modal-close {
|
|
36
30
|
animation: slideOut-left 300ms ease;
|
|
31
|
+
transform: translateX(-100%);
|
|
37
32
|
}
|
|
38
33
|
|
|
39
34
|
.close-modal-button {
|
|
@@ -44,17 +39,12 @@ export const AsideModalStyle = styled.section`
|
|
|
44
39
|
}
|
|
45
40
|
|
|
46
41
|
&.right {
|
|
47
|
-
height: 100%;
|
|
48
|
-
top: 0;
|
|
49
|
-
right: 0;
|
|
50
42
|
border-left: 4px solid ${props => props.theme.colors.primary.main};
|
|
43
|
+
animation: slideIn-right 300ms ease;
|
|
51
44
|
|
|
52
|
-
&.
|
|
53
|
-
animation: slideIn-right 300ms ease;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
&.close-animation {
|
|
45
|
+
&.modal-close {
|
|
57
46
|
animation: slideOut-right 300ms ease;
|
|
47
|
+
transform: translateX(100%);
|
|
58
48
|
}
|
|
59
49
|
|
|
60
50
|
.close-modal-button {
|
|
@@ -65,17 +55,12 @@ export const AsideModalStyle = styled.section`
|
|
|
65
55
|
}
|
|
66
56
|
|
|
67
57
|
&.top {
|
|
68
|
-
top: 0;
|
|
69
|
-
right: 0;
|
|
70
|
-
width: 100%;
|
|
71
58
|
border-bottom: 4px solid ${props => props.theme.colors.primary.main};
|
|
59
|
+
animation: slideIn-top 300ms ease;
|
|
72
60
|
|
|
73
|
-
&.
|
|
74
|
-
animation: slideIn-top 300ms ease;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
&.close-animation {
|
|
61
|
+
&.modal-close {
|
|
78
62
|
animation: slideOut-top 300ms ease;
|
|
63
|
+
transform: translateY(-100%);
|
|
79
64
|
}
|
|
80
65
|
|
|
81
66
|
.close-modal-button {
|
|
@@ -86,17 +71,12 @@ export const AsideModalStyle = styled.section`
|
|
|
86
71
|
}
|
|
87
72
|
|
|
88
73
|
&.bottom {
|
|
89
|
-
bottom: 0;
|
|
90
|
-
right: 0;
|
|
91
|
-
width: 100%;
|
|
92
74
|
border-top: 4px solid ${props => props.theme.colors.primary.main};
|
|
75
|
+
animation: slideIn-bottom 300ms ease;
|
|
93
76
|
|
|
94
|
-
&.
|
|
95
|
-
animation: slideIn-bottom 300ms ease;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
&.close-animation {
|
|
77
|
+
&.modal-close {
|
|
99
78
|
animation: slideOut-bottom 300ms ease;
|
|
79
|
+
transform: translateY(100%);
|
|
100
80
|
}
|
|
101
81
|
|
|
102
82
|
.close-modal-button {
|
|
@@ -1,66 +1,64 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CSSProperties, useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Icon } from '../../icon';
|
|
4
|
-
import {
|
|
4
|
+
import { ModalProps } from '../modal';
|
|
5
5
|
|
|
6
6
|
import { AsideModalStyle } from './aside-modal.style';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
LEFT = 'left',
|
|
10
|
-
RIGHT = 'right',
|
|
11
|
-
TOP = 'top',
|
|
12
|
-
BOTTOM = 'bottom',
|
|
13
|
-
}
|
|
8
|
+
import { PositionContainer } from '@polpo/helpers';
|
|
14
9
|
|
|
15
|
-
type AsideModalProps =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
position?:
|
|
10
|
+
type AsideModalProps = Omit<
|
|
11
|
+
ModalProps,
|
|
12
|
+
'id' | 'animation' | 'closeAnimationClassName' | 'position' | 'rootStyle' | 'className' | 'style'
|
|
13
|
+
> & {
|
|
14
|
+
position?:
|
|
15
|
+
| `${PositionContainer.TOP}`
|
|
16
|
+
| `${PositionContainer.LEFT}`
|
|
17
|
+
| `${PositionContainer.RIGHT}`
|
|
18
|
+
| `${PositionContainer.BOTTOM}`;
|
|
20
19
|
size?: number | `${number}px` | `${number}em`;
|
|
21
20
|
className?: string;
|
|
22
21
|
style?: React.CSSProperties;
|
|
23
|
-
zIndex?: number;
|
|
24
22
|
};
|
|
25
23
|
|
|
26
24
|
export const AsideModal = ({
|
|
27
25
|
children,
|
|
28
26
|
isOpen,
|
|
29
27
|
onClose,
|
|
30
|
-
position =
|
|
28
|
+
position = PositionContainer.LEFT,
|
|
31
29
|
size,
|
|
32
30
|
className = '',
|
|
33
|
-
|
|
34
|
-
style = {},
|
|
31
|
+
...modalProps
|
|
35
32
|
}: AsideModalProps) => {
|
|
36
|
-
const
|
|
33
|
+
const modalRootStyles = useMemo<CSSProperties>(() => {
|
|
34
|
+
const computedSize = {
|
|
35
|
+
[PositionContainer.TOP]: { height: size, width: '100%' },
|
|
36
|
+
[PositionContainer.LEFT]: { height: '100%', width: size },
|
|
37
|
+
[PositionContainer.RIGHT]: { height: '100%', width: size },
|
|
38
|
+
[PositionContainer.BOTTOM]: { height: size, width: '100%' },
|
|
39
|
+
};
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
ref.current?.classList.add('close-animation');
|
|
41
|
-
setTimeout(() => {
|
|
42
|
-
callback();
|
|
43
|
-
ref.current?.classList.remove('close-animation');
|
|
44
|
-
}, 290);
|
|
45
|
-
}, []);
|
|
41
|
+
return computedSize[position];
|
|
42
|
+
}, [position, size]);
|
|
46
43
|
|
|
47
44
|
return (
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<
|
|
63
|
-
</
|
|
64
|
-
|
|
45
|
+
<AsideModalStyle
|
|
46
|
+
id='aside'
|
|
47
|
+
isOpen={isOpen}
|
|
48
|
+
onClose={onClose}
|
|
49
|
+
opacity={0.6}
|
|
50
|
+
windowOffset={0}
|
|
51
|
+
animation='none'
|
|
52
|
+
className={`${className} ${position}`}
|
|
53
|
+
rootStyle={modalRootStyles}
|
|
54
|
+
backdropOnClick={onClose}
|
|
55
|
+
position={position}
|
|
56
|
+
{...modalProps}
|
|
57
|
+
>
|
|
58
|
+
<span className='close-modal-button' onClick={onClose}>
|
|
59
|
+
<Icon name='cross' />
|
|
60
|
+
</span>
|
|
61
|
+
<section className='aside-modal-content'>{children}</section>
|
|
62
|
+
</AsideModalStyle>
|
|
65
63
|
);
|
|
66
64
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
1
|
import { Button } from '../../buttons';
|
|
4
2
|
import ActionModalStory from '../action-modal/action-modal.stories';
|
|
5
3
|
|
|
6
4
|
import { ConfirmationModal } from './confirmation-modal';
|
|
7
5
|
|
|
6
|
+
import { useModal } from '@polpo/hooks';
|
|
7
|
+
|
|
8
8
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
9
9
|
|
|
10
10
|
const meta: Meta<typeof ConfirmationModal> = {
|
|
@@ -26,20 +26,32 @@ const meta: Meta<typeof ConfirmationModal> = {
|
|
|
26
26
|
children: 'Are you sure want to execute an action?',
|
|
27
27
|
},
|
|
28
28
|
decorators: [
|
|
29
|
-
Story => {
|
|
30
|
-
const
|
|
29
|
+
(Story, { args }) => {
|
|
30
|
+
const { isOpen, openModal, closeModal } = useModal();
|
|
31
31
|
|
|
32
32
|
return (
|
|
33
33
|
<>
|
|
34
|
-
<Button onClick={
|
|
35
|
-
<Story
|
|
34
|
+
<Button onClick={openModal}>Open modal</Button>
|
|
35
|
+
<Story
|
|
36
|
+
args={{
|
|
37
|
+
...args,
|
|
38
|
+
isOpen,
|
|
39
|
+
onClose: closeModal,
|
|
40
|
+
onAccept: () =>
|
|
41
|
+
new Promise(resolve => {
|
|
42
|
+
setTimeout(resolve, 1000);
|
|
43
|
+
}),
|
|
44
|
+
|
|
45
|
+
onReject: () =>
|
|
46
|
+
new Promise(resolve => {
|
|
47
|
+
setTimeout(resolve, 1000);
|
|
48
|
+
}),
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
36
51
|
</>
|
|
37
52
|
);
|
|
38
53
|
},
|
|
39
54
|
],
|
|
40
|
-
render: (args, { isOpen, onClose }) => {
|
|
41
|
-
return <ConfirmationModal {...args} isOpen={isOpen} onClose={onClose} />;
|
|
42
|
-
},
|
|
43
55
|
};
|
|
44
56
|
|
|
45
57
|
export default meta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './menu';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Button } from '../../buttons';
|
|
2
|
+
|
|
3
|
+
import { Menu } from './menu';
|
|
4
|
+
|
|
5
|
+
import { PositionContainer } from '@polpo/helpers';
|
|
6
|
+
import { useModal } from '@polpo/hooks';
|
|
7
|
+
import { ModalBackdrop } from '@polpo/ui';
|
|
8
|
+
|
|
9
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof Menu> = {
|
|
12
|
+
title: 'Modals/Menu',
|
|
13
|
+
component: Menu,
|
|
14
|
+
argTypes: {
|
|
15
|
+
closeOnClickOutside: { control: 'boolean' },
|
|
16
|
+
transitionDuration: { control: false },
|
|
17
|
+
windowOffset: { control: { type: 'range', min: 0, max: 100 } },
|
|
18
|
+
position: { control: 'inline-radio', options: Object.values(PositionContainer) },
|
|
19
|
+
offset: { control: { type: 'range', min: 0, max: 100 } },
|
|
20
|
+
},
|
|
21
|
+
args: {
|
|
22
|
+
offset: 5,
|
|
23
|
+
windowOffset: 10,
|
|
24
|
+
children: 'Menu content',
|
|
25
|
+
position: PositionContainer.BOTTOM_RIGHT,
|
|
26
|
+
backdrop: ModalBackdrop.TRANSPARENT,
|
|
27
|
+
},
|
|
28
|
+
decorators: [
|
|
29
|
+
(Story, { args }) => {
|
|
30
|
+
const { openModal, closeModal, isOpen, containerRef } = useModal<HTMLButtonElement>();
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<Button width='full' leftIcon={isOpen ? 'door-open' : 'door-closed'} ref={containerRef} onClick={openModal}>
|
|
35
|
+
Menu
|
|
36
|
+
</Button>
|
|
37
|
+
<Story
|
|
38
|
+
args={{
|
|
39
|
+
...args,
|
|
40
|
+
isOpen,
|
|
41
|
+
onClose: closeModal,
|
|
42
|
+
containerRef,
|
|
43
|
+
children: (
|
|
44
|
+
<>
|
|
45
|
+
<Menu.Option asCheckbox icon='house' disabled label='Option 1' />
|
|
46
|
+
<Menu.Option asCheckbox icon='magnifying-glass' selected label='Option 2' />
|
|
47
|
+
<Menu.Option asCheckbox icon='document' label='Option 3' />
|
|
48
|
+
<Menu.Option asCheckbox icon='spinner' disabled selected label='Option 4' />
|
|
49
|
+
<Menu.Divider />
|
|
50
|
+
<Menu.Option icon='instagram' label='Option 5' />
|
|
51
|
+
<Menu.Option icon='airplane' disabled label='Option 6' />
|
|
52
|
+
<Menu.Option icon='whatsapp' disabled selected label='Option 7' />
|
|
53
|
+
<Menu.Option icon='order-list' label='Option 8' />
|
|
54
|
+
</>
|
|
55
|
+
),
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default meta;
|
|
65
|
+
type Story = StoryObj<typeof Menu>;
|
|
66
|
+
|
|
67
|
+
export const Default: Story = {
|
|
68
|
+
args: {},
|
|
69
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
import { Modal } from '@polpo/ui';
|
|
4
|
+
|
|
5
|
+
export const MenuModalStyle = styled(Modal)`
|
|
6
|
+
border-radius: 0.5em;
|
|
7
|
+
border: 1px solid ${props => props.theme.colors.border.main};
|
|
8
|
+
background: ${props => props.theme.colors.background.main};
|
|
9
|
+
box-shadow: 0 0 25px ${props => props.theme.colors.background.paper};
|
|
10
|
+
user-select: none;
|
|
11
|
+
|
|
12
|
+
.menu-content {
|
|
13
|
+
display: grid;
|
|
14
|
+
gap: 2px;
|
|
15
|
+
padding: 5px;
|
|
16
|
+
margin: 0;
|
|
17
|
+
list-style: none;
|
|
18
|
+
align-content: start;
|
|
19
|
+
height: 100%;
|
|
20
|
+
overflow-y: auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.divider {
|
|
24
|
+
margin: 0.4em 0;
|
|
25
|
+
color: ${props => props.theme.colors.border.main};
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
export const MenuOptionStyle = styled.li`
|
|
30
|
+
padding: 0.1em 0.5em;
|
|
31
|
+
border-radius: 0.3em;
|
|
32
|
+
border: 1px solid transparent;
|
|
33
|
+
transition: all 300ms ease;
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
outline: 0;
|
|
38
|
+
|
|
39
|
+
.option-icon {
|
|
40
|
+
margin-right: 0.5em;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.menu-checkbox {
|
|
44
|
+
width: 100%;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&.is-disabled {
|
|
48
|
+
opacity: 0.4;
|
|
49
|
+
pointer-events: none;
|
|
50
|
+
cursor: default;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&.is-selected,
|
|
54
|
+
&:hover {
|
|
55
|
+
background: ${props => props.theme.colors.background.paper};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&:focus,
|
|
59
|
+
&:hover {
|
|
60
|
+
border: 1px solid ${props => props.theme.colors.border.main};
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { MenuModalStyle, MenuOptionStyle } from './menu.style';
|
|
4
|
+
|
|
5
|
+
import { useClassNames } from '@polpo/hooks';
|
|
6
|
+
import {
|
|
7
|
+
Checkbox,
|
|
8
|
+
Icon,
|
|
9
|
+
IconNameT,
|
|
10
|
+
InfinityScroll,
|
|
11
|
+
InfinityScrollProps,
|
|
12
|
+
Line,
|
|
13
|
+
ModalProps,
|
|
14
|
+
Typography,
|
|
15
|
+
} from '@polpo/ui';
|
|
16
|
+
|
|
17
|
+
export type MenuOptionProps = {
|
|
18
|
+
id?: string;
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
label?: React.ReactNode;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
selected?: boolean;
|
|
23
|
+
className?: string;
|
|
24
|
+
style?: React.CSSProperties;
|
|
25
|
+
asCheckbox?: boolean;
|
|
26
|
+
icon?: IconNameT;
|
|
27
|
+
onClick?: (newValue: boolean) => void;
|
|
28
|
+
onKeyDown?: (event: React.KeyboardEvent) => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const MenuOption = ({
|
|
32
|
+
children,
|
|
33
|
+
label = '',
|
|
34
|
+
asCheckbox,
|
|
35
|
+
icon,
|
|
36
|
+
id,
|
|
37
|
+
disabled = false,
|
|
38
|
+
selected = false,
|
|
39
|
+
className = '',
|
|
40
|
+
style = {},
|
|
41
|
+
onClick = () => null,
|
|
42
|
+
onKeyDown = () => null,
|
|
43
|
+
}: MenuOptionProps) => {
|
|
44
|
+
const menuOptionClassName = useClassNames({
|
|
45
|
+
[className]: true,
|
|
46
|
+
'is-disabled': disabled,
|
|
47
|
+
'is-selected': selected,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const handleClick = useCallback(
|
|
51
|
+
(e: React.MouseEvent) => {
|
|
52
|
+
e.stopPropagation();
|
|
53
|
+
|
|
54
|
+
if (!disabled) {
|
|
55
|
+
onClick(!selected);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
[disabled, onClick, selected],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const menuOptionContent = useMemo(() => {
|
|
62
|
+
if (children) {
|
|
63
|
+
return children;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (asCheckbox) {
|
|
67
|
+
return (
|
|
68
|
+
<Checkbox
|
|
69
|
+
className='menu-checkbox'
|
|
70
|
+
disabled={disabled}
|
|
71
|
+
value={selected}
|
|
72
|
+
setValue={n => onClick(n)}
|
|
73
|
+
name='option'
|
|
74
|
+
label={
|
|
75
|
+
<>
|
|
76
|
+
{icon !== undefined && <Icon className='option-icon' name={icon} />}
|
|
77
|
+
<Typography variant='label'>{label}</Typography>
|
|
78
|
+
</>
|
|
79
|
+
}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
{icon !== undefined && <Icon className='option-icon' name={icon} />}
|
|
87
|
+
<Typography variant='label'>{label}</Typography>
|
|
88
|
+
</>
|
|
89
|
+
);
|
|
90
|
+
}, [asCheckbox, children, disabled, icon, label, onClick, selected]);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<MenuOptionStyle
|
|
94
|
+
id={id}
|
|
95
|
+
role='option'
|
|
96
|
+
tabIndex={-1}
|
|
97
|
+
aria-selected={selected}
|
|
98
|
+
aria-disabled={disabled}
|
|
99
|
+
onClick={handleClick}
|
|
100
|
+
onKeyDown={onKeyDown}
|
|
101
|
+
className={menuOptionClassName}
|
|
102
|
+
style={style}
|
|
103
|
+
>
|
|
104
|
+
{menuOptionContent}
|
|
105
|
+
</MenuOptionStyle>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
type MenuProps = ModalProps & {
|
|
110
|
+
children: React.ReactNode;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const Menu = ({ children, isOpen, onClose, id, ...modalProps }: MenuProps) => {
|
|
114
|
+
return (
|
|
115
|
+
<MenuModalStyle {...modalProps} id={`menu-${id}`} isOpen={isOpen} onClose={onClose}>
|
|
116
|
+
{children}
|
|
117
|
+
</MenuModalStyle>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
Menu.Option = MenuOption;
|
|
122
|
+
|
|
123
|
+
const MenuDivider = () => {
|
|
124
|
+
return <Line className='divider' />;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
Menu.Divider = MenuDivider;
|
|
128
|
+
|
|
129
|
+
type MenuOptionsGroupProps<T> = InfinityScrollProps<T> & {
|
|
130
|
+
className?: string;
|
|
131
|
+
style?: React.CSSProperties;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const MenuOptionsGroup = <T,>({ className = '', style = {}, ...infinityScrollProps }: MenuOptionsGroupProps<T>) => {
|
|
135
|
+
return (
|
|
136
|
+
<ul className={`menu-content ${className}`} role='listbox' style={style}>
|
|
137
|
+
<InfinityScroll {...infinityScrollProps} />
|
|
138
|
+
</ul>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
Menu.OptionsGroup = MenuOptionsGroup;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { useTheme } from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import { BackdropStyle } from './modal.style';
|
|
5
|
+
|
|
6
|
+
import { ModalState, useClassNames } from '@polpo/hooks';
|
|
7
|
+
|
|
8
|
+
export enum ModalBackdrop {
|
|
9
|
+
OPAQUE = 'opaque',
|
|
10
|
+
TRANSPARENT = 'transparent',
|
|
11
|
+
BLUR = 'blur',
|
|
12
|
+
NONE = 'none',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type BackdropProps = {
|
|
16
|
+
opacity?: number;
|
|
17
|
+
backdrop?: `${ModalBackdrop}`;
|
|
18
|
+
zIndex?: React.CSSProperties['zIndex'];
|
|
19
|
+
backdropOnClick?: () => void;
|
|
20
|
+
modalState?: ModalState;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const Backdrop = ({
|
|
24
|
+
opacity = 0.6,
|
|
25
|
+
backdrop = ModalBackdrop.BLUR,
|
|
26
|
+
zIndex,
|
|
27
|
+
backdropOnClick,
|
|
28
|
+
modalState,
|
|
29
|
+
}: BackdropProps) => {
|
|
30
|
+
const theme = useTheme();
|
|
31
|
+
const backdropClassName = useClassNames({
|
|
32
|
+
'backdrop-close': modalState === ModalState.CLOSING || modalState === ModalState.CLOSED,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const backgroundStyles = useMemo(() => {
|
|
36
|
+
const backdropStyles = {
|
|
37
|
+
[ModalBackdrop.OPAQUE]: {
|
|
38
|
+
background: `${theme.colors.background.paper}${(opacity * 255)?.toString(16)}`,
|
|
39
|
+
},
|
|
40
|
+
[ModalBackdrop.TRANSPARENT]: {
|
|
41
|
+
background: 'transparent',
|
|
42
|
+
},
|
|
43
|
+
[ModalBackdrop.BLUR]: {
|
|
44
|
+
background: `${theme.colors.background.paper}${(opacity * 255)?.toString(16)}`,
|
|
45
|
+
backdropFilter: 'blur(5px)',
|
|
46
|
+
},
|
|
47
|
+
[ModalBackdrop.NONE]: {
|
|
48
|
+
display: 'none',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return backdropStyles[backdrop] ?? {};
|
|
53
|
+
}, [backdrop, theme.colors.background.paper, opacity]);
|
|
54
|
+
|
|
55
|
+
if (backdrop === ModalBackdrop.NONE) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<BackdropStyle
|
|
61
|
+
tabIndex={-1}
|
|
62
|
+
onClick={backdropOnClick}
|
|
63
|
+
className={backdropClassName}
|
|
64
|
+
style={{
|
|
65
|
+
zIndex,
|
|
66
|
+
...backgroundStyles,
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
};
|