Skip to content

Repository files navigation

bnm-ui

A React component library and the sites built on top of it, kept in one workspace. The library is styled-components on top of a shared token set, written in TypeScript, and consumed by apps through Vite path aliases rather than a build step. Nothing is published to npm; the packages are private and imported straight from source.

Layout

packages/
  tokens/       colour, space, radius and type scales
  theme/        styled-components ThemeProvider and global styles
  primitives/   Box, Flex, Grid, Stack, Container, Button, Input, Heading, Text
  sections/     composed page sections (empty so far)
  utils/        small helpers

apps/
  playground/         visual harness for the primitives
  bnm-software/       brutalist marketing site for BNM Software
  siblings-reunited/  scaffolded, not yet using the library

types/
  styled-components/  augments DefaultTheme with the shape of the real theme

Running it

pnpm install
pnpm dev

pnpm dev runs the playground. To work on a specific app, run Vite from inside it:

cd apps/bnm-software
pnpm dev

Other root scripts: typecheck runs tsc across every package and app with noEmit, lint runs ESLint over the whole tree, and build builds each workspace in turn.

Every app resolves @bnm/* through aliases in its own vite.config.ts, pointing at packages/*/src. TypeScript resolves the same names through paths in tsconfig.base.json. Adding a new package means updating both.

Tokens

@bnm/tokens exports four objects. color holds a dark base palette under color.bg plus a separate color.colors group used by form controls. space is a numeric scale from 1 to 16 in rem. radius covers sm through xl. font carries the family stacks, weights, line heights and a fluid size scale from 1 to 7, each one a clamp() that grows with the viewport, so headings resize without media queries.

There's also packages/tokens/styles/tokens.css, an equivalent set of CSS custom properties for anything reaching for var(--space-4) instead of the theme object. The two aren't generated from a single source, so a change in one won't show up in the other.

Theme

@bnm/theme bundles the token objects into a single theme, wraps styled-components' provider, and injects global styles that set the body background, text colour and base font.

import { ThemeProvider } from '@bnm/theme/ThemeProvider';

createRoot(document.getElementById('root')!).render(
  <ThemeProvider>
    <App />
  </ThemeProvider>
);

The theme type flows through to every styled component via types/styled-components/index.d.ts, which declares DefaultTheme as typeof theme. That's what makes theme.space[4] and theme.color.bg.accent autocomplete inside template literals.

To restyle an app without touching the library, spread the base theme and override what you need, then wrap that section of the tree in styled-components' own provider. The BNM Software site does exactly this to swap the dark default palette for a light brutalist one.

const brutalTheme: DefaultTheme = {
  ...baseTheme,
  color: {
    ...baseTheme.color,
    bg: { ...baseTheme.color.bg, default: '#f5f2e9', text: '#0b0b0b' },
  },
};

Primitives

Everything builds on Box, a polymorphic styled div that takes an as prop to change the rendered element, shorthand p, m, gap and display props, and two escape hatches: styleOverrides for inline styles and sx for a styled-components block or a function of the theme.

<Box as="article" p="1rem" sx={(theme) => `border-radius: ${theme.radius.md};`}>
  ...
</Box>

Flex exposes direction, align, justify, wrap, inline, grow, shrink and basis, mapping them internally onto transient $-prefixed props so they never leak into the DOM as attributes. VStack and HStack are thin wrappers that preset the direction.

Grid covers the grid template properties with the same clean-prop-to-transient-prop pattern. GridItem takes col, row and area strings and merges them into the element's inline style.

Container centres content with a default max width of min(1100px, 92vw) and symmetric horizontal padding, both overridable. SectionRoot renders a <section> with vertical padding and a tone of default, surface or accent, pulling the background from the theme.

Heading and Text are styled boxes driven by transient props: $level picks a font size from the type scale, $size, $muted, $align and $weight do the same for body copy.

Button takes variant (accent, surface, ghost), size (sm, md, lg) and fullWidth. It forwards refs, defaults type to button when it renders as one, and switches to aria-disabled when rendered as a link. Focus rings use the accent colour with an offset. Disabled state drops opacity and cancels the hover filter.

Input wraps a label, field and error message in a column. Pass label, error and fullWidth; the id is generated with useId when you don't supply one, and the error state recolours the border and focus ring.

<SectionRoot tone="surface" paddingY="4rem">
  <Container>
    <VStack gap="1rem">
      <Heading as="h1" $level={5}>Section title</Heading>
      <Text $size={2} $muted>Supporting copy.</Text>
      <Button variant="accent" size="md" onClick={handleClick}>Get in touch</Button>
      <Input label="Email address" type="email" fullWidth />
    </VStack>
  </Container>
</SectionRoot>

Apps

playground renders every primitive across each section tone and every button and input state on one page. It's the fastest way to see a change to the library.

bnm-software is the marketing site, built entirely from the primitives with a locally overridden brutalist theme.

siblings-reunited is still the stock Vite React template. It has no aliases wired up and doesn't import from the library yet.

Rough edges

  • The workspace is set up twice over. Root package.json declares npm workspaces and its scripts call npm run -w, but there's also a pnpm-workspace.yaml, a packageManager field pinning pnpm, and both lockfiles committed. bnm-software declares its @bnm/* dependencies with the workspace:* protocol, which npm won't install. Pick one package manager and delete the other's config and lockfile.
  • Because apps alias @bnm/* straight to source, the workspace:* dependencies aren't really doing any work at the moment. playground doesn't declare styled-components or any @bnm package at all and relies on hoisting from the root.
  • packages/tokens/src has empty colors.ts, spacing.ts, typography.ts, radius.ts, motion.ts, breakpoints.ts and zindex.ts files. Every token currently lives in index.ts.
  • packages/sections/src/index.ts and packages/utils/src/index.ts are both placeholder files. utils has a clamp helper that the index doesn't export.
  • tokens.css and the token objects define different palettes and different type scales. Anything using var(--color-accent) won't match a component reading theme.color.bg.accent.
  • Input reads from theme.color.colors, a light-mode group that sits apart from the theme.color.bg palette everything else uses. On the dark default theme the inputs come out white.
  • Heading, Text and Button mix conventions. The first two take transient $-prefixed props as their public API, while Button takes clean names and converts internally.
  • Only packages/primitives has its own tsconfig.json with outDir and rootDir set. tsup is installed at the root but nothing is wired up to build the packages for distribution.
  • There's no root .gitignore and no licence file.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages