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
276 lines
6.8 KiB
'use client';
|
|
import React from 'react';
|
|
import { createModal } from '@gluestack-ui/modal';
|
|
import { Pressable, View, ScrollView, ViewStyle } from 'react-native';
|
|
import {
|
|
Motion,
|
|
AnimatePresence,
|
|
createMotionAnimatedComponent,
|
|
MotionComponentProps,
|
|
} from '@legendapp/motion';
|
|
import { tva } from '@gluestack-ui/nativewind-utils/tva';
|
|
import {
|
|
withStyleContext,
|
|
useStyleContext,
|
|
} from '@gluestack-ui/nativewind-utils/withStyleContext';
|
|
import { cssInterop } from 'nativewind';
|
|
import type { VariantProps } from '@gluestack-ui/nativewind-utils';
|
|
|
|
type IAnimatedPressableProps = React.ComponentProps<typeof Pressable> &
|
|
MotionComponentProps<typeof Pressable, ViewStyle, unknown, unknown, unknown>;
|
|
|
|
const AnimatedPressable = createMotionAnimatedComponent(
|
|
Pressable
|
|
) as React.ComponentType<IAnimatedPressableProps>;
|
|
const SCOPE = 'MODAL';
|
|
|
|
type IMotionViewProps = React.ComponentProps<typeof View> &
|
|
MotionComponentProps<typeof View, ViewStyle, unknown, unknown, unknown>;
|
|
|
|
const MotionView = Motion.View as React.ComponentType<IMotionViewProps>;
|
|
|
|
const UIModal = createModal({
|
|
Root: withStyleContext(View, SCOPE),
|
|
Backdrop: AnimatedPressable,
|
|
Content: MotionView,
|
|
Body: ScrollView,
|
|
CloseButton: Pressable,
|
|
Footer: View,
|
|
Header: View,
|
|
AnimatePresence: AnimatePresence,
|
|
});
|
|
|
|
cssInterop(AnimatedPressable, { className: 'style' });
|
|
cssInterop(MotionView, { className: 'style' });
|
|
|
|
const modalStyle = tva({
|
|
base: 'group/modal w-full h-full justify-center items-center web:pointer-events-none',
|
|
variants: {
|
|
size: {
|
|
xs: '',
|
|
sm: '',
|
|
md: '',
|
|
lg: '',
|
|
full: '',
|
|
},
|
|
},
|
|
});
|
|
|
|
const modalBackdropStyle = tva({
|
|
base: 'absolute left-0 top-0 right-0 bottom-0 bg-background-dark web:cursor-default',
|
|
});
|
|
|
|
const modalContentStyle = tva({
|
|
base: 'bg-background-0 rounded-md overflow-hidden border border-outline-100 shadow-hard-2 p-6',
|
|
parentVariants: {
|
|
size: {
|
|
xs: 'w-[60%] max-w-[360px]',
|
|
sm: 'w-[70%] max-w-[420px]',
|
|
md: 'w-[80%] max-w-[510px]',
|
|
lg: 'w-[90%] max-w-[640px]',
|
|
full: 'w-full',
|
|
},
|
|
},
|
|
});
|
|
|
|
const modalBodyStyle = tva({
|
|
base: 'mt-2 mb-6',
|
|
});
|
|
|
|
const modalCloseButtonStyle = tva({
|
|
base: 'group/modal-close-button z-10 rounded data-[focus-visible=true]:web:bg-background-100 web:outline-0 cursor-pointer',
|
|
});
|
|
|
|
const modalHeaderStyle = tva({
|
|
base: 'justify-between items-center flex-row',
|
|
});
|
|
|
|
const modalFooterStyle = tva({
|
|
base: 'flex-row justify-end items-center gap-2',
|
|
});
|
|
|
|
type IModalProps = React.ComponentProps<typeof UIModal> &
|
|
VariantProps<typeof modalStyle> & { className?: string };
|
|
|
|
type IModalBackdropProps = React.ComponentProps<typeof UIModal.Backdrop> &
|
|
VariantProps<typeof modalBackdropStyle> & { className?: string };
|
|
|
|
type IModalContentProps = React.ComponentProps<typeof UIModal.Content> &
|
|
VariantProps<typeof modalContentStyle> & { className?: string };
|
|
|
|
type IModalHeaderProps = React.ComponentProps<typeof UIModal.Header> &
|
|
VariantProps<typeof modalHeaderStyle> & { className?: string };
|
|
|
|
type IModalBodyProps = React.ComponentProps<typeof UIModal.Body> &
|
|
VariantProps<typeof modalBodyStyle> & { className?: string };
|
|
|
|
type IModalFooterProps = React.ComponentProps<typeof UIModal.Footer> &
|
|
VariantProps<typeof modalFooterStyle> & { className?: string };
|
|
|
|
type IModalCloseButtonProps = React.ComponentProps<typeof UIModal.CloseButton> &
|
|
VariantProps<typeof modalCloseButtonStyle> & { className?: string };
|
|
|
|
const Modal = React.forwardRef<React.ComponentRef<typeof UIModal>, IModalProps>(
|
|
({ className, size = 'md', ...props }, ref) => (
|
|
<UIModal
|
|
ref={ref}
|
|
{...props}
|
|
pointerEvents="box-none"
|
|
className={modalStyle({ size, class: className })}
|
|
context={{ size }}
|
|
/>
|
|
)
|
|
);
|
|
|
|
const ModalBackdrop = React.forwardRef<
|
|
React.ComponentRef<typeof UIModal.Backdrop>,
|
|
IModalBackdropProps
|
|
>(function ModalBackdrop({ className, ...props }, ref) {
|
|
return (
|
|
<UIModal.Backdrop
|
|
ref={ref}
|
|
initial={{
|
|
opacity: 0,
|
|
}}
|
|
animate={{
|
|
opacity: 0.5,
|
|
}}
|
|
exit={{
|
|
opacity: 0,
|
|
}}
|
|
transition={{
|
|
type: 'spring',
|
|
damping: 18,
|
|
stiffness: 250,
|
|
opacity: {
|
|
type: 'timing',
|
|
duration: 250,
|
|
},
|
|
}}
|
|
{...props}
|
|
className={modalBackdropStyle({
|
|
class: className,
|
|
})}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const ModalContent = React.forwardRef<
|
|
React.ComponentRef<typeof UIModal.Content>,
|
|
IModalContentProps
|
|
>(function ModalContent({ className, size, ...props }, ref) {
|
|
const { size: parentSize } = useStyleContext(SCOPE);
|
|
|
|
return (
|
|
<UIModal.Content
|
|
ref={ref}
|
|
initial={{
|
|
opacity: 0,
|
|
scale: 0.9,
|
|
}}
|
|
animate={{
|
|
opacity: 1,
|
|
scale: 1,
|
|
}}
|
|
exit={{
|
|
opacity: 0,
|
|
}}
|
|
transition={{
|
|
type: 'spring',
|
|
damping: 18,
|
|
stiffness: 250,
|
|
opacity: {
|
|
type: 'timing',
|
|
duration: 250,
|
|
},
|
|
}}
|
|
{...props}
|
|
className={modalContentStyle({
|
|
parentVariants: {
|
|
size: parentSize,
|
|
},
|
|
size,
|
|
class: className,
|
|
})}
|
|
pointerEvents="auto"
|
|
/>
|
|
);
|
|
});
|
|
|
|
const ModalHeader = React.forwardRef<
|
|
React.ComponentRef<typeof UIModal.Header>,
|
|
IModalHeaderProps
|
|
>(function ModalHeader({ className, ...props }, ref) {
|
|
return (
|
|
<UIModal.Header
|
|
ref={ref}
|
|
{...props}
|
|
className={modalHeaderStyle({
|
|
class: className,
|
|
})}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const ModalBody = React.forwardRef<
|
|
React.ComponentRef<typeof UIModal.Body>,
|
|
IModalBodyProps
|
|
>(function ModalBody({ className, ...props }, ref) {
|
|
return (
|
|
<UIModal.Body
|
|
ref={ref}
|
|
{...props}
|
|
className={modalBodyStyle({
|
|
class: className,
|
|
})}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const ModalFooter = React.forwardRef<
|
|
React.ComponentRef<typeof UIModal.Footer>,
|
|
IModalFooterProps
|
|
>(function ModalFooter({ className, ...props }, ref) {
|
|
return (
|
|
<UIModal.Footer
|
|
ref={ref}
|
|
{...props}
|
|
className={modalFooterStyle({
|
|
class: className,
|
|
})}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const ModalCloseButton = React.forwardRef<
|
|
React.ComponentRef<typeof UIModal.CloseButton>,
|
|
IModalCloseButtonProps
|
|
>(function ModalCloseButton({ className, ...props }, ref) {
|
|
return (
|
|
<UIModal.CloseButton
|
|
ref={ref}
|
|
{...props}
|
|
className={modalCloseButtonStyle({
|
|
class: className,
|
|
})}
|
|
/>
|
|
);
|
|
});
|
|
|
|
Modal.displayName = 'Modal';
|
|
ModalBackdrop.displayName = 'ModalBackdrop';
|
|
ModalContent.displayName = 'ModalContent';
|
|
ModalHeader.displayName = 'ModalHeader';
|
|
ModalBody.displayName = 'ModalBody';
|
|
ModalFooter.displayName = 'ModalFooter';
|
|
ModalCloseButton.displayName = 'ModalCloseButton';
|
|
|
|
export {
|
|
Modal,
|
|
ModalBackdrop,
|
|
ModalContent,
|
|
ModalCloseButton,
|
|
ModalHeader,
|
|
ModalBody,
|
|
ModalFooter,
|
|
};
|