277 lines
13 KiB
Markdown
277 lines
13 KiB
Markdown
# @wrnexus/ui
|
||
|
||
> First-party WrNexus UI component library — a set of themeable `.wrn` components plus a single tokenized stylesheet.
|
||
|
||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||
|
||
## Overview
|
||
|
||
`@wrnexus/ui` ships a library of server-rendered `.wrn` components (layout, form
|
||
controls, and feedback UI) together with one themeable stylesheet, `ui.css`. The
|
||
components are **auto-discovered** by the framework router — you don't import them
|
||
in code. Once the package's component directory is on the router's scan path, you
|
||
mount any component in a page with `data-component="<name>"`. Every visual is
|
||
driven by `var(--wrn-*)` theme tokens, so components restyle instantly when the
|
||
theme changes. The tiny JS surface (`src/index.ts`) exists only so the toolchain
|
||
(CLI build + dev server) can locate the component directory and stylesheet.
|
||
|
||
The complete PDF-aligned catalog currently contains **85 components**. The
|
||
generated `COMPONENTS.md` and `component-reference.json` files document every
|
||
mount name, prop, inferred type, default/required status, slot, event, category,
|
||
and source file directly from the packaged `.wrn` source.
|
||
|
||
## Installation
|
||
|
||
```bash
|
||
bun add @wrnexus/ui
|
||
```
|
||
|
||
> Private package — the machine must be authenticated to the `wrnexus` npm org
|
||
> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported).
|
||
|
||
In practice you rarely install this directly: `@wrnexus/cli` and
|
||
`@wrnexus/dev-server` already depend on it and wrn it into the router for you
|
||
(see [Auto-discovery](#auto-discovery)).
|
||
|
||
## Components
|
||
|
||
Components live as `.wrn` files under `packages/ui/components/`. The canonical mount
|
||
name comes from the component declaration (for example, `component Button` mounts as
|
||
`data-component="Button"`). Component lookup is case-insensitive, so existing lowercase
|
||
mounts continue to work. Each component accepts a `class` prop (appended to its root
|
||
element), and most render their body from either a named prop or the default slot.
|
||
|
||
### Layout
|
||
|
||
| Name | Purpose | Key props |
|
||
| ----------- | ---------------------------------- | ----------- |
|
||
| `container` | Max-width centered content wrapper | `class` |
|
||
| `stack` | Vertical column with gap | `gap` (0–8) |
|
||
| `hstack` | Horizontal row with gap | `gap` (0–8) |
|
||
| `grid` | CSS grid container | see source |
|
||
| `divider` | Horizontal rule | `class` |
|
||
| `spacer` | Flexible/empty spacing element | see source |
|
||
|
||
### Core / feedback
|
||
|
||
| Name | Purpose | Key props |
|
||
| -------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||
| `button` | Button | `label`, `variant` (`default`\|`primary`\|`danger`\|`ghost`), `size` (`sm`\|`md`\|`lg`), `type` |
|
||
| `input` | Text input | see source |
|
||
| `textarea` | Multi-line input | see source |
|
||
| `checkbox` | Checkbox | see source |
|
||
| `badge` | Small status badge | `label`, `variant` |
|
||
| `alert` | Callout box | `variant` (`info`\|`success`\|`danger`\|`warning`), `title`, `message` |
|
||
| `card` | Padded, bordered surface | `class` |
|
||
| `avatar` | User avatar | see source |
|
||
| `spinner` | Loading indicator | see source |
|
||
| `disclosure` | Expandable details/summary | see source |
|
||
| `theme-toggle` | Theme switch button (binds `data-wrn-theme-toggle`) | `label` |
|
||
|
||
### Additional controls & data display
|
||
|
||
Also shipped: `select`, `radio`, `switch`, `progress`, `tag`, `skeleton`,
|
||
`tooltip`, `table`, `FAQAccordion`, `AnnouncementBar`, and `BackToTop`.
|
||
|
||
The PDF-defined minimum release and essential build-first set also includes
|
||
typed typography, form primitives, loading actions, combobox and multi-select,
|
||
time/date-time and recurring schedule controls, confirmation dialogs, data
|
||
tables, filters, desktop/mobile navigation, mega menus, marketing/product/legal
|
||
page shells, product and metric cards, FAQ composition, pricing comparison,
|
||
SDK tabs, legal navigation, and cookie preferences.
|
||
|
||
`Seo` and `StructuredData` remain framework/page concerns rather than body
|
||
components: use the native page `seo { ... }` block and document-head APIs so
|
||
metadata is emitted in `<head>` instead of invalid component markup.
|
||
|
||
The authoritative, always-current list is `uiComponentNames()` (below), which reads
|
||
the component directory at runtime.
|
||
|
||
For the full catalog, see [`COMPONENTS.md`](./COMPONENTS.md). The machine-readable
|
||
equivalent is exported as `@wrnexus/ui/component-reference.json`.
|
||
|
||
### Typed Navbar items
|
||
|
||
Keep navigation data in a shared TypeScript module and validate it with the
|
||
package contract. `NavbarItem` is a discriminated union of `link`, `dropdown`,
|
||
and `mega`; a mega item accepts every `MegaMenu` layout option.
|
||
|
||
```ts
|
||
import type { NavbarItem } from "@wrnexus/ui";
|
||
|
||
export const navigation = [
|
||
{ type: "link", label: "Pricing", href: "/pricing" },
|
||
{
|
||
type: "dropdown",
|
||
label: "Company",
|
||
items: [
|
||
{ label: "About", href: "/about" },
|
||
{ label: "Contact", href: "/contact" },
|
||
],
|
||
},
|
||
{
|
||
type: "mega",
|
||
label: "Products",
|
||
mega: {
|
||
variant: "icon-grid",
|
||
panelWidth: "2xl",
|
||
columnCount: 3,
|
||
rail: [{ label: "All products", href: "/products" }],
|
||
columns: [
|
||
{
|
||
heading: "Work",
|
||
items: [
|
||
{
|
||
label: "Projects",
|
||
href: "/products/projects",
|
||
icon: "icon-[lucide--folders]",
|
||
description: "Plan and deliver work",
|
||
badge: "New",
|
||
},
|
||
],
|
||
},
|
||
],
|
||
featured: {
|
||
title: "What's new",
|
||
actionLabel: "Explore",
|
||
actionHref: "/new",
|
||
},
|
||
actionLabel: "View all products",
|
||
actionHref: "/products",
|
||
},
|
||
},
|
||
] satisfies NavbarItem[];
|
||
```
|
||
|
||
Importing from `@wrnexus/ui/navigation` is also supported. Existing untyped
|
||
Navbar data using `children` remains accepted at runtime for migration.
|
||
|
||
## API
|
||
|
||
The JS module (`@wrnexus/ui`) exposes five helpers used by the build tooling to
|
||
locate the component assets. There is no component code to import — the components
|
||
are `.wrn` files rendered server-side.
|
||
|
||
| Export | Signature | Returns |
|
||
| ------------------ | -------------------------- | ------------------------------------------------------------------------------------------ |
|
||
| `uiComponentsDir` | `() => string` | Absolute path to the `.wrn` component directory (feed to `buildRouter`'s `componentDirs`). |
|
||
| `uiCssPath` | `() => string` | Absolute path to `ui.css`. |
|
||
| `uiCss` | `() => string` | The `ui.css` file contents (all `.wrn-*` classes, themed via tokens). |
|
||
| `uiComponentNames` | `() => string[]` | Sorted list of declared built-in component names. |
|
||
| `uiComponentPath` | `(name: string) => string` | Absolute source path for a declared component name or case-insensitive alias. |
|
||
|
||
### `./ui.css` asset export
|
||
|
||
`package.json` also exposes the raw stylesheet as a subpath asset:
|
||
|
||
```json
|
||
"exports": {
|
||
".": "./src/index.ts",
|
||
"./ui.css": "./ui.css"
|
||
}
|
||
```
|
||
|
||
The framework serves this stylesheet once at `/__wrnexus/ui.css`, so pages get all
|
||
component styles from a single request.
|
||
|
||
### Component styles and motion
|
||
|
||
Components own their BEM styles in local `style {}` blocks and do not require a
|
||
Tailwind scan. `ui.css` supplies only global tokens, resets, and shared
|
||
primitives. Applications can still use Tailwind independently in their own
|
||
source files.
|
||
|
||
The shared stylesheet gives all component boundaries consistent, GPU-friendly
|
||
entry and interaction motion. Override `--wrn-motion-fast`,
|
||
`--wrn-motion-base`, `--wrn-motion-slow`, `--wrn-ease-standard`, or
|
||
`--wrn-ease-emphasized` to tune it. Hover lift is limited to precise pointing
|
||
devices and `prefers-reduced-motion` is honored automatically.
|
||
|
||
### Using the selected theme in application UI
|
||
|
||
The active theme and palette are not limited to `@wrnexus/ui` components. The
|
||
framework exposes the resolved values as semantic CSS custom properties, so
|
||
pages and custom `.wrn` components can use the same contract:
|
||
|
||
```css
|
||
.account-card {
|
||
background: var(--wrn-color-surface);
|
||
color: var(--wrn-color-text);
|
||
border: 1px solid var(--wrn-color-border);
|
||
}
|
||
|
||
.account-card__action {
|
||
background: var(--wrn-color-primary);
|
||
color: var(--wrn-color-primary-contrast);
|
||
}
|
||
```
|
||
|
||
Stable no-spacing helper classes are also available: `wrn-bg-page`,
|
||
`wrn-bg-surface`, `wrn-bg-surface-2`, `wrn-bg-primary`, `wrn-bg-secondary`,
|
||
`wrn-text`, `wrn-text-muted`, `wrn-text-primary`, `wrn-text-success`,
|
||
`wrn-text-warning`, `wrn-text-danger`, and `wrn-border`.
|
||
|
||
Tailwind-authored custom markup can continue using the palette families already
|
||
used by packaged components. `indigo-*` and `violet-*` resolve to primary,
|
||
`blue-*` to info, `emerald-*`/`green-*` to success, `amber-*` to warning, and
|
||
`red-*`/`rose-*` to danger. These aliases live at `:root`, so they work outside
|
||
a `[data-component]` boundary too.
|
||
|
||
## Usage
|
||
|
||
### Auto-discovery
|
||
|
||
The router scans extra `componentDirs` (in addition to the app's own
|
||
`app/components`) and keys components by name. Library dirs are scanned **first**
|
||
and `app/components` **last**, so an app component of the same name shadows the
|
||
library's. The CLI build (`@wrnexus/cli`) and dev server (`@wrnexus/dev-server`)
|
||
both wrn the UI directory in for you:
|
||
|
||
```ts
|
||
import { buildRouter } from "@wrnexus/router";
|
||
import { uiComponentsDir } from "@wrnexus/ui";
|
||
|
||
const router = buildRouter(appDir, { componentDirs: [uiComponentsDir()] });
|
||
```
|
||
|
||
### Mounting components in a page
|
||
|
||
Once discovered, mount any component by name via `data-component`. Quoted
|
||
attributes (other than `data-component`) become string props:
|
||
|
||
```html
|
||
<div data-component="card">
|
||
<div data-component="badge" label="New"></div>
|
||
<button data-component="button" label="Save" variant="primary" size="lg"></button>
|
||
<div data-component="alert" variant="success" title="Done" message="Saved."></div>
|
||
</div>
|
||
```
|
||
|
||
## Overrides
|
||
|
||
Ways to customize the components, in increasing order of power:
|
||
|
||
1. **Theme tokens** — override CSS custom properties such as `--wrn-color-primary`,
|
||
`--wrn-color-surface`, `--wrn-radius-sm`, etc. Every component style resolves
|
||
through `var(--wrn-*)`, so changing a token restyles everything instantly
|
||
(including across theme switches).
|
||
2. **App CSS** — redefine a `.wrn-*` class in your own stylesheet, which is loaded
|
||
after `ui.css` and therefore wins.
|
||
3. **`class` prop** — pass a `class` prop to a component; it is appended to the
|
||
component's root element, letting you add per-instance classes without touching
|
||
the base styles.
|
||
4. **`wrnexus eject <name>`** — copy the component's `.wrn` source into your
|
||
`app/components`, where (because app components shadow library ones) you fully
|
||
own and can edit it. Use `uiComponentNames()` for the list of ejectable names.
|
||
|
||
## Requirements / Notes
|
||
|
||
- **Bun-only** — the package uses standard fs/path/url APIs but is published and
|
||
consumed within the Bun-native WrNexus toolchain (Node is not supported).
|
||
- Peer packages: components are discovered and rendered by
|
||
[`@wrnexus/router`](../router) (via `componentDirs`) and served by
|
||
[`@wrnexus/dev-server`](../dev-server) / built by [`@wrnexus/cli`](../cli).
|
||
- Depends on [`@wrnexus/core`](../core) (`dependencies`).
|
||
- `theme-toggle` relies on the framework's theme runtime, which binds the
|
||
`data-wrn-theme-toggle` attribute — no per-component JS is required.
|