@edwinvakayil/calligraphy 1.4.0 → 1.5.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 +351 -130
- package/dist/animation.d.ts +19 -0
- package/dist/index.d.ts +104 -3
- package/dist/index.esm.js +201 -42
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +201 -41
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +98 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @edwinvakayil/calligraphy
|
|
2
2
|
|
|
3
|
-
A lightweight **React + TypeScript** typography component with automatic **Google Fonts** support, **hero entrance animations**, and an **italic accent toggle**.
|
|
3
|
+
A lightweight **React + TypeScript** typography component with automatic **Google Fonts** support, **30+ hero entrance animations**, **custom motion config**, and an **italic accent toggle**.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -44,20 +44,20 @@ The `font` prop auto-injects the matching Google Font `<link>` tag — no manual
|
|
|
44
44
|
|
|
45
45
|
## Variants
|
|
46
46
|
|
|
47
|
-
| Variant | Tag | Role
|
|
48
|
-
|
|
49
|
-
| `Display` | `h1` | Hero / landing page headline
|
|
50
|
-
| `H1` | `h1` | Primary page heading
|
|
51
|
-
| `H2` | `h2` | Section heading
|
|
52
|
-
| `H3` | `h3` | Sub-section heading
|
|
53
|
-
| `H4` | `h4` | Card / panel heading
|
|
54
|
-
| `H5` | `h5` | Small heading
|
|
55
|
-
| `H6` | `h6` | Micro heading
|
|
56
|
-
| `Subheading` | `h6` | Supporting subtitle
|
|
57
|
-
| `Overline` | `span` | ALL CAPS label above a heading
|
|
58
|
-
| `Body` | `p` | Main body copy
|
|
59
|
-
| `Label` | `label` | Form labels, tags
|
|
60
|
-
| `Caption` | `span` | Image captions, fine print
|
|
47
|
+
| Variant | Tag | Role |
|
|
48
|
+
|--------------|----------|--------------------------------|
|
|
49
|
+
| `Display` | `h1` | Hero / landing page headline |
|
|
50
|
+
| `H1` | `h1` | Primary page heading |
|
|
51
|
+
| `H2` | `h2` | Section heading |
|
|
52
|
+
| `H3` | `h3` | Sub-section heading |
|
|
53
|
+
| `H4` | `h4` | Card / panel heading |
|
|
54
|
+
| `H5` | `h5` | Small heading |
|
|
55
|
+
| `H6` | `h6` | Micro heading |
|
|
56
|
+
| `Subheading` | `h6` | Supporting subtitle |
|
|
57
|
+
| `Overline` | `span` | ALL CAPS label above a heading |
|
|
58
|
+
| `Body` | `p` | Main body copy |
|
|
59
|
+
| `Label` | `label` | Form labels, tags |
|
|
60
|
+
| `Caption` | `span` | Image captions, fine print |
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
@@ -65,18 +65,20 @@ The `font` prop auto-injects the matching Google Font `<link>` tag — no manual
|
|
|
65
65
|
|
|
66
66
|
```ts
|
|
67
67
|
interface TypographyProps {
|
|
68
|
-
variant?:
|
|
69
|
-
font?:
|
|
70
|
-
color?:
|
|
71
|
-
align?:
|
|
72
|
-
as?:
|
|
73
|
-
truncate?:
|
|
74
|
-
maxLines?:
|
|
75
|
-
animation?:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
variant?: TypographyVariant // default: "Body"
|
|
69
|
+
font?: string // Google Font name e.g. "Bricolage Grotesque"
|
|
70
|
+
color?: string // Any CSS color value
|
|
71
|
+
align?: "left" | "center" | "right" | "justify"
|
|
72
|
+
as?: ElementType // Override rendered HTML tag
|
|
73
|
+
truncate?: boolean // Single-line ellipsis
|
|
74
|
+
maxLines?: number // Multi-line line-clamp
|
|
75
|
+
animation?: HeroAnimation // Built-in entrance animation (Display / H1 only)
|
|
76
|
+
motionConfig?: MotionConfig // Custom keyframe animation — any variant
|
|
77
|
+
motionRef?: (el: HTMLElement | null) => void // Direct DOM access for GSAP / Framer
|
|
78
|
+
italic?: boolean // default: false — serif italic accent on <em>
|
|
79
|
+
accentColor?: string // default: "#c8b89a" — color for <em> italic text
|
|
80
|
+
className?: string
|
|
81
|
+
style?: CSSProperties
|
|
80
82
|
}
|
|
81
83
|
```
|
|
82
84
|
|
|
@@ -84,7 +86,7 @@ interface TypographyProps {
|
|
|
84
86
|
|
|
85
87
|
## Hero Animations
|
|
86
88
|
|
|
87
|
-
The `animation` prop works on `Display` and `H1` variants
|
|
89
|
+
The `animation` prop works on `Display` and `H1` variants. It injects a tiny stylesheet once (no external dependency) and applies a CSS keyframe entrance.
|
|
88
90
|
|
|
89
91
|
```tsx
|
|
90
92
|
<Typography variant="Display" font="Bricolage Grotesque" animation="rise">
|
|
@@ -94,141 +96,220 @@ The `animation` prop works on `Display` and `H1` variants only. It injects a tin
|
|
|
94
96
|
|
|
95
97
|
### Available animations
|
|
96
98
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
|
100
|
-
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
99
|
+
#### Original
|
|
100
|
+
|
|
101
|
+
| Value | Description |
|
|
102
|
+
|--------------|------------------------------------------------------|
|
|
103
|
+
| `rise` | Smooth upward fade-in — clean, universal |
|
|
104
|
+
| `stagger` | Each word rises in sequence |
|
|
105
|
+
| `clip` | Text unmasked left to right — editorial feel |
|
|
106
|
+
| `pop` | Spring scale-in — energetic and confident |
|
|
107
|
+
| `letters` | Each letter slides in with a slight rotation |
|
|
108
|
+
| `blur` | Emerges from a blur — cinematic and dreamy |
|
|
109
|
+
| `flip` | 3-D perspective rotate on entry — depth and gravitas |
|
|
110
|
+
| `swipe` | Slides in from the right — directional flow |
|
|
111
|
+
| `typewriter` | Character-by-character reveal — focused |
|
|
112
|
+
| `bounce` | Drops from above with a soft bounce |
|
|
113
|
+
|
|
114
|
+
#### Modern
|
|
115
|
+
|
|
116
|
+
| Value | Description |
|
|
117
|
+
|-------------|----------------------------------------------------------|
|
|
118
|
+
| `velvet` | Words drift in with a soft skew — buttery & modern |
|
|
119
|
+
| `curtain` | Each word clips upward like a rising curtain |
|
|
120
|
+
| `morph` | Squash-and-stretch spring — expressive & bold |
|
|
121
|
+
| `ground` | Words emerge from behind the baseline — editorial |
|
|
122
|
+
| `cascade` | Diagonal character waterfall — dynamic & layered |
|
|
123
|
+
| `spotlight` | Expands from compressed letterspace — cinematic |
|
|
124
|
+
| `ink` | Words fade in with a gentle scale — calm & precise |
|
|
125
|
+
| `hinge` | Words rotate in from their left edge — mechanical |
|
|
126
|
+
| `stretch` | Horizontal rubber-band expand — playful & punchy |
|
|
127
|
+
| `peel` | Bottom-to-top clip reveal per word — sharp |
|
|
128
|
+
| `ripple` | Words scale out from a compressed point — elastic |
|
|
129
|
+
| `cinch` | Characters pinch then snap open — crisp & mechanical |
|
|
130
|
+
| `tiltrise` | Words rise while untilting from a lean — editorial |
|
|
131
|
+
|
|
132
|
+
#### New mechanics
|
|
133
|
+
|
|
134
|
+
| Value | Description |
|
|
135
|
+
|--------------|----------------------------------------------------------|
|
|
136
|
+
| `unfurl` | Line expands from horizontal center — origami opening |
|
|
137
|
+
| `billboard` | Whole line rotates in on Y-axis — turning sign |
|
|
138
|
+
| `tectonic` | Words slam from alternating sides with skew |
|
|
139
|
+
| `stratify` | Per-word Z-depth flight through blur — depth-of-field |
|
|
140
|
+
| `orbit` | Words grow from dot while rotating on Z-axis |
|
|
141
|
+
| `liquid` | Per-word cross-axis squash then spring — fluid inertia |
|
|
142
|
+
| `noiseFade` | 3 random opacity waveforms per word — signal locking |
|
|
143
|
+
| `slab` | Words stamp from left with scaleX — print-press energy |
|
|
144
|
+
| `thread` | Characters float on individual sine-wave Y offsets |
|
|
145
|
+
| `glassReveal`| Backdrop blur evaporates as text solidifies |
|
|
146
|
+
| `wordPop` | Per-word spring from zero scale at its own centre |
|
|
147
|
+
| `scanline` | Single-pixel horizontal slice expands to full height |
|
|
148
|
+
| `chromaShift`| RGB channel offsets collapse to zero — printing register |
|
|
149
|
+
| `wordFade` | Per-word cross-dissolve with warmth scale |
|
|
150
|
+
| `rotateIn` | Full Y-axis card-flip per word — face-up reveal |
|
|
151
|
+
| `pressIn` | Presses to 0.92 then springs past 1 — physical button |
|
|
152
|
+
| `unfurl` | Text splits open from center — origami feel |
|
|
153
|
+
| `dissolve` | Characters dissolve in with random micro-rotation |
|
|
154
|
+
| `depth` | Z-axis push from far to near — immersive 3D |
|
|
155
|
+
| `maskSweep` | Mask-image sweep reveals text left to right |
|
|
156
|
+
| `gradSweep` | Gradient sweep across text then resolves to solid |
|
|
109
157
|
|
|
110
158
|
All animations use only `transform`, `opacity`, and `filter` — GPU-composited, no layout thrashing, 60fps safe.
|
|
111
159
|
|
|
112
160
|
---
|
|
113
161
|
|
|
114
|
-
##
|
|
162
|
+
## Custom Motion — `motionConfig`
|
|
115
163
|
|
|
116
|
-
When
|
|
164
|
+
When the built-in presets don't fit, write your own keyframe. Works on **any variant**, not just heroes.
|
|
117
165
|
|
|
118
166
|
```tsx
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
Build with <em>intention</em>
|
|
122
|
-
</Typography>
|
|
167
|
+
import { Typography, type MotionConfig } from "@edwinvakayil/calligraphy";
|
|
168
|
+
```
|
|
123
169
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
170
|
+
### Whole-element animation
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
<Typography
|
|
174
|
+
variant="H2"
|
|
175
|
+
font="Syne"
|
|
176
|
+
motionConfig={{
|
|
177
|
+
keyframes: `from { opacity: 0; transform: translateY(24px) skewX(6deg); }
|
|
178
|
+
to { opacity: 1; transform: none; }`,
|
|
179
|
+
duration: "0.8s",
|
|
180
|
+
easing: "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
181
|
+
delay: "0.1s",
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
Section heading
|
|
127
185
|
</Typography>
|
|
128
186
|
```
|
|
129
187
|
|
|
130
|
-
###
|
|
188
|
+
### Per-word stagger
|
|
131
189
|
|
|
132
190
|
```tsx
|
|
133
191
|
<Typography
|
|
134
192
|
variant="Display"
|
|
135
193
|
font="Bricolage Grotesque"
|
|
136
|
-
|
|
194
|
+
motionConfig={{
|
|
195
|
+
keyframes: `from { opacity: 0; transform: translateX(-20px) rotate(-4deg); }
|
|
196
|
+
to { opacity: 1; transform: none; }`,
|
|
197
|
+
duration: "0.65s",
|
|
198
|
+
split: "words",
|
|
199
|
+
staggerDelay: 0.09,
|
|
200
|
+
}}
|
|
137
201
|
>
|
|
138
|
-
|
|
202
|
+
Design with <em>intention</em>
|
|
139
203
|
</Typography>
|
|
140
204
|
```
|
|
141
205
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## Examples
|
|
147
|
-
|
|
148
|
-
### Overline + Display combo
|
|
206
|
+
### Per-character stagger
|
|
149
207
|
|
|
150
208
|
```tsx
|
|
151
|
-
<Typography variant="Overline" color="#6366f1">
|
|
152
|
-
New Feature
|
|
153
|
-
</Typography>
|
|
154
209
|
<Typography
|
|
155
210
|
variant="Display"
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
211
|
+
motionConfig={{
|
|
212
|
+
keyframes: `from { opacity: 0; transform: scaleY(0) translateY(10px); }
|
|
213
|
+
to { opacity: 1; transform: none; }`,
|
|
214
|
+
duration: "0.5s",
|
|
215
|
+
split: "chars",
|
|
216
|
+
staggerDelay: 0.035,
|
|
217
|
+
}}
|
|
159
218
|
>
|
|
160
|
-
|
|
219
|
+
Motion
|
|
161
220
|
</Typography>
|
|
162
221
|
```
|
|
163
222
|
|
|
164
|
-
###
|
|
223
|
+
### `MotionConfig` shape
|
|
165
224
|
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
225
|
+
```ts
|
|
226
|
+
interface MotionConfig {
|
|
227
|
+
keyframes: string // CSS keyframe body (without @keyframes name {})
|
|
228
|
+
duration?: string // default: "0.8s"
|
|
229
|
+
easing?: string // default: "cubic-bezier(0.16, 1, 0.3, 1)"
|
|
230
|
+
delay?: string // default: "0s"
|
|
231
|
+
fillMode?: "none"|"forwards"|"backwards"|"both" // default: "both"
|
|
232
|
+
split?: "none" | "words" | "chars" // default: "none"
|
|
233
|
+
staggerDelay?: number // seconds between spans; default 0.07 (words) / 0.04 (chars)
|
|
234
|
+
}
|
|
173
235
|
```
|
|
174
236
|
|
|
175
|
-
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Direct DOM Access — `motionRef`
|
|
240
|
+
|
|
241
|
+
For full control with GSAP, Framer Motion, or the Web Animations API. The ref callback fires after mount and on every re-render.
|
|
242
|
+
|
|
243
|
+
`motionRef` takes priority over both `animation` and `motionConfig`.
|
|
176
244
|
|
|
177
245
|
```tsx
|
|
178
|
-
|
|
179
|
-
<Typography
|
|
180
|
-
|
|
246
|
+
// Web Animations API
|
|
247
|
+
<Typography
|
|
248
|
+
variant="Display"
|
|
249
|
+
font="Bricolage Grotesque"
|
|
250
|
+
motionRef={(el) => {
|
|
251
|
+
if (!el) return;
|
|
252
|
+
el.animate(
|
|
253
|
+
[{ opacity: 0, transform: "translateY(32px)" }, { opacity: 1, transform: "none" }],
|
|
254
|
+
{ duration: 900, easing: "cubic-bezier(0.16,1,0.3,1)", fill: "both" }
|
|
255
|
+
);
|
|
256
|
+
}}
|
|
257
|
+
>
|
|
258
|
+
Full control
|
|
181
259
|
</Typography>
|
|
182
260
|
|
|
183
|
-
|
|
184
|
-
<Typography
|
|
185
|
-
|
|
261
|
+
// GSAP
|
|
262
|
+
<Typography
|
|
263
|
+
variant="H1"
|
|
264
|
+
motionRef={(el) => {
|
|
265
|
+
if (!el) return;
|
|
266
|
+
gsap.from(el, { opacity: 0, y: 40, duration: 0.9, ease: "power3.out" });
|
|
267
|
+
}}
|
|
268
|
+
>
|
|
269
|
+
GSAP powered
|
|
186
270
|
</Typography>
|
|
187
271
|
```
|
|
188
272
|
|
|
189
|
-
###
|
|
273
|
+
### Priority order
|
|
190
274
|
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
Renders as a div, styled as H2
|
|
194
|
-
</Typography>
|
|
275
|
+
```
|
|
276
|
+
motionRef > motionConfig > animation > no animation
|
|
195
277
|
```
|
|
196
278
|
|
|
197
279
|
---
|
|
198
280
|
|
|
199
|
-
##
|
|
281
|
+
## Italic Accent
|
|
200
282
|
|
|
201
|
-
|
|
283
|
+
When `italic={true}`, any `<em>` tag inside a `Display` or `H1` hero renders in **Instrument Serif italic** with an accent color. Off by default.
|
|
202
284
|
|
|
203
285
|
```tsx
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
```
|
|
286
|
+
// Off by default
|
|
287
|
+
<Typography variant="Display" font="Bricolage Grotesque">
|
|
288
|
+
Build with <em>intention</em>
|
|
289
|
+
</Typography>
|
|
209
290
|
|
|
210
|
-
|
|
291
|
+
// Turn on
|
|
292
|
+
<Typography variant="Display" font="Bricolage Grotesque" italic>
|
|
293
|
+
Build with <em>intention</em>
|
|
294
|
+
</Typography>
|
|
211
295
|
|
|
212
|
-
|
|
296
|
+
// Custom accent color
|
|
297
|
+
<Typography variant="Display" font="Bricolage Grotesque" italic accentColor="#7F77DD">
|
|
298
|
+
Crafted with <em>precision</em>
|
|
299
|
+
</Typography>
|
|
300
|
+
```
|
|
213
301
|
|
|
214
|
-
|
|
215
|
-
|------------------------|------------|------------------------------|
|
|
216
|
-
| `Bricolage Grotesque` | Bold sans | Startups, SaaS, modern brand |
|
|
217
|
-
| `Syne` | Geometric | Creative, portfolio, agency |
|
|
218
|
-
| `Fraunces` | Serif | Editorial, luxury, fashion |
|
|
219
|
-
| `Bebas Neue` | Condensed | Sports, bold campaigns |
|
|
220
|
-
| `Playfair Display` | Serif | Journalism, books, culture |
|
|
221
|
-
| `Outfit` | Clean sans | Apps, dashboards, fintech |
|
|
302
|
+
`accentColor` has no effect when `italic={false}`.
|
|
222
303
|
|
|
223
|
-
|
|
304
|
+
---
|
|
224
305
|
|
|
225
306
|
## TypographyProvider
|
|
226
|
-
|
|
307
|
+
|
|
227
308
|
Wrap your app (or a section of it) with `TypographyProvider` to set defaults once. Any prop passed directly to `<Typography>` still wins — the provider is just the fallback.
|
|
228
|
-
|
|
309
|
+
|
|
229
310
|
```tsx
|
|
230
|
-
import { TypographyProvider, Typography } from "
|
|
231
|
-
|
|
311
|
+
import { TypographyProvider, Typography } from "@edwinvakayil/calligraphy";
|
|
312
|
+
|
|
232
313
|
export default function App() {
|
|
233
314
|
return (
|
|
234
315
|
<TypographyProvider
|
|
@@ -240,21 +321,16 @@ export default function App() {
|
|
|
240
321
|
color: "#1a1a1a",
|
|
241
322
|
}}
|
|
242
323
|
>
|
|
243
|
-
{/* Inherits
|
|
324
|
+
{/* Inherits all theme values */}
|
|
244
325
|
<Typography variant="Display">
|
|
245
326
|
Build with <em>intention</em>
|
|
246
327
|
</Typography>
|
|
247
|
-
|
|
248
|
-
{/* Overrides just the animation
|
|
328
|
+
|
|
329
|
+
{/* Overrides just the animation */}
|
|
249
330
|
<Typography variant="H1" animation="clip">
|
|
250
331
|
Another hero heading
|
|
251
332
|
</Typography>
|
|
252
|
-
|
|
253
|
-
{/* Overrides font only */}
|
|
254
|
-
<Typography variant="Body" font="Lora">
|
|
255
|
-
Body copy in a different font.
|
|
256
|
-
</Typography>
|
|
257
|
-
|
|
333
|
+
|
|
258
334
|
{/* italic=false wins over theme's italic=true */}
|
|
259
335
|
<Typography variant="Display" italic={false}>
|
|
260
336
|
No serif accent here
|
|
@@ -263,33 +339,33 @@ export default function App() {
|
|
|
263
339
|
);
|
|
264
340
|
}
|
|
265
341
|
```
|
|
266
|
-
|
|
342
|
+
|
|
267
343
|
### Theme shape
|
|
268
|
-
|
|
344
|
+
|
|
269
345
|
```ts
|
|
270
346
|
interface TypographyTheme {
|
|
271
|
-
font?: string // Google Font
|
|
347
|
+
font?: string // Google Font applied to all variants
|
|
272
348
|
accentColor?: string // <em> accent color for Display / H1
|
|
273
349
|
italic?: boolean // italic accent on/off for Display / H1
|
|
274
350
|
animation?: HeroAnimation // entrance animation for Display / H1
|
|
275
351
|
color?: string // default text color for all variants
|
|
276
352
|
}
|
|
277
353
|
```
|
|
278
|
-
|
|
354
|
+
|
|
279
355
|
### Priority order
|
|
280
|
-
|
|
356
|
+
|
|
281
357
|
```
|
|
282
358
|
Explicit prop > TypographyProvider theme > built-in default
|
|
283
359
|
```
|
|
284
|
-
|
|
360
|
+
|
|
285
361
|
### Nesting providers
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
362
|
+
|
|
363
|
+
The nearest provider wins:
|
|
364
|
+
|
|
289
365
|
```tsx
|
|
290
366
|
<TypographyProvider theme={{ font: "Bricolage Grotesque", color: "#111" }}>
|
|
291
367
|
<Typography variant="H1">Uses Bricolage Grotesque</Typography>
|
|
292
|
-
|
|
368
|
+
|
|
293
369
|
<TypographyProvider theme={{ font: "Playfair Display", accentColor: "#e11d48" }}>
|
|
294
370
|
<Typography variant="Display">
|
|
295
371
|
Uses Playfair Display with red accent
|
|
@@ -298,12 +374,157 @@ Providers can be nested. The nearest one wins:
|
|
|
298
374
|
</TypographyProvider>
|
|
299
375
|
```
|
|
300
376
|
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Examples
|
|
380
|
+
|
|
381
|
+
### Overline + Display combo
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
<Typography variant="Overline" color="#6366f1">New Feature</Typography>
|
|
385
|
+
<Typography variant="Display" font="Bricolage Grotesque" animation="clip" accentColor="#6366f1">
|
|
386
|
+
Build faster with <em>types</em>
|
|
387
|
+
</Typography>
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Body + Caption
|
|
391
|
+
|
|
392
|
+
```tsx
|
|
393
|
+
<Typography variant="Body" font="Lora">
|
|
394
|
+
A well-set paragraph in a refined serif font brings reading pleasure.
|
|
395
|
+
</Typography>
|
|
396
|
+
<Typography variant="Caption" color="#888">
|
|
397
|
+
Fig. 1 — System architecture overview
|
|
398
|
+
</Typography>
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Truncation
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
<Typography variant="H2" truncate>
|
|
405
|
+
This very long title will be cut off with an ellipsis
|
|
406
|
+
</Typography>
|
|
407
|
+
|
|
408
|
+
<Typography variant="Body" maxLines={3}>
|
|
409
|
+
This paragraph will show at most three lines...
|
|
410
|
+
</Typography>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Override HTML tag
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
<Typography variant="H2" as="div">
|
|
417
|
+
Renders as a div, styled as H2
|
|
418
|
+
</Typography>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Pre-loading fonts
|
|
424
|
+
|
|
425
|
+
To avoid FOUT (flash of unstyled text), pre-load fonts at the top of your app:
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
import { preloadFonts } from "@edwinvakayil/calligraphy";
|
|
429
|
+
|
|
430
|
+
// In your _app.tsx / main.tsx / layout.tsx
|
|
431
|
+
preloadFonts(["Bricolage Grotesque", "Instrument Serif", "DM Sans"]);
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Recommended hero font pairings
|
|
437
|
+
|
|
438
|
+
| Heading font | Style | Works well for |
|
|
439
|
+
|-----------------------|------------|------------------------------|
|
|
440
|
+
| `Bricolage Grotesque` | Bold sans | Startups, SaaS, modern brand |
|
|
441
|
+
| `Syne` | Geometric | Creative, portfolio, agency |
|
|
442
|
+
| `Fraunces` | Serif | Editorial, luxury, fashion |
|
|
443
|
+
| `Bebas Neue` | Condensed | Sports, bold campaigns |
|
|
444
|
+
| `Playfair Display` | Serif | Journalism, books, culture |
|
|
445
|
+
| `Outfit` | Clean sans | Apps, dashboards, fintech |
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## SSR & Next.js
|
|
450
|
+
|
|
451
|
+
`@edwinvakayil/calligraphy` is fully SSR-safe. All DOM work (`<link>` and `<style>` injection) happens inside `useInsertionEffect`, which React never calls on the server. The text renders in the server HTML; animations play after hydration.
|
|
452
|
+
|
|
453
|
+
### Next.js App Router
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
// app/layout.tsx
|
|
457
|
+
import { TypographyProvider } from "@edwinvakayil/calligraphy";
|
|
458
|
+
|
|
459
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
460
|
+
return (
|
|
461
|
+
<html lang="en">
|
|
462
|
+
<body>
|
|
463
|
+
<TypographyProvider theme={{ font: "Bricolage Grotesque", accentColor: "#6366f1" }}>
|
|
464
|
+
{children}
|
|
465
|
+
</TypographyProvider>
|
|
466
|
+
</body>
|
|
467
|
+
</html>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
```tsx
|
|
473
|
+
// app/page.tsx — Server Component
|
|
474
|
+
import { Typography } from "@edwinvakayil/calligraphy";
|
|
475
|
+
|
|
476
|
+
export default function Page() {
|
|
477
|
+
return (
|
|
478
|
+
<Typography variant="Display" animation="rise">
|
|
479
|
+
Renders on the server, <em>animates</em> on the client
|
|
480
|
+
</Typography>
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Next.js Pages Router
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
// pages/_app.tsx
|
|
489
|
+
import { TypographyProvider } from "@edwinvakayil/calligraphy";
|
|
490
|
+
import type { AppProps } from "next/app";
|
|
491
|
+
|
|
492
|
+
export default function App({ Component, pageProps }: AppProps) {
|
|
493
|
+
return (
|
|
494
|
+
<TypographyProvider theme={{ font: "Syne" }}>
|
|
495
|
+
<Component {...pageProps} />
|
|
496
|
+
</TypographyProvider>
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### What renders where
|
|
502
|
+
|
|
503
|
+
| Prop | Server | Client |
|
|
504
|
+
|------|--------|--------|
|
|
505
|
+
| `variant`, `font`, `color`, `align`, `truncate`, `maxLines` | Inline styles in HTML | Hydrated |
|
|
506
|
+
| `animation` | Class name in HTML | Keyframe `<style>` injected, animation plays |
|
|
507
|
+
| `motionConfig` | Raw HTML (split spans present) | Animation applied after hydration |
|
|
508
|
+
| `motionRef` | Plain HTML | Callback fires after hydration |
|
|
509
|
+
| `italic` / `accentColor` | Inline styles on `<em>` | No flash on hydration |
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## Build & Publish
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
npm install
|
|
517
|
+
npm run build # outputs CJS + ESM + .d.ts to /dist
|
|
518
|
+
npm pack # inspect the tarball before publishing
|
|
519
|
+
npm publish --access public
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
301
524
|
## License
|
|
302
525
|
|
|
303
526
|
Copyright (c) 2025 Edwin Vakayil. All rights reserved.
|
|
304
527
|
|
|
305
|
-
This package is proprietary software. You may install and use it for personal
|
|
306
|
-
or internal business purposes, but you may not copy, modify, distribute, or
|
|
307
|
-
create derivative works from it without explicit written permission from the author.
|
|
528
|
+
This package is proprietary software. You may install and use it for personal or internal business purposes, but you may not copy, modify, distribute, or create derivative works from it without explicit written permission from the author.
|
|
308
529
|
|
|
309
530
|
See the [LICENSE](./LICENSE) file for full terms.
|
package/dist/animation.d.ts
CHANGED
|
@@ -10,3 +10,22 @@ export declare function applyThreadOffsets(container: HTMLElement): void;
|
|
|
10
10
|
export declare function buildSplitHTML(animation: HeroAnimation, html: string): string;
|
|
11
11
|
export declare const buildStaggerHTML: (html: string) => string;
|
|
12
12
|
export declare const buildLettersHTML: (html: string) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Injects a one-off @keyframes rule with a generated name and returns that name.
|
|
15
|
+
* Deduped by a hash of the keyframe body so the same keyframe is only injected once.
|
|
16
|
+
*/
|
|
17
|
+
declare function injectCustomKeyframes(keyframeBody: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Builds the innerHTML for a custom motionConfig.
|
|
20
|
+
* - split "none" → returns the raw html unchanged; caller applies class to element
|
|
21
|
+
* - split "words" → wraps each word in a span with the animation + stagger delay
|
|
22
|
+
* - split "chars" → wraps each character in a span with the animation + stagger delay
|
|
23
|
+
*
|
|
24
|
+
* Returns { html, animationValue } where animationValue is the full CSS animation
|
|
25
|
+
* shorthand to set on each span (or on the element itself when split is "none").
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildCustomHTML(html: string, keyframeName: string, duration: string, easing: string, delay: string, fillMode: string, split: "none" | "words" | "chars", staggerDelay: number): {
|
|
28
|
+
html: string;
|
|
29
|
+
baseAnimation: string;
|
|
30
|
+
};
|
|
31
|
+
export { injectCustomKeyframes };
|