@toneflix/flipbook-native 0.1.0 → 0.2.0
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 +1 -1
- package/lib/pages.js +14 -3
- package/package.json +3 -3
- package/src/pages.tsx +29 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @toneflix/flipbook-native
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Turn any set of photos into an album that bends, settles and feels like paper, in React Native and Expo, with page turns drawn by native views. A page folds exactly where a finger bends it, photos sink into the paper's grain, and phones open the book sideways across two pages.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx expo install @toneflix/flipbook-native react-native-svg expo-screen-orientation
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toneflix/flipbook-native",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Bind photos into a page-turning album in React Native and Expo: fingertip page turns, prints set into paper, frames and layouts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"assets"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@toneflix/flipbook-core": "^0.
|
|
28
|
+
"@toneflix/flipbook-core": "^0.2.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"expo-screen-orientation": "*",
|
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' },
|