@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @toneflix/flipbook-native
2
2
 
3
- A photo book for React Native and Expo. Pages peel, fold and fall with real physics, photos are printed into textured paper, and the book reads sideways like the real thing.
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, frameOf } from '@toneflix/flipbook-core';
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 frame = frameOf(page, useBookLook().frame);
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.1.0",
4
- "description": "A photo book for React Native: peel-and-fold page turns, printed photos, frames and paper textures.",
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.1.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, frameOf, type FlipbookBook, type FlipbookPage } from '@toneflix/flipbook-core'
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 frame = frameOf(page, useBookLook().frame)
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' },