@waukeshamakerspace/react-components
v2.0.0
Published
Shared Waukesha Makerspace React components (theme toggle, avatar, branded spinner), styled with design-tokens semantic CSS variables so they work in any WMI UI stack
Maintainers
Readme
@waukeshamakerspace/react-components
Shared Waukesha Makerspace React components: the brand-specific widgets every WMI UI needs but no framework provides. Published on the public npm registry.
All styling comes from the semantic CSS variables in
@waukeshamakerspace/design-tokens
(var(--primary), var(--surface), ...), so the components render correctly
in light and dark mode and drop into any WMI stack: Next.js + Tailwind, MUI,
or plain CSS. They carry no styling framework of their own.
Scope
This package is deliberately small. It holds brand-specific, cross-app widgets only. Generic primitives (buttons, inputs, cards) stay out: Tailwind and MUI apps already have strong opinions there, and duplicating them here would fight every consumer's styling system.
Install
npm install @waukeshamakerspace/react-components @waukeshamakerspace/design-tokensPeer dependencies: React 18 or 19, @waukeshamakerspace/design-tokens >= 1.3.
Load the token variables and the component styles once, at app root:
import '@waukeshamakerspace/design-tokens/css/tokens.css'; // or the Tailwind layer
import '@waukeshamakerspace/react-components/styles.css';Apps already consuming the tokens package (per
waukesha-makers-architecture/shared/ui-guidelines.md) only add the second
line.
Theme state
Theme state has one source of truth, owned by the tokens package's
theme-init: the override attribute on <html> plus the persisted choice in
localStorage (wm-theme). Explicit light/dark choices persist across
visits; "system" (follow the device) is the default and choosing it clears
the stored choice. This package never keeps its own copy, so every theme
control on a page, and in other open tabs, stays in sync.
useThemeMode
import { useThemeMode } from '@waukeshamakerspace/react-components';
const { mode, resolvedTheme, setMode } = useThemeMode();
// mode: 'light' | 'dark' | 'system' (the user's choice)
// resolvedTheme: 'light' | 'dark' (what's applied right now)
// setMode: change + persist the choiceBuilt on useSyncExternalStore; SSR renders as system/light and corrects
itself after hydration. Use it wherever app code needs theme state (chart
palettes, embedded maps, ...) instead of reading the DOM or duplicating
state.
Components
ThemeToggle
The compact theme control that ui-guidelines requires in every WMI UI, as one import. An icon button that cycles system → light → dark through the shared theme state.
import { ThemeToggle } from '@waukeshamakerspace/react-components';
<ThemeToggle />| Prop | Type | Default | Notes |
|------|------|---------|-------|
| onModeChange | (mode) => void | | Called after each switch |
| className | string | | Merged onto the button |
The component assumes the app follows ui-guidelines: the theme-init script
is inlined (or initTheme() runs at startup) so the stored choice applies
pre-paint and OS-change syncing coordinates.
ThemeModeSwitch
The settings-page sibling of ThemeToggle: a segmented control showing light / auto / dark at once. A WAI-ARIA radio group (arrow keys move and select, roving tabindex), sharing the same theme state as everything else.
import { ThemeModeSwitch } from '@waukeshamakerspace/react-components';
<ThemeModeSwitch />| Prop | Type | Default | Notes |
|------|------|---------|-------|
| onModeChange | (mode) => void | | Called after each selection |
| label | string | 'Theme' | Accessible name for the group |
| className | string | | Merged onto the group |
Avatar
User avatar with an initials fallback, clipped to the brand gear silhouette by default (a scalable SVG clip, so any size works). The fallback tone (primary, secondary, or accent token pair) is hashed from the name, so the same person gets the same color in every app.
import { Avatar } from '@waukeshamakerspace/react-components';
<Avatar name="Casey Kerr" src={member.photoUrl} />
<Avatar name="Casey Kerr" shape="circle" />| Prop | Type | Default | Notes |
|------|------|---------|-------|
| name | string | required | Accessible label + initials source |
| src | string | | Falls back to initials on load error |
| size | 'sm' \| 'md' \| 'lg' \| number | 'md' | 28 / 40 / 64 px, or exact px |
| shape | 'gear' \| 'circle' | 'gear' | |
| className | string | | |
BrandSpinner
Branded loading indicator built from meshed gears in the token colors.
Announces itself as role="status" with a visually hidden label, and swaps
rotation for a gentle opacity pulse under prefers-reduced-motion.
import { BrandSpinner } from '@waukeshamakerspace/react-components';
<BrandSpinner size="lg" label="Fetching inventory" />
<BrandSpinner variant="train" />| Prop | Type | Default | Notes |
|------|------|---------|-------|
| size | 'sm' \| 'md' \| 'lg' \| number | 'md' | 24 / 48 / 96 px, or exact px |
| variant | 'gears' \| 'train' \| 'solo' | 'gears' | Two meshed gears / three-gear chain / single gear |
| label | string | 'Loading…' | |
| className | string | | |
RandomSpinner
BrandSpinner that picks a random variant each time it mounts, for variety
on repeat loads. Takes every BrandSpinner prop except variant, plus an
optional variants array to restrict the pool. The pick happens after
mount, so SSR markup stays deterministic (no hydration mismatch).
import { RandomSpinner } from '@waukeshamakerspace/react-components';
<RandomSpinner size="lg" />
<RandomSpinner variants={['gears', 'train']} />Development
npm install
npm run dev # Ladle playground on http://localhost:3801 (reserved port)
npm test # vitest + testing-library under jsdom
npm run build # tsc to dist/Use the ThemeToggle story to preview every component in both themes before shipping a change.
Releases
Same automation as design-tokens: conventional commits accumulate into a release-please PR; merging it publishes to npm via trusted publishing (OIDC, no tokens). Consumer repos must run Renovate or Dependabot so releases arrive as bump PRs.
