From 06c60a49ec1c5f486d7f03f9874ad3e8611ab8d7 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 2 Oct 2021 15:30:05 +1300 Subject: [PATCH] feat: add "animatedStyleWorklet" prop to allow animated style customisation --- README.md | 66 ++++++++-- example/src/App.tsx | 92 +++++++------- src/DuoDragDrop.tsx | 295 ++++++++++++++++++++++--------------------- src/SortableWord.tsx | 11 +- src/index.tsx | 3 +- src/types.ts | 17 +++ 6 files changed, 276 insertions(+), 208 deletions(-) create mode 100644 src/types.ts diff --git a/README.md b/README.md index bbc970c..3ca8c20 100644 --- a/README.md +++ b/README.md @@ -39,19 +39,20 @@ export default App; ## DuoDragDrop props -| Prop | Type | Description | -| ----------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| words | string[] | List of words that will be used for the drag-and-drop | -| extraData | any? | (Optional) A marker property for telling the words to re-render | -| wordHeight | number? | (Optional) height of an individual word. Default: 45 | -| wordGap | number? | (Optional) The gap between each word / line: Default: 4 | -| wordBankOffsetY | number? | (Optional) The offset between the "Bank" pile and the "Answer" pile. Default: 20 | -| wordBankAlignment | "center" \| "right" \| "left" | (Optional) Alignment of the words in the word bank. Default "center" | -| gesturesDisabled | boolean? | (Optional) Whether tap & drag gestures are disabled. Default: false | -| rtl | boolean? | (Optional) Whether to lay out words in the "Answer" pile from right-to-left (for languages such as Arabic) | -| renderWord | (word: string, index: number) => JSX.Element | (Optional) Overrides the default word renderer | -| renderPlaceholder | (props: {style: StyleProp\}) => JSX.Element | (Optional) Overrides the default placeholder renderer | -| renderLines | (props: { numLines: number; containerHeight: number; lineHeight: number }) => JSX.Element | (Optional) Overrides the default lines renderer | +| Prop | Type | Description | +| -------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | +| words | string[] | List of words that will be used for the drag-and-drop | +| extraData | any? | (Optional) A marker property for telling the words to re-render | +| wordHeight | number? | (Optional) height of an individual word. Default: 45 | +| wordGap | number? | (Optional) The gap between each word / line: Default: 4 | +| wordBankOffsetY | number? | (Optional) The offset between the "Bank" pile and the "Answer" pile. Default: 20 | +| wordBankAlignment | "center" \| "right" \| "left" | (Optional) Alignment of the words in the word bank. Default "center" | +| gesturesDisabled | boolean? | (Optional) Whether tap & drag gestures are disabled. Default: false | +| rtl | boolean? | (Optional) Whether to lay out words in the "Answer" pile from right-to-left (for languages such as Arabic) | +| renderWord | (word: string, index: number) => JSX.Element | (Optional) Overrides the default word renderer | +| renderPlaceholder | (props: {style: StyleProp\}) => JSX.Element | (Optional) Overrides the default placeholder renderer | +| renderLines | (props: { numLines: number; containerHeight: number; lineHeight: number }) => JSX.Element | (Optional) Overrides the default lines renderer | +| animatedStyleWorklet | (style: ViewStyle, isGestureActive: boolean) => ViewStyle | (Optional) A worket function that allows you to modify the style of the word while it is being dragged. | ## DuoDragDrop ref value @@ -60,9 +61,10 @@ export default App; getWords(): { answered: string[]; bank: string[] }; /** Returns an array of words that are outside the "word bank" */ getAnsweredWords(): string[]; -/* +/** * Gets the order value of each word by the word's index. * -1 indicates that it's in the "bank" +* * e.g. ["hello", "world", "foo", "bar"] -> [1, -1, 0, 2] corresponds to: * - ["hello", "foo", "bar"] (unordered) or * - ["foo", "hello", "bar"] (ordered) in the "answered" pile @@ -139,6 +141,42 @@ function DndExample() { } ``` +## Customising animations + +You can customise how the words are animated by providing a worklet function. + +```tsx +import DuoDragDrop, { DuoAnimatedStyleWorklet } from "@jamsch/react-native-duo-drag-drop"; +import { withTiming, withSpring } from "react-native-reanimated"; + +const customAnimatedStyle: DuoAnimatedStyleWorklet = (style, isGestureActive) => { + "worklet"; + // Scale the word when the gesture is active + style.transform.push({ + scale: withTiming(isGestureActive ? 1.5 : 1, { duration: 250 }), + }); + style.opacity = withTiming(isGestureActive ? 0.8 : 1, { duration: 250 }); + style.top = withTiming(isGestureActive ? -10 : 0, { duration: 250 }); + + // Apply a spring when the word moves to it's destination + if (!isGestureActive) { + style.transform[0].translateX = withSpring(style.transform[0].translateX); + style.transform[1].translateY = withSpring(style.transform[1].translateY); + } + + return style; +}; + +export default function DragDrop() { + return ( + + ); +} +``` + ## Contributing See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. diff --git a/example/src/App.tsx b/example/src/App.tsx index 82bb50b..1812702 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,18 +1,40 @@ -import React, { useRef, useState } from 'react'; -import { StyleSheet, View, Text, Button } from 'react-native'; +/* eslint-disable react-native/no-inline-styles */ +import React, { useRef, useState } from "react"; +import { StyleSheet, View, Text, Button } from "react-native"; import DuoDragDrop, { DuoDragDropRef, Word, Placeholder, Lines, -} from '@jamsch/react-native-duo-drag-drop'; -import { GestureHandlerRootView } from 'react-native-gesture-handler'; + DuoAnimatedStyleWorklet, +} from "@jamsch/react-native-duo-drag-drop"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { withSpring, withTiming } from "react-native-reanimated"; + +const customAnimatedStyle: DuoAnimatedStyleWorklet = (style, isGestureActive) => { + "worklet"; + // Scale the word when the gesture is active + style.transform.push({ + scale: withTiming(isGestureActive ? 1.5 : 1, { duration: 250 }), + }); + style.opacity = withTiming(isGestureActive ? 0.8 : 1, { duration: 250 }); + style.top = withTiming(isGestureActive ? -10 : 0, { duration: 250 }); + + // Apply a spring when the word moves to it's destination + if (!isGestureActive) { + style.transform[0].translateX = withSpring(style.transform[0].translateX); + style.transform[1].translateY = withSpring(style.transform[1].translateY); + } + + return style; +}; export default function App() { const [rtl, setRtl] = useState(false); const [gradeWords, setGradeWords] = useState([]); const [gesturedDisabled, setGesturesDisabled] = useState(false); const [answeredWords, setAnsweredWords] = useState([]); + const [shouldUseCustomWorket, setShouldUseCustomWorket] = useState(false); const duoDragDropRef = useRef(null); return ( @@ -20,16 +42,7 @@ export default function App() { ( + animatedStyleWorklet={shouldUseCustomWorket ? customAnimatedStyle : undefined} + renderWord={(_word, index) => ( )} - renderPlaceholder={({ style }) => ( - - )} + renderPlaceholder={({ style }) => } renderLines={(props) => ( - + )} />