-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathThreadText.tsx
More file actions
117 lines (109 loc) · 6.38 KB
/
Copy pathThreadText.tsx
File metadata and controls
117 lines (109 loc) · 6.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// @ts-nocheck
// threadText/src/framer/ThreadText.tsx — Framer code component wrapping the threadText core.
//
// Distribution: paste this file into Framer (Insert → Code → New Component), or host it as an
// ES module and add it by URL. It imports the framework-agnostic core straight from the CDN, so
// it needs no build step — the core is imperative (createThreadText takes a DOM element, not
// React), so there is no React version/externalisation issue.
import { useEffect, useRef } from "react"
import { addPropertyControls, ControlType, RenderTarget } from "framer"
// Pin to a published version so shared instances stay stable. Bump when the core changes.
import { createThreadText } from "https://esm.sh/@liiift-studio/threadtext@0.4.6"
/** Props surfaced to the Framer UI via addPropertyControls.
* Option fields are declared explicitly so the component needs no type import over HTTP. */
interface ThreadTextFramerProps {
/** The word (or short phrase) to embroider. */
text: string
/** CSS font-family of a loaded font used to rasterise the glyphs. */
fontFamily: string
/** Numeric font weight (100–900). */
weight: number
/** Floss (thread) colour — the lit crest of each thread. */
threadColor: string
/** Second floss colour (used by two-tone / gradient colour modes). */
threadColor2: string
/** How the floss is coloured: one colour, two side-by-side, or a gradient across the word. */
colorMode: "solid" | "twotone" | "gradient"
/** Add a darker running-stitch backstitch outline around each glyph. */
backstitch: boolean
/** Backstitch outline colour. */
outlineColor: string
/** Fraction of the width the word spans (its size). */
fill: number
/** Horizontal alignment of the word within the canvas. */
align: "left" | "center" | "right"
/** Sew-in style: 'machine' satin rows, or 'hand' single-thread. */
sewStyle: "machine" | "hand"
/** Stitch texture: satin / cross / chain / running. */
stitchMode: "satin" | "cross" | "chain" | "running"
/** Variable-font optical size (opsz axis) — where the font supports it. */
opsz: number
/** Satin cross-rows laid per second during the sew-in. */
sewRate: number
/** Cursor-following sheen on the overlay canvas. */
sheen: boolean
/** Play the sew-in animation on mount. */
animate: boolean
}
/**
* Procedural satin-stitch embroidery, as a Framer code component.
*
* @framerSupportedLayoutWidth any-prefer-fixed
* @framerSupportedLayoutHeight auto
*/
export default function ThreadText(props: Partial<ThreadTextFramerProps>) {
const {
text = "Thread",
fontFamily = "Georgia, serif",
weight = 680,
threadColor = "#fffbf3",
threadColor2 = "#c0532f",
colorMode = "solid",
backstitch = false,
outlineColor = "#3a2410",
fill = 0.9,
align = "center",
sewStyle = "machine",
stitchMode = "satin",
opsz = 40,
sewRate = 110,
sheen = true,
animate = true,
} = props
const ref = useRef<HTMLDivElement>(null)
const instRef = useRef(null)
// Animate on the live site and on the editing canvas (so the designer sees the sew-in);
// draw a single static frame on export / thumbnails where a loop is undesirable.
const live = RenderTarget.current() === RenderTarget.preview || RenderTarget.current() === RenderTarget.canvas
// Create once; option changes are applied live (no re-sew).
useEffect(() => {
const el = ref.current
if (!el) return
const instance = createThreadText(el, { text, font: fontFamily, weight, threadColor, threadColor2, colorMode, backstitch, outlineColor, fill, align, sewStyle, stitchMode, axes: { opsz }, sewRate, sheen: live ? sheen : false, animate: live ? animate : false })
instRef.current = instance
return () => instance.destroy()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [live])
useEffect(() => { instRef.current?.update({ font: fontFamily, weight, threadColor, threadColor2, colorMode, backstitch, outlineColor, fill, align, sewStyle, stitchMode, axes: { opsz }, sewRate, sheen: live ? sheen : false, animate: live ? animate : false }) }, [fontFamily, weight, threadColor, threadColor2, colorMode, backstitch, outlineColor, fill, align, sewStyle, stitchMode, opsz, sewRate, sheen, animate, live])
useEffect(() => { instRef.current?.setText(text) }, [text])
return <div ref={ref} style={{ width: "100%" }} role="img" aria-label={text} />
}
// Map every meaningful ThreadTextOptions field to a Framer control.
addPropertyControls(ThreadText, {
text: { type: ControlType.String, title: "Text", defaultValue: "Thread", displayTextArea: false },
fontFamily: { type: ControlType.String, title: "Font", defaultValue: "Georgia, serif", description: "A loaded font family. The glyph geometry drives the stitch flow." },
weight: { type: ControlType.Number, title: "Weight", defaultValue: 680, min: 100, max: 900, step: 10 },
colorMode: { type: ControlType.Enum, title: "Colour", options: ["solid", "twotone", "gradient"], optionTitles: ["Solid", "Two-tone", "Gradient"], defaultValue: "solid" },
threadColor: { type: ControlType.Color, title: "Thread", defaultValue: "#fffbf3" },
threadColor2: { type: ControlType.Color, title: "Thread 2", defaultValue: "#c0532f", hidden: (p) => p.colorMode === "solid" },
backstitch: { type: ControlType.Boolean, title: "Backstitch", defaultValue: false },
outlineColor: { type: ControlType.Color, title: "Outline", defaultValue: "#3a2410", hidden: (p) => !p.backstitch },
fill: { type: ControlType.Number, title: "Size", defaultValue: 0.9, min: 0.3, max: 1, step: 0.02, description: "Fraction of the width the word fills." },
align: { type: ControlType.Enum, title: "Align", options: ["left", "center", "right"], optionTitles: ["Left", "Center", "Right"], defaultValue: "center" },
sewStyle: { type: ControlType.Enum, title: "Sew style", options: ["machine", "hand"], optionTitles: ["Machine", "Hand"], defaultValue: "machine" },
stitchMode: { type: ControlType.Enum, title: "Stitch", options: ["satin", "cross", "chain", "running"], optionTitles: ["Satin", "Cross", "Chain", "Running"], defaultValue: "satin" },
opsz: { type: ControlType.Number, title: "Optical", defaultValue: 40, min: 9, max: 144, step: 1, description: "Optical size (opsz) on fonts that have it." },
sewRate: { type: ControlType.Number, title: "Sew rate", defaultValue: 110, min: 20, max: 400, step: 10, description: "Rows/stitches per second." },
sheen: { type: ControlType.Boolean, title: "Sheen", defaultValue: true },
animate: { type: ControlType.Boolean, title: "Sew-in", defaultValue: true },
})