@lettermark/react 0.1.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 +102 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +1 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @lettermark/react
|
|
2
|
+
|
|
3
|
+
> Scalable SVG avatar fallback for React — initials, deterministic colors, image fallback chain.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@lettermark/react)
|
|
6
|
+
[](https://bundlephobia.com/package/@lettermark/react)
|
|
7
|
+
[](https://github.com/tomaszbilka/lettermark/blob/main/LICENSE)
|
|
8
|
+
|
|
9
|
+
React component built on [`lettermark`](https://www.npmjs.com/package/lettermark) core — drop-in avatar fallback for user lists, nav bars and profile placeholders. No CSS file to import.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Grapheme-correct initials** — powered by `Intl.Segmenter` via core; emoji, flags and CJK names stay intact.
|
|
14
|
+
- **Deterministic, WCAG-safe colors** — same name → same color; foreground picked for AA contrast (≥ 4.5:1).
|
|
15
|
+
- **SSR-safe** — identical server and client markup for initials; no hydration mismatch from color or text logic.
|
|
16
|
+
- **Lightweight** — ~0.9 kB gzipped; React is a peer dependency (not bundled into your app).
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @lettermark/react
|
|
22
|
+
# peer: react >=18, react-dom >=18 (already in your app)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`lettermark` is installed automatically as a dependency. React is **not** bundled — your app supplies it via `peerDependencies`.
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { Lettermark } from "@lettermark/react";
|
|
31
|
+
|
|
32
|
+
export function UserAvatar({ name }: { name: string }) {
|
|
33
|
+
return <Lettermark name={name} size={40} />;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
With image fallback:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<Lettermark name="Jan Kowalski" src={user.avatarUrl} size={48} shape="rounded" loading="lazy" />
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Initials render immediately; the image overlays on load. On error, initials stay visible.
|
|
44
|
+
|
|
45
|
+
## Props
|
|
46
|
+
|
|
47
|
+
| Prop | Type | Default | Description |
|
|
48
|
+
|------|------|---------|-------------|
|
|
49
|
+
| `name` | `string` | — | Source for initials and color |
|
|
50
|
+
| `src` | `string?` | — | Image URL; falls back to initials on error |
|
|
51
|
+
| `size` | `number \| string?` | fluid | `40` → px, `"3rem"` → CSS; omit → `100%` of parent (parent needs explicit dimensions; root uses `aspect-ratio: 1/1`) |
|
|
52
|
+
| `shape` | `'circle' \| 'square' \| 'rounded' \| 'squircle'` | `'circle'` | Avatar shape |
|
|
53
|
+
| `color` | `string?` | auto | Fixed background hex |
|
|
54
|
+
| `palette` | `string[]?` | auto | Brand colors — picked deterministically |
|
|
55
|
+
| `className` / `style` | — | — | Root styling |
|
|
56
|
+
| `classNames` | `{ root?, image?, initials? }` | — | Per-element classes (Tailwind-friendly) |
|
|
57
|
+
| `alt` | `string?` | `name` | Accessible label (`aria-label`) |
|
|
58
|
+
| `title` | `string?` | — | Native tooltip |
|
|
59
|
+
| `as` | `ElementType` | `'span'` | Polymorphic root (`button`, `div`, …) |
|
|
60
|
+
| `loading` | `img loading` | — | Passed to `<img>` (`"lazy"` / `"eager"`) |
|
|
61
|
+
| `fallback` | `ReactNode?` | — | Replaces initials SVG when no image is shown (`src` still takes priority when it loads) |
|
|
62
|
+
| `length` | `1 \| 2 \| 3` | `2` | Max initials (from core) |
|
|
63
|
+
| `locale` | `string?` | — | `Intl.Segmenter` locale |
|
|
64
|
+
| `fontSize` | `number?` | auto | Override SVG font size on 100×100 canvas |
|
|
65
|
+
|
|
66
|
+
Spread props (`onClick`, etc.) go to the root element.
|
|
67
|
+
|
|
68
|
+
## Examples
|
|
69
|
+
|
|
70
|
+
**Fluid — fills parent container:**
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
<div style={{ width: 64, height: 64 }}>
|
|
74
|
+
<Lettermark name="Jan Kowalski" />
|
|
75
|
+
</div>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Next.js App Router:**
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
"use client";
|
|
82
|
+
|
|
83
|
+
import { Lettermark } from "@lettermark/react";
|
|
84
|
+
|
|
85
|
+
<Lettermark name={user.name} size={40} src={user.avatarUrl} />;
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Button avatar:**
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<Lettermark as="button" name="Jan Kowalski" size={32} type="button" onClick={openProfile} />
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Custom fallback:**
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
<Lettermark name="" fallback={<span>?</span>} size={40} />
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
[MIT](https://github.com/tomaszbilka/lettermark/blob/main/LICENSE)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as react0 from "react";
|
|
2
|
+
import { CSSProperties, ComponentPropsWithoutRef, ElementType, ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/lettermark.d.ts
|
|
5
|
+
type LettermarkShape = "circle" | "square" | "rounded" | "squircle";
|
|
6
|
+
interface LettermarkClassNames {
|
|
7
|
+
root?: string;
|
|
8
|
+
image?: string;
|
|
9
|
+
initials?: string;
|
|
10
|
+
}
|
|
11
|
+
type Size = number | string | undefined;
|
|
12
|
+
interface LettermarkOwnProps {
|
|
13
|
+
/** Full name — source for initials and deterministic color. */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Optional image URL; falls back to initials on load error. */
|
|
16
|
+
src?: string;
|
|
17
|
+
/** Fixed px, CSS units, or omit for fluid (100% of parent). */
|
|
18
|
+
size?: Size;
|
|
19
|
+
shape?: LettermarkShape;
|
|
20
|
+
/** Fixed background color (hex). Overrides auto-generated hue. */
|
|
21
|
+
color?: string;
|
|
22
|
+
/** Brand palette — background picked deterministically from these colors. */
|
|
23
|
+
palette?: string[];
|
|
24
|
+
className?: string;
|
|
25
|
+
style?: CSSProperties;
|
|
26
|
+
classNames?: LettermarkClassNames;
|
|
27
|
+
/** Accessible label; defaults to `name`. */
|
|
28
|
+
alt?: string;
|
|
29
|
+
/** Native tooltip with the full name. */
|
|
30
|
+
title?: string;
|
|
31
|
+
loading?: ComponentPropsWithoutRef<"img">["loading"];
|
|
32
|
+
/** Custom content when no image is shown; replaces default initials SVG. */
|
|
33
|
+
fallback?: ReactNode;
|
|
34
|
+
length?: 1 | 2 | 3;
|
|
35
|
+
locale?: string;
|
|
36
|
+
fontSize?: number;
|
|
37
|
+
}
|
|
38
|
+
type LettermarkProps<C extends ElementType = "span"> = LettermarkOwnProps & {
|
|
39
|
+
as?: C;
|
|
40
|
+
} & Omit<ComponentPropsWithoutRef<C>, keyof LettermarkOwnProps | "as" | "children">;
|
|
41
|
+
declare function Lettermark<C extends ElementType = "span">({
|
|
42
|
+
as,
|
|
43
|
+
name,
|
|
44
|
+
src,
|
|
45
|
+
size,
|
|
46
|
+
shape,
|
|
47
|
+
color,
|
|
48
|
+
palette,
|
|
49
|
+
className,
|
|
50
|
+
style,
|
|
51
|
+
classNames,
|
|
52
|
+
alt,
|
|
53
|
+
title,
|
|
54
|
+
loading,
|
|
55
|
+
fallback,
|
|
56
|
+
length,
|
|
57
|
+
locale,
|
|
58
|
+
fontSize,
|
|
59
|
+
...rest
|
|
60
|
+
}: LettermarkProps<C>): react0.JSX.Element;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { Lettermark, type LettermarkClassNames, type LettermarkOwnProps, type LettermarkProps, type LettermarkShape };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getLettermark as e}from"lettermark";import{useState as t}from"react";import{jsx as n,jsxs as r}from"react/jsx-runtime";const i={square:0,rounded:16,squircle:28},a={circle:`50%`,square:`0`,rounded:`16%`,squircle:`28%`};function o(e){return typeof e==`number`?`${e}px`:typeof e==`string`?e:`100%`}function s({shape:e,background:t,foreground:a,fontSize:o,initials:s,className:c}){return r(`svg`,{viewBox:`0 0 100 100`,className:c,style:{display:`block`,width:`100%`,height:`100%`},"aria-hidden":`true`,focusable:`false`,children:[e===`circle`?n(`circle`,{cx:`50`,cy:`50`,r:`50`,fill:t}):n(`rect`,{width:`100`,height:`100`,rx:i[e],fill:t}),n(`text`,{x:`50`,y:`50`,dy:`0.36em`,textAnchor:`middle`,fill:a,fontSize:o,fontFamily:`inherit`,fontWeight:600,children:s})]})}function c({as:i,name:c,src:l,size:u,shape:d=`circle`,color:f,palette:p,className:m,style:h,classNames:g,alt:_,title:v,loading:y,fallback:b,length:x,locale:S,fontSize:C,...w}){let T=i??`span`,[E,D]=t(null),[O,k]=t(null),{initials:A,background:j,foreground:M,fontSize:N}=e(c,{length:x,locale:S,fontSize:C,palette:p??(f?[f]:void 0)}),P=b!=null&&(!l||O===l),F=b==null,I=!!l&&O!==l,L=!!l&&E===l,R={display:`inline-block`,width:o(u),height:o(u),aspectRatio:u===void 0?`1 / 1`:void 0,position:`relative`,overflow:`hidden`,borderRadius:a[d],...h};return r(T,{role:`img`,"aria-label":_??c,title:v,"data-shape":d,className:g?.root??m,style:R,...w,children:[F&&n(s,{shape:d,background:j,foreground:M,fontSize:N,initials:A,className:g?.initials}),P&&b,I&&n(`img`,{src:l,alt:``,loading:y,onLoad:()=>D(l??null),onError:()=>k(l??null),className:g?.image,style:{position:`absolute`,inset:0,width:`100%`,height:`100%`,objectFit:`cover`,opacity:L?1:0}},l)]})}export{c as Lettermark};
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lettermark/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scalable SVG avatar fallback component for React — initials, deterministic colors, image fallback chain.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsdown",
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"react",
|
|
25
|
+
"react-component",
|
|
26
|
+
"avatar",
|
|
27
|
+
"avatar-fallback",
|
|
28
|
+
"initials",
|
|
29
|
+
"monogram",
|
|
30
|
+
"lettermark",
|
|
31
|
+
"placeholder",
|
|
32
|
+
"fallback",
|
|
33
|
+
"svg"
|
|
34
|
+
],
|
|
35
|
+
"author": "tomaszbilka",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/tomaszbilka/lettermark.git",
|
|
40
|
+
"directory": "packages/react"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/tomaszbilka/lettermark/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/tomaszbilka/lettermark/tree/main/packages/react#readme",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"lettermark": "workspace:*"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"react": ">=18",
|
|
51
|
+
"react-dom": ">=18"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
55
|
+
"@testing-library/react": "^16.3.2",
|
|
56
|
+
"@types/react": "^19.2.17",
|
|
57
|
+
"@types/react-dom": "^19.2.3",
|
|
58
|
+
"react": "^19.2.7",
|
|
59
|
+
"react-dom": "^19.2.7",
|
|
60
|
+
"vitest": "^3.2.4"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=20"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public",
|
|
67
|
+
"provenance": true
|
|
68
|
+
}
|
|
69
|
+
}
|