4.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
modern-sk — a tactile, dark-first React component library built on Radix primitives. Distributed as a git-installable / publishable package; only dist/ ships. Consumers get built ESM + CJS, .d.ts types, and a single styles.css.
Commands
npm run dev # Rsbuild playground at http://localhost:3000 (renders src/App.tsx — every component on one page)
npm run storybook # Storybook component explorer + autodocs at http://localhost:6006
npm run build # build publishable package: tsup (JS/types) + build:css into dist/
npm run lint # rslint (rslint.config.ts) — covers src/ including stories
npm run format # prettier --write .
No test suite exists. npm run build runs automatically on install via the prepare script — consumers build the package themselves.
The build is two steps that must both run (the build script chains them):
tsupbundlessrc/index.ts→ ESM/CJS + types.react/react-domare externalized (peer deps);radix-ui+@phosphor-icons/reactare bundled.build:cssruns esbuild twice:src/styles/index.css→dist/styles.css(fontless core), andsrc/styles/fonts.css→dist/fonts.csswith--loader:.ttf=dataurl(inlines the self-hosted Anta font). Both are package exports.
Architecture
Two parallel surfaces share the same source but never mix at publish time:
- Library (shipped):
src/index.tsis the public entry. It re-exports everything fromsrc/components/ui.tsx, plusThemeProvider/useThemefromsrc/components/theme.tsx, and exposesTooltipProvider(Radix'sTooltip.Provider). The shipped stylesheet entry issrc/styles/index.css. - Playground (dev-only, never published): two of them —
src/index.tsxmounts thesrc/App.tsxkitchen sink (Rsbuild dev target,src/styles/global.css), and Storybook (.storybook/config +src/stories/*.stories.tsx) is the component catalogue with autodocs.
Only dist/ ships (files: ["dist"]), so the playground, stories, and .storybook/ are excluded from the package automatically — but they ARE committed to git so anyone can run them.
src/styles/index.css (shipped) vs src/styles/global.css (dev) is a deliberate split: index.css imports only tokens.css + components.css and applies box-sizing at zero specificity via :where([class^='modern-sk-']) so it never touches consumer elements. global.css adds a global reset, the optional fonts.css, and kitchen-sink layout helpers — those must stay out of the shipped bundle.
Fonts (src/styles/fonts.css)
Fonts are NOT in the core stylesheet. tokens.css defines --font-display/sans/mono chains that degrade to system-ui; the actual faces (Anta @font-face + Onest/Geist Mono Google Fonts @import) live in src/styles/fonts.css, shipped as the optional modern-sk/fonts.css export. Consumers either import it, or override the --font-* tokens to remap typefaces. Storybook's preview.tsx and the dev global.css both import fonts.css so the playgrounds stay branded.
Components (src/components/ui.tsx)
All components live in one file. Pattern: Radix provides logic/accessibility, every visual comes from CSS. Components are thin wrappers that attach modern-sk-* classes and spread props. The cx() helper joins class names (no classnames dependency). Components forward refs where they wrap a DOM element. There is no inline styling and no CSS-in-JS — all appearance is driven by modern-sk-* classes resolving against CSS custom properties.
Styling system (src/styles/)
tokens.css— single source of truth: color/type CSS custom properties (no font loading — see Fonts above). Every component reads from here.components.css— themodern-sk-*class definitions.- Dark/light is driven by
data-themeon<html>, set byThemeProvider(persisted tolocalStorageunder keymodern-sk-theme, defaultdark).
When adding or changing a component: add the wrapper in ui.tsx, define its modern-sk-* styles in components.css, and pull any new color/spacing value from a token in tokens.css rather than hardcoding.
Consumer contract
Consumers import modern-sk/styles.css once at app root, wrap their tree in ThemeProvider, and wrap any tooltip-using subtree in TooltipProvider. Keep these provider requirements intact when refactoring exports.
Conventions
- React 19, React Compiler is enabled (
babel-plugin-react-compilerin the Rsbuild dev pipeline). - TypeScript is
noEmit+verbatimModuleSyntax: useimport typefor type-only imports.noUnusedLocals/noUnusedParametersare on. - ESM-only package (
"type": "module").