Skip to content

Commit 4ba9279

Browse files
committed
examples: push examples on master
1 parent 82d0eef commit 4ba9279

31 files changed

+7530
-0
lines changed

examples/.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
.cache
3+
dist

examples/.postcssrc.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
modules: true,
3+
plugins: [require('postcss-preset-env')({ stage: 2 })],
4+
}

examples/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
7+
<title>React Use Gesture Examples</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script src="./src/index.tsx"></script>
12+
</body>
13+
</html>

examples/package.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "react-use-gesture-examples",
3+
"version": "1.0.0",
4+
"main": "./src/index.tsx",
5+
"scripts": {
6+
"start": "parcel index.html",
7+
"build": "parcel build index.html"
8+
},
9+
"author": "David Bismut",
10+
"license": "MIT",
11+
"dependencies": {
12+
"@reach/router": "^1.3.3",
13+
"@tim-soft/react-dat-gui": "^4.0.11",
14+
"add": "^2.0.6",
15+
"change-case": "^4.1.1",
16+
"classnames": "^2.2.6",
17+
"lodash": "^4.17.15",
18+
"lodash-move": "^1.1.1",
19+
"react-app-polyfill": "^1.0.6",
20+
"react-spring": "^9.0.0-rc.2",
21+
"react-three-fiber": "^4.1.4",
22+
"react-toastify": "^5.5.0",
23+
"three": "^0.116.0",
24+
"yarn": "^1.22.4"
25+
},
26+
"devDependencies": {
27+
"@types/classnames": "^2.2.10",
28+
"@types/lodash": "^4.14.150",
29+
"@types/reach__router": "^1.3.5",
30+
"@types/react": "^16.9.34",
31+
"@types/react-dom": "^16.9.7",
32+
"parcel": "^1.12.4",
33+
"postcss-modules": "^2.0.0",
34+
"postcss-preset-env": "^6.7.0",
35+
"typescript": "^3.8.3"
36+
},
37+
"alias": {
38+
"react-use-gesture": "../src/index",
39+
"react": "../node_modules/react",
40+
"react-dom": "../node_modules/react-dom/profiling",
41+
"scheduler/tracing": "../node_modules/scheduler/tracing-profiling"
42+
}
43+
}

examples/src/action-sheet/index.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React, { useRef } from 'react'
2+
import { useSpring, a, config } from 'react-spring'
3+
import { useDrag } from 'react-use-gesture'
4+
import styles from './styles.css'
5+
6+
const items = ['save item', 'open item', 'share item', 'delete item', 'cancel']
7+
const height = items.length * 60 + 80
8+
9+
export default function ActionSheet() {
10+
const draggingRef = useRef(false)
11+
const [{ y }, set] = useSpring(() => ({ y: height }))
12+
13+
const open = (canceled?: boolean) => {
14+
// when cancel is true, it means that the user passed the upwards threshold
15+
// so we change the spring config to create a nice wobbly effect
16+
set({ y: 0, config: canceled ? config.wobbly : config.stiff })
17+
}
18+
const close = (velocity = 0) => {
19+
set({ y: height, config: { ...config.stiff, velocity } })
20+
}
21+
22+
const bind = useDrag(
23+
({ first, last, vxvy: [, vy], movement: [, my], cancel, canceled }) => {
24+
if (first) draggingRef.current = true
25+
// if this is not the first or last frame, it's a moving frame
26+
// then it means the user is dragging
27+
else if (last) draggingRef.current = false
28+
29+
// if the user drags up passed a threshold, then we cancel
30+
// the drag so that the sheet resets to its open position
31+
if (my < -70) cancel()
32+
33+
// when the user releases the sheet, we check whether it passed
34+
// the threshold for it to close, or if we reset it to its open positino
35+
if (last) my > height * 0.75 || vy > 0.5 ? close(vy) : open(canceled)
36+
// when the user keeps dragging, we just move the sheet according to
37+
// the cursor position
38+
else set({ y: my, immediate: false, config: config.stiff })
39+
},
40+
{ initial: () => [0, y.get()], bounds: { top: 0 }, rubberband: true }
41+
)
42+
43+
const display = y.to(py => (py < height ? 'block' : 'none'))
44+
45+
const bgStyle = {
46+
transform: y.to([0, height], ['translateY(-8%) scale(1.16)', 'translateY(0px) scale(1)']),
47+
opacity: y.to([0, height], [0.4, 1], 'clamp'),
48+
// TODO check this on react-spring
49+
// touchAction: y.to(v => (v > 0 ? 'auto' : 'none')),
50+
}
51+
return (
52+
<div className={styles.wrapper}>
53+
<a.div className={styles.bg} onClick={() => close()} style={bgStyle}>
54+
<img
55+
src="https://images.pexels.com/photos/1170831/pexels-photo-1170831.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"
56+
alt=""
57+
/>
58+
<img
59+
src="https://images.pexels.com/photos/1657110/pexels-photo-1657110.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=650&w=940"
60+
alt=""
61+
/>
62+
</a.div>
63+
<div className={styles.actionBtn} onClick={() => open()} />
64+
<a.div className={styles.sheet} {...bind()} style={{ display, bottom: `calc(-100vh + ${height - 100}px)`, y }}>
65+
{items.map(entry => (
66+
<div key={entry} onClick={() => !draggingRef.current && close()} children={entry} />
67+
))}
68+
</a.div>
69+
</div>
70+
)
71+
}

examples/src/action-sheet/styles.css

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
.wrapper {
2+
background-color: #000;
3+
}
4+
5+
.actionBtn {
6+
position: fixed;
7+
z-index: 100;
8+
bottom: 80px;
9+
right: 40px;
10+
height: 48px;
11+
width: 48px;
12+
border-radius: 24px;
13+
background: coral;
14+
box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14),
15+
0px 1px 18px 0px rgba(0, 0, 0, 0);
16+
display: flex;
17+
align-items: center;
18+
justify-content: center;
19+
}
20+
21+
.actionBtn:after {
22+
content: ' ';
23+
display: block;
24+
background: #fff;
25+
height: 20%;
26+
width: 20%;
27+
border-radius: 50%;
28+
}
29+
30+
.bg > img {
31+
width: 100%;
32+
margin: 0;
33+
display: block;
34+
}
35+
36+
.sheet {
37+
z-index: 100;
38+
position: fixed;
39+
left: 2vw;
40+
height: calc(100vh + 100px);
41+
width: 96vw;
42+
border-radius: 12px 12px 0px;
43+
background: #fff;
44+
touch-action: none;
45+
will-change: transform;
46+
}
47+
48+
.sheet > div {
49+
height: 60px;
50+
border-bottom: 1px solid #eee;
51+
display: flex;
52+
align-items: center;
53+
justify-content: center;
54+
padding: 0 20px;
55+
text-transform: capitalize;
56+
}

examples/src/cards/index.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React, { useState } from 'react'
2+
import { useSprings, animated, to as interpolate } from 'react-spring'
3+
import { useDrag } from 'react-use-gesture'
4+
import styles from './styles.css'
5+
6+
const cards = [
7+
'https://upload.wikimedia.org/wikipedia/en/f/f5/RWS_Tarot_08_Strength.jpg',
8+
'https://upload.wikimedia.org/wikipedia/en/5/53/RWS_Tarot_16_Tower.jpg',
9+
'https://upload.wikimedia.org/wikipedia/en/9/9b/RWS_Tarot_07_Chariot.jpg',
10+
'https://upload.wikimedia.org/wikipedia/en/d/db/RWS_Tarot_06_Lovers.jpg',
11+
'https://upload.wikimedia.org/wikipedia/en/thumb/8/88/RWS_Tarot_02_High_Priestess.jpg/690px-RWS_Tarot_02_High_Priestess.jpg',
12+
'https://upload.wikimedia.org/wikipedia/en/d/de/RWS_Tarot_01_Magician.jpg',
13+
]
14+
15+
// These two are just helpers, they curate spring data, values that are later being interpolated into css
16+
const to = (i: number) => ({
17+
x: 0,
18+
y: i * -4,
19+
scale: 1,
20+
rot: -10 + Math.random() * 20,
21+
delay: i * 100,
22+
})
23+
const from = (_i: number) => ({ x: 0, rot: 0, scale: 1.5, y: -1000 })
24+
// This is being used down there in the view, it interpolates rotation and scale into a css transform
25+
const trans = (r: number, s: number) =>
26+
`perspective(1500px) rotateX(30deg) rotateY(${r / 10}deg) rotateZ(${r}deg) scale(${s})`
27+
28+
export default function Deck() {
29+
const [gone] = useState(() => new Set()) // The set flags all the cards that are flicked out
30+
const [props, set] = useSprings(cards.length, i => ({
31+
...to(i),
32+
from: from(i),
33+
})) // Create a bunch of springs using the helpers above
34+
// Create a gesture, we're interested in down-state, delta (current-pos - click-pos), direction and velocity
35+
const bind = useDrag(({ args: [index], down, movement: [mx], direction: [xDir], velocity }) => {
36+
const trigger = velocity > 0.2 // If you flick hard enough it should trigger the card to fly out
37+
const dir = xDir < 0 ? -1 : 1 // Direction should either point left or right
38+
if (!down && trigger) gone.add(index) // If button/finger's up and trigger velocity is reached, we flag the card ready to fly out
39+
40+
set((i: number) => {
41+
if (index !== i) return null // We're only interested in changing spring-data for the current spring
42+
const isGone = gone.has(index)
43+
const x = isGone ? (200 + window.innerWidth) * dir : down ? mx : 0 // When a card is gone it flys out left or right, otherwise goes back to zero
44+
const rot = mx / 100 + (isGone ? dir * 10 * velocity : 0) // How much the card tilts, flicking it harder makes it rotate faster
45+
const scale = down ? 1.1 : 1 // Active cards lift up a bit
46+
return {
47+
x,
48+
rot,
49+
scale,
50+
delay: undefined,
51+
config: { friction: 50, tension: down ? 800 : isGone ? 200 : 500 },
52+
}
53+
})
54+
if (!down && gone.size === cards.length) setTimeout(() => void gone.clear() || set(i => to(i)), 600)
55+
})
56+
// Now we're just mapping the animated values to our view, that's it. Btw, this component only renders once. :-)
57+
return (
58+
<div className={`${styles.cards} flex`}>
59+
{props.map(({ x, y, rot, scale }, i) => (
60+
<animated.div key={i} style={{ x, y }}>
61+
{/* This is the card itself, we're binding our gesture to it (and inject its index so we know which is which) */}
62+
<animated.div
63+
{...bind(i)}
64+
style={{
65+
transform: interpolate([rot, scale], trans),
66+
backgroundImage: `url(${cards[i]})`,
67+
}}
68+
/>
69+
</animated.div>
70+
))}
71+
</div>
72+
)
73+
}

examples/src/cards/styles.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.cards {
2+
background: lightblue;
3+
overflow: hidden;
4+
width: 100%;
5+
height: 100%;
6+
cursor: url('https://uploads.codesandbox.io/uploads/user/b3e56831-8b98-4fee-b941-0e27f39883ab/Ad1_-cursor.png') 39 39,
7+
auto;
8+
}
9+
10+
.cards > div {
11+
position: absolute;
12+
width: 100vw;
13+
height: 100vh;
14+
will-change: transform;
15+
display: flex;
16+
align-items: center;
17+
justify-content: center;
18+
}
19+
20+
.cards > div > div {
21+
background-color: white;
22+
background-size: auto 85%;
23+
background-repeat: no-repeat;
24+
background-position: center center;
25+
width: 45vh;
26+
max-width: 300px;
27+
height: 85vh;
28+
max-height: 570px;
29+
will-change: transform;
30+
border-radius: 10px;
31+
box-shadow: 0 12.5px 100px -10px rgba(50, 50, 73, 0.4), 0 10px 10px -10px rgba(50, 50, 73, 0.3);
32+
}

examples/src/docs-example/GUI.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React from 'react'
2+
import DatGui, { DatBoolean, DatNumber, DatSelect, DatFolder } from '@tim-soft/react-dat-gui'
3+
4+
export default ({ data = {}, onUpdate, style }) => (
5+
<DatGui data={data} onUpdate={onUpdate} style={style}>
6+
<DatFolder title="Drag Detection">
7+
{/* <DatBoolean path="filterTaps" label="Filter Taps" /> */}
8+
{/* <DatNumber path="delay" label="Drag Delay (ms)" min={0} max={1000} /> */}
9+
<DatNumber path="threshold" label="Int. Threshold" min={-1} max={100} step={1} />
10+
<DatBoolean path="lockDirection" label="Lock direction" />
11+
<DatSelect path="axis" label="Axis" options={[undefined, 'x', 'y']} />
12+
<DatBoolean path="activateBounds" label="Activate Bounds" />
13+
</DatFolder>
14+
{data.activateBounds ? (
15+
<DatFolder title="Bounds">
16+
<DatNumber path="rubberband" label="Rubberband" min={0} max={3} step={0.05} />
17+
<DatNumber path="bounds.top" label="Top" min={-200} max={0} />
18+
<DatNumber path="bounds.bottom" label="Bottom" min={0} max={200} />
19+
<DatNumber path="bounds.right" label="Right" min={0} max={200} />
20+
<DatNumber path="bounds.left" label="Left" min={-200} max={0} />
21+
</DatFolder>
22+
) : null}
23+
<DatFolder title="Swipe Detection">
24+
<DatNumber path="swipeVel" label="Swipe Velocity" min={0} max={10} step={0.1} />
25+
<DatNumber path="swipeDist" label="Swipe Distance" min={0} max={200} />
26+
</DatFolder>
27+
</DatGui>
28+
)
29+
30+
export const initialConfig = {
31+
axis: undefined,
32+
// delay: 0,
33+
lockDirection: false,
34+
// filterTaps: true,
35+
threshold: 10,
36+
swipeDist: 60,
37+
swipeVel: 0.5,
38+
activateBounds: false,
39+
rubberband: 0.15,
40+
bounds: {
41+
enabled: false,
42+
top: -100,
43+
bottom: 100,
44+
left: -100,
45+
right: 100,
46+
},
47+
}

0 commit comments

Comments
 (0)