polpo 0.1.13 → 0.1.16

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 (204) hide show
  1. package/.turbo/cache/eslint/.cache_16wwsjl +1 -0
  2. package/.turbo/cache/eslint/.cache_rpznjo +1 -0
  3. package/.turbo/turbo-build$colon$code.log +49 -0
  4. package/.turbo/turbo-build$colon$css.log +2 -0
  5. package/.turbo/turbo-build.log +43 -0
  6. package/.turbo/turbo-lint.log +2 -0
  7. package/dist/components.cjs +1 -3655
  8. package/dist/components.css +2 -2173
  9. package/dist/components.js +1 -3576
  10. package/dist/helpers.cjs +1 -311
  11. package/dist/helpers.d.cts +4 -1
  12. package/dist/helpers.d.ts +4 -1
  13. package/dist/helpers.js +1 -276
  14. package/dist/hooks.cjs +1 -1065
  15. package/dist/hooks.js +1 -998
  16. package/dist/layouts.cjs +1 -146
  17. package/dist/layouts.css +2 -5
  18. package/dist/layouts.js +1 -119
  19. package/dist/types.cjs +1 -19
  20. package/dist/types.js +0 -1
  21. package/eslint.config.js +4 -0
  22. package/package.json +16 -11
  23. package/postcss.config.js +3 -0
  24. package/src/components/accordion/accordion-item.tsx +123 -0
  25. package/src/components/accordion/accordion.component.tsx +80 -0
  26. package/src/components/accordion/accordion.styles.css +41 -0
  27. package/src/components/accordion/index.ts +2 -0
  28. package/src/components/button/button.component.tsx +50 -0
  29. package/src/components/button/button.styles.css +317 -0
  30. package/src/components/button/index.ts +1 -0
  31. package/src/components/cards/flip-card/flip-card.component.tsx +66 -0
  32. package/src/components/cards/flip-card/flip-card.styles.css +50 -0
  33. package/src/components/cards/flip-card/index.ts +1 -0
  34. package/src/components/cards/hover-card/hover-card.component.tsx +76 -0
  35. package/src/components/cards/hover-card/hover-card.styles.css +13 -0
  36. package/src/components/cards/hover-card/index.ts +1 -0
  37. package/src/components/cards/index.ts +3 -0
  38. package/src/components/cards/slide-card/index.ts +1 -0
  39. package/src/components/cards/slide-card/slide-card.component.tsx +27 -0
  40. package/src/components/component.types.ts +31 -0
  41. package/src/components/cursor/cursor.component.tsx +49 -0
  42. package/src/components/cursor/cursor.styles.css +73 -0
  43. package/src/components/cursor/index.ts +1 -0
  44. package/src/components/form/checkbox/checkbox.component.tsx +95 -0
  45. package/src/components/form/checkbox/checkbox.styles.css +162 -0
  46. package/src/components/form/checkbox/index.ts +1 -0
  47. package/src/components/form/controller/controller.component.tsx +42 -0
  48. package/src/components/form/controller/index.ts +1 -0
  49. package/src/components/form/date-picker/date-picker.component.tsx +66 -0
  50. package/src/components/form/date-picker/index.ts +1 -0
  51. package/src/components/form/field/field.component.tsx +84 -0
  52. package/src/components/form/field/field.styles.css +98 -0
  53. package/src/components/form/field/field.types.ts +34 -0
  54. package/src/components/form/field/index.ts +2 -0
  55. package/src/components/form/form.types.ts +37 -0
  56. package/src/components/form/index.ts +14 -0
  57. package/src/components/form/input/index.ts +1 -0
  58. package/src/components/form/input/input.component.tsx +75 -0
  59. package/src/components/form/input-color/index.ts +1 -0
  60. package/src/components/form/input-color/input-color.component.tsx +170 -0
  61. package/src/components/form/input-color/input-color.styles.css +92 -0
  62. package/src/components/form/input-file/index.ts +1 -0
  63. package/src/components/form/input-file/input-file.component.tsx +221 -0
  64. package/src/components/form/input-file/input-file.styles.css +143 -0
  65. package/src/components/form/input-number/index.ts +1 -0
  66. package/src/components/form/input-number/input-number.component.tsx +144 -0
  67. package/src/components/form/input-password/index.ts +1 -0
  68. package/src/components/form/input-password/input-password.component.tsx +77 -0
  69. package/src/components/form/radio/index.ts +1 -0
  70. package/src/components/form/radio/radio.component.tsx +92 -0
  71. package/src/components/form/radio/radio.styles.css +117 -0
  72. package/src/components/form/select/index.ts +1 -0
  73. package/src/components/form/select/option.tsx +101 -0
  74. package/src/components/form/select/options.tsx +165 -0
  75. package/src/components/form/select/select.component.tsx +317 -0
  76. package/src/components/form/select/select.styles.css +115 -0
  77. package/src/components/form/select/select.types.ts +97 -0
  78. package/src/components/form/slider/index.ts +1 -0
  79. package/src/components/form/slider/slider.component.tsx +117 -0
  80. package/src/components/form/slider/slider.styles.css +94 -0
  81. package/src/components/form/switch/index.ts +1 -0
  82. package/src/components/form/switch/switch.component.tsx +166 -0
  83. package/src/components/form/switch/switch.styles.css +165 -0
  84. package/src/components/form/textarea/index.ts +1 -0
  85. package/src/components/form/textarea/textarea.component.tsx +80 -0
  86. package/src/components/form/textarea/textarea.styles.css +7 -0
  87. package/src/components/image/image.tsx +7 -0
  88. package/src/components/image/index.ts +1 -0
  89. package/src/components/index.ts +15 -0
  90. package/src/components/infinity-scroll/index.ts +1 -0
  91. package/src/components/infinity-scroll/infinity-scroll.component.tsx +51 -0
  92. package/src/components/infinity-scroll/infinity-scroll.styles.css +25 -0
  93. package/src/components/line/index.ts +1 -0
  94. package/src/components/line/line.component.tsx +77 -0
  95. package/src/components/line/line.styles.css +61 -0
  96. package/src/components/modals/action-modal/action-modal.component.tsx +150 -0
  97. package/src/components/modals/action-modal/action-modal.styles.css +115 -0
  98. package/src/components/modals/action-modal/index.ts +1 -0
  99. package/src/components/modals/aside-modal/aside-modal.component.tsx +63 -0
  100. package/src/components/modals/aside-modal/aside-modal.styles.css +86 -0
  101. package/src/components/modals/aside-modal/index.ts +1 -0
  102. package/src/components/modals/confirmation-modal/confirmation-modal.component.tsx +43 -0
  103. package/src/components/modals/confirmation-modal/confirmation-modal.styles.css +17 -0
  104. package/src/components/modals/confirmation-modal/index.ts +1 -0
  105. package/src/components/modals/index.ts +6 -0
  106. package/src/components/modals/menu/index.ts +1 -0
  107. package/src/components/modals/menu/menu.component.tsx +194 -0
  108. package/src/components/modals/menu/menu.styles.css +101 -0
  109. package/src/components/modals/modal/index.ts +2 -0
  110. package/src/components/modals/modal/modal.backdrop.tsx +67 -0
  111. package/src/components/modals/modal/modal.component.tsx +104 -0
  112. package/src/components/modals/modal/modal.styles.css +70 -0
  113. package/src/components/modals/portal/index.ts +1 -0
  114. package/src/components/modals/portal/portal.component.tsx +19 -0
  115. package/src/components/ripple/index.ts +1 -0
  116. package/src/components/ripple/ripple.component.tsx +70 -0
  117. package/src/components/ripple/ripple.styles.css +33 -0
  118. package/src/components/smart-table/index.ts +2 -0
  119. package/src/components/smart-table/smart-table.column.tsx +62 -0
  120. package/src/components/smart-table/smart-table.component.tsx +116 -0
  121. package/src/components/smart-table/smart-table.helpers.tsx +58 -0
  122. package/src/components/smart-table/smart-table.hooks.ts +27 -0
  123. package/src/components/smart-table/smart-table.row.tsx +28 -0
  124. package/src/components/smart-table/smart-table.styles.css +102 -0
  125. package/src/components/smart-table/smart-table.types.ts +42 -0
  126. package/src/components/tabs/index.ts +1 -0
  127. package/src/components/tabs/tabs-list.tsx +120 -0
  128. package/src/components/tabs/tabs.styles.css +164 -0
  129. package/src/components/tabs/tabs.tsx +119 -0
  130. package/src/components/tag/index.ts +1 -0
  131. package/src/components/tag/tag.component.tsx +34 -0
  132. package/src/components/tag/tag.styles.css +50 -0
  133. package/src/components/tooltips/click-to-copy/click-to-copy.component.tsx +41 -0
  134. package/src/components/tooltips/click-to-copy/index.ts +1 -0
  135. package/src/components/tooltips/index.ts +2 -0
  136. package/src/components/tooltips/tooltip/index.ts +1 -0
  137. package/src/components/tooltips/tooltip/tooltip.component.tsx +64 -0
  138. package/src/components/tooltips/tooltip/tooltip.styles.css +91 -0
  139. package/src/components/typography/index.ts +1 -0
  140. package/src/components/typography/typography.component.tsx +81 -0
  141. package/src/components/typography/typography.constants.ts +53 -0
  142. package/src/components/typography/typography.styles.css +122 -0
  143. package/src/helpers/cn.ts +6 -0
  144. package/src/helpers/format-bytes.ts +11 -0
  145. package/src/helpers/format-dates.ts +47 -0
  146. package/src/helpers/get-modal-position-relative-to-screen.ts +86 -0
  147. package/src/helpers/get-modal-position.ts +211 -0
  148. package/src/helpers/index.ts +6 -0
  149. package/src/helpers/text/index.ts +1 -0
  150. package/src/helpers/text/to-capitalize.ts +17 -0
  151. package/src/hooks/index.ts +30 -0
  152. package/src/hooks/use-async.ts +88 -0
  153. package/src/hooks/use-classnames.ts +13 -0
  154. package/src/hooks/use-click-outside.ts +32 -0
  155. package/src/hooks/use-constant.ts +3 -0
  156. package/src/hooks/use-cookie.ts +124 -0
  157. package/src/hooks/use-debounce-state.ts +13 -0
  158. package/src/hooks/use-debounce.ts +15 -0
  159. package/src/hooks/use-dimensions.ts +19 -0
  160. package/src/hooks/use-dom-container.ts +35 -0
  161. package/src/hooks/use-event-listener.ts +71 -0
  162. package/src/hooks/use-file-reader.ts +69 -0
  163. package/src/hooks/use-geolocation.ts +63 -0
  164. package/src/hooks/use-hover.ts +17 -0
  165. package/src/hooks/use-in-view.ts +18 -0
  166. package/src/hooks/use-input-handlers.ts +52 -0
  167. package/src/hooks/use-intersection-observer.ts +19 -0
  168. package/src/hooks/use-media-query.ts +25 -0
  169. package/src/hooks/use-modal-in-container.ts +85 -0
  170. package/src/hooks/use-modal-transition.ts +64 -0
  171. package/src/hooks/use-modal.ts +21 -0
  172. package/src/hooks/use-mouse-position.ts +64 -0
  173. package/src/hooks/use-online-status.ts +12 -0
  174. package/src/hooks/use-render-count.ts +11 -0
  175. package/src/hooks/use-resize-observer.ts +18 -0
  176. package/src/hooks/use-safe-dispatch.ts +22 -0
  177. package/src/hooks/use-scroll.ts +31 -0
  178. package/src/hooks/use-state-history.ts +22 -0
  179. package/src/hooks/use-toggle-values.ts +14 -0
  180. package/src/hooks/use-toggle.ts +11 -0
  181. package/src/hooks/use-viewport.ts +38 -0
  182. package/src/index.ts +5 -0
  183. package/src/layouts/flex/flex.component.tsx +75 -0
  184. package/src/layouts/flex/index.ts +1 -0
  185. package/src/layouts/grid/grid.component.tsx +89 -0
  186. package/src/layouts/grid/grid.styles.css +5 -0
  187. package/src/layouts/grid/index.ts +1 -0
  188. package/src/layouts/index.ts +2 -0
  189. package/src/types/generics.ts +68 -0
  190. package/src/types/index.ts +1 -0
  191. package/tsconfig.json +10 -0
  192. package/tsup.config.cjs +36 -0
  193. package/dist/components.cjs.map +0 -1
  194. package/dist/components.css.map +0 -1
  195. package/dist/components.js.map +0 -1
  196. package/dist/helpers.cjs.map +0 -1
  197. package/dist/helpers.js.map +0 -1
  198. package/dist/hooks.cjs.map +0 -1
  199. package/dist/hooks.js.map +0 -1
  200. package/dist/layouts.cjs.map +0 -1
  201. package/dist/layouts.css.map +0 -1
  202. package/dist/layouts.js.map +0 -1
  203. package/dist/types.cjs.map +0 -1
  204. package/dist/types.js.map +0 -1
@@ -0,0 +1 @@
1
+ export * from './confirmation-modal.component';
@@ -0,0 +1,6 @@
1
+ export * from './action-modal';
2
+ export * from './aside-modal';
3
+ export * from './confirmation-modal';
4
+ export * from './menu';
5
+ export * from './modal';
6
+ export * from './portal';
@@ -0,0 +1 @@
1
+ export * from './menu.component';
@@ -0,0 +1,194 @@
1
+ import React, { RefObject, useCallback, useMemo } from 'react';
2
+ import { IconType } from 'react-icons';
3
+
4
+ import { useClassNames } from '../../../hooks';
5
+ import { Checkbox } from '../../form';
6
+ import { Line } from '../../line';
7
+ import { Ripple } from '../../ripple';
8
+ import { Typography, TypographyProps } from '../../typography';
9
+ import { Modal, ModalProps } from '../modal';
10
+
11
+ import './menu.styles.css';
12
+
13
+ type MenuProps = ModalProps & {
14
+ children: React.ReactNode;
15
+ contentClassName?: string;
16
+ contentStyle?: React.CSSProperties;
17
+ menuContentRef?: RefObject<HTMLUListElement>;
18
+ };
19
+
20
+ export const Menu = ({
21
+ children,
22
+ isOpen,
23
+ onClose,
24
+ id,
25
+ menuContentRef,
26
+ contentClassName = '',
27
+ contentStyle = {},
28
+ className = '',
29
+ ...modalProps
30
+ }: MenuProps) => {
31
+ const modalClassName = useClassNames({
32
+ 'menu-modal': true,
33
+ [className]: Boolean(className),
34
+ });
35
+
36
+ return (
37
+ <Modal className={modalClassName} {...modalProps} id={`menu-${id}`} isOpen={isOpen} onClose={onClose}>
38
+ <ul className={`menu-content ${contentClassName}`} role='listbox' style={contentStyle} ref={menuContentRef}>
39
+ {children}
40
+ </ul>
41
+ </Modal>
42
+ );
43
+ };
44
+
45
+ export type MenuOptionProps = Omit<React.HTMLAttributes<HTMLLIElement>, 'onClick'> & {
46
+ id?: string;
47
+ children?: React.ReactNode;
48
+ label?: React.ReactNode;
49
+ disabled?: boolean;
50
+ selected?: boolean;
51
+ className?: string;
52
+ style?: React.CSSProperties;
53
+ asCheckbox?: boolean;
54
+ icon?: IconType;
55
+ onClick?: (newValue: boolean) => void;
56
+ };
57
+
58
+ const MenuOption = ({
59
+ children,
60
+ label = '',
61
+ asCheckbox,
62
+ icon: Icon,
63
+ id,
64
+ disabled = false,
65
+ selected = false,
66
+ className = '',
67
+ style = {},
68
+ onClick = () => null,
69
+ ...liProps
70
+ }: MenuOptionProps) => {
71
+ const menuOptionClassName = useClassNames({
72
+ 'menu-option': true,
73
+ [className]: true,
74
+ 'is-disabled': disabled,
75
+ 'is-selected': selected,
76
+ });
77
+
78
+ const handleClick = useCallback(
79
+ (e: React.MouseEvent) => {
80
+ e.stopPropagation();
81
+
82
+ if (!disabled) {
83
+ onClick(!selected);
84
+ }
85
+ },
86
+ [disabled, onClick, selected],
87
+ );
88
+
89
+ const menuOptionContent = useMemo(() => {
90
+ if (children) {
91
+ return children;
92
+ }
93
+
94
+ if (asCheckbox) {
95
+ return (
96
+ <Checkbox
97
+ className='menu-checkbox'
98
+ disabled={disabled}
99
+ value={selected}
100
+ setValue={n => onClick(n)}
101
+ name='option'
102
+ style={{ pointerEvents: 'none' }}
103
+ size='small'
104
+ label={
105
+ <>
106
+ {Icon !== undefined && <Icon className='option-icon' />}
107
+ <Typography variant='label'>{label}</Typography>
108
+ </>
109
+ }
110
+ />
111
+ );
112
+ }
113
+
114
+ return (
115
+ <>
116
+ {Icon !== undefined && <Icon className='option-icon' />}
117
+ {typeof label === 'string' ? <Typography variant='label'>{label}</Typography> : label}
118
+ </>
119
+ );
120
+ }, [asCheckbox, children, disabled, Icon, label, onClick, selected]);
121
+
122
+ return (
123
+ <li
124
+ {...liProps}
125
+ id={id}
126
+ role='option'
127
+ tabIndex={-1}
128
+ aria-selected={selected}
129
+ aria-disabled={disabled}
130
+ onClick={handleClick}
131
+ className={menuOptionClassName}
132
+ style={style}
133
+ >
134
+ <Ripple zIndex={10} />
135
+ {menuOptionContent}
136
+ </li>
137
+ );
138
+ };
139
+
140
+ const Divider = () => {
141
+ return (
142
+ <li>
143
+ <Line className='divider' />
144
+ </li>
145
+ );
146
+ };
147
+
148
+ const Label = ({ children, className = '', ...props }: Omit<TypographyProps, 'variant'>) => {
149
+ return (
150
+ <li tabIndex={-1} className='menu-label'>
151
+ <Typography {...props} variant='small' className={`menu-group-label ${className}`}>
152
+ {children}
153
+ </Typography>
154
+ </li>
155
+ );
156
+ };
157
+
158
+ type MenuGroupProps = Omit<React.HTMLAttributes<HTMLLIElement>, 'onClick'> & {
159
+ children: React.ReactNode;
160
+ label: React.ReactNode;
161
+ className?: string;
162
+ style?: React.CSSProperties;
163
+ contentClassName?: string;
164
+ contentStyle?: React.CSSProperties;
165
+ };
166
+
167
+ const Group = ({
168
+ children,
169
+ label,
170
+ className = '',
171
+ style = {},
172
+ contentClassName = '',
173
+ contentStyle = {},
174
+ ...props
175
+ }: MenuGroupProps) => {
176
+ const groupClassName = useClassNames({
177
+ 'menu-group': true,
178
+ [className]: Boolean(className),
179
+ });
180
+
181
+ return (
182
+ <li tabIndex={-1} {...props} className={groupClassName} style={style}>
183
+ {label && <Label>{label}</Label>}
184
+ <ul className={`menu-group-content ${contentClassName}`} role='listbox' style={contentStyle}>
185
+ {children}
186
+ </ul>
187
+ </li>
188
+ );
189
+ };
190
+
191
+ Menu.Option = MenuOption;
192
+ Menu.Divider = Divider;
193
+ Menu.GroupLabel = Label;
194
+ Menu.Group = Group;
@@ -0,0 +1,101 @@
1
+ @reference "polpo-tailwind-config/styles";
2
+
3
+ .menu-modal {
4
+ border-radius: 0.5em;
5
+ border: 1px solid var(--color-border);
6
+ background: var(--color-background);
7
+ box-shadow: 0 0 25px var(--color-background-paper);
8
+ user-select: none;
9
+ position: relative;
10
+ overflow-y: auto;
11
+
12
+ .menu-content {
13
+ display: grid;
14
+ gap: 0.2em;
15
+ padding: 0;
16
+ margin: 0;
17
+ list-style: none;
18
+ align-content: start;
19
+ height: 100%;
20
+ position: relative;
21
+ overflow-y: auto;
22
+ }
23
+
24
+ .divider {
25
+ margin: 0.4em 0;
26
+ color: var(--color-border);
27
+ }
28
+ }
29
+
30
+ .menu-label {
31
+ background: var(--color-background);
32
+ color: var(--color-foreground);
33
+ font-weight: lighter;
34
+ padding: 0.5em 1.2em;
35
+ position: sticky;
36
+ top: 0;
37
+ z-index: 10;
38
+ pointer-events: none;
39
+ }
40
+
41
+ .menu-option {
42
+ padding: 0.2em 1em;
43
+ border-radius: 0.3em;
44
+ transition: all 300ms ease;
45
+ cursor: pointer;
46
+ display: flex;
47
+ align-items: center;
48
+ outline: 0;
49
+ margin: 0 0.2em;
50
+ position: relative;
51
+
52
+ .ripple-effect {
53
+ background: hsl(from var(--color-foreground) h s l / 31%);
54
+ }
55
+
56
+ &:first-child {
57
+ margin-top: 5px;
58
+ }
59
+
60
+ .option-icon {
61
+ margin-right: 0.5em;
62
+ }
63
+
64
+ .menu-checkbox {
65
+ width: 100%;
66
+ }
67
+
68
+ &.is-disabled {
69
+ opacity: 0.4;
70
+ pointer-events: none;
71
+ cursor: default;
72
+ }
73
+
74
+ &:focus,
75
+ &:hover {
76
+ background: hsl(from var(--color-background-paper) h s l / 25%);
77
+ }
78
+
79
+ &.is-selected {
80
+ background: var(--color-background-paper);
81
+
82
+ &:focus,
83
+ &:hover {
84
+ background: hsl(from var(--color-background-paper) h s l / 75%);
85
+ }
86
+ }
87
+ }
88
+
89
+ .menu-group {
90
+ position: relative;
91
+
92
+ .menu-group-content {
93
+ display: grid;
94
+ gap: 0.2em;
95
+ padding: 0;
96
+ margin: 0;
97
+ list-style: none;
98
+ align-content: start;
99
+ position: relative;
100
+ }
101
+ }
@@ -0,0 +1,2 @@
1
+ export * from './modal.component';
2
+ export * from './modal.backdrop';
@@ -0,0 +1,67 @@
1
+ import React, { useMemo } from 'react';
2
+
3
+ import { ModalState, useClassNames } from '../../../hooks';
4
+
5
+ export enum ModalBackdrop {
6
+ OPAQUE = 'opaque',
7
+ TRANSPARENT = 'transparent',
8
+ BLUR = 'blur',
9
+ NONE = 'none',
10
+ }
11
+
12
+ export type BackdropProps = {
13
+ opacity?: number;
14
+ backdrop?: `${ModalBackdrop}`;
15
+ zIndex?: React.CSSProperties['zIndex'];
16
+ backdropOnClick?: () => void;
17
+ modalState?: ModalState;
18
+ };
19
+
20
+ export const Backdrop = ({
21
+ opacity = 0.6,
22
+ backdrop = ModalBackdrop.BLUR,
23
+ zIndex,
24
+ backdropOnClick,
25
+ modalState,
26
+ }: BackdropProps) => {
27
+ const backdropClassName = useClassNames({
28
+ 'modal-backdrop': true,
29
+ 'backdrop-close': modalState === ModalState.CLOSING || modalState === ModalState.CLOSED,
30
+ });
31
+
32
+ const backgroundStyles = useMemo(() => {
33
+ const backdropStyles = {
34
+ [ModalBackdrop.OPAQUE]: {
35
+ background: `hsl(from var(--color-background-paper) h s l / ${opacity * 100}%)`,
36
+ },
37
+ [ModalBackdrop.TRANSPARENT]: {
38
+ background: 'transparent',
39
+ },
40
+ [ModalBackdrop.BLUR]: {
41
+ background: `hsl(from var(--color-background-paper) h s l / ${opacity * 100}%)`,
42
+ backdropFilter: 'blur(5px)',
43
+ },
44
+ [ModalBackdrop.NONE]: {
45
+ display: 'none',
46
+ },
47
+ };
48
+
49
+ return backdropStyles[backdrop] ?? {};
50
+ }, [backdrop, opacity]);
51
+
52
+ if (backdrop === ModalBackdrop.NONE) {
53
+ return null;
54
+ }
55
+
56
+ return (
57
+ <section
58
+ tabIndex={-1}
59
+ onClick={backdropOnClick}
60
+ className={backdropClassName}
61
+ style={{
62
+ zIndex,
63
+ ...backgroundStyles,
64
+ }}
65
+ />
66
+ );
67
+ };
@@ -0,0 +1,104 @@
1
+ import React, { CSSProperties, useMemo, useRef } from 'react';
2
+
3
+ import {
4
+ ModalState,
5
+ useClassNames,
6
+ useClickOutside,
7
+ useModalInContainer,
8
+ UseModalInContainerParams,
9
+ useModalTransition,
10
+ UseModalTransitionParams,
11
+ } from '../../../hooks';
12
+ import { Portal } from '../portal';
13
+
14
+ import { Backdrop, BackdropProps } from './modal.backdrop';
15
+ import './modal.styles.css';
16
+
17
+ export type ModalProps = Omit<BackdropProps, 'modalState'> &
18
+ UseModalTransitionParams &
19
+ Omit<UseModalInContainerParams, 'modalRef'> & {
20
+ id: string;
21
+ children: React.ReactNode;
22
+ onClose: () => void;
23
+ className?: string;
24
+ style?: React.CSSProperties;
25
+ rootStyle?: CSSProperties;
26
+ animation?: 'none' | 'fade-down' | 'bounce';
27
+ closeAnimationClassName?: string;
28
+ modalRef?: React.RefObject<HTMLElement | null>;
29
+ closeOnClickOutside?: boolean;
30
+ };
31
+
32
+ export const Modal = ({
33
+ id,
34
+ children,
35
+ isOpen,
36
+ onClose,
37
+ className = '',
38
+ style = {},
39
+ rootStyle = {},
40
+ animation = 'fade-down',
41
+ closeAnimationClassName = 'modal-close',
42
+ modalRef: modalRefProp,
43
+ closeOnClickOutside = true,
44
+ transitionDuration = 300,
45
+ windowOffset = 10,
46
+ offset = 20,
47
+ position,
48
+ containerRef,
49
+ zIndex = 1000,
50
+ ...backdropProps
51
+ }: ModalProps) => {
52
+ const uuid = useMemo(() => crypto.randomUUID(), []);
53
+ const modalRef = useRef<HTMLElement>(null);
54
+ const { modalState, isVisible } = useModalTransition({
55
+ transitionDuration,
56
+ isOpen,
57
+ });
58
+
59
+ useModalInContainer({
60
+ modalRef: modalRefProp ?? modalRef,
61
+ containerRef,
62
+ offset,
63
+ windowOffset,
64
+ position,
65
+ isOpen: isVisible,
66
+ });
67
+
68
+ useClickOutside<HTMLElement>(modalRefProp ?? modalRef, () => {
69
+ if (isOpen && closeOnClickOutside) {
70
+ onClose();
71
+ }
72
+ });
73
+
74
+ const modalContentClassName = useClassNames({
75
+ 'modal-content': true,
76
+ [className]: Boolean(className),
77
+ [`animation-${animation}`]: Boolean(animation) && animation !== 'none',
78
+ [closeAnimationClassName]: modalState === ModalState.CLOSING || modalState === ModalState.CLOSED,
79
+ });
80
+
81
+ if (!isVisible) {
82
+ return null;
83
+ }
84
+
85
+ return (
86
+ <Portal id={`modal-${id}-${uuid}`}>
87
+ <Backdrop {...backdropProps} modalState={modalState} zIndex={zIndex} />
88
+ <section
89
+ className='modal'
90
+ ref={modalRefProp ?? modalRef}
91
+ style={{
92
+ maxWidth: `calc(100dvw - ${windowOffset * 2}px)`,
93
+ maxHeight: `calc(100dvh - ${windowOffset * 2}px)`,
94
+ ...rootStyle,
95
+ zIndex: +zIndex + 1,
96
+ }}
97
+ >
98
+ <section style={{ ...style, animationDuration: `${transitionDuration}ms` }} className={modalContentClassName}>
99
+ {children}
100
+ </section>
101
+ </section>
102
+ </Portal>
103
+ );
104
+ };
@@ -0,0 +1,70 @@
1
+ @reference "polpo-tailwind-config/styles";
2
+
3
+ .modal {
4
+ position: fixed;
5
+ z-index: 1001;
6
+ pointer-events: none;
7
+ }
8
+
9
+ .modal-content {
10
+ background: var(--color-background-paper);
11
+ pointer-events: initial;
12
+
13
+ &.animation-fade-down {
14
+ animation: fadeInDown 200ms ease;
15
+
16
+ &.modal-close {
17
+ animation: fadeOutUp 200ms ease;
18
+ transform: translateY(-10px);
19
+ opacity: 0;
20
+ }
21
+ }
22
+
23
+ &.animation-bounce {
24
+ animation: bounceIn 500ms ease;
25
+
26
+ &.modal-close {
27
+ animation: bounceOut 500ms ease;
28
+ transform: scale3d(0.3, 0.3, 0.3);
29
+ opacity: 0;
30
+ }
31
+ }
32
+ }
33
+
34
+ .modal-backdrop {
35
+ position: fixed;
36
+ width: 100%;
37
+ height: 100%;
38
+ top: 0;
39
+ left: 0;
40
+ z-index: 1000;
41
+ animation: backdropOpen 500ms cubic-bezier(0.215, 0.61, 0.355, 1);
42
+
43
+ &.backdrop-close {
44
+ animation: backdropClose 500ms cubic-bezier(0.215, 0.61, 0.355, 1);
45
+ opacity: 0;
46
+ transform: translateY(-10px);
47
+ }
48
+ }
49
+
50
+ @keyframes backdropOpen {
51
+ from {
52
+ opacity: 0;
53
+ transform: translateY(-10px);
54
+ }
55
+ to {
56
+ opacity: 1;
57
+ transform: translateY(0);
58
+ }
59
+ }
60
+
61
+ @keyframes backdropClose {
62
+ from {
63
+ opacity: 1;
64
+ transform: translateY(0);
65
+ }
66
+ to {
67
+ opacity: 0;
68
+ transform: translateY(-10px);
69
+ }
70
+ }
@@ -0,0 +1 @@
1
+ export * from './portal.component';
@@ -0,0 +1,19 @@
1
+ import { ReactNode } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+
4
+ import { useDomContainer } from '../../../hooks';
5
+
6
+ type PortalProps = {
7
+ id: string;
8
+ children: ReactNode;
9
+ };
10
+
11
+ export const Portal = ({ children, id }: PortalProps) => {
12
+ const root = useDomContainer(id);
13
+
14
+ if (root === null) {
15
+ return null;
16
+ }
17
+
18
+ return createPortal(children, root);
19
+ };
@@ -0,0 +1 @@
1
+ export * from './ripple.component';
@@ -0,0 +1,70 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+
3
+ import './ripple.styles.css';
4
+
5
+ interface RippleProps {
6
+ color?: string;
7
+ duration?: number;
8
+ timingFunction?: React.CSSProperties['animationTimingFunction'];
9
+ times?: number;
10
+ className?: string;
11
+ style?: React.CSSProperties;
12
+ zIndex?: React.CSSProperties['zIndex'];
13
+ }
14
+
15
+ export const Ripple = ({
16
+ color,
17
+ duration = 1000,
18
+ timingFunction = 'ease-out',
19
+ times = 1,
20
+ className = '',
21
+ style = {},
22
+ zIndex = 1,
23
+ }: RippleProps) => {
24
+ const ref = useRef<HTMLElement>(null);
25
+
26
+ useEffect(() => {
27
+ if (!ref.current?.parentElement) {
28
+ return;
29
+ }
30
+
31
+ const computedPosition = getComputedStyle(ref.current.parentElement).position;
32
+
33
+ if (!['absolute', 'fixed', 'sticky', 'relative'].includes(computedPosition)) {
34
+ ref.current.parentElement.style.position = 'relative';
35
+ }
36
+ }, []);
37
+
38
+ const handleClick = (e: React.MouseEvent<HTMLSpanElement>) => {
39
+ const { pageX, pageY, currentTarget } = e;
40
+
41
+ const rect = currentTarget.getBoundingClientRect();
42
+
43
+ const x = ((pageX - rect.left) * 100) / rect.width;
44
+ const y = ((pageY - rect.top) * 100) / rect.height;
45
+
46
+ console.log({ y, pageY, rectTop: rect.top, rectHeight: rect.height });
47
+
48
+ const ripple = document.createElement('span');
49
+ ripple.classList.add('ripple-effect');
50
+ ripple.style.animationDuration = `${duration}ms`;
51
+ ripple.style.animationTimingFunction = timingFunction;
52
+ ripple.style.left = `${x}%`;
53
+ ripple.style.top = `${y}%`;
54
+
55
+ if (color) {
56
+ ripple.style.background = color;
57
+ }
58
+
59
+ currentTarget.appendChild(ripple);
60
+
61
+ setTimeout(
62
+ () => {
63
+ ripple.remove();
64
+ },
65
+ Math.max(duration, 500) * Math.max(times, 1),
66
+ );
67
+ };
68
+
69
+ return <span ref={ref} onMouseDown={handleClick} className={`ripple ${className}`} style={{ ...style, zIndex }} />;
70
+ };
@@ -0,0 +1,33 @@
1
+ @reference "polpo-tailwind-config/styles";
2
+
3
+ .ripple {
4
+ position: absolute;
5
+ display: block;
6
+ top: 0;
7
+ left: 0;
8
+ right: 0;
9
+ bottom: 0;
10
+ overflow: hidden;
11
+ border-radius: inherit;
12
+ z-index: 1;
13
+
14
+ .ripple-effect {
15
+ position: absolute;
16
+ transform: translate(-50%, -50%);
17
+ border-radius: 50%;
18
+ width: 0;
19
+ height: 0;
20
+ pointer-events: none;
21
+ animation: ripple-effect infinite;
22
+ opacity: 0.4;
23
+ }
24
+ }
25
+
26
+ @keyframes ripple-effect {
27
+ to {
28
+ width: 300px;
29
+ height: 300px;
30
+ opacity: 0;
31
+ filter: blur(5px);
32
+ }
33
+ }
@@ -0,0 +1,2 @@
1
+ export * from './smart-table.types';
2
+ export * from './smart-table.component';