@aznabee/freehand-ui 0.1.1 → 0.1.3

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
@@ -84,6 +84,8 @@ function Card() {
84
84
 
85
85
  - Inline object props (`note={{ text: "hi" }}`) are compared by value, so a
86
86
  re-render does not tear down and redraw the overlay with a new random seed.
87
+ - The handwriting font is fetched automatically — see [Handwriting
88
+ font](#handwriting-font) to use `next/font` or self-host instead.
87
89
  - `disabled` skips drawing entirely — useful behind a reduced-motion check or a
88
90
  feature flag.
89
91
  - TypeScript definitions ship with the package for both entry points.
@@ -96,13 +98,51 @@ doodle(".card", {
96
98
  color: "#ffffff",
97
99
  strokeWidth: 1.5,
98
100
  roughness: 1.5,
99
- padding: 8,
100
- radius: 20, // optional override; auto-detected from CSS when omitted
101
+ padding: 0, // gap between the element's edge and the frame
102
+ radius: 20, // optional override; auto-detected from CSS when omitted
101
103
  opacity: 0.9,
102
- addBreaks: false, // lift the pen at random points around the outline
104
+ addBreaks: false, // lift the pen at random points around the outline
105
+ fontFamily: null, // override the handwriting stack
106
+ autoLoadFont: true,
103
107
  });
104
108
  ```
105
109
 
110
+ The frame traces the element's own edge by default. Raise `padding` to stand it
111
+ off — `padding: 8` leaves a comfortable margin around a card.
112
+
113
+ ## Handwriting font
114
+
115
+ Notes are set in [Caveat](https://fonts.google.com/specimen/Caveat). The library
116
+ loads it for you the first time a note is drawn, so the handwriting looks right
117
+ in a plain app with no font setup of its own — without it the text falls back to
118
+ the generic `cursive` family, which on macOS is a calligraphic serif rather than
119
+ anything handwritten.
120
+
121
+ It skips the request when the page already provides the family, and only ever
122
+ requests it once. To take over:
123
+
124
+ ```javascript
125
+ // self-hosted, or already loaded elsewhere on the page
126
+ doodle(".card", { note: "hi", autoLoadFont: false });
127
+
128
+ // your own family — a next/font CSS variable, for instance
129
+ doodle(".card", { note: "hi", fontFamily: "var(--font-caveat)" });
130
+ ```
131
+
132
+ ```jsx
133
+ import { Caveat } from "next/font/google";
134
+ const caveat = Caveat({ subsets: ["latin"], variable: "--font-caveat" });
135
+
136
+ <Doodle note="start chat" fontFamily="var(--font-caveat)" autoLoadFont={false}>
137
+ <Button />
138
+ </Doodle>;
139
+ ```
140
+
141
+ Setting `autoLoadFont: false` without a `fontFamily` falls back to whatever
142
+ handwriting faces the system has (Bradley Hand, Segoe Script, Comic Sans MS).
143
+ Notes are re-measured when a font finishes loading, so the underline always
144
+ matches the final glyph widths.
145
+
106
146
  ## Broken outlines
107
147
 
108
148
  `addBreaks` cuts randomly sized gaps into the border, so it reads as a few
@@ -140,11 +180,34 @@ doodle(".cta", {
140
180
 
141
181
  Positions: `top`, `top-right`, `right`, `bottom-right`, `bottom`, `bottom-left`, `left`, `top-left`. Same names apply to `arrow.from`/`arrow.to`.
142
182
 
143
- The position is a preference, not a command. Notes are measured from their real
144
- glyph metrics and laid out so they never sit on top of the element if the
145
- preferred side would run off screen, the note flips to a side that fits. The
146
- underline is drawn to the measured text width; set `underline: false` for plain
147
- handwriting.
183
+ Notes are measured from their real glyph metrics and laid out so they never sit
184
+ on top of the element. The underline is drawn to the measured text width; set
185
+ `underline: false` for plain handwriting.
186
+
187
+ **The side you ask for is the side you get.** When a note runs past the edge of
188
+ the viewport it slides horizontally by exactly the amount it overhangs, so a
189
+ narrowing viewport walks it gradually inward instead of snapping it to the
190
+ opposite side. The only limit is the element itself: a note level with it stops
191
+ once they meet, rather than sliding across it.
192
+
193
+ Vertical placement is fixed relative to the element and does not react to
194
+ scrolling, so a note stays pinned to what it annotates instead of crawling back
195
+ into view as the page moves. A note beside an element that is itself jammed
196
+ against the viewport edge has nowhere to slide, so it may sit partly off screen
197
+ — use a different `position` or an `offset` there.
198
+
199
+ Nudge a note off its computed spot with `offset`:
200
+
201
+ ```javascript
202
+ doodle(".cta", {
203
+ note: { text: "start chat", position: "top", offset: { x: 40, y: -12 } },
204
+ });
205
+ ```
206
+
207
+ The offset moves the note from the spot `position` chose, and staying in view is
208
+ judged from there — so a large offset slides back into the viewport rather than
209
+ carrying the note off screen. Vertical offsets are always applied verbatim,
210
+ since vertical placement never reacts to the viewport.
148
211
 
149
212
  ## Arrows
150
213
 
@@ -153,17 +216,36 @@ doodle(".cta", { arrow: true });
153
216
 
154
217
  doodle(".cta", {
155
218
  arrow: {
156
- from: "top-right",
219
+ from: "top-right", // note | any position name
157
220
  to: "center",
158
- style: "curved", // curved | straight | dotted
221
+ style: "curved", // curved | straight | dotted | looped
159
222
  },
160
223
  });
161
224
  ```
162
225
 
163
- When the element also has a `note`, the arrow ignores `from` and launches from
164
- the note itself. `to: "edge"` (the default) lands the tip just *outside* the
165
- frame; `to: "center"` is the one setting that deliberately points into the
166
- element.
226
+ `looped` ties a curl into the middle of the sweep the hand-drawn flourish that
227
+ doubles back on itself before reaching the tip.
228
+
229
+ `from` decides where the arrow leaves. **With a note it names a side of the
230
+ note** — where the pen lifts off the handwriting — and the arrow then travels to
231
+ the element wherever that is:
232
+
233
+ ```javascript
234
+ doodle(".cta", {
235
+ note: { text: "start chat", position: "top-right" },
236
+ arrow: { from: "bottom-left" }, // leaves the note's bottom-left corner
237
+ });
238
+ ```
239
+
240
+ `"note"` is the default and picks the side of the note facing the element. A
241
+ callout always departs from its note, so moving the note with `offset` takes the
242
+ arrow with it rather than leaving it stranded by the element.
243
+
244
+ Without a note there is no handwriting to leave from, so `from` falls back to
245
+ naming a side of the element itself.
246
+
247
+ `to: "edge"` (the default) lands the tip just *outside* the frame; `to: "center"`
248
+ is the one setting that deliberately points into the element.
167
249
 
168
250
  ## Decorations
169
251