A design system containing themes, colours, form fields, etc. for use with across multiple apps.
  • TypeScript 91.2%
  • JavaScript 8.8%
Find a file
Chris Harrington 8041c446a8
All checks were successful
ci / check (push) Successful in 12s
Enforce single quotes in JSX attributes, not just JS/TS strings (#9)
Drops the double-quote-JSX-attributes exception (jsx-quotes: prefer-double
-> prefer-single) so the whole repo reads as one consistent quoting
convention instead of the mixed Prettier-style pairing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Reviewed-on: #9
2026-09-03 13:57:30 -06:00
.forgejo/workflows Add commit-level Plane ticket linking to CI (#5) 2026-09-03 07:17:59 -06:00
.storybook [DS-2] Install and integrate React Hook Form (#7) 2026-09-02 21:21:06 -06:00
src Enforce single quotes in JSX attributes, not just JS/TS strings (#9) 2026-09-03 13:57:30 -06:00
stories [DS-2] Install and integrate React Hook Form (#7) 2026-09-02 21:21:06 -06:00
.env.example [DS-5] Set up Storybook for the design system (#3) 2026-09-02 07:31:38 -06:00
.gitignore [DS-1] Build core infrastructure: React/TS/Tailwind tooling (#1) 2026-09-01 18:42:55 -06:00
bun.lock [DS-2] Install and integrate React Hook Form (#7) 2026-09-02 21:21:06 -06:00
bunfig.toml [DS-2] Install and integrate React Hook Form (#7) 2026-09-02 21:21:06 -06:00
CLAUDE.md Enforce single quotes in JSX attributes, not just JS/TS strings (#9) 2026-09-03 13:57:30 -06:00
docker-compose.yml [DS-5] Set up Storybook for the design system (#3) 2026-09-02 07:31:38 -06:00
Dockerfile.dev [DS-36] Set up docker-compose dev containers (tests/typecheck + Storybook) (#2) 2026-09-01 20:12:06 -06:00
eslint.config.cjs Enforce single quotes in JSX attributes, not just JS/TS strings (#9) 2026-09-03 13:57:30 -06:00
package.json [DS-2] Install and integrate React Hook Form (#7) 2026-09-02 21:21:06 -06:00
README.md [DS-2] Install and integrate React Hook Form (#7) 2026-09-02 21:21:06 -06:00
tsconfig.json [DS-5] Set up Storybook for the design system (#3) 2026-09-02 07:31:38 -06:00
tsup.config.ts [DS-2] Install and integrate React Hook Form (#7) 2026-09-02 21:21:06 -06:00

@chrisharrington/design-system

Shared design-system package (colors/tokens, form field components) for the budget-tracker/app and irrigo/app React Native / Expo apps. Not published to a registry — consumers pull it in via bun's link: protocol against a local checkout of this repo.

Any infrastructure added here (build tooling, packaging, module resolution, native modules, etc.) must stay Expo-compatible. See CLAUDE.md for the durable constraint.

Status

Core tooling, plus default design tokens — build pipeline, TypeScript config, and a Tailwind preset (designSystemPreset) exposing the default theme's semantic colors, typography (sizes, weights, and font family), and radii, all defined in src/tokens.ts. React Hook Form is wired up (src/form.ts), with Text (src/Text/) as the first field component — see "Component file structure" in CLAUDE.md for the folder convention future field components follow.

Consuming this package

This package is not published — consumers link to a local checkout instead of installing from a registry.

  1. In the consumer app's package.json, add a link: dependency pointing at a sibling checkout of this repo. Both budget-tracker/app and irrigo/app sit one level deeper than their repo root, so the path is ../../design-system, not ../design-system:

    {
      "dependencies": {
        "@chrisharrington/design-system": "link:../../design-system"
      }
    }
    

    Then run bun install in the consumer app. This package's dist/ is gitignored, so a bun install in the consumer runs this package's prepare script (tsup) via the link, building dist/ on first install.

  2. Package-exports resolution (the exports map in this package's package.json) is enabled by default in current Expo/Metro (SDK 53+), so no metro.config.js change should be needed for a consumer on those SDKs. Metro requests a react-native condition during resolution, which this package's exports["."] map lists first — so import { Text } from "@chrisharrington/design-system" resolves to dist/native/ under Metro and to dist/ (the web build) under any other bundler (Vite, webpack, Node, Storybook, Jest/bun test), with no separate import path for consumers to choose between. As a fallback for a consumer whose Metro config predates package-exports support (or has it disabled), the package also sets a root-level "react-native" field pointing at dist/native/index.js — older/non-exports-aware resolvers check that field before falling back to "main" (the web build), so a misconfigured Metro setup still gets the native bundle instead of silently shipping DOM markup into a native app.

This flow is documented but not yet exercised against a real consumer app — that verification is tracked separately.

Storybook

Storybook renders through @storybook/react-vite + react-native-web (not @storybook/react-native — there's no RN/Expo scaffold, simulator, or device in this repo's headless dev container). .storybook/main.ts aliases react-native imports to react-native-web at bundle time, so stories can import RN primitives (View, Text, etc.) the same way a real component would.

Running it:

  • docker compose up -d storybook, then open http://localhost:${STORYBOOK_PORT} (see .env for the port on this checkout). This is the only route reachable from the host — the dev service doesn't publish any ports.
  • Or, from a shell already inside the dev container (e.g. VS Code's Dev Containers extension): bun run storybook serves on port 6006 inside that container, but isn't reachable from the host since dev publishes no ports — use this only when you're also browsing from inside the container.

Adding a story for a new component: co-locate ComponentName.stories.tsx next to the component under src/ — the glob in .storybook/main.ts already picks up src/**/*.stories.@(ts|tsx). Use stories/ExampleCard.stories.tsx's Meta/StoryObj shape (title, component, args) as the copy-paste template for the story file — that part generalizes. stories/ExampleCard.tsx itself is Storybook-only scaffolding, not a component to build on: it styles via useTheme() + inline styles for demo purposes only, whereas a real design-system component styles via NativeWind classNames from designSystemPreset (compile-time) and must never import useTheme() / .storybook/theme-context.

Theme switcher: the toolbar's "Theme" dropdown (in the Storybook UI) switches between the themes registered in .storybook/themes.ts, each a full ThemeTokens object (see src/tokens.ts) applied via a .storybook/theme-context.tsx ThemeProvider decorator. To add a new theme, add an entry to the themes record in .storybook/themes.ts — no other wiring is needed. This context is Storybook-only dev tooling; it isn't exported from src/index.ts, since production theming for consumers stays compile-time-only via the Tailwind preset.