Skip to content

Commit fce3015

Browse files
authored
Merge pull request #211 from corbitsdev/danny/css-refactor
refactor(web): move styles into per-component CSS modules
2 parents 562d8f7 + 78e1ff8 commit fce3015

152 files changed

Lines changed: 8448 additions & 7201 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Dependencies and build output
22
node_modules/
33
dist/
4+
storybook-static/
45
coverage/
56
*.tsbuildinfo
67
.cache/
@@ -31,3 +32,6 @@ test-results/
3132
.DS_Store
3233
.claude/
3334
.amp/
35+
36+
# Generated from each CSS module by `bun run --cwd apps/web css-types`
37+
*.module.css.d.ts

‎apps/web/.storybook/main.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { defineMain } from "@storybook/react-vite/node";
2+
3+
// Storybook reads apps/web/vite.config.ts, so stories build with the same
4+
// plugins the interface does.
5+
export default defineMain({
6+
framework: "@storybook/react-vite",
7+
stories: ["../src/**/*.stories.tsx"],
8+
});

‎apps/web/.storybook/preview.tsx‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { useEffect, type ReactNode } from "react";
2+
import { ThemeProvider, useTheme } from "@corbits/react-ui";
3+
import type { Preview } from "@storybook/react-vite";
4+
import { Toaster } from "sonner";
5+
// The same order main.tsx imports them in: the library's sheet, then ours.
6+
import "@corbits/react-ui/styles.css";
7+
import "../src/styles.css";
8+
9+
type Mode = "light" | "dark";
10+
11+
// Follows the toolbar instead of the stored preference, so a story renders
12+
// in the mode the reviewer picked.
13+
function Mode({ mode, children }: { mode: Mode; children: ReactNode }) {
14+
const { setMode, resolvedMode } = useTheme();
15+
useEffect(() => setMode(mode), [mode, setMode]);
16+
return (
17+
<>
18+
{children}
19+
<Toaster theme={resolvedMode} richColors closeButton />
20+
</>
21+
);
22+
}
23+
24+
const preview: Preview = {
25+
globalTypes: {
26+
mode: {
27+
description: "Light or dark theme",
28+
toolbar: {
29+
title: "Theme",
30+
icon: "mirror",
31+
items: [
32+
{ value: "light", title: "Light" },
33+
{ value: "dark", title: "Dark" },
34+
],
35+
dynamicTitle: true,
36+
},
37+
},
38+
},
39+
initialGlobals: { mode: "light" },
40+
decorators: [
41+
(Story, context) => (
42+
<ThemeProvider storageKey="builder-storybook-theme" defaultMode="light">
43+
<Mode mode={context.globals["mode"] === "dark" ? "dark" : "light"}>
44+
<Story />
45+
</Mode>
46+
</ThemeProvider>
47+
),
48+
],
49+
parameters: {
50+
layout: "padded",
51+
controls: { expanded: true },
52+
},
53+
};
54+
55+
export default preview;

‎apps/web/AGENTS.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,31 @@ like what is already there, and extends it rather than standing beside it.
1818
maintainers decide whether it moves.
1919
- Wording lives in the interface's config.
2020

21+
## Styles
22+
23+
- A component's styles live in a CSS module beside it (`pane-bar.tsx`,
24+
`pane-bar.module.css`), imported as `styles`, with camelCase class names.
25+
`styles.css` holds only what is global: fonts, tokens, element defaults,
26+
overrides of the library that apply everywhere, and shared keyframes.
27+
- Each module has exact types, generated by `bun run css-types` (the `dev`
28+
script keeps them current and `typecheck` regenerates them), so
29+
`styles.pane` is a `string` and a misspelt class is a type error. Join
30+
classes with `clsx` only when one is conditional.
31+
- A look used in more than one place is a component, such as `InlineNote`,
32+
`Field`, `Fault` or `Banner`, not a class two components share. Shared
33+
components live in `components.tsx`, or in a file of their own when they
34+
belong to one area.
35+
- A shared component owns its whole look and takes no `className`. Variants
36+
are props, rendered as `data-*` attributes (`data-tone="error"`, or an
37+
empty `data-pill=""` for a flag); the element it renders is an `as` prop.
38+
- Every shared component's root carries a `data-slot` naming it. A parent
39+
positions it from its own module through that attribute, such as
40+
`.notes > [data-slot="inline-note"]` for margin, size or placement, never
41+
through another module's class, and never by restyling its inside.
42+
- The library's own classes, such as `.dark`, go in `:global(...)`, and a
43+
global keyframe in `global(...)`: `animation: global(fade-in) 200ms`.
44+
- Code and tests find elements by `data-*` attributes, never by a class name.
45+
2146
## Every artifact kind is a first-class document
2247

2348
A new kind (a sheet, a deck, a diagram) is integrated everywhere a document

‎apps/web/package.json‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@
77
"author": "Sawyer Cutler <sawyer@dirtroad.dev>",
88
"type": "module",
99
"scripts": {
10-
"dev": "vite",
11-
"build": "vite build"
10+
"dev": "bun run --parallel css-types:watch serve",
11+
"serve": "vite",
12+
"build": "vite build",
13+
"css-types": "tcm -s -p \"src/**/*.module.css\"",
14+
"css-types:watch": "tcm -s -w -p \"src/**/*.module.css\"",
15+
"storybook": "storybook dev -p 6006",
16+
"build-storybook": "storybook build -o storybook-static"
1217
},
1318
"dependencies": {
1419
"@corbits-builder/client": "workspace:*",
@@ -23,6 +28,7 @@
2328
"@tanstack/react-query": "5.104.1",
2429
"@tanstack/react-query-persist-client": "5.104.1",
2530
"arktype": "catalog:",
31+
"clsx": "2.1.1",
2632
"diff": "9.0.0",
2733
"idb-keyval": "6.2.2",
2834
"jszip": "3.10.2",
@@ -32,9 +38,12 @@
3238
"sonner": "2.0.8"
3339
},
3440
"devDependencies": {
41+
"@storybook/react-vite": "11.0.0-beta.0",
3542
"@types/react": "19.3.0",
3643
"@types/react-dom": "19.3.0",
3744
"@vitejs/plugin-react": "5.2.0",
45+
"storybook": "11.0.0-beta.0",
46+
"typed-css-modules": "0.9.1",
3847
"typescript": "5.9.3",
3948
"vite": "6.4.5"
4049
}

0 commit comments

Comments
 (0)