@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 ADDED
@@ -0,0 +1,102 @@
1
+ # @lettermark/react
2
+
3
+ > Scalable SVG avatar fallback for React — initials, deterministic colors, image fallback chain.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@lettermark/react.svg)](https://www.npmjs.com/package/@lettermark/react)
6
+ [![minzipped size](https://img.shields.io/bundlephobia/minzip/@lettermark/react)](https://bundlephobia.com/package/@lettermark/react)
7
+ [![license](https://img.shields.io/npm/l/@lettermark/react.svg)](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)
@@ -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
+ }