@aznabee/freehand-ui 0.1.5 → 0.1.7
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 +121 -11
- package/dist/freehand-ui.cjs +513 -120
- package/dist/freehand-ui.js +513 -120
- package/dist/index.d.ts +56 -6
- package/dist/react.cjs +518 -121
- package/dist/react.d.ts +2 -0
- package/dist/react.js +518 -121
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,17 +6,20 @@ Wrap any existing web element with a beautiful, responsive, hand-drawn doodle la
|
|
|
6
6
|
|
|
7
7
|
## What it does
|
|
8
8
|
|
|
9
|
-
`freehand-ui` adds a subtle SVG overlay on top of your UI - thin pen strokes, handwritten notes, arrows, and small decorations. It does
|
|
9
|
+
`freehand-ui` adds a subtle SVG overlay on top of your UI - thin pen strokes, handwritten notes, arrows, and small decorations. It does not change your element's own styling and never blocks clicks (`pointer-events: none`).
|
|
10
10
|
|
|
11
11
|
**Good for:** call-to-action buttons, feature cards, onboarding hints, playful marketing UI.
|
|
12
12
|
|
|
13
|
-
| Feature | One-liner
|
|
14
|
-
| --------------- |
|
|
15
|
-
| **Border** | Hand-drawn frame that follows the element's shape and border-radius
|
|
16
|
-
| **Notes** | Caveat handwriting beside the element
|
|
17
|
-
| **Arrows** | Curved, straight, dotted, or looped pointers
|
|
18
|
-
| **Decorations** | Corner accents, stars, hearts, and more
|
|
19
|
-
| **
|
|
13
|
+
| Feature | One-liner |
|
|
14
|
+
| --------------- | -------------------------------------------------------------------- |
|
|
15
|
+
| **Border** | Hand-drawn frame that follows the element's shape and border-radius |
|
|
16
|
+
| **Notes** | Caveat handwriting beside the element |
|
|
17
|
+
| **Arrows** | Curved, straight, dotted, or looped pointers that draw themselves in |
|
|
18
|
+
| **Decorations** | Corner accents, stars, hearts, and more |
|
|
19
|
+
| **Localised** | Notes in the visitor's own language, right-to-left included |
|
|
20
|
+
| **Responsive** | Stays aligned on resize, scroll, and reflow |
|
|
21
|
+
|
|
22
|
+
**DOM note:** the element is wrapped in a plain positioning `<div>` so the overlay can be mounted beside it (last child, so it paints on top) - the wrapped element keeps its own display and, in a flex/grid parent, its own placement, but a selector depending on it being a *direct* child of its original parent (`.parent > .card`, `:nth-child`) will no longer match. The overlay is also clipped by an `overflow: hidden` ancestor, since it no longer escapes the page via `position: fixed`.
|
|
20
23
|
|
|
21
24
|
**Live on [Aznabee.com](https://aznabee.com)** - see it in production on the real product.
|
|
22
25
|
|
|
@@ -62,6 +65,7 @@ doodle(".card", {
|
|
|
62
65
|
addBreaks: false, // lift the pen at random points around the outline
|
|
63
66
|
fontFamily: null, // override the handwriting stack
|
|
64
67
|
autoLoadFont: true,
|
|
68
|
+
locale: null, // language for a multilingual note; defaults to the visitor's
|
|
65
69
|
});
|
|
66
70
|
```
|
|
67
71
|
|
|
@@ -119,6 +123,54 @@ doodle(".cta", {
|
|
|
119
123
|
|
|
120
124
|
Notes are measured from real glyph metrics and laid out so they never sit on top of the element. The underline is drawn to the measured text width; set `underline: false` for plain handwriting.
|
|
121
125
|
|
|
126
|
+
### Notes in the visitor's language
|
|
127
|
+
|
|
128
|
+
Write the note once per language and it is drawn in the one the visitor reads - taken from `navigator.languages`, which is what their operating system is set to. No configuration, no i18n library.
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
doodle(".cta", {
|
|
132
|
+
note: {
|
|
133
|
+
en: "start chat",
|
|
134
|
+
"pt-BR": "iniciar conversa",
|
|
135
|
+
hi: "चैट शुरू करें",
|
|
136
|
+
ar: "ابدأ الدردشة",
|
|
137
|
+
default: "start chat", // for languages you have not covered
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// with the rest of the note's options
|
|
142
|
+
doodle(".cta", {
|
|
143
|
+
note: {
|
|
144
|
+
text: { en: "try me", fr: "essaie-moi" },
|
|
145
|
+
position: "bottom-right",
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Keys are BCP-47 tags. Matching follows what the visitor asked for, most wanted language first:
|
|
151
|
+
|
|
152
|
+
- **Exact tag** - `pt-BR` for a `pt-BR` reader.
|
|
153
|
+
- **Less specific** - `zh-Hant-TW` takes `zh-Hant` over a bare `zh`, so the script is only given up after the region.
|
|
154
|
+
- **A sibling of the same language** - `en-AU` reads `en-GB` rather than dropping to another language.
|
|
155
|
+
- **`default`**, or failing that the first translation written.
|
|
156
|
+
|
|
157
|
+
Each wanted language is exhausted before the next is tried, so a `fr, en` visitor gets any French on offer ahead of the English original. The page's own `<html lang>` is consulted last, behind the visitor's list.
|
|
158
|
+
|
|
159
|
+
Take over from the browser - to follow your app's own i18n state - with `locale`:
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
doodle(".cta", {
|
|
163
|
+
note: { en: "start chat", fr: "démarrer le chat" },
|
|
164
|
+
locale: i18n.language, // or a list, in preference order
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Notes are redrawn when the browser fires `languagechange`, so switching language mid-session updates them without a reload.
|
|
169
|
+
|
|
170
|
+
**Right-to-left** notes need no flag. The base direction is read off the text itself, by its first strong character - the rule behind `dir="auto"` - so an Arabic or Hebrew note is laid out and underlined right-to-left whether it came from a locale map or a plain string.
|
|
171
|
+
|
|
172
|
+
Caveat itself only ships Latin and Cyrillic. Devanagari, Arabic, CJK and the rest fall through to the system handwriting stack and, past that, to whatever the browser picks - so those notes are legible but not handwritten. Point `fontFamily` at a face that covers your script to keep the handwriting.
|
|
173
|
+
|
|
122
174
|
### Placement behavior
|
|
123
175
|
|
|
124
176
|
- **The side you ask for is the side you get.** When a note runs past the viewport edge it slides horizontally by exactly the amount it overhangs, so a narrowing viewport walks it gradually inward instead of snapping to the opposite side.
|
|
@@ -151,7 +203,9 @@ doodle(".cta", {
|
|
|
151
203
|
|
|
152
204
|

|
|
153
205
|
|
|
154
|
-
**Styles:** `curved` (default), `straight`, `dotted`, `looped` - `looped`
|
|
206
|
+
**Styles:** `curved` (default), `straight`, `dotted`, `looped` - `looped` ties a curl into the sweep, crossing the shaft once on the inside of its bow.
|
|
207
|
+
|
|
208
|
+
Every arc sweeps _over_ the straight line and drops onto its target, the way a hand draws one. With nothing to steer around, that side is fixed rather than picked at random, so a redraw never mirrors the gesture.
|
|
155
209
|
|
|
156
210
|
**`from`** decides where the arrow leaves:
|
|
157
211
|
|
|
@@ -168,6 +222,43 @@ doodle(".cta", {
|
|
|
168
222
|
|
|
169
223
|
**`to`:** `"edge"` (default) lands the tip just outside the frame; `"center"` points into the element.
|
|
170
224
|
|
|
225
|
+
### Arrow animation
|
|
226
|
+
|
|
227
|
+
Arrows animate by default. Two gestures, both pure CSS:
|
|
228
|
+
|
|
229
|
+
- **draw** - the shaft is revealed by winding its dash offset down to zero, so the pen appears to travel from the note to the tip. The arrowhead flicks in as the pen arrives.
|
|
230
|
+
- **drift** - the whole arrow leans a few pixels along its own line of travel, toward what it points at.
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
doodle(".cta", { arrow: { style: "looped" } }); // both, at defaults
|
|
234
|
+
|
|
235
|
+
doodle(".cta", { arrow: { animate: false } }); // draw it static
|
|
236
|
+
|
|
237
|
+
doodle(".cta", {
|
|
238
|
+
arrow: {
|
|
239
|
+
animate: {
|
|
240
|
+
draw: true, // dash sweep along the shaft
|
|
241
|
+
drift: true, // lean toward the target
|
|
242
|
+
speed: 1, // multiplier over every duration - 2 is twice as fast
|
|
243
|
+
duration: 900, // shaft draw, ms, before `speed`
|
|
244
|
+
delay: 150, // wait before the first stroke appears, ms
|
|
245
|
+
distance: 5, // how far the lean travels, px
|
|
246
|
+
driftDuration: 2200, // one full lean-and-return, ms
|
|
247
|
+
repeat: true, // false leans in once and stays there
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`speed` scales every phase at once, so the whole gesture keeps its shape - use it in preference to setting durations individually.
|
|
254
|
+
|
|
255
|
+
A `dotted` arrow spends its dash pattern on the dots, and one pattern cannot both space them and hide the undrawn tail, so its dots flow toward the tip instead of the shaft being drawn on.
|
|
256
|
+
|
|
257
|
+
Notes:
|
|
258
|
+
|
|
259
|
+
- **`prefers-reduced-motion: reduce` turns both gestures off** and leaves the arrow fully drawn.
|
|
260
|
+
- The overlay moves with the element for free through ordinary layout - scrolling, a sibling loading in above it - and is only rebuilt when the element's own size actually changes. Animations are placed by how long the overlay has been on the page rather than restarted, so repositioning never replays a draw or jolts a lean mid-cycle.
|
|
261
|
+
|
|
171
262
|
### Decorations
|
|
172
263
|
|
|
173
264
|
```javascript
|
|
@@ -200,7 +291,7 @@ Corner marks follow the _rounded_ corner rather than the bounding box. Marks too
|
|
|
200
291
|
|
|
201
292
|
Chips and icon buttons get a mirrored pair of two-stroke `emphasis` marks - one either side - instead of corner marks. This is automatic when the element is under 48px on its short side or under 130px wide. `style: "sides"` forces it at any size; `style: "corners"` opts out.
|
|
202
293
|
|
|
203
|
-
Other types - `star`, `
|
|
294
|
+
Other types - `star`, `glimmer`, `smiley` - fill a corner slot when no accent or star was requested.
|
|
204
295
|
|
|
205
296
|
### Child selector mode
|
|
206
297
|
|
|
@@ -312,6 +403,25 @@ const caveat = Caveat({ subsets: ["latin"], variable: "--font-caveat" });
|
|
|
312
403
|
</Doodle>;
|
|
313
404
|
```
|
|
314
405
|
|
|
406
|
+
### Localised notes
|
|
407
|
+
|
|
408
|
+
Same as the core - a map of languages goes straight in as the note, and `locale` hands the choice to your own i18n state:
|
|
409
|
+
|
|
410
|
+
```jsx
|
|
411
|
+
<Doodle note={{ en: "start chat", fr: "démarrer le chat" }}>
|
|
412
|
+
<Button />
|
|
413
|
+
</Doodle>;
|
|
414
|
+
|
|
415
|
+
<Doodle
|
|
416
|
+
note={{ en: "start chat", fr: "démarrer le chat" }}
|
|
417
|
+
locale={i18n.language}
|
|
418
|
+
>
|
|
419
|
+
<Button />
|
|
420
|
+
</Doodle>;
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Server-rendered markup is unaffected: the overlay is drawn in an effect, so the language is read on the client and there is nothing to mismatch on hydration.
|
|
424
|
+
|
|
315
425
|
### React notes
|
|
316
426
|
|
|
317
427
|
- Inline object props (`note={{ text: "hi" }}`) are compared by value, so a re-render does not tear down and redraw the overlay with a new random seed.
|
|
@@ -346,7 +456,7 @@ Then open:
|
|
|
346
456
|
- **Framework agnostic** - plain JavaScript at the core; React is an optional entry point
|
|
347
457
|
- **SVG based** - hand-drawn paths, not CSS borders
|
|
348
458
|
- **Non-invasive** - `pointer-events: none`, no layout changes
|
|
349
|
-
- **Responsive** -
|
|
459
|
+
- **Responsive** - absolutely positioned beside the element, so it tracks scroll and layout shifts for free; `ResizeObserver` covers the rest
|
|
350
460
|
- **Lightweight** - zero runtime dependencies
|
|
351
461
|
|
|
352
462
|
---
|