Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions apps/expo-nativewind/app/(components)/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@ export default function DialogScreen() {
</DialogHeader>

<DialogFooter>
<DialogClose asChild>
<Button>
<Text>OK</Text>
</Button>
<DialogClose>
<Text>OK</Text>
</DialogClose>
</DialogFooter>
</DialogContent>
Expand Down
182 changes: 83 additions & 99 deletions apps/expo-nativewind/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Platform, View } from '@rn-primitives/core';
import * as DialogPrimitive from '@rn-primitives/dialog';
import { mergeProps } from '@rn-primitives/utils';
import * as React from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
import { FadeIn, FadeOut, ZoomIn, ZoomOut } from 'react-native-reanimated';
import { buttonTextVariants, buttonVariants } from '~/components/ui/button';
import { TextClassContext } from '~/components/ui/text';
import { X } from '~/lib/icons/X';
import { cn } from '~/lib/utils';

Expand All @@ -11,139 +14,120 @@ const DialogTrigger = DialogPrimitive.Trigger;

const DialogPortal = DialogPrimitive.Portal;

const DialogClose = DialogPrimitive.Close;
const OVERLAY_NATIVE_PROPS = {
isAnimated: true,
entering: FadeIn,
exiting: FadeOut.duration(150),
};

const DialogOverlayWeb = ({
ref,
className,
...props
}: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> & {
ref?: React.RefObject<React.ElementRef<typeof DialogPrimitive.Overlay>>;
}) => {
const { open } = DialogPrimitive.useRootContext();
function DialogOverlay({ className, native, ...props }: DialogPrimitive.OverlayProps) {
return (
<DialogPrimitive.Overlay
native={mergeProps(OVERLAY_NATIVE_PROPS, native)}
className={cn(
'bg-black/80 flex justify-center items-center p-2 absolute top-0 right-0 bottom-0 left-0',
open ? 'web:animate-in web:fade-in-0' : 'web:animate-out web:fade-out-0',
// z-50 important for exit animation on native
'z-50 top-0 right-0 bottom-0 left-0',
Platform.select({
web: 'fixed bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
native: 'bg-black/50 dark:bg-black/80 flex justify-center items-center p-2 absolute ',
}),
className
)}
{...props}
ref={ref}
/>
);
};
}

DialogOverlayWeb.displayName = 'DialogOverlayWeb';
const CONTENT_NATIVE_PROPS = {
isAnimated: true,
entering: ZoomIn.duration(200).withInitialValues({ transform: [{ scale: 0.85 }] }),
exiting: ZoomOut.duration(400),
};

const DialogOverlayNative = ({
ref,
function DialogContent({
className,
children,
native: { portalHost, ...nativeProp } = {},
...props
}: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> & {
ref?: React.RefObject<React.ElementRef<typeof DialogPrimitive.Overlay>>;
}) => {
return (
<DialogPrimitive.Overlay
style={StyleSheet.absoluteFill}
className={cn('flex bg-black/80 justify-center items-center p-2', className)}
{...props}
ref={ref}
>
<Animated.View entering={FadeIn.duration(150)} exiting={FadeOut.duration(150)}>
<>{children}</>
</Animated.View>
</DialogPrimitive.Overlay>
);
};

DialogOverlayNative.displayName = 'DialogOverlayNative';

const DialogOverlay = Platform.select({
web: DialogOverlayWeb,
default: DialogOverlayNative,
});

const DialogContent = ({ ref, className, children, portalHost, ...props }) => {
}: Omit<DialogPrimitive.ContentProps, 'native'> & {
native?: DialogPrimitive.ContentProps['native'] & { portalHost?: string };
}) {
const { open } = DialogPrimitive.useRootContext();
return (
<DialogPortal hostName={portalHost}>
<DialogPortal native={portalHost ? { hostName: portalHost } : undefined}>
<DialogOverlay>
<DialogPrimitive.Content
ref={ref}
className={cn(
'max-w-lg gap-4 border border-border web:cursor-default bg-background p-6 shadow-lg web:duration-200 rounded-lg',
open
? 'web:animate-in web:fade-in-0 web:zoom-in-95'
: 'web:animate-out web:fade-out-0 web:zoom-out-95',
className
)}
{...props}
>
{children}
<DialogPrimitive.Close
className={
'absolute right-4 top-4 p-0.5 web:group rounded-sm opacity-70 web:ring-offset-background web:transition-opacity web:hover:opacity-100 web:focus:outline-none web:focus:ring-2 web:focus:ring-ring web:focus:ring-offset-2 web:disabled:pointer-events-none'
}
<DialogPrimitive.Content asChild={Platform.OS === 'web'}>
{/* DialogPrimitive.Content uses `nativeID` for accessibility, so it prevents the entering animation from working https://docs.swmansion.com/react-native-reanimated/docs/layout-animations/entering-exiting-animations/#remarks */}
<View
className={cn(
Platform.select({
web: 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
native:
'z-50 max-w-lg gap-4 border bg-background p-6 shadow-lg border-border rounded-lg',
}),
className
)}
native={mergeProps(CONTENT_NATIVE_PROPS, nativeProp)}
{...props}
>
<X
size={Platform.OS === 'web' ? 16 : 18}
className={cn('text-muted-foreground', open && 'text-accent-foreground')}
/>
</DialogPrimitive.Close>
{children}
<DialogPrimitive.Close
className={
'absolute right-4 top-4 p-0.5 web:group rounded-sm opacity-70 web:ring-offset-background web:transition-opacity web:hover:opacity-100 web:focus:outline-none web:focus:ring-2 web:focus:ring-ring web:focus:ring-offset-2 web:disabled:pointer-events-none'
}
>
<X
size={Platform.OS === 'web' ? 16 : 18}
className={cn('text-muted-foreground', open && 'text-accent-foreground')}
/>
</DialogPrimitive.Close>
</View>
</DialogPrimitive.Content>
</DialogOverlay>
</DialogPortal>
);
};
DialogContent.displayName = DialogPrimitive.Content.displayName;
}

const DialogHeader = ({ className, ...props }: React.ComponentPropsWithoutRef<typeof View>) => (
<View className={cn('flex flex-col gap-1.5 text-center sm:text-left', className)} {...props} />
);
DialogHeader.displayName = 'DialogHeader';

const DialogFooter = ({ className, ...props }: React.ComponentPropsWithoutRef<typeof View>) => (
<View
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end gap-2', className)}
{...props}
/>
);
DialogFooter.displayName = 'DialogFooter';

const DialogTitle = ({
ref,
className,
...props
}: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> & {
ref?: React.RefObject<React.ElementRef<typeof DialogPrimitive.Title>>;
}) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
'text-lg native:text-xl text-foreground font-semibold leading-none tracking-tight',
className
)}
{...props}
/>
);
DialogTitle.displayName = DialogPrimitive.Title.displayName;
function DialogTitle({ className, ...props }: DialogPrimitive.TitleProps) {
return (
<DialogPrimitive.Title
className={cn(
'text-lg native:text-xl text-foreground font-semibold leading-none tracking-tight',
className
)}
{...props}
/>
);
}

const DialogDescription = ({
ref,
className,
...props
}: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> & {
ref?: React.RefObject<React.ElementRef<typeof DialogPrimitive.Description>>;
}) => (
<DialogPrimitive.Description
ref={ref}
className={cn('text-sm native:text-base text-muted-foreground', className)}
{...props}
/>
function DialogDescription({ className, ...props }: DialogPrimitive.DescriptionProps) {
return (
<DialogPrimitive.Description
className={cn('text-sm native:text-base text-muted-foreground', className)}
{...props}
/>
);
}

const DialogClose = ({ className, ...props }: DialogPrimitive.CloseProps) => (
<TextClassContext.Provider value={buttonTextVariants({ className, variant: 'outline' })}>
<DialogPrimitive.Close
className={cn(buttonVariants({ variant: 'outline', className }))}
{...props}
/>
</TextClassContext.Provider>
);
DialogDescription.displayName = DialogPrimitive.Description.displayName;

export {
Dialog,
Expand Down
10 changes: 4 additions & 6 deletions apps/nextjs-nativewind/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { View } from '@rn-primitives/core';
import { CollapsibleExample } from '~/components/CollapsibleExample';
import { Core } from '~/components/core';
import { ToggleExample } from '~/components/ToggleExample';
import { ToggleGroupExample } from '~/components/ToggleGroupExample';
Expand All @@ -22,7 +23,6 @@ import {
import { AspectRatio } from '~/components/ui/aspect-ratio';
import { Avatar, AvatarFallback, AvatarImage } from '~/components/ui/avatar';
import { Button } from '~/components/ui/button';
import { CollapsibleExample } from '~/components/CollapsibleExample';
import {
Dialog,
DialogClose,
Expand Down Expand Up @@ -103,14 +103,14 @@ export default function Home() {
<AspectRatioExample />
<AvatarExample />
<CollapsibleExample />
<DialogExample />
<LabelExample />
<ProgressExample />
<Separator />
<ToggleExample />
<ToggleGroupExample />
{/*<CheckboxExample />
<ContextMenuExample />
<DialogExample />
<DropdownMenuExample />
<HoverCardExample />
<MenubarExample />
Expand Down Expand Up @@ -243,10 +243,8 @@ function DialogExample() {
</DialogHeader>

<DialogFooter>
<DialogClose asChild>
<Button>
<Text>OK</Text>
</Button>
<DialogClose>
<Text>OK</Text>
</DialogClose>
</DialogFooter>
</DialogContent>
Expand Down
Loading