廓形仪rn版本-技术调研
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
6.8 KiB

  1. 'use client';
  2. import React from 'react';
  3. import { createModal } from '@gluestack-ui/modal';
  4. import { Pressable, View, ScrollView, ViewStyle } from 'react-native';
  5. import {
  6. Motion,
  7. AnimatePresence,
  8. createMotionAnimatedComponent,
  9. MotionComponentProps,
  10. } from '@legendapp/motion';
  11. import { tva } from '@gluestack-ui/nativewind-utils/tva';
  12. import {
  13. withStyleContext,
  14. useStyleContext,
  15. } from '@gluestack-ui/nativewind-utils/withStyleContext';
  16. import { cssInterop } from 'nativewind';
  17. import type { VariantProps } from '@gluestack-ui/nativewind-utils';
  18. type IAnimatedPressableProps = React.ComponentProps<typeof Pressable> &
  19. MotionComponentProps<typeof Pressable, ViewStyle, unknown, unknown, unknown>;
  20. const AnimatedPressable = createMotionAnimatedComponent(
  21. Pressable
  22. ) as React.ComponentType<IAnimatedPressableProps>;
  23. const SCOPE = 'MODAL';
  24. type IMotionViewProps = React.ComponentProps<typeof View> &
  25. MotionComponentProps<typeof View, ViewStyle, unknown, unknown, unknown>;
  26. const MotionView = Motion.View as React.ComponentType<IMotionViewProps>;
  27. const UIModal = createModal({
  28. Root: withStyleContext(View, SCOPE),
  29. Backdrop: AnimatedPressable,
  30. Content: MotionView,
  31. Body: ScrollView,
  32. CloseButton: Pressable,
  33. Footer: View,
  34. Header: View,
  35. AnimatePresence: AnimatePresence,
  36. });
  37. cssInterop(AnimatedPressable, { className: 'style' });
  38. cssInterop(MotionView, { className: 'style' });
  39. const modalStyle = tva({
  40. base: 'group/modal w-full h-full justify-center items-center web:pointer-events-none',
  41. variants: {
  42. size: {
  43. xs: '',
  44. sm: '',
  45. md: '',
  46. lg: '',
  47. full: '',
  48. },
  49. },
  50. });
  51. const modalBackdropStyle = tva({
  52. base: 'absolute left-0 top-0 right-0 bottom-0 bg-background-dark web:cursor-default',
  53. });
  54. const modalContentStyle = tva({
  55. base: 'bg-background-0 rounded-md overflow-hidden border border-outline-100 shadow-hard-2 p-6',
  56. parentVariants: {
  57. size: {
  58. xs: 'w-[60%] max-w-[360px]',
  59. sm: 'w-[70%] max-w-[420px]',
  60. md: 'w-[80%] max-w-[510px]',
  61. lg: 'w-[90%] max-w-[640px]',
  62. full: 'w-full',
  63. },
  64. },
  65. });
  66. const modalBodyStyle = tva({
  67. base: 'mt-2 mb-6',
  68. });
  69. const modalCloseButtonStyle = tva({
  70. base: 'group/modal-close-button z-10 rounded data-[focus-visible=true]:web:bg-background-100 web:outline-0 cursor-pointer',
  71. });
  72. const modalHeaderStyle = tva({
  73. base: 'justify-between items-center flex-row',
  74. });
  75. const modalFooterStyle = tva({
  76. base: 'flex-row justify-end items-center gap-2',
  77. });
  78. type IModalProps = React.ComponentProps<typeof UIModal> &
  79. VariantProps<typeof modalStyle> & { className?: string };
  80. type IModalBackdropProps = React.ComponentProps<typeof UIModal.Backdrop> &
  81. VariantProps<typeof modalBackdropStyle> & { className?: string };
  82. type IModalContentProps = React.ComponentProps<typeof UIModal.Content> &
  83. VariantProps<typeof modalContentStyle> & { className?: string };
  84. type IModalHeaderProps = React.ComponentProps<typeof UIModal.Header> &
  85. VariantProps<typeof modalHeaderStyle> & { className?: string };
  86. type IModalBodyProps = React.ComponentProps<typeof UIModal.Body> &
  87. VariantProps<typeof modalBodyStyle> & { className?: string };
  88. type IModalFooterProps = React.ComponentProps<typeof UIModal.Footer> &
  89. VariantProps<typeof modalFooterStyle> & { className?: string };
  90. type IModalCloseButtonProps = React.ComponentProps<typeof UIModal.CloseButton> &
  91. VariantProps<typeof modalCloseButtonStyle> & { className?: string };
  92. const Modal = React.forwardRef<React.ComponentRef<typeof UIModal>, IModalProps>(
  93. ({ className, size = 'md', ...props }, ref) => (
  94. <UIModal
  95. ref={ref}
  96. {...props}
  97. pointerEvents="box-none"
  98. className={modalStyle({ size, class: className })}
  99. context={{ size }}
  100. />
  101. )
  102. );
  103. const ModalBackdrop = React.forwardRef<
  104. React.ComponentRef<typeof UIModal.Backdrop>,
  105. IModalBackdropProps
  106. >(function ModalBackdrop({ className, ...props }, ref) {
  107. return (
  108. <UIModal.Backdrop
  109. ref={ref}
  110. initial={{
  111. opacity: 0,
  112. }}
  113. animate={{
  114. opacity: 0.5,
  115. }}
  116. exit={{
  117. opacity: 0,
  118. }}
  119. transition={{
  120. type: 'spring',
  121. damping: 18,
  122. stiffness: 250,
  123. opacity: {
  124. type: 'timing',
  125. duration: 250,
  126. },
  127. }}
  128. {...props}
  129. className={modalBackdropStyle({
  130. class: className,
  131. })}
  132. />
  133. );
  134. });
  135. const ModalContent = React.forwardRef<
  136. React.ComponentRef<typeof UIModal.Content>,
  137. IModalContentProps
  138. >(function ModalContent({ className, size, ...props }, ref) {
  139. const { size: parentSize } = useStyleContext(SCOPE);
  140. return (
  141. <UIModal.Content
  142. ref={ref}
  143. initial={{
  144. opacity: 0,
  145. scale: 0.9,
  146. }}
  147. animate={{
  148. opacity: 1,
  149. scale: 1,
  150. }}
  151. exit={{
  152. opacity: 0,
  153. }}
  154. transition={{
  155. type: 'spring',
  156. damping: 18,
  157. stiffness: 250,
  158. opacity: {
  159. type: 'timing',
  160. duration: 250,
  161. },
  162. }}
  163. {...props}
  164. className={modalContentStyle({
  165. parentVariants: {
  166. size: parentSize,
  167. },
  168. size,
  169. class: className,
  170. })}
  171. pointerEvents="auto"
  172. />
  173. );
  174. });
  175. const ModalHeader = React.forwardRef<
  176. React.ComponentRef<typeof UIModal.Header>,
  177. IModalHeaderProps
  178. >(function ModalHeader({ className, ...props }, ref) {
  179. return (
  180. <UIModal.Header
  181. ref={ref}
  182. {...props}
  183. className={modalHeaderStyle({
  184. class: className,
  185. })}
  186. />
  187. );
  188. });
  189. const ModalBody = React.forwardRef<
  190. React.ComponentRef<typeof UIModal.Body>,
  191. IModalBodyProps
  192. >(function ModalBody({ className, ...props }, ref) {
  193. return (
  194. <UIModal.Body
  195. ref={ref}
  196. {...props}
  197. className={modalBodyStyle({
  198. class: className,
  199. })}
  200. />
  201. );
  202. });
  203. const ModalFooter = React.forwardRef<
  204. React.ComponentRef<typeof UIModal.Footer>,
  205. IModalFooterProps
  206. >(function ModalFooter({ className, ...props }, ref) {
  207. return (
  208. <UIModal.Footer
  209. ref={ref}
  210. {...props}
  211. className={modalFooterStyle({
  212. class: className,
  213. })}
  214. />
  215. );
  216. });
  217. const ModalCloseButton = React.forwardRef<
  218. React.ComponentRef<typeof UIModal.CloseButton>,
  219. IModalCloseButtonProps
  220. >(function ModalCloseButton({ className, ...props }, ref) {
  221. return (
  222. <UIModal.CloseButton
  223. ref={ref}
  224. {...props}
  225. className={modalCloseButtonStyle({
  226. class: className,
  227. })}
  228. />
  229. );
  230. });
  231. Modal.displayName = 'Modal';
  232. ModalBackdrop.displayName = 'ModalBackdrop';
  233. ModalContent.displayName = 'ModalContent';
  234. ModalHeader.displayName = 'ModalHeader';
  235. ModalBody.displayName = 'ModalBody';
  236. ModalFooter.displayName = 'ModalFooter';
  237. ModalCloseButton.displayName = 'ModalCloseButton';
  238. export {
  239. Modal,
  240. ModalBackdrop,
  241. ModalContent,
  242. ModalCloseButton,
  243. ModalHeader,
  244. ModalBody,
  245. ModalFooter,
  246. };