@admin-layout/gluestack-ui-mobile 11.0.4-alpha.4 → 12.0.16-alpha.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 (180) hide show
  1. package/CHANGELOG.md +67 -13
  2. package/lib/components/AuthWrapper.js +5 -3
  3. package/lib/components/AuthWrapper.js.map +1 -1
  4. package/lib/components/WithConfiguration.js +1 -1
  5. package/lib/components/WithConfiguration.js.map +1 -1
  6. package/lib/components/ui/accordion/index.web.d.ts +209 -0
  7. package/lib/components/ui/accordion/index.web.js +156 -0
  8. package/lib/components/ui/accordion/index.web.js.map +1 -0
  9. package/lib/components/ui/actionsheet/index.web.d.ts +872 -0
  10. package/lib/components/ui/actionsheet/index.web.js +286 -0
  11. package/lib/components/ui/actionsheet/index.web.js.map +1 -0
  12. package/lib/components/ui/alert/index.web.d.ts +135 -0
  13. package/lib/components/ui/alert/index.web.js +38 -0
  14. package/lib/components/ui/alert/index.web.js.map +1 -0
  15. package/lib/components/ui/alert-dialog/index.web.d.ts +64 -0
  16. package/lib/components/ui/alert-dialog/index.web.js +139 -0
  17. package/lib/components/ui/alert-dialog/index.web.js.map +1 -0
  18. package/lib/components/ui/avatar/index.web.d.ts +21 -0
  19. package/lib/components/ui/avatar/index.web.js +92 -0
  20. package/lib/components/ui/avatar/index.web.js.map +1 -0
  21. package/lib/components/ui/badge/index.web.d.ts +382 -0
  22. package/lib/components/ui/badge/index.web.js +138 -0
  23. package/lib/components/ui/badge/index.web.js.map +1 -0
  24. package/lib/components/ui/button/index.web.d.ts +247 -0
  25. package/lib/components/ui/button/index.web.js +57 -0
  26. package/lib/components/ui/button/index.web.js.map +1 -0
  27. package/lib/components/ui/checkbox/index.web.d.ts +265 -0
  28. package/lib/components/ui/checkbox/index.web.js +77 -0
  29. package/lib/components/ui/checkbox/index.web.js.map +1 -0
  30. package/lib/components/ui/divider/index.web.d.ts +53 -0
  31. package/lib/components/ui/divider/index.web.js +21 -0
  32. package/lib/components/ui/divider/index.web.js.map +1 -0
  33. package/lib/components/ui/drawer/index.web.d.ts +174 -0
  34. package/lib/components/ui/drawer/index.web.js +65 -0
  35. package/lib/components/ui/drawer/index.web.js.map +1 -0
  36. package/lib/components/ui/fab/index.web.d.ts +520 -0
  37. package/lib/components/ui/fab/index.web.js +146 -0
  38. package/lib/components/ui/fab/index.web.js.map +1 -0
  39. package/lib/components/ui/flat-list/index.web.d.ts +21 -0
  40. package/lib/components/ui/flat-list/index.web.js +41 -0
  41. package/lib/components/ui/flat-list/index.web.js.map +1 -0
  42. package/lib/components/ui/form-control/index.web.d.ts +2071 -0
  43. package/lib/components/ui/form-control/index.web.js +290 -0
  44. package/lib/components/ui/form-control/index.web.js.map +1 -0
  45. package/lib/components/ui/image/index.web.d.ts +142 -0
  46. package/lib/components/ui/image/index.web.js +29 -0
  47. package/lib/components/ui/image/index.web.js.map +1 -0
  48. package/lib/components/ui/image-background/index.web.d.ts +8 -0
  49. package/lib/components/ui/image-background/index.web.js +23 -0
  50. package/lib/components/ui/image-background/index.web.js.map +1 -0
  51. package/lib/components/ui/input/index.web.d.ts +163 -0
  52. package/lib/components/ui/input/index.web.js +50 -0
  53. package/lib/components/ui/input/index.web.js.map +1 -0
  54. package/lib/components/ui/input-accessory-view/index.web.d.ts +9 -0
  55. package/lib/components/ui/input-accessory-view/index.web.js +16 -0
  56. package/lib/components/ui/input-accessory-view/index.web.js.map +1 -0
  57. package/lib/components/ui/keyboard-avoiding-view/index.web.d.ts +9 -0
  58. package/lib/components/ui/keyboard-avoiding-view/index.web.js +8 -0
  59. package/lib/components/ui/keyboard-avoiding-view/index.web.js.map +1 -0
  60. package/lib/components/ui/link/index.web.d.ts +365 -0
  61. package/lib/components/ui/link/index.web.js +70 -0
  62. package/lib/components/ui/link/index.web.js.map +1 -0
  63. package/lib/components/ui/menu/index.web.d.ts +142 -0
  64. package/lib/components/ui/menu/index.web.js +83 -0
  65. package/lib/components/ui/menu/index.web.js.map +1 -0
  66. package/lib/components/ui/modal/index.web.d.ts +111 -0
  67. package/lib/components/ui/modal/index.web.js +58 -0
  68. package/lib/components/ui/modal/index.web.js.map +1 -0
  69. package/lib/components/ui/popover/index.web.d.ts +309 -0
  70. package/lib/components/ui/popover/index.web.js +184 -0
  71. package/lib/components/ui/popover/index.web.js.map +1 -0
  72. package/lib/components/ui/portal/index.web.d.ts +9 -0
  73. package/lib/components/ui/portal/index.web.js +16 -0
  74. package/lib/components/ui/portal/index.web.js.map +1 -0
  75. package/lib/components/ui/pressable/index.web.d.ts +8 -0
  76. package/lib/components/ui/pressable/index.web.js +44 -0
  77. package/lib/components/ui/pressable/index.web.js.map +1 -0
  78. package/lib/components/ui/progress/index.web.d.ts +237 -0
  79. package/lib/components/ui/progress/index.web.js +73 -0
  80. package/lib/components/ui/progress/index.web.js.map +1 -0
  81. package/lib/components/ui/radio/index.web.d.ts +224 -0
  82. package/lib/components/ui/radio/index.web.js +69 -0
  83. package/lib/components/ui/radio/index.web.js.map +1 -0
  84. package/lib/components/ui/refresh-control/index.web.d.ts +14 -0
  85. package/lib/components/ui/refresh-control/index.web.js +69 -0
  86. package/lib/components/ui/refresh-control/index.web.js.map +1 -0
  87. package/lib/components/ui/safe-area-view/index.web.d.ts +6 -0
  88. package/lib/components/ui/safe-area-view/index.web.js +10 -0
  89. package/lib/components/ui/safe-area-view/index.web.js.map +1 -0
  90. package/lib/components/ui/scroll-view/index.web.d.ts +44 -0
  91. package/lib/components/ui/scroll-view/index.web.js +41 -0
  92. package/lib/components/ui/scroll-view/index.web.js.map +1 -0
  93. package/lib/components/ui/section-list/index.web.d.ts +26 -0
  94. package/lib/components/ui/section-list/index.web.js +35 -0
  95. package/lib/components/ui/section-list/index.web.js.map +1 -0
  96. package/lib/components/ui/select/index.web.d.ts +162 -0
  97. package/lib/components/ui/select/index.web.js +51 -0
  98. package/lib/components/ui/select/index.web.js.map +1 -0
  99. package/lib/components/ui/slider/index.web.d.ts +223 -0
  100. package/lib/components/ui/slider/index.web.js +189 -0
  101. package/lib/components/ui/slider/index.web.js.map +1 -0
  102. package/lib/components/ui/spinner/index.web.d.ts +176 -0
  103. package/lib/components/ui/spinner/index.web.js +55 -0
  104. package/lib/components/ui/spinner/index.web.js.map +1 -0
  105. package/lib/components/ui/status-bar/index.web.d.ts +14 -0
  106. package/lib/components/ui/status-bar/index.web.js +7 -0
  107. package/lib/components/ui/status-bar/index.web.js.map +1 -0
  108. package/lib/components/ui/switch/index.web.d.ts +71 -0
  109. package/lib/components/ui/switch/index.web.js +27 -0
  110. package/lib/components/ui/switch/index.web.js.map +1 -0
  111. package/lib/components/ui/tabs/index.web.d.ts +351 -0
  112. package/lib/components/ui/tabs/index.web.js +120 -0
  113. package/lib/components/ui/tabs/index.web.js.map +1 -0
  114. package/lib/components/ui/textarea/index.web.d.ts +162 -0
  115. package/lib/components/ui/textarea/index.web.js +50 -0
  116. package/lib/components/ui/textarea/index.web.js.map +1 -0
  117. package/lib/components/ui/toast/index.web.d.ts +225 -0
  118. package/lib/components/ui/toast/index.web.js +80 -0
  119. package/lib/components/ui/toast/index.web.js.map +1 -0
  120. package/lib/components/ui/tooltip/index.web.d.ts +86 -0
  121. package/lib/components/ui/tooltip/index.web.js +110 -0
  122. package/lib/components/ui/tooltip/index.web.js.map +1 -0
  123. package/lib/components/ui/view/index.web.d.ts +2 -0
  124. package/lib/components/ui/view/index.web.js +7 -0
  125. package/lib/components/ui/view/index.web.js.map +1 -0
  126. package/lib/components/ui/virtualized-list/index.web.d.ts +19 -0
  127. package/lib/components/ui/virtualized-list/index.web.js +30 -0
  128. package/lib/components/ui/virtualized-list/index.web.js.map +1 -0
  129. package/lib/containers/layout/DrawerBottomNavigationConfig.d.ts +47 -47
  130. package/lib/containers/layout/DrawerConfig.d.ts +31 -31
  131. package/lib/interfaces/settings.d.ts +1 -0
  132. package/lib/redux/settings.d.ts +4 -9
  133. package/lib/utils/generateMobileNavigations.js +8 -8
  134. package/lib/utils/generateMobileNavigations.js.map +1 -1
  135. package/package.json +8 -4
  136. package/src/components/AuthWrapper.tsx +6 -3
  137. package/src/components/WithConfiguration.tsx +1 -1
  138. package/src/components/ui/accordion/index.web.tsx +294 -0
  139. package/src/components/ui/actionsheet/index.web.tsx +555 -0
  140. package/src/components/ui/alert/index.web.tsx +71 -0
  141. package/src/components/ui/alert-dialog/index.web.tsx +241 -0
  142. package/src/components/ui/avatar/index.web.tsx +150 -0
  143. package/src/components/ui/badge/index.web.tsx +188 -0
  144. package/src/components/ui/button/index.web.tsx +86 -0
  145. package/src/components/ui/checkbox/index.web.tsx +151 -0
  146. package/src/components/ui/divider/index.web.tsx +37 -0
  147. package/src/components/ui/drawer/index.web.tsx +144 -0
  148. package/src/components/ui/fab/index.web.tsx +201 -0
  149. package/src/components/ui/flat-list/index.web.tsx +89 -0
  150. package/src/components/ui/form-control/index.web.tsx +451 -0
  151. package/src/components/ui/image/index.web.tsx +43 -0
  152. package/src/components/ui/image-background/index.web.tsx +43 -0
  153. package/src/components/ui/input/index.web.tsx +80 -0
  154. package/src/components/ui/input-accessory-view/index.web.tsx +31 -0
  155. package/src/components/ui/keyboard-avoiding-view/index.web.tsx +23 -0
  156. package/src/components/ui/link/index.web.tsx +103 -0
  157. package/src/components/ui/menu/index.web.tsx +159 -0
  158. package/src/components/ui/modal/index.web.tsx +135 -0
  159. package/src/components/ui/popover/index.web.tsx +326 -0
  160. package/src/components/ui/portal/index.web.tsx +34 -0
  161. package/src/components/ui/pressable/index.web.tsx +73 -0
  162. package/src/components/ui/progress/index.web.tsx +123 -0
  163. package/src/components/ui/radio/index.web.tsx +130 -0
  164. package/src/components/ui/refresh-control/index.web.tsx +104 -0
  165. package/src/components/ui/safe-area-view/index.web.tsx +24 -0
  166. package/src/components/ui/scroll-view/index.web.tsx +142 -0
  167. package/src/components/ui/section-list/index.web.tsx +93 -0
  168. package/src/components/ui/select/index.web.tsx +83 -0
  169. package/src/components/ui/slider/index.web.tsx +283 -0
  170. package/src/components/ui/spinner/index.web.tsx +81 -0
  171. package/src/components/ui/status-bar/index.web.tsx +20 -0
  172. package/src/components/ui/switch/index.web.tsx +44 -0
  173. package/src/components/ui/tabs/index.web.tsx +205 -0
  174. package/src/components/ui/textarea/index.web.tsx +86 -0
  175. package/src/components/ui/toast/index.web.tsx +132 -0
  176. package/src/components/ui/tooltip/index.web.tsx +155 -0
  177. package/src/components/ui/view/index.web.tsx +15 -0
  178. package/src/components/ui/virtualized-list/index.web.tsx +84 -0
  179. package/src/interfaces/settings.ts +1 -0
  180. package/src/utils/generateMobileNavigations.ts +23 -23
@@ -0,0 +1,103 @@
1
+ 'use client';
2
+ import { createLink } from '@gluestack-ui/link';
3
+ import { tva } from '@gluestack-ui/nativewind-utils/tva';
4
+ import { withStyleContext } from '@gluestack-ui/nativewind-utils/withStyleContext';
5
+ import { cssInterop } from 'nativewind';
6
+ import type { VariantProps } from '@gluestack-ui/nativewind-utils';
7
+ import React from 'react';
8
+
9
+ // Create styled components for web
10
+ const StyledAnchor = withStyleContext('a' as any);
11
+ const StyledSpan = withStyleContext('span' as any);
12
+
13
+ export const UILink = createLink({
14
+ Root: StyledAnchor,
15
+ Text: StyledSpan,
16
+ }) as any;
17
+
18
+ cssInterop(UILink, { className: 'style' as any });
19
+ cssInterop(UILink.Text, { className: 'style' as any });
20
+
21
+ const linkStyle = tva({
22
+ base: 'group/link outline-0 cursor-pointer data-[disabled=true]:cursor-not-allowed data-[focus-visible=true]:ring-2 data-[focus-visible=true]:ring-indicator-primary data-[focus-visible=true]:outline-0 data-[disabled=true]:opacity-4',
23
+ });
24
+
25
+ const linkTextStyle = tva({
26
+ base: 'underline text-info-700 hover:text-info-600 hover:no-underline active:text-info-700 font-normal font-sans tracking-sm my-0 bg-transparent border-0 box-border display-inline list-none margin-0 padding-0 position-relative text-start whitespace-pre-wrap word-wrap-break-word',
27
+
28
+ variants: {
29
+ isTruncated: {
30
+ true: 'truncate',
31
+ },
32
+ bold: {
33
+ true: 'font-bold',
34
+ },
35
+ underline: {
36
+ true: 'underline',
37
+ },
38
+ strikeThrough: {
39
+ true: 'line-through',
40
+ },
41
+ size: {
42
+ '2xs': 'text-2xs',
43
+ xs: 'text-xs',
44
+ sm: 'text-sm',
45
+ md: 'text-base',
46
+ lg: 'text-lg',
47
+ xl: 'text-xl',
48
+ '2xl': 'text-2xl',
49
+ '3xl': 'text-3xl',
50
+ '4xl': 'text-4xl',
51
+ '5xl': 'text-5xl',
52
+ '6xl': 'text-6xl',
53
+ },
54
+ sub: {
55
+ true: 'text-xs',
56
+ },
57
+ italic: {
58
+ true: 'italic',
59
+ },
60
+ highlight: {
61
+ true: 'bg-yellow-500',
62
+ },
63
+ },
64
+ });
65
+
66
+ interface ILinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement>, VariantProps<typeof linkStyle> {
67
+ className?: string;
68
+ }
69
+
70
+ const Link = React.forwardRef<HTMLAnchorElement, ILinkProps>(({ className, href, target, rel, ...props }, ref) => {
71
+ return (
72
+ <UILink
73
+ ref={ref}
74
+ {...props}
75
+ href={href}
76
+ target={target}
77
+ rel={target === '_blank' ? 'noopener noreferrer' : rel}
78
+ className={linkStyle({ class: className })}
79
+ />
80
+ );
81
+ });
82
+
83
+ interface ILinkTextProps extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof linkTextStyle> {
84
+ className?: string;
85
+ }
86
+
87
+ const LinkText = React.forwardRef<HTMLSpanElement, ILinkTextProps>(({ className, size = 'md', ...props }, ref) => {
88
+ return (
89
+ <UILink.Text
90
+ ref={ref}
91
+ {...props}
92
+ className={linkTextStyle({
93
+ class: className,
94
+ size,
95
+ })}
96
+ />
97
+ );
98
+ });
99
+
100
+ Link.displayName = 'Link';
101
+ LinkText.displayName = 'LinkText';
102
+
103
+ export { Link, LinkText };
@@ -0,0 +1,159 @@
1
+ 'use client';
2
+ import React, { forwardRef, useEffect, useRef, useState } from 'react';
3
+ import { createMenu } from '@gluestack-ui/menu';
4
+ import { tva } from '@gluestack-ui/nativewind-utils/tva';
5
+ import { withStyleContext } from '@gluestack-ui/nativewind-utils/withStyleContext';
6
+ import { cssInterop } from 'nativewind';
7
+ import type { VariantProps } from '@gluestack-ui/nativewind-utils';
8
+ import { createPortal } from 'react-dom';
9
+
10
+ const SCOPE = 'MENU';
11
+
12
+ const menuStyle = tva({
13
+ base: 'absolute z-50 min-w-[200px] py-1 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none',
14
+ variants: {
15
+ placement: {
16
+ top: 'bottom-full mb-2',
17
+ bottom: 'top-full mt-2',
18
+ left: 'right-full mr-2',
19
+ right: 'left-full ml-2',
20
+ },
21
+ size: {
22
+ sm: 'text-sm',
23
+ md: 'text-base',
24
+ lg: 'text-lg',
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ placement: 'bottom',
29
+ size: 'md',
30
+ },
31
+ });
32
+
33
+ export const MenuItem = forwardRef<HTMLButtonElement, React.ComponentProps<'button'>>(
34
+ ({ className, onClick, disabled, ...props }, ref) => (
35
+ <button
36
+ ref={ref}
37
+ className={`w-full px-4 py-2 text-left hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed ${
38
+ className || ''
39
+ }`}
40
+ onClick={onClick}
41
+ disabled={disabled}
42
+ role="menuitem"
43
+ {...props}
44
+ />
45
+ ),
46
+ );
47
+
48
+ MenuItem.displayName = 'MenuItem';
49
+
50
+ export const MenuDivider = forwardRef<HTMLHRElement, React.ComponentProps<'hr'>>(({ className, ...props }, ref) => (
51
+ <hr ref={ref} className={`my-1 border-gray-200 ${className || ''}`} role="separator" {...props} />
52
+ ));
53
+
54
+ MenuDivider.displayName = 'MenuDivider';
55
+
56
+ export const MenuGroup = forwardRef<HTMLDivElement, React.ComponentProps<'div'> & { title?: string }>(
57
+ ({ className, title, children, ...props }, ref) => (
58
+ <div ref={ref} role="group" aria-label={title} {...props}>
59
+ {title && <div className="px-4 py-2 text-sm font-semibold text-gray-500">{title}</div>}
60
+ {children}
61
+ </div>
62
+ ),
63
+ );
64
+
65
+ MenuGroup.displayName = 'MenuGroup';
66
+
67
+ type IMenuProps = {
68
+ trigger: React.ReactElement;
69
+ isOpen?: boolean;
70
+ onOpen?: () => void;
71
+ onClose?: () => void;
72
+ children?: React.ReactNode;
73
+ className?: string;
74
+ placement?: VariantProps<typeof menuStyle>['placement'];
75
+ size?: VariantProps<typeof menuStyle>['size'];
76
+ };
77
+
78
+ const Menu = forwardRef<HTMLDivElement, IMenuProps>(
79
+ ({ trigger, isOpen: controlledIsOpen, onOpen, onClose, children, className, placement, size, ...props }, ref) => {
80
+ const [isOpen, setIsOpen] = useState(controlledIsOpen || false);
81
+ const menuRef = useRef<HTMLDivElement>(null);
82
+ const triggerRef = useRef<HTMLDivElement>(null);
83
+
84
+ const handleOpen = () => {
85
+ setIsOpen(true);
86
+ if (onOpen) onOpen();
87
+ };
88
+
89
+ const handleClose = () => {
90
+ setIsOpen(false);
91
+ if (onClose) onClose();
92
+ };
93
+
94
+ useEffect(() => {
95
+ if (controlledIsOpen !== undefined) {
96
+ setIsOpen(controlledIsOpen);
97
+ }
98
+ }, [controlledIsOpen]);
99
+
100
+ useEffect(() => {
101
+ const handleClickOutside = (event: MouseEvent) => {
102
+ if (
103
+ menuRef.current &&
104
+ !menuRef.current.contains(event.target as Node) &&
105
+ triggerRef.current &&
106
+ !triggerRef.current.contains(event.target as Node)
107
+ ) {
108
+ handleClose();
109
+ }
110
+ };
111
+
112
+ const handleEscape = (event: KeyboardEvent) => {
113
+ if (event.key === 'Escape') {
114
+ handleClose();
115
+ }
116
+ };
117
+
118
+ if (isOpen) {
119
+ document.addEventListener('mousedown', handleClickOutside);
120
+ document.addEventListener('keydown', handleEscape);
121
+ }
122
+
123
+ return () => {
124
+ document.removeEventListener('mousedown', handleClickOutside);
125
+ document.removeEventListener('keydown', handleEscape);
126
+ };
127
+ }, [isOpen]);
128
+
129
+ return (
130
+ <div className="relative inline-block" ref={ref}>
131
+ <div
132
+ ref={triggerRef}
133
+ onClick={() => (isOpen ? handleClose() : handleOpen())}
134
+ aria-haspopup="true"
135
+ aria-expanded={isOpen}
136
+ >
137
+ {trigger}
138
+ </div>
139
+ {isOpen &&
140
+ createPortal(
141
+ <div
142
+ ref={menuRef}
143
+ className={menuStyle({ placement, size, class: className })}
144
+ role="menu"
145
+ aria-orientation="vertical"
146
+ {...props}
147
+ >
148
+ {children}
149
+ </div>,
150
+ document.body,
151
+ )}
152
+ </div>
153
+ );
154
+ },
155
+ );
156
+
157
+ Menu.displayName = 'Menu';
158
+
159
+ export { Menu };
@@ -0,0 +1,135 @@
1
+ 'use client';
2
+ import React, { forwardRef, useEffect, useRef } from 'react';
3
+ import { createModal } from '@gluestack-ui/modal';
4
+ import { tva } from '@gluestack-ui/nativewind-utils/tva';
5
+ import { withStyleContext } from '@gluestack-ui/nativewind-utils/withStyleContext';
6
+ import { cssInterop } from 'nativewind';
7
+ import type { VariantProps } from '@gluestack-ui/nativewind-utils';
8
+ import { createPortal } from 'react-dom';
9
+
10
+ const SCOPE = 'MODAL';
11
+
12
+ interface ModalOverlayProps {
13
+ onPress?: () => void;
14
+ className?: string;
15
+ }
16
+
17
+ const ModalOverlay = ({ onPress, className }: ModalOverlayProps) => (
18
+ <div
19
+ onClick={onPress}
20
+ className={`fixed inset-0 bg-black bg-opacity-40 transition-opacity ${className || ''}`}
21
+ aria-hidden="true"
22
+ />
23
+ );
24
+
25
+ const modalStyle = tva({
26
+ base: 'fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-xl p-6 max-h-[90vh] overflow-y-auto',
27
+ variants: {
28
+ size: {
29
+ xs: 'w-[95%] max-w-xs',
30
+ sm: 'w-[95%] max-w-sm',
31
+ md: 'w-[95%] max-w-md',
32
+ lg: 'w-[95%] max-w-lg',
33
+ xl: 'w-[95%] max-w-xl',
34
+ full: 'w-[95%]',
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ size: 'md',
39
+ },
40
+ });
41
+
42
+ export const ModalContent = forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(({ className, ...props }, ref) => (
43
+ <div ref={ref} role="dialog" aria-modal="true" className={className} {...props} />
44
+ ));
45
+
46
+ ModalContent.displayName = 'ModalContent';
47
+
48
+ export const ModalHeader = forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(({ className, ...props }, ref) => (
49
+ <div ref={ref} className={`text-xl font-semibold mb-4 ${className || ''}`} {...props} />
50
+ ));
51
+
52
+ ModalHeader.displayName = 'ModalHeader';
53
+
54
+ export const ModalBody = forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(({ className, ...props }, ref) => (
55
+ <div ref={ref} className={`mb-6 ${className || ''}`} {...props} />
56
+ ));
57
+
58
+ ModalBody.displayName = 'ModalBody';
59
+
60
+ export const ModalFooter = forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(({ className, ...props }, ref) => (
61
+ <div ref={ref} className={`flex justify-end space-x-2 ${className || ''}`} {...props} />
62
+ ));
63
+
64
+ ModalFooter.displayName = 'ModalFooter';
65
+
66
+ export const ModalCloseButton = forwardRef<HTMLButtonElement, React.ComponentProps<'button'>>(
67
+ ({ className, onClick, ...props }, ref) => (
68
+ <button
69
+ ref={ref}
70
+ className={`absolute right-4 top-4 text-gray-400 hover:text-gray-600 ${className || ''}`}
71
+ onClick={onClick}
72
+ aria-label="Close modal"
73
+ {...props}
74
+ >
75
+ ×
76
+ </button>
77
+ ),
78
+ );
79
+
80
+ ModalCloseButton.displayName = 'ModalCloseButton';
81
+
82
+ type IModalProps = {
83
+ isOpen?: boolean;
84
+ onClose?: () => void;
85
+ children?: React.ReactNode;
86
+ className?: string;
87
+ size?: VariantProps<typeof modalStyle>['size'];
88
+ closeOnOverlayClick?: boolean;
89
+ };
90
+
91
+ const Modal = forwardRef<HTMLDivElement, IModalProps>(
92
+ ({ isOpen, onClose, children, className, size, closeOnOverlayClick = true, ...props }, ref) => {
93
+ const modalRef = useRef<HTMLDivElement>(null);
94
+
95
+ useEffect(() => {
96
+ const handleEscape = (event: KeyboardEvent) => {
97
+ if (event.key === 'Escape' && onClose) {
98
+ onClose();
99
+ }
100
+ };
101
+
102
+ if (isOpen) {
103
+ document.addEventListener('keydown', handleEscape);
104
+ document.body.style.overflow = 'hidden';
105
+ }
106
+
107
+ return () => {
108
+ document.removeEventListener('keydown', handleEscape);
109
+ document.body.style.overflow = '';
110
+ };
111
+ }, [isOpen, onClose]);
112
+
113
+ if (!isOpen) return null;
114
+
115
+ return createPortal(
116
+ <>
117
+ <ModalOverlay onPress={closeOnOverlayClick ? onClose : undefined} />
118
+ <div
119
+ ref={ref}
120
+ className={modalStyle({ size, class: className })}
121
+ role="dialog"
122
+ aria-modal="true"
123
+ {...props}
124
+ >
125
+ {children}
126
+ </div>
127
+ </>,
128
+ document.body,
129
+ );
130
+ },
131
+ );
132
+
133
+ Modal.displayName = 'Modal';
134
+
135
+ export { Modal };