react-native-laminar 1.0.0 → 1.0.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 +213 -2
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -4,8 +4,219 @@ React Native morphing text and number animation.
|
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
6
|
import { Laminar } from "react-native-laminar";
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`Laminar` (also exported as `MorphingText`) animates changes to a string or number value. Matching characters hold position across transitions. New characters enter. Removed characters exit. Numbers align from the right so place-value columns stay in stable lanes.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install react-native-laminar
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Laminar requires [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/). Follow its installation guide before using this package.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### Text
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import React, { useState } from "react";
|
|
29
|
+
import { Button, View } from "react-native";
|
|
30
|
+
import { Laminar } from "react-native-laminar";
|
|
31
|
+
|
|
32
|
+
export default function Example() {
|
|
33
|
+
const [word, setWord] = useState("Laminar");
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
|
|
37
|
+
<Laminar
|
|
38
|
+
text={word}
|
|
39
|
+
fontSize={40}
|
|
40
|
+
style={{ color: "#000000", fontFamily: "System" }}
|
|
41
|
+
/>
|
|
42
|
+
<Button
|
|
43
|
+
title="Morph"
|
|
44
|
+
onPress={() => setWord((w) => (w === "Laminar" ? "Linear" : "Laminar"))}
|
|
45
|
+
/>
|
|
46
|
+
</View>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Number
|
|
7
52
|
|
|
8
|
-
|
|
53
|
+
```tsx
|
|
54
|
+
import React, { useState } from "react";
|
|
55
|
+
import { Button, View } from "react-native";
|
|
56
|
+
import { Laminar } from "react-native-laminar";
|
|
57
|
+
|
|
58
|
+
export default function Counter() {
|
|
59
|
+
const [value, setValue] = useState("$1,234");
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
|
|
63
|
+
<Laminar
|
|
64
|
+
text={value}
|
|
65
|
+
variant="number"
|
|
66
|
+
fontSize={40}
|
|
67
|
+
animationPreset="snappy"
|
|
68
|
+
style={{ color: "#000000", fontVariant: ["tabular-nums"] }}
|
|
69
|
+
/>
|
|
70
|
+
<Button title="Change" onPress={() => setValue("$12,345")} />
|
|
71
|
+
</View>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
9
74
|
```
|
|
10
75
|
|
|
11
|
-
|
|
76
|
+
### Inside a button with auto-sizing
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
<Pressable style={{ paddingHorizontal: 24, paddingVertical: 12, borderRadius: 36, backgroundColor: "#007aff" }}>
|
|
80
|
+
<Laminar
|
|
81
|
+
text={label}
|
|
82
|
+
autoSize
|
|
83
|
+
fontSize={18}
|
|
84
|
+
style={{ color: "#ffffff" }}
|
|
85
|
+
/>
|
|
86
|
+
</Pressable>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`autoSize` (default `true`) animates the outer container width toward the measured final text width. The button grows and shrinks without layout feedback loops.
|
|
90
|
+
|
|
91
|
+
### Standalone word without auto-sizing
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
<Laminar
|
|
95
|
+
text={word}
|
|
96
|
+
autoSize={false}
|
|
97
|
+
fontSize={40}
|
|
98
|
+
style={{ color: "#000000" }}
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Use `autoSize={false}` when the parent already defines the space and you only want the glyph animation.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Props
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
type LaminarProps = {
|
|
110
|
+
text: string | number;
|
|
111
|
+
variant?: "text" | "number";
|
|
112
|
+
fontSize?: number;
|
|
113
|
+
color?: string;
|
|
114
|
+
style?: StyleProp<TextStyle>;
|
|
115
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
116
|
+
fontStyle?: StyleProp<TextStyle>;
|
|
117
|
+
animationDuration?: number;
|
|
118
|
+
animationPreset?: "default" | "smooth" | "snappy" | "bouncy";
|
|
119
|
+
stagger?: number;
|
|
120
|
+
autoSize?: boolean;
|
|
121
|
+
clipToBounds?: boolean;
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
| Prop | Default | Description |
|
|
126
|
+
| ------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
127
|
+
| `text` | Required | The value to display. Numbers are converted to strings internally. |
|
|
128
|
+
| `variant` | `"text"` | `"text"` uses LCS glyph reconciliation. `"number"` uses right-aligned digit lanes. |
|
|
129
|
+
| `fontSize` | Undefined | Convenience prop merged into the text style. |
|
|
130
|
+
| `color` | Undefined | Convenience prop merged into the text style. |
|
|
131
|
+
| `style` | Undefined | Text style applied after `fontSize` and `color`. |
|
|
132
|
+
| `containerStyle` | Undefined | Style for the outer viewport shell. Use for placement and alignment. |
|
|
133
|
+
| `fontStyle` | Undefined | Additional text style merged before `style`. |
|
|
134
|
+
| `animationDuration` | Preset default | Duration override in milliseconds. |
|
|
135
|
+
| `animationPreset` | `"default"` for text, `"snappy"` for number | Named motion recipe. |
|
|
136
|
+
| `stagger` | `0.02` | Delay in seconds between numeric lane animations. |
|
|
137
|
+
| `autoSize` | `true` | Animate the outer width to the measured final text width. |
|
|
138
|
+
| `clipToBounds` | `false` | Clip animated overflow to the viewport bounds. |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Animation Presets
|
|
143
|
+
|
|
144
|
+
| Preset | Character | Default Duration |
|
|
145
|
+
| --------- | -------------------------- | ---------------- |
|
|
146
|
+
| `default` | Smooth cubic-bezier timing | 380ms |
|
|
147
|
+
| `smooth` | Spring with no bounce | 400ms |
|
|
148
|
+
| `snappy` | Spring with light bounce | 350ms |
|
|
149
|
+
| `bouncy` | Spring with more bounce | 500ms |
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<Laminar text={word} animationPreset="smooth" />
|
|
153
|
+
<Laminar text={count} variant="number" animationPreset="snappy" />
|
|
154
|
+
|
|
155
|
+
// Override duration
|
|
156
|
+
<Laminar text={word} animationPreset="default" animationDuration={520} />
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Best Practices
|
|
162
|
+
|
|
163
|
+
**Keep `text` stable — don't change `key`.**
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
// Wrong — forces remount, loses glyph identity
|
|
167
|
+
<Laminar key={label} text={label} />
|
|
168
|
+
|
|
169
|
+
// Correct
|
|
170
|
+
<Laminar text={label} />
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Choose `autoSize` based on layout role.**
|
|
174
|
+
|
|
175
|
+
- Buttons, chips, badges → `autoSize={true}`
|
|
176
|
+
- Standalone centered words → `autoSize={false}`
|
|
177
|
+
- Fixed-width counters → `autoSize={false}`
|
|
178
|
+
|
|
179
|
+
**Match formatting across renders for numbers.**
|
|
180
|
+
|
|
181
|
+
Switching between `$1,234` and `1234` mid-session breaks lane alignment. Keep the format consistent.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
// Consistent formatting keeps lanes stable
|
|
185
|
+
<Laminar text={`$${value.toLocaleString()}`} variant="number" />
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Duration guidelines.**
|
|
189
|
+
|
|
190
|
+
| Range | Best for |
|
|
191
|
+
| ------------- | ------------------------------- |
|
|
192
|
+
| 180ms – 260ms | Rapid counters, live data |
|
|
193
|
+
| 300ms – 450ms | Normal labels, button text |
|
|
194
|
+
| 500ms – 700ms | Expressive or teaching moments |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Troubleshooting
|
|
199
|
+
|
|
200
|
+
**Animations not playing.**
|
|
201
|
+
1. Verify Reanimated is installed and configured.
|
|
202
|
+
2. Check that the component is not remounting due to a changing `key`.
|
|
203
|
+
3. Check that updates are not arriving faster than the animation duration.
|
|
204
|
+
|
|
205
|
+
**Glyphs clipped.**
|
|
206
|
+
1. Set `clipToBounds={false}` if overflow should be visible.
|
|
207
|
+
2. Verify the parent does not have `overflow: "hidden"` set.
|
|
208
|
+
|
|
209
|
+
**Numbers misaligned or jumpy.**
|
|
210
|
+
1. Use `variant="number"`.
|
|
211
|
+
2. Keep formatting consistent between renders.
|
|
212
|
+
3. Add `fontVariant: ["tabular-nums"]` if your font supports it.
|
|
213
|
+
|
|
214
|
+
**Auto-sizing not working.**
|
|
215
|
+
1. Check that `autoSize` is not explicitly set to `false`.
|
|
216
|
+
2. Verify the parent allows its child to define the width — a fixed-width parent overrides the animated child width.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
See the repository root for license information.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-laminar",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A React Native component for morphing text and numbers with character-level identity.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -54,7 +54,12 @@
|
|
|
54
54
|
"output": "lib",
|
|
55
55
|
"targets": [
|
|
56
56
|
"commonjs",
|
|
57
|
-
[
|
|
57
|
+
[
|
|
58
|
+
"module",
|
|
59
|
+
{
|
|
60
|
+
"esm": true
|
|
61
|
+
}
|
|
62
|
+
],
|
|
58
63
|
"typescript"
|
|
59
64
|
]
|
|
60
65
|
}
|