@toneflix/flipbook-native 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/look.js +5 -5
- package/lib/pages.js +14 -3
- package/package.json +7 -3
- package/src/look.tsx +5 -5
- package/src/pages.tsx +29 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @toneflix/flipbook-native
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An expressive flipbook for creating personalised and interactive reading and photo viewing experiences, for React Native and Expo.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx expo install @toneflix/flipbook-native react-native-svg expo-screen-orientation
|
|
@@ -21,4 +21,4 @@ export function Book({ book }) {
|
|
|
21
21
|
}
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
See the [documentation](https://toneflix.github.io/flipbook/
|
|
24
|
+
See the [documentation](https://toneflix.github.io/flipbook/frameworks/react-native).
|
package/lib/look.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext } from 'react';
|
|
3
3
|
import { Platform } from 'react-native';
|
|
4
|
-
const fine = { texture: require('../assets/textures/paper-fine
|
|
4
|
+
const fine = { texture: require('../assets/textures/paper-fine.webp'), grain: require('../assets/textures/grain-fine.webp') };
|
|
5
5
|
const ivory = {
|
|
6
6
|
background: '#fdf9f0',
|
|
7
7
|
ink: [61, 55, 47],
|
|
@@ -25,9 +25,9 @@ export const PAPERS = {
|
|
|
25
25
|
plateTint: 'rgba(90,70,40,0.03)',
|
|
26
26
|
plateLow: 'rgba(80,50,20,0.16)',
|
|
27
27
|
plateHigh: 'rgba(255,245,225,0.35)',
|
|
28
|
-
texture: require('../assets/textures/paper-kraft
|
|
28
|
+
texture: require('../assets/textures/paper-kraft.webp'),
|
|
29
29
|
textureOpacity: 0.45,
|
|
30
|
-
grain: require('../assets/textures/grain-kraft
|
|
30
|
+
grain: require('../assets/textures/grain-kraft.webp'),
|
|
31
31
|
grainOpacity: 0.35,
|
|
32
32
|
light: 'rgba(255,255,255,0.18)',
|
|
33
33
|
vignette: 'rgba(80,50,20,0.12)',
|
|
@@ -39,9 +39,9 @@ export const PAPERS = {
|
|
|
39
39
|
plateTint: 'rgba(0,0,0,0.08)',
|
|
40
40
|
plateLow: 'rgba(0,0,0,0.35)',
|
|
41
41
|
plateHigh: 'rgba(255,255,255,0.07)',
|
|
42
|
-
texture: require('../assets/textures/paper-charcoal
|
|
42
|
+
texture: require('../assets/textures/paper-charcoal.webp'),
|
|
43
43
|
textureOpacity: 0.25,
|
|
44
|
-
grain: require('../assets/textures/grain-charcoal
|
|
44
|
+
grain: require('../assets/textures/grain-charcoal.webp'),
|
|
45
45
|
grainOpacity: 0.22,
|
|
46
46
|
light: 'rgba(255,255,255,0.05)',
|
|
47
47
|
vignette: 'rgba(0,0,0,0.25)',
|
package/lib/pages.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useId } from 'react';
|
|
3
3
|
import { Image, Pressable, StyleSheet, Text, View } from 'react-native';
|
|
4
4
|
import Svg, { Circle, Defs, Path, RadialGradient, Rect, Stop } from 'react-native-svg';
|
|
5
|
-
import { COVER_THEMES,
|
|
5
|
+
import { COVER_THEMES, PLACED_LAYOUTS, photoFrame } from '@toneflix/flipbook-core';
|
|
6
6
|
import { Gradient } from './Gradient';
|
|
7
7
|
import { ink, useBookLook, usePaper, useSerif } from './look';
|
|
8
8
|
import { PrintedPhoto } from './PrintedPhoto';
|
|
@@ -17,10 +17,12 @@ export function PageView({ page, width, number, side, showImages = true, onSlotP
|
|
|
17
17
|
const serif = useSerif();
|
|
18
18
|
const unit = width / 100;
|
|
19
19
|
const { layout, caption, photos } = page;
|
|
20
|
-
const
|
|
20
|
+
const bookFrame = useBookLook().frame;
|
|
21
|
+
const placed = PLACED_LAYOUTS[layout];
|
|
21
22
|
const captionStyle = [serif.italic, styles.caption, { fontSize: 3.8 * unit, lineHeight: 5.2 * unit, color: ink(look) }];
|
|
22
23
|
const slot = (index, style, fit = 'cover') => {
|
|
23
24
|
const photo = photos[index] ?? null;
|
|
25
|
+
const frame = photoFrame(page, photo, bookFrame);
|
|
24
26
|
return (_jsxs(Pressable, { disabled: !onSlotPress, onPress: () => onSlotPress?.(index), role: onSlotPress ? 'button' : undefined, "aria-label": onSlotPress ? `Photo slot ${index + 1}` : undefined, style: [style, fit === 'cover' && styles.clip], children: [photo ? (_jsx(PrintedPhoto, { photo: photo, frame: frame, unit: unit, fit: fit, show: showImages })) : (_jsx(View, { style: [
|
|
25
27
|
StyleSheet.absoluteFill,
|
|
26
28
|
styles.empty,
|
|
@@ -28,12 +30,20 @@ export function PageView({ page, width, number, side, showImages = true, onSlotP
|
|
|
28
30
|
onSlotPress && { borderWidth: 0.4 * unit, borderColor: ink(look, 0.3) },
|
|
29
31
|
], children: onSlotPress && _jsx(Text, { style: { fontSize: 3.2 * unit, color: ink(look, 0.7) }, children: "Add a photo" }) })), onSlotPress && activeSlot === index && (_jsx(View, { pointerEvents: "none", style: [StyleSheet.absoluteFill, { borderWidth: 0.8 * unit, borderColor: accent }] }))] }, index));
|
|
30
32
|
};
|
|
33
|
+
const boxStyle = ({ x, y, width: w, height: h, rotate }) => ({
|
|
34
|
+
position: 'absolute',
|
|
35
|
+
left: x * unit,
|
|
36
|
+
top: y * unit,
|
|
37
|
+
width: w * unit,
|
|
38
|
+
height: h * unit,
|
|
39
|
+
transform: rotate ? [{ rotate: `${rotate}deg` }] : undefined,
|
|
40
|
+
});
|
|
31
41
|
const pageCaption = caption ? _jsx(Text, { style: captionStyle, children: caption }) : null;
|
|
32
42
|
return (_jsxs(Paper, { width: width, side: side, children: [layout === 'single' && (_jsxs(_Fragment, { children: [slot(0, StyleSheet.absoluteFill), caption ? (_jsxs(View, { pointerEvents: "none", style: [styles.overlay, { paddingTop: 14 * unit, paddingHorizontal: 7 * unit, paddingBottom: 7 * unit }], children: [_jsx(Gradient, { to: "top", stops: [
|
|
33
43
|
{ offset: 0, color: '#000', opacity: 0.7 },
|
|
34
44
|
{ offset: 0.5, color: '#000', opacity: 0.3 },
|
|
35
45
|
{ offset: 1, color: '#000', opacity: 0 },
|
|
36
|
-
] }), _jsx(Text, { style: [serif.italic, { fontSize: 4.2 * unit, lineHeight: 5.7 * unit, color: '#fff' }], children: caption })] })) : null] })), layout === 'framed' && (_jsxs(View, { style: [styles.fill, { paddingTop: 10 * unit, paddingHorizontal: 9 * unit, paddingBottom: 6 * unit }], children: [slot(0, styles.flex, 'contain'), _jsx(Text, { style: [captionStyle, { marginTop: 5 * unit, minHeight: 6 * unit, fontSize: 4.2 * unit, lineHeight: 5.7 * unit }], children: caption })] })), layout === 'pair' && (_jsxs(View, { style: [styles.fill, { padding: 7 * unit, gap: 4 * unit }], children: [slot(0, styles.flex), slot(1, styles.flex), pageCaption] })), layout === 'grid' && (_jsxs(View, { style: [styles.fill, { padding: 7 * unit, gap: 3 * unit }], children: [[0, 2].map((first) => (_jsxs(View, { style: [styles.row, { gap: 3 * unit }], children: [slot(first, styles.flex), slot(first + 1, styles.flex)] }, first))), pageCaption] })), layout === 'collage' && (_jsxs(View, { style: [styles.fill, { padding: 7 * unit, gap: 3 * unit }], children: [slot(0, { flex: 1.4 }), _jsxs(View, { style: [styles.row, { gap: 3 * unit }], children: [slot(1, styles.flex), slot(2, styles.flex)] }), pageCaption] })), layout === 'text' && (_jsxs(View, { style: [styles.fill, styles.centered, { paddingVertical: 14 * unit, paddingHorizontal: 12 * unit }], children: [_jsx(Text, { style: [serif.upright, { fontSize: 18 * unit, lineHeight: 16 * unit, height: 10 * unit, color: ink(look, 0.3) }], accessible: false, children: "\u201C" }), _jsx(Text, { style: [serif.italic, styles.caption, { marginTop: 4 * unit, fontSize: 6 * unit, lineHeight: 8.4 * unit, color: ink(look) }], children: caption || (onSlotPress ? 'Write something in the caption box below.' : '') })] })), number !== undefined && layout !== 'single' && (_jsx(Text, { style: [
|
|
46
|
+
] }), _jsx(Text, { style: [serif.italic, { fontSize: 4.2 * unit, lineHeight: 5.7 * unit, color: '#fff' }], children: caption })] })) : null] })), layout === 'framed' && (_jsxs(View, { style: [styles.fill, { paddingTop: 10 * unit, paddingHorizontal: 9 * unit, paddingBottom: 6 * unit }], children: [slot(0, styles.flex, 'contain'), _jsx(Text, { style: [captionStyle, { marginTop: 5 * unit, minHeight: 6 * unit, fontSize: 4.2 * unit, lineHeight: 5.7 * unit }], children: caption })] })), layout === 'pair' && (_jsxs(View, { style: [styles.fill, { padding: 7 * unit, gap: 4 * unit }], children: [slot(0, styles.flex), slot(1, styles.flex), pageCaption] })), layout === 'grid' && (_jsxs(View, { style: [styles.fill, { padding: 7 * unit, gap: 3 * unit }], children: [[0, 2].map((first) => (_jsxs(View, { style: [styles.row, { gap: 3 * unit }], children: [slot(first, styles.flex), slot(first + 1, styles.flex)] }, first))), pageCaption] })), layout === 'collage' && (_jsxs(View, { style: [styles.fill, { padding: 7 * unit, gap: 3 * unit }], children: [slot(0, { flex: 1.4 }), _jsxs(View, { style: [styles.row, { gap: 3 * unit }], children: [slot(1, styles.flex), slot(2, styles.flex)] }), pageCaption] })), placed && (_jsxs(_Fragment, { children: [placed.slots.map((box, i) => slot(i, { ...boxStyle(box), ...(placed.loose && styles.loose) })), caption ? (_jsx(View, { pointerEvents: "none", style: [boxStyle(placed.caption), !placed.prose && styles.centered], children: _jsx(Text, { style: placed.prose ? [serif.upright, { fontSize: 4.4 * unit, lineHeight: 6.8 * unit, color: ink(look) }] : captionStyle, children: caption }) })) : null] })), layout === 'text' && (_jsxs(View, { style: [styles.fill, styles.centered, { paddingVertical: 14 * unit, paddingHorizontal: 12 * unit }], children: [_jsx(Text, { style: [serif.upright, { fontSize: 18 * unit, lineHeight: 16 * unit, height: 10 * unit, color: ink(look, 0.3) }], accessible: false, children: "\u201C" }), _jsx(Text, { style: [serif.italic, styles.caption, { marginTop: 4 * unit, fontSize: 6 * unit, lineHeight: 8.4 * unit, color: ink(look) }], children: caption || (onSlotPress ? 'Write something in the caption box below.' : '') })] })), number !== undefined && layout !== 'single' && (_jsx(Text, { style: [
|
|
37
47
|
serif.upright,
|
|
38
48
|
styles.number,
|
|
39
49
|
side === 'left' ? styles.left : styles.right,
|
|
@@ -110,6 +120,7 @@ const styles = StyleSheet.create({
|
|
|
110
120
|
centered: { alignItems: 'center', justifyContent: 'center' },
|
|
111
121
|
center: { textAlign: 'center' },
|
|
112
122
|
clip: { overflow: 'hidden' },
|
|
123
|
+
loose: { boxShadow: '0 2px 6px rgba(0,0,0,0.25)' },
|
|
113
124
|
empty: { alignItems: 'center', justifyContent: 'center', borderStyle: 'dashed' },
|
|
114
125
|
overlay: { position: 'absolute', left: 0, right: 0, bottom: 0 },
|
|
115
126
|
caption: { textAlign: 'center' },
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toneflix/flipbook-native",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "An expressive flipbook for creating personalised and interactive reading and photo viewing experiences, for React Native and Expo.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"homepage": "https://toneflix.github.io/flipbook/frameworks/react-native",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/toneflix/flipbook/issues"
|
|
9
|
+
},
|
|
6
10
|
"repository": {
|
|
7
11
|
"type": "git",
|
|
8
12
|
"url": "https://github.com/toneflix/flipbook",
|
|
@@ -25,7 +29,7 @@
|
|
|
25
29
|
"assets"
|
|
26
30
|
],
|
|
27
31
|
"dependencies": {
|
|
28
|
-
"@toneflix/flipbook-core": "^0.1
|
|
32
|
+
"@toneflix/flipbook-core": "^0.2.1"
|
|
29
33
|
},
|
|
30
34
|
"peerDependencies": {
|
|
31
35
|
"expo-screen-orientation": "*",
|
package/src/look.tsx
CHANGED
|
@@ -20,7 +20,7 @@ export interface PaperLook {
|
|
|
20
20
|
vignette: string
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const fine = { texture: require('../assets/textures/paper-fine
|
|
23
|
+
const fine = { texture: require('../assets/textures/paper-fine.webp'), grain: require('../assets/textures/grain-fine.webp') }
|
|
24
24
|
|
|
25
25
|
const ivory: PaperLook = {
|
|
26
26
|
background: '#fdf9f0',
|
|
@@ -46,9 +46,9 @@ export const PAPERS: Record<PaperColor, PaperLook> = {
|
|
|
46
46
|
plateTint: 'rgba(90,70,40,0.03)',
|
|
47
47
|
plateLow: 'rgba(80,50,20,0.16)',
|
|
48
48
|
plateHigh: 'rgba(255,245,225,0.35)',
|
|
49
|
-
texture: require('../assets/textures/paper-kraft
|
|
49
|
+
texture: require('../assets/textures/paper-kraft.webp'),
|
|
50
50
|
textureOpacity: 0.45,
|
|
51
|
-
grain: require('../assets/textures/grain-kraft
|
|
51
|
+
grain: require('../assets/textures/grain-kraft.webp'),
|
|
52
52
|
grainOpacity: 0.35,
|
|
53
53
|
light: 'rgba(255,255,255,0.18)',
|
|
54
54
|
vignette: 'rgba(80,50,20,0.12)',
|
|
@@ -60,9 +60,9 @@ export const PAPERS: Record<PaperColor, PaperLook> = {
|
|
|
60
60
|
plateTint: 'rgba(0,0,0,0.08)',
|
|
61
61
|
plateLow: 'rgba(0,0,0,0.35)',
|
|
62
62
|
plateHigh: 'rgba(255,255,255,0.07)',
|
|
63
|
-
texture: require('../assets/textures/paper-charcoal
|
|
63
|
+
texture: require('../assets/textures/paper-charcoal.webp'),
|
|
64
64
|
textureOpacity: 0.25,
|
|
65
|
-
grain: require('../assets/textures/grain-charcoal
|
|
65
|
+
grain: require('../assets/textures/grain-charcoal.webp'),
|
|
66
66
|
grainOpacity: 0.22,
|
|
67
67
|
light: 'rgba(255,255,255,0.05)',
|
|
68
68
|
vignette: 'rgba(0,0,0,0.25)',
|
package/src/pages.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useId, type ReactNode } from 'react'
|
|
2
2
|
import { Image, Pressable, StyleSheet, Text, View, type ViewStyle } from 'react-native'
|
|
3
3
|
import Svg, { Circle, Defs, Path, RadialGradient, Rect, Stop } from 'react-native-svg'
|
|
4
|
-
import { COVER_THEMES,
|
|
4
|
+
import { COVER_THEMES, PLACED_LAYOUTS, photoFrame, type FlipbookBook, type FlipbookPage, type PageBox } from '@toneflix/flipbook-core'
|
|
5
5
|
import { Gradient } from './Gradient'
|
|
6
6
|
import { ink, useBookLook, usePaper, useSerif, type PaperLook } from './look'
|
|
7
7
|
import { PrintedPhoto } from './PrintedPhoto'
|
|
@@ -34,11 +34,13 @@ export function PageView({ page, width, number, side, showImages = true, onSlotP
|
|
|
34
34
|
const serif = useSerif()
|
|
35
35
|
const unit = width / 100
|
|
36
36
|
const { layout, caption, photos } = page
|
|
37
|
-
const
|
|
37
|
+
const bookFrame = useBookLook().frame
|
|
38
|
+
const placed = PLACED_LAYOUTS[layout]
|
|
38
39
|
const captionStyle = [serif.italic, styles.caption, { fontSize: 3.8 * unit, lineHeight: 5.2 * unit, color: ink(look) }]
|
|
39
40
|
|
|
40
41
|
const slot = (index: number, style: ViewStyle, fit: 'cover' | 'contain' = 'cover') => {
|
|
41
42
|
const photo = photos[index] ?? null
|
|
43
|
+
const frame = photoFrame(page, photo, bookFrame)
|
|
42
44
|
|
|
43
45
|
return (
|
|
44
46
|
<Pressable
|
|
@@ -70,6 +72,15 @@ export function PageView({ page, width, number, side, showImages = true, onSlotP
|
|
|
70
72
|
)
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
const boxStyle = ({ x, y, width: w, height: h, rotate }: PageBox): ViewStyle => ({
|
|
76
|
+
position: 'absolute',
|
|
77
|
+
left: x * unit,
|
|
78
|
+
top: y * unit,
|
|
79
|
+
width: w * unit,
|
|
80
|
+
height: h * unit,
|
|
81
|
+
transform: rotate ? [{ rotate: `${rotate}deg` }] : undefined,
|
|
82
|
+
})
|
|
83
|
+
|
|
73
84
|
const pageCaption = caption ? <Text style={captionStyle}>{caption}</Text> : null
|
|
74
85
|
|
|
75
86
|
return (
|
|
@@ -136,6 +147,21 @@ export function PageView({ page, width, number, side, showImages = true, onSlotP
|
|
|
136
147
|
</View>
|
|
137
148
|
)}
|
|
138
149
|
|
|
150
|
+
{placed && (
|
|
151
|
+
<>
|
|
152
|
+
{placed.slots.map((box, i) => slot(i, { ...boxStyle(box), ...(placed.loose && styles.loose) }))}
|
|
153
|
+
{caption ? (
|
|
154
|
+
<View pointerEvents="none" style={[boxStyle(placed.caption), !placed.prose && styles.centered]}>
|
|
155
|
+
<Text
|
|
156
|
+
style={placed.prose ? [serif.upright, { fontSize: 4.4 * unit, lineHeight: 6.8 * unit, color: ink(look) }] : captionStyle}
|
|
157
|
+
>
|
|
158
|
+
{caption}
|
|
159
|
+
</Text>
|
|
160
|
+
</View>
|
|
161
|
+
) : null}
|
|
162
|
+
</>
|
|
163
|
+
)}
|
|
164
|
+
|
|
139
165
|
{layout === 'text' && (
|
|
140
166
|
<View style={[styles.fill, styles.centered, { paddingVertical: 14 * unit, paddingHorizontal: 12 * unit }]}>
|
|
141
167
|
<Text
|
|
@@ -399,6 +425,7 @@ const styles = StyleSheet.create({
|
|
|
399
425
|
centered: { alignItems: 'center', justifyContent: 'center' },
|
|
400
426
|
center: { textAlign: 'center' },
|
|
401
427
|
clip: { overflow: 'hidden' },
|
|
428
|
+
loose: { boxShadow: '0 2px 6px rgba(0,0,0,0.25)' },
|
|
402
429
|
empty: { alignItems: 'center', justifyContent: 'center', borderStyle: 'dashed' },
|
|
403
430
|
overlay: { position: 'absolute', left: 0, right: 0, bottom: 0 },
|
|
404
431
|
caption: { textAlign: 'center' },
|