Skip to content

Commit

Permalink
chore: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
gunnartorfis committed Sep 3, 2024
1 parent 76f0c5b commit 1e45d49
Show file tree
Hide file tree
Showing 14 changed files with 16,919 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,6 @@ lib/
# React Native Codegen
ios/generated
android/generated

example/ios
example/android
15 changes: 11 additions & 4 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,25 @@
"main": "expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
"android": "expo run:android",
"prebuild": "expo prebuild --clean",
"ios": "expo run:ios"
},
"dependencies": {
"@expo/metro-runtime": "~3.2.3",
"clsx": "^2.1.0",
"expo": "~51.0.28",
"expo-status-bar": "~1.12.1",
"nativewind": "next",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.74.5",
"react-native-web": "~0.19.10"
"react-native-gesture-handler": "~2.16.1",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.1",
"react-native-svg": "15.2.0",
"react-native-web": "~0.19.10",
"tailwindcss": "^3.4.10"
},
"devDependencies": {
"@babel/core": "^7.20.0",
Expand Down
1 change: 1 addition & 0 deletions nativewind-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="nativewind/types" />
24 changes: 23 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,32 +62,54 @@
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"dependencies": {
"uuid": "3.4.0"
},
"devDependencies": {
"@commitlint/config-conventional": "^17.0.2",
"@evilmartians/lefthook": "^1.5.0",
"@react-native/eslint-config": "^0.73.1",
"@release-it/conventional-changelog": "^5.0.0",
"@types/jest": "^29.5.5",
"@types/react": "^18.2.44",
"@types/uuid": "^10.0.0",
"clsx": "^2.1.0",
"commitlint": "^17.0.2",
"del-cli": "^5.1.0",
"eslint": "^8.51.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1",
"jest": "^29.7.0",
"lucide-react-native": "^0.438.0",
"nativewind": "next",
"prettier": "^3.0.3",
"react": "18.2.0",
"react-native": "0.74.5",
"react-native-builder-bob": "^0.30.0",
"react-native-gesture-handler": "~2.16.1",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.1",
"react-native-svg": "^15.6.0",
"release-it": "^15.0.0",
"tailwind-merge": "^2.2.2",
"tailwindcss": "^3.4.10",
"typescript": "^5.2.2"
},
"resolutions": {
"@types/react": "^18.2.44"
},
"peerDependencies": {
"clsx": "^2.1.0",
"lucide-react-native": ">=0",
"nativewind": "*",
"react": "*",
"react-native": "*"
"react-native": "*",
"react-native-gesture-handler": ">=2.0.0",
"react-native-reanimated": ">=3.0.0",
"react-native-safe-area-context": ">=4.0.0",
"react-native-svg": ">=15.0.0",
"tailwind-merge": ">=2.0.0",
"tailwindcss": ">=3.0.0"
},
"workspaces": [
"example"
Expand Down
207 changes: 207 additions & 0 deletions src/components/toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { CircleCheck, CircleX, Info, X } from 'lucide-react-native';
import * as React from 'react';
import type { ViewStyle } from 'react-native';
import { ActivityIndicator, Pressable, View, Text } from 'react-native';
import Animated from 'react-native-reanimated';
import { cn } from '@/utils/tailwind-utils';
import { ToastVariant, type ToastProps } from '@/types/toastTypes';
import { useToastContext } from '@/components/toast/ToastContext';
import {
ANIMATION_DURATION,
useToastLayoutAnimations,
} from '@/components/toast/toastAnimations';
import { ToastSwipeHandler } from '@/components/toast/ToastSwipeHandler';

const Toast: React.FC<ToastProps> = ({
id,
title,
description,
duration: durationProps,
variant,
action,
onHide,
className,
containerClassName,
actionClassName,
actionLabelClassName,
titleClassName,
descriptionClassName,
getIconColorForVariant: getIconColorForVariant,
closeIconColor,
promiseOptions,
}) => {
const { duration: durationContext, updateToast } = useToastContext();
const duration = durationProps ?? durationContext;

const isDragging = React.useRef(false);
const timer = React.useRef<NodeJS.Timeout>();
const timerStart = React.useRef<number | undefined>();
const isResolvingPromise = React.useRef(false);

React.useEffect(() => {
if (promiseOptions?.promise) {
try {
isResolvingPromise.current = true;
promiseOptions.promise.then((data) => {
updateToast(id, {
title: promiseOptions.success(data) ?? 'Success',
variant: ToastVariant.SUCCESS,
promiseOptions: undefined,
id,
});
isResolvingPromise.current = false;
});
} catch (error) {
updateToast(id, {
title: promiseOptions.error ?? 'Success',
variant: ToastVariant.SUCCESS,
id,
promiseOptions: undefined,
});
isResolvingPromise.current = false;
}

return;
}

timerStart.current = Date.now();
timer.current = setTimeout(() => {
if (!isDragging.current) {
onHide?.();
timerStart.current = undefined;
}
}, ANIMATION_DURATION + duration); // Auto-hide after 3 seconds

return () => clearTimeout(timer.current);
}, [duration, id, onHide, promiseOptions, updateToast]);

const { entering, exiting } = useToastLayoutAnimations();

return (
<ToastSwipeHandler
onRemove={() => {
onHide?.();
}}
onBegin={() => {
isDragging.current = true;
}}
onFinalize={() => {
isDragging.current = false;
const timeElapsed = Date.now() - timerStart.current!;

if (timeElapsed < duration) {
timer.current = setTimeout(() => {
onHide?.();
}, duration - timeElapsed);
} else {
onHide?.();
}
}}
enabled={!promiseOptions}
className={cn('w-full', containerClassName)}
>
<Animated.View
className={cn('p-4 rounded-2xl mb-4 mx-4', className)}
style={elevationStyle}
entering={entering}
exiting={exiting}
>
<View
className={cn('flex flex-row gap-4', {
'items-center': description?.length === 0,
})}
>
{promiseOptions ? (
<ActivityIndicator />
) : (
<ToastIcon
variant={variant}
getIconColorForVariant={getIconColorForVariant}
/>
)}
<View className="flex-1">
<Text className={cn('font-semibold leading-5', titleClassName)}>
{title}
</Text>
{description ? (
<Text className={cn('text-sm mt-[2px]', descriptionClassName)}>
{description}
</Text>
) : null}
{action ? (
<View className="flex flex-row items-center gap-4 mt-4">
<Pressable
onPress={action.onPress}
className={cn(
'rounded-full border px-2 py-1',
actionClassName
)}
style={{

Check warning on line 139 in src/components/toast/Toast.tsx

View workflow job for this annotation

GitHub Actions / lint

Inline style: { borderCurve: 'continuous' }
borderCurve: 'continuous',
}}
>
<Text
numberOfLines={1}
className={cn(
'text-sm font-semibold',
actionLabelClassName
)}
>
{action.label}
</Text>
</Pressable>
</View>
) : null}
</View>
<Pressable onPress={onHide} hitSlop={10}>
<X size={20} color={closeIconColor ?? '#ccc'} />
</Pressable>
</View>
</Animated.View>
</ToastSwipeHandler>
);
};

export const ToastIcon: React.FC<
Pick<ToastProps, 'variant' | 'getIconColorForVariant'>
> = ({ variant, getIconColorForVariant: getIconColorForVariant }) => {
switch (variant) {
case 'success':
return (
<CircleCheck
size={20}
color={getIconColorForVariant?.(ToastVariant.SUCCESS) ?? '#fff'}
/>
);
case 'error':
return (
<CircleX
size={20}
color={getIconColorForVariant?.(ToastVariant.SUCCESS) ?? '#fff'}
/>
);
default:
case 'info':
return (
<Info
size={20}
color={getIconColorForVariant?.(ToastVariant.INFO) ?? '#fff'}
/>
);
}
};

const elevationStyle = {
borderCurve: 'continuous' as ViewStyle['borderCurve'],
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 7,
},
shadowOpacity: 0.43,
shadowRadius: 9.51,

elevation: 15,
};

export default Toast;
Loading

0 comments on commit 1e45d49

Please sign in to comment.