@react-native-motion-kit/text-motion 0.1.0 → 0.1.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.ko.md CHANGED
@@ -1,442 +1,67 @@
1
- # @react-native-motion-kit/text-motion
1
+ # React Native Text Motion
2
2
 
3
- React Native에서 고성능 split text animation을 만들기 위한 라이브러리입니다. Reanimated 기반으로 동작하고, 나중에 다른 renderer를 붙일 수 있도록 설계되어 있습니다.
3
+ > [English](https://github.com/react-native-motion-kit/react-native-text-motion/blob/main/packages/text-motion/README.md) | 한국어
4
4
 
5
- English documentation: [README.md](./README.md)
5
+ <div align="center">
6
+ <img src="https://raw.githubusercontent.com/react-native-motion-kit/react-native-text-motion/main/docs/docs/public/logo.png" width="300" alt="React Native Motion Kit logo" />
7
+ </div>
6
8
 
7
- `text-motion`은 짧은 제목, 라벨, 온보딩 문구, 제품 설명 문구처럼 token 단위 motion이 필요한 UI 텍스트를 위한 패키지입니다.
9
+ ## 소개
8
10
 
9
- 이 패키지는 아래처럼 prop을 많이 받는 단일 wrapper가 아닙니다.
11
+ `@react-native-motion-kit/text-motion`은 Reanimated 기반의 React Native 텍스트 애니메이션 라이브러리입니다. 짧은 제목, 레이블, 온보딩 문구에 맞춰 텍스트 분할 방식, 렌더러, 타임라인, 효과를 조합하고 재사용 가능한 컴포넌트를 만듭니다.
10
12
 
11
- ```tsx
12
- <AnimatedText text="Hello" animation="fadeIn" delay={100} />
13
- ```
14
-
15
- 대신 text splitting 전략, token renderer, timeline, effect를 조합해서 재사용 가능한 animated text component를 만들 수 있게 합니다.
16
-
17
- ```txt
18
- split -> layout(renderer) -> timeline -> effect -> motion/accessibility -> component
19
- ```
20
-
21
- 빠르게 쓰고 싶으면 preset을 사용하면 되고, 세부 동작을 잡고 싶으면 텍스트를 어떻게 쪼갤지, token을 어떻게 렌더링할지, 각 visible token이 언제 시작할지, 어떤 motion을 적용할지를 직접 조합하면 됩니다.
22
-
23
- TypeScript에서는 `.layout(...)`으로 renderer를 선택한 뒤에만 `.component()`를 호출할 수 있습니다. 아직 renderer를 정하지 않은 recipe를 확인하거나 전달해야 한다면 `.recipe()`는 layout 전에도 사용할 수 있습니다.
24
-
25
- ## 설치
26
-
27
- ```sh
28
- pnpm add @react-native-motion-kit/text-motion react-native-reanimated react-native-worklets
29
- ```
30
-
31
- MVP의 peer dependency minimum은 다음과 같습니다.
32
-
33
- - `react-native-reanimated`: `>=4.0.0`
34
- - `react-native-worklets`: `>=0.5.0`
35
-
36
- 앱에서는 Reanimated와 Worklets의 공식 설정을 따라야 합니다. Reanimated 4의 New Architecture 요구사항도 함께 확인해야 합니다.
37
-
38
- ## 빠른 시작
39
-
40
- 좋은 기본값이 필요하면 preset을 사용할 수 있습니다.
13
+ ### 주요 기능
41
14
 
42
- ```tsx
43
- import { editorialRise } from '@react-native-motion-kit/text-motion/presets';
44
-
45
- const HeroReveal = editorialRise().component();
46
-
47
- export function HeroTitle() {
48
- return <HeroReveal>Design motion that feels native</HeroReveal>;
49
- }
50
- ```
15
+ - **조합 가능한 레시피** — 분할, 레이아웃, 타이밍, 효과를 TypeScript API로 구성합니다.
16
+ - **단어와 글자 애니메이션** — `nativeText()`로 단어나 그래핌을 움직이고 `typewriter()`로 한 글자씩 표시합니다.
17
+ - **실제 줄 기준 모션** — `overlayText()`와 `lineReveal()`로 React Native가 렌더링한 줄을 드러냅니다.
18
+ - **재생 제어** — 이벤트에서 play/replay/reset/stop을 실행하거나 외부 Reanimated shared value로 진행률을 제어합니다.
19
+ - **접근성** — 기본적으로 전체 문장을 하나의 레이블로 제공하고 장식용 토큰은 스크린 리더에서 숨깁니다. 모션 줄이기 설정도 반영합니다.
20
+ - **기본 프리셋** — 프리셋으로 시작하고 필요에 따라 레시피를 조정합니다.
51
21
 
52
- `editorialRise()`는 recipe factory입니다. 내부적으로 문장을 단어 단위로 나누고, `nativeText()`로 렌더링하며, 가운데 근처에서 시작하는 stagger와 `rise`, `fade`, 약한 `scale`을 조합합니다.
22
+ ## 빠르게 시작하기
53
23
 
54
- ## 직접 recipe 만들기
24
+ ### 문서
55
25
 
56
- 세부 동작을 직접 제어하고 싶으면 recipe를 만들면 됩니다.
26
+ [한국어 문서](https://react-native-text-motion.pages.dev/ko/) · [설치](https://react-native-text-motion.pages.dev/ko/guide/getting-started/installation.html) · [빠른 시작](https://react-native-text-motion.pages.dev/ko/guide/getting-started/quick-start.html)
57
27
 
58
- ```tsx
59
- import {
60
- defineTextMotion,
61
- fade,
62
- nativeText,
63
- rise,
64
- scale,
65
- stagger,
66
- words,
67
- } from '@react-native-motion-kit/text-motion';
28
+ ### 예제
68
29
 
69
- const ProductHeadline = defineTextMotion()
70
- .split(words())
71
- .layout(nativeText())
72
- .timeline(stagger(0.032, { from: 'center' }))
73
- .effect(rise({ y: 14 }).and(fade()).and(scale({ from: 0.98 })))
74
- .motion({ kind: 'timing', options: { duration: 320 } })
75
- .component();
30
+ - [예제 모음](https://react-native-text-motion.pages.dev/ko/guide/usage/examples.html) — 동작을 확인하고 예제 코드를 살펴봅니다.
31
+ - [Expo 예제 앱](https://github.com/react-native-motion-kit/react-native-text-motion/tree/main/example) — 레시피, 재생 제어, 네이티브 줄 애니메이션을 실행합니다.
76
32
 
77
- export function Header() {
78
- return <ProductHeadline>Design motion that feels native</ProductHeadline>;
79
- }
80
- ```
33
+ ### AI
81
34
 
82
- `0.032`는 초 단위입니다. 선택한 origin 기준으로 visible token마다 32ms씩 시작 시간이 달라집니다. `duration`은 각 token이 자기 delay 이후 움직이는 시간입니다.
35
+ [AI 도구와 함께 사용하기](https://react-native-text-motion.pages.dev/ko/guide/getting-started/ai.html)에서 문서 활용 방법을 확인하세요.
83
36
 
84
- ### Motion Options
37
+ - [llms.txt](https://react-native-text-motion.pages.dev/ko/llms.txt) — 문서 제목과 링크를 담은 색인입니다.
38
+ - [llms-full.txt](https://react-native-text-motion.pages.dev/ko/llms-full.txt) — 전체 문서를 하나의 텍스트 파일로 제공합니다.
39
+ - [영문 색인](https://react-native-text-motion.pages.dev/llms.txt) · [영문 전체 문서](https://react-native-text-motion.pages.dev/llms-full.txt)
85
40
 
86
- `.motion()`은 작은 discriminated config를 받습니다.
41
+ ### 설치
87
42
 
88
- ```tsx
89
- .motion({ kind: 'timing', options: { duration: 320 } });
90
- .motion({ kind: 'spring', options: { damping: 14, stiffness: 160 } });
43
+ ```sh
44
+ pnpm add @react-native-motion-kit/text-motion react-native-reanimated react-native-worklets
91
45
  ```
92
46
 
93
- `timing` options는 Reanimated `withTiming` config를 따르고, `spring` options는 Reanimated `withSpring` config를 따릅니다. `reduceMotion`은 여기서 받지 않습니다. text-motion에서는 reduced-motion 동작을 accessibility policy에서 제어합니다.
47
+ peer dependency 최소 버전은 Reanimated `4.0.0`, Worklets `0.5.0`입니다. 이 최소값만으로 네이티브 의존성의 호환성이 보장되지는 않습니다. [설치 가이드](https://react-native-text-motion.pages.dev/ko/guide/getting-started/installation.html)와 [Reanimated 공식 설정 안내](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/)에 따라 앱의 React Native 또는 Expo 버전에 맞게 설정하세요. Reanimated 4는 New Architecture가 필요합니다.
94
48
 
95
- Custom `easing`처럼 function-valued option은 animation을 유지할지 다시 실행할지 판단할 때 reference로 비교합니다. Parent rerender에서도 progress를 유지하려면 같은 function reference를 재사용하거나 hoist하세요. 새 function을 만들면 motion config가 바뀐 것으로 보고 replay됩니다.
49
+ ### 기본 사용법
96
50
 
97
- `.motion()`은 text-motion이 소유하는 playback에 적용됩니다. 기본 mount autoplay와 `controls` command에서 사용됩니다. Component에 raw `progress`를 넘기면 앱이 playback을 소유하므로 해당 instance에는 `.motion()`이 적용되지 않습니다.
98
-
99
- ## Presets
100
-
101
- Preset은 string 이름이 아니라 조합 가능한 recipe factory입니다.
51
+ 프리셋에서 컴포넌트를 만들고 애니메이션을 적용할 텍스트를 전달하세요.
102
52
 
103
53
  ```tsx
104
- import {
105
- editorialRise,
106
- gentleEmphasis,
107
- softWave,
108
- } from '@react-native-motion-kit/text-motion/presets';
54
+ import { editorialRise } from '@react-native-motion-kit/text-motion/presets';
109
55
 
56
+ // 재사용할 컴포넌트는 렌더 함수 밖에서 만드세요.
110
57
  const HeroReveal = editorialRise().component();
111
- const ProductCopy = softWave().component();
112
- const LabelEmphasis = gentleEmphasis().component();
113
- ```
114
-
115
- - `editorialRise()` - 큰 제목용 reveal입니다. 가운데 근처에서 시작해서 양옆으로 퍼집니다.
116
- - `softWave()` - 제품 문구에 어울리는 reveal입니다. sine wave 기반 delay를 사용합니다.
117
- - `gentleEmphasis()` - 작은 라벨용 강조 preset입니다. fade와 midpoint pulse를 사용합니다.
118
-
119
- ## Splitters
120
-
121
- Splitter는 token의 단위를 결정합니다.
122
-
123
- Splitter factory는 입력을 타입으로 검증하지만, 반환되는 splitter는 opaque handle입니다. `defineTextMotion().split(...)`에 전달해서 사용하면 되고, `kind`나 내부 `split` 함수 같은 descriptor field는 root 사용자 API에 포함하지 않습니다.
124
-
125
- ### Words
126
-
127
- ```tsx
128
- const WordReveal = defineTextMotion()
129
- .split(words())
130
- .layout(nativeText())
131
- .timeline(stagger(0.034))
132
- .effect(fade().and(rise({ y: 10 })))
133
- .component();
134
- ```
135
-
136
- `words()`는 spacing을 보존하기 위해 공백 token도 만듭니다. 하지만 공백은 timeline delay slot을 소비하지 않습니다. `"Words move as groups"`에서는 visible word만 motion index `0, 1, 2, 3`으로 계산됩니다.
137
-
138
- 명시적인 newline도 layout에 보존됩니다. 예를 들어
139
- `"First line\nSecond line"`은 `nativeText()`에서 두 줄로 hard break되어
140
- 렌더링되고, newline 자체는 여전히 motion index를 소비하지 않습니다. 이것은
141
- React Native가 화면 너비 때문에 자동 줄바꿈한 실제 줄을 측정하는 기능과는
142
- 다릅니다.
143
-
144
- `nativeText()`는 title, label, onboarding copy, 짧은 product copy의 split
145
- motion을 위한 renderer입니다. Animated token은 animated container와 내부
146
- `Text`로 렌더링됩니다. Generated token `testID`는 그 container를 가리키고,
147
- `allowFontScaling` 같은 text rendering prop은 내부 `Text`로 전달됩니다.
148
- React Native `Text`의 완전한 drop-in 대체품은 아니며, 매우 긴 grapheme
149
- animation은 아직 stable performance promise가 아니라 stress case입니다.
150
-
151
- ### Graphemes
152
-
153
- ```tsx
154
- const GlyphReveal = defineTextMotion()
155
- .split(graphemes())
156
- .layout(nativeText())
157
- .timeline(stagger(0.014, { from: 'center' }))
158
- .effect(fade().and(scale({ from: 0.86 })))
159
- .component();
160
- ```
161
-
162
- `graphemes()`는 사용자가 글자 하나처럼 인식하는 단위로 motion을 적용합니다. `text.split('')`보다 accent, 한글, emoji sequence, CJK, RTL 샘플에 더 안전합니다.
163
-
164
- ### Lines
165
-
166
- ```tsx
167
- const LineReveal = defineTextMotion()
168
- .split(lines())
169
- .layout(nativeText())
170
- .timeline(stagger(0.12))
171
- .effect(slide({ y: 16 }).and(fade()))
172
- .component();
173
- ```
174
-
175
- `lines()`는 experimental 기능이며 newline-only splitter입니다. 명시적인 `\n` 기준으로만 나눕니다. React Native가 화면에서 자동 줄바꿈한 실제 줄을 측정하지는 않습니다.
176
-
177
- ### Rendered Line Reveal
178
-
179
- Motion unit이 원문 token이 아니라 React Native가 width, font, font scale, alignment, language shaping, wrapping을 적용한 뒤 실제로 렌더링한 줄이어야 한다면 `overlayText()`와 `lineReveal()`을 사용하세요.
180
-
181
- ```tsx
182
- import {
183
- defineTextMotion,
184
- lineReveal,
185
- overlayText,
186
- stagger,
187
- } from '@react-native-motion-kit/text-motion';
188
-
189
- const HeroLines = defineTextMotion()
190
- .layout(overlayText())
191
- .timeline(stagger(0.08))
192
- .effect(lineReveal({ y: 18 }))
193
- .motion({ kind: 'timing', options: { duration: 420 } })
194
- .component();
195
58
 
196
59
  export function HeroTitle() {
197
- return <HeroLines>Design motion that feels native</HeroLines>;
198
- }
199
- ```
200
-
201
- `overlayText()`에는 `.split(...)`을 호출하지 않습니다. `overlayText()`는 layout 이후 rendered line을 소유하고, `nativeText()`는 원문 word, grapheme, custom, explicit newline token을 소비합니다. TypeScript는 맞지 않는 chain을 제거하고, runtime capability check는 unsafe JavaScript 또는 cast로 만든 잘못된 조합도 거부합니다.
202
-
203
- Responsive hero title, marketing heading, onboarding headline, 짧은 product copy처럼 최종 시각적 줄바꿈이 중요한 곳에 사용하세요. Word 또는 grapheme motion에는 `nativeText()`를 사용합니다. 작성자가 넣은 `\n`을 token boundary로 쓰고 싶을 때만 `lines()`를 사용하세요. `lines()`는 자동 줄바꿈을 찾지 않습니다.
204
-
205
- `overlayText()`는 각 rendered line frame에 적용되는 built-in style-transform effect(`fade`, `rise`, `slide`, `shake`, `scale`, `pulse`)도 받습니다. 내부적으로 `lineReveal()`은 opacity를 line frame에 적용하고, vertical reveal offset만 line mask 안의 paragraph copy에 적용합니다. 그래서 `lineReveal().and(scale())`, 더 큰 finite `scale()`, `pulse()`가 measured line center를 기준으로 커지며 renderer 자신의 mask에 잘리지 않습니다.
206
-
207
- Blank 또는 whitespace-only rendered line은 시각적 spacing으로 보존되지만 motion index를 소비하지 않습니다. `"First\n\nSecond"` 같은 title은 blank line을 유지하면서 두 nonblank line이 연속된 stagger slot으로 reveal됩니다.
208
-
209
- Relayout 동작은 예측 가능하게 고정되어 있습니다.
210
-
211
- - 동일한 line measurement는 replay를 만들지 않습니다.
212
- - geometry만 바뀐 relayout은 현재 run을 보존합니다.
213
- - line topology가 바뀌면 component-owned playback이 한 번 다시 시작합니다.
214
- - external `progress`는 앱이 소유한 shared value를 유지하고 새 geometry에 적용합니다.
215
- - reduced motion은 measurement 또는 playback overlay 없이 최종 readable source text를 렌더링합니다.
216
- - malformed 또는 unsupported native geometry는 readable final source text로 fallback합니다.
217
-
218
- `overlayText()`는 움직이는 rendered line마다 full paragraph copy 하나를 만듭니다. 성능 비용은 원문 word/grapheme 수가 아니라 visible rendered line 수에 비례합니다. Title, heading, product copy에 맞춘 renderer로 보세요. 같은 case family에서 iOS와 Android evidence가 모두 있기 전에는 measured performance라고 주장하지 않습니다.
219
-
220
- Valid line geometry가 생기기 전에는 source paragraph가 layout을 소유합니다. Initial scale이 정확히 `1`이면 pending source는 다른 renderer와 같은 combined initial opacity/transform을 유지합니다. Initial scale이 정확히 `1`이 아니면 geometry가 valid해질 때까지 pending source를 시각적으로 숨겨, 처음 보이는 scaled frame이 measured line center를 사용하게 합니다.
221
-
222
- Renderer 자신의 line mask 때문에 생기던 self-clipping은 해결됩니다. 큰 transform을 위한 layout 공간을 추가로 예약하지는 않으며, parent 또는 ancestor의 `overflow: hidden` clipping도 우회하지 않습니다. 이는 React Native transform의 정상적인 limitation입니다.
223
-
224
- ### Custom
225
-
226
- ```tsx
227
- const CustomReveal = defineTextMotion()
228
- .split(custom((input) => input.match(/\S+|\s+/g) ?? []))
229
- .layout(nativeText())
230
- .timeline(stagger(0.028, { from: 'edges' }))
231
- .effect(rise({ y: 8 }).and(fade()))
232
- .component();
233
- ```
234
-
235
- `custom()`은 제품에 맞는 token boundary가 필요할 때 사용합니다. 예를 들면 hashtag, mention, 가격, 문장부호 그룹, 도메인 특화 용어를 직접 나눌 수 있습니다. custom splitter는 string이나 token object를 반환할 수 있습니다.
236
-
237
- ```tsx
238
- custom((input) => [
239
- {
240
- text: input,
241
- metadata: { kind: 'headline' },
242
- sourceRange: { start: 0, end: input.length },
243
- },
244
- ]);
245
- ```
246
-
247
- Custom splitter가 string만 반환하면 `text-motion`은 원문에서 왼쪽부터 순서대로 각 token의 위치를 찾습니다. `input.match(...)`처럼 원문 순서 그대로 나누는 대부분의 splitter는 이 방식으로 충분합니다.
248
-
249
- splitter가 token 순서를 바꾸거나, 같은 text를 의도적으로 반복하거나, 원문에서의 정확한 위치가 중요하다면 string 대신 `sourceRange`가 포함된 token object를 반환하세요.
250
-
251
- ## Timelines
252
-
253
- Timeline은 token별 시작 delay를 초 단위로 계산합니다.
254
-
255
- ```tsx
256
- stagger(0.04);
257
- stagger(0.04, { from: 'center' });
258
- stagger(0.04, { from: 'end' });
259
- stagger(0.04, { from: 'edges' });
260
- wave({ amplitude: 0.06, wavelength: 5 });
261
- sequence(stagger(0.02), wave({ amplitude: 0.04, wavelength: 4 }));
262
- parallel(stagger(0.08), wave({ amplitude: 0.05, wavelength: 3 }));
263
- ```
264
-
265
- - `stagger(step)` - 선택한 origin에서 고정 간격 delay를 만듭니다.
266
- - `wave()` - token index에 따라 sine wave delay를 만듭니다.
267
- - `sequence()` - 여러 timeline delay를 더합니다.
268
- - `parallel()` - 여러 timeline 중 가장 빠른 delay를 사용합니다.
269
-
270
- Timeline factory는 입력 options를 타입으로 검증하지만, 반환되는 timeline은 opaque handle입니다. `.timeline(...)`에 전달해서 사용하면 되고, `delayFor`, `name`, `options` 같은 descriptor field는 root 사용자 API에 포함하지 않습니다.
271
-
272
- Timeline 숫자 입력은 finite number여야 합니다. `stagger()`의 step은 0 이상, `wave()`의 amplitude는 0 이상이어야 하며, `wave()`의 wavelength는 0보다 커야 합니다.
273
-
274
- ## Effects
275
-
276
- Effect는 각 token이 어떻게 움직이는지 정의합니다.
277
-
278
- ```tsx
279
- fade();
280
- rise({ y: 14 });
281
- slide({ x: -12, y: 8 });
282
- scale({ from: 0.92, to: 1 });
283
- pulse({ scale: 1.08 });
284
- shake({ x: 6 });
285
- lineReveal({ y: 18 });
286
- typewriter();
287
- ```
288
-
289
- Effect는 `.and(...)`로 조합할 수 있습니다.
290
-
291
- ```tsx
292
- rise({ y: 14 }).and(fade()).and(scale({ from: 0.98 }));
293
- ```
294
-
295
- `pulse()`는 midpoint emphasis effect입니다. 애니메이션 중간에 token이 커졌다가 최종 scale로 돌아옵니다. 텍스트가 다른 크기에서 시작하거나 끝나야 한다면 `scale()`을 사용하세요.
296
-
297
- Effect factory는 입력 options를 타입으로 검증하지만, 반환되는 effect는 opaque handle입니다. Effect는 `.and(...)`로 조합하면 되고, `name`, `options`, `requiredCapabilities` 같은 descriptor field는 root 사용자 API에서 숨깁니다.
298
-
299
- Built-in effect의 숫자 입력은 finite number여야 합니다. `rise({ y })`, `slide({ x, y })`, `shake({ x })`처럼 방향을 의미하는 offset option은 음수도 허용합니다.
300
-
301
- `lineReveal()`은 `overlayText()`가 제공하는 `line-mask` capability가 필요합니다. `nativeText()`와는 호환되지 않습니다.
302
-
303
- ## Typewriter
304
-
305
- 짧은 인사말, 온보딩 문구, 라벨을 한 글자씩 보여주면서 완성 문장의 공간과 줄바꿈을 유지하고 싶다면 `typewriter()`를 사용하세요. 패키지 root에서 가져오는 옵션 없는 effect이며, preset이나 별도 renderer가 아닙니다.
306
-
307
- ```tsx
308
- import {
309
- defineTextMotion,
310
- graphemes,
311
- nativeText,
312
- parentLabelPolicy,
313
- stagger,
314
- typewriter,
315
- useTextMotionControls,
316
- } from '@react-native-motion-kit/text-motion';
317
- import { Button } from 'react-native';
318
-
319
- const TypewriterText = defineTextMotion()
320
- .split(graphemes())
321
- .layout(nativeText())
322
- .timeline(stagger(0.04))
323
- .effect(typewriter())
324
- .motion({ kind: 'timing', options: { duration: 0 } })
325
- .accessibility(parentLabelPolicy({ reducedMotion: 'final-state' }))
326
- .component();
327
-
328
- export function Greeting() {
329
- const controls = useTextMotionControls();
330
-
331
- return (
332
- <>
333
- <TypewriterText controls={controls}>안녕 세계! 👩🏽‍💻</TypewriterText>
334
- <Button title="Replay" onPress={controls.replay} />
335
- </>
336
- );
60
+ return <HeroReveal>Design motion that feels native</HeroReveal>;
337
61
  }
338
62
  ```
339
63
 
340
- `stagger(0.04)`의 단위는 초이므로 보이는 글자가 40ms 간격으로 시작합니다. 첫 글자는 첫 playback update에서 나타납니다. 공백과 명시적인 줄바꿈은 자리를 유지하되 delay slot을 소비하지 않고, 문장부호는 일반 글자처럼 slot을 하나 소비합니다. 실제 표시 시각은 기기의 프레임에 맞춰집니다. `duration: 0`은 autoplay와 controls 재생에서 각 글자를 즉시 드러냅니다.
341
-
342
- 기존 controls로 replay, reset, stop을 실행할 수 있습니다. `stop()`은 현재 상태를 유지하지만, 이후 `play()`가 남은 타이핑 일정을 정확히 이어 주는 것은 아닙니다. 다른 문장을 처음부터 모두 다시 타이핑하려면 React `key`를 바꿔 remount하세요. 텍스트 append는 streaming API가 아닙니다.
343
-
344
- 외부 재생에는 `controls` 대신 `progress={progress}`를 전달합니다. 해당 instance에서는 `.motion()`을 실행하지 않습니다. **Progress는 표시된 글자의 비율이 아니라 effect timeline의 진행률입니다.** 보이는 글자 10개에 `stagger(0.05)`를 쓰면 progress가 약 `0.310345`를 넘을 때 모두 드러나므로, `0.5`가 다섯 글자를 뜻하지 않습니다. Progress를 줄이면 글자가 다시 숨겨질 수 있습니다. 실제 시간상 일정한 간격으로 드러내려면 외부 timing curve를 linear로 설정하세요.
345
-
346
- `typewriter()`에 `rise()`나 `fade()`를 조합할 수도 있습니다. 각 글자는 local progress가 양수가 될 때까지 숨겨지고, 시작한 뒤에는 다른 effect가 계속 적용됩니다. `fade()`를 더하면 의도적으로 서서히 나타납니다. 조합 순서를 바꿔도 이 표시 규칙은 유지됩니다. 글자가 나타난 뒤 움직이는 모습까지 보이려면 `.motion()`에 0보다 큰 duration을 지정하세요.
347
-
348
- 이 effect는 `nativeText()`만 지원합니다. 해당 renderer가 만든 완성 token layout을 유지하며, 단일 `Text`와 같은 조판·줄바꿈, 커서, 실제 문자열의 글자별 추가·삭제, streaming은 제공하지 않습니다. 내장 grapheme fallback은 이제 `한` 같은 분해 한글 음절을 원문 정규화 없이 한 단위로 보존하지만, Unicode 전체 grapheme 규칙을 구현한 것은 아닙니다. 복잡한 script에는 앱이 제공하는 `Intl.Segmenter`나 `custom()` splitter가 필요할 수 있습니다. 긴 문장이나 많은 row는 target device에서 먼저 성능을 측정하세요.
349
-
350
- ### 등장 방식과 motion 선택
351
-
352
- `typewriter()`는 splitter를 바꾸거나 타이핑 속도를 소유하지 않습니다. 글자별로 보여 주려면 `graphemes()`, 단어별로 보여 주려면 `words()`를 선택하세요. Timeline은 시작 간격을 정하고, `.motion()`은 autoplay 또는 controls mode에서 각 token의 부가 motion을 정합니다. Stagger origin을 바꾸거나 `wave()`를 사용하면 해당 timeline 순서로 나타나며, 앞에서부터 타이핑하는 순서를 보장하지 않습니다.
353
-
354
- 예를 들어 다음 effect와 motion을 사용하면 글자가 나타난 뒤 120ms 동안 위로 움직입니다.
355
-
356
- ```tsx
357
- .effect(typewriter().and(rise({ y: 6 })))
358
- .motion({ kind: 'timing', options: { duration: 120 } })
359
- ```
360
-
361
- `fade()`와 조합하면 opacity animation은 그대로 적용되면서 시작 전의 글자는 숨겨집니다. `typewriter()`를 두 번 조합해도 표시 동작이나 playback clock이 하나 더 생기지 않습니다.
362
-
363
- 외부 `progress`에서 `0`은 animated 글자를 숨기고 `1`은 최종 상태를 렌더링합니다. 기존 mapping은 token span `1.0`을 사용합니다. Token delay가 `d`, 가장 큰 delay가 `D`라면 local progress는 `clamp(progress * (D + 1) - d, 0, 1)`이며, 이 값이 양수인 글자를 보여 줍니다. 최대 progress를 기억하지 않으므로 각 글자의 시작 지점 이전으로 되돌리면 다시 숨겨집니다. Spring이 `1`을 overshoot하는 것만으로 이미 나타난 글자가 숨겨지지는 않고, 해당 글자의 시작 threshold를 역방향으로 지나야 합니다.
364
-
365
- 예제의 `parentLabelPolicy({ reducedMotion: 'final-state' })`는 reduced motion이 켜지면 완성 문장을 보여 줍니다. 외부 progress가 아직 `0`인 instance에도 적용됩니다. Screen reader에는 부모의 전체 문장이 한 번 노출되고 장식용 글자 node는 숨겨지며, 글자마다 읽기 알림을 보내지 않습니다.
366
-
367
- ## Native Renderer Contract
368
-
369
- `nativeText()`는 stable MVP renderer입니다. Animated token은 React Native에서 `rise()`, `slide()`, `scale()`, `pulse()` 같은 transform effect가 실제로 보이도록 animated `View` container와 내부 `Text`로 렌더링됩니다. Static token은 그대로 plain `Text`로 렌더링됩니다.
370
-
371
- `nativeText()`는 `.layout(...)`에 전달하는 opaque renderer handle을 반환합니다. `kind`, `capabilities`, 구현 `Component` 같은 descriptor field는 root 사용자 API에서 숨깁니다.
372
-
373
- 이 renderer는 React Native `Text`의 완전한 drop-in 대체품이 아닙니다. 정확한 platform text layout보다 token별 transform의 신뢰성을 우선합니다. 실제 rendered line 기준 motion이 필요하면 `overlayText()`를 사용하세요. RN line-to-token mapping은 deferred 상태입니다.
374
-
375
- `nativeText()`로 만든 component는 의도적으로 좁은 prop surface만 지원합니다.
376
-
377
- - `style`
378
- - `testID`
379
- - `nativeID`
380
- - `allowFontScaling`
381
- - `maxFontSizeMultiplier`
382
- - `controls`
383
- - `progress`
384
- - `accessibilityLabel`, `accessibilityHint`, `accessibilityRole`, `accessibilityState`, `accessibilityValue`, `accessibilityActions`, `accessibilityLanguage`, `onAccessibilityAction`
385
-
386
- 단일 native text node가 필요한 layout/interaction prop은 stable MVP 계약에 포함되지 않습니다. 예: `numberOfLines`, `ellipsizeMode`, `lineBreakMode`, `onTextLayout`, `selectable`, text `onPress`.
387
-
388
- 테스트에서 token별 `testID`가 필요하면 `nativeText({ testIDPrefix: 'word' })`를 사용하세요. Generated token `testID`는 animated token container를 가리키고, `allowFontScaling`, `maxFontSizeMultiplier` 같은 text rendering prop은 내부 `Text`로 전달됩니다.
389
-
390
- ```tsx
391
- const TestableReveal = defineTextMotion()
392
- .split(words())
393
- .layout(nativeText({ testIDPrefix: 'word' }))
394
- .effect(fade())
395
- .component();
396
- ```
397
-
398
- ### Renderer Performance Thresholds
399
-
400
- `nativeText()`는 title, label, onboarding copy, product copy처럼 사용자가 token motion을 실제로 볼 수 있는 UI text를 위한 기본 renderer입니다. 긴 문단이나 수백 개의 grapheme token을 비용 없이 처리하는 renderer로 설계한 것은 아닙니다.
401
-
402
- Example app에는 실제 경계를 확인하기 위한 `Playback -> Renderer Performance` probe가 있습니다. Word, grapheme, shared-row, replay, progress 케이스를 포함합니다. React Native Perf Monitor, Flashlight, Xcode Instruments, Android Studio profiler처럼 FPS를 확인할 수 있는 도구와 함께 사용하세요.
403
-
404
- 결과를 기록할 때는 이 용어를 사용합니다.
405
-
406
- - `measured`: 같은 case family를 iOS와 Android에서 모두 확인한 상태
407
- - `provisional`: 한 플랫폼만 확인했거나 simulator/emulator/dev mode만 확인한 상태
408
- - `unknown`: 아직 측정하지 않은 상태
409
-
410
- 판단 기준은 보수적으로 잡습니다. 일반적인 title, label, product copy workload가 부드럽다면 `nativeText()`를 default renderer로 유지하고, grapheme-heavy boundary는 문서 가이드로 남깁니다. 일반 workload도 끊긴다면 새 effect를 추가하기 전에 renderer optimization 계획을 먼저 세웁니다. Stress grapheme case만 실패한다면 default renderer가 틀렸다는 증거로 보지 않고 stress boundary로 문서화합니다.
411
-
412
- ## Playback Lifecycle
413
-
414
- 기본 playback은 자동 lifecycle 기반입니다.
415
-
416
- - 처음 mount되면 animated visible token은 initial style에서 target style로 자동 실행됩니다.
417
- - 같은 text와 같은 recipe로 parent rerender가 일어나면 진행 중인 progress를 유지합니다.
418
- - text가 바뀌면 token shape가 같아도 해당 token animation은 다시 실행됩니다.
419
- - effect, timeline, motion config가 바뀌면 affected animated token은 reset되고 변경된 입력으로 다시 autoplay됩니다.
420
- - whitespace/static token은 layout text를 보존하지만 animation하지 않고 motion index를 소비하지 않습니다.
421
- - `parentLabelPolicy({ reducedMotion: 'final-state' })`는 reduced-motion 사용자에게 initial animated state를 깜빡이지 않고 final style을 유지합니다.
422
-
423
- MVP의 text change는 enter-only입니다. 이전 text를 남겨서 exit animation, crossfade, token diff를 만들지는 않습니다. Component에 `controls`가 있으면 이후 command는 현재 렌더링된 text를 대상으로 동작합니다. Component에 `progress`가 있으면 앱이 소유한 shared value가 새 text의 시각 상태를 결정합니다. 예를 들어 `progress.value`가 `0.5`일 때 새 문장이 렌더링되면 autoplay를 시작하지 않고 그에 맞는 중간 상태로 보입니다.
424
-
425
- Example app은 animation을 반복 확인하기 쉽도록 demo player를 remount할 수 있습니다. 이건 demo UI 동작이지, 앱에서 권장하는 public API 패턴은 아닙니다.
426
-
427
- ### Playback Controls
428
-
429
- 외부 이벤트가 text motion component에게 play, replay, reset, stop을 지시해야 한다면 `controls`를 사용하세요.
430
-
431
- 자주 쓰이는 예시는 이런 경우입니다.
432
-
433
- - 버튼을 누른 뒤 headline이 reveal되어야 할 때
434
- - onboarding copy가 현재 step에 맞춰 진행되어야 할 때
435
- - screen focus를 받을 때 title을 다시 보여주고 싶을 때
436
- - form field가 valid가 된 뒤 label이 나타나야 할 때
437
- - hero title과 subtitle을 한 번에 replay하고 싶을 때
438
-
439
- 이런 경우 앱은 이벤트를 소유하고, text component는 실행 방식을 소유합니다. Component는 자기 recipe, timeline, effect, `.motion()` 설정으로 playback을 실행합니다.
64
+ 직접 레시피를 조합할 수도 있습니다. `stagger()`의 시간 단위는 초이며, `.component()`를 호출하기 전에 `.layout(...)`으로 렌더러를 선택해야 합니다.
440
65
 
441
66
  ```tsx
442
67
  import {
@@ -445,254 +70,35 @@ import {
445
70
  nativeText,
446
71
  rise,
447
72
  stagger,
448
- useTextMotionControls,
449
73
  words,
450
74
  } from '@react-native-motion-kit/text-motion';
451
- import { Button } from 'react-native';
452
75
 
453
- const ReplayableReveal = defineTextMotion()
76
+ const WordReveal = defineTextMotion()
454
77
  .split(words())
455
78
  .layout(nativeText())
456
79
  .timeline(stagger(0.04))
457
- .effect(rise({ y: 12 }).and(fade()))
458
- .motion({ kind: 'timing', options: { duration: 420 } })
80
+ .effect(fade().and(rise({ y: 12 })))
459
81
  .component();
460
-
461
- export function Headline() {
462
- const controls = useTextMotionControls();
463
-
464
- return (
465
- <>
466
- <ReplayableReveal controls={controls}>
467
- Replay without remounting
468
- </ReplayableReveal>
469
-
470
- <Button title="Replay" onPress={controls.replay} />
471
- <Button title="Reset" onPress={controls.reset} />
472
- </>
473
- );
474
- }
475
82
  ```
476
83
 
477
- `controls`는 progress value가 아니라 command channel입니다.
478
-
479
- - `play()`는 현재 progress에서 final state로 이동합니다.
480
- - `replay()`는 initial state로 돌아간 뒤 timeline delay와 `.motion()`으로 다시 실행합니다.
481
- - `reset()`은 진행 중인 playback을 취소하고 initial state로 돌아갑니다.
482
- - `stop()`은 진행 중인 playback을 취소하고 현재 보이는 progress를 유지합니다.
84
+ 자세한 내용은 [레시피 API](https://react-native-text-motion.pages.dev/ko/api/recipe.html), [렌더러](https://react-native-text-motion.pages.dev/ko/api/renderers.html), [재생 제어](https://react-native-text-motion.pages.dev/ko/guide/usage/playback.html)를 참고하세요. `controls`와 외부 `progress`는 함께 사용할 수 없습니다.
483
85
 
484
- 하나의 controls object를 여러 text motion component에 넘길 수 있습니다. 이 경우 command는 연결된 모든 component에 broadcast되고, 각 component는 자기 recipe로 command를 실행합니다.
86
+ ## 사용 범위
485
87
 
486
- `controls`와 `progress`는 함께 사용할 수 없습니다. 버튼, screen focus처럼 불연속 이벤트에는 `controls`를 사용하고, scroll/gesture처럼 연속 값에 따라 움직여야 하면 `progress`를 사용하세요.
88
+ 짧은 UI 텍스트를 위한 라이브러리이며 네이티브 `Text`의 모든 기능을 대체하지 않습니다. `nativeText()`는 소스 토큰을, `overlayText()`는 실제 렌더링된 줄을 애니메이션 단위로 사용합니다. `overlayText()`에는 `.split(...)`을 추가하지 마세요. `lines()`는 명시적인 `\n`만 분리합니다.
487
89
 
488
- Text Motion은 playback을 위한 context/provider API나 public component ref API를 제공하지 않습니다. 연결 관계가 JSX에서 보이도록 `controls={controls}`로 명시적으로 전달하세요.
90
+ 긴 문단이나 많은 애니메이션 행은 대상 기기에서 측정하세요. 브라우저 예제는 네이티브 성능 측정 자료가 아닙니다. [성능 안내](https://react-native-text-motion.pages.dev/ko/guide/usage/performance.html)와 [접근성 안내](https://react-native-text-motion.pages.dev/ko/guide/usage/accessibility.html)를 참고하세요.
489
91
 
490
- 성능 관점에서 `controls`는 짧은 UI 텍스트에 맞춰 설계되어 있습니다. Title, label, 짧은 product sentence라면 playback work가 작고 예측 가능합니다. 긴 문단, 많은 row, 글자 단위로 쪼개는 grapheme split 텍스트에 쓰려면 example의 `Renderer Performance` probe를 확인하고 target device에서 먼저 측정하세요.
92
+ ## 프로젝트 방향
491
93
 
492
- 큰 workload가 중요해지면 renderer 내부 구현은 바뀔 수 있습니다. 그래서 example stress case는 controls 구현 방식에 대한 public promise가 아니라 profiling aid로 보는 편이 안전합니다.
94
+ 우선순위와 기능 아이디어는 [프로젝트 방향·로드맵 Discussion](https://github.com/react-native-motion-kit/react-native-text-motion/discussions/10)에서 논의합니다.
493
95
 
494
- ### Raw Progress
96
+ 라이브러리 확장 전 현재 우선순위와 설계 범위는 [프로젝트 맥락과 로드맵](https://github.com/react-native-motion-kit/react-native-text-motion/blob/main/ROADMAP.ko.md)을 참고하세요.
495
97
 
496
- Text motion이 앱이 이미 소유한 raw value를 따라가야 한다면 `progress`를 사용하세요.
98
+ ## 개발 참여
497
99
 
498
- 자주 쓰이는 예시는 이런 경우입니다.
499
-
500
- - scroll 또는 gesture shared value에 맞춰 text가 따라와야 할 때
501
- - 여러 UI 요소가 같은 shared progress value를 기준으로 함께 움직여야 할 때
502
- - 앱에서 직접 구성한 Reanimated sequence가 문장 전체를 `0`에서 `1`로 밀어야 할 때
503
-
504
- 이런 경우에는 text component가 언제 시작할지 또는 어떤 curve로 움직일지 결정하지 않습니다. 앱이 Reanimated shared value를 소유하고 text component에는 그 값을 넘깁니다.
505
-
506
- ```tsx
507
- import {
508
- defineTextMotion,
509
- fade,
510
- nativeText,
511
- rise,
512
- stagger,
513
- words,
514
- } from '@react-native-motion-kit/text-motion';
515
- import { Button } from 'react-native';
516
- import { useSharedValue, withTiming } from 'react-native-reanimated';
517
-
518
- const ProgressReveal = defineTextMotion()
519
- .split(words())
520
- .layout(nativeText())
521
- .timeline(stagger(0.08))
522
- .effect(rise({ y: 12 }).and(fade()))
523
- .component();
524
-
525
- export function Headline() {
526
- const progress = useSharedValue(0);
527
-
528
- return (
529
- <>
530
- <ProgressReveal progress={progress}>
531
- Progress drives the whole reveal
532
- </ProgressReveal>
533
-
534
- <Button
535
- title="Play"
536
- onPress={() => {
537
- progress.value = withTiming(1, { duration: 720 });
538
- }}
539
- />
540
- </>
541
- );
542
- }
543
- ```
544
-
545
- `progress`는 전체 text motion의 normalized progress입니다.
546
-
547
- - `0`은 initial token style을 렌더링합니다.
548
- - `1`은 모든 animated token을 final style로 렌더링합니다.
549
- - `0..1` 밖의 값은 clamp됩니다.
550
- - finite number가 아닌 값은 `0`처럼 처리합니다.
551
-
552
- `progress`는 단어 하나의 progress가 아니라 문장 전체의 progress라고 보면 됩니다. 예를 들어 `stagger(0.08)`을 쓰면 첫 번째 단어는 이미 끝났는데 뒤쪽 단어는 아직 따라오는 중일 수 있습니다. Renderer는 global shared value를 visible token별 local progress로 바꾸기 때문에 `stagger()`, `wave()`, `sequence()`, `parallel()`의 timing shape가 controlled mode에서도 유지됩니다. 공백은 계속 렌더링되지만 motion index를 소비하지 않습니다.
553
-
554
- `progress`가 있으면 앱이 playback을 소유하고 `.motion()`은 내부 autoplay를 시작하지 않습니다. Playback curve는 shared value를 업데이트하는 곳에서 정합니다.
555
-
556
- ```tsx
557
- progress.value = withTiming(1, { duration: 720 });
558
- progress.value = withSpring(1);
559
- progress.value = 0;
560
- ```
561
-
562
- 이 rendered component에 `progress`를 넘기면 해당 instance에서는 `.motion()`이 적용되지 않습니다. Autoplay 또는 controls-driven version으로도 쓸 때만 `.motion()`을 추가하세요.
563
-
564
- 중요한 제한도 있습니다. Controlled mode는 현재 whole-text progress mapping 안에서 fixed token timeline span `1.0`을 사용합니다. 실제로는 timeline delay가 각 token이 언제 시작할지 만들고, `withTiming` 또는 `withSpring`이 전체 문장이 `0`에서 `1`까지 얼마나 빠르게 움직일지를 정합니다. 나중에 controlled mode에서 token별 span 또는 duration 제어가 필요해진다면 `.motion()` duration을 몰래 빌려오는 방식이 아니라 별도 API로 설계하는 편이 안전합니다.
565
-
566
- `pause`, `seek`, `reverse`, screen focus helper, in-view helper, scroll/gesture driver는 아직 deferred입니다.
567
-
568
- ## 접근성
569
-
570
- 기본 renderer는 parent accessible label을 만들고 animated token node를 장식용으로 숨깁니다. 그래서 screen reader는 token 하나씩이 아니라 문장을 한 번만 읽습니다.
571
-
572
- ```tsx
573
- import { parentLabelPolicy } from '@react-native-motion-kit/text-motion';
574
-
575
- const ReducedReveal = defineTextMotion()
576
- .split(words())
577
- .layout(nativeText())
578
- .effect(rise().and(fade()))
579
- .accessibility(parentLabelPolicy({ reducedMotion: 'final-state' }))
580
- .component();
581
- ```
582
-
583
- ## React Native Text Segmentation
584
-
585
- React Native/Hermes 환경에서는 `Intl.Segmenter`가 항상 보장되지 않습니다. 내장 splitter는 먼저 런타임의 `Intl.Segmenter`를 감지해서 사용하고, 없으면 작은 내부 tokenizer로 fallback합니다. 그래서 앱 시작 시 animation이 crash하지 않습니다.
586
-
587
- Grapheme fallback은 `한` 같은 분해 한글 음절과 완성형 한글·자모가 이어지는 음절 경계를 원문 정규화나 변경 없이 보존합니다. Unicode 전체 grapheme 규칙을 구현한 것은 아니므로 `क्ष` 같은 일부 인도계 결합 문자는 여전히 나뉠 수 있습니다. Token별 렌더링도 단일 native `Text`의 조판이나 ligature를 보장하지는 않습니다.
588
-
589
- 이 fallback은 UI motion의 안정성을 위한 것이지, full ICU 수준의 locale segmentation을 목표로 하지 않습니다. 정확한 locale-aware word boundary가 필요한 앱, 특히 복잡한 word breaking이 필요한 script를 다루는 앱은 app entrypoint에서 `@formatjs/intl-segmenter` 같은 polyfill을 로드하거나 제품에 맞는 `custom()` splitter를 사용해야 합니다.
590
-
591
- ## Stable MVP
592
-
593
- - `defineTextMotion()`
594
- - `graphemes()`, `words()`, `custom()`
595
- - experimental newline-only `lines()`
596
- - `nativeText()`, `overlayText()`
597
- - `stagger()`, `sequence()`, `parallel()`, `wave()`
598
- - `fade()`, `rise()`, `slide()`, `scale()`, `pulse()`, `shake()`, `lineReveal()`, `typewriter()`
599
- - 명시적인 `controls` prop과 `useTextMotionControls()` 기반 play/replay/reset/stop
600
- - external Reanimated shared value 기반 controlled `progress`
601
- - hidden animated token node를 사용하는 parent-label accessibility policy
602
- - `/presets` subpath recipe factories
603
-
604
- Custom effect factory와 renderer capability factory는 MVP root API에서 의도적으로 export하지 않습니다. `nativeText()`는 위에 나열된 built-in native text effect만 구현합니다. 이 helper들은 stable renderer-extension SDK를 설계한 뒤 public API로 열 예정입니다.
605
-
606
- ## Deferred
607
-
608
- 다음 항목들은 MVP에서 stable export가 아닙니다.
609
-
610
- - custom effect factory API
611
- - renderer capability factory API
612
- - `wipe`
613
- - `scramble`
614
- - Skia renderer 또는 Skia-only effects
615
- - `pause`, `seek`, `reverse` 같은 추가 playback API
616
- - first-class screen focus, in-view, scroll, gesture driver
617
- - RN-rendered line-to-token mapping
618
-
619
- Skia는 core package의 dependency가 아니라 future optional package boundary로 남겨둡니다.
620
-
621
- context/provider playback wiring과 public component ref playback은 deferred feature가 아닙니다. ownership이 숨겨지고 remount/lifecycle 동작을 잘못 사용하기 쉬워서 이 패키지의 API 방향에서 의도적으로 제외합니다. 하나 이상의 text component가 event-driven playback command에 반응해야 한다면 `controls={controls}`를 명시적으로 전달하세요.
622
-
623
- ## Scope & Roadmap
624
-
625
- Text Motion은 generic animation package가 아니라 split text motion engine입니다. 이 패키지에 기능을 넣을지 판단할 때 핵심 질문은 이것입니다.
626
-
627
- ```txt
628
- 이 기능이 split text에 필요한 일을 개선하는가? 예를 들면 text split, layout mapping, token timing, text effect, renderer capability, accessibility, token별 playback.
629
- ```
630
-
631
- 답이 yes라면 `@react-native-motion-kit/text-motion`에 들어올 후보가 될 수 있습니다.
632
-
633
- Core text-motion 작업은 다음에 집중해야 합니다.
634
-
635
- - splitter, token metadata, layout-aware text motion
636
- - native text renderer의 신뢰성과 성능
637
- - split token을 위한 timeline, effect, motion, controls, progress composition
638
- - decorative token animation을 위한 accessibility behavior
639
- - title, label, product copy motion을 빠르게 만들 수 있는 preset
640
-
641
- Advanced text effect는 여전히 text token 또는 text layout에 의존할 때 고려할 수 있습니다.
642
-
643
- - scramble, wipe, highlight sweep effect
644
- - source range 정책이 안정된 뒤 다룰 수 있는 word-in-line effect와 RN-rendered line-to-token mapping
645
- - old/new token set의 playback policy가 필요한 text-change transition
646
-
647
- Renderer-specific effect는 renderer capability boundary 뒤에 둬야 합니다.
648
-
649
- - blur, glow, shader text, mask, glyph distortion은 core native text renderer보다 optional Skia renderer package 후보에 가깝습니다.
650
- - native renderer가 지원하지 못하는 renderer-specific option을 조용히 받아들이면 안 됩니다.
651
-
652
- 다른 문제를 푸는 기능은 별도 package가 더 적합할 가능성이 큽니다.
653
-
654
- - number count-up, odometer, currency, percentage, timer, delta animation은 split-text layout 문제가 아니라 value formatting 문제입니다.
655
- - generic `View` animation, scene animation, Lottie-style workflow는 이 패키지 밖의 영역입니다.
656
- - 나중에 별도의 value-motion 계열 package가 필요해질 수 있지만, 그건 text-motion API surface를 넓히는 방식보다 별도 package로 두는 편이 안전합니다.
657
-
658
- 결정 기준은 보수적으로 잡습니다. 사용자의 text-specific production work를 개선하면서 common recipe API를 더 어렵게 만들지 않을 때만 이 패키지에 추가합니다. 그렇지 않다면 deferred, renderer-specific, 또는 별도 package로 둡니다.
659
-
660
- ## 지금 사용해도 될까?
661
-
662
- 이 패키지는 MVP에서 의도적으로 작게 시작합니다. layout 전체를 지배하기보다, 짧고 중요한 UI 텍스트에 split motion을 더하는 경우에 가장 잘 맞습니다.
663
-
664
- 지금 쓰기 좋은 경우:
665
-
666
- - hero title, section title, 짧은 label, onboarding copy, product description
667
- - `fade`, `rise`, `slide`, `scale`, `pulse`, `shake`를 조합한 word/grapheme entrance motion
668
- - 완성 문장의 배치를 유지하면서 한 글자씩 드러나는 Typewriter
669
- - preset이나 `defineTextMotion()`으로 재사용 가능한 recipe component를 만들고 싶은 경우
670
- - component를 remount하지 않고 replay/reset/stop을 실행해야 하는 경우
671
- - external Reanimated shared value로 text motion progress를 직접 제어해야 하는 경우
672
- - responsive hero title, heading, 짧은 product copy를 실제 rendered line 기준으로 reveal해야 하는 경우
673
- - 이미 Reanimated 4를 사용하고 Worklets 설정을 따를 수 있는 앱
674
- - 전체 문장은 한 번만 읽히고, animated token은 장식처럼 처리되어야 하는 accessible text motion
675
-
676
- 다음 기능이 핵심이라면 이후 버전을 기다리는 편이 좋습니다.
677
-
678
- - `pause`, `seek`, `reverse` 같은 추가 playback API
679
- - scroll progress, gesture progress, viewport/in-view trigger를 first-class driver로 쓰는 기능
680
- - token별 line measurement 또는 word-in-line animation
681
- - blur, glow, shader, mask, glyph distortion 같은 Skia-only visual effect
682
- - rich nested text, inline link, selectable text, native `Text`와 완전히 같은 동작
683
- - target app에서 성능 측정 없이 긴 문단이나 많은 animated row에 적용하는 경우
684
-
685
- 다음 제품 초점은 line renderer performance와 platform behavior를 검증한 뒤, state-transition prop, `pause`/`seek`/`reverse`, screen focus helper, in-view helper, scroll/gesture driver, word-in-line effect, Skia renderer 후보를 first-class API로 올릴 가치가 있는지 판단하는 것입니다. Deferred에 적힌 항목은 release promise가 아닙니다. 동작, 예제, 테스트, 문서가 준비되었을 때만 stable API로 올리는 방향입니다.
686
-
687
- ## 개발
688
-
689
- ```sh
690
- corepack pnpm install
691
- corepack pnpm run typecheck
692
- corepack pnpm run test
693
- corepack pnpm run build
694
- ```
100
+ [기여 가이드와 개발 환경](https://github.com/react-native-motion-kit/react-native-text-motion/blob/main/CONTRIBUTING.md)
695
101
 
696
102
  ## License
697
103
 
698
- MIT
104
+ [MIT](./LICENSE)