# @enya-learning/nova

Enya Learning's component library for building modern web applications.

Nova provides accessible, themeable UI components built on
[Base UI](https://base-ui.com) primitives and styled with Tailwind CSS v4. It
handles keyboard navigation, focus management, and ARIA attributes so you can
build accessible applications without thinking through every detail.

## Installation

```bash
pnpm add @enya-learning/nova
```

## Peer Dependencies

```bash
pnpm add react react-dom
```

## Import Components

```tsx
// Main package import
import { Button, Input, Dialog } from "@enya-learning/nova"

// Granular imports (recommended for tree-shaking)
import { Button } from "@enya-learning/nova/components/button"
```

## Import Styles

### For Tailwind CSS v4 Users

Tailwind CSS v4 does not scan `node_modules/` by default. Add a `@source`
directive so Tailwind discovers the utility classes Nova uses — without this,
components may render with missing styles.

In your main CSS file:

```css
@source "../node_modules/@enya-learning/nova/dist/**/*.{js,jsx,ts,tsx}";
@import "@enya-learning/nova/styles";
@import "tailwindcss";
```

Import order matters — `@enya-learning/nova/styles` must come before
`@import "tailwindcss"` so Nova's `@theme` tokens are registered first.

> **Note:** The `@source` path is relative to your CSS file. Adjust it based on
> your project structure.

### Global CSS (optional)

Nova ships a `globals.css` for base resets and font loading:

```css
@import "@enya-learning/nova/globals.css";
```

## Styles architecture

Nova's CSS is split across four files with distinct responsibilities:

| File               | Role                                                                                                                                                                                                                        |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `nova-binding.css` | Registers the raw palette into Tailwind's `@theme` — every `nova-*` colour stop and utilities like `--ease-bounce`. This is what makes `bg-nova-sky-500` work as a class.                                                   |
| `theme-nova.css`   | Semantic tokens — maps palette stops to _meaning_ (`--color-nova-brand`, `--color-nova-success`, `--text-color-nova-subtle`, etc.) with `light-dark()` for automatic light/dark switching.                                  |
| `nova.css`         | The bridge to shadcn — remaps shadcn's generic variables (`--primary`, `--background`, `--destructive`, etc.) onto nova semantic tokens so shadcn components pick up nova colours without modification.                     |
| `globals.css`      | App entry point — imports Tailwind, fonts, shadcn base, and `nova.css`. Also sets `@theme inline` to wire shadcn colour vars into Tailwind's class system, defines the `dark` variant, and applies base body/border styles. |

**Import chain:**

```
globals.css → nova.css → nova-binding.css → theme-nova.css
```

**Where to make changes:**

- Adding a new colour stop → `nova-binding.css`
- Adding or changing what a colour _means_ (e.g. a new `nova-brand-subtle`
  surface) → `theme-nova.css`
- Swapping which palette scale powers a semantic role (e.g. changing the brand
  variant) → `nova-binding.css` brand alias block
- Mapping a new shadcn variable to nova → `nova.css`

---

## Base UI Primitives

Nova is built on [Base UI](https://base-ui.com). For advanced use cases
requiring fine-grained control, reach into your own `@base-ui/react` dependency
directly:

```tsx
import { Popover } from "@base-ui/react"
```

Prefer styled Nova components when available — use Base UI primitives only for
custom components not yet in Nova.

---

## Table of contents

- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [Development workflows](#development-workflows)
- [Adding a new component](#adding-a-new-component)
- [Writing stories](#writing-stories)
- [Generating docs](#generating-docs)
- [Testing and quality](#testing-and-quality)
- [Publishing](#publishing)

---

## Overview

```
nova/
├── apps/
│   └── web/           Astro documentation site (localhost:4321)
└── packages/
    └── nova/          This package — the component library
        ├── src/
        │   ├── components/   One folder per component
        │   ├── styles/       Design tokens and global CSS
        │   ├── lib/          Utilities (cn, etc.)
        │   └── index.ts      Library barrel export
        └── .storybook/       Storybook configuration
```

**Three moving parts:**

| Tool                         | Purpose                                           | Port |
| ---------------------------- | ------------------------------------------------- | ---- |
| Storybook                    | Isolated component development & interactive docs | 6006 |
| `pnpm dev`                   | Vite watch build — feeds the Astro docs site      | —    |
| Astro docs site (`apps/web`) | Static documentation website                      | 4321 |

Storybook is the primary development environment. The Astro docs site is for
publishing static reference documentation.

---

## Prerequisites

- **Node** ≥ 22.12
- **pnpm** 10

```bash
# From the monorepo root — installs all workspaces
pnpm install
```

---

## Development workflows

### Option A — Storybook (recommended for component work)

The fastest way to build and test components in isolation.

```bash
# From packages/nova
pnpm storybook
# → http://localhost:6006
```

What you get:

- Full HMR with React Fast Refresh
- Interactive controls for every prop
- Auto-generated prop tables from TypeScript types (`autodocs` tag)
- Light/dark mode toggle (sets `data-mode="dark"` on the root element)
- Accessibility panel

### Option B — Watch build + Astro docs site

Use this when you need to see your changes reflected in the actual documentation
website.

```bash
# Terminal 1 — rebuild nova on every save (~400ms)
cd packages/nova
pnpm dev

# Terminal 2 — Astro dev server
cd apps/web
pnpm dev
# → http://localhost:4321
```

Refresh the browser after a nova rebuild to see changes.

---

## Adding a new component

Use the generator to scaffold the files and wire up the exports in one step:

```bash
pnpm --filter @enya-learning/nova new-component
```

The generator creates the component file, barrel index, and story, then
automatically adds the barrel export to `src/index.ts` and the build entry to
`vite.config.ts`. The `package.json` exports use a wildcard (`"./components/*"`)
— no manual edit needed.

After running the generator, complete these final steps manually:

### 1. Implement the component

Fill in `src/components/{name}/{name}.tsx` — the generator creates a minimal
`<div>` shell.

### 2. Write a story

The generator creates a stub story with a single `Default` export. Add variant
and state stories. See [Writing stories](#writing-stories) below.

### 3. Generate the docs page

```bash
pnpm generate-docs
```

This creates `apps/web/src/content/components/{name}.mdx` from your story file.
Commit it.

---

### Reference: what the generator does

For manual setups or debugging, here is what `pnpm new-component` automates:

**Creates:**

```
src/components/{name}/
├── {name}.tsx          component shell
├── {name}.stories.tsx  Storybook story stub
└── index.ts            barrel re-export (export * from "./{name}")
```

**Modifies `src/index.ts`** — appends before the utils export:

```ts
export * from "./components/{name}"
```

**Modifies `vite.config.ts`** — inserts before `// ADD_COMPONENT_ENTRY_HERE`:

```ts
"components/{name}": resolve(__dirname, "src/components/{name}/index.ts"),
```

---

## Writing stories

Stories live alongside their component:

```
src/components/button/
├── button.tsx
├── button.stories.tsx   ← story file
└── index.ts
```

### Canonical pattern

```tsx
import type { Meta, StoryObj } from "@storybook/react"
import { Button } from "./button"

// Meta: describes the component and sets defaults shared across all stories
const meta: Meta<typeof Button> = {
  title: "Components/Button",
  component: Button,
  tags: ["autodocs"], // enables the auto-generated Docs tab
  args: { children: "Click me" }, // default prop values for all stories
}

export default meta
type Story = StoryObj<typeof meta>

// Each named export is one story (one state of the component)
export const Default: Story = {}

export const Outline: Story = {
  args: { variant: "outline" }, // overrides meta.args
}

export const Disabled: Story = {
  args: { disabled: true },
}
```

**Rules of thumb:**

- Set shared defaults in `meta.args` (e.g. `children`, `placeholder`). Per-story
  `args` merge on top.
- Add `tags: ["autodocs"]` to every meta object — this activates the
  auto-generated props table.
- Give every variant and state its own named export so it appears in the
  sidebar.
- For components that render void HTML elements (e.g. `Input`, `Checkbox`), do
  not set `children` in `args` — the generator produces self-closing JSX.
- Use a `render:` function for multi-component showcase stories (AllVariants,
  AllSizes). The doc generator skips these.

### Dark mode

The Storybook toolbar has a light/dark toggle. It sets `data-mode="dark"` on the
HTML root, activating Nova's `dark:` Tailwind variant. All components respond
automatically — no extra work needed in stories.

---

## Generating docs

The generate-docs script reads every `*.stories.tsx` file, extracts prop types
and story examples, and writes a `.mdx` file to the Astro docs site.

```bash
# From packages/nova
pnpm generate-docs
```

Output: `apps/web/src/content/components/{name}.mdx`

Re-run this whenever you:

- Add a new component story
- Add or rename stories
- Update `meta.args`

The generated MDX files are committed to the repo so the Astro site can build
without running the script in CI.

---

## Testing and quality

```bash
# Unit tests (vitest)
pnpm test           # single run
pnpm test:watch     # watch mode

# TypeScript
pnpm typecheck

# Lint
pnpm lint

# Format
pnpm format
```

Run all checks from the monorepo root:

```bash
pnpm --filter @enya-learning/nova test
pnpm typecheck   # runs across all workspaces via turbo
pnpm lint
```

---

## Publishing

Nova is published to the GitHub npm registry (`npm.pkg.github.com`). Publishing
requires a GitHub Personal Access Token with `packages:write` scope in your
`~/.npmrc`:

```
//npm.pkg.github.com/:_authToken=YOUR_TOKEN
```

### Release flow

**1. Create a changeset** — describe what changed and choose a semver bump:

```bash
# From the monorepo root
pnpm changeset
```

Select `@enya-learning/nova`, choose `patch / minor / major`, write a one-line
description. This creates a `.changeset/*.md` file — commit it with your feature
branch.

**Semver guide:** | Change type | Bump | |-------------|------| | Bug fix, style
tweak | `patch` | | New component, new prop, new variant | `minor` | | Removed
export, renamed prop, breaking API change | `major` |

**2. Bump the version** — consumes all pending changesets and updates
`CHANGELOG.md`:

```bash
pnpm version
```

**3. Build and publish**:

```bash
pnpm release
# → builds packages/nova, then runs changeset publish
```

**4. Push the version commit and tags**:

```bash
git push && git push --tags
```
