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.
Files changed (128) hide show
  1. package/.storybook/theme.ts +2 -2
  2. package/.turbo/turbo-build.log +0 -77
  3. package/.turbo/turbo-lint.log +1 -1
  4. package/README.md +2 -5
  5. package/dist/chunk-CFYQBHH5.js +3 -0
  6. package/dist/chunk-CFYQBHH5.js.map +1 -0
  7. package/dist/chunk-MAWW6AA7.js +3 -0
  8. package/dist/chunk-MAWW6AA7.js.map +1 -0
  9. package/dist/get-modal-position-drle0OjP.d.cts +49 -0
  10. package/dist/get-modal-position-drle0OjP.d.ts +49 -0
  11. package/dist/helpers.cjs +1 -1
  12. package/dist/helpers.cjs.map +1 -1
  13. package/dist/helpers.d.cts +9 -2
  14. package/dist/helpers.d.ts +9 -2
  15. package/dist/helpers.js +1 -1
  16. package/dist/hooks.cjs +1 -1
  17. package/dist/hooks.cjs.map +1 -1
  18. package/dist/hooks.d.cts +59 -21
  19. package/dist/hooks.d.ts +59 -21
  20. package/dist/hooks.js +1 -1
  21. package/dist/ui.cjs +601 -389
  22. package/dist/ui.cjs.map +1 -1
  23. package/dist/ui.d.cts +97 -77
  24. package/dist/ui.d.ts +97 -77
  25. package/dist/ui.js +585 -373
  26. package/dist/ui.js.map +1 -1
  27. package/dist/use-modal-in-container-DiNW1PE_.d.cts +34 -0
  28. package/dist/use-modal-in-container-neGo-kMk.d.ts +34 -0
  29. package/package.json +5 -5
  30. package/src/components/buttons/button/button.stories.tsx +4 -4
  31. package/src/components/buttons/button/button.style.ts +10 -5
  32. package/src/components/buttons/button/button.tsx +7 -19
  33. package/src/components/cards/flip-card/flip-card.tsx +1 -1
  34. package/src/components/cursor/cursor.stories.tsx +35 -0
  35. package/src/components/cursor/cursor.style.ts +73 -0
  36. package/src/components/cursor/cursor.tsx +49 -0
  37. package/src/components/cursor/index.ts +1 -0
  38. package/src/components/form/checkbox/checkbox.stories.tsx +51 -0
  39. package/src/components/form/checkbox/checkbox.style.ts +73 -37
  40. package/src/components/form/checkbox/checkbox.tsx +38 -4
  41. package/src/components/form/field/field.stories.tsx +5 -1
  42. package/src/components/form/field/field.style.ts +12 -0
  43. package/src/components/form/field/field.tsx +3 -1
  44. package/src/components/form/field/field.types.ts +6 -0
  45. package/src/components/form/input-color/input-color.style.ts +5 -4
  46. package/src/components/form/input-color/input-color.tsx +41 -44
  47. package/src/components/form/radio/radio.stories.tsx +29 -5
  48. package/src/components/form/radio/radio.style.ts +45 -24
  49. package/src/components/form/radio/radio.tsx +22 -3
  50. package/src/components/form/select/options.tsx +119 -67
  51. package/src/components/form/select/select.stories.tsx +103 -42
  52. package/src/components/form/select/select.style.ts +10 -92
  53. package/src/components/form/select/select.tsx +19 -42
  54. package/src/components/form/select/select.types.ts +4 -21
  55. package/src/components/form/slider/slider.style.ts +2 -0
  56. package/src/components/icon/icons/social.tsx +17 -1
  57. package/src/components/index.ts +1 -0
  58. package/src/components/infinity-scroll/infinity-scroll.tsx +1 -1
  59. package/src/components/line/line.stories.tsx +3 -4
  60. package/src/components/modals/action-modal/action-modal.stories.tsx +58 -39
  61. package/src/components/modals/action-modal/action-modal.style.ts +13 -25
  62. package/src/components/modals/action-modal/action-modal.tsx +68 -70
  63. package/src/components/modals/aside-modal/aside-modal.stories.tsx +11 -15
  64. package/src/components/modals/aside-modal/aside-modal.style.ts +17 -37
  65. package/src/components/modals/aside-modal/aside-modal.tsx +41 -43
  66. package/src/components/modals/confirmation-modal/confirmation-modal.stories.tsx +21 -9
  67. package/src/components/modals/index.ts +2 -0
  68. package/src/components/modals/menu/index.ts +1 -0
  69. package/src/components/modals/menu/menu.stories.tsx +69 -0
  70. package/src/components/modals/menu/menu.style.ts +62 -0
  71. package/src/components/modals/menu/menu.tsx +142 -0
  72. package/src/components/modals/modal/backdrop.tsx +70 -0
  73. package/src/components/modals/modal/index.ts +1 -0
  74. package/src/components/modals/modal/modal.stories.tsx +325 -0
  75. package/src/components/modals/modal/modal.style.ts +62 -2
  76. package/src/components/modals/modal/modal.tsx +82 -123
  77. package/src/components/modals/portal/index.ts +1 -0
  78. package/src/components/modals/portal/portal.tsx +18 -0
  79. package/src/components/tabs/tabs-list.tsx +13 -10
  80. package/src/components/tabs/tabs.style.ts +48 -43
  81. package/src/components/tag/tag.stories.tsx +11 -12
  82. package/src/components/tag/tag.style.ts +9 -4
  83. package/src/components/tag/tag.tsx +2 -12
  84. package/src/components/tooltips/tooltip/tooltip.stories.tsx +5 -2
  85. package/src/components/tooltips/tooltip/tooltip.style.ts +37 -6
  86. package/src/components/tooltips/tooltip/tooltip.tsx +33 -19
  87. package/src/components/typography/typography.stories.tsx +3 -1
  88. package/src/components/typography/typography.tsx +21 -0
  89. package/src/contexts/theme-context/theme.animations.ts +91 -2
  90. package/src/contexts/theme-context/theme.defaults.ts +1 -1
  91. package/src/core/http-client.ts +49 -47
  92. package/src/core/variants/color.ts +3 -30
  93. package/src/core/variants/radius.ts +12 -41
  94. package/src/core/variants/size.ts +8 -33
  95. package/src/helpers/get-modal-position-relative-to-screen.ts +86 -0
  96. package/src/helpers/get-modal-position.ts +173 -28
  97. package/src/helpers/index.ts +1 -0
  98. package/src/hooks/index.ts +9 -3
  99. package/src/hooks/use-click-outside.ts +32 -0
  100. package/src/hooks/use-cookie.ts +124 -0
  101. package/src/hooks/use-dimensions.ts +11 -14
  102. package/src/hooks/use-dom-container.ts +32 -0
  103. package/src/hooks/use-event-listener.ts +4 -4
  104. package/src/hooks/use-geolocation.ts +63 -0
  105. package/src/hooks/use-in-view.ts +9 -11
  106. package/src/hooks/use-intersection-observer.ts +19 -0
  107. package/src/hooks/use-modal-in-container.ts +60 -52
  108. package/src/hooks/use-modal-transition.ts +54 -0
  109. package/src/hooks/use-modal.ts +21 -0
  110. package/src/hooks/use-mouse-position.ts +55 -7
  111. package/src/hooks/use-resize-observer.ts +18 -0
  112. package/src/stories/GettingStarted.mdx +2 -6
  113. package/svg/Name=npm, Category=social.svg +3 -0
  114. package/tsconfig.json +1 -0
  115. package/vite.config.ts +1 -0
  116. package/.turbo/daemon/f5c5c8fb195b01d0-turbo.log.2024-05-26 +0 -0
  117. package/.turbo/turbo-build$colon$watch.log +0 -96
  118. package/.turbo/turbo-build-storybook.log +0 -0
  119. package/.turbo/turbo-lint$colon$fix.log +0 -2
  120. package/dist/chunk-M4KRSYE7.js +0 -3
  121. package/dist/chunk-M4KRSYE7.js.map +0 -1
  122. package/dist/chunk-U5XSMSKZ.js +0 -3
  123. package/dist/chunk-U5XSMSKZ.js.map +0 -1
  124. package/dist/get-modal-position-DPftPoU2.d.cts +0 -28
  125. package/dist/get-modal-position-DPftPoU2.d.ts +0 -28
  126. package/src/components/form/select/select-option.tsx +0 -84
  127. package/src/hooks/use-observer.ts +0 -18
  128. 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
- export const AsideModalStyle = styled.section`
4
- position: absolute;
5
- background: ${props => props.theme.colors.background.main};
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
- &.open-animation {
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
- &.open-animation {
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
- &.open-animation {
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
- &.open-animation {
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 { useCallback, useRef } from 'react';
1
+ import { CSSProperties, useMemo } from 'react';
2
2
 
3
3
  import { Icon } from '../../icon';
4
- import { Modal } from '../modal';
4
+ import { ModalProps } from '../modal';
5
5
 
6
6
  import { AsideModalStyle } from './aside-modal.style';
7
7
 
8
- export enum AsidePosition {
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
- children: React.ReactNode;
17
- isOpen: boolean;
18
- onClose: () => void;
19
- position?: `${AsidePosition}`;
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 = AsidePosition.LEFT,
28
+ position = PositionContainer.LEFT,
31
29
  size,
32
30
  className = '',
33
- zIndex = 100,
34
- style = {},
31
+ ...modalProps
35
32
  }: AsideModalProps) => {
36
- const ref = useRef<HTMLElement>(null);
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
- const handleOnClose = useCallback((callback: () => void) => {
39
- ref.current?.classList.remove('open-animation');
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
- <Modal id='aside' isOpen={isOpen} opacity={0.6} onClick={() => handleOnClose(onClose)} zIndex={100}>
49
- <AsideModalStyle
50
- className={`open-animation ${position} ${className}`}
51
- ref={ref}
52
- style={{
53
- ...style,
54
- zIndex,
55
- width: position === 'left' || position === 'right' ? size : undefined,
56
- height: position === 'top' || position === 'bottom' ? size : undefined,
57
- }}
58
- >
59
- <span className='close-modal-button' onClick={() => handleOnClose(onClose)}>
60
- <Icon name='cross' />
61
- </span>
62
- <section className='aside-modal-content'>{children}</section>
63
- </AsideModalStyle>
64
- </Modal>
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 [isOpen, setIsOpen] = useState(false);
29
+ (Story, { args }) => {
30
+ const { isOpen, openModal, closeModal } = useModal();
31
31
 
32
32
  return (
33
33
  <>
34
- <Button onClick={() => setIsOpen(true)}>Open modal</Button>
35
- <Story isOpen={isOpen} onClose={() => setIsOpen(false)} />
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;
@@ -2,3 +2,5 @@ export * from './action-modal';
2
2
  export * from './aside-modal';
3
3
  export * from './confirmation-modal';
4
4
  export * from './modal';
5
+ export * from './menu';
6
+ export * from './portal';
@@ -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
+ };
@@ -1 +1,2 @@
1
1
  export * from './modal';
2
+ export * from './backdrop';