Skip to content

Commit

Permalink
feat: add "animatedStyleWorklet" prop to allow animated style customi…
Browse files Browse the repository at this point in the history
…sation
  • Loading branch information
jamsch committed Oct 2, 2021
1 parent b2c98d9 commit 06c60a4
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 208 deletions.
66 changes: 52 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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\<ViewStyle\>}) => 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\<ViewStyle\>}) => 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

Expand All @@ -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
Expand Down Expand Up @@ -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 (
<DuoDragDrop
words={["Juan", "She", "apples", "today", "with", "eats", "her", "another"]}
animatedStyleWorklet={customAnimatedStyle}
/>
);
}
```

## Contributing

See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
Expand Down
92 changes: 44 additions & 48 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,83 @@
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<boolean[]>([]);
const [gesturedDisabled, setGesturesDisabled] = useState(false);
const [answeredWords, setAnsweredWords] = useState<string[]>([]);
const [shouldUseCustomWorket, setShouldUseCustomWorket] = useState(false);
const duoDragDropRef = useRef<DuoDragDropRef>(null);

return (
<GestureHandlerRootView style={styles.container}>
<View style={styles.dragDropContainer}>
<DuoDragDrop
ref={duoDragDropRef}
words={[
'Juan',
'She',
'apples',
'today',
'with',
'eats',
'her',
'another',
]}
words={["Juan", "She", "apples", "today", "with", "eats", "her", "another"]}
wordHeight={45}
wordGap={4}
gesturesDisabled={gesturedDisabled}
rtl={rtl}
wordBankOffsetY={20}
wordBankAlignment="center"
extraData={gradeWords}
renderWord={(word, index) => (
animatedStyleWorklet={shouldUseCustomWorket ? customAnimatedStyle : undefined}
renderWord={(_word, index) => (
<Word
containerStyle={
typeof gradeWords?.[index] === 'boolean' && {
backgroundColor: gradeWords?.[index] ? 'green' : 'red',
borderColor: gradeWords?.[index] ? 'green' : 'red',
typeof gradeWords?.[index] === "boolean" && {
backgroundColor: gradeWords?.[index] ? "green" : "red",
borderColor: gradeWords?.[index] ? "green" : "red",
}
}
textStyle={{
color:
typeof gradeWords?.[index] === 'boolean' ? 'white' : 'black',
color: typeof gradeWords?.[index] === "boolean" ? "white" : "black",
}}
/>
)}
renderPlaceholder={({ style }) => (
<Placeholder style={[style, { borderRadius: 5 }]} />
)}
renderPlaceholder={({ style }) => <Placeholder style={[style, { borderRadius: 5 }]} />}
renderLines={(props) => (
<Lines
{...props}
containerStyle={{ backgroundColor: 'transparent' }}
lineStyle={{ borderColor: '#CCC' }}
/>
<Lines {...props} containerStyle={{ backgroundColor: "transparent" }} lineStyle={{ borderColor: "#CCC" }} />
)}
/>
</View>
<View style={{ margin: 10 }}>
<Button
title="Get answered words"
onPress={() =>
setAnsweredWords(duoDragDropRef.current?.getAnsweredWords() || [])
}
onPress={() => setAnsweredWords(duoDragDropRef.current?.getAnsweredWords() || [])}
/>
{answeredWords.length > 0 && (
<View style={{ marginTop: 10 }}>
<Text>{answeredWords.join(', ')}</Text>
<Text>{answeredWords.join(", ")}</Text>
</View>
)}
<View style={{ marginTop: 10 }}>
Expand All @@ -82,32 +87,23 @@ export default function App() {
if (gradeWords.length > 0) {
setGradeWords([]);
} else {
setGradeWords([
true,
false,
true,
false,
false,
true,
false,
false,
]);
setGradeWords([true, false, true, false, false, true, false, false]);
}
}}
/>
</View>
<View style={{ marginTop: 10 }}>
<Button
title={`Gestures disabled: ${gesturedDisabled}`}
onPress={() => setGesturesDisabled((s) => !s)}
/>
<Button title={`Gestures disabled: ${gesturedDisabled}`} onPress={() => setGesturesDisabled((s) => !s)} />
</View>
<View style={{ marginTop: 10 }}>
<Button
title={`Right-To-Left: ${rtl}`}
onPress={() => setRtl((s) => !s)}
title={`Use custom animations: ${shouldUseCustomWorket}`}
onPress={() => setShouldUseCustomWorket((s) => !s)}
/>
</View>
<View style={{ marginTop: 10 }}>
<Button title={`Right-To-Left: ${rtl}`} onPress={() => setRtl((s) => !s)} />
</View>
</View>
</GestureHandlerRootView>
);
Expand Down
Loading

0 comments on commit 06c60a4

Please sign in to comment.