Compare commits

..
Author SHA1 Message Date
ClintchizandClaude Opus 5 52660cbb8e feat(ui): build Scrollspy, fix aria-current across the navigation group
Scrollspy replaces a scaffold that rendered bare anchors. The runtime observes
the sections the links point at and writes the marker straight onto the links:
an IntersectionObserver callback fires long after the client function that
registered it returned, so a state write there would be dropped.

Navbar and Breadcrumb both emitted aria-current="" for every inactive link.
That is not a valid value -- the attribute takes a token or must be absent --
so every link claimed a state it did not have. Breadcrumb had it too, despite
being the strongest component in the group.

Navbar also takes roving arrow-key focus across its menu bar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 19:18:25 +05:30
ClintchizandClaude Opus 5 ca2f9451ab merge: navigation components phase 1 and 2
Quality / quality (ubuntu-latest) (push) Failing after 12m45s
Quality / quality (windows-latest) (push) Canceled after 0s
Roving arrow-key focus in the runtime, then Nav, Pagination, Stepper,
MegaMenu, a Sidebar rebuilt on Drawer, and a Tabs rewritten off Tailwind onto
wire classes with real outputs and url-backed selection.

Also fixes the modal focus trap shipped in 0.8.5, which gated on
getBoundingClientRect and so never ran under test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:54:26 +05:30
ClintchizandClaude Opus 5 b3116de354 fix(ui): derive tab selection from the url instead of syncing to it
My diagnosis in the previous commit was wrong. The component root was not
being replaced by a reactive re-render: the client router owns popstate and
swaps the whole page shell on back and forward, which discards component
state entirely. Every mechanism that tried to push state into the component
from outside was therefore doomed -- clicking a tab, announcing an event,
tracking the last applied value.

In url mode the query parameter is now simply the source of truth, read where
the selection is computed. Whatever render happens next produces the right
tab, with no listener to lose and nothing to keep in step.

This deletes the runtime tab sync entirely -- 1590 bytes -- and fixes the
back/forward cases that were previously broken. Verified in the showcase:
click writes the url, two backs and two forwards each land on the right tab,
and a ?tab= deep link opens on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:54:13 +05:30
ClintchizandClaude Opus 5 f6993e6cdb fix(csr): drive tab url restore by announcement, anchor nav submenus
Two mechanisms tried and rejected while testing this against the live
showcase, both failing the same way on a second history step:

  - synthesising a click on the matching tab: a re-render replaces the tab
    buttons, and clicking a freshly replaced node that has not been bound
    does nothing at all
  - tracking the last applied value in the runtime: that state drifts out of
    step with the component and silently swallows real changes

The runtime is now stateless. It announces the value the URL names via a
wrnexus:tabs:restore event and the component applies it, comparing against
its own selection rather than a DOM attribute a re-render owns.

Nav submenus are anchored so the viewport clamp keeps them on screen.

Comments in the runtime template trimmed to stay inside the size budget
rather than raising the ceiling again.

Known limitation: a second consecutive back/forward does not update the
selection, because the re-render replaces the component root without
rebinding its declarative listeners. That is a framework defect, not a Tabs
one, and needs its own fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:47:41 +05:30
ClintchizandClaude Opus 5 f1d1081b67 feat(ui): build MegaMenu and rebuild Sidebar on Drawer
MegaMenu replaces a scaffold with a trigger and a wide panel of grouped link
columns. One level deep on purpose: a mega menu exists to show breadth flat so
everything is one click away, and nesting inside the panel buries content
behind hover-within-hover. Nav is the component for cascading submenus. The
panel is anchored so the runtime clamp keeps it inside the viewport.

Sidebar now composes Drawer for its off-canvas presentation instead of a
hand-rolled backdrop, inheriting the focus trap and scroll lock from one
place. Single items, labelled groups and branches nested to three levels, with
vertical roving focus.

Sidebar classes move to the BEM naming the rest of the library uses, which is
a breaking change; nesting via children still works, since that is what
shipped in 0.8.5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:24:03 +05:30
ClintchizandClaude Opus 5 b4d3cb3695 feat(ui): rewrite Tabs onto wire classes with URL sync and roving focus
Tabs was the only component in the library styled with Tailwind utilities, so
it could not be themed like the rest and assumed Tailwind was present. It also
fired raw CustomEvents instead of declaring outputs, and set a roving tabindex
with no keydown handler at all -- which left every inactive tab unreachable by
Tab while the arrows did nothing.

It now uses wire-* classes and a local style block, declares change and select
outputs, and opts into the roving runtime.

mode=url mirrors the selection into a query parameter via pushState. Back and
forward are handled in the runtime, which activates the matching tab rather
than assigning to component state: a popstate listener writing state would be
writing after the client function returned, and that write is dropped. The
round trip is marked so the component does not push a second history entry for
a navigation that came from history.

Also anchors Nav submenus so the viewport clamp can pull them back on screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:18:00 +05:30
ClintchizandClaude Opus 5 124da548b8 chore(ui): wire color and size into the new navigation components
Every bundled component must expose color and size; the rewrites dropped
them, which the library-wide invariant test caught. Rather than re-adding
them as dead props, each component now maps color onto an accent variable
that its active, current and focus affordances actually use, and size onto
the root font scale.

Regenerates the component reference, showcase and visual contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:57:10 +05:30
ClintchizandClaude Opus 5 f4960f2fc5 feat(ui): build the Nav component with submenus and roving focus
Replaces a scaffold that rendered bare anchors. Flat or nested to three
levels, icons, badges, disabled items, aria-current on the active link,
arrow-key roving focus, and a disclosure arrow that rotates on open.

Three levels rather than arbitrary depth because this template language has
no component recursion, so each level is written out.

On a phone the bar becomes a toggle and submenus stack inline rather than
floating: a hover-opened overlay cannot be reached on touch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:47:20 +05:30
ClintchizandClaude Opus 5 45ef035bae feat(ui): build the Stepper component
Replaces a scaffold that rendered bare anchors with ordered steps: complete,
current and upcoming status derived from the active index, horizontal or
vertical, optional icons, and indexed named slots (step-0, step-1, ...) for
authoring a step body by hand.

Also tightens the roving contract from the previous commit. A template writes
data-wrn-roving="" or data-wrn-roving-item="false" to mean not this time, but
a bare [attr] selector matches either, so a read-only stepper would still have
taken arrow-key focus. The container now requires a named axis, while a bare
item marker still counts as opted in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:44:12 +05:30
ClintchizandClaude Opus 5 b67a5e43eb feat(ui): build the Pagination component
Replaces a scaffold that rendered a bare list of anchors with real page
controls: compact arrows or windowed page numbers, a range summary, and a
change output carrying the requested page. Out-of-range pages clamp rather
than rendering nothing, because page arrives as an HTML attribute and callers
compute it from data that may have shrunk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:41:56 +05:30
ClintchizandClaude Opus 5 7ac6e08544 fix(csr): make dialog visibility testable and cover the focus trap
The focus trap and scroll lock shipped in 0.8.5 gated on
getBoundingClientRect, which the test DOM always reports as zero, so a dialog
never counted as open and none of that behaviour ran under test. focusableWithin
had the same measurement gate and would have found no items even once the
visibility check was fixed.

Both now use the hidden attribute and the data-show marker the components
already emit. Behaviour in a real browser is unchanged; the difference is that
it is now covered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:40:30 +05:30
ClintchizandClaude Opus 5 f94004648d feat(csr): runtime-owned roving arrow-key focus
A container marked data-wrn-roving owns its [data-wrn-roving-item]
descendants: one carries tabindex=0 so Tab reaches the group once, and the
arrow keys move within it, with Home/End, wrap-around and skip-disabled.

Written once here rather than five times across Tabs, Nav, MegaMenu, Sidebar
and Stepper, and because focus bookkeeping cannot live in component state --
a client function writing after it returns has that write dropped.

Item visibility is checked via hidden and data-show rather than measured
size: the test DOM reports every element as zero-sized, which is exactly what
left the dialog focus trap uncovered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:38:44 +05:30
ClintchizandClaude Opus 5 e8e1a2623b docs: navigation phase 1 implementation plan
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:35:42 +05:30
ClintchizandClaude Opus 5 ecb93c7116 docs: navigation component group design
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:28:30 +05:30
ClintchizandClaude Opus 5 f01a308287 chore(release): stage 0.8.5 package tarballs
Quality / quality (ubuntu-latest) (push) Failing after 12m26s
Quality / quality (windows-latest) (push) Canceled after 0s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:16:43 +05:30
ClintchizandClaude Opus 5 1fb1a8d2d0 chore(release): prepare 0.8.5
Quality / quality (ubuntu-latest) (push) Failing after 12m21s
Quality / quality (windows-latest) (push) Canceled after 0s
Bumps every @wrnexus package 0.8.4 -> 0.8.5 and adds the matching update
migration. The migration is documentation only: moving off <Table> to
<DataTable> and off the @wrnexus/ui main entry to @wrnexus/ui/registry are
source changes no codemod can make safely.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:08:05 +05:30
ClintchizandClaude Opus 5 949cf78636 feat(ui): add DataTable and Toaster, drop the legacy Table, fix overlay dialogs
Quality / quality (ubuntu-latest) (push) Failing after 13m40s
Quality / quality (windows-latest) (push) Canceled after 0s
DataTable replaces the 20-line Table scaffold entirely: columns, sorting,
filtering, pagination, selection, bulk actions, comparison layout, sticky
first column, custom HTML cells, and a remote source driven by a `request`
output rather than a function prop (props travel as HTML attributes, so a
function arrives as its own source text).

Toaster replaces the hand-rolled status div: tone icons, actions, hover
pause/resume and a progress bar.

Overlays audit -- Modal and Drawer declared aria-modal="true" but nothing
ever moved focus into the panel, so the @keydown handler on their root
never ran and closeOnEscape did nothing. Focus, focus restore, a Tab trap
and a body scroll lock now live in the reactive runtime, shared by both.

ContextMenu placed pointer menus by subtracting a guessed 340x420 from the
viewport, which pushed every menu that was not that size away from the
pointer; it now positions at the pointer and lets the anchored clamp pull
it back once it can be measured.

The reactive runtime size budget moves 150k -> 175k to cover anchored
overlays, dialog behaviour, the toaster and the DataTable client half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 14:59:58 +05:30
127 changed files with 8252 additions and 3035 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "component-showcase",
"runtimeExecutable": "bun",
"runtimeArgs": ["run", "--cwd", "examples/component-showcase", "dev"],
"port": 3000
}
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
"license": "MIT", "license": "MIT",
+153
View File
@@ -149,3 +149,156 @@ app.put(
- **Bun-only** — like the rest of WrNexus, this package targets the Bun runtime; Node is not supported. - **Bun-only** — like the rest of WrNexus, this package targets the Bun runtime; Node is not supported.
- Works with [`@wrnexus/core`](../core) — the guards return `Middleware` and read the subject from `ctx.user` on the request `Context`. Both types are imported from `@wrnexus/core`. - Works with [`@wrnexus/core`](../core) — the guards return `Middleware` and read the subject from `ctx.user` on the request `Context`. Both types are imported from `@wrnexus/core`.
- Policy combinators (`any`, `all`) and `authorize` are async-aware, so policies may return a `Promise<boolean>` (e.g. for a database ownership check). - Policy combinators (`any`, `all`) and `authorize` are async-aware, so policies may return a `Promise<boolean>` (e.g. for a database ownership check).
## Declaring permissions
The RBAC/PBAC/ABAC surface above is the low-level toolkit. On top of it sits a
declarative **registry + catalog + store + engine**: permissions, roles, and
policies are declared once in code, merged into a frozen catalog at boot, and
resolved per-request against a pluggable `PermissionStore` that holds who has
what.
Put declarations in `app/authz/<name>.ts`; they are discovered automatically
and merged (conflicting declarations of the same permission/role/policy across
files fail the boot loudly, naming both source files).
```ts
import { defineAuthz, owner } from "@wrnexus/authz";
export default defineAuthz({
permissions: {
"post:read": { title: "View posts", public: true },
"post:delete": { title: "Delete posts", risk: "high" },
},
// "post:*" is a namespace wildcard grant, valid inside a role's list — it is
// not itself a registered permission, so it can only ever grant permissions
// that ARE declared above (e.g. "post:read", "post:delete").
roles: { editor: ["post:*"], admin: ["role:editor"] },
policies: { ownsPost: owner("id", "authorId") },
bindings: { "post:delete": ["ownsPost"] },
});
```
`public: true` means anonymous callers may hold the permission — but any
policy bound to it still runs, and can still veto the anonymous caller (e.g. a
`notBanned` policy on a public `post:preview` permission).
## Checking permissions
Register `authzMiddleware` once, in `app/middleware/`, with the merged
catalog and a `PermissionStore`. Like every other `app/middleware/*.ts` file,
the registration is an eager, module-scope call — the same shape as
`authzMiddleware({ catalog, store })` requires — so it must run after the
catalog has been populated. Both the dev server and `wrnexus build`'s
generated production entry guarantee `getAuthzCatalog()` is populated before
any app middleware module evaluates. Name the file so it sorts after whatever
middleware sets `ctx.user` (middleware runs in alphabetical filename order —
`authz.ts` after `auth.ts`, for instance).
```ts
// app/middleware/authz.ts
import { authzMiddleware, getAuthzCatalog } from "@wrnexus/authz";
import { dbPermissionStore } from "@wrnexus/authz/db";
import { getDb } from "@wrnexus/db";
export default authzMiddleware({ catalog: getAuthzCatalog(), store: dbPermissionStore(getDb()) });
```
> **`subject.id` must be a non-empty string.** The engine denies (and logs to
> stderr) whenever `ctx.user.id` is present but not a non-empty string — this
> includes the common case of an integer primary key. Coerce it before it
> reaches `ctx.user`, e.g. `user.id = String(row.id)`, or every request for
> that user denies with "Invalid subject" instead of resolving normally.
> `owner()` (the built-in ownership policy) compares subject and resource ids
> with `Object.is`, so both sides must be the same type too — `owner()` on a
> numeric `resource.authorId` against a stringified `subject.id` never
> matches even when they represent "the same" id.
There is no per-route `middleware` export — `app/middleware/*.ts` is the only
place middleware is registered. To gate part of the app, branch on the
request the same way any other conditional middleware does (compare
`app/middleware/captcha-login.ts` in the auth showcase, which branches on
method + path the same way):
```ts
// app/middleware/protect-posts.ts
import type { Context, Next } from "@wrnexus/core";
import { guardPermission } from "@wrnexus/authz";
const guardPostWrite = guardPermission("post:write");
export default function protectPosts(ctx: Context, next: Next) {
return ctx.url.pathname.startsWith("/api/posts") && ctx.req.method !== "GET"
? guardPostWrite(ctx, next)
: next();
}
```
Or check inline inside a route handler with the free function `can()`:
```ts
// app/api/posts/[id].ts
import type { Context } from "@wrnexus/core";
import { can } from "@wrnexus/authz";
export const DELETE = async (ctx: Context) => {
const post = { id: "1", authorId: "alice" }; // load your own resource here
if (!(await can(ctx, "post:delete", post))) {
return Response.json({ ok: false, error: "Forbidden" }, { status: 403 });
}
return Response.json({ ok: true });
};
```
`can()` is a free function taking `ctx`, not `ctx.can``@wrnexus/core` must
not depend on `@wrnexus/authz`, so the per-request resolver lives in
`ctx.locals` instead, reached through `can()` / `decideFor()` /
`guardPermission()` / `filterCan()`. Calling any of them before
`authzMiddleware` has run for that request throws a `WRN-AUTHZ-SETUP` error
naming the missing registration, rather than silently denying.
See `examples/auth-showcase/app/authz/showcase.ts` and
`examples/auth-showcase/app/middleware/authz.ts` for a complete, runnable
version of this wiring.
## Precedence
1. An explicit deny wins over everything, including `*` — and honours the
same namespace-wildcard matching as grants (denying `post:*` blocks
`post:comment:delete`, not just `post:*` itself).
2. A bound policy can veto a permission a role grants, and runs even for a
`public: true` permission — including for an anonymous caller.
3. Otherwise the permission must be held via a role or an explicit grant.
4. Default deny.
Every failure — an unknown permission (outside strict/dev mode), a store
outage, a thrown policy — denies rather than throwing through to the caller.
`permissionsFor()` (on the resolver returned by `createAuthzResolver`) is a
coarse hint for hiding UI (e.g. a menu section), **never authoritative**. A
`Set<string>` cannot represent "granted `post:*` except `post:delete`", so a
narrow deny beneath a broad grant is invisible to it — the set still contains
`post:*` while `can()` / `decide()` correctly refuse `post:delete`. Gate real
actions with `can()`, `decideFor()`, or `filterCan()`; never by matching
against `permissionsFor()`'s result.
## CLI
```bash
wrnexus authz list # every registered permission, role, and policy
wrnexus authz generate # app/authz/permissions.gen.ts type unions
wrnexus authz init # scaffold the assignment-table migration
```
`wrnexus authz generate`'s output is a plain `Permission | Role` string-literal
union — `can()`, `guardPermission()`, and `decideFor()` all take a bare
`string` and nothing reads this file automatically, so import it to type your
own helpers/constants against the registered catalog, e.g.:
```ts
import type { Permission } from "app/authz/permissions.gen.ts";
function guard(permission: Permission) {
return guardPermission(permission);
}
```
+9 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/authz — part of the WrNexus framework.", "description": "@wrnexus/authz — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -34,8 +34,16 @@
".": { ".": {
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/index.js" "import": "./dist/index.js"
},
"./db": {
"types": "./dist/db.d.ts",
"import": "./dist/db.js"
} }
}, },
"dependencies": {
"@wrnexus/core": "^0.8.5",
"@wrnexus/db": "^0.8.5"
},
"files": [ "files": [
"dist", "dist",
"README.md" "README.md"
+18 -17
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/cli — part of the WrNexus framework.", "description": "@wrnexus/cli — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -44,22 +44,23 @@
"wrnexus": "./dist/index.js" "wrnexus": "./dist/index.js"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4", "@wrnexus/core": "^0.8.5",
"@wrnexus/router": "^0.8.4", "@wrnexus/router": "^0.8.5",
"@wrnexus/csr": "^0.8.4", "@wrnexus/csr": "^0.8.5",
"@wrnexus/compiler": "^0.8.4", "@wrnexus/compiler": "^0.8.5",
"@wrnexus/styles": "^0.8.4", "@wrnexus/styles": "^0.8.5",
"@wrnexus/dev-server": "^0.8.4", "@wrnexus/dev-server": "^0.8.5",
"@wrnexus/ui": "^0.8.4", "@wrnexus/ui": "^0.8.5",
"@wrnexus/validation": "^0.8.4", "@wrnexus/validation": "^0.8.5",
"@wrnexus/i18n": "^0.8.4", "@wrnexus/i18n": "^0.8.5",
"@wrnexus/mcp": "^0.8.4", "@wrnexus/mcp": "^0.8.5",
"@wrnexus/playground": "^0.8.4", "@wrnexus/playground": "^0.8.5",
"@wrnexus/db": "^0.8.4", "@wrnexus/db": "^0.8.5",
"@wrnexus/plugin": "^0.8.4", "@wrnexus/authz": "^0.8.5",
"@wrnexus/syntax": "^0.8.4", "@wrnexus/plugin": "^0.8.5",
"@wrnexus/typecheck": "^0.8.4", "@wrnexus/syntax": "^0.8.5",
"@wrnexus/security": "^0.8.4", "@wrnexus/typecheck": "^0.8.5",
"@wrnexus/security": "^0.8.5",
"selfsigned": "^5.5.0" "selfsigned": "^5.5.0"
}, },
"files": [ "files": [
+5 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/compiler — part of the WrNexus framework.", "description": "@wrnexus/compiler — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,10 +37,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/csr": "^0.8.4", "@wrnexus/csr": "^0.8.5",
"@wrnexus/syntax": "^0.8.4", "@wrnexus/syntax": "^0.8.5",
"@wrnexus/store": "^0.8.4", "@wrnexus/store": "^0.8.5",
"@wrnexus/validation": "^0.8.4" "@wrnexus/validation": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/core — part of the WrNexus framework.", "description": "@wrnexus/core — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/csr — part of the WrNexus framework.", "description": "@wrnexus/csr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4" "@wrnexus/core": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Typed database drivers, migrations, instrumentation, repositories, pagination, and transaction helpers.", "description": "Typed database drivers, migrations, instrumentation, repositories, pagination, and transaction helpers.",
"license": "MIT", "license": "MIT",
+22 -20
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/dev-server — part of the WrNexus framework.", "description": "@wrnexus/dev-server — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -41,25 +41,27 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4", "@wrnexus/authz": "^0.8.5",
"@wrnexus/dev-toolbar": "^0.8.4", "@wrnexus/rpc": "^0.8.5",
"@wrnexus/router": "^0.8.4", "@wrnexus/core": "^0.8.5",
"@wrnexus/ssr": "^0.8.4", "@wrnexus/dev-toolbar": "^0.8.5",
"@wrnexus/csr": "^0.8.4", "@wrnexus/router": "^0.8.5",
"@wrnexus/compiler": "^0.8.4", "@wrnexus/ssr": "^0.8.5",
"@wrnexus/styles": "^0.8.4", "@wrnexus/csr": "^0.8.5",
"@wrnexus/ui": "^0.8.4", "@wrnexus/compiler": "^0.8.5",
"@wrnexus/validation": "^0.8.4", "@wrnexus/styles": "^0.8.5",
"@wrnexus/i18n": "^0.8.4", "@wrnexus/ui": "^0.8.5",
"@wrnexus/db": "^0.8.4", "@wrnexus/validation": "^0.8.5",
"@wrnexus/pubsub": "^0.8.4", "@wrnexus/i18n": "^0.8.5",
"@wrnexus/uploader": "^0.8.4", "@wrnexus/db": "^0.8.5",
"@wrnexus/plugin": "^0.8.4", "@wrnexus/pubsub": "^0.8.5",
"@wrnexus/store": "^0.8.4", "@wrnexus/uploader": "^0.8.5",
"@wrnexus/security": "^0.8.4", "@wrnexus/plugin": "^0.8.5",
"@wrnexus/observability": "^0.8.4", "@wrnexus/store": "^0.8.5",
"@wrnexus/cache": "^0.8.4", "@wrnexus/security": "^0.8.5",
"@wrnexus/pwa": "^0.8.4" "@wrnexus/observability": "^0.8.5",
"@wrnexus/cache": "^0.8.5",
"@wrnexus/pwa": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/dev-toolbar — part of the WrNexus framework.", "description": "@wrnexus/dev-toolbar — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Authenticated encryption, key rotation, hashing, and optional application-layer encrypted HTTP envelopes.", "description": "Authenticated encryption, key rotation, hashing, and optional application-layer encrypted HTTP envelopes.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4" "@wrnexus/core": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4" "@wrnexus/core": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Locale loading, fallback resolution, SSR/browser translations, formatters, and WRNexusJS language components.", "description": "Locale loading, fallback resolution, SSR/browser translations, formatters, and WRNexusJS language components.",
"license": "MIT", "license": "MIT",
@@ -42,9 +42,9 @@
"./components/*": "./components/*" "./components/*": "./components/*"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4", "@wrnexus/core": "^0.8.5",
"@wrnexus/plugin": "^0.8.4", "@wrnexus/plugin": "^0.8.5",
"@wrnexus/ui": "^0.8.4" "@wrnexus/ui": "^0.8.5"
}, },
"wrnexus": { "wrnexus": {
"plugin": { "plugin": {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "HS256 JSON Web Tokens, key rotation, access/refresh helpers, scopes, cookies, and auth middleware.", "description": "HS256 JSON Web Tokens, key rotation, access/refresh helpers, scopes, cookies, and auth middleware.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4" "@wrnexus/core": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/mobile — part of the WrNexus framework.", "description": "@wrnexus/mobile — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/native": "^0.8.4" "@wrnexus/native": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/native — part of the WrNexus framework.", "description": "@wrnexus/native — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/oauth — part of the WrNexus framework.", "description": "@wrnexus/oauth — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/jwt": "^0.8.4" "@wrnexus/jwt": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/plugin", "name": "@wrnexus/plugin",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/plugin — part of the WrNexus framework.", "description": "@wrnexus/plugin — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -49,7 +49,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/syntax": "^0.8.4" "@wrnexus/syntax": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/pubsub — part of the WrNexus framework.", "description": "@wrnexus/pubsub — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/queue — part of the WrNexus framework.", "description": "@wrnexus/queue — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4" "@wrnexus/core": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/reactive — part of the WrNexus framework.", "description": "@wrnexus/reactive — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/router — part of the WrNexus framework.", "description": "@wrnexus/router — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,8 +37,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/compiler": "^0.8.4", "@wrnexus/compiler": "^0.8.5",
"@wrnexus/core": "^0.8.4" "@wrnexus/core": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/ssr — part of the WrNexus framework.", "description": "@wrnexus/ssr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -45,9 +45,9 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4", "@wrnexus/core": "^0.8.5",
"@wrnexus/store": "^0.8.4", "@wrnexus/store": "^0.8.5",
"@wrnexus/security": "^0.8.4" "@wrnexus/security": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/styles — part of the WrNexus framework.", "description": "@wrnexus/styles — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,9 +37,9 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/uploader": "^0.8.4", "@wrnexus/uploader": "^0.8.5",
"@wrnexus/core": "^0.8.4", "@wrnexus/core": "^0.8.5",
"@wrnexus/plugin": "^0.8.4" "@wrnexus/plugin": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/syntax — part of the WrNexus framework.", "description": "@wrnexus/syntax — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/test — part of the WrNexus framework.", "description": "@wrnexus/test — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/tracking — part of the WrNexus framework.", "description": "@wrnexus/tracking — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+16 -16
View File
@@ -359,6 +359,15 @@ Reusable preference switcher component.
- Slots: None - Slots: None
- Outputs: `theme({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `color({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `language({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])` - Outputs: `theme({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `color({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `language({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`
### Toaster
Reusable toaster component.
- Mount: `data-component="Toaster"`
- Props: `color: string = "info"`, `size: string = "default"`, `position: string = "bottom-right"`, `duration: number = 4500`, `max: number = 4`, `pauseOnHover: boolean = true`, `showIcon: boolean = true`, `successIcon: string = ""`, `dangerIcon: string = ""`, `warningIcon: string = ""`, `infoIcon: string = ""`, `closable: boolean = true`, `showProgress: boolean = true`, `closeLabel: string = "Dismiss notification"`, `class: string = ""`
- Slots: None
- Outputs: `show({ id: number; message: string; tone: string })`, `dismiss({ id: number; reason: string })`, `action({ id: number; sourceEvent: Event })`
## Data ## Data
### MetricCard ### MetricCard
@@ -554,15 +563,6 @@ Theme-aware, responsive data map component.
- Slots: `default` - Slots: `default`
- Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)` - Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`
### DataTable
Theme-aware, responsive data table component.
- Mount: `data-component="DataTable"`
- Props: `size: string = "default"`, `color: string = "primary"`, `caption: string = "Data Table"`, `columns: unknown[] = []`, `rows: unknown[] = []`, `striped: boolean = true`, `class: string = ""`
- Slots: `default`
- Outputs: `sort({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `rowClick({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `pageChange({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`
### DragAndDrop ### DragAndDrop
Theme-aware, responsive drag and drop component. Theme-aware, responsive drag and drop component.
@@ -945,7 +945,7 @@ Open an accessible keyboard-aware action menu from pointer or keyboard context i
Present responsive modal side or bottom content with focus management, backdrop behavior, slots, and close events. Present responsive modal side or bottom content with focus management, backdrop behavior, slots, and close events.
- Mount: `data-component="Drawer"` - Mount: `data-component="Drawer"`
- Props: `open: boolean = false`, `defaultOpen: boolean = false`, `placement: string = "right"`, `size: string = "md"`, `color: string = "primary"`, `variant: string = "default"`, `title: string = "Drawer"`, `description: string = ""`, `icon: string = ""`, `label: string = "Drawer"`, `closeLabel: string = "Close drawer"`, `showClose: boolean = true`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `overlay: boolean = true`, `scrollable: boolean = true`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `class: string = ""` - Props: `open: boolean = false`, `defaultOpen: boolean = false`, `placement: string = "right"`, `size: string = "md"`, `color: string = "primary"`, `variant: string = "default"`, `title: string = "Drawer"`, `description: string = ""`, `icon: string = ""`, `label: string = "Drawer"`, `closeLabel: string = "Close drawer"`, `showClose: boolean = true`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `duration: number = 260`, `overlay: boolean = true`, `scrollable: boolean = true`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `class: string = ""`
- Slots: `trigger`, `header`, `default`, `footer` - Slots: `trigger`, `header`, `default`, `footer`
- Outputs: `open({ placement: string; sourceEvent: Event })`, `close({ reason: string; placement: string; sourceEvent: Event })`, `cancel({ placement: string; sourceEvent: Event })` - Outputs: `open({ placement: string; sourceEvent: Event })`, `close({ reason: string; placement: string; sourceEvent: Event })`, `cancel({ placement: string; sourceEvent: Event })`
@@ -963,7 +963,7 @@ Open an accessible anchored menu with keyboard navigation, item selection, actio
Present an accessible modal dialog with focus management, confirmation, cancellation, slots, and responsive sizing. Present an accessible modal dialog with focus management, confirmation, cancellation, slots, and responsive sizing.
- Mount: `data-component="Modal"` - Mount: `data-component="Modal"`
- Props: `open: boolean = false`, `defaultOpen: boolean = false`, `title: string = "Modal"`, `description: string = ""`, `icon: string = ""`, `label: string = "Modal dialog"`, `size: string = "md"`, `placement: string = "center"`, `color: string = "primary"`, `variant: string = "default"`, `showClose: boolean = true`, `closeLabel: string = "Close modal"`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `closeOnCancel: boolean = true`, `closeOnConfirm: boolean = false`, `showFooter: boolean = true`, `cancelLabel: string = "Cancel"`, `cancelIcon: string = ""`, `confirmLabel: string = "Confirm"`, `confirmIcon: string = ""`, `confirmDisabled: boolean = false`, `confirmLoading: boolean = false`, `destructive: boolean = false`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `scrollable: boolean = true`, `class: string = ""` - Props: `open: boolean = false`, `defaultOpen: boolean = false`, `title: string = "Modal"`, `description: string = ""`, `icon: string = ""`, `label: string = "Modal dialog"`, `size: string = "md"`, `placement: string = "center"`, `color: string = "primary"`, `variant: string = "default"`, `showClose: boolean = true`, `closeLabel: string = "Close modal"`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `closeOnCancel: boolean = true`, `closeOnConfirm: boolean = false`, `showFooter: boolean = true`, `cancelLabel: string = "Cancel"`, `cancelIcon: string = ""`, `confirmLabel: string = "Confirm"`, `confirmIcon: string = ""`, `confirmDisabled: boolean = false`, `confirmLoading: boolean = false`, `destructive: boolean = false`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `scrollable: boolean = true`, `scrollBehavior: string = "inside"`, `class: string = ""`
- Slots: `trigger`, `header`, `default`, `footer` - Slots: `trigger`, `header`, `default`, `footer`
- Outputs: `open({ sourceEvent: Event })`, `close({ reason: string; sourceEvent: Event })`, `cancel({ sourceEvent: Event })`, `confirm({ sourceEvent: Event })` - Outputs: `open({ sourceEvent: Event })`, `close({ reason: string; sourceEvent: Event })`, `cancel({ sourceEvent: Event })`, `confirm({ sourceEvent: Event })`
@@ -987,11 +987,11 @@ Show concise accessible contextual help on hover, focus, click, or controlled op
## Tables ## Tables
### Table ### DataTable
Theme-aware, responsive table component. Sortable, filterable, paginated data table with row selection.
- Mount: `data-component="Table"` - Mount: `data-component="DataTable"`
- Props: `size: string = "default"`, `color: string = "primary"`, `caption: string = "Table"`, `columns: unknown[] = []`, `rows: unknown[] = []`, `striped: boolean = true`, `class: string = ""` - Props: `color: string = "primary"`, `size: string = "default"`, `columns: unknown[] = []`, `rows: unknown[] = []`, `rowKey: string = "id"`, `remote: boolean = false`, `loadingLabel: string = "Loading"`, `errorLabel: string = "Could not load this data"`, `retryLabel: string = "Try again"`, `caption: string = ""`, `description: string = ""`, `searchable: boolean = true`, `searchPlaceholder: string = "Search"`, `paginated: boolean = true`, `pageSize: number = 10`, `paginationStyle: string = "compact"`, `pageSizes: number[] = [10, 25, 50]`, `selectable: boolean = false`, `actions: unknown[] = []`, `striped: boolean = true`, `bordered: boolean = true`, `gridlines: string = "rows"`, `density: string = "default"`, `emptyLabel: string = "No records to show"`, `noResultsLabel: string = "No records match your search"`, `clearSearchLabel: string = "Clear search"`, `stickyFirstColumn: boolean = false`, `layout: string = "rows"`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: `sort({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `rowClick({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)` - Outputs: `sort({ key: string; direction: string })`, `search({ query: string })`, `pageChange({ page: number; pageSize: number })`, `select({ selected: Array<string | number>; all: boolean })`, `change({ page: number; pageSize: number; total: number; query: string; sortKey: string; sortDirection: string })`, `rowClick({ row: object; sourceEvent: Event })`, `action({ id: string; selected: Array<string | number>; rows: object[]; sourceEvent: Event })`, `request({ instanceId: number; page: number; pageSize: number; sortKey: string; sortDirection: string; query: string })`
+7 -7
View File
@@ -168,8 +168,8 @@
}, },
{ {
"name": "DataTable", "name": "DataTable",
"category": "integrations", "category": "tables",
"purpose": "Theme-aware, responsive data table component." "purpose": "Sortable, filterable, paginated data table with row selection."
}, },
{ {
"name": "DatePicker", "name": "DatePicker",
@@ -471,11 +471,6 @@
"category": "forms", "category": "forms",
"purpose": "Theme-aware, responsive switch component." "purpose": "Theme-aware, responsive switch component."
}, },
{
"name": "Table",
"category": "tables",
"purpose": "Theme-aware, responsive table component."
},
{ {
"name": "Tabs", "name": "Tabs",
"category": "navigation", "category": "navigation",
@@ -511,6 +506,11 @@
"category": "integrations", "category": "integrations",
"purpose": "Theme-aware, responsive toast notifications component." "purpose": "Theme-aware, responsive toast notifications component."
}, },
{
"name": "Toaster",
"category": "core",
"purpose": "Reusable toaster component."
},
{ {
"name": "ToggleCount", "name": "ToggleCount",
"category": "advanced-forms", "category": "advanced-forms",
+343 -98
View File
@@ -4569,16 +4569,9 @@
{ {
"name": "DataTable", "name": "DataTable",
"mount": "DataTable", "mount": "DataTable",
"category": "integrations", "category": "tables",
"purpose": "Theme-aware, responsive data table component.", "purpose": "Sortable, filterable, paginated data table with row selection.",
"props": [ "props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "color", "name": "color",
"type": "string", "type": "string",
@@ -4587,10 +4580,10 @@
"options": [] "options": []
}, },
{ {
"name": "caption", "name": "size",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"Data Table\"", "default": "\"default\"",
"options": [] "options": []
}, },
{ {
@@ -4607,6 +4600,111 @@
"default": "[]", "default": "[]",
"options": [] "options": []
}, },
{
"name": "rowKey",
"type": "string",
"required": false,
"default": "\"id\"",
"options": []
},
{
"name": "remote",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "loadingLabel",
"type": "string",
"required": false,
"default": "\"Loading\"",
"options": []
},
{
"name": "errorLabel",
"type": "string",
"required": false,
"default": "\"Could not load this data\"",
"options": []
},
{
"name": "retryLabel",
"type": "string",
"required": false,
"default": "\"Try again\"",
"options": []
},
{
"name": "caption",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "description",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "searchable",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{
"name": "searchPlaceholder",
"type": "string",
"required": false,
"default": "\"Search\"",
"options": []
},
{
"name": "paginated",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{
"name": "pageSize",
"type": "number",
"required": false,
"default": "10",
"options": []
},
{
"name": "paginationStyle",
"type": "string",
"required": false,
"default": "\"compact\"",
"options": []
},
{
"name": "pageSizes",
"type": "number[]",
"required": false,
"default": "[10, 25, 50]",
"options": []
},
{
"name": "selectable",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "actions",
"type": "unknown[]",
"required": false,
"default": "[]",
"options": []
},
{ {
"name": "striped", "name": "striped",
"type": "boolean", "type": "boolean",
@@ -4614,6 +4712,62 @@
"default": "true", "default": "true",
"options": [] "options": []
}, },
{
"name": "bordered",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{
"name": "gridlines",
"type": "string",
"required": false,
"default": "\"rows\"",
"options": []
},
{
"name": "density",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "emptyLabel",
"type": "string",
"required": false,
"default": "\"No records to show\"",
"options": []
},
{
"name": "noResultsLabel",
"type": "string",
"required": false,
"default": "\"No records match your search\"",
"options": []
},
{
"name": "clearSearchLabel",
"type": "string",
"required": false,
"default": "\"Clear search\"",
"options": []
},
{
"name": "stickyFirstColumn",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "layout",
"type": "string",
"required": false,
"default": "\"rows\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -4626,26 +4780,47 @@
"outputs": [ "outputs": [
{ {
"name": "sort", "name": "sort",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null" "payloadType": "{ key: string; direction: string }"
}, },
{ {
"name": "select", "name": "search",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null" "payloadType": "{ query: string }"
},
{
"name": "change",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null"
},
{
"name": "rowClick",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null"
}, },
{ {
"name": "pageChange", "name": "pageChange",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null" "payloadType": "{ page: number; pageSize: number }"
},
{
"name": "select",
"payloadType": "{ selected: Array<string | number>; all: boolean }"
},
{
"name": "change",
"payloadType": "{ page: number; pageSize: number; total: number; query: string; sortKey: string; sortDirection: string }"
},
{
"name": "rowClick",
"payloadType": "{ row: object; sourceEvent: Event }"
},
{
"name": "action",
"payloadType": "{ id: string; selected: Array<string | number>; rows: object[]; sourceEvent: Event }"
},
{
"name": "request",
"payloadType": "{ instanceId: number; page: number; pageSize: number; sortKey: string; sortDirection: string; query: string }"
} }
], ],
"events": ["sort", "select", "change", "rowClick", "pageChange"], "events": [
"sort",
"search",
"pageChange",
"select",
"change",
"rowClick",
"action",
"request"
],
"source": "components/DataTable.wrn" "source": "components/DataTable.wrn"
}, },
{ {
@@ -5220,6 +5395,13 @@
"default": "true", "default": "true",
"options": [] "options": []
}, },
{
"name": "duration",
"type": "number",
"required": false,
"default": "260",
"options": []
},
{ {
"name": "overlay", "name": "overlay",
"type": "boolean", "type": "boolean",
@@ -8736,6 +8918,13 @@
"default": "true", "default": "true",
"options": [] "options": []
}, },
{
"name": "scrollBehavior",
"type": "string",
"required": false,
"default": "\"inside\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -12009,80 +12198,6 @@
"events": ["input", "change", "focus", "blur"], "events": ["input", "change", "focus", "blur"],
"source": "components/Switch.wrn" "source": "components/Switch.wrn"
}, },
{
"name": "Table",
"mount": "Table",
"category": "tables",
"purpose": "Theme-aware, responsive table component.",
"props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "color",
"type": "string",
"required": false,
"default": "\"primary\"",
"options": []
},
{
"name": "caption",
"type": "string",
"required": false,
"default": "\"Table\"",
"options": []
},
{
"name": "columns",
"type": "unknown[]",
"required": false,
"default": "[]",
"options": []
},
{
"name": "rows",
"type": "unknown[]",
"required": false,
"default": "[]",
"options": []
},
{
"name": "striped",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{
"name": "class",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
}
],
"slots": ["default"],
"outputs": [
{
"name": "sort",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null"
},
{
"name": "select",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null"
},
{
"name": "rowClick",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null"
}
],
"events": ["sort", "select", "rowClick"],
"source": "components/table.wrn"
},
{ {
"name": "Tabs", "name": "Tabs",
"mount": "Tabs", "mount": "Tabs",
@@ -12893,6 +13008,136 @@
"events": ["add", "dismiss", "clear", "action"], "events": ["add", "dismiss", "clear", "action"],
"source": "components/ToastNotifications.wrn" "source": "components/ToastNotifications.wrn"
}, },
{
"name": "Toaster",
"mount": "Toaster",
"category": "core",
"purpose": "Reusable toaster component.",
"props": [
{
"name": "color",
"type": "string",
"required": false,
"default": "\"info\"",
"options": []
},
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "position",
"type": "string",
"required": false,
"default": "\"bottom-right\"",
"options": []
},
{
"name": "duration",
"type": "number",
"required": false,
"default": "4500",
"options": []
},
{
"name": "max",
"type": "number",
"required": false,
"default": "4",
"options": []
},
{
"name": "pauseOnHover",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{
"name": "showIcon",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{
"name": "successIcon",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "dangerIcon",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "warningIcon",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "infoIcon",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "closable",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{
"name": "showProgress",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{
"name": "closeLabel",
"type": "string",
"required": false,
"default": "\"Dismiss notification\"",
"options": []
},
{
"name": "class",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
}
],
"slots": [],
"outputs": [
{
"name": "show",
"payloadType": "{ id: number; message: string; tone: string }"
},
{
"name": "dismiss",
"payloadType": "{ id: number; reason: string }"
},
{
"name": "action",
"payloadType": "{ id: number; sourceEvent: Event }"
}
],
"events": ["show", "dismiss", "action"],
"source": "components/Toaster.wrn"
},
{ {
"name": "ToggleCount", "name": "ToggleCount",
"mount": "ToggleCount", "mount": "ToggleCount",
+7 -2
View File
@@ -45,8 +45,12 @@ component ContextMenu {
sourceEvent.preventDefault() sourceEvent.preventDefault()
} }
if (placement === "pointer" && sourceEvent) { if (placement === "pointer" && sourceEvent) {
positionX = Math.max(12, Math.min(sourceEvent.clientX || 12, window.innerWidth - 340)) // Place the menu at the pointer and let the anchored clamp in the
positionY = Math.max(12, Math.min(sourceEvent.clientY || 12, window.innerHeight - 420)) // runtime pull it back on screen once it has been laid out and can
// actually be measured. Subtracting a guessed 340x420 here instead
// pushed every menu that was not that size away from the pointer.
positionX = Math.max(12, sourceEvent.clientX || 12)
positionY = Math.max(12, sourceEvent.clientY || 12)
} }
visible = true visible = true
output.open({ output.open({
@@ -161,6 +165,7 @@ component ContextMenu {
<div <div
class="wire-context-menu__panel" class="wire-context-menu__panel"
data-wrn-anchored="true"
data-show='{open || visible}' data-show='{open || visible}'
role="menu" role="menu"
aria-label='{label}' aria-label='{label}'
File diff suppressed because it is too large Load Diff
+83 -1
View File
@@ -20,6 +20,8 @@ open: boolean = false
showClose: boolean = true showClose: boolean = true
closeOnBackdrop: boolean = true closeOnBackdrop: boolean = true
closeOnEscape: boolean = true closeOnEscape: boolean = true
// Open/close animation length in ms. 0 disables the animation entirely.
duration: number = 260
overlay: boolean = true overlay: boolean = true
scrollable: boolean = true scrollable: boolean = true
triggerLabel: string = "" triggerLabel: string = ""
@@ -79,7 +81,9 @@ open: boolean = false
data-overlay='{overlay ? "true" : "false"}' data-overlay='{overlay ? "true" : "false"}'
data-scrollable='{scrollable ? "true" : "false"}' data-scrollable='{scrollable ? "true" : "false"}'
class='wire-drawer {class}' class='wire-drawer {class}'
style='--drawer-duration: {duration}ms'
> >
{#if triggerLabel} {#if triggerLabel}
<button <button
type="button" type="button"
@@ -101,7 +105,6 @@ open: boolean = false
<div <div
class="wire-drawer__layer" class="wire-drawer__layer"
data-show='{open || visible}'
role="presentation" role="presentation"
@keydown='handleKeydown(event)' @keydown='handleKeydown(event)'
> >
@@ -218,12 +221,32 @@ open: boolean = false
cursor: pointer; cursor: pointer;
} }
/*
* The layer stays in the layout and is revealed by [data-open]; it used to
* be toggled with data-show, which sets display:none, and display cannot
* be transitioned -- the drawer simply snapped in and out. visibility is
* delayed by the duration on the way out so the panel can finish sliding
* before the layer is taken out of the hit-testing tree.
*/
.wire-drawer__layer { .wire-drawer__layer {
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 1200; z-index: 1200;
display: flex; display: flex;
pointer-events: none; pointer-events: none;
visibility: hidden;
opacity: 0;
transition:
opacity var(--drawer-duration, 260ms) ease,
visibility 0s linear var(--drawer-duration, 260ms);
}
.wire-drawer[data-open="true"] .wire-drawer__layer {
visibility: visible;
opacity: 1;
transition:
opacity var(--drawer-duration, 260ms) ease,
visibility 0s linear 0s;
} }
.wire-drawer__backdrop { .wire-drawer__backdrop {
@@ -262,6 +285,38 @@ open: boolean = false
box-shadow: -28px 0 80px color-mix(in srgb, black 28%, transparent); box-shadow: -28px 0 80px color-mix(in srgb, black 28%, transparent);
pointer-events: auto; pointer-events: auto;
overflow: hidden; overflow: hidden;
/* Slides in from whichever edge the placement puts it on. */
transform: translateX(100%);
transition: transform var(--drawer-duration, 260ms) cubic-bezier(0.32, 0.72, 0, 1);
}
.wire-drawer[data-open="true"] .wire-drawer__panel {
transform: none;
}
.wire-drawer[data-placement="left"] .wire-drawer__panel {
transform: translateX(-100%);
}
.wire-drawer[data-placement="top"] .wire-drawer__panel {
transform: translateY(-100%);
}
.wire-drawer[data-placement="bottom"] .wire-drawer__panel {
transform: translateY(100%);
}
.wire-drawer[data-open="true"][data-placement="left"] .wire-drawer__panel,
.wire-drawer[data-open="true"][data-placement="top"] .wire-drawer__panel,
.wire-drawer[data-open="true"][data-placement="bottom"] .wire-drawer__panel {
transform: none;
}
@media (prefers-reduced-motion: reduce) {
.wire-drawer__layer,
.wire-drawer__panel {
transition: none;
}
} }
.wire-drawer[data-size="sm"] .wire-drawer__panel { .wire-drawer[data-size="sm"] .wire-drawer__panel {
@@ -395,12 +450,19 @@ open: boolean = false
color: color-mix(in srgb, currentColor 76%, transparent); color: color-mix(in srgb, currentColor 76%, transparent);
} }
/*
* padding is reset explicitly: an app-level `button { padding: ... }` rule
* outranks the browser default and leaves this fixed-size button with a
* content box of a couple of pixels, which squeezes the icon to a sliver
* and reads as "the close button has no icon". Same trap as Modal.
*/
.wire-drawer__close { .wire-drawer__close {
appearance: none; appearance: none;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex: 0 0 auto; flex: 0 0 auto;
padding: 0;
width: 2.35rem; width: 2.35rem;
height: 2.35rem; height: 2.35rem;
color: var(--wire-color-text-muted); color: var(--wire-color-text-muted);
@@ -417,10 +479,30 @@ open: boolean = false
outline: none; outline: none;
} }
/* Never let the glyph be shrunk by the flex container. */
.wire-drawer__close svg {
flex: 0 0 auto;
width: 1rem;
height: 1rem;
}
/*
* Slot content is authored by the host app, so the app global stylesheet
* styles it too. A bare element selector there (p { color: ... }) beats
* anything the panel merely *inherits*, which is how modal body copy ended
* up muted grey on a saturated background. State the colour explicitly;
* :where() keeps the specificity low enough that any class the app puts on
* its own slot content still wins.
*/
.wire-drawer__body { .wire-drawer__body {
flex: 1 1 auto; flex: 1 1 auto;
min-height: 0; min-height: 0;
padding: 1.35rem; padding: 1.35rem;
color: var(--wire-color-text);
}
.wire-drawer__body :where(p, li, dd, dt, h1, h2, h3, h4, h5, h6, span, label, code) {
color: inherit;
} }
.wire-drawer[data-scrollable="true"] .wire-drawer__body { .wire-drawer[data-scrollable="true"] .wire-drawer__body {
+1
View File
@@ -171,6 +171,7 @@ items: unknown[] = []
<div <div
class="wire-dropdown__panel" class="wire-dropdown__panel"
data-wrn-anchored="true"
data-show='{open || visible}' data-show='{open || visible}'
role="menu" role="menu"
aria-label='{menuLabel}' aria-label='{menuLabel}'
+196 -17
View File
@@ -35,6 +35,7 @@ component Modal {
triggerLabel: string = "" triggerLabel: string = ""
triggerIcon: string = "" triggerIcon: string = ""
scrollable: boolean = true scrollable: boolean = true
scrollBehavior: string = "inside"
class: string = "" class: string = ""
} }
@@ -50,6 +51,16 @@ component Modal {
output.open({ sourceEvent: sourceEvent }) output.open({ sourceEvent: sourceEvent })
} }
// Slot content can close the modal it sits in by dispatching a bubbling
// wrnexus:modal:close event, e.g. from a form success handler:
//
// event.target.dispatchEvent(
// new CustomEvent("wrnexus:modal:close", { bubbles: true })
// )
//
// The listener is on the modal root, so the event only ever closes the
// modal the dispatching element is actually inside -- no ids to wire up
// and no way to close somebody else's modal by accident.
client function hideModal(reason, sourceEvent) { client function hideModal(reason, sourceEvent) {
visible = false visible = false
output.close({ output.close({
@@ -87,21 +98,23 @@ component Modal {
<div <div
{...attrs} {...attrs}
data-ui-component="Modal" data-ui-component="Modal"
data-open='{open || visible ? "true" : "false"}' data-open='{isOpen() ? "true" : "false"}'
data-size='{size}' data-size='{size}'
data-placement='{placement}' data-placement='{placement}'
data-color='{color}' data-color='{color}'
data-variant='{variant}' data-variant='{variant}'
data-scrollable='{scrollable ? "true" : "false"}' data-scrollable='{scrollable ? "true" : "false"}'
data-scroll='{scrollBehavior}'
data-destructive='{destructive ? "true" : "false"}' data-destructive='{destructive ? "true" : "false"}'
class='wire-modal {class}' class='wire-modal {class}'
@wrnexus:modal:close='hideModal("api", event)'
> >
{#if triggerLabel} {#if triggerLabel}
<button <button
type="button" type="button"
class="wire-modal__trigger" class="wire-modal__trigger"
aria-haspopup="dialog" aria-haspopup="dialog"
aria-expanded='{open || visible ? "true" : "false"}' aria-expanded='{isOpen() ? "true" : "false"}'
@click='showModal(event)' @click='showModal(event)'
> >
{#if triggerIcon} {#if triggerIcon}
@@ -127,7 +140,7 @@ component Modal {
<div <div
class="wire-modal__layer" class="wire-modal__layer"
data-show='{open || visible}' data-show="isOpen()"
role="presentation" role="presentation"
@keydown='handleKeydown(event)' @keydown='handleKeydown(event)'
> >
@@ -183,11 +196,20 @@ component Modal {
aria-label='{closeLabel}' aria-label='{closeLabel}'
@click='hideModal("close-button", event)' @click='hideModal("close-button", event)'
> >
<span <svg
class="icon-[lucide--x]" viewBox="0 0 24 24"
width="16"
height="16"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true" aria-hidden="true"
> >
</span> <path d="M18 6 6 18" />
<path d="M6 6 18 18" />
</svg>
</button> </button>
{/if} {/if}
</header> </header>
@@ -277,16 +299,26 @@ component Modal {
--modal-contrast: var(--wire-color-secondary-contrast); --modal-contrast: var(--wire-color-secondary-contrast);
} }
/*
* Fallbacks below (the second var() argument): the theme token generator
* (packages/styles/src/theme.ts) only emits a real -contrast token for
* primary and secondary. info, success, and danger have no contrast
* token defined at all, so var(--wire-color-info-contrast) with no
* fallback resolves to nothing, and --modal-contrast becomes invalid --
* which made confirm-button and solid-panel text unreadable. White is a
* safe default against these saturated colors until the theme package
* defines real tokens for them.
*/
.wire-modal[data-color="info"] { .wire-modal[data-color="info"] {
--modal-accent: var(--wire-color-info); --modal-accent: var(--wire-color-info);
--modal-soft: var(--wire-color-info-soft); --modal-soft: var(--wire-color-info-soft);
--modal-contrast: var(--wire-color-info-contrast); --modal-contrast: var(--wire-color-info-contrast, white);
} }
.wire-modal[data-color="success"] { .wire-modal[data-color="success"] {
--modal-accent: var(--wire-color-success); --modal-accent: var(--wire-color-success);
--modal-soft: var(--wire-color-success-soft); --modal-soft: var(--wire-color-success-soft);
--modal-contrast: var(--wire-color-success-contrast); --modal-contrast: var(--wire-color-success-contrast, white);
} }
.wire-modal[data-color="warning"] { .wire-modal[data-color="warning"] {
@@ -299,7 +331,7 @@ component Modal {
.wire-modal[data-destructive="true"] { .wire-modal[data-destructive="true"] {
--modal-accent: var(--wire-color-danger); --modal-accent: var(--wire-color-danger);
--modal-soft: var(--wire-color-danger-soft); --modal-soft: var(--wire-color-danger-soft);
--modal-contrast: var(--wire-color-on-danger); --modal-contrast: var(--wire-color-on-danger, white);
} }
.wire-modal__trigger, .wire-modal__trigger,
@@ -321,8 +353,35 @@ component Modal {
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 650; font-weight: 650;
cursor: pointer; cursor: pointer;
transition: opacity 150ms ease, transform 150ms ease, box-shadow 150ms ease;
} }
.wire-modal__trigger:hover {
opacity: 0.92;
}
.wire-modal__trigger:active {
transform: scale(0.97);
}
.wire-modal__trigger:focus-visible {
outline: none;
box-shadow: 0 0 0 3px color-mix(in srgb, var(--modal-accent) 40%, transparent);
}
/*
* Hidden by default so the SSR-rendered HTML never paints the layer before
* hydration runs. data-open on the wire-modal root is evaluated and
* serialized to a real true or false string at render time -- a plain
* bind, not the raw-expression data-show directive -- so this selector
* is correct on first paint with zero flash, with no dependency on
* client JS having run yet. The data-show attribute and client directive
* still run after hydration to keep things in sync for state changes,
* but visibility itself is driven by CSS here. visibility (rather than
* display) is used so the open and close transitions below can actually
* animate -- a box that starts at display: none has no prior frame to
* transition from.
*/
.wire-modal__layer { .wire-modal__layer {
position: fixed; position: fixed;
inset: 0; inset: 0;
@@ -331,6 +390,15 @@ component Modal {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 1rem; padding: 1rem;
visibility: hidden;
opacity: 0;
transition: opacity 180ms ease, visibility 0s linear 180ms;
}
.wire-modal[data-open="true"] .wire-modal__layer {
visibility: visible;
opacity: 1;
transition: opacity 180ms ease, visibility 0s linear 0s;
} }
.wire-modal[data-placement="top"] .wire-modal__layer { .wire-modal[data-placement="top"] .wire-modal__layer {
@@ -338,6 +406,12 @@ component Modal {
padding-top: clamp(1rem, 8vh, 5rem); padding-top: clamp(1rem, 8vh, 5rem);
} }
.wire-modal[data-scroll="page"] .wire-modal__layer {
align-items: flex-start;
overflow-y: auto;
padding: 2.5rem 1rem;
}
.wire-modal__backdrop { .wire-modal__backdrop {
position: absolute; position: absolute;
inset: 0; inset: 0;
@@ -370,6 +444,18 @@ component Modal {
0 1px 0 color-mix(in srgb, white 5%, transparent) inset, 0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
0 40px 110px color-mix(in srgb, black 38%, transparent); 0 40px 110px color-mix(in srgb, black 38%, transparent);
overflow: hidden; overflow: hidden;
opacity: 0;
transform: scale(0.96) translateY(10px);
transition: opacity 180ms ease, transform 220ms cubic-bezier(0.16, 1, 0.3, 1);
}
.wire-modal[data-open="true"] .wire-modal__panel {
opacity: 1;
transform: none;
}
.wire-modal[data-size="xs"] .wire-modal__panel {
width: min(19rem, calc(100vw - 2rem));
} }
.wire-modal[data-size="sm"] .wire-modal__panel { .wire-modal[data-size="sm"] .wire-modal__panel {
@@ -390,6 +476,10 @@ component Modal {
max-height: none; max-height: none;
} }
.wire-modal[data-scroll="page"] .wire-modal__panel {
max-height: none;
}
.wire-modal[data-variant="soft"] .wire-modal__panel { .wire-modal[data-variant="soft"] .wire-modal__panel {
background: background:
linear-gradient(145deg, var(--modal-soft), transparent 68%), linear-gradient(145deg, var(--modal-soft), transparent 68%),
@@ -447,6 +537,7 @@ component Modal {
} }
.wire-modal__heading-copy h2 { .wire-modal__heading-copy h2 {
color: inherit;
font-size: 1.08rem; font-size: 1.08rem;
font-weight: 650; font-weight: 650;
line-height: 1.3; line-height: 1.3;
@@ -462,32 +553,100 @@ component Modal {
color: color-mix(in srgb, currentColor 76%, transparent); color: color-mix(in srgb, currentColor 76%, transparent);
} }
/*
* padding is reset explicitly: an app-level `button { padding: … }` rule
* outranks the browser default, and 1rem of horizontal padding left this
* 2.15rem button with a ~2px content box -- which squeezed the icon to
* 0.4px wide and read as "the close button has no icon".
*/
.wire-modal__close { .wire-modal__close {
appearance: none; appearance: none;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex: 0 0 auto; flex: 0 0 auto;
width: 2.35rem; padding: 0;
height: 2.35rem; width: 2.15rem;
height: 2.15rem;
color: var(--wire-color-text-muted); color: var(--wire-color-text-muted);
background: var(--wire-color-surface-soft); background: transparent;
border: 1px solid var(--wire-color-border); border: 1px solid transparent;
border-radius: 0.75rem; border-radius: 9999px;
cursor: pointer; cursor: pointer;
transition: background 150ms ease, color 150ms ease, border-color 150ms ease, transform 150ms ease;
}
/*
* On a solid panel the background is the accent color, so the muted-grey
* default is close to invisible -- the dismiss affordance reads as
* missing rather than subtle. Derive it from the panel contrast color
* instead, and give it a faint ring so it is unmistakably a control.
*/
.wire-modal[data-variant="solid"] .wire-modal__close {
color: color-mix(in srgb, currentColor 82%, transparent);
border-color: color-mix(in srgb, currentColor 35%, transparent);
}
.wire-modal[data-variant="solid"] .wire-modal__close:hover {
color: currentColor;
background: color-mix(in srgb, black 18%, transparent);
border-color: color-mix(in srgb, currentColor 55%, transparent);
}
.wire-modal__close:hover {
color: var(--wire-color-text);
background: var(--wire-color-surface-soft);
} }
.wire-modal__close:hover,
.wire-modal__close:focus-visible { .wire-modal__close:focus-visible {
color: var(--modal-accent); color: var(--modal-accent);
border-color: color-mix(in srgb, var(--modal-accent) 34%, var(--wire-color-border)); border-color: color-mix(in srgb, var(--modal-accent) 45%, transparent);
outline: none; outline: none;
} }
.wire-modal__close:active {
transform: scale(0.92);
}
/* Never let the glyph be shrunk by the flex container. */
.wire-modal__close svg {
flex: 0 0 auto;
width: 1rem;
height: 1rem;
}
/*
* Slot content is authored by the host app, so the app global stylesheet
* styles it too. A bare element selector there (p { color: ... }) beats
* anything the panel merely *inherits*, which is how solid-variant modals
* ended up with muted grey body copy on a saturated accent background --
* unreadable, and worst exactly where contrast matters most (the
* destructive confirm). Setting the color on the body makes the panel
* choice explicit instead of leaving it to inheritance.
*
* NOTE: apostrophes are avoided in .wrn style comments on purpose -- the
* block scanner treats a quote as a string delimiter while it counts
* braces, so a stray one breaks parsing of the whole component.
*/
.wire-modal__body { .wire-modal__body {
flex: 1 1 auto; flex: 1 1 auto;
min-height: 0; min-height: 0;
padding: 1.4rem; padding: 1.4rem;
color: var(--wire-color-text);
}
/*
* :where() keeps this at the specificity of .wire-modal__body alone, so it
* outranks a global element selector but still yields to any class the app
* puts on its own slot content (an error message, a muted caption). A
* plain .wire-modal__body p list would have quietly overridden those.
*/
.wire-modal__body :where(p, li, dd, dt, h1, h2, h3, h4, h5, h6, span, label, code) {
color: inherit;
}
.wire-modal[data-variant="solid"] .wire-modal__body {
color: var(--modal-contrast);
} }
.wire-modal[data-scrollable="true"] .wire-modal__body { .wire-modal[data-scrollable="true"] .wire-modal__body {
@@ -548,6 +707,18 @@ component Modal {
border: 1px solid transparent; border: 1px solid transparent;
} }
/*
* On a solid-variant panel the panel background is also --modal-accent,
* so a plain primary button (same color) has no visible edge against it.
* Darken the fill slightly and add a light border so the button still
* reads as a distinct, clickable pill instead of blending into the panel.
*/
.wire-modal[data-variant="solid"] .wire-modal__button--primary {
background: color-mix(in srgb, black 18%, var(--modal-accent));
border-color: color-mix(in srgb, white 32%, transparent);
box-shadow: 0 1px 0 color-mix(in srgb, white 12%, transparent) inset;
}
.wire-modal__button:disabled { .wire-modal__button:disabled {
opacity: 0.55; opacity: 0.55;
cursor: not-allowed; cursor: not-allowed;
@@ -575,6 +746,7 @@ component Modal {
} }
.wire-modal__panel, .wire-modal__panel,
.wire-modal[data-size="xs"] .wire-modal__panel,
.wire-modal[data-size="sm"] .wire-modal__panel, .wire-modal[data-size="sm"] .wire-modal__panel,
.wire-modal[data-size="lg"] .wire-modal__panel, .wire-modal[data-size="lg"] .wire-modal__panel,
.wire-modal[data-size="xl"] .wire-modal__panel { .wire-modal[data-size="xl"] .wire-modal__panel {
@@ -599,10 +771,17 @@ component Modal {
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.wire-modal__button, .wire-modal__button,
.wire-modal__spinner { .wire-modal__spinner,
.wire-modal__layer,
.wire-modal__panel,
.wire-modal__close {
animation: none; animation: none;
transition: none; transition: none;
} }
.wire-modal__panel {
transform: none;
}
} }
} }
} }
+8 -1
View File
@@ -123,12 +123,13 @@ component Popover {
<section <section
class="wire-popover__panel" class="wire-popover__panel"
data-wrn-anchored="true"
data-show='{open || visible}' data-show='{open || visible}'
role="dialog" role="dialog"
aria-label='{title || triggerLabel}' aria-label='{title || triggerLabel}'
> >
{#if showArrow} {#if showArrow}
<span class="wire-popover__arrow" aria-hidden="true"></span> <span class="wire-popover__arrow" data-wrn-anchor-arrow="true" aria-hidden="true"></span>
{/if} {/if}
{#if title || description || icon || showClose} {#if title || description || icon || showClose}
@@ -471,7 +472,13 @@ component Popover {
color: color-mix(in srgb, currentColor 76%, transparent); color: color-mix(in srgb, currentColor 76%, transparent);
} }
/*
* padding is reset explicitly: an app-level `button { padding: ... }` rule
* outranks the browser default and crushes the icon inside this
* fixed-size button. Same trap as Modal and Drawer.
*/
.wire-popover__close { .wire-popover__close {
padding: 0;
appearance: none; appearance: none;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
-20
View File
@@ -1,20 +0,0 @@
component Table {
outputs {
sort(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
rowClick(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
}
props {
size: string = "default"
color: string = "primary"
caption: string = "Table"
columns: unknown[] = []
rows: unknown[] = []
striped: boolean = true
class: string = ""
}
view {
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--table {class}"><table><caption>{caption}</caption><thead><tr>{#each columns as column}<th>{column.label}</th>{/each}</tr></thead><tbody>{#each rows as row}<tr>{#each columns as column}<td>{row[column.key]}</td>{/each}</tr>{/each}</tbody></table><slot /></div>
}
}
+2 -1
View File
@@ -97,11 +97,12 @@ id: string = ""
<span <span
id='{id}' id='{id}'
class="wire-tooltip__content" class="wire-tooltip__content"
data-wrn-anchored="true"
data-show='{open || visible}' data-show='{open || visible}'
role="tooltip" role="tooltip"
> >
{#if showArrow} {#if showArrow}
<span class="wire-tooltip__arrow" aria-hidden="true"></span> <span class="wire-tooltip__arrow" data-wrn-anchor-arrow="true" aria-hidden="true"></span>
{/if} {/if}
{#if title} {#if title}
+6 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "@wrnexus/ui — part of the WrNexus framework.", "description": "@wrnexus/ui — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -35,6 +35,10 @@
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/index.js" "import": "./dist/index.js"
}, },
"./registry": {
"types": "./dist/registry.d.ts",
"import": "./dist/registry.js"
},
"./components/*": "./components/*", "./components/*": "./components/*",
"./component-catalog.json": "./component-catalog.json", "./component-catalog.json": "./component-catalog.json",
"./component-migrations.json": "./component-migrations.json", "./component-migrations.json": "./component-migrations.json",
@@ -42,7 +46,7 @@
"./ui.css": "./ui.css" "./ui.css": "./ui.css"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4" "@wrnexus/core": "^0.8.5"
}, },
"files": [ "files": [
"dist", "dist",
-22
View File
@@ -8037,28 +8037,6 @@
cursor: pointer; cursor: pointer;
} }
.wire-next--table {
width: 100%;
overflow: auto;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-md);
}
.wire-next--table table {
width: 100%;
border-collapse: collapse;
}
.wire-next--table caption,
.wire-next--table th,
.wire-next--table td {
padding: 0.75rem;
border-bottom: 1px solid var(--wire-color-border);
text-align: left;
}
.wire-next--table caption,
.wire-next--table th {
font-weight: 800;
}
@keyframes wire-shimmer { @keyframes wire-shimmer {
to { to {
background-position: -200% 0; background-position: -200% 0;
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Secure upload drivers, policies, client runtime, helper functions, and reusable upload components.", "description": "Secure upload drivers, policies, client runtime, helper functions, and reusable upload components.",
"license": "MIT", "license": "MIT",
@@ -42,9 +42,9 @@
"./components/*": "./components/*" "./components/*": "./components/*"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4", "@wrnexus/core": "^0.8.5",
"@wrnexus/plugin": "^0.8.4", "@wrnexus/plugin": "^0.8.5",
"@wrnexus/ui": "^0.8.4" "@wrnexus/ui": "^0.8.5"
}, },
"wrnexus": { "wrnexus": {
"plugin": { "plugin": {
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Shared server/browser schemas, validation helpers, form runtime, and reusable error components.", "description": "Shared server/browser schemas, validation helpers, form runtime, and reusable error components.",
"license": "MIT", "license": "MIT",
@@ -42,9 +42,9 @@
"./components/*": "./components/*" "./components/*": "./components/*"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.4", "@wrnexus/core": "^0.8.5",
"@wrnexus/plugin": "^0.8.4", "@wrnexus/plugin": "^0.8.5",
"@wrnexus/ui": "^0.8.4" "@wrnexus/ui": "^0.8.5"
}, },
"wrnexus": { "wrnexus": {
"plugin": { "plugin": {
+47 -47
View File
@@ -107,11 +107,11 @@
}, },
"packages/ai": { "packages/ai": {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.8.4", "version": "0.8.5",
}, },
"packages/auth": { "packages/auth": {
"name": "@wrnexus/auth", "name": "@wrnexus/auth",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
"@wrnexus/captcha": "workspace:*", "@wrnexus/captcha": "workspace:*",
@@ -133,7 +133,7 @@
}, },
"packages/authz": { "packages/authz": {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/db": "workspace:*", "@wrnexus/db": "workspace:*",
@@ -141,18 +141,18 @@
}, },
"packages/benchmark": { "packages/benchmark": {
"name": "@wrnexus/benchmark", "name": "@wrnexus/benchmark",
"version": "0.8.4", "version": "0.8.5",
}, },
"packages/cache": { "packages/cache": {
"name": "@wrnexus/cache", "name": "@wrnexus/cache",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/captcha": { "packages/captcha": {
"name": "@wrnexus/captcha", "name": "@wrnexus/captcha",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -167,7 +167,7 @@
}, },
"packages/cli": { "packages/cli": {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.8.4", "version": "0.8.5",
"bin": { "bin": {
"wrnexus": "src/index.ts", "wrnexus": "src/index.ts",
}, },
@@ -194,7 +194,7 @@
}, },
"packages/compiler": { "packages/compiler": {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/csr": "workspace:*", "@wrnexus/csr": "workspace:*",
"@wrnexus/store": "workspace:*", "@wrnexus/store": "workspace:*",
@@ -204,7 +204,7 @@
}, },
"packages/content": { "packages/content": {
"name": "@wrnexus/content", "name": "@wrnexus/content",
"version": "0.8.4", "version": "0.8.5",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -212,18 +212,18 @@
}, },
"packages/core": { "packages/core": {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.8.4", "version": "0.8.5",
}, },
"packages/csr": { "packages/csr": {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/db": { "packages/db": {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.8.4", "version": "0.8.5",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -231,7 +231,7 @@
}, },
"packages/dev-server": { "packages/dev-server": {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
"@wrnexus/cache": "workspace:*", "@wrnexus/cache": "workspace:*",
@@ -258,7 +258,7 @@
}, },
"packages/dev-toolbar": { "packages/dev-toolbar": {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.8.4", "version": "0.8.5",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -266,7 +266,7 @@
}, },
"packages/encryption": { "packages/encryption": {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
@@ -277,21 +277,21 @@
}, },
"packages/graphql": { "packages/graphql": {
"name": "@wrnexus/graphql", "name": "@wrnexus/graphql",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
}, },
}, },
"packages/helpers": { "packages/helpers": {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/i18n": { "packages/i18n": {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -305,7 +305,7 @@
}, },
"packages/identity": { "packages/identity": {
"name": "@wrnexus/identity", "name": "@wrnexus/identity",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/auth": "workspace:*", "@wrnexus/auth": "workspace:*",
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
@@ -314,7 +314,7 @@
}, },
"packages/image": { "packages/image": {
"name": "@wrnexus/image", "name": "@wrnexus/image",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
"@wrnexus/security": "workspace:*", "@wrnexus/security": "workspace:*",
@@ -334,7 +334,7 @@
}, },
"packages/jwt": { "packages/jwt": {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
@@ -345,7 +345,7 @@
}, },
"packages/language-server": { "packages/language-server": {
"name": "@wrnexus/language-server", "name": "@wrnexus/language-server",
"version": "0.8.4", "version": "0.8.5",
"bin": { "bin": {
"wrnexus-language-server": "src/server.ts", "wrnexus-language-server": "src/server.ts",
}, },
@@ -356,39 +356,39 @@
}, },
"packages/mcp": { "packages/mcp": {
"name": "@wrnexus/mcp", "name": "@wrnexus/mcp",
"version": "0.8.4", "version": "0.8.5",
"bin": { "bin": {
"wrnexus-mcp": "src/stdio.ts", "wrnexus-mcp": "src/stdio.ts",
}, },
}, },
"packages/mobile": { "packages/mobile": {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/native": "workspace:*", "@wrnexus/native": "workspace:*",
}, },
}, },
"packages/native": { "packages/native": {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.8.4", "version": "0.8.5",
}, },
"packages/oauth": { "packages/oauth": {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/jwt": "workspace:*", "@wrnexus/jwt": "workspace:*",
}, },
}, },
"packages/observability": { "packages/observability": {
"name": "@wrnexus/observability", "name": "@wrnexus/observability",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/playground": { "packages/playground": {
"name": "@wrnexus/playground", "name": "@wrnexus/playground",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/compiler": "workspace:*", "@wrnexus/compiler": "workspace:*",
"@wrnexus/syntax": "workspace:*", "@wrnexus/syntax": "workspace:*",
@@ -396,18 +396,18 @@
}, },
"packages/plugin": { "packages/plugin": {
"name": "@wrnexus/plugin", "name": "@wrnexus/plugin",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/syntax": "workspace:*", "@wrnexus/syntax": "workspace:*",
}, },
}, },
"packages/pubsub": { "packages/pubsub": {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.8.4", "version": "0.8.5",
}, },
"packages/pwa": { "packages/pwa": {
"name": "@wrnexus/pwa", "name": "@wrnexus/pwa",
"version": "0.8.4", "version": "0.8.5",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -415,18 +415,18 @@
}, },
"packages/queue": { "packages/queue": {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/reactive": { "packages/reactive": {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.8.4", "version": "0.8.5",
}, },
"packages/realtime": { "packages/realtime": {
"name": "@wrnexus/realtime", "name": "@wrnexus/realtime",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -440,7 +440,7 @@
}, },
"packages/router": { "packages/router": {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/compiler": "workspace:*", "@wrnexus/compiler": "workspace:*",
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
@@ -448,7 +448,7 @@
}, },
"packages/rpc": { "packages/rpc": {
"name": "@wrnexus/rpc", "name": "@wrnexus/rpc",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
@@ -463,14 +463,14 @@
}, },
"packages/security": { "packages/security": {
"name": "@wrnexus/security", "name": "@wrnexus/security",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/ssr": { "packages/ssr": {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/security": "workspace:*", "@wrnexus/security": "workspace:*",
@@ -479,11 +479,11 @@
}, },
"packages/store": { "packages/store": {
"name": "@wrnexus/store", "name": "@wrnexus/store",
"version": "0.8.4", "version": "0.8.5",
}, },
"packages/styles": { "packages/styles": {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -492,19 +492,19 @@
}, },
"packages/syntax": { "packages/syntax": {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.8.4", "version": "0.8.5",
}, },
"packages/test": { "packages/test": {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.8.4", "version": "0.8.5",
}, },
"packages/tracking": { "packages/tracking": {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.8.4", "version": "0.8.5",
}, },
"packages/typecheck": { "packages/typecheck": {
"name": "@wrnexus/typecheck", "name": "@wrnexus/typecheck",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/syntax": "workspace:*", "@wrnexus/syntax": "workspace:*",
"typescript": "^5.5.0", "typescript": "^5.5.0",
@@ -512,14 +512,14 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/uploader": { "packages/uploader": {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -533,7 +533,7 @@
}, },
"packages/validation": { "packages/validation": {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.8.4", "version": "0.8.5",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,184 @@
# Navigation component group — design
Date: 2026-08-07
Status: approved, not yet implemented
## Problem
The navigation group has nine components. Five of them — Nav, MegaMenu,
Scrollspy, Pagination, Stepper — are byte-identical scaffold stubs that differ
only in one CSS class name. Each renders `{#each items}<a href>{label}</a>{/each}`
and nothing else. MegaMenu has no panel, Scrollspy never observes scroll,
Stepper has no steps or progress, Pagination has no pages.
Of the remaining four, Breadcrumb is genuinely built (323 lines, own style
block). Navbar and Sidebar render but have no keyboard handling and keep their
styles in `ui.css`. Tabs works but is off-pattern in three ways: it is the only
component in the library using Tailwind utility classes rather than `wire-*` +
a local style block, it has no `outputs` block and fires raw `CustomEvent`s via
`dispatchEvent`, and it sets a roving `tabindex` with no `@keydown` handler at
all — which is worse than having no keyboard support, because the roving
tabindex makes every inactive tab unreachable by Tab while arrows do nothing.
## Scope
All nine components, delivered in three phases.
## Decisions
### Styling
Every navigation component gets a local `style {}` block using `wire-*`
classes. This matches DataTable, Toaster, Modal, Drawer, ContextMenu and
Breadcrumb — the 22 of 108 components that carry local styles are exactly the
recently built ones. Navbar and Sidebar styles move out of `ui.css` as part of
this work.
### Dropdowns: Nav vs MegaMenu
Both get dropdowns, of deliberately different kinds.
- **Nav** gets multi-level cascading submenus. It is the link bar, so nested
submenus are its job.
- **MegaMenu** gets a single-level rich panel: columns of grouped links with
headings, descriptions and icons. This is deliberate, not a shortcut. A mega
menu exists to show breadth flat so everything is one click away; nesting
inside the panel buries content behind hover-within-hover and is close to
unusable by keyboard and touch.
Neither reuses the existing Dropdown component: Dropdown is click-triggered and
has no nesting support.
**Depth limit.** There is no recursive-component precedent in this library, so
multi-level means fixed depth via nested `{#each}` loops — the pattern Navbar
(4 loops) and Sidebar (2 loops) already use. Depth is **3 levels**. Unlimited
nesting would require proving out component self-reference, which is out of
scope here.
### Roving focus lives in the runtime
Arrow-key roving focus is identical logic for Tabs, Nav, MegaMenu, Sidebar and
Stepper. It goes in the reactive runtime as a declarative attribute rather than
five near-identical client functions:
- `data-wrn-roving="horizontal|vertical|both"` on the container
- `[data-wrn-roving-item]` on each focusable child
The runtime owns arrow keys, Home/End, wrap-around, skip-disabled, and
maintenance of the roving `tabindex`. Components declare intent only.
This follows the modal-dialog focus work already in the runtime, and for the
same reason: a client function cannot hold focus state across callbacks,
because state written after the function returns is dropped.
### Scrollspy needs runtime support too
`data-wrn-scrollspy` backed by `IntersectionObserver`. It cannot be a client
function — the observer callback fires long after the function returns, and
that state write would be lost.
### Runtime budget
Roving focus is roughly 34k, scrollspy roughly 1.5k. The runtime is at 167k
against the 175k ceiling raised on 2026-08-07. This fits, but leaves little
room. The group after this one forces the decision about splitting the runtime
into loadable chunks so pages pay only for behaviour they use.
### Tabs URL mode
`mode="client" | "url"`. URL mode uses a query parameter (`?tab=value`) driven
by `history.pushState`, so content swaps with no page load and the back button
works.
Query parameter rather than hash: it is shareable, survives reload, and does
not collide with in-page anchors or with Scrollspy, which wants the hash.
Tabs also gets a panel transition on change.
### Sidebar composes Drawer
Sidebar does not reimplement off-canvas behaviour. Desktop renders a static
rail; mobile renders inside Drawer. This inherits Drawer's focus trap and
scroll lock rather than duplicating them.
### Cross-cutting requirements
- Icons on every menu item, tab, and step
- Dropdown arrows that animate on open
- Responsive behaviour per component: Nav collapses to a toggle, MegaMenu
stacks its panel, Tabs becomes a scrollable strip, Stepper goes vertical,
Pagination goes compact
- Full ARIA Authoring Practices patterns for each role, including correct
`aria-current` / `aria-selected` / `aria-expanded`
## Components
| Component | Work |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Nav | Build. Flat or multi-level (3 deep), horizontal/vertical, `items[{label,href,value,icon,badge,disabled,items}]`, `aria-current="page"`, roving focus, animated arrows, collapses on mobile. `outputs: select` |
| Tabs | Rewrite off Tailwind onto `wire-*` + local styles. Real `outputs {change, select}` replacing raw `dispatchEvent`. Roving + Home/End. `mode="client"\|"url"`. Panel transition. Fix the `<slot>` that renders regardless of active tab |
| Pagination | Build standalone. `page`/`pageSize`/`total`, compact + numbered styles, windowed page numbers. `outputs: change, previous, next` |
| Stepper | Build. `<ol>`/`<li>`, status derived from `active` index, horizontal/vertical, optionally clickable, `aria-current="step"`. Indexed named slots (`data-slot="step-0"`, `step-1`, …) for custom per-step content, falling back to built-in rendering |
| MegaMenu | Build. Trigger plus a single-level panel of link columns. Panel marked `data-wrn-anchored` so the existing clamp handles viewport containment. Hover and focus open, Escape closes, click-outside closes |
| Scrollspy | Build. Observes section ids, moves `aria-current` to the matching link |
| Navbar | Audit. Styles move to a local block, roving focus on the link group, keyboard for the mobile toggle |
| Sidebar | Audit and extend. Local styles, composes Drawer for mobile, single items / labelled groups / multi-level (3 deep), vertical roving |
| Breadcrumb | Audit only — already the strongest of the nine |
Component count stays at 108; all nine files already exist.
## Data flow
Props in, outputs out, no global state. Every component takes its data as props
and reports interaction through declared `outputs`.
## Error handling
`items` arrives as an HTML attribute and is frequently a JSON string, so:
- A non-array `items` renders empty rather than throwing
- Out-of-range `active` / `page` clamps to bounds
## Testing
- Per-component entries in `packages/ui/test/ui.test.ts` covering names,
declared outputs and props
- Showcase profiles for each so every component gets live demos
- Regenerate component reference, catalog, showcase, and the UI visual contract
- Each phase ends with `bun run check:production` green
## Phases
Each phase ends green, committed and pushed.
1. **Runtime and primitives** — roving-focus runtime, Nav, Pagination, Stepper
2. **Composed** — Tabs (rewrite, URL sync, animation), Sidebar (on Drawer),
MegaMenu
3. **Audit and polish** — Navbar, Breadcrumb, Scrollspy, responsive pass,
showcase generation
## Risks
- **Scrollspy and MegaMenu** are the only two needing new runtime behaviour.
MegaMenu leans on the anchored clamp, which could not be verified
interactively on 2026-08-07 because the browser pane was degraded
(screenshots timing out, `scrollIntoView` inert). Verify the pane works
before starting phase 2.
- **Tabs is a breaking change.** Replacing raw `CustomEvent`s with declared
outputs changes its public contract; anything listening for the old events
breaks. 0.8.5 shipped on 2026-08-07, so this needs a migration entry in
`packages/cli/src/update.ts`.
## Codebase constraints to respect
Hazards this codebase has already hit:
- No apostrophes in `.wrn` comments — the brace scanner breaks on them
- `/* */` only inside style blocks; `//` is not a CSS comment and silently eats
the following rule
- No deferred state writes in client functions; state written after the
function returns is dropped
- Never call a peer function after an application callback — the wrapper
flushes the entry-time snapshot
- No boolean attributes bound to loop variables
- Package components need explicit imports
+9 -9
View File
@@ -13,7 +13,7 @@
"packages/ui/components/BackToTop.wrn": "b12c56ec8b7aabad68cbdc47f4b3237e2c21a61398f0aa51f9add6229b7357cd", "packages/ui/components/BackToTop.wrn": "b12c56ec8b7aabad68cbdc47f4b3237e2c21a61398f0aa51f9add6229b7357cd",
"packages/ui/components/Badge.wrn": "6b55b4d85100d605cf168857aec1dc57da62c1f6a0a5cd6dd877023f24b189cc", "packages/ui/components/Badge.wrn": "6b55b4d85100d605cf168857aec1dc57da62c1f6a0a5cd6dd877023f24b189cc",
"packages/ui/components/Blockquote.wrn": "6016dd4450de7cbf5c3cb413599baa2e502321e8eedef54439a0df6d0aae8bcb", "packages/ui/components/Blockquote.wrn": "6016dd4450de7cbf5c3cb413599baa2e502321e8eedef54439a0df6d0aae8bcb",
"packages/ui/components/Breadcrumb.wrn": "8bf6239b95b382ce68f8411aac0d364f67a1c278dfbbf08f1501cd0482751e30", "packages/ui/components/Breadcrumb.wrn": "df27101d41cf018d55b6909e0399286a4661637140e341c8624615051d485142",
"packages/ui/components/ButtonGroup.wrn": "0d97fbeed531d1d8ef4b59531923b47b34100d0de71d7bf524b02c668920b3d8", "packages/ui/components/ButtonGroup.wrn": "0d97fbeed531d1d8ef4b59531923b47b34100d0de71d7bf524b02c668920b3d8",
"packages/ui/components/CTASection.wrn": "2d2241d4f1018cfd2f8ea1ddd5fa8c8fcb8d1bbb2d4e4498cb9f39ff47a3801c", "packages/ui/components/CTASection.wrn": "2d2241d4f1018cfd2f8ea1ddd5fa8c8fcb8d1bbb2d4e4498cb9f39ff47a3801c",
"packages/ui/components/Card.wrn": "4370df341235819b8a968a3f812209c2e6c29e4471d06578aaa5b577fb6e2dc8", "packages/ui/components/Card.wrn": "4370df341235819b8a968a3f812209c2e6c29e4471d06578aaa5b577fb6e2dc8",
@@ -61,14 +61,14 @@
"packages/ui/components/Map.wrn": "7b8a0d5412a464fd7cab8977d816542c506d8c5dab8230a3c984c2caee407c91", "packages/ui/components/Map.wrn": "7b8a0d5412a464fd7cab8977d816542c506d8c5dab8230a3c984c2caee407c91",
"packages/ui/components/MarketingSectionHeader.wrn": "7d6ffcc01a9331474475ea57ca58598be757abd9428b66bdfaf6f3603c5b145b", "packages/ui/components/MarketingSectionHeader.wrn": "7d6ffcc01a9331474475ea57ca58598be757abd9428b66bdfaf6f3603c5b145b",
"packages/ui/components/Marquee.wrn": "b2b35eedf3297ba12ab3776385a9f3eab001027648d87a2a1968f576eee6c025", "packages/ui/components/Marquee.wrn": "b2b35eedf3297ba12ab3776385a9f3eab001027648d87a2a1968f576eee6c025",
"packages/ui/components/MegaMenu.wrn": "4a084eaf6aae77bb9023d2f3589bc6b80119b9be63982a90a80f9d280cc9c0a5", "packages/ui/components/MegaMenu.wrn": "48f4ced485df0e8de5217630c8bdcec0b2317bf04e6b279399f2c93d98d56995",
"packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92", "packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92",
"packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364", "packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364",
"packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f", "packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f",
"packages/ui/components/Nav.wrn": "78f215c94caf68e0968449a23e23bd3409a689e0c52a6c1770aa8373c767d080", "packages/ui/components/Nav.wrn": "7dc456b879e9248bde267b986cdcb138b8f3127d98b51e27bc9708129953f5e2",
"packages/ui/components/Navbar.wrn": "e68f9d3643e500e43124e9c7a6d4c7f6722657377ea7313f3e3cf5093a81b360", "packages/ui/components/Navbar.wrn": "cf28cf4cdd23c85821d3302e6881115c2c14977e7cb7142d306cf559fea2a518",
"packages/ui/components/PageHeader.wrn": "0761235a4924eec09877be40b292d27b70db22d210be7d8e989493165c20df86", "packages/ui/components/PageHeader.wrn": "0761235a4924eec09877be40b292d27b70db22d210be7d8e989493165c20df86",
"packages/ui/components/Pagination.wrn": "9169e724f89992dacd10e9492a95438c8016affb39c0fd17a4f6fe26bd21d8b4", "packages/ui/components/Pagination.wrn": "9ceb74745b159150b015bbbb1974c68e14a7d4b92bd03491cb23b8855aa07983",
"packages/ui/components/PinInput.wrn": "5196f584de8d548a5dfa03688c3c95da3c948cead2662b05e927386ccea74299", "packages/ui/components/PinInput.wrn": "5196f584de8d548a5dfa03688c3c95da3c948cead2662b05e927386ccea74299",
"packages/ui/components/Popover.wrn": "167f6c476cf3114ac5062ecf7739bddd9b5a81b1d209396a3675067dad1577b2", "packages/ui/components/Popover.wrn": "167f6c476cf3114ac5062ecf7739bddd9b5a81b1d209396a3675067dad1577b2",
"packages/ui/components/PortalDashboard.wrn": "037d4300b59c7d60543abc7d4aba5c738efc143e9b61abc48aad0ddfcfe6845b", "packages/ui/components/PortalDashboard.wrn": "037d4300b59c7d60543abc7d4aba5c738efc143e9b61abc48aad0ddfcfe6845b",
@@ -77,19 +77,19 @@
"packages/ui/components/Radio.wrn": "0b2d38a5aee3e859280e4590fa1d509d789cc2239082054872fa342817735f1b", "packages/ui/components/Radio.wrn": "0b2d38a5aee3e859280e4590fa1d509d789cc2239082054872fa342817735f1b",
"packages/ui/components/RangeSlider.wrn": "21eb7a5df0ee2cb5e78f628eb920265998eb9f1a4933d4e5c57db0323fd66b41", "packages/ui/components/RangeSlider.wrn": "21eb7a5df0ee2cb5e78f628eb920265998eb9f1a4933d4e5c57db0323fd66b41",
"packages/ui/components/Rating.wrn": "a2d74dc748fc7d98892684c760518b87fa65411b1102e3611252b87245b3ffcb", "packages/ui/components/Rating.wrn": "a2d74dc748fc7d98892684c760518b87fa65411b1102e3611252b87245b3ffcb",
"packages/ui/components/Scrollspy.wrn": "bbe0788f63c2bc8850649c1dee78391d485a1c93876015bc536d838273d5fae4", "packages/ui/components/Scrollspy.wrn": "76d1c17580c8d460f1222ce97585bdcac9779141f2f30d5460c84ff9f75413d8",
"packages/ui/components/SearchBox.wrn": "315f54f1feaaa47a815d1e2d7a35b492f09fbfadfc6d37228e1009e19e0652ad", "packages/ui/components/SearchBox.wrn": "315f54f1feaaa47a815d1e2d7a35b492f09fbfadfc6d37228e1009e19e0652ad",
"packages/ui/components/Section.wrn": "21485de07d8bb986837a65c61c5dd01ba6c2f3d4e37d58c154a584c7b58de0ed", "packages/ui/components/Section.wrn": "21485de07d8bb986837a65c61c5dd01ba6c2f3d4e37d58c154a584c7b58de0ed",
"packages/ui/components/SectionHeader.wrn": "512cb96636ee1f0540a0f4b4c75603fc0fe7536e7ed7ed7dc82173a16f48a4d3", "packages/ui/components/SectionHeader.wrn": "512cb96636ee1f0540a0f4b4c75603fc0fe7536e7ed7ed7dc82173a16f48a4d3",
"packages/ui/components/Select.wrn": "7f046bb11b7c2470dae26d91a4b2261c66a40ae054d0e04c69b6748912d1e204", "packages/ui/components/Select.wrn": "7f046bb11b7c2470dae26d91a4b2261c66a40ae054d0e04c69b6748912d1e204",
"packages/ui/components/Sidebar.wrn": "05f4bd216ae8bfcee2460fe638e431174d5f3c8e10003f346644e5a2939e7ce5", "packages/ui/components/Sidebar.wrn": "03f1bbce75ca6d50ed940e62df39c539610e89eadd2064fe24abc7d5470f98bf",
"packages/ui/components/SplitHero.wrn": "70e843565ff869bcf413b11b6d9830b2e2bd229863a00904596f71e2dff0e76d", "packages/ui/components/SplitHero.wrn": "70e843565ff869bcf413b11b6d9830b2e2bd229863a00904596f71e2dff0e76d",
"packages/ui/components/StatsBar.wrn": "c7d1df3895f25b6d3a2e1012a18b97592c7f33e65418b880d486f20c2d490686", "packages/ui/components/StatsBar.wrn": "c7d1df3895f25b6d3a2e1012a18b97592c7f33e65418b880d486f20c2d490686",
"packages/ui/components/Stepper.wrn": "4fec0c91a9006319ec27f3198d1176acdfd93abf62d76159aa55c0879bb081d9", "packages/ui/components/Stepper.wrn": "09f216a44f888421091bd1bce760a076927500a573550d5b2b10c4f27f608e92",
"packages/ui/components/StrongPassword.wrn": "c7c5ef26ece6170dd7db9882f0dc98cb2e4607f1e5d43eb3fd39e5d15bbd3a2e", "packages/ui/components/StrongPassword.wrn": "c7c5ef26ece6170dd7db9882f0dc98cb2e4607f1e5d43eb3fd39e5d15bbd3a2e",
"packages/ui/components/StyledIcon.wrn": "4a4504e357dee9dd0418edbe85fc90b824ccdb3752123a2125da95b77bf0b336", "packages/ui/components/StyledIcon.wrn": "4a4504e357dee9dd0418edbe85fc90b824ccdb3752123a2125da95b77bf0b336",
"packages/ui/components/Switch.wrn": "ccb74599fab72b0d68b09a7f1f90b7732cb2f9cbed67a84219e897575ce30c28", "packages/ui/components/Switch.wrn": "ccb74599fab72b0d68b09a7f1f90b7732cb2f9cbed67a84219e897575ce30c28",
"packages/ui/components/Tabs.wrn": "8a1b98c7395b27b09f16f0ebdc14d3d84473b06023ec2b3ed0af495cc2f076bb", "packages/ui/components/Tabs.wrn": "4fa0c0854700532960043290700d7cf99663ec41692068101f0aae4c2b1a1181",
"packages/ui/components/TextLink.wrn": "30782039293eb36d63b7b3a4f32a71a47177a3a68c7e90184be7cf7b4385eb19", "packages/ui/components/TextLink.wrn": "30782039293eb36d63b7b3a4f32a71a47177a3a68c7e90184be7cf7b4385eb19",
"packages/ui/components/Textarea.wrn": "ddf0b4f124b2cf0c0ab3d820d3ac0085f7c20466e977231949be264cd0cee8cf", "packages/ui/components/Textarea.wrn": "ddf0b4f124b2cf0c0ab3d820d3ac0085f7c20466e977231949be264cd0cee8cf",
"packages/ui/components/TimePicker.wrn": "2e8e7a90f6b6069a07e7ffd2725ba1e1031e84d55a4f1254025befbb314fa695", "packages/ui/components/TimePicker.wrn": "2e8e7a90f6b6069a07e7ffd2725ba1e1031e84d55a4f1254025befbb314fa695",
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "wrnexus", "name": "wrnexus",
"version": "0.8.4", "version": "0.8.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wrnexus", "name": "wrnexus",
"version": "0.8.4", "version": "0.8.5",
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"dependencies": { "dependencies": {
"vscode-languageclient": "^10.1.0" "vscode-languageclient": "^10.1.0"
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "wrnexus", "name": "wrnexus",
"displayName": "WRNexus Language Support", "displayName": "WRNexus Language Support",
"description": "Complete WRNexus v0.8.3 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.", "description": "Complete WRNexus v0.8.3 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.",
"version": "0.8.4", "version": "0.8.5",
"publisher": "wrnexus", "publisher": "wrnexus",
"private": true, "private": true,
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
+1 -1
View File
@@ -1,4 +1,4 @@
// WRN editor extension source hash: 174fee91d6ec09c86f71f1204aeb46a20e5a563cc8af1484051d68dde01c8e10 // WRN editor extension source hash: 6afaf7958331407986761b491ad81d56458dcb162f56369a4cc29f86ff95d981
// WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728 // WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728
"use strict"; "use strict";
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
// WRN editor language server source hash: de46b5767d808fb99bb470ac9ccd0bc9504aa9ab40f0cf8ad9ec4e945303200d // WRN editor language server source hash: c4362f7edf117e0c403c9bce9435bc02ae0febd963edb4af9a18d2d74d87eb47
// WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72 // WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72
// @bun @bun-cjs // @bun @bun-cjs
(function(exports, require, module, __filename, __dirname) {var __create = Object.create; (function(exports, require, module, __filename, __dirname) {var __create = Object.create;
@@ -1,12 +1,13 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page MegaMenuDetail { page MegaMenuDetail {
layout = "showcase" layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary" state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Mega Menu" state playground_label = ctx.url.searchParams.get("pg_label") ?? "Mega Menu"
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"mega-menu-0-1\",\"key\":\"mega-menu-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#mega-menu-demo\",\"actionHref\":\"#mega-menu-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"mega-menu-0-2\",\"key\":\"mega-menu-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#mega-menu-demo\",\"actionHref\":\"#mega-menu-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]") state playground_icon = ctx.url.searchParams.get("pg_icon") ?? ""
state playground_active = ctx.url.searchParams.get("pg_active") ?? "" state playground_columns = JSON.parse(ctx.url.searchParams.get("pg_columns") ?? "[{\"id\":\"mega-menu-0-1\",\"key\":\"mega-menu-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#mega-menu-demo\",\"actionHref\":\"#mega-menu-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"mega-menu-0-2\",\"key\":\"mega-menu-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#mega-menu-demo\",\"actionHref\":\"#mega-menu-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal" state playground_footer = ctx.url.searchParams.get("pg_footer") ?? ""
state playground_defaultOpen = (ctx.url.searchParams.get("pg_defaultOpen") ?? "false") === "true"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Mega Menu" title = "Mega Menu"
@@ -21,7 +22,7 @@ page MegaMenuDetail {
<span class="showcase-eyebrow">Navigation component</span> <span class="showcase-eyebrow">Navigation component</span>
<h1>Mega Menu</h1> <h1>Mega Menu</h1>
<p>Theme-aware, responsive mega menu component.</p> <p>Theme-aware, responsive mega menu component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div> <div class="detail-badges"><span>8 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div> </div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
@@ -30,11 +31,12 @@ page MegaMenuDetail {
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><MegaMenu size='{playground_size}' color='{playground_color}' label='{playground_label}' items='{playground_items}' active='{playground_active}' orientation='{playground_orientation}' class='{playground_class}' /></div> <div class="playground-preview-source" data-playground-preview><MegaMenu color='{playground_color}' size='{playground_size}' label='{playground_label}' icon='{playground_icon}' columns='{playground_columns}' footer='{playground_footer}' defaultOpen='{playground_defaultOpen}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code"> <section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;MegaMenu <pre><code data-playground-code>&lt;MegaMenu
items='[ label="Mega Menu"
columns='[
<span>&#123;</span> <span>&#123;</span>
"id": "mega-menu-0-1", "id": "mega-menu-0-1",
"key": "mega-menu-0-1", "key": "mega-menu-0-1",
@@ -148,8 +150,8 @@ page MegaMenuDetail {
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div> <div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div> </div>
<form class="playground-controls" data-playground-form> <form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header> <header><div><span>Component props</span><strong>8 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-mega-menu-size"><span><strong>size</strong><small>string</small></span><select id="playground-mega-menu-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-mega-menu-color"><span><strong>color</strong><small>string</small></span><select id="playground-mega-menu-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-mega-menu-label"><span><strong>label</strong><small>string</small></span><input id="playground-mega-menu-label" name="pg_label" type="text" value="Mega Menu" /></label><label class="playground-field" for="playground-mega-menu-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-mega-menu-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[ <div class="playground-fields"><label class="playground-field" for="playground-mega-menu-color"><span><strong>color</strong><small>string</small></span><select id="playground-mega-menu-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-mega-menu-size"><span><strong>size</strong><small>string</small></span><select id="playground-mega-menu-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-mega-menu-label"><span><strong>label</strong><small>string</small></span><input id="playground-mega-menu-label" name="pg_label" type="text" value="Mega Menu" /></label><label class="playground-field" for="playground-mega-menu-icon"><span><strong>icon</strong><small>string</small></span><input id="playground-mega-menu-icon" name="pg_icon" type="text" value="" /></label><label class="playground-field" for="playground-mega-menu-columns"><span><strong>columns</strong><small>unknown[]</small></span><textarea id="playground-mega-menu-columns" name="pg_columns" rows="5" spellcheck="false" data-playground-json>[
{ {
"id": "mega-menu-0-1", "id": "mega-menu-0-1",
"key": "mega-menu-0-1", "key": "mega-menu-0-1",
@@ -256,7 +258,7 @@ page MegaMenuDetail {
"Standard" "Standard"
] ]
} }
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-mega-menu-active"><span><strong>active</strong><small>string</small></span><input id="playground-mega-menu-active" name="pg_active" type="text" value="" /></label><label class="playground-field" for="playground-mega-menu-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-mega-menu-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-mega-menu-class"><span><strong>class</strong><small>string</small></span><input id="playground-mega-menu-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div> ]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-mega-menu-footer"><span><strong>footer</strong><small>string</small></span><input id="playground-mega-menu-footer" name="pg_footer" type="text" value="" /></label><label class="playground-toggle" for="playground-mega-menu-defaultOpen"><span><strong>default Open</strong><small>boolean</small></span><input id="playground-mega-menu-defaultOpen" name="pg_defaultOpen" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-mega-menu-class"><span><strong>class</strong><small>string</small></span><input id="playground-mega-menu-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form> </form>
</div> </div>
</section> </section>
@@ -265,123 +267,49 @@ page MegaMenuDetail {
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list"> <section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div> <div><span class="demo-case-eyebrow">Recommended</span><h2>Grouped columns</h2><p>One level deep by design: a mega menu shows breadth flat so everything is one click away. Nesting inside the panel would bury content behind hover-within-hover. Use Nav when you want cascading submenus.</p></div>
<span class="demo-case-number">01</span> <span class="demo-case-number">01</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="mega-menu-demo-1" class="demo-canvas demo-canvas--1"> <div id="mega-menu-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><MegaMenu size="default" label="Mega Menu" items='[{"id":"mega-menu-0-1","key":"mega-menu-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#mega-menu-demo","actionHref":"#mega-menu-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"mega-menu-0-2","key":"mega-menu-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#mega-menu-demo","actionHref":"#mega-menu-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--1" /></div> <div class="demo-render"><MegaMenu size="default" label="Products" icon="icon-[lucide--box]" columns='[{"heading":"Platform","items":[{"label":"Runtime","href":"/runtime","description":"The browser half of the framework."},{"label":"Compiler","href":"/compiler","description":"WRN to JavaScript."}]},{"heading":"Tooling","items":[{"label":"CLI","href":"/cli","description":"Scaffold, build and deploy."},{"label":"Editor","href":"/editor","description":"Language server and syntax."}]}]' defaultOpen="false" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<section class="demo-code" aria-label="Production default usage"> <section class="demo-code" aria-label="Grouped columns usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;MegaMenu <pre><code>&lt;MegaMenu
items='[ label="Products"
icon="icon-[lucide--box]"
columns='[
<span>&#123;</span> <span>&#123;</span>
"id": "mega-menu-0-1", "heading": "Platform",
"key": "mega-menu-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#mega-menu-demo",
"actionHref": "#mega-menu-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [ "items": [
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option A", "label": "Runtime",
"value": "nested-a" "href": "/runtime",
"description": "The browser half of the framework."
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option B", "label": "Compiler",
"value": "nested-b" "href": "/compiler",
"description": "WRN to JavaScript."
<span>&#125;</span> <span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
] ]
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"id": "mega-menu-0-2", "heading": "Tooling",
"key": "mega-menu-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#mega-menu-demo",
"actionHref": "#mega-menu-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [ "items": [
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option A", "label": "CLI",
"value": "nested-a" "href": "/cli",
"description": "Scaffold, build and deploy."
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option B", "label": "Editor",
"value": "nested-b" "href": "/editor",
"description": "Language server and syntax."
<span>&#125;</span> <span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'
@@ -391,256 +319,57 @@ page MegaMenuDetail {
</article> </article>
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div> <div><span class="demo-case-eyebrow">Advanced</span><h2>With a footer note</h2><p>The panel is anchored, so the runtime clamp pulls it back inside the viewport instead of letting it hang off a wide layout. On a phone it stops floating and joins the flow.</p></div>
<span class="demo-case-number">02</span> <span class="demo-case-number">02</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="mega-menu-demo-2" class="demo-canvas demo-canvas--2"> <div id="mega-menu-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><MegaMenu size="sm" label="Compact example" items='[{"id":"mega-menu-1-1","key":"mega-menu-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#mega-menu-demo","actionHref":"#mega-menu-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"mega-menu-1-2","key":"mega-menu-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#mega-menu-demo","actionHref":"#mega-menu-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--2" /></div> <div class="demo-render"><MegaMenu size="sm" label="Solutions" icon="icon-[lucide--arrow-right]" columns='[{"heading":"By team","items":[{"label":"Engineering","href":"/eng","icon":"icon-[lucide--code]"},{"label":"Design","href":"/design","icon":"icon-[lucide--palette]"},{"label":"Support","href":"/support","icon":"icon-[lucide--life-buoy]"}]},{"heading":"By size","items":[{"label":"Startup","href":"/startup"},{"label":"Enterprise","href":"/enterprise"}]}]' footer="Not sure where to start? Talk to us." defaultOpen="false" class="showcase-instance showcase-instance--2" /></div>
</div> </div>
<section class="demo-code" aria-label="Compact application usage"> <section class="demo-code" aria-label="With a footer note usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;MegaMenu <pre><code>&lt;MegaMenu
size="sm" size="sm"
label="Compact example" label="Solutions"
items='[ icon="icon-[lucide--arrow-right]"
columns='[
<span>&#123;</span> <span>&#123;</span>
"id": "mega-menu-1-1", "heading": "By team",
"key": "mega-menu-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#mega-menu-demo",
"actionHref": "#mega-menu-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [ "items": [
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option A", "label": "Engineering",
"value": "nested-a" "href": "/eng",
"icon": "icon-[lucide--code]"
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option B", "label": "Design",
"value": "nested-b" "href": "/design",
<span>&#125;</span> "icon": "icon-[lucide--palette]"
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"label": "API reference", "label": "Support",
"href": "#api-reference" "href": "/support",
"icon": "icon-[lucide--life-buoy]"
<span>&#125;</span> <span>&#125;</span>
],
"values": [
"Included",
"Standard"
] ]
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"id": "mega-menu-1-2", "heading": "By size",
"key": "mega-menu-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#mega-menu-demo",
"actionHref": "#mega-menu-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [ "items": [
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option A", "label": "Startup",
"value": "nested-a" "href": "/startup"
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option B", "label": "Enterprise",
"value": "nested-b" "href": "/enterprise"
<span>&#125;</span> <span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="mega-menu-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><MegaMenu size="lg" label="Advanced example" items='[{"id":"mega-menu-2-1","key":"mega-menu-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#mega-menu-demo","actionHref":"#mega-menu-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"mega-menu-2-2","key":"mega-menu-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#mega-menu-demo","actionHref":"#mega-menu-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;MegaMenu
size="lg"
label="Advanced example"
items='[
<span>&#123;</span>
"id": "mega-menu-2-1",
"key": "mega-menu-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#mega-menu-demo",
"actionHref": "#mega-menu-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "mega-menu-2-2",
"key": "mega-menu-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#mega-menu-demo",
"actionHref": "#mega-menu-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'
footer="Not sure where to start? Talk to us."
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -664,10 +393,10 @@ if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload) console.log("select", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Mega Menu"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section> <section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Menu"</code></td><td>No</td></tr><tr><td><code>icon</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>columns</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>footer</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>defaultOpen</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main> </main>
<aside class="detail-aside"> <aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#mega-menu-demo-1">Production default</a><a href="#mega-menu-demo-2">Compact application</a><a href="#mega-menu-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div> <div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#mega-menu-demo-1">Grouped columns</a><a href="#mega-menu-demo-2">With a footer note</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
@@ -1,12 +1,14 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page NavDetail { page NavDetail {
layout = "showcase" layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary" state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Nav" state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"nav-0-1\",\"key\":\"nav-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#nav-demo\",\"actionHref\":\"#nav-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"nav-0-2\",\"key\":\"nav-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#nav-demo\",\"actionHref\":\"#nav-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]") state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"nav-0-1\",\"key\":\"nav-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#nav-demo\",\"actionHref\":\"#nav-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"nav-0-2\",\"key\":\"nav-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#nav-demo\",\"actionHref\":\"#nav-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_active = ctx.url.searchParams.get("pg_active") ?? "" state playground_active = ctx.url.searchParams.get("pg_active") ?? ""
state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal" state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Nav"
state playground_collapsible = (ctx.url.searchParams.get("pg_collapsible") ?? "true") === "true"
state playground_toggleLabel = ctx.url.searchParams.get("pg_toggleLabel") ?? "Nav"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Nav" title = "Nav"
@@ -21,16 +23,16 @@ page NavDetail {
<span class="showcase-eyebrow">Navigation component</span> <span class="showcase-eyebrow">Navigation component</span>
<h1>Nav</h1> <h1>Nav</h1>
<p>Theme-aware, responsive nav component.</p> <p>Theme-aware, responsive nav component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>2 outputs</span><span>Theme ready</span><span>Responsive</span></div> <div class="detail-badges"><span>9 props</span><span>1 slots</span><span>1 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div> </div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Nav" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="select,change"> <section id="playground" class="detail-section playground-section" data-playground data-playground-component="Nav" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="select">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Nav</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div> <div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Nav</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Nav size='{playground_size}' color='{playground_color}' label='{playground_label}' items='{playground_items}' active='{playground_active}' orientation='{playground_orientation}' class='{playground_class}' /></div> <div class="playground-preview-source" data-playground-preview><Nav color='{playground_color}' size='{playground_size}' items='{playground_items}' active='{playground_active}' orientation='{playground_orientation}' label='{playground_label}' collapsible='{playground_collapsible}' toggleLabel='{playground_toggleLabel}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code"> <section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Nav <pre><code data-playground-code>&lt;Nav
@@ -142,14 +144,16 @@ page NavDetail {
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'
label="Nav"
toggleLabel="Nav"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div> <div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div> </div>
<form class="playground-controls" data-playground-form> <form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header> <header><div><span>Component props</span><strong>9 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-nav-size"><span><strong>size</strong><small>string</small></span><select id="playground-nav-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-nav-color"><span><strong>color</strong><small>string</small></span><select id="playground-nav-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-nav-label"><span><strong>label</strong><small>string</small></span><input id="playground-nav-label" name="pg_label" type="text" value="Nav" /></label><label class="playground-field" for="playground-nav-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-nav-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[ <div class="playground-fields"><label class="playground-field" for="playground-nav-color"><span><strong>color</strong><small>string</small></span><select id="playground-nav-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-nav-size"><span><strong>size</strong><small>string</small></span><select id="playground-nav-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-nav-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-nav-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
{ {
"id": "nav-0-1", "id": "nav-0-1",
"key": "nav-0-1", "key": "nav-0-1",
@@ -256,7 +260,7 @@ page NavDetail {
"Standard" "Standard"
] ]
} }
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-nav-active"><span><strong>active</strong><small>string</small></span><input id="playground-nav-active" name="pg_active" type="text" value="" /></label><label class="playground-field" for="playground-nav-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-nav-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-nav-class"><span><strong>class</strong><small>string</small></span><input id="playground-nav-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div> ]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-nav-active"><span><strong>active</strong><small>string</small></span><input id="playground-nav-active" name="pg_active" type="text" value="" /></label><label class="playground-field" for="playground-nav-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-nav-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-nav-label"><span><strong>label</strong><small>string</small></span><input id="playground-nav-label" name="pg_label" type="text" value="Nav" /></label><label class="playground-toggle" for="playground-nav-collapsible"><span><strong>collapsible</strong><small>boolean</small></span><input id="playground-nav-collapsible" name="pg_collapsible" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-nav-toggleLabel"><span><strong>toggle Label</strong><small>string</small></span><input id="playground-nav-toggleLabel" name="pg_toggleLabel" type="text" value="Nav" /></label><label class="playground-field" for="playground-nav-class"><span><strong>class</strong><small>string</small></span><input id="playground-nav-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form> </form>
</div> </div>
</section> </section>
@@ -265,404 +269,163 @@ page NavDetail {
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list"> <section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div> <div><span class="demo-case-eyebrow">Recommended</span><h2>Horizontal links</h2><p>A flat link bar. The active item is marked with aria-current, and the arrow keys move between items.</p></div>
<span class="demo-case-number">01</span> <span class="demo-case-number">01</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="nav-demo-1" class="demo-canvas demo-canvas--1"> <div id="nav-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Nav size="default" label="Nav" items='[{"id":"nav-0-1","key":"nav-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#nav-demo","actionHref":"#nav-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"nav-0-2","key":"nav-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#nav-demo","actionHref":"#nav-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--1" /></div> <div class="demo-render"><Nav size="default" items='[{"label":"Home","href":"/","value":"home","icon":"icon-[lucide--house]"},{"label":"Inbox","href":"/inbox","value":"inbox","badge":"9"},{"label":"Reports","href":"/reports","value":"reports"},{"label":"Archive","href":"/archive","value":"archive","disabled":true}]' active="inbox" label="Nav" collapsible="true" toggleLabel="Nav" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<section class="demo-code" aria-label="Production default usage"> <section class="demo-code" aria-label="Horizontal links usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Nav <pre><code>&lt;Nav
items='[ items='[
<span>&#123;</span> <span>&#123;</span>
"id": "nav-0-1", "label": "Home",
"key": "nav-0-1", "href": "/",
"value": "primary", "value": "home",
"label": "Primary workflow", "icon": "icon-[lucide--house]"
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#nav-demo",
"actionHref": "#nav-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"id": "nav-0-2", "label": "Inbox",
"key": "nav-0-2", "href": "/inbox",
"value": "secondary", "value": "inbox",
"label": "Additional option", "badge": "9"
"title": "Supporting example", <span>&#125;</span>,
"description": "A clean default configuration for everyday product interfaces.", <span>&#123;</span>
"text": "Another configurable message.", "label": "Reports",
"href": "#nav-demo", "href": "/reports",
"actionHref": "#nav-demo", "value": "reports"
"actionLabel": "View details", <span>&#125;</span>,
"icon": "icon-[lucide--sparkles]", <span>&#123;</span>
"iconClass": "icon-[lucide--sparkles]", "label": "Archive",
"variant": "primary", "href": "/archive",
"status": "Active", "value": "archive",
"time": "09:30", "disabled": true
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span> <span>&#125;</span>
]' ]'
active="inbox"
label="Nav"
toggleLabel="Nav"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
</article> </article>
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div> <div><span class="demo-case-eyebrow">Advanced</span><h2>Nested submenus</h2><p>An item carrying its own items array opens a submenu on hover or focus, to a maximum of three levels. On a phone the submenus stack inline instead of floating, because a hover-opened overlay is unreachable on touch.</p></div>
<span class="demo-case-number">02</span> <span class="demo-case-number">02</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="nav-demo-2" class="demo-canvas demo-canvas--2"> <div id="nav-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Nav size="sm" label="Compact example" items='[{"id":"nav-1-1","key":"nav-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#nav-demo","actionHref":"#nav-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"nav-1-2","key":"nav-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#nav-demo","actionHref":"#nav-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--2" /></div> <div class="demo-render"><Nav size="sm" items='[{"label":"Home","href":"/","value":"home"},{"label":"Products","value":"products","items":[{"label":"Overview","href":"/p","value":"p-overview"},{"label":"Platform","value":"p-platform","items":[{"label":"Runtime","href":"/p/runtime","value":"runtime"},{"label":"Compiler","href":"/p/compiler","value":"compiler"}]}]}]' active="p-overview" label="Compact example" collapsible="true" toggleLabel="Compact example" class="showcase-instance showcase-instance--2" /></div>
</div> </div>
<section class="demo-code" aria-label="Compact application usage"> <section class="demo-code" aria-label="Nested submenus usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Nav <pre><code>&lt;Nav
size="sm" size="sm"
label="Compact example"
items='[ items='[
<span>&#123;</span> <span>&#123;</span>
"id": "nav-1-1", "label": "Home",
"key": "nav-1-1", "href": "/",
"value": "primary", "value": "home"
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#nav-demo",
"actionHref": "#nav-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"id": "nav-1-2", "label": "Products",
"key": "nav-1-2", "value": "products",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#nav-demo",
"actionHref": "#nav-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [ "items": [
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option A", "label": "Overview",
"value": "nested-a" "href": "/p",
"value": "p-overview"
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option B", "label": "Platform",
"value": "nested-b" "value": "p-platform",
"items": [
<span>&#123;</span>
"label": "Runtime",
"href": "/p/runtime",
"value": "runtime"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Compiler",
"href": "/p/compiler",
"value": "compiler"
<span>&#125;</span>
]
<span>&#125;</span> <span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'
active="p-overview"
label="Compact example"
toggleLabel="Compact example"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
</article> </article>
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div> <div><span class="demo-case-eyebrow">Recommended</span><h2>Vertical rail</h2><p>Vertical orientation switches the arrow keys to up and down.</p></div>
<span class="demo-case-number">03</span> <span class="demo-case-number">03</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="nav-demo-3" class="demo-canvas demo-canvas--3"> <div id="nav-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Nav size="lg" label="Advanced example" items='[{"id":"nav-2-1","key":"nav-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#nav-demo","actionHref":"#nav-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"nav-2-2","key":"nav-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#nav-demo","actionHref":"#nav-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' class="showcase-instance showcase-instance--3" /></div> <div class="demo-render"><Nav size="lg" items='[{"label":"Dashboard","href":"/","value":"dash","icon":"icon-[lucide--gauge]"},{"label":"Team","href":"/team","value":"team","icon":"icon-[lucide--users]"},{"label":"Settings","href":"/settings","value":"settings","icon":"icon-[lucide--settings]"}]' active="team" orientation="vertical" label="Advanced example" collapsible="false" toggleLabel="Advanced example" class="showcase-instance showcase-instance--3" /></div>
</div> </div>
<section class="demo-code" aria-label="Rich configuration usage"> <section class="demo-code" aria-label="Vertical rail usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Nav <pre><code>&lt;Nav
size="lg" size="lg"
label="Advanced example"
items='[ items='[
<span>&#123;</span> <span>&#123;</span>
"id": "nav-2-1", "label": "Dashboard",
"key": "nav-2-1", "href": "/",
"value": "primary", "value": "dash",
"label": "Advanced workflow", "icon": "icon-[lucide--gauge]"
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#nav-demo",
"actionHref": "#nav-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"id": "nav-2-2", "label": "Team",
"key": "nav-2-2", "href": "/team",
"value": "secondary", "value": "team",
"label": "Additional option", "icon": "icon-[lucide--users]"
"title": "Supporting example", <span>&#125;</span>,
"description": "A richer configuration with more supporting information and actions.", <span>&#123;</span>
"text": "Another configurable message.", "label": "Settings",
"href": "#nav-demo", "href": "/settings",
"actionHref": "#nav-demo", "value": "settings",
"actionLabel": "Explore workflow", "icon": "icon-[lucide--settings]"
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span> <span>&#125;</span>
]' ]'
active="team"
orientation="vertical"
label="Advanced example"
collapsible="false"
toggleLabel="Advanced example"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
</article></div></section> </article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Nav <section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Nav
@select='console.log(payload)' @select='console.log(payload)'
@change='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs" /&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Nav\"], [data-component=\"Nav\"]") const component = document.querySelector("[data-ui-component=\"Nav\"], [data-component=\"Nav\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload) console.log("select", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Nav"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section> <section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Main"</code></td><td>No</td></tr><tr><td><code>collapsible</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>toggleLabel</code></td><td>string</td><td><code>"Menu"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main> </main>
<aside class="detail-aside"> <aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#nav-demo-1">Production default</a><a href="#nav-demo-2">Compact application</a><a href="#nav-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div> <div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#nav-demo-1">Horizontal links</a><a href="#nav-demo-2">Nested submenus</a><a href="#nav-demo-3">Vertical rail</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
@@ -1,12 +1,17 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page PaginationDetail { page PaginationDetail {
layout = "showcase" layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary" state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_page = Number(ctx.url.searchParams.get("pg_page") ?? "3")
state playground_pageSize = Number(ctx.url.searchParams.get("pg_pageSize") ?? "3")
state playground_total = Number(ctx.url.searchParams.get("pg_total") ?? "3")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "compact"
state playground_siblingCount = Number(ctx.url.searchParams.get("pg_siblingCount") ?? "3")
state playground_showSummary = (ctx.url.searchParams.get("pg_showSummary") ?? "true") === "true"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Pagination" state playground_label = ctx.url.searchParams.get("pg_label") ?? "Pagination"
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"pagination-0-1\",\"key\":\"pagination-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#pagination-demo\",\"actionHref\":\"#pagination-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"pagination-0-2\",\"key\":\"pagination-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#pagination-demo\",\"actionHref\":\"#pagination-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]") state playground_previousLabel = ctx.url.searchParams.get("pg_previousLabel") ?? "Pagination"
state playground_active = ctx.url.searchParams.get("pg_active") ?? "" state playground_nextLabel = ctx.url.searchParams.get("pg_nextLabel") ?? "Pagination"
state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Pagination" title = "Pagination"
@@ -21,7 +26,7 @@ page PaginationDetail {
<span class="showcase-eyebrow">Navigation component</span> <span class="showcase-eyebrow">Navigation component</span>
<h1>Pagination</h1> <h1>Pagination</h1>
<p>Theme-aware, responsive pagination component.</p> <p>Theme-aware, responsive pagination component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div> <div class="detail-badges"><span>12 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div> </div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
@@ -30,233 +35,24 @@ page PaginationDetail {
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Pagination size='{playground_size}' color='{playground_color}' label='{playground_label}' items='{playground_items}' active='{playground_active}' orientation='{playground_orientation}' class='{playground_class}' /></div> <div class="playground-preview-source" data-playground-preview><Pagination color='{playground_color}' size='{playground_size}' page='{playground_page}' pageSize='{playground_pageSize}' total='{playground_total}' variant='{playground_variant}' siblingCount='{playground_siblingCount}' showSummary='{playground_showSummary}' label='{playground_label}' previousLabel='{playground_previousLabel}' nextLabel='{playground_nextLabel}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code"> <section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Pagination <pre><code data-playground-code>&lt;Pagination
items='[ page="3"
<span>&#123;</span> pageSize="3"
"id": "pagination-0-1", total="3"
"key": "pagination-0-1", siblingCount="3"
"value": "primary", previousLabel="Pagination"
"label": "Primary workflow", nextLabel="Pagination"
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#pagination-demo",
"actionHref": "#pagination-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "pagination-0-2",
"key": "pagination-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#pagination-demo",
"actionHref": "#pagination-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div> <div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div> </div>
<form class="playground-controls" data-playground-form> <form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header> <header><div><span>Component props</span><strong>12 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-pagination-size"><span><strong>size</strong><small>string</small></span><select id="playground-pagination-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-pagination-color"><span><strong>color</strong><small>string</small></span><select id="playground-pagination-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-pagination-label"><span><strong>label</strong><small>string</small></span><input id="playground-pagination-label" name="pg_label" type="text" value="Pagination" /></label><label class="playground-field" for="playground-pagination-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-pagination-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[ <div class="playground-fields"><label class="playground-field" for="playground-pagination-color"><span><strong>color</strong><small>string</small></span><select id="playground-pagination-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-pagination-size"><span><strong>size</strong><small>string</small></span><select id="playground-pagination-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-pagination-page"><span><strong>page</strong><small>number</small></span><input id="playground-pagination-page" name="pg_page" type="number" value="3" /></label><label class="playground-field" for="playground-pagination-pageSize"><span><strong>page Size</strong><small>number</small></span><input id="playground-pagination-pageSize" name="pg_pageSize" type="number" value="3" /></label><label class="playground-field" for="playground-pagination-total"><span><strong>total</strong><small>number</small></span><input id="playground-pagination-total" name="pg_total" type="number" value="3" /></label><label class="playground-field" for="playground-pagination-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-pagination-variant" name="pg_variant" type="text" value="compact" /></label><label class="playground-field" for="playground-pagination-siblingCount"><span><strong>sibling Count</strong><small>number</small></span><input id="playground-pagination-siblingCount" name="pg_siblingCount" type="number" value="3" /></label><label class="playground-toggle" for="playground-pagination-showSummary"><span><strong>show Summary</strong><small>boolean</small></span><input id="playground-pagination-showSummary" name="pg_showSummary" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-pagination-label"><span><strong>label</strong><small>string</small></span><input id="playground-pagination-label" name="pg_label" type="text" value="Pagination" /></label><label class="playground-field" for="playground-pagination-previousLabel"><span><strong>previous Label</strong><small>string</small></span><input id="playground-pagination-previousLabel" name="pg_previousLabel" type="text" value="Pagination" /></label><label class="playground-field" for="playground-pagination-nextLabel"><span><strong>next Label</strong><small>string</small></span><input id="playground-pagination-nextLabel" name="pg_nextLabel" type="text" value="Pagination" /></label><label class="playground-field" for="playground-pagination-class"><span><strong>class</strong><small>string</small></span><input id="playground-pagination-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
{
"id": "pagination-0-1",
"key": "pagination-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#pagination-demo",
"actionHref": "#pagination-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
{
"label": "Nested option A",
"value": "nested-a"
},
{
"label": "Nested option B",
"value": "nested-b"
}
],
"links": [
{
"label": "Documentation",
"href": "#documentation"
},
{
"label": "API reference",
"href": "#api-reference"
}
],
"values": [
"Included",
"Standard"
]
},
{
"id": "pagination-0-2",
"key": "pagination-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#pagination-demo",
"actionHref": "#pagination-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
{
"label": "Nested option A",
"value": "nested-a"
},
{
"label": "Nested option B",
"value": "nested-b"
}
],
"links": [
{
"label": "Documentation",
"href": "#documentation"
},
{
"label": "API reference",
"href": "#api-reference"
}
],
"values": [
"Included",
"Standard"
]
}
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-pagination-active"><span><strong>active</strong><small>string</small></span><input id="playground-pagination-active" name="pg_active" type="text" value="" /></label><label class="playground-field" for="playground-pagination-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-pagination-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-pagination-class"><span><strong>class</strong><small>string</small></span><input id="playground-pagination-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form> </form>
</div> </div>
</section> </section>
@@ -265,382 +61,75 @@ page PaginationDetail {
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list"> <section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div> <div><span class="demo-case-eyebrow">Recommended</span><h2>Compact arrows</h2><p>The default: previous and next with a page counter, and a summary of the range in view.</p></div>
<span class="demo-case-number">01</span> <span class="demo-case-number">01</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="pagination-demo-1" class="demo-canvas demo-canvas--1"> <div id="pagination-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Pagination size="default" label="Pagination" items='[{"id":"pagination-0-1","key":"pagination-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#pagination-demo","actionHref":"#pagination-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"pagination-0-2","key":"pagination-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#pagination-demo","actionHref":"#pagination-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--1" /></div> <div class="demo-render"><Pagination size="default" page="2" pageSize="10" total="137" variant="compact" siblingCount="3" showSummary="true" label="Pagination" previousLabel="Pagination" nextLabel="Pagination" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<section class="demo-code" aria-label="Production default usage"> <section class="demo-code" aria-label="Compact arrows usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Pagination <pre><code>&lt;Pagination
items='[ page="2"
<span>&#123;</span> total="137"
"id": "pagination-0-1", siblingCount="3"
"key": "pagination-0-1", previousLabel="Pagination"
"value": "primary", nextLabel="Pagination"
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#pagination-demo",
"actionHref": "#pagination-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "pagination-0-2",
"key": "pagination-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#pagination-demo",
"actionHref": "#pagination-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
</article> </article>
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div> <div><span class="demo-case-eyebrow">Recommended</span><h2>Numbered pages</h2><p>Page numbers windowed around the current page with ellipsis gaps, so a large set never renders hundreds of buttons.</p></div>
<span class="demo-case-number">02</span> <span class="demo-case-number">02</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="pagination-demo-2" class="demo-canvas demo-canvas--2"> <div id="pagination-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Pagination size="sm" label="Compact example" items='[{"id":"pagination-1-1","key":"pagination-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#pagination-demo","actionHref":"#pagination-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"pagination-1-2","key":"pagination-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#pagination-demo","actionHref":"#pagination-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--2" /></div> <div class="demo-render"><Pagination size="sm" page="5" pageSize="10" total="200" variant="numbered" siblingCount="1" showSummary="true" label="Compact example" previousLabel="Compact example" nextLabel="Compact example" class="showcase-instance showcase-instance--2" /></div>
</div> </div>
<section class="demo-code" aria-label="Compact application usage"> <section class="demo-code" aria-label="Numbered pages usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Pagination <pre><code>&lt;Pagination
size="sm" size="sm"
page="5"
total="200"
variant="numbered"
label="Compact example" label="Compact example"
items='[ previousLabel="Compact example"
<span>&#123;</span> nextLabel="Compact example"
"id": "pagination-1-1",
"key": "pagination-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#pagination-demo",
"actionHref": "#pagination-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "pagination-1-2",
"key": "pagination-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#pagination-demo",
"actionHref": "#pagination-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
</article> </article>
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div> <div><span class="demo-case-eyebrow">Advanced</span><h2>Wider window</h2><p>siblingCount widens how many pages sit either side of the current one.</p></div>
<span class="demo-case-number">03</span> <span class="demo-case-number">03</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="pagination-demo-3" class="demo-canvas demo-canvas--3"> <div id="pagination-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Pagination size="lg" label="Advanced example" items='[{"id":"pagination-2-1","key":"pagination-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#pagination-demo","actionHref":"#pagination-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"pagination-2-2","key":"pagination-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#pagination-demo","actionHref":"#pagination-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' class="showcase-instance showcase-instance--3" /></div> <div class="demo-render"><Pagination size="lg" page="8" pageSize="25" total="900" variant="numbered" siblingCount="2" showSummary="true" label="Advanced example" previousLabel="Advanced example" nextLabel="Advanced example" class="showcase-instance showcase-instance--3" /></div>
</div> </div>
<section class="demo-code" aria-label="Rich configuration usage"> <section class="demo-code" aria-label="Wider window usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Pagination <pre><code>&lt;Pagination
size="lg" size="lg"
page="8"
pageSize="25"
total="900"
variant="numbered"
siblingCount="2"
label="Advanced example" label="Advanced example"
items='[ previousLabel="Advanced example"
<span>&#123;</span> nextLabel="Advanced example"
"id": "pagination-2-1",
"key": "pagination-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#pagination-demo",
"actionHref": "#pagination-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "pagination-2-2",
"key": "pagination-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#pagination-demo",
"actionHref": "#pagination-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -664,10 +153,10 @@ if (component) registerOutputHandler(component, "previous", (payload) =&gt; <spa
if (component) registerOutputHandler(component, "next", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "next", (payload) =&gt; <span>&#123;</span>
console.log("next", payload) console.log("next", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Pagination"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section> <section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>page</code></td><td>number</td><td><code>1</code></td><td>No</td></tr><tr><td><code>pageSize</code></td><td>number</td><td><code>10</code></td><td>No</td></tr><tr><td><code>total</code></td><td>number</td><td><code>0</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"compact"</code></td><td>No</td></tr><tr><td><code>siblingCount</code></td><td>number</td><td><code>1</code></td><td>No</td></tr><tr><td><code>showSummary</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Pagination"</code></td><td>No</td></tr><tr><td><code>previousLabel</code></td><td>string</td><td><code>"Previous"</code></td><td>No</td></tr><tr><td><code>nextLabel</code></td><td>string</td><td><code>"Next"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main> </main>
<aside class="detail-aside"> <aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#pagination-demo-1">Production default</a><a href="#pagination-demo-2">Compact application</a><a href="#pagination-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div> <div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#pagination-demo-1">Compact arrows</a><a href="#pagination-demo-2">Numbered pages</a><a href="#pagination-demo-3">Wider window</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
@@ -1,12 +1,12 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page ScrollspyDetail { page ScrollspyDetail {
layout = "showcase" layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary" state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Scrollspy" state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"scrollspy-0-1\",\"key\":\"scrollspy-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#scrollspy-demo\",\"actionHref\":\"#scrollspy-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"scrollspy-0-2\",\"key\":\"scrollspy-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#scrollspy-demo\",\"actionHref\":\"#scrollspy-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]") state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"scrollspy-0-1\",\"key\":\"scrollspy-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#scrollspy-demo\",\"actionHref\":\"#scrollspy-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"scrollspy-0-2\",\"key\":\"scrollspy-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#scrollspy-demo\",\"actionHref\":\"#scrollspy-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_active = ctx.url.searchParams.get("pg_active") ?? "" state playground_active = ctx.url.searchParams.get("pg_active") ?? ""
state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal" state playground_label = ctx.url.searchParams.get("pg_label") ?? "Scrollspy"
state playground_heading = ctx.url.searchParams.get("pg_heading") ?? ""
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Scrollspy" title = "Scrollspy"
@@ -30,7 +30,7 @@ page ScrollspyDetail {
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Scrollspy size='{playground_size}' color='{playground_color}' label='{playground_label}' items='{playground_items}' active='{playground_active}' orientation='{playground_orientation}' class='{playground_class}' /></div> <div class="playground-preview-source" data-playground-preview><Scrollspy color='{playground_color}' size='{playground_size}' items='{playground_items}' active='{playground_active}' label='{playground_label}' heading='{playground_heading}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code"> <section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Scrollspy <pre><code data-playground-code>&lt;Scrollspy
@@ -142,6 +142,7 @@ page ScrollspyDetail {
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'
label="Scrollspy"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <div class="playground-status" data-playground-status aria-live="polite"></div>
@@ -149,7 +150,7 @@ page ScrollspyDetail {
</div> </div>
<form class="playground-controls" data-playground-form> <form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header> <header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-scrollspy-size"><span><strong>size</strong><small>string</small></span><select id="playground-scrollspy-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-scrollspy-color"><span><strong>color</strong><small>string</small></span><select id="playground-scrollspy-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-scrollspy-label"><span><strong>label</strong><small>string</small></span><input id="playground-scrollspy-label" name="pg_label" type="text" value="Scrollspy" /></label><label class="playground-field" for="playground-scrollspy-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-scrollspy-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[ <div class="playground-fields"><label class="playground-field" for="playground-scrollspy-color"><span><strong>color</strong><small>string</small></span><select id="playground-scrollspy-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-scrollspy-size"><span><strong>size</strong><small>string</small></span><select id="playground-scrollspy-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-scrollspy-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-scrollspy-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
{ {
"id": "scrollspy-0-1", "id": "scrollspy-0-1",
"key": "scrollspy-0-1", "key": "scrollspy-0-1",
@@ -256,7 +257,7 @@ page ScrollspyDetail {
"Standard" "Standard"
] ]
} }
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-scrollspy-active"><span><strong>active</strong><small>string</small></span><input id="playground-scrollspy-active" name="pg_active" type="text" value="" /></label><label class="playground-field" for="playground-scrollspy-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-scrollspy-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-scrollspy-class"><span><strong>class</strong><small>string</small></span><input id="playground-scrollspy-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div> ]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-scrollspy-active"><span><strong>active</strong><small>string</small></span><input id="playground-scrollspy-active" name="pg_active" type="text" value="" /></label><label class="playground-field" for="playground-scrollspy-label"><span><strong>label</strong><small>string</small></span><input id="playground-scrollspy-label" name="pg_label" type="text" value="Scrollspy" /></label><label class="playground-field" for="playground-scrollspy-heading"><span><strong>heading</strong><small>string</small></span><input id="playground-scrollspy-heading" name="pg_heading" type="text" value="" /></label><label class="playground-field" for="playground-scrollspy-class"><span><strong>class</strong><small>string</small></span><input id="playground-scrollspy-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form> </form>
</div> </div>
</section> </section>
@@ -265,382 +266,75 @@ page ScrollspyDetail {
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list"> <section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div> <div><span class="demo-case-eyebrow">Recommended</span><h2>Table of contents</h2><p>Each href points at an element on the page. The runtime observes those elements and moves aria-current to the link for whichever one is in view, so the marker follows the reader without any component state.</p></div>
<span class="demo-case-number">01</span> <span class="demo-case-number">01</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="scrollspy-demo-1" class="demo-canvas demo-canvas--1"> <div id="scrollspy-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Scrollspy size="default" label="Scrollspy" items='[{"id":"scrollspy-0-1","key":"scrollspy-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#scrollspy-demo","actionHref":"#scrollspy-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"scrollspy-0-2","key":"scrollspy-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#scrollspy-demo","actionHref":"#scrollspy-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--1" /></div> <div class="demo-render"><Scrollspy size="default" items='[{"label":"Overview","href":"#overview"},{"label":"Installation","href":"#installation"},{"label":"Usage","href":"#usage"},{"label":"API","href":"#api"}]' active="#overview" label="Scrollspy" heading="On this page" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<section class="demo-code" aria-label="Production default usage"> <section class="demo-code" aria-label="Table of contents usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Scrollspy <pre><code>&lt;Scrollspy
items='[ items='[
<span>&#123;</span> <span>&#123;</span>
"id": "scrollspy-0-1", "label": "Overview",
"key": "scrollspy-0-1", "href": "#overview"
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#scrollspy-demo",
"actionHref": "#scrollspy-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"id": "scrollspy-0-2", "label": "Installation",
"key": "scrollspy-0-2", "href": "#installation"
"value": "secondary", <span>&#125;</span>,
"label": "Additional option", <span>&#123;</span>
"title": "Supporting example", "label": "Usage",
"description": "A clean default configuration for everyday product interfaces.", "href": "#usage"
"text": "Another configurable message.", <span>&#125;</span>,
"href": "#scrollspy-demo", <span>&#123;</span>
"actionHref": "#scrollspy-demo", "label": "API",
"actionLabel": "View details", "href": "#api"
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span> <span>&#125;</span>
]' ]'
active="#overview"
label="Scrollspy"
heading="On this page"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
</article> </article>
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div> <div><span class="demo-case-eyebrow">Advanced</span><h2>Compact rail</h2><p>On a phone the rail becomes a horizontal strip that scrolls, so a long contents list costs one line rather than a screenful.</p></div>
<span class="demo-case-number">02</span> <span class="demo-case-number">02</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="scrollspy-demo-2" class="demo-canvas demo-canvas--2"> <div id="scrollspy-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Scrollspy size="sm" label="Compact example" items='[{"id":"scrollspy-1-1","key":"scrollspy-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#scrollspy-demo","actionHref":"#scrollspy-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"scrollspy-1-2","key":"scrollspy-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#scrollspy-demo","actionHref":"#scrollspy-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--2" /></div> <div class="demo-render"><Scrollspy size="sm" items='[{"label":"Getting started","href":"#getting-started"},{"label":"Configuration","href":"#configuration"},{"label":"Troubleshooting","href":"#troubleshooting"}]' active="#configuration" label="Compact example" heading="Sections" class="showcase-instance showcase-instance--2" /></div>
</div> </div>
<section class="demo-code" aria-label="Compact application usage"> <section class="demo-code" aria-label="Compact rail usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Scrollspy <pre><code>&lt;Scrollspy
size="sm" size="sm"
items='[
<span>&#123;</span>
"label": "Getting started",
"href": "#getting-started"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Configuration",
"href": "#configuration"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Troubleshooting",
"href": "#troubleshooting"
<span>&#125;</span>
]'
active="#configuration"
label="Compact example" label="Compact example"
items='[ heading="Sections"
<span>&#123;</span>
"id": "scrollspy-1-1",
"key": "scrollspy-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#scrollspy-demo",
"actionHref": "#scrollspy-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "scrollspy-1-2",
"key": "scrollspy-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#scrollspy-demo",
"actionHref": "#scrollspy-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="scrollspy-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Scrollspy size="lg" label="Advanced example" items='[{"id":"scrollspy-2-1","key":"scrollspy-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#scrollspy-demo","actionHref":"#scrollspy-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"scrollspy-2-2","key":"scrollspy-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#scrollspy-demo","actionHref":"#scrollspy-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Scrollspy
size="lg"
label="Advanced example"
items='[
<span>&#123;</span>
"id": "scrollspy-2-1",
"key": "scrollspy-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#scrollspy-demo",
"actionHref": "#scrollspy-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "scrollspy-2-2",
"key": "scrollspy-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#scrollspy-demo",
"actionHref": "#scrollspy-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -654,10 +348,10 @@ const component = document.querySelector("[data-ui-component=\"Scrollspy\"], [da
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload) console.log("change", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Scrollspy"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section> <section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"On this page"</code></td><td>No</td></tr><tr><td><code>heading</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main> </main>
<aside class="detail-aside"> <aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#scrollspy-demo-1">Production default</a><a href="#scrollspy-demo-2">Compact application</a><a href="#scrollspy-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div> <div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#scrollspy-demo-1">Table of contents</a><a href="#scrollspy-demo-2">Compact rail</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
@@ -1,13 +1,13 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page SidebarDetail { page SidebarDetail {
layout = "showcase" layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary" state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Sidebar" state playground_label = ctx.url.searchParams.get("pg_label") ?? "Sidebar"
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"sidebar-0-1\",\"key\":\"sidebar-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#sidebar-demo\",\"actionHref\":\"#sidebar-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"sidebar-0-2\",\"key\":\"sidebar-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#sidebar-demo\",\"actionHref\":\"#sidebar-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]") state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"sidebar-0-1\",\"key\":\"sidebar-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#sidebar-demo\",\"actionHref\":\"#sidebar-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"sidebar-0-2\",\"key\":\"sidebar-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#sidebar-demo\",\"actionHref\":\"#sidebar-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_active = ctx.url.searchParams.get("pg_active") ?? "" state playground_active = ctx.url.searchParams.get("pg_active") ?? ""
state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal"
state playground_mobileLabel = ctx.url.searchParams.get("pg_mobileLabel") ?? "Sidebar" state playground_mobileLabel = ctx.url.searchParams.get("pg_mobileLabel") ?? "Sidebar"
state playground_drawerTitle = ctx.url.searchParams.get("pg_drawerTitle") ?? "Sidebar example"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Sidebar" title = "Sidebar"
@@ -22,7 +22,7 @@ page SidebarDetail {
<span class="showcase-eyebrow">Navigation component</span> <span class="showcase-eyebrow">Navigation component</span>
<h1>Sidebar</h1> <h1>Sidebar</h1>
<p>Theme-aware, responsive sidebar component.</p> <p>Theme-aware, responsive sidebar component.</p>
<div class="detail-badges"><span>8 props</span><span>1 slots</span><span>4 outputs</span><span>Theme ready</span><span>Responsive</span></div> <div class="detail-badges"><span>8 props</span><span>2 slots</span><span>4 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div> </div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
@@ -31,7 +31,7 @@ page SidebarDetail {
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Sidebar size='{playground_size}' color='{playground_color}' label='{playground_label}' items='{playground_items}' active='{playground_active}' orientation='{playground_orientation}' mobileLabel='{playground_mobileLabel}' class='{playground_class}' /></div> <div class="playground-preview-source" data-playground-preview><Sidebar color='{playground_color}' size='{playground_size}' label='{playground_label}' items='{playground_items}' active='{playground_active}' mobileLabel='{playground_mobileLabel}' drawerTitle='{playground_drawerTitle}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code"> <section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Sidebar <pre><code data-playground-code>&lt;Sidebar
@@ -144,14 +144,18 @@ page SidebarDetail {
<span>&#125;</span> <span>&#125;</span>
]' ]'
mobileLabel="Sidebar" mobileLabel="Sidebar"
/&gt;</code></pre> drawerTitle="Sidebar example"&gt;
&lt;div data-slot="drawer"&gt;
&lt;div class="showcase-slot"&gt;drawer slot&lt;/div&gt;
&lt;/div&gt;
&lt;/Sidebar&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div> <div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div> </div>
<form class="playground-controls" data-playground-form> <form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>8 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header> <header><div><span>Component props</span><strong>8 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-sidebar-size"><span><strong>size</strong><small>string</small></span><select id="playground-sidebar-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-sidebar-color"><span><strong>color</strong><small>string</small></span><select id="playground-sidebar-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-sidebar-label"><span><strong>label</strong><small>string</small></span><input id="playground-sidebar-label" name="pg_label" type="text" value="Sidebar" /></label><label class="playground-field" for="playground-sidebar-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-sidebar-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[ <div class="playground-fields"><label class="playground-field" for="playground-sidebar-color"><span><strong>color</strong><small>string</small></span><select id="playground-sidebar-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-sidebar-size"><span><strong>size</strong><small>string</small></span><select id="playground-sidebar-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-sidebar-label"><span><strong>label</strong><small>string</small></span><input id="playground-sidebar-label" name="pg_label" type="text" value="Sidebar" /></label><label class="playground-field" for="playground-sidebar-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-sidebar-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
{ {
"id": "sidebar-0-1", "id": "sidebar-0-1",
"key": "sidebar-0-1", "key": "sidebar-0-1",
@@ -258,7 +262,7 @@ page SidebarDetail {
"Standard" "Standard"
] ]
} }
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-sidebar-active"><span><strong>active</strong><small>string</small></span><input id="playground-sidebar-active" name="pg_active" type="text" value="" /></label><label class="playground-field" for="playground-sidebar-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-sidebar-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-sidebar-mobileLabel"><span><strong>mobile Label</strong><small>string</small></span><input id="playground-sidebar-mobileLabel" name="pg_mobileLabel" type="text" value="Sidebar" /></label><label class="playground-field" for="playground-sidebar-class"><span><strong>class</strong><small>string</small></span><input id="playground-sidebar-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div> ]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-sidebar-active"><span><strong>active</strong><small>string</small></span><input id="playground-sidebar-active" name="pg_active" type="text" value="" /></label><label class="playground-field" for="playground-sidebar-mobileLabel"><span><strong>mobile Label</strong><small>string</small></span><input id="playground-sidebar-mobileLabel" name="pg_mobileLabel" type="text" value="Sidebar" /></label><label class="playground-field" for="playground-sidebar-drawerTitle"><span><strong>drawer Title</strong><small>string</small></span><input id="playground-sidebar-drawerTitle" name="pg_drawerTitle" type="text" value="Sidebar example" /></label><label class="playground-field" for="playground-sidebar-class"><span><strong>class</strong><small>string</small></span><input id="playground-sidebar-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form> </form>
</div> </div>
</section> </section>
@@ -267,386 +271,111 @@ page SidebarDetail {
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list"> <section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div> <div><span class="demo-case-eyebrow">Recommended</span><h2>Groups and nested levels</h2><p>An entry is a single link, a labelled group, or a branch nested up to three levels. Arrow keys move down the rail.</p></div>
<span class="demo-case-number">01</span> <span class="demo-case-number">01</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="sidebar-demo-1" class="demo-canvas demo-canvas--1"> <div id="sidebar-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Sidebar size="default" label="Sidebar" items='[{"id":"sidebar-0-1","key":"sidebar-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#sidebar-demo","actionHref":"#sidebar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"sidebar-0-2","key":"sidebar-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#sidebar-demo","actionHref":"#sidebar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' mobileLabel="Sidebar" class="showcase-instance showcase-instance--1" /></div> <div class="demo-render"><Sidebar size="default" label="Workspace" items='[{"label":"Dashboard","href":"/","value":"dash","icon":"icon-[lucide--gauge]"},{"heading":"Projects","items":[{"label":"Active","href":"/active","value":"active","badge":"4"},{"label":"Archive","value":"archive","items":[{"label":"2025","href":"/a/2025","value":"a2025"},{"label":"2024","href":"/a/2024","value":"a2024"}]}]},{"heading":"Account","items":[{"label":"Settings","href":"/settings","value":"settings","icon":"icon-[lucide--settings]"}]}]' active="active" mobileLabel="Sidebar" drawerTitle="Sidebar example" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<section class="demo-code" aria-label="Production default usage"> <section class="demo-code" aria-label="Groups and nested levels usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Sidebar <pre><code>&lt;Sidebar
label="Workspace"
items='[ items='[
<span>&#123;</span> <span>&#123;</span>
"id": "sidebar-0-1", "label": "Dashboard",
"key": "sidebar-0-1", "href": "/",
"value": "primary", "value": "dash",
"label": "Primary workflow", "icon": "icon-[lucide--gauge]"
"title": "Primary workflow", <span>&#125;</span>,
"description": "A clean default configuration for everyday product interfaces.", <span>&#123;</span>
"text": "A configurable sample message.", "heading": "Projects",
"href": "#sidebar-demo",
"actionHref": "#sidebar-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [ "items": [
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option A", "label": "Active",
"value": "nested-a" "href": "/active",
"value": "active",
"badge": "4"
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option B", "label": "Archive",
"value": "nested-b" "value": "archive",
"items": [
<span>&#123;</span>
"label": "2025",
"href": "/a/2025",
"value": "a2025"
<span>&#125;</span>,
<span>&#123;</span>
"label": "2024",
"href": "/a/2024",
"value": "a2024"
<span>&#125;</span>
]
<span>&#125;</span> <span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
] ]
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"id": "sidebar-0-2", "heading": "Account",
"key": "sidebar-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#sidebar-demo",
"actionHref": "#sidebar-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [ "items": [
<span>&#123;</span> <span>&#123;</span>
"label": "Nested option A", "label": "Settings",
"value": "nested-a" "href": "/settings",
<span>&#125;</span>, "value": "settings",
<span>&#123;</span> "icon": "icon-[lucide--settings]"
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span> <span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'
active="active"
mobileLabel="Sidebar" mobileLabel="Sidebar"
/&gt;</code></pre> drawerTitle="Sidebar example"&gt;
&lt;div data-slot="drawer"&gt;
&lt;div class="showcase-slot"&gt;drawer slot&lt;/div&gt;
&lt;/div&gt;
&lt;/Sidebar&gt;</code></pre>
</section> </section>
</div> </div>
</article> </article>
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div> <div><span class="demo-case-eyebrow">Advanced</span><h2>Drawer on small screens</h2><p>Below the tablet breakpoint the rail is replaced by a launcher that opens a Drawer. Composing Drawer rather than reimplementing it means the focus trap and the body scroll lock come from one place.</p></div>
<span class="demo-case-number">02</span> <span class="demo-case-number">02</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="sidebar-demo-2" class="demo-canvas demo-canvas--2"> <div id="sidebar-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Sidebar size="sm" label="Compact example" items='[{"id":"sidebar-1-1","key":"sidebar-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#sidebar-demo","actionHref":"#sidebar-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"sidebar-1-2","key":"sidebar-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#sidebar-demo","actionHref":"#sidebar-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' mobileLabel="Compact example" class="showcase-instance showcase-instance--2" /></div> <div class="demo-render"><Sidebar size="sm" label="Operations" items='[{"label":"Queue","href":"/queue","value":"queue","icon":"icon-[lucide--inbox]"},{"label":"Reports","href":"/reports","value":"reports","icon":"icon-[lucide--chart-no-axes-column]"}]' active="queue" mobileLabel="Open navigation" drawerTitle="Operations" class="showcase-instance showcase-instance--2" /></div>
</div> </div>
<section class="demo-code" aria-label="Compact application usage"> <section class="demo-code" aria-label="Drawer on small screens usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Sidebar <pre><code>&lt;Sidebar
size="sm" size="sm"
label="Compact example" label="Operations"
items='[ items='[
<span>&#123;</span> <span>&#123;</span>
"id": "sidebar-1-1", "label": "Queue",
"key": "sidebar-1-1", "href": "/queue",
"value": "primary", "value": "queue",
"label": "Secondary workflow", "icon": "icon-[lucide--inbox]"
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#sidebar-demo",
"actionHref": "#sidebar-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"id": "sidebar-1-2", "label": "Reports",
"key": "sidebar-1-2", "href": "/reports",
"value": "secondary", "value": "reports",
"label": "Additional option", "icon": "icon-[lucide--chart-no-axes-column]"
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#sidebar-demo",
"actionHref": "#sidebar-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span> <span>&#125;</span>
]' ]'
mobileLabel="Compact example" active="queue"
/&gt;</code></pre> drawerTitle="Operations"&gt;
</section> &lt;div data-slot="drawer"&gt;
</div> &lt;div class="showcase-slot"&gt;drawer slot&lt;/div&gt;
</article> &lt;/div&gt;
<article class="demo-case"> &lt;/Sidebar&gt;</code></pre>
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="sidebar-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Sidebar size="lg" label="Advanced example" items='[{"id":"sidebar-2-1","key":"sidebar-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#sidebar-demo","actionHref":"#sidebar-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"sidebar-2-2","key":"sidebar-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#sidebar-demo","actionHref":"#sidebar-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' mobileLabel="Advanced example" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Sidebar
size="lg"
label="Advanced example"
items='[
<span>&#123;</span>
"id": "sidebar-2-1",
"key": "sidebar-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#sidebar-demo",
"actionHref": "#sidebar-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "sidebar-2-2",
"key": "sidebar-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#sidebar-demo",
"actionHref": "#sidebar-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
mobileLabel="Advanced example"
/&gt;</code></pre>
</section> </section>
</div> </div>
</article></div></section> </article></div></section>
@@ -674,10 +403,10 @@ if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload) console.log("select", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Sidebar"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>mobileLabel</code></td><td>string</td><td><code>"Open navigation"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section> <section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Sidebar"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>mobileLabel</code></td><td>string</td><td><code>"Open navigation"</code></td><td>No</td></tr><tr><td><code>drawerTitle</code></td><td>string</td><td><code>"Navigation"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main> </main>
<aside class="detail-aside"> <aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#sidebar-demo-1">Production default</a><a href="#sidebar-demo-2">Compact application</a><a href="#sidebar-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div> <div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#sidebar-demo-1">Groups and nested levels</a><a href="#sidebar-demo-2">Drawer on small screens</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
@@ -1,12 +1,13 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page StepperDetail { page StepperDetail {
layout = "showcase" layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary" state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Stepper" state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"stepper-0-1\",\"key\":\"stepper-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#stepper-demo\",\"actionHref\":\"#stepper-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"stepper-0-2\",\"key\":\"stepper-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#stepper-demo\",\"actionHref\":\"#stepper-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]") state playground_steps = JSON.parse(ctx.url.searchParams.get("pg_steps") ?? "[{\"id\":\"stepper-0-1\",\"key\":\"stepper-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#stepper-demo\",\"actionHref\":\"#stepper-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"stepper-0-2\",\"key\":\"stepper-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#stepper-demo\",\"actionHref\":\"#stepper-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_active = ctx.url.searchParams.get("pg_active") ?? "" state playground_active = Number(ctx.url.searchParams.get("pg_active") ?? "0")
state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal" state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal"
state playground_clickable = (ctx.url.searchParams.get("pg_clickable") ?? "false") === "true"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Stepper"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Stepper" title = "Stepper"
@@ -21,20 +22,20 @@ page StepperDetail {
<span class="showcase-eyebrow">Navigation component</span> <span class="showcase-eyebrow">Navigation component</span>
<h1>Stepper</h1> <h1>Stepper</h1>
<p>Theme-aware, responsive stepper component.</p> <p>Theme-aware, responsive stepper component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>4 outputs</span><span>Theme ready</span><span>Responsive</span></div> <div class="detail-badges"><span>8 props</span><span>2 slots</span><span>1 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div> </div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Stepper" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="change,previous,next,complete"> <section id="playground" class="detail-section playground-section" data-playground data-playground-component="Stepper" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="change">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Stepper</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div> <div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Stepper</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Stepper size='{playground_size}' color='{playground_color}' label='{playground_label}' items='{playground_items}' active='{playground_active}' orientation='{playground_orientation}' class='{playground_class}' /></div> <div class="playground-preview-source" data-playground-preview><Stepper color='{playground_color}' size='{playground_size}' steps='{playground_steps}' active='{playground_active}' orientation='{playground_orientation}' clickable='{playground_clickable}' label='{playground_label}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code"> <section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Stepper <pre><code data-playground-code>&lt;Stepper
items='[ steps='[
<span>&#123;</span> <span>&#123;</span>
"id": "stepper-0-1", "id": "stepper-0-1",
"key": "stepper-0-1", "key": "stepper-0-1",
@@ -142,14 +143,18 @@ page StepperDetail {
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'
/&gt;</code></pre> label="Stepper"&gt;
&lt;div data-slot="step-<span>&#123;</span>index<span>&#125;</span>"&gt;
&lt;div class="showcase-slot"&gt;step-<span>&#123;</span>index<span>&#125;</span> slot&lt;/div&gt;
&lt;/div&gt;
&lt;/Stepper&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div> <div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div> </div>
<form class="playground-controls" data-playground-form> <form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header> <header><div><span>Component props</span><strong>8 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-stepper-size"><span><strong>size</strong><small>string</small></span><select id="playground-stepper-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-stepper-color"><span><strong>color</strong><small>string</small></span><select id="playground-stepper-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-stepper-label"><span><strong>label</strong><small>string</small></span><input id="playground-stepper-label" name="pg_label" type="text" value="Stepper" /></label><label class="playground-field" for="playground-stepper-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-stepper-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[ <div class="playground-fields"><label class="playground-field" for="playground-stepper-color"><span><strong>color</strong><small>string</small></span><select id="playground-stepper-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-stepper-size"><span><strong>size</strong><small>string</small></span><select id="playground-stepper-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-stepper-steps"><span><strong>steps</strong><small>unknown[]</small></span><textarea id="playground-stepper-steps" name="pg_steps" rows="5" spellcheck="false" data-playground-json>[
{ {
"id": "stepper-0-1", "id": "stepper-0-1",
"key": "stepper-0-1", "key": "stepper-0-1",
@@ -256,7 +261,7 @@ page StepperDetail {
"Standard" "Standard"
] ]
} }
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-stepper-active"><span><strong>active</strong><small>string</small></span><input id="playground-stepper-active" name="pg_active" type="text" value="" /></label><label class="playground-field" for="playground-stepper-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-stepper-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-stepper-class"><span><strong>class</strong><small>string</small></span><input id="playground-stepper-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div> ]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-stepper-active"><span><strong>active</strong><small>number</small></span><input id="playground-stepper-active" name="pg_active" type="number" value="0" /></label><label class="playground-field" for="playground-stepper-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-stepper-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-toggle" for="playground-stepper-clickable"><span><strong>clickable</strong><small>boolean</small></span><input id="playground-stepper-clickable" name="pg_clickable" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-stepper-label"><span><strong>label</strong><small>string</small></span><input id="playground-stepper-label" name="pg_label" type="text" value="Stepper" /></label><label class="playground-field" for="playground-stepper-class"><span><strong>class</strong><small>string</small></span><input id="playground-stepper-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form> </form>
</div> </div>
</section> </section>
@@ -265,414 +270,128 @@ page StepperDetail {
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list"> <section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div> <div><span class="demo-case-eyebrow">Recommended</span><h2>Horizontal progress</h2><p>Steps before the active one read as complete, the active one is highlighted, and the rest are muted.</p></div>
<span class="demo-case-number">01</span> <span class="demo-case-number">01</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="stepper-demo-1" class="demo-canvas demo-canvas--1"> <div id="stepper-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Stepper size="default" label="Stepper" items='[{"id":"stepper-0-1","key":"stepper-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#stepper-demo","actionHref":"#stepper-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"stepper-0-2","key":"stepper-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#stepper-demo","actionHref":"#stepper-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--1" /></div> <div class="demo-render"><Stepper size="default" steps='[{"label":"Account","description":"Your details"},{"label":"Billing","description":"Payment method"},{"label":"Confirm","description":"Review and submit"}]' active="1" clickable="false" label="Stepper" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<section class="demo-code" aria-label="Production default usage"> <section class="demo-code" aria-label="Horizontal progress usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Stepper <pre><code>&lt;Stepper
items='[ steps='[
<span>&#123;</span> <span>&#123;</span>
"id": "stepper-0-1", "label": "Account",
"key": "stepper-0-1", "description": "Your details"
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#stepper-demo",
"actionHref": "#stepper-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"id": "stepper-0-2", "label": "Billing",
"key": "stepper-0-2", "description": "Payment method"
"value": "secondary", <span>&#125;</span>,
"label": "Additional option", <span>&#123;</span>
"title": "Supporting example", "label": "Confirm",
"description": "A clean default configuration for everyday product interfaces.", "description": "Review and submit"
"text": "Another configurable message.",
"href": "#stepper-demo",
"actionHref": "#stepper-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span> <span>&#125;</span>
]' ]'
/&gt;</code></pre> active="1"
label="Stepper"&gt;
&lt;div data-slot="step-<span>&#123;</span>index<span>&#125;</span>"&gt;
&lt;div class="showcase-slot"&gt;step-<span>&#123;</span>index<span>&#125;</span> slot&lt;/div&gt;
&lt;/div&gt;
&lt;/Stepper&gt;</code></pre>
</section> </section>
</div> </div>
</article> </article>
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div> <div><span class="demo-case-eyebrow">Recommended</span><h2>Vertical with icons</h2><p>Vertical orientation suits a sidebar or a narrow column. Any step may carry an iconify class instead of its number.</p></div>
<span class="demo-case-number">02</span> <span class="demo-case-number">02</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="stepper-demo-2" class="demo-canvas demo-canvas--2"> <div id="stepper-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Stepper size="sm" label="Compact example" items='[{"id":"stepper-1-1","key":"stepper-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#stepper-demo","actionHref":"#stepper-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"stepper-1-2","key":"stepper-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#stepper-demo","actionHref":"#stepper-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--2" /></div> <div class="demo-render"><Stepper size="sm" steps='[{"label":"Cloned","icon":"icon-[lucide--git-branch]"},{"label":"Built","icon":"icon-[lucide--hammer]"},{"label":"Deployed","icon":"icon-[lucide--rocket]"}]' active="2" orientation="vertical" clickable="false" label="Compact example" class="showcase-instance showcase-instance--2" /></div>
</div> </div>
<section class="demo-code" aria-label="Compact application usage"> <section class="demo-code" aria-label="Vertical with icons usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Stepper <pre><code>&lt;Stepper
size="sm" size="sm"
label="Compact example" steps='[
items='[
<span>&#123;</span> <span>&#123;</span>
"id": "stepper-1-1", "label": "Cloned",
"key": "stepper-1-1", "icon": "icon-[lucide--git-branch]"
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#stepper-demo",
"actionHref": "#stepper-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"id": "stepper-1-2", "label": "Built",
"key": "stepper-1-2", "icon": "icon-[lucide--hammer]"
"value": "secondary", <span>&#125;</span>,
"label": "Additional option", <span>&#123;</span>
"title": "Supporting example", "label": "Deployed",
"description": "A compact configuration designed for dense application layouts.", "icon": "icon-[lucide--rocket]"
"text": "Another configurable message.",
"href": "#stepper-demo",
"actionHref": "#stepper-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span> <span>&#125;</span>
]' ]'
/&gt;</code></pre> active="2"
orientation="vertical"
label="Compact example"&gt;
&lt;div data-slot="step-<span>&#123;</span>index<span>&#125;</span>"&gt;
&lt;div class="showcase-slot"&gt;step-<span>&#123;</span>index<span>&#125;</span> slot&lt;/div&gt;
&lt;/div&gt;
&lt;/Stepper&gt;</code></pre>
</section> </section>
</div> </div>
</article> </article>
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div> <div><span class="demo-case-eyebrow">Advanced</span><h2>Clickable steps</h2><p>With clickable the steps emit a change output and take arrow-key roving focus. Without it the stepper is read-only and stays out of the tab order.</p></div>
<span class="demo-case-number">03</span> <span class="demo-case-number">03</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="stepper-demo-3" class="demo-canvas demo-canvas--3"> <div id="stepper-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Stepper size="lg" label="Advanced example" items='[{"id":"stepper-2-1","key":"stepper-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#stepper-demo","actionHref":"#stepper-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"stepper-2-2","key":"stepper-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#stepper-demo","actionHref":"#stepper-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' class="showcase-instance showcase-instance--3" /></div> <div class="demo-render"><Stepper size="lg" steps='[{"label":"One"},{"label":"Two"},{"label":"Three"}]' active="0" clickable="true" label="Advanced example" class="showcase-instance showcase-instance--3" /></div>
</div> </div>
<section class="demo-code" aria-label="Rich configuration usage"> <section class="demo-code" aria-label="Clickable steps usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Stepper <pre><code>&lt;Stepper
size="lg" size="lg"
label="Advanced example" steps='[
items='[
<span>&#123;</span> <span>&#123;</span>
"id": "stepper-2-1", "label": "One"
"key": "stepper-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#stepper-demo",
"actionHref": "#stepper-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>, <span>&#125;</span>,
<span>&#123;</span> <span>&#123;</span>
"id": "stepper-2-2", "label": "Two"
"key": "stepper-2-2", <span>&#125;</span>,
"value": "secondary", <span>&#123;</span>
"label": "Additional option", "label": "Three"
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#stepper-demo",
"actionHref": "#stepper-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span> <span>&#125;</span>
]' ]'
/&gt;</code></pre> clickable="true"
label="Advanced example"&gt;
&lt;div data-slot="step-<span>&#123;</span>index<span>&#125;</span>"&gt;
&lt;div class="showcase-slot"&gt;step-<span>&#123;</span>index<span>&#125;</span> slot&lt;/div&gt;
&lt;/div&gt;
&lt;/Stepper&gt;</code></pre>
</section> </section>
</div> </div>
</article></div></section> </article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div><div><code>@previous</code><span>Fires when the component emits the previous event.</span></div><div><code>@next</code><span>Fires when the component emits the next event.</span></div><div><code>@complete</code><span>Fires when the component emits the complete event.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Stepper <section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Stepper
@change='console.log(payload)' @change='console.log(payload)'
@previous='console.log(payload)'
@next='console.log(payload)'
@complete='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs" /&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Stepper\"], [data-component=\"Stepper\"]") const component = document.querySelector("[data-ui-component=\"Stepper\"], [data-component=\"Stepper\"]")
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload) console.log("change", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "previous", (payload) =&gt; <span>&#123;</span>
console.log("previous", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "next", (payload) =&gt; <span>&#123;</span>
console.log("next", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "complete", (payload) =&gt; <span>&#123;</span>
console.log("complete", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Stepper"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section> <section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>steps</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>number</td><td><code>0</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>clickable</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Progress"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main> </main>
<aside class="detail-aside"> <aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#stepper-demo-1">Production default</a><a href="#stepper-demo-2">Compact application</a><a href="#stepper-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div> <div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#stepper-demo-1">Horizontal progress</a><a href="#stepper-demo-2">Vertical with icons</a><a href="#stepper-demo-3">Clickable steps</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
@@ -1,12 +1,14 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page TabsDetail { page TabsDetail {
layout = "showcase" layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary" state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Tabs" state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"tabs-0-1\",\"key\":\"tabs-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#tabs-demo\",\"actionHref\":\"#tabs-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"tabs-0-2\",\"key\":\"tabs-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#tabs-demo\",\"actionHref\":\"#tabs-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]") state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"tabs-0-1\",\"key\":\"tabs-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#tabs-demo\",\"actionHref\":\"#tabs-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"tabs-0-2\",\"key\":\"tabs-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#tabs-demo\",\"actionHref\":\"#tabs-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_active = ctx.url.searchParams.get("pg_active") ?? "" state playground_active = ctx.url.searchParams.get("pg_active") ?? ""
state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal" state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal"
state playground_mode = ctx.url.searchParams.get("pg_mode") ?? "client"
state playground_param = ctx.url.searchParams.get("pg_param") ?? "tab"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Tabs"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Tabs" title = "Tabs"
@@ -21,16 +23,16 @@ page TabsDetail {
<span class="showcase-eyebrow">Navigation component</span> <span class="showcase-eyebrow">Navigation component</span>
<h1>Tabs</h1> <h1>Tabs</h1>
<p>Switch between related responsive content panels with horizontal or vertical orientation and selection events.</p> <p>Switch between related responsive content panels with horizontal or vertical orientation and selection events.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div> <div class="detail-badges"><span>9 props</span><span>2 slots</span><span>2 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div> </div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Tabs" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events=""> <section id="playground" class="detail-section playground-section" data-playground data-playground-component="Tabs" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="change,select">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Tabs</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div> <div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Tabs</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Tabs size='{playground_size}' color='{playground_color}' label='{playground_label}' items='{playground_items}' active='{playground_active}' orientation='{playground_orientation}' class='{playground_class}' /></div> <div class="playground-preview-source" data-playground-preview><Tabs color='{playground_color}' size='{playground_size}' items='{playground_items}' active='{playground_active}' orientation='{playground_orientation}' mode='{playground_mode}' param='{playground_param}' label='{playground_label}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code"> <section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Tabs <pre><code data-playground-code>&lt;Tabs
@@ -141,15 +143,18 @@ page TabsDetail {
"Standard" "Standard"
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'&gt;
/&gt;</code></pre> &lt;div data-slot="panel-<span>&#123;</span>valueOf(item, index)<span>&#125;</span>"&gt;
&lt;div class="showcase-slot"&gt;panel-<span>&#123;</span>value Of(item, index)<span>&#125;</span> slot&lt;/div&gt;
&lt;/div&gt;
&lt;/Tabs&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div> </div>
<form class="playground-controls" data-playground-form> <form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header> <header><div><span>Component props</span><strong>9 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-tabs-size"><span><strong>size</strong><small>string</small></span><select id="playground-tabs-size" name="pg_size"><option value="default" selected>default</option><option value="sm">sm</option><option value="lg">lg</option><option value="xs">xs</option><option value="md">md</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-tabs-color"><span><strong>color</strong><small>string</small></span><select id="playground-tabs-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-tabs-label"><span><strong>label</strong><small>string</small></span><input id="playground-tabs-label" name="pg_label" type="text" value="Tabs" /></label><label class="playground-field" for="playground-tabs-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-tabs-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[ <div class="playground-fields"><label class="playground-field" for="playground-tabs-color"><span><strong>color</strong><small>string</small></span><select id="playground-tabs-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-tabs-size"><span><strong>size</strong><small>string</small></span><select id="playground-tabs-size" name="pg_size"><option value="default" selected>default</option><option value="sm">sm</option><option value="lg">lg</option><option value="xs">xs</option><option value="md">md</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-tabs-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-tabs-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
{ {
"id": "tabs-0-1", "id": "tabs-0-1",
"key": "tabs-0-1", "key": "tabs-0-1",
@@ -256,7 +261,7 @@ page TabsDetail {
"Standard" "Standard"
] ]
} }
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-tabs-active"><span><strong>active</strong><small>string</small></span><input id="playground-tabs-active" name="pg_active" type="text" value="" /></label><label class="playground-field" for="playground-tabs-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-tabs-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-tabs-class"><span><strong>class</strong><small>string</small></span><input id="playground-tabs-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div> ]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-tabs-active"><span><strong>active</strong><small>string</small></span><input id="playground-tabs-active" name="pg_active" type="text" value="" /></label><label class="playground-field" for="playground-tabs-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-tabs-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-tabs-mode"><span><strong>mode</strong><small>string</small></span><input id="playground-tabs-mode" name="pg_mode" type="text" value="client" /></label><label class="playground-field" for="playground-tabs-param"><span><strong>param</strong><small>string</small></span><input id="playground-tabs-param" name="pg_param" type="text" value="tab" /></label><label class="playground-field" for="playground-tabs-label"><span><strong>label</strong><small>string</small></span><input id="playground-tabs-label" name="pg_label" type="text" value="Tabs" /></label><label class="playground-field" for="playground-tabs-class"><span><strong>class</strong><small>string</small></span><input id="playground-tabs-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form> </form>
</div> </div>
</section> </section>
@@ -272,12 +277,11 @@ page TabsDetail {
<div id="tabs-demo-1" class="demo-canvas demo-canvas--1"> <div id="tabs-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Tabs size="default" label="Service information" items='[{"label":"Overview","value":"overview","content":"Overview content"},{"label":"Requirements","value":"requirements","content":"Requirements content"},{"label":"Process","value":"process","content":"Process content"}]' active="overview" orientation="horizontal" class="showcase-instance showcase-instance--1" /></div> <div class="demo-render"><Tabs size="default" items='[{"label":"Overview","value":"overview","content":"Overview content"},{"label":"Requirements","value":"requirements","content":"Requirements content"},{"label":"Process","value":"process","content":"Process content"}]' active="overview" orientation="horizontal" label="Service information" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<section class="demo-code" aria-label="Horizontal content tabs usage"> <section class="demo-code" aria-label="Horizontal content tabs usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Tabs <pre><code>&lt;Tabs
label="Service information"
items='[ items='[
<span>&#123;</span> <span>&#123;</span>
"label": "Overview", "label": "Overview",
@@ -296,26 +300,71 @@ page TabsDetail {
<span>&#125;</span> <span>&#125;</span>
]' ]'
active="overview" active="overview"
/&gt;</code></pre> label="Service information"&gt;
&lt;div data-slot="panel-<span>&#123;</span>valueOf(item, index)<span>&#125;</span>"&gt;
&lt;div class="showcase-slot"&gt;panel-<span>&#123;</span>value Of(item, index)<span>&#125;</span> slot&lt;/div&gt;
&lt;/div&gt;
&lt;/Tabs&gt;</code></pre>
</section> </section>
</div> </div>
</article> </article>
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Compact</span><h2>Compact application tabs</h2><p>Use smaller tabs in dashboards, panels, and dense product interfaces.</p></div> <div><span class="demo-case-eyebrow">Advanced</span><h2>Mirrored into the URL</h2><p>With mode=url the selection is written to a query parameter using pushState, so the panel swaps without a page load, the tab survives a reload, and the back button steps through the tabs you visited.</p></div>
<span class="demo-case-number">02</span> <span class="demo-case-number">02</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="tabs-demo-2" class="demo-canvas demo-canvas--2"> <div id="tabs-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Tabs size="sm" label="Record views" items='[{"label":"Details","value":"details"},{"label":"History","value":"history"},{"label":"Files","value":"files"}]' active="details" class="showcase-instance showcase-instance--2" /></div> <div class="demo-render"><Tabs size="sm" items='[{"label":"Account","value":"account","description":"Profile and credentials."},{"label":"Billing","value":"billing","description":"Invoices and payment method."},{"label":"Team","value":"team","description":"Members and their roles."}]' active="account" mode="url" param="tab" label="Account settings" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Mirrored into the URL usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Tabs
size="sm"
items='[
<span>&#123;</span>
"label": "Account",
"value": "account",
"description": "Profile and credentials."
<span>&#125;</span>,
<span>&#123;</span>
"label": "Billing",
"value": "billing",
"description": "Invoices and payment method."
<span>&#125;</span>,
<span>&#123;</span>
"label": "Team",
"value": "team",
"description": "Members and their roles."
<span>&#125;</span>
]'
active="account"
mode="url"
label="Account settings"&gt;
&lt;div data-slot="panel-<span>&#123;</span>valueOf(item, index)<span>&#125;</span>"&gt;
&lt;div class="showcase-slot"&gt;panel-<span>&#123;</span>value Of(item, index)<span>&#125;</span> slot&lt;/div&gt;
&lt;/div&gt;
&lt;/Tabs&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Compact</span><h2>Compact application tabs</h2><p>Use smaller tabs in dashboards, panels, and dense product interfaces.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="tabs-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Tabs size="sm" items='[{"label":"Details","value":"details"},{"label":"History","value":"history"},{"label":"Files","value":"files"}]' active="details" label="Record views" class="showcase-instance showcase-instance--3" /></div>
</div> </div>
<section class="demo-code" aria-label="Compact application tabs usage"> <section class="demo-code" aria-label="Compact application tabs usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Tabs <pre><code>&lt;Tabs
size="sm" size="sm"
label="Record views"
items='[ items='[
<span>&#123;</span> <span>&#123;</span>
"label": "Details", "label": "Details",
@@ -331,26 +380,29 @@ page TabsDetail {
<span>&#125;</span> <span>&#125;</span>
]' ]'
active="details" active="details"
/&gt;</code></pre> label="Record views"&gt;
&lt;div data-slot="panel-<span>&#123;</span>valueOf(item, index)<span>&#125;</span>"&gt;
&lt;div class="showcase-slot"&gt;panel-<span>&#123;</span>value Of(item, index)<span>&#125;</span> slot&lt;/div&gt;
&lt;/div&gt;
&lt;/Tabs&gt;</code></pre>
</section> </section>
</div> </div>
</article> </article>
<article class="demo-case"> <article class="demo-case">
<header class="demo-case-header"> <header class="demo-case-header">
<div><span class="demo-case-eyebrow">Advanced</span><h2>Vertical settings tabs</h2><p>Use vertical tabs for settings, configuration, and long-form navigation.</p></div> <div><span class="demo-case-eyebrow">Advanced</span><h2>Vertical settings tabs</h2><p>Use vertical tabs for settings, configuration, and long-form navigation.</p></div>
<span class="demo-case-number">03</span> <span class="demo-case-number">04</span>
</header> </header>
<div class="demo-workbench"> <div class="demo-workbench">
<div id="tabs-demo-3" class="demo-canvas demo-canvas--3"> <div id="tabs-demo-4" class="demo-canvas demo-canvas--4">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Tabs size="lg" label="Settings" items='[{"label":"Profile","value":"profile","icon":"icon-[lucide--user]"},{"label":"Security","value":"security","icon":"icon-[lucide--shield]"},{"label":"Notifications","value":"notifications","icon":"icon-[lucide--bell]"}]' active="security" orientation="vertical" class="showcase-instance showcase-instance--3" /></div> <div class="demo-render"><Tabs size="lg" items='[{"label":"Profile","value":"profile","icon":"icon-[lucide--user]"},{"label":"Security","value":"security","icon":"icon-[lucide--shield]"},{"label":"Notifications","value":"notifications","icon":"icon-[lucide--bell]"}]' active="security" orientation="vertical" label="Settings" class="showcase-instance showcase-instance--4" /></div>
</div> </div>
<section class="demo-code" aria-label="Vertical settings tabs usage"> <section class="demo-code" aria-label="Vertical settings tabs usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Tabs <pre><code>&lt;Tabs
size="lg" size="lg"
label="Settings"
items='[ items='[
<span>&#123;</span> <span>&#123;</span>
"label": "Profile", "label": "Profile",
@@ -370,15 +422,32 @@ page TabsDetail {
]' ]'
active="security" active="security"
orientation="vertical" orientation="vertical"
/&gt;</code></pre> label="Settings"&gt;
&lt;div data-slot="panel-<span>&#123;</span>valueOf(item, index)<span>&#125;</span>"&gt;
&lt;div class="showcase-slot"&gt;panel-<span>&#123;</span>value Of(item, index)<span>&#125;</span> slot&lt;/div&gt;
&lt;/div&gt;
&lt;/Tabs&gt;</code></pre>
</section> </section>
</div> </div>
</article></div></section> </article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Tabs
@change='console.log(payload)'
@select='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Tabs"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section> const component = document.querySelector("[data-ui-component=\"Tabs\"], [data-component=\"Tabs\"]")
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>mode</code></td><td>string</td><td><code>"client"</code></td><td>No</td></tr><tr><td><code>param</code></td><td>string</td><td><code>"tab"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Tabs"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main> </main>
<aside class="detail-aside"> <aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#tabs-demo-1">Horizontal content tabs</a><a href="#tabs-demo-2">Compact application tabs</a><a href="#tabs-demo-3">Vertical settings tabs</a><a href="#api">Props API</a></div> <div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#tabs-demo-1">Horizontal content tabs</a><a href="#tabs-demo-2">Mirrored into the URL</a><a href="#tabs-demo-3">Compact application tabs</a><a href="#tabs-demo-4">Vertical settings tabs</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
@@ -17,11 +17,11 @@ page ComponentShowcase {
<section class="home-product-grid"><article><span class="icon-[lucide--component] size-7"></span><strong>108 components</strong><p>Accessible primitives for every product surface.</p><a href="/base">Browse components →</a></article><article><span class="icon-[lucide--layout-template] size-7"></span><strong>Ready-made blocks</strong><p>Composable sections assembled from WRNexus UI.</p><a href="/block-library">Explore blocks →</a></article><article><span class="icon-[lucide--panels-top-left] size-7"></span><strong>Page templates</strong><p>Complete responsive pages ready to customize.</p><a href="/templates">View templates →</a></article></section> <section class="home-product-grid"><article><span class="icon-[lucide--component] size-7"></span><strong>108 components</strong><p>Accessible primitives for every product surface.</p><a href="/base">Browse components →</a></article><article><span class="icon-[lucide--layout-template] size-7"></span><strong>Ready-made blocks</strong><p>Composable sections assembled from WRNexus UI.</p><a href="/block-library">Explore blocks →</a></article><article><span class="icon-[lucide--panels-top-left] size-7"></span><strong>Page templates</strong><p>Complete responsive pages ready to customize.</p><a href="/templates">View templates →</a></article></section>
<section class="showcase-stats"> <section class="showcase-stats">
<div class="showcase-stat"><strong>108</strong><span>Unique components</span></div> <div class="showcase-stat"><strong>108</strong><span>Unique components</span></div>
<div class="showcase-stat"><strong>421</strong><span>Live configurations</span></div> <div class="showcase-stat"><strong>419</strong><span>Live configurations</span></div>
<div class="showcase-stat"><strong>0</strong><span>Duplicate implementations</span></div> <div class="showcase-stat"><strong>0</strong><span>Duplicate implementations</span></div>
<div class="showcase-stat"><strong>11</strong><span>Focused categories</span></div> <div class="showcase-stat"><strong>11</strong><span>Focused categories</span></div>
</section> </section>
<section class="home-categories"><div class="home-section-heading"><span>Explore the system</span><h2>Everything your product needs</h2></div><div class="home-category-grid"><a class="home-category home-category--1" href="/advanced-forms"><span class="home-category-index">01</span><div><strong>Advanced Forms</strong><p>8 components · 112 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/base"><span class="home-category-index">02</span><div><strong>Base</strong><p>27 components · 86 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/core"><span class="home-category-index">03</span><div><strong>Core</strong><p>5 components · 13 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/data"><span class="home-category-index">04</span><div><strong>Data</strong><p>3 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/forms"><span class="home-category-index">05</span><div><strong>Forms</strong><p>12 components · 36 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/integrations"><span class="home-category-index">06</span><div><strong>Integrations</strong><p>11 components · 33 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/layout"><span class="home-category-index">07</span><div><strong>Layout</strong><p>15 components · 45 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/marketing"><span class="home-category-index">08</span><div><strong>Marketing</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/navigation"><span class="home-category-index">09</span><div><strong>Navigation</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/overlays"><span class="home-category-index">010</span><div><strong>Overlays</strong><p>6 components · 18 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/tables"><span class="home-category-index">011</span><div><strong>Tables</strong><p>1 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a></div></section> <section class="home-categories"><div class="home-section-heading"><span>Explore the system</span><h2>Everything your product needs</h2></div><div class="home-category-grid"><a class="home-category home-category--1" href="/advanced-forms"><span class="home-category-index">01</span><div><strong>Advanced Forms</strong><p>8 components · 112 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/base"><span class="home-category-index">02</span><div><strong>Base</strong><p>27 components · 86 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/core"><span class="home-category-index">03</span><div><strong>Core</strong><p>5 components · 13 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/data"><span class="home-category-index">04</span><div><strong>Data</strong><p>3 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/forms"><span class="home-category-index">05</span><div><strong>Forms</strong><p>12 components · 36 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/integrations"><span class="home-category-index">06</span><div><strong>Integrations</strong><p>11 components · 33 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/layout"><span class="home-category-index">07</span><div><strong>Layout</strong><p>15 components · 45 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/marketing"><span class="home-category-index">08</span><div><strong>Marketing</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/navigation"><span class="home-category-index">09</span><div><strong>Navigation</strong><p>10 components · 28 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/overlays"><span class="home-category-index">010</span><div><strong>Overlays</strong><p>6 components · 18 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/tables"><span class="home-category-index">011</span><div><strong>Tables</strong><p>1 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a></div></section>
</div> </div>
} }
} }
@@ -10,7 +10,7 @@ page NavigationShowcase {
<span class="showcase-eyebrow">Component category</span> <span class="showcase-eyebrow">Component category</span>
<h1 class="showcase-title">Navigation</h1> <h1 class="showcase-title">Navigation</h1>
<p class="showcase-description">10 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p> <p class="showcase-description">10 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
<div class="category-meta"><span>10 components</span><span>Multiple use cases</span><span>30 live configurations</span></div> <div class="category-meta"><span>10 components</span><span>Multiple use cases</span><span>28 live configurations</span></div>
</header> </header>
<div class="catalog-grid"> <div class="catalog-grid">
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
@@ -36,21 +36,21 @@ page NavigationShowcase {
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><MegaMenu size="default" label="Mega Menu" items='[{"id":"mega-menu-0-1","key":"mega-menu-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#mega-menu-demo","actionHref":"#mega-menu-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"mega-menu-0-2","key":"mega-menu-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#mega-menu-demo","actionHref":"#mega-menu-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--1" /></div> <div class="catalog-card-stage"><MegaMenu size="default" label="Products" icon="icon-[lucide--box]" columns='[{"heading":"Platform","items":[{"label":"Runtime","href":"/runtime","description":"The browser half of the framework."},{"label":"Compiler","href":"/compiler","description":"WRN to JavaScript."}]},{"heading":"Tooling","items":[{"label":"CLI","href":"/cli","description":"Scaffold, build and deploy."},{"label":"Editor","href":"/editor","description":"Language server and syntax."}]}]' defaultOpen="false" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<div class="catalog-card-body"> <div class="catalog-card-body">
<div><span class="catalog-card-category">Navigation</span><h2>Mega Menu</h2><p>Theme-aware, responsive mega menu component.</p></div> <div><span class="catalog-card-category">Navigation</span><h2>Mega Menu</h2><p>Theme-aware, responsive mega menu component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/mega-menu">Explore component <span aria-hidden="true">→</span></a></div> <div class="catalog-card-footer"><span>8 props · 1 slots</span><a href="/components/mega-menu">Explore component <span aria-hidden="true">→</span></a></div>
</div> </div>
</article> </article>
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><Nav size="default" label="Nav" items='[{"id":"nav-0-1","key":"nav-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#nav-demo","actionHref":"#nav-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"nav-0-2","key":"nav-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#nav-demo","actionHref":"#nav-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--1" /></div> <div class="catalog-card-stage"><Nav size="default" items='[{"label":"Home","href":"/","value":"home","icon":"icon-[lucide--house]"},{"label":"Inbox","href":"/inbox","value":"inbox","badge":"9"},{"label":"Reports","href":"/reports","value":"reports"},{"label":"Archive","href":"/archive","value":"archive","disabled":true}]' active="inbox" label="Nav" collapsible="true" toggleLabel="Nav" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<div class="catalog-card-body"> <div class="catalog-card-body">
<div><span class="catalog-card-category">Navigation</span><h2>Nav</h2><p>Theme-aware, responsive nav component.</p></div> <div><span class="catalog-card-category">Navigation</span><h2>Nav</h2><p>Theme-aware, responsive nav component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/nav">Explore component <span aria-hidden="true">→</span></a></div> <div class="catalog-card-footer"><span>9 props · 1 slots</span><a href="/components/nav">Explore component <span aria-hidden="true">→</span></a></div>
</div> </div>
</article> </article>
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
@@ -66,17 +66,17 @@ page NavigationShowcase {
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><Pagination size="default" label="Pagination" items='[{"id":"pagination-0-1","key":"pagination-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#pagination-demo","actionHref":"#pagination-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"pagination-0-2","key":"pagination-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#pagination-demo","actionHref":"#pagination-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--1" /></div> <div class="catalog-card-stage"><Pagination size="default" page="2" pageSize="10" total="137" variant="compact" siblingCount="3" showSummary="true" label="Pagination" previousLabel="Pagination" nextLabel="Pagination" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<div class="catalog-card-body"> <div class="catalog-card-body">
<div><span class="catalog-card-category">Navigation</span><h2>Pagination</h2><p>Theme-aware, responsive pagination component.</p></div> <div><span class="catalog-card-category">Navigation</span><h2>Pagination</h2><p>Theme-aware, responsive pagination component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/pagination">Explore component <span aria-hidden="true">→</span></a></div> <div class="catalog-card-footer"><span>12 props · 1 slots</span><a href="/components/pagination">Explore component <span aria-hidden="true">→</span></a></div>
</div> </div>
</article> </article>
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><Scrollspy size="default" label="Scrollspy" items='[{"id":"scrollspy-0-1","key":"scrollspy-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#scrollspy-demo","actionHref":"#scrollspy-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"scrollspy-0-2","key":"scrollspy-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#scrollspy-demo","actionHref":"#scrollspy-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--1" /></div> <div class="catalog-card-stage"><Scrollspy size="default" items='[{"label":"Overview","href":"#overview"},{"label":"Installation","href":"#installation"},{"label":"Usage","href":"#usage"},{"label":"API","href":"#api"}]' active="#overview" label="Scrollspy" heading="On this page" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<div class="catalog-card-body"> <div class="catalog-card-body">
<div><span class="catalog-card-category">Navigation</span><h2>Scrollspy</h2><p>Theme-aware, responsive scrollspy component.</p></div> <div><span class="catalog-card-category">Navigation</span><h2>Scrollspy</h2><p>Theme-aware, responsive scrollspy component.</p></div>
@@ -86,31 +86,31 @@ page NavigationShowcase {
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><Sidebar size="default" label="Sidebar" items='[{"id":"sidebar-0-1","key":"sidebar-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#sidebar-demo","actionHref":"#sidebar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"sidebar-0-2","key":"sidebar-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#sidebar-demo","actionHref":"#sidebar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' mobileLabel="Sidebar" class="showcase-instance showcase-instance--1" /></div> <div class="catalog-card-stage"><Sidebar size="default" label="Workspace" items='[{"label":"Dashboard","href":"/","value":"dash","icon":"icon-[lucide--gauge]"},{"heading":"Projects","items":[{"label":"Active","href":"/active","value":"active","badge":"4"},{"label":"Archive","value":"archive","items":[{"label":"2025","href":"/a/2025","value":"a2025"},{"label":"2024","href":"/a/2024","value":"a2024"}]}]},{"heading":"Account","items":[{"label":"Settings","href":"/settings","value":"settings","icon":"icon-[lucide--settings]"}]}]' active="active" mobileLabel="Sidebar" drawerTitle="Sidebar example" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<div class="catalog-card-body"> <div class="catalog-card-body">
<div><span class="catalog-card-category">Navigation</span><h2>Sidebar</h2><p>Theme-aware, responsive sidebar component.</p></div> <div><span class="catalog-card-category">Navigation</span><h2>Sidebar</h2><p>Theme-aware, responsive sidebar component.</p></div>
<div class="catalog-card-footer"><span>8 props · 1 slots</span><a href="/components/sidebar">Explore component <span aria-hidden="true">→</span></a></div> <div class="catalog-card-footer"><span>8 props · 2 slots</span><a href="/components/sidebar">Explore component <span aria-hidden="true">→</span></a></div>
</div> </div>
</article> </article>
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><Stepper size="default" label="Stepper" items='[{"id":"stepper-0-1","key":"stepper-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#stepper-demo","actionHref":"#stepper-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"stepper-0-2","key":"stepper-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#stepper-demo","actionHref":"#stepper-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' class="showcase-instance showcase-instance--1" /></div> <div class="catalog-card-stage"><Stepper size="default" steps='[{"label":"Account","description":"Your details"},{"label":"Billing","description":"Payment method"},{"label":"Confirm","description":"Review and submit"}]' active="1" clickable="false" label="Stepper" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<div class="catalog-card-body"> <div class="catalog-card-body">
<div><span class="catalog-card-category">Navigation</span><h2>Stepper</h2><p>Theme-aware, responsive stepper component.</p></div> <div><span class="catalog-card-category">Navigation</span><h2>Stepper</h2><p>Theme-aware, responsive stepper component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/stepper">Explore component <span aria-hidden="true">→</span></a></div> <div class="catalog-card-footer"><span>8 props · 2 slots</span><a href="/components/stepper">Explore component <span aria-hidden="true">→</span></a></div>
</div> </div>
</article> </article>
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><Tabs size="default" label="Service information" items='[{"label":"Overview","value":"overview","content":"Overview content"},{"label":"Requirements","value":"requirements","content":"Requirements content"},{"label":"Process","value":"process","content":"Process content"}]' active="overview" orientation="horizontal" class="showcase-instance showcase-instance--1" /></div> <div class="catalog-card-stage"><Tabs size="default" items='[{"label":"Overview","value":"overview","content":"Overview content"},{"label":"Requirements","value":"requirements","content":"Requirements content"},{"label":"Process","value":"process","content":"Process content"}]' active="overview" orientation="horizontal" label="Service information" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<div class="catalog-card-body"> <div class="catalog-card-body">
<div><span class="catalog-card-category">Navigation</span><h2>Tabs</h2><p>Switch between related responsive content panels with horizontal or vertical orientation and selection events.</p></div> <div><span class="catalog-card-category">Navigation</span><h2>Tabs</h2><p>Switch between related responsive content panels with horizontal or vertical orientation and selection events.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/tabs">Explore component <span aria-hidden="true">→</span></a></div> <div class="catalog-card-footer"><span>9 props · 2 slots</span><a href="/components/tabs">Explore component <span aria-hidden="true">→</span></a></div>
</div> </div>
</article></div> </article></div>
} }
@@ -102,7 +102,6 @@ export interface Routes {
"/components/strong-password": Record<string, never>; "/components/strong-password": Record<string, never>;
"/components/styled-icon": Record<string, never>; "/components/styled-icon": Record<string, never>;
"/components/switch": Record<string, never>; "/components/switch": Record<string, never>;
"/components/table": Record<string, never>;
"/components/tabs": Record<string, never>; "/components/tabs": Record<string, never>;
"/components/text-link": Record<string, never>; "/components/text-link": Record<string, never>;
"/components/textarea": Record<string, never>; "/components/textarea": Record<string, never>;
@@ -246,7 +245,6 @@ export interface RouteNames {
"components.strong.password": "/components/strong-password"; "components.strong.password": "/components/strong-password";
"components.styled.icon": "/components/styled-icon"; "components.styled.icon": "/components/styled-icon";
"components.switch": "/components/switch"; "components.switch": "/components/switch";
"components.table": "/components/table";
"components.tabs": "/components/tabs"; "components.tabs": "/components/tabs";
"components.text.link": "/components/text-link"; "components.text.link": "/components/text-link";
"components.textarea": "/components/textarea"; "components.textarea": "/components/textarea";
@@ -459,7 +457,6 @@ export function route<N extends RouteName>(
"components.strong.password": "/components/strong-password", "components.strong.password": "/components/strong-password",
"components.styled.icon": "/components/styled-icon", "components.styled.icon": "/components/styled-icon",
"components.switch": "/components/switch", "components.switch": "/components/switch",
"components.table": "/components/table",
"components.tabs": "/components/tabs", "components.tabs": "/components/tabs",
"components.text.link": "/components/text-link", "components.text.link": "/components/text-link",
"components.textarea": "/components/textarea", "components.textarea": "/components/textarea",
@@ -253,6 +253,268 @@ const DATATABLE_ROWS =
'[{"id": 1, "name": "Northwind", "plan": "Scale", "owner": "A. Okafor", "seats": 6, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 2, "name": "Acme Industrial", "plan": "Team", "owner": "R. Silva", "seats": 13, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 3, "name": "Globex", "plan": "Enterprise", "owner": "M. Chen", "seats": 20, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 4, "name": "Initech", "plan": "Starter", "owner": "J. Dubois", "seats": 27, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 5, "name": "Umbrella", "plan": "Scale", "owner": "P. Novak", "seats": 34, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 6, "name": "Stark Labs", "plan": "Team", "owner": "A. Okafor", "seats": 41, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 7, "name": "Wayne Foods", "plan": "Enterprise", "owner": "R. Silva", "seats": 48, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 8, "name": "Soylent", "plan": "Starter", "owner": "M. Chen", "seats": 55, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 9, "name": "Hooli", "plan": "Scale", "owner": "J. Dubois", "seats": 62, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 10, "name": "Vehement", "plan": "Team", "owner": "P. Novak", "seats": 69, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 11, "name": "Massive Dynamic", "plan": "Enterprise", "owner": "A. Okafor", "seats": 76, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 12, "name": "Cyberdyne", "plan": "Starter", "owner": "R. Silva", "seats": 83, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 13, "name": "Tyrell", "plan": "Scale", "owner": "M. Chen", "seats": 90, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 14, "name": "Aperture", "plan": "Team", "owner": "J. Dubois", "seats": 97, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 15, "name": "Black Mesa", "plan": "Enterprise", "owner": "P. Novak", "seats": 104, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}]'; '[{"id": 1, "name": "Northwind", "plan": "Scale", "owner": "A. Okafor", "seats": 6, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 2, "name": "Acme Industrial", "plan": "Team", "owner": "R. Silva", "seats": 13, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 3, "name": "Globex", "plan": "Enterprise", "owner": "M. Chen", "seats": 20, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 4, "name": "Initech", "plan": "Starter", "owner": "J. Dubois", "seats": 27, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 5, "name": "Umbrella", "plan": "Scale", "owner": "P. Novak", "seats": 34, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 6, "name": "Stark Labs", "plan": "Team", "owner": "A. Okafor", "seats": 41, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 7, "name": "Wayne Foods", "plan": "Enterprise", "owner": "R. Silva", "seats": 48, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 8, "name": "Soylent", "plan": "Starter", "owner": "M. Chen", "seats": 55, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 9, "name": "Hooli", "plan": "Scale", "owner": "J. Dubois", "seats": 62, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 10, "name": "Vehement", "plan": "Team", "owner": "P. Novak", "seats": 69, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 11, "name": "Massive Dynamic", "plan": "Enterprise", "owner": "A. Okafor", "seats": 76, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 12, "name": "Cyberdyne", "plan": "Starter", "owner": "R. Silva", "seats": 83, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 13, "name": "Tyrell", "plan": "Scale", "owner": "M. Chen", "seats": 90, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 14, "name": "Aperture", "plan": "Team", "owner": "J. Dubois", "seats": 97, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 15, "name": "Black Mesa", "plan": "Enterprise", "owner": "P. Novak", "seats": 104, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}]';
export const componentProfiles = { export const componentProfiles = {
Scrollspy: {
demos: [
standard(
"Table of contents",
"Each href points at an element on the page. The runtime observes those elements and moves aria-current to the link for whichever one is in view, so the marker follows the reader without any component state.",
{
heading: "On this page",
items: [
{ label: "Overview", href: "#overview" },
{ label: "Installation", href: "#installation" },
{ label: "Usage", href: "#usage" },
{ label: "API", href: "#api" },
],
active: "#overview",
},
),
advanced(
"Compact rail",
"On a phone the rail becomes a horizontal strip that scrolls, so a long contents list costs one line rather than a screenful.",
{
heading: "Sections",
size: "sm",
items: [
{ label: "Getting started", href: "#getting-started" },
{ label: "Configuration", href: "#configuration" },
{ label: "Troubleshooting", href: "#troubleshooting" },
],
active: "#configuration",
},
),
],
},
MegaMenu: {
demos: [
standard(
"Grouped columns",
"One level deep by design: a mega menu shows breadth flat so everything is one click away. Nesting inside the panel would bury content behind hover-within-hover. Use Nav when you want cascading submenus.",
{
label: "Products",
icon: "icon-[lucide--box]",
columns: [
{
heading: "Platform",
items: [
{
label: "Runtime",
href: "/runtime",
description: "The browser half of the framework.",
},
{ label: "Compiler", href: "/compiler", description: "WRN to JavaScript." },
],
},
{
heading: "Tooling",
items: [
{ label: "CLI", href: "/cli", description: "Scaffold, build and deploy." },
{ label: "Editor", href: "/editor", description: "Language server and syntax." },
],
},
],
},
),
advanced(
"With a footer note",
"The panel is anchored, so the runtime clamp pulls it back inside the viewport instead of letting it hang off a wide layout. On a phone it stops floating and joins the flow.",
{
label: "Solutions",
columns: [
{
heading: "By team",
items: [
{ label: "Engineering", href: "/eng", icon: "icon-[lucide--code]" },
{ label: "Design", href: "/design", icon: "icon-[lucide--palette]" },
{ label: "Support", href: "/support", icon: "icon-[lucide--life-buoy]" },
],
},
{
heading: "By size",
items: [
{ label: "Startup", href: "/startup" },
{ label: "Enterprise", href: "/enterprise" },
],
},
],
footer: "Not sure where to start? Talk to us.",
},
),
],
},
Sidebar: {
demos: [
standard(
"Groups and nested levels",
"An entry is a single link, a labelled group, or a branch nested up to three levels. Arrow keys move down the rail.",
{
label: "Workspace",
items: [
{ label: "Dashboard", href: "/", value: "dash", icon: "icon-[lucide--gauge]" },
{
heading: "Projects",
items: [
{ label: "Active", href: "/active", value: "active", badge: "4" },
{
label: "Archive",
value: "archive",
items: [
{ label: "2025", href: "/a/2025", value: "a2025" },
{ label: "2024", href: "/a/2024", value: "a2024" },
],
},
],
},
{
heading: "Account",
items: [
{
label: "Settings",
href: "/settings",
value: "settings",
icon: "icon-[lucide--settings]",
},
],
},
],
active: "active",
},
),
advanced(
"Drawer on small screens",
"Below the tablet breakpoint the rail is replaced by a launcher that opens a Drawer. Composing Drawer rather than reimplementing it means the focus trap and the body scroll lock come from one place.",
{
label: "Operations",
mobileLabel: "Open navigation",
drawerTitle: "Operations",
items: [
{ label: "Queue", href: "/queue", value: "queue", icon: "icon-[lucide--inbox]" },
{
label: "Reports",
href: "/reports",
value: "reports",
icon: "icon-[lucide--chart-no-axes-column]",
},
],
active: "queue",
},
),
],
},
Nav: {
demos: [
standard(
"Horizontal links",
"A flat link bar. The active item is marked with aria-current, and the arrow keys move between items.",
{
items: [
{ label: "Home", href: "/", value: "home", icon: "icon-[lucide--house]" },
{ label: "Inbox", href: "/inbox", value: "inbox", badge: "9" },
{ label: "Reports", href: "/reports", value: "reports" },
{ label: "Archive", href: "/archive", value: "archive", disabled: true },
],
active: "inbox",
},
),
advanced(
"Nested submenus",
"An item carrying its own items array opens a submenu on hover or focus, to a maximum of three levels. On a phone the submenus stack inline instead of floating, because a hover-opened overlay is unreachable on touch.",
{
items: [
{ label: "Home", href: "/", value: "home" },
{
label: "Products",
value: "products",
items: [
{ label: "Overview", href: "/p", value: "p-overview" },
{
label: "Platform",
value: "p-platform",
items: [
{ label: "Runtime", href: "/p/runtime", value: "runtime" },
{ label: "Compiler", href: "/p/compiler", value: "compiler" },
],
},
],
},
],
active: "p-overview",
},
),
standard("Vertical rail", "Vertical orientation switches the arrow keys to up and down.", {
items: [
{ label: "Dashboard", href: "/", value: "dash", icon: "icon-[lucide--gauge]" },
{ label: "Team", href: "/team", value: "team", icon: "icon-[lucide--users]" },
{
label: "Settings",
href: "/settings",
value: "settings",
icon: "icon-[lucide--settings]",
},
],
active: "team",
orientation: "vertical",
collapsible: false,
}),
],
},
Stepper: {
demos: [
standard(
"Horizontal progress",
"Steps before the active one read as complete, the active one is highlighted, and the rest are muted.",
{
steps: [
{ label: "Account", description: "Your details" },
{ label: "Billing", description: "Payment method" },
{ label: "Confirm", description: "Review and submit" },
],
active: 1,
},
),
standard(
"Vertical with icons",
"Vertical orientation suits a sidebar or a narrow column. Any step may carry an iconify class instead of its number.",
{
steps: [
{ label: "Cloned", icon: "icon-[lucide--git-branch]" },
{ label: "Built", icon: "icon-[lucide--hammer]" },
{ label: "Deployed", icon: "icon-[lucide--rocket]" },
],
active: 2,
orientation: "vertical",
},
),
advanced(
"Clickable steps",
"With clickable the steps emit a change output and take arrow-key roving focus. Without it the stepper is read-only and stays out of the tab order.",
{
steps: [{ label: "One" }, { label: "Two" }, { label: "Three" }],
active: 0,
clickable: true,
},
),
],
},
Pagination: {
demos: [
standard(
"Compact arrows",
"The default: previous and next with a page counter, and a summary of the range in view.",
{ page: 2, pageSize: 10, total: 137, variant: "compact" },
),
standard(
"Numbered pages",
"Page numbers windowed around the current page with ellipsis gaps, so a large set never renders hundreds of buttons.",
{ page: 5, pageSize: 10, total: 200, variant: "numbered", siblingCount: 1 },
),
advanced(
"Wider window",
"siblingCount widens how many pages sit either side of the current one.",
{ page: 8, pageSize: 25, total: 900, variant: "numbered", siblingCount: 2 },
),
],
},
DataTable: { DataTable: {
demos: [ demos: [
standard( standard(
@@ -2250,6 +2512,21 @@ export const componentProfiles = {
orientation: "horizontal", orientation: "horizontal",
}, },
), ),
advanced(
"Mirrored into the URL",
"With mode=url the selection is written to a query parameter using pushState, so the panel swaps without a page load, the tab survives a reload, and the back button steps through the tabs you visited.",
{
label: "Account settings",
items: [
{ label: "Account", value: "account", description: "Profile and credentials." },
{ label: "Billing", value: "billing", description: "Invoices and payment method." },
{ label: "Team", value: "team", description: "Members and their roles." },
],
active: "account",
mode: "url",
param: "tab",
},
),
compact( compact(
"Compact application tabs", "Compact application tabs",
"Use smaller tabs in dashboards, panels, and dense product interfaces.", "Use smaller tabs in dashboards, panels, and dense product interfaces.",
@@ -2,7 +2,7 @@
"generatedFrom": "packages/ui/component-reference.json", "generatedFrom": "packages/ui/component-reference.json",
"componentCount": 108, "componentCount": 108,
"categoryCount": 11, "categoryCount": 11,
"totalDemoCount": 421, "totalDemoCount": 419,
"components": [ "components": [
{ {
"name": "Accordion", "name": "Accordion",
@@ -786,11 +786,11 @@
"slug": "mega-menu", "slug": "mega-menu",
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive mega menu component.", "purpose": "Theme-aware, responsive mega menu component.",
"demoCount": 3, "demoCount": 2,
"propCount": 7, "propCount": 8,
"slots": ["default"], "slots": ["default"],
"events": ["open", "close", "select"], "events": ["open", "close", "select"],
"profiled": false "profiled": true
}, },
{ {
"name": "MetricCard", "name": "MetricCard",
@@ -835,10 +835,10 @@
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive nav component.", "purpose": "Theme-aware, responsive nav component.",
"demoCount": 3, "demoCount": 3,
"propCount": 7, "propCount": 9,
"slots": ["default"], "slots": ["default"],
"events": ["select", "change"], "events": ["select"],
"profiled": false "profiled": true
}, },
{ {
"name": "Navbar", "name": "Navbar",
@@ -871,10 +871,10 @@
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive pagination component.", "purpose": "Theme-aware, responsive pagination component.",
"demoCount": 3, "demoCount": 3,
"propCount": 7, "propCount": 12,
"slots": ["default"], "slots": ["default"],
"events": ["change", "previous", "next"], "events": ["change", "previous", "next"],
"profiled": false "profiled": true
}, },
{ {
"name": "PinInput", "name": "PinInput",
@@ -990,11 +990,11 @@
"slug": "scrollspy", "slug": "scrollspy",
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive scrollspy component.", "purpose": "Theme-aware, responsive scrollspy component.",
"demoCount": 3, "demoCount": 2,
"propCount": 7, "propCount": 7,
"slots": ["default"], "slots": ["default"],
"events": ["change"], "events": ["change"],
"profiled": false "profiled": true
}, },
{ {
"name": "SearchBox", "name": "SearchBox",
@@ -1050,11 +1050,11 @@
"slug": "sidebar", "slug": "sidebar",
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive sidebar component.", "purpose": "Theme-aware, responsive sidebar component.",
"demoCount": 3, "demoCount": 2,
"propCount": 8, "propCount": 8,
"slots": ["default"], "slots": ["default", "drawer"],
"events": ["toggle", "open", "close", "select"], "events": ["toggle", "open", "close", "select"],
"profiled": false "profiled": true
}, },
{ {
"name": "Skeleton", "name": "Skeleton",
@@ -1111,10 +1111,10 @@
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive stepper component.", "purpose": "Theme-aware, responsive stepper component.",
"demoCount": 3, "demoCount": 3,
"propCount": 7, "propCount": 8,
"slots": ["default"], "slots": ["step-{index}", "default"],
"events": ["change", "previous", "next", "complete"], "events": ["change"],
"profiled": false "profiled": true
}, },
{ {
"name": "StrongPassword", "name": "StrongPassword",
@@ -1158,10 +1158,10 @@
"slug": "tabs", "slug": "tabs",
"category": "navigation", "category": "navigation",
"purpose": "Switch between related responsive content panels with horizontal or vertical orientation and selection events.", "purpose": "Switch between related responsive content panels with horizontal or vertical orientation and selection events.",
"demoCount": 3, "demoCount": 4,
"propCount": 7, "propCount": 9,
"slots": ["default"], "slots": ["panel-{valueOf(item, index)}", "default"],
"events": [], "events": ["change", "select"],
"profiled": true "profiled": true
}, },
{ {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "wrnexus", "name": "wrnexus",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "An SSR-first full-stack web framework with server-rendered reactive components. Bun-first, Node-friendly.", "description": "An SSR-first full-stack web framework with server-rendered reactive components. Bun-first, Node-friendly.",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/auth", "name": "@wrnexus/auth",
"version": "0.8.4", "version": "0.8.5",
"description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.", "description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/benchmark", "name": "@wrnexus/benchmark",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Deterministic benchmark runner and performance regression budgets for WRNexusJS.", "description": "Deterministic benchmark runner and performance regression budgets for WRNexusJS.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cache", "name": "@wrnexus/cache",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Tag-aware memory and response caching with stale-while-revalidate for WRNexusJS.", "description": "Tag-aware memory and response caching with stale-while-revalidate for WRNexusJS.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/captcha", "name": "@wrnexus/captcha",
"version": "0.8.4", "version": "0.8.5",
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.", "description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+16
View File
@@ -2013,6 +2013,22 @@ const MIGRATIONS: Migration[] = [
// WRNexus Language Support VS Code extension. // WRNexus Language Support VS Code extension.
}, },
}, },
{
version: "0.8.5",
id: "0.8.5-datatable-toaster-overlay-dialogs",
description:
"Adds the DataTable and Toaster components, removes the Table scaffold, and gives modal dialogs focus management, a Tab trap and a scroll lock.",
apply() {
// Applications using <Table> must move to <DataTable>. The two are not
// prop-compatible -- Table took `columns`/`rows` and rendered them as
// plain text, while DataTable owns sorting, filtering, paging and
// selection -- so this is a source change no codemod can make safely.
//
// The `@wrnexus/ui` main entry no longer re-exports the filesystem
// helpers; import them from `@wrnexus/ui/registry` instead. Everything
// else arrives through the dependency update.
},
},
]; ];
/** Release tooling uses this to require an explicit migration entry per version. */ /** Release tooling uses this to require an explicit migration entry per version. */
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/content", "name": "@wrnexus/content",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Typed Markdown content collections, loaders, indexes, feeds, and preview workflows for WRNexusJS.", "description": "Typed Markdown content collections, loaders, indexes, feeds, and preview workflows for WRNexusJS.",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+220 -18
View File
@@ -2908,16 +2908,12 @@ export const REACTIVE_RUNTIME = String.raw`
} }
/* /*
* Modal dialog behaviour: focus, focus restore, a Tab trap and a scroll lock. * Modal dialog focus, focus restore, Tab trap and scroll lock. Shared by
* Modal and Drawer, and here rather than in them because a client function
* cannot hold the previously focused element across a close.
* *
* These live here rather than in Modal.wrn and Drawer.wrn because every one * Fixing focus also repairs Escape: both bind @keydown on their own root,
* of them is the same code, and because a client function cannot hold the * so until focus moved inside, closeOnEscape did nothing.
* "element that had focus before we opened" across a close -- state written
* after an await or inside a callback is dropped.
*
* Fixing focus also repairs Escape. Both components bind @keydown on their
* own root, so the handler only ever runs when focus is inside the dialog;
* until something moved focus there, closeOnEscape did nothing at all.
*/ */
var DIALOG_SELECTOR = '[role="dialog"][aria-modal="true"]'; var DIALOG_SELECTOR = '[role="dialog"][aria-modal="true"]';
var FOCUSABLE_SELECTOR = var FOCUSABLE_SELECTOR =
@@ -2927,11 +2923,13 @@ export const REACTIVE_RUNTIME = String.raw`
var dialogRestoreFocus = null; var dialogRestoreFocus = null;
var dialogScrollLock = null; var dialogScrollLock = null;
// data-show, not measured size: the test DOM reports everything as
// zero-sized, which is what left the trap uncovered.
function isDialogVisible(dialog) { function isDialogVisible(dialog) {
if (!dialog || !dialog.getBoundingClientRect) return false; if (!dialog || !dialog.isConnected) return false;
if (dialog.hasAttribute("hidden")) return false;
if (dialog.closest('[data-show="false"]')) return false; if (dialog.closest('[data-show="false"]')) return false;
var rect = dialog.getBoundingClientRect(); return true;
return rect.width > 0 && rect.height > 0;
} }
function focusableWithin(dialog) { function focusableWithin(dialog) {
@@ -2939,8 +2937,10 @@ export const REACTIVE_RUNTIME = String.raw`
var candidates = dialog.querySelectorAll(FOCUSABLE_SELECTOR); var candidates = dialog.querySelectorAll(FOCUSABLE_SELECTOR);
for (var index = 0; index < candidates.length; index += 1) { for (var index = 0; index < candidates.length; index += 1) {
var candidate = candidates[index]; var candidate = candidates[index];
var rect = candidate.getBoundingClientRect(); // Markers, not measurement.
if (rect.width > 0 || rect.height > 0) found.push(candidate); if (candidate.hasAttribute("hidden")) continue;
if (candidate.closest('[data-show="false"]')) continue;
found.push(candidate);
} }
return found; return found;
} }
@@ -2969,8 +2969,7 @@ export const REACTIVE_RUNTIME = String.raw`
} }
openDialogs.push(dialog); openDialogs.push(dialog);
// Prefer a real control so keyboard users land somewhere useful, and fall // Prefer a real control; the panel carries tabindex="-1" as a fallback.
// back to the panel itself, which carries tabindex="-1" for exactly this.
var targets = focusableWithin(dialog); var targets = focusableWithin(dialog);
var target = targets.length ? targets[0] : dialog; var target = targets.length ? targets[0] : dialog;
if (target && target.focus) target.focus(); if (target && target.focus) target.focus();
@@ -3007,8 +3006,7 @@ export const REACTIVE_RUNTIME = String.raw`
var dialog = openDialogs[openDialogs.length - 1]; var dialog = openDialogs[openDialogs.length - 1];
var targets = focusableWithin(dialog); var targets = focusableWithin(dialog);
if (!targets.length) { if (!targets.length) {
// Nothing to cycle through; keep focus on the panel rather than letting // Nothing to cycle; keep focus on the panel rather than the page behind.
// Tab walk out into the page sitting behind the backdrop.
event.preventDefault(); event.preventDefault();
if (dialog.focus) dialog.focus(); if (dialog.focus) dialog.focus();
return; return;
@@ -3047,6 +3045,208 @@ export const REACTIVE_RUNTIME = String.raw`
syncDialogs(); syncDialogs();
} }
/*
* Roving arrow-key focus. A container marked data-wrn-roving owns its
* [data-wrn-roving-item] descendants: one carries tabindex="0" so Tab
* reaches the group once, and the arrows move within it. Here rather than
* in five components because focus bookkeeping cannot live in component
* state.
*/
var ROVING_SELECTOR = "[data-wrn-roving]";
var ROVING_ITEM_SELECTOR = "[data-wrn-roving-item]";
// Templates stringify these: "" and "false" mean opted out, and a bare
// [attr] selector matches either, so the value must be checked.
// Bare means yes; only an explicit "false" opts out.
function rovingItemOff(value) {
return value === null || value === "false";
}
// The container must name an axis; empty is what {cond ? "x" : ""} emits.
function rovingOrientation(container) {
var value = container.getAttribute("data-wrn-roving");
if (value === null || value === "" || value === "false") return "";
return value;
}
function rovingItems(container) {
var found = [];
var candidates = container.querySelectorAll(ROVING_ITEM_SELECTOR);
for (var index = 0; index < candidates.length; index += 1) {
var candidate = candidates[index];
if (rovingItemOff(candidate.getAttribute("data-wrn-roving-item"))) continue;
// A nested group owns its own items; do not steal them.
if (candidate.closest(ROVING_SELECTOR) !== container) continue;
if (candidate.hasAttribute("disabled")) continue;
if (candidate.getAttribute("aria-disabled") === "true") continue;
// Markers, not measurement (see isDialogVisible).
if (candidate.hasAttribute("hidden")) continue;
if (candidate.closest('[data-show="false"]')) continue;
found.push(candidate);
}
return found;
}
function rovingActiveIndex(items) {
for (var index = 0; index < items.length; index += 1) {
var item = items[index];
if (item.getAttribute("aria-selected") === "true") return index;
var current = item.getAttribute("aria-current");
if (current === "page" || current === "step" || current === "true") return index;
}
return 0;
}
function applyRovingTabindex(items, activeIndex) {
for (var index = 0; index < items.length; index += 1) {
items[index].setAttribute("tabindex", index === activeIndex ? "0" : "-1");
}
}
function syncRovingGroup(container) {
if (!rovingOrientation(container)) return;
var items = rovingItems(container);
if (!items.length) return;
applyRovingTabindex(items, rovingActiveIndex(items));
}
function syncRovingGroups() {
var groups = document.querySelectorAll(ROVING_SELECTOR);
for (var index = 0; index < groups.length; index += 1) syncRovingGroup(groups[index]);
}
function handleRovingKeydown(event) {
var target = event.target;
if (!target || !target.closest) return;
var item = target.closest(ROVING_ITEM_SELECTOR);
if (!item) return;
var container = item.closest(ROVING_SELECTOR);
if (!container) return;
var items = rovingItems(container);
var index = items.indexOf(item);
if (index === -1) return;
var orientation = rovingOrientation(container);
if (!orientation) return;
var horizontal = orientation === "horizontal" || orientation === "both";
var vertical = orientation === "vertical" || orientation === "both";
var key = event.key;
var next = -1;
if ((horizontal && key === "ArrowRight") || (vertical && key === "ArrowDown")) {
next = (index + 1) % items.length;
} else if ((horizontal && key === "ArrowLeft") || (vertical && key === "ArrowUp")) {
next = (index - 1 + items.length) % items.length;
} else if (key === "Home") {
next = 0;
} else if (key === "End") {
next = items.length - 1;
} else {
return;
}
event.preventDefault();
applyRovingTabindex(items, next);
if (items[next].focus) items[next].focus();
}
function setupRovingFocus() {
if (window.__wrnexusRovingBound) return;
window.__wrnexusRovingBound = true;
document.addEventListener("keydown", handleRovingKeydown, true);
if (typeof MutationObserver === "function") {
new MutationObserver(function () {
window.setTimeout(syncRovingGroups, 0);
}).observe(document.documentElement, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ["aria-selected", "aria-current", "disabled", "aria-disabled", "hidden"],
});
}
syncRovingGroups();
}
// Scrollspy. The marker goes onto the links, not into state: an observer
// callback fires after the client function returned, so that write is lost.
function applyScrollspyCurrent(nav, href) {
var links = nav.querySelectorAll('a[href^="#"]');
var changed = false;
var label = "";
for (var index = 0; index < links.length; index += 1) {
var link = links[index];
var current = link.getAttribute("href") === href;
if (current) label = (link.textContent || "").trim();
if ((link.getAttribute("data-active") === "true") !== current) changed = true;
link.setAttribute("data-active", current ? "true" : "false");
link.setAttribute("aria-current", current ? "location" : "false");
}
if (!changed) return;
// Named for the component output so a parent @change binding receives it.
nav.dispatchEvent(new CustomEvent("change", { detail: { href: href, label: label } }));
}
function wireScrollspy(nav) {
if (nav.__wrnScrollspyWired) return;
nav.__wrnScrollspyWired = true;
nav.addEventListener("click", function (event) {
var t = event.target;
var link = t && t.closest ? t.closest('a[href^="#"]') : null;
if (link && nav.contains(link)) applyScrollspyCurrent(nav, link.getAttribute("href"));
});
if (typeof IntersectionObserver === "undefined") return;
var links = nav.querySelectorAll('a[href^="#"]');
var targets = [];
for (var index = 0; index < links.length; index += 1) {
var href = links[index].getAttribute("href");
var section = document.getElementById(href.slice(1));
if (section) targets.push({ section: section, href: href });
}
if (!targets.length) return;
var visible = {};
var observer = new IntersectionObserver(
function (entries) {
for (var entryIndex = 0; entryIndex < entries.length; entryIndex += 1) {
visible[entries[entryIndex].target.id] = entries[entryIndex].isIntersecting;
}
// First visible section in document order wins, so up and down
// settle on the same link.
for (var pick = 0; pick < targets.length; pick += 1) {
if (visible[targets[pick].section.id]) {
applyScrollspyCurrent(nav, targets[pick].href);
return;
}
}
},
// Biased to the upper third: the current section is the one being read.
{ rootMargin: "-80px 0px -55% 0px" },
);
for (var watch = 0; watch < targets.length; watch += 1) observer.observe(targets[watch].section);
}
function setupScrollspy() {
if (window.__wrnexusScrollspyBound) return;
window.__wrnexusScrollspyBound = true;
var wireAll = function () {
var navs = document.querySelectorAll("[data-wrn-scrollspy]");
for (var index = 0; index < navs.length; index += 1) wireScrollspy(navs[index]);
};
wireAll();
if (typeof MutationObserver === "function") {
new MutationObserver(function () {
window.setTimeout(wireAll, 0);
}).observe(document.documentElement, { subtree: true, childList: true });
}
}
function hydrateScopes(root) { function hydrateScopes(root) {
var host = root || document; var host = root || document;
@@ -5264,6 +5464,8 @@ export const REACTIVE_RUNTIME = String.raw`
window.__wrnexusHydrateAsyncBoundaries = hydrateAsyncBoundaries; window.__wrnexusHydrateAsyncBoundaries = hydrateAsyncBoundaries;
setupAnchoredOverlays(); setupAnchoredOverlays();
setupModalDialogs(); setupModalDialogs();
setupRovingFocus();
setupScrollspy();
window.__wrnexusRepositionAnchored = repositionAnchored; window.__wrnexusRepositionAnchored = repositionAnchored;
window.__wrnexusHydrateScopes = hydrateScopes; window.__wrnexusHydrateScopes = hydrateScopes;
window.__wrnexusInvalidateClientModule = function (url) { clientModuleCache.delete(url); }; window.__wrnexusInvalidateClientModule = function (url) { clientModuleCache.delete(url); };
+158
View File
@@ -894,3 +894,161 @@ test("comments are ignored inside interpreted statements", () => {
(win.document.querySelector("button") as unknown as HTMLElement).click(); (win.document.querySelector("button") as unknown as HTMLElement).click();
expect(win.document.querySelector("#out")?.textContent).toBe("2"); expect(win.document.querySelector("#out")?.textContent).toBe("2");
}); });
/*
* happy-dom builds its own KeyboardEvent, which is structurally distinct from
* the DOM lib Event that dispatchEvent is typed against. Same cast the window
* dispatches above use, kept in one place.
*/
function keydown(win: Window, key: string): Event {
const ctor = (win as unknown as { KeyboardEvent: new (type: string, init: unknown) => unknown })
.KeyboardEvent;
return new ctor("keydown", { key, bubbles: true }) as unknown as Event;
}
test("roving focus moves with arrow keys and wraps", () => {
const win = mount(
`<div data-wrn-roving="horizontal">
<button data-wrn-roving-item id="a">A</button>
<button data-wrn-roving-item id="b">B</button>
<button data-wrn-roving-item id="c">C</button>
</div>`,
);
const doc = win.document;
const a = doc.querySelector("#a") as unknown as HTMLElement;
const c = doc.querySelector("#c") as unknown as HTMLElement;
expect(a.getAttribute("tabindex")).toBe("0");
expect(doc.querySelector("#b")!.getAttribute("tabindex")).toBe("-1");
a.focus();
a.dispatchEvent(keydown(win, "ArrowRight"));
expect(doc.activeElement!.id).toBe("b");
expect(doc.querySelector("#b")!.getAttribute("tabindex")).toBe("0");
expect(a.getAttribute("tabindex")).toBe("-1");
(doc.querySelector("#b") as unknown as HTMLElement).dispatchEvent(keydown(win, "ArrowLeft"));
expect(doc.activeElement!.id).toBe("a");
a.dispatchEvent(keydown(win, "ArrowLeft"));
expect(doc.activeElement!.id).toBe("c");
c.dispatchEvent(keydown(win, "ArrowRight"));
expect(doc.activeElement!.id).toBe("a");
});
test("roving focus honours Home and End and skips disabled items", () => {
const win = mount(
`<div data-wrn-roving="vertical">
<button data-wrn-roving-item id="a">A</button>
<button data-wrn-roving-item id="b" disabled>B</button>
<button data-wrn-roving-item id="c">C</button>
</div>`,
);
const doc = win.document;
const a = doc.querySelector("#a") as unknown as HTMLElement;
a.focus();
a.dispatchEvent(keydown(win, "ArrowDown"));
expect(doc.activeElement!.id).toBe("c");
(doc.querySelector("#c") as unknown as HTMLElement).dispatchEvent(keydown(win, "Home"));
expect(doc.activeElement!.id).toBe("a");
a.dispatchEvent(keydown(win, "End"));
expect(doc.activeElement!.id).toBe("c");
});
test("horizontal roving ignores vertical arrows so the page still scrolls", () => {
const win = mount(
`<div data-wrn-roving="horizontal">
<button data-wrn-roving-item id="a">A</button>
<button data-wrn-roving-item id="b">B</button>
</div>`,
);
const a = win.document.querySelector("#a") as unknown as HTMLElement;
a.focus();
a.dispatchEvent(keydown(win, "ArrowDown"));
expect(win.document.activeElement!.id).toBe("a");
});
test("roving tabindex starts on the selected item, not the first", () => {
const win = mount(
`<div data-wrn-roving="horizontal">
<button data-wrn-roving-item id="a">A</button>
<button data-wrn-roving-item id="b" aria-selected="true">B</button>
</div>`,
);
expect(win.document.querySelector("#b")!.getAttribute("tabindex")).toBe("0");
expect(win.document.querySelector("#a")!.getAttribute("tabindex")).toBe("-1");
});
test("nested roving groups do not capture the outer group items", () => {
const win = mount(
`<div data-wrn-roving="horizontal" id="outer">
<button data-wrn-roving-item id="a">A</button>
<div data-wrn-roving="vertical" id="inner">
<button data-wrn-roving-item id="x">X</button>
<button data-wrn-roving-item id="y">Y</button>
</div>
<button data-wrn-roving-item id="b">B</button>
</div>`,
);
const doc = win.document;
const a = doc.querySelector("#a") as unknown as HTMLElement;
a.focus();
a.dispatchEvent(keydown(win, "ArrowRight"));
expect(doc.activeElement!.id).toBe("b");
});
test("opening a modal dialog traps Tab inside it", () => {
const win = mount(
`<div>
<button id="outside">Outside</button>
<div data-show="true">
<section role="dialog" aria-modal="true" tabindex="-1">
<button id="first">First</button>
<button id="last">Last</button>
</section>
</div>
</div>`,
);
const doc = win.document;
const last = doc.querySelector("#last") as unknown as HTMLElement;
last.focus();
last.dispatchEvent(keydown(win, "Tab"));
expect(doc.activeElement!.id).toBe("first");
});
test("a hidden dialog does not trap Tab", () => {
const win = mount(
`<div>
<button id="outside">Outside</button>
<div data-show="false">
<section role="dialog" aria-modal="true" tabindex="-1">
<button id="first">First</button>
</section>
</div>
</div>`,
);
const doc = win.document;
const outside = doc.querySelector("#outside") as unknown as HTMLElement;
outside.focus();
outside.dispatchEvent(keydown(win, "Tab"));
expect(doc.activeElement!.id).toBe("outside");
});
test("an empty or false roving attribute opts the group out entirely", () => {
const win = mount(
`<div data-wrn-roving="">
<button data-wrn-roving-item="false" id="a">A</button>
<button data-wrn-roving-item="false" id="b">B</button>
</div>`,
);
const doc = win.document;
const a = doc.querySelector("#a") as unknown as HTMLElement;
expect(a.getAttribute("tabindex")).toBeNull();
a.focus();
a.dispatchEvent(keydown(win, "ArrowRight"));
expect(doc.activeElement!.id).toBe("a");
});
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/graphql", "name": "@wrnexus/graphql",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/identity", "name": "@wrnexus/identity",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Enterprise federation, provisioning, machine identity, and privacy governance for WRNexusJS.", "description": "Enterprise federation, provisioning, machine identity, and privacy governance for WRNexusJS.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/image", "name": "@wrnexus/image",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Responsive image planning, secure remote image policies, and performance auditing for WRNexusJS.", "description": "Responsive image planning, secure remote image policies, and performance auditing for WRNexusJS.",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/language-server", "name": "@wrnexus/language-server",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Editor-neutral Language Server Protocol implementation for WRNexus .wrn files.", "description": "Editor-neutral Language Server Protocol implementation for WRNexus .wrn files.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mcp", "name": "@wrnexus/mcp",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Model Context Protocol server exposing WRNexus application and framework context.", "description": "Model Context Protocol server exposing WRNexus application and framework context.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/observability", "name": "@wrnexus/observability",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Metrics, Web Vitals collection, request instrumentation, and exporter adapters for WRNexusJS.", "description": "Metrics, Web Vitals collection, request instrumentation, and exporter adapters for WRNexusJS.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/playground", "name": "@wrnexus/playground",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Secure, shareable WRNexus compiler and UI playground.", "description": "Secure, shareable WRNexus compiler and UI playground.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/plugin", "name": "@wrnexus/plugin",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pwa", "name": "@wrnexus/pwa",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"description": "Service workers, manifests, offline queues, background sync, push, and conflict resolution for WRNexusJS.", "description": "Service workers, manifests, offline queues, background sync, push, and conflict resolution for WRNexusJS.",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.8.4", "version": "0.8.5",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.8.4", "version": "0.8.5",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {

Some files were not shown because too many files have changed in this diff Show More