@welcome-back/brand 0.1.0 → 0.1.2
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 +301 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
# @welcome-back/brand
|
|
2
2
|
|
|
3
|
-
WelcomeBack brand assets for React apps.
|
|
4
|
-
|
|
3
|
+
WelcomeBack brand assets for React apps.
|
|
4
|
+
|
|
5
|
+
Today the package has one component: **`RunnerLoader`**, the isotipo W running
|
|
6
|
+
left. Its legs swing on their hips and cross under the body while the body bobs.
|
|
7
|
+
Use it for loading moments that mean "on the way to the next step".
|
|
8
|
+
|
|
9
|
+
- React 18 or 19. Works in Next.js (App Router and Pages), Vite and plain React.
|
|
10
|
+
- No CSS import, no Tailwind, no bundler setup: the animation ships inside the SVG.
|
|
11
|
+
- Server-renderable: it shows before JavaScript loads. Works in React Server
|
|
12
|
+
Components (it uses only `useId`).
|
|
13
|
+
- ESM and CommonJS builds, so Jest suites load it with no transform setup.
|
|
14
|
+
|
|
15
|
+
## Contents
|
|
16
|
+
|
|
17
|
+
- [Install](#install)
|
|
18
|
+
- [Quick start](#quick-start)
|
|
19
|
+
- [API](#api)
|
|
20
|
+
- [Examples](#examples)
|
|
21
|
+
- [Accessibility](#accessibility)
|
|
22
|
+
- [Where to use it, and where not](#where-to-use-it-and-where-not)
|
|
23
|
+
- [Troubleshooting](#troubleshooting)
|
|
24
|
+
- [Release](#release)
|
|
25
|
+
- [Source art](#source-art)
|
|
5
26
|
|
|
6
27
|
## Install
|
|
7
28
|
|
|
@@ -9,37 +30,295 @@ running left with its legs crossing under the body.
|
|
|
9
30
|
npm i @welcome-back/brand
|
|
10
31
|
```
|
|
11
32
|
|
|
12
|
-
|
|
13
|
-
the SVG. ESM and CommonJS builds, so Jest suites can load it too.
|
|
33
|
+
The package is public on npm. No token or `.npmrc` is needed.
|
|
14
34
|
|
|
15
|
-
##
|
|
35
|
+
## Quick start
|
|
16
36
|
|
|
17
37
|
```tsx
|
|
18
38
|
import { RunnerLoader } from "@welcome-back/brand";
|
|
19
39
|
|
|
20
|
-
|
|
21
|
-
<
|
|
22
|
-
|
|
40
|
+
export function Loading() {
|
|
41
|
+
return <RunnerLoader className="w-24" label="Loading" />;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
That is all. Set a width, and the height follows the art.
|
|
46
|
+
|
|
47
|
+
## API
|
|
48
|
+
|
|
49
|
+
### Props
|
|
50
|
+
|
|
51
|
+
| Prop | Type | Default | What it does |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| `className` | `string` | — | Added after the built-in `wb-runner` class. Use it for size, color and the CSS variables below. |
|
|
54
|
+
| `label` | `string` | — | Accessible name. With a label the SVG is `role="img"` and screen readers announce it. Without a label it is `aria-hidden`: use that when a parent element already names the loading state. |
|
|
55
|
+
|
|
56
|
+
The component renders one `<svg>`. It takes no other props.
|
|
57
|
+
|
|
58
|
+
### CSS custom properties
|
|
59
|
+
|
|
60
|
+
You can set these in `className` (Tailwind arbitrary properties), in your own
|
|
61
|
+
CSS class, or on any ancestor element. Values inherit, so one setting on a
|
|
62
|
+
wrapper applies to all runners inside it.
|
|
63
|
+
|
|
64
|
+
| Property | Controls | Default | Notes |
|
|
65
|
+
|---|---|---|---|
|
|
66
|
+
| `color` | Ink of the W, legs and shoes | `currentColor` | Any Tailwind `text-*` class works. |
|
|
67
|
+
| `--wb-runner-pants` | Fill inside the pants | `Canvas` | Set it to the color **behind** the runner. The near leg covers the far leg when they cross, so the pants must be opaque. |
|
|
68
|
+
| `--wb-runner-step` | Duration of one step | `0.32s` | A full stride is two steps (0.64 s). Lower is faster. |
|
|
69
|
+
|
|
70
|
+
`Canvas` is the page background color that the browser uses for the current
|
|
71
|
+
`color-scheme`: white in light mode, near-black in dark mode. On any other
|
|
72
|
+
surface (a card, a tinted panel, a brand color) set `--wb-runner-pants`.
|
|
73
|
+
|
|
74
|
+
### Size guide
|
|
75
|
+
|
|
76
|
+
| Width | Use |
|
|
77
|
+
|---|---|
|
|
78
|
+
| 96–128 px | Full-screen or full-panel loading |
|
|
79
|
+
| 48–64 px | Loading inside a card or a list area |
|
|
80
|
+
| 32–40 px | Next to text ("Loading…") |
|
|
81
|
+
| under 32 px | Not recommended: the legs become unreadable. Use a plain spinner. |
|
|
82
|
+
|
|
83
|
+
## Examples
|
|
84
|
+
|
|
85
|
+
### Tailwind: size, color and speed in one `className`
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
<RunnerLoader className="w-16 text-foreground" />
|
|
89
|
+
|
|
90
|
+
// Faster (0.2 s per step) and in the muted text color:
|
|
91
|
+
<RunnerLoader className="w-16 text-muted-foreground [--wb-runner-step:0.2s]" />
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Plain CSS (no Tailwind)
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
/* loading.css */
|
|
98
|
+
.page-loader {
|
|
99
|
+
width: 112px;
|
|
100
|
+
color: #171717;
|
|
101
|
+
--wb-runner-pants: #ffffff;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import "./loading.css";
|
|
107
|
+
|
|
108
|
+
<RunnerLoader className="page-loader" label="Loading" />;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Inline style on a wrapper
|
|
112
|
+
|
|
113
|
+
`RunnerLoader` has no `style` prop. The CSS variables inherit, so put inline
|
|
114
|
+
styles on a parent element. TypeScript does not know custom properties, so cast
|
|
115
|
+
the style object:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import type { CSSProperties } from "react";
|
|
119
|
+
|
|
120
|
+
const runnerTheme = {
|
|
121
|
+
color: "#171717",
|
|
122
|
+
"--wb-runner-pants": "#F6F4E9",
|
|
123
|
+
"--wb-runner-step": "0.28s",
|
|
124
|
+
} as CSSProperties;
|
|
125
|
+
|
|
126
|
+
<div style={runnerTheme}>
|
|
127
|
+
<RunnerLoader className="w-24" label="Loading" />
|
|
128
|
+
</div>;
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Match a shadcn / Tailwind 3 theme token
|
|
132
|
+
|
|
133
|
+
shadcn stores colors as bare HSL values (`--background: 0 0% 100%`), so wrap
|
|
134
|
+
them in `hsl()`:
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
<div className="bg-background text-foreground [--wb-runner-pants:hsl(var(--background))]">
|
|
138
|
+
<RunnerLoader className="w-24" />
|
|
23
139
|
</div>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
In Tailwind 4 the tokens are full colors, so drop the `hsl()`:
|
|
143
|
+
`[--wb-runner-pants:var(--background)]`.
|
|
144
|
+
|
|
145
|
+
### On a card or a colored surface
|
|
24
146
|
|
|
25
|
-
|
|
26
|
-
|
|
147
|
+
The pants must match the surface directly behind the runner, not the page:
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
// On a card
|
|
151
|
+
<div className="rounded-xl bg-card p-8 [--wb-runner-pants:hsl(var(--card))]">
|
|
152
|
+
<RunnerLoader className="w-16" />
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
// On a brand-orange disc, with a cream runner
|
|
156
|
+
<div className="grid size-32 place-items-center rounded-full bg-[#FF6E26] text-[#FFFAF0] [--wb-runner-pants:#FF6E26]">
|
|
157
|
+
<RunnerLoader className="w-20" label="Loading" />
|
|
158
|
+
</div>
|
|
27
159
|
```
|
|
28
160
|
|
|
29
|
-
|
|
161
|
+
### Dark mode
|
|
30
162
|
|
|
31
|
-
|
|
163
|
+
With the defaults, no extra code is needed on the page background: `Canvas`
|
|
164
|
+
follows `color-scheme`, and the ink follows your text color.
|
|
32
165
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
166
|
+
```tsx
|
|
167
|
+
<main className="bg-background text-foreground">
|
|
168
|
+
<RunnerLoader className="w-24" />
|
|
169
|
+
</main>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If your dark mode is a `.dark` class and not `color-scheme`, set the pants from
|
|
173
|
+
the token, as in the shadcn example above.
|
|
38
174
|
|
|
39
|
-
|
|
40
|
-
`style={{ "--wb-runner-pants": "hsl(var(--background))" }}` on the wrapping element.
|
|
175
|
+
### Next to text
|
|
41
176
|
|
|
42
|
-
|
|
177
|
+
The label is on the wrapper, so the runner stays decorative:
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
<div role="status" className="flex items-center gap-3 text-sm text-muted-foreground">
|
|
181
|
+
<RunnerLoader className="w-9" />
|
|
182
|
+
<span>Loading your rewards…</span>
|
|
183
|
+
</div>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Next.js route loading (`loading.tsx`)
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
// app/dashboard/loading.tsx
|
|
190
|
+
import { RunnerLoader } from "@welcome-back/brand";
|
|
191
|
+
|
|
192
|
+
export default function Loading() {
|
|
193
|
+
return (
|
|
194
|
+
<div className="grid min-h-[60vh] place-items-center">
|
|
195
|
+
<RunnerLoader className="w-24" label="Loading" />
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
This is a Server Component. It needs no `"use client"`.
|
|
202
|
+
|
|
203
|
+
### React `Suspense` fallback
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
<Suspense fallback={<RunnerLoader className="mx-auto w-20" label="Loading" />}>
|
|
207
|
+
<CustomerDetails id={id} />
|
|
208
|
+
</Suspense>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Show only if the wait is long
|
|
212
|
+
|
|
213
|
+
On a fast request the runner can flash for a fraction of a stride. Wait before
|
|
214
|
+
you show it:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
"use client";
|
|
218
|
+
import { useEffect, useState } from "react";
|
|
219
|
+
import { RunnerLoader } from "@welcome-back/brand";
|
|
220
|
+
|
|
221
|
+
export function DelayedRunner({ delayMs = 200 }: { delayMs?: number }) {
|
|
222
|
+
const [visible, setVisible] = useState(false);
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
const timer = setTimeout(() => setVisible(true), delayMs);
|
|
225
|
+
return () => clearTimeout(timer);
|
|
226
|
+
}, [delayMs]);
|
|
227
|
+
return visible ? <RunnerLoader className="w-16" label="Loading" /> : null;
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Full-screen splash
|
|
232
|
+
|
|
233
|
+
A server-rendered overlay covers the page before JavaScript runs. Hide it after
|
|
234
|
+
the app hydrates. This is a short version of staff-ui's `AppSplash`, which also
|
|
235
|
+
holds the splash for two strides on the first open of each session.
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
"use client";
|
|
239
|
+
import { useEffect, useState } from "react";
|
|
240
|
+
import { RunnerLoader } from "@welcome-back/brand";
|
|
241
|
+
|
|
242
|
+
export function Splash() {
|
|
243
|
+
const [leaving, setLeaving] = useState(false);
|
|
244
|
+
const [gone, setGone] = useState(false);
|
|
245
|
+
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
setLeaving(true);
|
|
248
|
+
const timer = setTimeout(() => setGone(true), 300); // fade duration
|
|
249
|
+
return () => clearTimeout(timer);
|
|
250
|
+
}, []);
|
|
251
|
+
|
|
252
|
+
if (gone) return null;
|
|
253
|
+
return (
|
|
254
|
+
<div
|
|
255
|
+
role="status"
|
|
256
|
+
aria-label="Loading"
|
|
257
|
+
className={`fixed inset-0 z-50 grid place-items-center bg-background text-foreground
|
|
258
|
+
[--wb-runner-pants:hsl(var(--background))] transition-opacity duration-300
|
|
259
|
+
${leaving ? "pointer-events-none opacity-0" : ""}`}
|
|
260
|
+
>
|
|
261
|
+
<RunnerLoader className="w-28" />
|
|
262
|
+
</div>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Vite / plain React
|
|
268
|
+
|
|
269
|
+
Same import, no config:
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
import { RunnerLoader } from "@welcome-back/brand";
|
|
273
|
+
|
|
274
|
+
function App() {
|
|
275
|
+
const { data, isLoading } = useQuery(/* … */);
|
|
276
|
+
if (isLoading) return <RunnerLoader className="loader" label="Loading" />;
|
|
277
|
+
return <Dashboard data={data} />;
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Jest and Testing Library
|
|
282
|
+
|
|
283
|
+
Jest loads the CommonJS build automatically. The SVG always has the class
|
|
284
|
+
`wb-runner`, so you can find it in tests:
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
render(<Loading />);
|
|
288
|
+
expect(screen.getByRole("img", { name: "Loading" })).toHaveClass("wb-runner");
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Accessibility
|
|
292
|
+
|
|
293
|
+
- **Give exactly one accessible name per loading state.** Either put `label` on
|
|
294
|
+
the runner, or put `role="status"` and `aria-label` on its wrapper and leave
|
|
295
|
+
`label` out. Do not do both, or screen readers announce it twice.
|
|
296
|
+
- **Translate the label.** Pass it from your i18n layer, for example
|
|
297
|
+
`label={t("loading")}`.
|
|
298
|
+
- **Reduced motion is built in.** With `prefers-reduced-motion: reduce` the
|
|
299
|
+
runner holds one pose (legs apart, mid-stride), so the loading state is still
|
|
300
|
+
visible.
|
|
301
|
+
|
|
302
|
+
## Where to use it, and where not
|
|
303
|
+
|
|
304
|
+
| Use it | Do not use it |
|
|
305
|
+
|---|---|
|
|
306
|
+
| App open and splash screens | Inside buttons: at button size the legs cannot be read |
|
|
307
|
+
| Moves to the next step (after a scan, before a customer page) | Pages where the customer sees a business's own brand first (enrollment, card download): our W would lead over their brand |
|
|
308
|
+
| Route and Suspense fallbacks for a full page or panel | Every small wait: if it is everywhere, staff stop noticing it |
|
|
309
|
+
|
|
310
|
+
A skeleton is better when the layout that is coming is known: it shows where
|
|
311
|
+
the content will appear.
|
|
312
|
+
|
|
313
|
+
## Troubleshooting
|
|
314
|
+
|
|
315
|
+
| Symptom | Cause | Fix |
|
|
316
|
+
|---|---|---|
|
|
317
|
+
| The legs show a light or dark rectangle | `--wb-runner-pants` does not match the surface behind the runner | Set it to that surface color |
|
|
318
|
+
| The runner is invisible | Ink is the same color as the background | Set `color` / a `text-*` class |
|
|
319
|
+
| The runner does not move | The OS asks for reduced motion | Nothing to fix; this is intended |
|
|
320
|
+
| The runner fills the whole container | No width is set | Give it a width (`w-24`, or `width` in CSS) |
|
|
321
|
+
| A Tailwind arbitrary property has no effect | Tailwind does not scan the file | Check the `content` globs in the Tailwind config |
|
|
43
322
|
|
|
44
323
|
## Release
|
|
45
324
|
|
|
@@ -50,7 +329,8 @@ With `prefers-reduced-motion: reduce`, the runner holds one pose.
|
|
|
50
329
|
|
|
51
330
|
Publishing uses npm trusted publishing (GitHub OIDC): no npm token is stored.
|
|
52
331
|
The trusted publisher is set on npmjs.com → `@welcome-back/brand` → Settings:
|
|
53
|
-
GitHub Actions, `WelcomeBackLoyalty/brand`, workflow `publish.yml
|
|
332
|
+
GitHub Actions, `WelcomeBackLoyalty/brand`, workflow `publish.yml`, with
|
|
333
|
+
**Allow npm publish** ticked.
|
|
54
334
|
|
|
55
335
|
## Source art
|
|
56
336
|
|