feat: support WRN imports and dynamic public shell
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ai",
|
"name": "@wrnexus/ai",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"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",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/authz",
|
"name": "@wrnexus/authz",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
+11
-11
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/cli",
|
"name": "@wrnexus/cli",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/cli — part of the WrNexus framework.",
|
"description": "@wrnexus/cli — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -28,16 +28,16 @@
|
|||||||
"wrnexus": "./dist/index.js"
|
"wrnexus": "./dist/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.73",
|
"@wrnexus/core": "^0.2.74",
|
||||||
"@wrnexus/router": "^0.2.73",
|
"@wrnexus/router": "^0.2.74",
|
||||||
"@wrnexus/csr": "^0.2.73",
|
"@wrnexus/csr": "^0.2.74",
|
||||||
"@wrnexus/compiler": "^0.2.73",
|
"@wrnexus/compiler": "^0.2.74",
|
||||||
"@wrnexus/styles": "^0.2.73",
|
"@wrnexus/styles": "^0.2.74",
|
||||||
"@wrnexus/dev-server": "^0.2.73",
|
"@wrnexus/dev-server": "^0.2.74",
|
||||||
"@wrnexus/ui": "^0.2.73",
|
"@wrnexus/ui": "^0.2.74",
|
||||||
"@wrnexus/validation": "^0.2.73",
|
"@wrnexus/validation": "^0.2.74",
|
||||||
"@wrnexus/i18n": "^0.2.73",
|
"@wrnexus/i18n": "^0.2.74",
|
||||||
"@wrnexus/db": "^0.2.73"
|
"@wrnexus/db": "^0.2.74"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
+26
-13
@@ -8,6 +8,19 @@ Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web fr
|
|||||||
|
|
||||||
`@wrnexus/compiler` turns `.wrn` source into TypeScript that targets the framework's runtime primitives. A `.wrn` file declares either a `page` (a route) or a `component` (a reusable, prop-driven fragment) with blocks for `state`, `view` (plain HTML), `seo`, `style`, `functions`, `api`, `ssr`/`client` data bindings, and `realtime` websocket handlers. The pipeline is `source → Lexer → parse() → PageAst → generate() → TypeScript`. It is a build/server-side library — the WrNexus dev loader calls it to compile `.wrn` files on the fly, surfacing `ParseError` as a readable error page.
|
`@wrnexus/compiler` turns `.wrn` source into TypeScript that targets the framework's runtime primitives. A `.wrn` file declares either a `page` (a route) or a `component` (a reusable, prop-driven fragment) with blocks for `state`, `view` (plain HTML), `seo`, `style`, `functions`, `api`, `ssr`/`client` data bindings, and `realtime` websocket handlers. The pipeline is `source → Lexer → parse() → PageAst → generate() → TypeScript`. It is a build/server-side library — the WrNexus dev loader calls it to compile `.wrn` files on the fly, surfacing `ParseError` as a readable error page.
|
||||||
|
|
||||||
|
Static ES module imports may appear before the root declaration. Imported values are
|
||||||
|
available to server-rendered expressions, including component props:
|
||||||
|
|
||||||
|
```wrn
|
||||||
|
import { appUrl } from "@wrnexus/helpers";
|
||||||
|
|
||||||
|
layout PublicLayout {
|
||||||
|
view {
|
||||||
|
<PublicHeader signInHref="{appUrl('sso', '/sign-in')}" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -76,19 +89,19 @@ class Lexer {
|
|||||||
|
|
||||||
Exported type-only symbols describing the parsed tree:
|
Exported type-only symbols describing the parsed tree:
|
||||||
|
|
||||||
| Type | Description |
|
| Type | Description |
|
||||||
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `PageAst` | Root node including `kind`, `name`, `types`, typed `props`, typed `states`, `view`, styles, functions, data APIs, lifecycle, and routes. |
|
| `PageAst` | Root node including top-level `imports`, `kind`, `name`, `types`, typed `props`, typed `states`, `view`, styles, functions, data APIs, lifecycle, and routes. |
|
||||||
| `ViewNode` | `{ type: "text"; value }` or `{ type: "element"; tag; attrs; children }`. |
|
| `ViewNode` | `{ type: "text"; value }` or `{ type: "element"; tag; attrs; children }`. |
|
||||||
| `Attr` | `{ name; value; event; boolean? }` — `event` marks `@event` bindings. |
|
| `Attr` | `{ name; value; event; boolean? }` — `event` marks `@event` bindings. |
|
||||||
| `StateDecl` | `{ name; valueType?; expr }` — a typed `state x: Type = <expr>` declaration. |
|
| `StateDecl` | `{ name; valueType?; expr }` — a typed `state x: Type = <expr>` declaration. |
|
||||||
| `PropDecl` | `{ name; valueType?; required; default }` — a typed prop declaration. |
|
| `PropDecl` | `{ name; valueType?; required; default }` — a typed prop declaration. |
|
||||||
| `SeoBlock` | `Record<string, string>` from the `seo { ... }` block. |
|
| `SeoBlock` | `Record<string, string>` from the `seo { ... }` block. |
|
||||||
| `ApiBlock` | `{ method; path; body }` — a top-level `api METHOD /path { ... }`. |
|
| `ApiBlock` | `{ method; path; body }` — a top-level `api METHOD /path { ... }`. |
|
||||||
| `DataApiBlock` | `{ mode; name; method; path; body }` — an `api` inside an `ssr`/`client` block. |
|
| `DataApiBlock` | `{ mode; name; method; path; body }` — an `api` inside an `ssr`/`client` block. |
|
||||||
| `DataMode` | `"ssr" \| "client"`. |
|
| `DataMode` | `"ssr" \| "client"`. |
|
||||||
| `ModeFunctionsBlock` | `{ mode; body }` — a `functions { ... }` inside an `ssr`/`client` block. |
|
| `ModeFunctionsBlock` | `{ mode; body }` — a `functions { ... }` inside an `ssr`/`client` block. |
|
||||||
| `RealtimeBlock` | `{ name; handlers }` — a `realtime <name> { on evt(args) { ... } }` block. |
|
| `RealtimeBlock` | `{ name; handlers }` — a `realtime <name> { on evt(args) { ... } }` block. |
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/core",
|
"name": "@wrnexus/core",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/core — part of the WrNexus framework.",
|
"description": "@wrnexus/core — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/csr",
|
"name": "@wrnexus/csr",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/csr — part of the WrNexus framework.",
|
"description": "@wrnexus/csr — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.73"
|
"@wrnexus/core": "^0.2.74"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/db",
|
"name": "@wrnexus/db",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/db — part of the WrNexus framework.",
|
"description": "@wrnexus/db — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/dev-server",
|
"name": "@wrnexus/dev-server",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"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",
|
||||||
@@ -25,18 +25,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.73",
|
"@wrnexus/core": "^0.2.74",
|
||||||
"@wrnexus/router": "^0.2.73",
|
"@wrnexus/router": "^0.2.74",
|
||||||
"@wrnexus/ssr": "^0.2.73",
|
"@wrnexus/ssr": "^0.2.74",
|
||||||
"@wrnexus/csr": "^0.2.73",
|
"@wrnexus/csr": "^0.2.74",
|
||||||
"@wrnexus/compiler": "^0.2.73",
|
"@wrnexus/compiler": "^0.2.74",
|
||||||
"@wrnexus/styles": "^0.2.73",
|
"@wrnexus/styles": "^0.2.74",
|
||||||
"@wrnexus/ui": "^0.2.73",
|
"@wrnexus/ui": "^0.2.74",
|
||||||
"@wrnexus/validation": "^0.2.73",
|
"@wrnexus/validation": "^0.2.74",
|
||||||
"@wrnexus/i18n": "^0.2.73",
|
"@wrnexus/i18n": "^0.2.74",
|
||||||
"@wrnexus/db": "^0.2.73",
|
"@wrnexus/db": "^0.2.74",
|
||||||
"@wrnexus/pubsub": "^0.2.73",
|
"@wrnexus/pubsub": "^0.2.74",
|
||||||
"@wrnexus/uploader": "^0.2.73"
|
"@wrnexus/uploader": "^0.2.74"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/encryption",
|
"name": "@wrnexus/encryption",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/encryption — part of the WrNexus framework.",
|
"description": "@wrnexus/encryption — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/helpers",
|
"name": "@wrnexus/helpers",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"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",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.73"
|
"@wrnexus/core": "^0.2.74"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/i18n",
|
"name": "@wrnexus/i18n",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/i18n — part of the WrNexus framework.",
|
"description": "@wrnexus/i18n — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.73"
|
"@wrnexus/core": "^0.2.74"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/jwt",
|
"name": "@wrnexus/jwt",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/jwt — part of the WrNexus framework.",
|
"description": "@wrnexus/jwt — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/mobile",
|
"name": "@wrnexus/mobile",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/mobile — part of the WrNexus framework.",
|
"description": "@wrnexus/mobile — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/native": "^0.2.73"
|
"@wrnexus/native": "^0.2.74"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/native",
|
"name": "@wrnexus/native",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/native — part of the WrNexus framework.",
|
"description": "@wrnexus/native — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/oauth",
|
"name": "@wrnexus/oauth",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/pubsub",
|
"name": "@wrnexus/pubsub",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/queue",
|
"name": "@wrnexus/queue",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/reactive",
|
"name": "@wrnexus/reactive",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/router",
|
"name": "@wrnexus/router",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/router — part of the WrNexus framework.",
|
"description": "@wrnexus/router — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/compiler": "^0.2.73",
|
"@wrnexus/compiler": "^0.2.74",
|
||||||
"@wrnexus/core": "^0.2.73"
|
"@wrnexus/core": "^0.2.74"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ssr",
|
"name": "@wrnexus/ssr",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/ssr — part of the WrNexus framework.",
|
"description": "@wrnexus/ssr — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.73"
|
"@wrnexus/core": "^0.2.74"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/styles",
|
"name": "@wrnexus/styles",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/styles — part of the WrNexus framework.",
|
"description": "@wrnexus/styles — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/uploader": "^0.2.73"
|
"@wrnexus/uploader": "^0.2.74"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/test",
|
"name": "@wrnexus/test",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/tracking",
|
"name": "@wrnexus/tracking",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -3253,7 +3253,7 @@ Reusable progress bar component.
|
|||||||
Reusable public footer component.
|
Reusable public footer component.
|
||||||
|
|
||||||
- Mount: `data-component="PublicFooter"`
|
- Mount: `data-component="PublicFooter"`
|
||||||
- Props: `class: string = ""`, `homeHref: string = "/"`, `brandName: string = "WrNexus"`, `brandTagline: string = "Identity Cloud"`, `brandAriaLabel: string = "WrNexus home"`, `newsletterEyebrow: string = "WrNexus updates"`, `newsletterTitle: string = "Identity insights delivered to your inbox"`, `newsletterDescription: string = "Get product updates, security guidance, implementation strategies, and practical identity architecture resources."`, `newsletterAction: string = "/api/newsletter/subscribe"`, `newsletterButtonLabel: string = "Subscribe"`, `newsletterPlaceholder: string = "Enter your work email"`, `newsletterSuccessMessage: string = "Thanks. Please check your inbox to confirm your subscription."`, `copyrightText: string = "© 2026 WrNexus. All rights reserved."`, `attributionText: string = "Built by WorkRoot Workspace."`, `showNewsletter: boolean = true`, `showSocialLinks: boolean = true`, `showThemeToggle: boolean = true`
|
- Props: `class: string = ""`, `homeHref: string = "/"`, `brandName: string = "WrNexus"`, `brandTagline: string = "Identity Cloud"`, `brandAriaLabel: string = "WrNexus home"`, `brandIcon: string = "icon-[lucide--blocks]"`, `brandDescription: string = "Secure authentication, user management, organizations, authorization, and enterprise identity for modern applications."`, `statusLabel: string = "All systems operational"`, `statusHref: string = "/status"`, `newsletterEyebrow: string = "WrNexus updates"`, `newsletterTitle: string = "Identity insights delivered to your inbox"`, `newsletterDescription: string = "Get product updates, security guidance, implementation strategies, and practical identity architecture resources."`, `newsletterAction: string = "/api/newsletter/subscribe"`, `newsletterButtonLabel: string = "Subscribe"`, `newsletterPlaceholder: string = "Enter your work email"`, `newsletterSuccessMessage: string = "Thanks. Please check your inbox to confirm your subscription."`, `newsletterPrivacyLabel: string = "privacy policy"`, `newsletterPrivacyHref: string = "/privacy"`, `newsletterFinePrintPrefix: string = "No spam. Unsubscribe at any time. Read our"`, `newsletterFinePrintSuffix: string = "."`, `newsletterEmailLabel: string = "Work email address"`, `newsletterEmailName: string = "email"`, `copyrightText: string = "© 2026 WrNexus. All rights reserved."`, `attributionText: string = "Built by WorkRoot Workspace."`, `showNewsletter: boolean = true`, `showSocialLinks: boolean = true`, `showThemeToggle: boolean = true`, `showStatus: boolean = true`, `showCookiePreferences: boolean = true`, `cookiePreferencesLabel: string = "Cookie preferences"`, `themeLabel: string = "Theme"`, `themeToggleLabel: string = "Toggle color theme"`, `legalTitle: string = "Legal"`, `socialLinks: PublicFooterLink[] = []`, `navigationColumns: PublicFooterColumn[] = []`, `legalLinks: PublicFooterLink[] = []`, `navigationAriaLabel: string = "Footer navigation"`
|
||||||
- Slots: `navigation`
|
- Slots: `navigation`
|
||||||
- Events: `submit`
|
- Events: `submit`
|
||||||
|
|
||||||
@@ -7203,7 +7203,7 @@ Reusable product page shell component.
|
|||||||
Reusable public header component.
|
Reusable public header component.
|
||||||
|
|
||||||
- Mount: `data-component="PublicHeader"`
|
- Mount: `data-component="PublicHeader"`
|
||||||
- Props: `class: string = ""`, `homeHref: string = "/"`, `brandName: string = "WrNexus"`, `brandTagline: string = "Identity Cloud"`, `brandAriaLabel: string = "WrNexus home"`, `brandIcon: string = "icon-[lucide--blocks]"`, `pricingLabel: string = "Pricing"`, `pricingHref: string = "/pricing"`, `statusLabel: string = "All systems operational"`, `statusHref: string = "/status"`, `signInLabel: string = "Sign in"`, `signInHref: string = "/sign-in"`, `primaryLabel: string = "Start free"`, `primaryHref: string = "/sign-up"`, `showStatus: boolean = true`, `showThemeToggle: boolean = true`, `showSignIn: boolean = true`, `showPrimaryAction: boolean = true`, `productLabel: string = "Product"`, `solutionsLabel: string = "Solutions"`, `developersLabel: string = "Developers"`, `resourcesLabel: string = "Resources"`
|
- Props: `class: string = ""`, `homeHref: string = "/"`, `brandName: string = "WrNexus"`, `brandTagline: string = "Identity Cloud"`, `brandAriaLabel: string = "WrNexus home"`, `brandIcon: string = "icon-[lucide--blocks]"`, `pricingLabel: string = "Pricing"`, `pricingHref: string = "/pricing"`, `statusLabel: string = "All systems operational"`, `statusHref: string = "/status"`, `signInLabel: string = "Sign in"`, `signInHref: string = "/sign-in"`, `primaryLabel: string = "Start free"`, `primaryHref: string = "/sign-up"`, `showStatus: boolean = true`, `showThemeToggle: boolean = true`, `showSignIn: boolean = true`, `showPrimaryAction: boolean = true`, `productLabel: string = "Product"`, `solutionsLabel: string = "Solutions"`, `developersLabel: string = "Developers"`, `resourcesLabel: string = "Resources"`, `navigationAriaLabel: string = "Main navigation"`, `themeToggleLabel: string = "Toggle color theme"`, `mobileMenuOpenLabel: string = "Open navigation menu"`, `mobileMenuCloseLabel: string = "Close navigation menu"`, `navigationItems: PublicHeaderNavigationItem[] = []`, `actionItems: PublicHeaderActionItem[] = []`, `showNavigation: boolean = true`, `showMobileThemeToggle: boolean = true`
|
||||||
- Slots: `navigation`, `actions`
|
- Slots: `navigation`, `actions`
|
||||||
- Events: `click`
|
- Events: `click`
|
||||||
|
|
||||||
|
|||||||
@@ -33954,6 +33954,30 @@
|
|||||||
"required": false,
|
"required": false,
|
||||||
"default": "\"WrNexus home\""
|
"default": "\"WrNexus home\""
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "brandIcon",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"icon-[lucide--blocks]\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "brandDescription",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Secure authentication, user management, organizations, authorization, and enterprise identity for modern applications.\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "statusLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"All systems operational\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "statusHref",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"/status\""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "newsletterEyebrow",
|
"name": "newsletterEyebrow",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -33996,6 +34020,42 @@
|
|||||||
"required": false,
|
"required": false,
|
||||||
"default": "\"Thanks. Please check your inbox to confirm your subscription.\""
|
"default": "\"Thanks. Please check your inbox to confirm your subscription.\""
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterPrivacyLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"privacy policy\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterPrivacyHref",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"/privacy\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterFinePrintPrefix",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"No spam. Unsubscribe at any time. Read our\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterFinePrintSuffix",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\".\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterEmailLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Work email address\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterEmailName",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"email\""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "copyrightText",
|
"name": "copyrightText",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -34025,6 +34085,66 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"required": false,
|
"required": false,
|
||||||
"default": "true"
|
"default": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "showStatus",
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false,
|
||||||
|
"default": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "showCookiePreferences",
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false,
|
||||||
|
"default": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cookiePreferencesLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Cookie preferences\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "themeLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Theme\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "themeToggleLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Toggle color theme\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "legalTitle",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Legal\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "socialLinks",
|
||||||
|
"type": "PublicFooterLink[]",
|
||||||
|
"required": false,
|
||||||
|
"default": "[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "navigationColumns",
|
||||||
|
"type": "PublicFooterColumn[]",
|
||||||
|
"required": false,
|
||||||
|
"default": "[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "legalLinks",
|
||||||
|
"type": "PublicFooterLink[]",
|
||||||
|
"required": false,
|
||||||
|
"default": "[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "navigationAriaLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Footer navigation\""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"slots": ["navigation"],
|
"slots": ["navigation"],
|
||||||
@@ -34168,6 +34288,54 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"required": false,
|
"required": false,
|
||||||
"default": "\"Resources\""
|
"default": "\"Resources\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "navigationAriaLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Main navigation\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "themeToggleLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Toggle color theme\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mobileMenuOpenLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Open navigation menu\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mobileMenuCloseLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Close navigation menu\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "navigationItems",
|
||||||
|
"type": "PublicHeaderNavigationItem[]",
|
||||||
|
"required": false,
|
||||||
|
"default": "[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "actionItems",
|
||||||
|
"type": "PublicHeaderActionItem[]",
|
||||||
|
"required": false,
|
||||||
|
"default": "[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "showNavigation",
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false,
|
||||||
|
"default": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "showMobileThemeToggle",
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false,
|
||||||
|
"default": "true"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"slots": ["navigation", "actions"],
|
"slots": ["navigation", "actions"],
|
||||||
|
|||||||
@@ -1,10 +1,27 @@
|
|||||||
component PublicFooter {
|
component PublicFooter {
|
||||||
|
types {
|
||||||
|
interface PublicFooterLink {
|
||||||
|
label: string
|
||||||
|
href: string
|
||||||
|
ariaLabel?: string
|
||||||
|
target?: string
|
||||||
|
icon?: string
|
||||||
|
}
|
||||||
|
interface PublicFooterColumn {
|
||||||
|
title: string
|
||||||
|
links: PublicFooterLink[]
|
||||||
|
}
|
||||||
|
}
|
||||||
props {
|
props {
|
||||||
class: string = ""
|
class: string = ""
|
||||||
homeHref: string = "/"
|
homeHref: string = "/"
|
||||||
brandName: string = "WrNexus"
|
brandName: string = "WrNexus"
|
||||||
brandTagline: string = "Identity Cloud"
|
brandTagline: string = "Identity Cloud"
|
||||||
brandAriaLabel: string = "WrNexus home"
|
brandAriaLabel: string = "WrNexus home"
|
||||||
|
brandIcon: string = "icon-[lucide--blocks]"
|
||||||
|
brandDescription: string = "Secure authentication, user management, organizations, authorization, and enterprise identity for modern applications."
|
||||||
|
statusLabel: string = "All systems operational"
|
||||||
|
statusHref: string = "/status"
|
||||||
newsletterEyebrow: string = "WrNexus updates"
|
newsletterEyebrow: string = "WrNexus updates"
|
||||||
newsletterTitle: string = "Identity insights delivered to your inbox"
|
newsletterTitle: string = "Identity insights delivered to your inbox"
|
||||||
newsletterDescription: string = "Get product updates, security guidance, implementation strategies, and practical identity architecture resources."
|
newsletterDescription: string = "Get product updates, security guidance, implementation strategies, and practical identity architecture resources."
|
||||||
@@ -12,11 +29,27 @@ component PublicFooter {
|
|||||||
newsletterButtonLabel: string = "Subscribe"
|
newsletterButtonLabel: string = "Subscribe"
|
||||||
newsletterPlaceholder: string = "Enter your work email"
|
newsletterPlaceholder: string = "Enter your work email"
|
||||||
newsletterSuccessMessage: string = "Thanks. Please check your inbox to confirm your subscription."
|
newsletterSuccessMessage: string = "Thanks. Please check your inbox to confirm your subscription."
|
||||||
|
newsletterPrivacyLabel: string = "privacy policy"
|
||||||
|
newsletterPrivacyHref: string = "/privacy"
|
||||||
|
newsletterFinePrintPrefix: string = "No spam. Unsubscribe at any time. Read our"
|
||||||
|
newsletterFinePrintSuffix: string = "."
|
||||||
|
newsletterEmailLabel: string = "Work email address"
|
||||||
|
newsletterEmailName: string = "email"
|
||||||
copyrightText: string = "© 2026 WrNexus. All rights reserved."
|
copyrightText: string = "© 2026 WrNexus. All rights reserved."
|
||||||
attributionText: string = "Built by WorkRoot Workspace."
|
attributionText: string = "Built by WorkRoot Workspace."
|
||||||
showNewsletter: boolean = true
|
showNewsletter: boolean = true
|
||||||
showSocialLinks: boolean = true
|
showSocialLinks: boolean = true
|
||||||
showThemeToggle: boolean = true
|
showThemeToggle: boolean = true
|
||||||
|
showStatus: boolean = true
|
||||||
|
showCookiePreferences: boolean = true
|
||||||
|
cookiePreferencesLabel: string = "Cookie preferences"
|
||||||
|
themeLabel: string = "Theme"
|
||||||
|
themeToggleLabel: string = "Toggle color theme"
|
||||||
|
legalTitle: string = "Legal"
|
||||||
|
socialLinks: PublicFooterLink[] = []
|
||||||
|
navigationColumns: PublicFooterColumn[] = []
|
||||||
|
legalLinks: PublicFooterLink[] = []
|
||||||
|
navigationAriaLabel: string = "Footer navigation"
|
||||||
}
|
}
|
||||||
state email = ""
|
state email = ""
|
||||||
state newsletterSubmitted = false
|
state newsletterSubmitted = false
|
||||||
@@ -81,7 +114,7 @@ component PublicFooter {
|
|||||||
class="sr-only"
|
class="sr-only"
|
||||||
for="footer-newsletter-email"
|
for="footer-newsletter-email"
|
||||||
>
|
>
|
||||||
Work email address
|
{newsletterEmailLabel}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="relative min-w-0 flex-1">
|
<div class="relative min-w-0 flex-1">
|
||||||
@@ -89,7 +122,7 @@ component PublicFooter {
|
|||||||
|
|
||||||
<input
|
<input
|
||||||
id="footer-newsletter-email"
|
id="footer-newsletter-email"
|
||||||
name="email"
|
name="{newsletterEmailName}"
|
||||||
type="email"
|
type="email"
|
||||||
autocomplete="email"
|
autocomplete="email"
|
||||||
required
|
required
|
||||||
@@ -113,13 +146,13 @@ component PublicFooter {
|
|||||||
<p class="mt-3 flex items-start gap-2 text-xs leading-5 text-slate-500 dark:text-slate-400">
|
<p class="mt-3 flex items-start gap-2 text-xs leading-5 text-slate-500 dark:text-slate-400">
|
||||||
<span class="icon-[lucide--shield-check] mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true" ></span>
|
<span class="icon-[lucide--shield-check] mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true" ></span>
|
||||||
|
|
||||||
No spam. Unsubscribe at any time. Read our
|
{newsletterFinePrintPrefix}
|
||||||
<a
|
<a
|
||||||
href="/privacy"
|
href="{newsletterPrivacyHref}"
|
||||||
class="font-semibold text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="font-semibold text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
||||||
>
|
>
|
||||||
privacy policy
|
{newsletterPrivacyLabel}
|
||||||
</a>.
|
</a>{newsletterFinePrintSuffix}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -148,7 +181,7 @@ component PublicFooter {
|
|||||||
<span
|
<span
|
||||||
class="grid h-11 w-11 place-items-center rounded-2xl bg-indigo-600 text-white shadow-lg shadow-indigo-600/20 transition group-hover:-translate-y-0.5 group-hover:bg-indigo-500"
|
class="grid h-11 w-11 place-items-center rounded-2xl bg-indigo-600 text-white shadow-lg shadow-indigo-600/20 transition group-hover:-translate-y-0.5 group-hover:bg-indigo-500"
|
||||||
>
|
>
|
||||||
<span class="icon-[lucide--blocks] h-5 w-5" aria-hidden="true" ></span>
|
<span class="{brandIcon} h-5 w-5" aria-hidden="true" ></span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
@@ -165,13 +198,12 @@ component PublicFooter {
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p class="mt-5 max-w-sm text-sm leading-6 text-slate-600 dark:text-slate-400">
|
<p class="mt-5 max-w-sm text-sm leading-6 text-slate-600 dark:text-slate-400">
|
||||||
Secure authentication, user management, organizations,
|
{brandDescription}
|
||||||
authorization, and enterprise identity for modern applications.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-6 flex flex-wrap items-center gap-2">
|
<div data-show="showStatus" class="mt-6 flex flex-wrap items-center gap-2">
|
||||||
<a
|
<a
|
||||||
href="/status"
|
href="{statusHref}"
|
||||||
class="inline-flex items-center gap-2 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs font-semibold text-emerald-800 transition hover:border-emerald-300 hover:bg-emerald-100 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-300 dark:hover:border-emerald-500/30"
|
class="inline-flex items-center gap-2 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs font-semibold text-emerald-800 transition hover:border-emerald-300 hover:bg-emerald-100 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-300 dark:hover:border-emerald-500/30"
|
||||||
>
|
>
|
||||||
<span class="relative flex h-2 w-2">
|
<span class="relative flex h-2 w-2">
|
||||||
@@ -179,12 +211,17 @@ component PublicFooter {
|
|||||||
<span class="relative inline-flex h-2 w-2 rounded-full bg-emerald-500"></span>
|
<span class="relative inline-flex h-2 w-2 rounded-full bg-emerald-500"></span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
All systems operational
|
{statusLabel}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Social links -->
|
<!-- Social links -->
|
||||||
<div data-show="showSocialLinks" class="mt-6 flex items-center gap-2">
|
<div data-show="showSocialLinks" class="mt-6 flex items-center gap-2">
|
||||||
|
{#if socialLinks.length > 0}
|
||||||
|
{#each socialLinks as item}
|
||||||
|
<a href="{item.href}" target="{item.target || '_blank'}" rel="noopener noreferrer" aria-label="{item.ariaLabel || item.label}" class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-500 transition hover:border-indigo-300 hover:text-indigo-700 dark:border-white/10 dark:bg-white/5 dark:text-slate-400 dark:hover:border-indigo-500/30 dark:hover:text-indigo-300"><span class="{item.icon || 'icon-[lucide--external-link]'} h-4.5 w-4.5" aria-hidden="true"></span><span class="sr-only">{item.label}</span></a>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/wrnexus"
|
href="https://github.com/wrnexus"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -224,15 +261,21 @@ component PublicFooter {
|
|||||||
>
|
>
|
||||||
<span class="icon-[lucide--youtube] h-4.5 w-4.5" aria-hidden="true" ></span>
|
<span class="icon-[lucide--youtube] h-4.5 w-4.5" aria-hidden="true" ></span>
|
||||||
</a>
|
</a>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navigation columns -->
|
<!-- Navigation columns -->
|
||||||
<nav
|
<nav
|
||||||
aria-label="Footer navigation"
|
aria-label="{navigationAriaLabel}"
|
||||||
class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-3 xl:grid-cols-5"
|
class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-3 xl:grid-cols-5"
|
||||||
>
|
>
|
||||||
<slot name="navigation">
|
<slot name="navigation">
|
||||||
|
{#if navigationColumns.length > 0}
|
||||||
|
{#each navigationColumns as column}
|
||||||
|
<div><h3 class="text-sm font-bold text-slate-950 dark:text-white">{column.title}</h3><ul class="mt-4 space-y-3">{#each column.links as item}<li><a href="{item.href}" target="{item.target || '_self'}" aria-label="{item.ariaLabel || item.label}" class="footer-link">{item.label}</a></li>{/each}</ul></div>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
<!-- Product -->
|
<!-- Product -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-bold text-slate-950 dark:text-white">
|
<h3 class="text-sm font-bold text-slate-950 dark:text-white">
|
||||||
@@ -547,6 +590,7 @@ component PublicFooter {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</slot>
|
</slot>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -570,6 +614,9 @@ component PublicFooter {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center gap-x-5 gap-y-3">
|
<div class="flex flex-wrap items-center gap-x-5 gap-y-3">
|
||||||
|
{#if legalLinks.length > 0}
|
||||||
|
{#each legalLinks as item}<a href="{item.href}" target="{item.target || '_self'}" class="footer-bottom-link">{item.label}</a>{/each}
|
||||||
|
{:else}
|
||||||
<a
|
<a
|
||||||
href="/privacy"
|
href="/privacy"
|
||||||
class="footer-bottom-link"
|
class="footer-bottom-link"
|
||||||
@@ -595,27 +642,30 @@ component PublicFooter {
|
|||||||
href="/legal"
|
href="/legal"
|
||||||
class="footer-bottom-link"
|
class="footer-bottom-link"
|
||||||
>
|
>
|
||||||
Legal
|
{legalTitle}
|
||||||
</a>
|
</a>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<button data-show="showThemeToggle"
|
<button data-show="showCookiePreferences"
|
||||||
type="button"
|
type="button"
|
||||||
data-open-cookie-preferences
|
data-open-cookie-preferences
|
||||||
class="footer-bottom-link"
|
class="footer-bottom-link"
|
||||||
>
|
>
|
||||||
Cookie preferences
|
{cookiePreferencesLabel}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button data-show="showThemeToggle"
|
||||||
type="button"
|
type="button"
|
||||||
data-wire-theme-toggle
|
data-wire-theme-toggle
|
||||||
|
aria-label="{themeToggleLabel}"
|
||||||
|
title="{themeToggleLabel}"
|
||||||
class="inline-flex items-center gap-2 rounded-lg text-xs font-semibold text-slate-500 transition hover:text-indigo-700 dark:text-slate-400 dark:hover:text-indigo-300"
|
class="inline-flex items-center gap-2 rounded-lg text-xs font-semibold text-slate-500 transition hover:text-indigo-700 dark:text-slate-400 dark:hover:text-indigo-300"
|
||||||
>
|
>
|
||||||
<span class="icon-[lucide--moon] h-3.5 w-3.5 dark:hidden" aria-hidden="true" ></span>
|
<span class="icon-[lucide--moon] h-3.5 w-3.5 dark:hidden" aria-hidden="true" ></span>
|
||||||
|
|
||||||
<span class="icon-[lucide--sun] hidden h-3.5 w-3.5 dark:block" aria-hidden="true" ></span>
|
<span class="icon-[lucide--sun] hidden h-3.5 w-3.5 dark:block" aria-hidden="true" ></span>
|
||||||
|
|
||||||
Theme
|
{themeLabel}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
component PublicHeader {
|
component PublicHeader {
|
||||||
|
types {
|
||||||
|
interface PublicHeaderNavigationItem {
|
||||||
|
label: string
|
||||||
|
href: string
|
||||||
|
ariaLabel?: string
|
||||||
|
target?: string
|
||||||
|
}
|
||||||
|
interface PublicHeaderActionItem extends PublicHeaderNavigationItem {
|
||||||
|
variant?: "primary" | "secondary" | "ghost"
|
||||||
|
}
|
||||||
|
}
|
||||||
props {
|
props {
|
||||||
class: string = ""
|
class: string = ""
|
||||||
homeHref: string = "/"
|
homeHref: string = "/"
|
||||||
@@ -22,6 +33,14 @@ component PublicHeader {
|
|||||||
solutionsLabel: string = "Solutions"
|
solutionsLabel: string = "Solutions"
|
||||||
developersLabel: string = "Developers"
|
developersLabel: string = "Developers"
|
||||||
resourcesLabel: string = "Resources"
|
resourcesLabel: string = "Resources"
|
||||||
|
navigationAriaLabel: string = "Main navigation"
|
||||||
|
themeToggleLabel: string = "Toggle color theme"
|
||||||
|
mobileMenuOpenLabel: string = "Open navigation menu"
|
||||||
|
mobileMenuCloseLabel: string = "Close navigation menu"
|
||||||
|
navigationItems: PublicHeaderNavigationItem[] = []
|
||||||
|
actionItems: PublicHeaderActionItem[] = []
|
||||||
|
showNavigation: boolean = true
|
||||||
|
showMobileThemeToggle: boolean = true
|
||||||
}
|
}
|
||||||
state activeMenu = ""
|
state activeMenu = ""
|
||||||
state mobileMenuOpen = false
|
state mobileMenuOpen = false
|
||||||
@@ -71,10 +90,16 @@ component PublicHeader {
|
|||||||
|
|
||||||
<!-- Desktop navigation -->
|
<!-- Desktop navigation -->
|
||||||
<nav
|
<nav
|
||||||
aria-label="Main navigation"
|
aria-label="{navigationAriaLabel}"
|
||||||
|
data-show="showNavigation"
|
||||||
class="hidden items-center gap-1 lg:flex"
|
class="hidden items-center gap-1 lg:flex"
|
||||||
>
|
>
|
||||||
<slot name="navigation">
|
<slot name="navigation">
|
||||||
|
{#if navigationItems.length > 0}
|
||||||
|
{#each navigationItems as item}
|
||||||
|
<a href="{item.href}" aria-label="{item.ariaLabel || item.label}" target="{item.target || '_self'}" class="inline-flex h-10 items-center rounded-xl px-3.5 text-sm font-semibold text-slate-700 transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-300 dark:hover:bg-white/5 dark:hover:text-white">{item.label}</a>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
<!-- Product -->
|
<!-- Product -->
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -171,12 +196,18 @@ component PublicHeader {
|
|||||||
>
|
>
|
||||||
{pricingLabel}
|
{pricingLabel}
|
||||||
</a>
|
</a>
|
||||||
|
{/if}
|
||||||
</slot>
|
</slot>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Desktop actions -->
|
<!-- Desktop actions -->
|
||||||
<div class="hidden items-center gap-2 lg:flex">
|
<div class="hidden items-center gap-2 lg:flex">
|
||||||
<slot name="actions">
|
<slot name="actions">
|
||||||
|
{#if actionItems.length > 0}
|
||||||
|
{#each actionItems as item}
|
||||||
|
<a href="{item.href}" aria-label="{item.ariaLabel || item.label}" target="{item.target || '_self'}" class="wire-public-header-action wire-public-header-action--{item.variant || 'secondary'}">{item.label}{#if item.variant === 'primary'}<span aria-hidden="true">→</span>{/if}</a>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
<a data-show="showStatus"
|
<a data-show="showStatus"
|
||||||
href="{statusHref}"
|
href="{statusHref}"
|
||||||
class="mr-1 inline-flex items-center gap-2 rounded-xl px-3 py-2 text-xs font-semibold text-slate-500 transition hover:bg-slate-100 hover:text-slate-950 dark:text-slate-400 dark:hover:bg-white/5 dark:hover:text-white"
|
class="mr-1 inline-flex items-center gap-2 rounded-xl px-3 py-2 text-xs font-semibold text-slate-500 transition hover:bg-slate-100 hover:text-slate-950 dark:text-slate-400 dark:hover:bg-white/5 dark:hover:text-white"
|
||||||
@@ -192,8 +223,8 @@ component PublicHeader {
|
|||||||
<button data-show="showThemeToggle"
|
<button data-show="showThemeToggle"
|
||||||
type="button"
|
type="button"
|
||||||
data-wire-theme-toggle
|
data-wire-theme-toggle
|
||||||
aria-label="Toggle color theme"
|
aria-label="{themeToggleLabel}"
|
||||||
title="Toggle color theme"
|
title="{themeToggleLabel}"
|
||||||
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:border-slate-300 hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-300 dark:hover:border-white/20 dark:hover:bg-white/10 dark:hover:text-white"
|
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:border-slate-300 hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-300 dark:hover:border-white/20 dark:hover:bg-white/10 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<span class="icon-[lucide--moon] h-4 w-4 dark:hidden" aria-hidden="true" ></span>
|
<span class="icon-[lucide--moon] h-4 w-4 dark:hidden" aria-hidden="true" ></span>
|
||||||
@@ -201,7 +232,7 @@ component PublicHeader {
|
|||||||
<span class="icon-[lucide--sun] hidden h-4 w-4 dark:block" aria-hidden="true" ></span>
|
<span class="icon-[lucide--sun] hidden h-4 w-4 dark:block" aria-hidden="true" ></span>
|
||||||
|
|
||||||
<span class="sr-only">
|
<span class="sr-only">
|
||||||
Toggle color theme
|
{themeToggleLabel}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -220,16 +251,17 @@ component PublicHeader {
|
|||||||
|
|
||||||
<span class="icon-[lucide--arrow-right] h-4 w-4 transition-transform group-hover:translate-x-0.5" aria-hidden="true" ></span>
|
<span class="icon-[lucide--arrow-right] h-4 w-4 transition-transform group-hover:translate-x-0.5" aria-hidden="true" ></span>
|
||||||
</a>
|
</a>
|
||||||
|
{/if}
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile actions -->
|
<!-- Mobile actions -->
|
||||||
<div class="flex items-center gap-2 lg:hidden">
|
<div class="flex items-center gap-2 lg:hidden">
|
||||||
<button
|
<button data-show="showThemeToggle && showMobileThemeToggle"
|
||||||
type="button"
|
type="button"
|
||||||
data-wire-theme-toggle
|
data-wire-theme-toggle
|
||||||
aria-label="Toggle color theme"
|
aria-label="{themeToggleLabel}"
|
||||||
title="Toggle color theme"
|
title="{themeToggleLabel}"
|
||||||
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white"
|
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<span class="icon-[lucide--moon] h-4 w-4 dark:hidden" aria-hidden="true" ></span>
|
<span class="icon-[lucide--moon] h-4 w-4 dark:hidden" aria-hidden="true" ></span>
|
||||||
@@ -240,7 +272,7 @@ component PublicHeader {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="mobileMenuOpen = !mobileMenuOpen"
|
@click="mobileMenuOpen = !mobileMenuOpen"
|
||||||
aria-label="{mobileMenuOpen ? 'Close navigation menu' : 'Open navigation menu'}"
|
aria-label="{mobileMenuOpen ? mobileMenuCloseLabel : mobileMenuOpenLabel}"
|
||||||
aria-expanded="{mobileMenuOpen}"
|
aria-expanded="{mobileMenuOpen}"
|
||||||
aria-controls="public-mobile-navigation"
|
aria-controls="public-mobile-navigation"
|
||||||
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-700 shadow-sm transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10 dark:hover:text-white"
|
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-700 shadow-sm transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10 dark:hover:text-white"
|
||||||
@@ -258,7 +290,7 @@ component PublicHeader {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Outside-click backdrop -->
|
<!-- Outside-click backdrop -->
|
||||||
<button type="button" data-show="activeMenu !== ''" @click="activeMenu = ''" aria-label="Close navigation menu" class="fixed inset-0 z-40 hidden cursor-default bg-transparent lg:block" class:hidden="activeMenu === ''" class:block="activeMenu !== ''" ></button>
|
<button type="button" data-show="activeMenu !== ''" @click="activeMenu = ''" aria-label="{mobileMenuCloseLabel}" class="fixed inset-0 z-40 hidden cursor-default bg-transparent lg:block" class:hidden="activeMenu === ''" class:block="activeMenu !== ''" ></button>
|
||||||
|
|
||||||
<!-- Desktop mega menus -->
|
<!-- Desktop mega menus -->
|
||||||
<div
|
<div
|
||||||
@@ -302,7 +334,16 @@ component PublicHeader {
|
|||||||
class:hidden="!mobileMenuOpen"
|
class:hidden="!mobileMenuOpen"
|
||||||
class:block="mobileMenuOpen"
|
class:block="mobileMenuOpen"
|
||||||
>
|
>
|
||||||
|
{#if navigationItems.length > 0 || actionItems.length > 0}
|
||||||
|
<nav aria-label="{navigationAriaLabel}" class="border-t border-slate-200 bg-white px-5 py-5 shadow-xl dark:border-white/10 dark:bg-slate-950">
|
||||||
|
<div class="grid gap-2">
|
||||||
|
{#each navigationItems as item}<a href="{item.href}" target="{item.target || '_self'}" class="rounded-xl px-4 py-3 text-sm font-semibold text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-white/5">{item.label}</a>{/each}
|
||||||
|
{#each actionItems as item}<a href="{item.href}" target="{item.target || '_self'}" class="wire-public-header-action wire-public-header-action--{item.variant || 'secondary'}">{item.label}</a>{/each}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
{:else}
|
||||||
<PublicMobileNavigation />
|
<PublicMobileNavigation />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ui",
|
"name": "@wrnexus/ui",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/ui — part of the WrNexus framework.",
|
"description": "@wrnexus/ui — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"./ui.css": "./ui.css"
|
"./ui.css": "./ui.css"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.73"
|
"@wrnexus/core": "^0.2.74"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1186,6 +1186,44 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wire-public-header-action {
|
||||||
|
display: inline-flex;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 0.625rem 1rem;
|
||||||
|
color: var(--wire-color-text);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none;
|
||||||
|
transition:
|
||||||
|
background var(--wire-motion-base),
|
||||||
|
border-color var(--wire-motion-base),
|
||||||
|
color var(--wire-motion-base),
|
||||||
|
transform var(--wire-motion-base) var(--wire-ease-emphasized);
|
||||||
|
}
|
||||||
|
.wire-public-header-action--primary {
|
||||||
|
color: var(--wire-color-primary-contrast);
|
||||||
|
background: var(--wire-color-primary);
|
||||||
|
box-shadow: 0 0.75rem 1.5rem color-mix(in srgb, var(--wire-color-primary) 20%, transparent);
|
||||||
|
}
|
||||||
|
.wire-public-header-action--primary:hover {
|
||||||
|
background: var(--wire-color-primary-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.wire-public-header-action--secondary {
|
||||||
|
border-color: var(--wire-color-border);
|
||||||
|
background: var(--wire-color-surface);
|
||||||
|
}
|
||||||
|
.wire-public-header-action--secondary:hover,
|
||||||
|
.wire-public-header-action--ghost:hover {
|
||||||
|
color: var(--wire-color-primary);
|
||||||
|
background: var(--wire-color-surface-2);
|
||||||
|
}
|
||||||
.wire-auth-field__input {
|
.wire-auth-field__input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 3.5rem;
|
min-height: 3.5rem;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/uploader",
|
"name": "@wrnexus/uploader",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/uploader — part of the WrNexus framework.",
|
"description": "@wrnexus/uploader — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.73"
|
"@wrnexus/core": "^0.2.74"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/validation",
|
"name": "@wrnexus/validation",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/validation — part of the WrNexus framework.",
|
"description": "@wrnexus/validation — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.2.14
|
||||||
|
|
||||||
|
- Added top-level ES module import highlighting, completion, snippets, formatting,
|
||||||
|
and diagnostics support for `.wrn` files.
|
||||||
|
- Imported helpers can now be used in server-rendered component prop expressions.
|
||||||
|
|
||||||
## 0.2.13
|
## 0.2.13
|
||||||
|
|
||||||
- Added explicit prop and state types, local `types` blocks, and typed function
|
- Added explicit prop and state types, local `types` blocks, and typed function
|
||||||
|
|||||||
@@ -27,6 +27,19 @@ component UserCard {
|
|||||||
|
|
||||||
The extension shows declared prop types and reports missing required props and incompatible values. Typed prop and state expressions passed into nested components retain their declared type.
|
The extension shows declared prop types and reports missing required props and incompatible values. Typed prop and state expressions passed into nested components retain their declared type.
|
||||||
|
|
||||||
|
Top-level imports are supported before `page`, `component`, or `layout`. Imported
|
||||||
|
helpers can be used in server-rendered prop expressions:
|
||||||
|
|
||||||
|
```wrn
|
||||||
|
import { appUrl } from "@wrnexus/helpers";
|
||||||
|
|
||||||
|
layout PublicLayout {
|
||||||
|
view {
|
||||||
|
<PublicHeader signInHref="{appUrl('sso', '/sign-in')}" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Search for **WRNexus Language Support** in the VS Code Extensions view or run:
|
Search for **WRNexus Language Support** in the VS Code Extensions view or run:
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "wrnexus",
|
"name": "wrnexus",
|
||||||
"version": "0.2.13",
|
"version": "0.2.14",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "wrnexus",
|
"name": "wrnexus",
|
||||||
"version": "0.2.13",
|
"version": "0.2.14",
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vscode/vsce": "^3.9.2"
|
"@vscode/vsce": "^3.9.2"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "wrnexus",
|
"name": "wrnexus",
|
||||||
"displayName": "WRNexus Language Support",
|
"displayName": "WRNexus Language Support",
|
||||||
"description": "Complete language support for WRNexus .wrn files, including highlighting, formatting, diagnostics, snippets, lifecycle hooks, state watchers, component functions, completions, and definition navigation.",
|
"description": "Complete language support for WRNexus .wrn files, including highlighting, formatting, diagnostics, snippets, lifecycle hooks, state watchers, component functions, completions, and definition navigation.",
|
||||||
"version": "0.2.13",
|
"version": "0.2.14",
|
||||||
"publisher": "wrnexus",
|
"publisher": "wrnexus",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
"extensions": [
|
"extensions": [
|
||||||
".wrn"
|
".wrn"
|
||||||
],
|
],
|
||||||
"firstLine": "^\\s*(?:page|component|layout)\\s+[A-Za-z_$][A-Za-z0-9_$]*\\s*\\{",
|
"firstLine": "^\\s*(?:(?:import\\b[^;\\n]*(?:;|\\n)\\s*)*)(?:page|component|layout)\\s+[A-Za-z_$][A-Za-z0-9_$]*\\s*\\{",
|
||||||
"configuration": "./language-configuration.json",
|
"configuration": "./language-configuration.json",
|
||||||
"icon": {
|
"icon": {
|
||||||
"light": "./icons/wrn.png",
|
"light": "./icons/wrn.png",
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
{
|
{
|
||||||
|
"WRN import": {
|
||||||
|
"prefix": ["wrn-import", "import"],
|
||||||
|
"description": "Import a server-side helper into a WRN file",
|
||||||
|
"body": ["import { ${1:appUrl} } from \"${2:@wrnexus/helpers}\";", "", "$0"]
|
||||||
|
},
|
||||||
"WRN page": {
|
"WRN page": {
|
||||||
"prefix": ["wrn-page", "page"],
|
"prefix": ["wrn-page", "page"],
|
||||||
"description": "Create a WRN page",
|
"description": "Create a WRN page",
|
||||||
|
|||||||
@@ -382,6 +382,24 @@ function unescapeSeoValue(value) {
|
|||||||
}
|
}
|
||||||
function parse(source) {
|
function parse(source) {
|
||||||
const lx = new Lexer(source);
|
const lx = new Lexer(source);
|
||||||
|
const imports = [];
|
||||||
|
const importPattern = /import\s+(?:type\s+)?(?:[\s\S]*?\s+from\s+)?["'][^"'\r\n]+["']\s*;?/y;
|
||||||
|
while (true) {
|
||||||
|
while (/\s/u.test(source[lx.pos] ?? ""))
|
||||||
|
lx.pos++;
|
||||||
|
if (source.startsWith("//", lx.pos)) {
|
||||||
|
while (lx.pos < source.length && source[lx.pos] !== `
|
||||||
|
`)
|
||||||
|
lx.pos++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
importPattern.lastIndex = lx.pos;
|
||||||
|
const statement = importPattern.exec(source);
|
||||||
|
if (!statement)
|
||||||
|
break;
|
||||||
|
imports.push(statement[0].trim());
|
||||||
|
lx.pos = importPattern.lastIndex;
|
||||||
|
}
|
||||||
const expect = (type) => {
|
const expect = (type) => {
|
||||||
const t = lx.next();
|
const t = lx.next();
|
||||||
if (t.type !== type) {
|
if (t.type !== type) {
|
||||||
@@ -627,6 +645,7 @@ function parse(source) {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: "page",
|
type: "page",
|
||||||
|
imports,
|
||||||
kind,
|
kind,
|
||||||
name,
|
name,
|
||||||
layout,
|
layout,
|
||||||
@@ -1304,6 +1323,9 @@ function generate(ast) {
|
|||||||
return generateComponent(ast);
|
return generateComponent(ast);
|
||||||
}
|
}
|
||||||
const out = [];
|
const out = [];
|
||||||
|
if (ast.imports.length > 0)
|
||||||
|
out.push(ast.imports.join(`
|
||||||
|
`));
|
||||||
const ssrBindings = [];
|
const ssrBindings = [];
|
||||||
const csrBindings = [];
|
const csrBindings = [];
|
||||||
const helpers = ast.functions.map((body2) => body2.trim()).filter(Boolean).join(`
|
const helpers = ast.functions.map((body2) => body2.trim()).filter(Boolean).join(`
|
||||||
@@ -1722,6 +1744,9 @@ function renderComponentNode(node, ctx) {
|
|||||||
}
|
}
|
||||||
function generateComponent(ast) {
|
function generateComponent(ast) {
|
||||||
const out = [];
|
const out = [];
|
||||||
|
if (ast.imports.length > 0)
|
||||||
|
out.push(ast.imports.join(`
|
||||||
|
`));
|
||||||
const hasServerEach = viewHasServerEach(ast.view);
|
const hasServerEach = viewHasServerEach(ast.view);
|
||||||
if (hasServerEach) {
|
if (hasServerEach) {
|
||||||
out.push(`import { Buffer } from "node:buffer";`);
|
out.push(`import { Buffer } from "node:buffer";`);
|
||||||
@@ -2192,6 +2217,8 @@ function generateNative(ast) {
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { ActivityIndicator, FlatList, Image, Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, View } from "react-native";
|
import { ActivityIndicator, FlatList, Image, Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, View } from "react-native";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
|
${ast.imports.join(`
|
||||||
|
`)}
|
||||||
|
|
||||||
${typeSource}
|
${typeSource}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,13 @@
|
|||||||
const vscode = require("vscode");
|
const vscode = require("vscode");
|
||||||
|
|
||||||
const BLOCK_COMPLETIONS = [
|
const BLOCK_COMPLETIONS = [
|
||||||
|
{
|
||||||
|
label: "import",
|
||||||
|
detail: "WRN server module import",
|
||||||
|
documentation:
|
||||||
|
"Import a package or module before the page, component, or layout declaration for server rendering.",
|
||||||
|
snippet: 'import { ${1:appUrl} } from "${2:@wrnexus/helpers}";\n\n$0',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "page",
|
label: "page",
|
||||||
detail: "WRN page",
|
detail: "WRN page",
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ function stripComments(source) {
|
|||||||
function maskLeadingTrivia(source) {
|
function maskLeadingTrivia(source) {
|
||||||
const masked = [...source];
|
const masked = [...source];
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
const importPattern = /import\s+(?:type\s+)?(?:[\s\S]*?\s+from\s+)?["'][^"'\r\n]+["']\s*;?/y;
|
||||||
|
|
||||||
while (offset < source.length) {
|
while (offset < source.length) {
|
||||||
if (/\s/u.test(source[offset])) {
|
if (/\s/u.test(source[offset])) {
|
||||||
@@ -96,6 +97,17 @@ function maskLeadingTrivia(source) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
importPattern.lastIndex = offset;
|
||||||
|
const importStatement = importPattern.exec(source);
|
||||||
|
if (importStatement) {
|
||||||
|
const end = importPattern.lastIndex;
|
||||||
|
while (offset < end) {
|
||||||
|
if (source[offset] !== "\n" && source[offset] !== "\r") masked[offset] = " ";
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!source.startsWith("//", offset)) break;
|
if (!source.startsWith("//", offset)) break;
|
||||||
|
|
||||||
while (offset < source.length && source[offset] !== "\n") {
|
while (offset < source.length && source[offset] !== "\n") {
|
||||||
|
|||||||
@@ -326,6 +326,20 @@ function formatWrn(source, options = {}) {
|
|||||||
|
|
||||||
previousWasBlank = false;
|
previousWasBlank = false;
|
||||||
|
|
||||||
|
if (/^import\b/.test(value)) {
|
||||||
|
const importLines = [value];
|
||||||
|
while (
|
||||||
|
!/(?:\bfrom\s+)?["'][^"']+["']\s*;?$/.test(importLines[importLines.length - 1]) &&
|
||||||
|
index + 1 < inputLines.length
|
||||||
|
) {
|
||||||
|
index += 1;
|
||||||
|
importLines.push(inputLines[index].trim());
|
||||||
|
}
|
||||||
|
output.push(importLines[0], ...importLines.slice(1).map((line) => `${unit}${line}`));
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (isMultilineOpeningTagStart(value)) {
|
if (isMultilineOpeningTagStart(value)) {
|
||||||
const collected = collectOpeningTag(inputLines, index);
|
const collected = collectOpeningTag(inputLines, index);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
{
|
{
|
||||||
"include": "#comments"
|
"include": "#comments"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"include": "#imports"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"include": "#declaration"
|
"include": "#declaration"
|
||||||
},
|
},
|
||||||
@@ -14,6 +17,21 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
|
"imports": {
|
||||||
|
"begin": "^\\s*(import)\\b",
|
||||||
|
"beginCaptures": {
|
||||||
|
"1": {
|
||||||
|
"name": "keyword.control.import.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"end": ";|$",
|
||||||
|
"name": "meta.import.wrn",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "source.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"comments": {
|
"comments": {
|
||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,6 +56,19 @@ component ThemeToggle {
|
|||||||
assert.equal(declaration.diagnostic, undefined);
|
assert.equal(declaration.diagnostic, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("recognizes a WRN declaration after top-level imports", () => {
|
||||||
|
const source = `import { appUrl } from "@wrnexus/helpers";
|
||||||
|
import type { Context } from "@wrnexus/core";
|
||||||
|
|
||||||
|
page Home {
|
||||||
|
view { <PublicHeader signInHref="{appUrl('sso', '/sign-in')}" /> }
|
||||||
|
}`;
|
||||||
|
const declaration = findTopLevelDeclaration({}, source);
|
||||||
|
assert.equal(declaration.kind, "page");
|
||||||
|
assert.equal(declaration.name, "Home");
|
||||||
|
assert.equal(declaration.diagnostic, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
test("masks only leading trivia and preserves source offsets", () => {
|
test("masks only leading trivia and preserves source offsets", () => {
|
||||||
const source = "\uFEFF// Summary\r\npage Home {\n // member comment\n}";
|
const source = "\uFEFF// Summary\r\npage Home {\n // member comment\n}";
|
||||||
const masked = maskLeadingTrivia(source);
|
const masked = maskLeadingTrivia(source);
|
||||||
|
|||||||
@@ -127,6 +127,13 @@ try {
|
|||||||
formatWrn(formatted, { insertSpaces: true, tabSize: 2 }) === formatted
|
formatWrn(formatted, { insertSpaces: true, tabSize: 2 }) === formatted
|
||||||
? ok("formatter is idempotent")
|
? ok("formatter is idempotent")
|
||||||
: bad("formatter is idempotent");
|
: bad("formatter is idempotent");
|
||||||
|
const imported = formatWrn(
|
||||||
|
`import {\nappUrl,\nother\n} from "@wrnexus/helpers";\n\nlayout Public {\nview { <p>Hi</p> }\n}\n`,
|
||||||
|
{ insertSpaces: true, tabSize: 2 },
|
||||||
|
);
|
||||||
|
imported.includes(`\nlayout Public {`) && formatWrn(imported, { tabSize: 2 }) === imported
|
||||||
|
? ok("formatter preserves top-level imports")
|
||||||
|
: bad("formatter preserves top-level imports");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
bad("formatter loads", e.message);
|
bad("formatter loads", e.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ai",
|
"name": "@wrnexus/ai",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/authz",
|
"name": "@wrnexus/authz",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/cli",
|
"name": "@wrnexus/cli",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -734,6 +734,15 @@ const MIGRATIONS: Migration[] = [
|
|||||||
// Shared UI visuals improve automatically after updating and rebuilding.
|
// Shared UI visuals improve automatically after updating and rebuilding.
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
version: "0.2.74",
|
||||||
|
id: "wrn-imports-and-dynamic-public-shell",
|
||||||
|
description:
|
||||||
|
"Adds top-level WRN imports and data-driven public header and footer navigation, actions, links, and labels.",
|
||||||
|
apply() {
|
||||||
|
// Language and shared UI behavior improve automatically after updating.
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Release tooling uses this to require an explicit migration entry per version. */
|
/** Release tooling uses this to require an explicit migration entry per version. */
|
||||||
|
|||||||
+26
-13
@@ -8,6 +8,19 @@ Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web fr
|
|||||||
|
|
||||||
`@wrnexus/compiler` turns `.wrn` source into TypeScript that targets the framework's runtime primitives. A `.wrn` file declares either a `page` (a route) or a `component` (a reusable, prop-driven fragment) with blocks for `state`, `view` (plain HTML), `seo`, `style`, `functions`, `api`, `ssr`/`client` data bindings, and `realtime` websocket handlers. The pipeline is `source → Lexer → parse() → PageAst → generate() → TypeScript`. It is a build/server-side library — the WrNexus dev loader calls it to compile `.wrn` files on the fly, surfacing `ParseError` as a readable error page.
|
`@wrnexus/compiler` turns `.wrn` source into TypeScript that targets the framework's runtime primitives. A `.wrn` file declares either a `page` (a route) or a `component` (a reusable, prop-driven fragment) with blocks for `state`, `view` (plain HTML), `seo`, `style`, `functions`, `api`, `ssr`/`client` data bindings, and `realtime` websocket handlers. The pipeline is `source → Lexer → parse() → PageAst → generate() → TypeScript`. It is a build/server-side library — the WrNexus dev loader calls it to compile `.wrn` files on the fly, surfacing `ParseError` as a readable error page.
|
||||||
|
|
||||||
|
Static ES module imports may appear before the root declaration. Imported values are
|
||||||
|
available to server-rendered expressions, including component props:
|
||||||
|
|
||||||
|
```wrn
|
||||||
|
import { appUrl } from "@wrnexus/helpers";
|
||||||
|
|
||||||
|
layout PublicLayout {
|
||||||
|
view {
|
||||||
|
<PublicHeader signInHref="{appUrl('sso', '/sign-in')}" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -76,19 +89,19 @@ class Lexer {
|
|||||||
|
|
||||||
Exported type-only symbols describing the parsed tree:
|
Exported type-only symbols describing the parsed tree:
|
||||||
|
|
||||||
| Type | Description |
|
| Type | Description |
|
||||||
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `PageAst` | Root node including `kind`, `name`, `types`, typed `props`, typed `states`, `view`, styles, functions, data APIs, lifecycle, and routes. |
|
| `PageAst` | Root node including top-level `imports`, `kind`, `name`, `types`, typed `props`, typed `states`, `view`, styles, functions, data APIs, lifecycle, and routes. |
|
||||||
| `ViewNode` | `{ type: "text"; value }` or `{ type: "element"; tag; attrs; children }`. |
|
| `ViewNode` | `{ type: "text"; value }` or `{ type: "element"; tag; attrs; children }`. |
|
||||||
| `Attr` | `{ name; value; event; boolean? }` — `event` marks `@event` bindings. |
|
| `Attr` | `{ name; value; event; boolean? }` — `event` marks `@event` bindings. |
|
||||||
| `StateDecl` | `{ name; valueType?; expr }` — a typed `state x: Type = <expr>` declaration. |
|
| `StateDecl` | `{ name; valueType?; expr }` — a typed `state x: Type = <expr>` declaration. |
|
||||||
| `PropDecl` | `{ name; valueType?; required; default }` — a typed prop declaration. |
|
| `PropDecl` | `{ name; valueType?; required; default }` — a typed prop declaration. |
|
||||||
| `SeoBlock` | `Record<string, string>` from the `seo { ... }` block. |
|
| `SeoBlock` | `Record<string, string>` from the `seo { ... }` block. |
|
||||||
| `ApiBlock` | `{ method; path; body }` — a top-level `api METHOD /path { ... }`. |
|
| `ApiBlock` | `{ method; path; body }` — a top-level `api METHOD /path { ... }`. |
|
||||||
| `DataApiBlock` | `{ mode; name; method; path; body }` — an `api` inside an `ssr`/`client` block. |
|
| `DataApiBlock` | `{ mode; name; method; path; body }` — an `api` inside an `ssr`/`client` block. |
|
||||||
| `DataMode` | `"ssr" \| "client"`. |
|
| `DataMode` | `"ssr" \| "client"`. |
|
||||||
| `ModeFunctionsBlock` | `{ mode; body }` — a `functions { ... }` inside an `ssr`/`client` block. |
|
| `ModeFunctionsBlock` | `{ mode; body }` — a `functions { ... }` inside an `ssr`/`client` block. |
|
||||||
| `RealtimeBlock` | `{ name; handlers }` — a `realtime <name> { on evt(args) { ... } }` block. |
|
| `RealtimeBlock` | `{ name; handlers }` — a `realtime <name> { on evt(args) { ... } }` block. |
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -660,6 +660,7 @@ export function generate(ast: PageAst): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const out: string[] = [];
|
const out: string[] = [];
|
||||||
|
if (ast.imports.length > 0) out.push(ast.imports.join("\n"));
|
||||||
const ssrBindings: SsrBinding[] = [];
|
const ssrBindings: SsrBinding[] = [];
|
||||||
const csrBindings: CsrBinding[] = [];
|
const csrBindings: CsrBinding[] = [];
|
||||||
const helpers = ast.functions
|
const helpers = ast.functions
|
||||||
@@ -1292,6 +1293,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
|||||||
|
|
||||||
function generateComponent(ast: PageAst): string {
|
function generateComponent(ast: PageAst): string {
|
||||||
const out: string[] = [];
|
const out: string[] = [];
|
||||||
|
if (ast.imports.length > 0) out.push(ast.imports.join("\n"));
|
||||||
const hasServerEach = viewHasServerEach(ast.view);
|
const hasServerEach = viewHasServerEach(ast.view);
|
||||||
|
|
||||||
if (hasServerEach) {
|
if (hasServerEach) {
|
||||||
|
|||||||
@@ -233,5 +233,5 @@ export function generateNative(ast: PageAst): string {
|
|||||||
.map((block) => block.trim())
|
.map((block) => block.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join("\n\n");
|
.join("\n\n");
|
||||||
return `// generated from .wrn for Expo/React Native\nimport React, { useState } from "react";\nimport { ActivityIndicator, FlatList, Image, Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, View } from "react-native";\nimport { useRouter } from "expo-router";\n\n${typeSource}\n\nexport default function ${ast.name}() {\n const router = useRouter();\n${hooks}\n return <>${body}</>;\n}\n\n${nativeStyles(ast.styles)}\n`;
|
return `// generated from .wrn for Expo/React Native\nimport React, { useState } from "react";\nimport { ActivityIndicator, FlatList, Image, Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, View } from "react-native";\nimport { useRouter } from "expo-router";\n${ast.imports.join("\n")}\n\n${typeSource}\n\nexport default function ${ast.name}() {\n const router = useRouter();\n${hooks}\n return <>${body}</>;\n}\n\n${nativeStyles(ast.styles)}\n`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,8 @@ export interface PropDecl {
|
|||||||
|
|
||||||
export interface PageAst {
|
export interface PageAst {
|
||||||
type: "page";
|
type: "page";
|
||||||
|
/** Static ES module imports declared before the WRN root declaration. */
|
||||||
|
imports: string[];
|
||||||
/**
|
/**
|
||||||
* `page` is a route, `component` is a reusable fragment,
|
* `page` is a route, `component` is a reusable fragment,
|
||||||
* and `layout` is a reusable page wrapper.
|
* and `layout` is a reusable page wrapper.
|
||||||
@@ -197,6 +199,21 @@ function unescapeSeoValue(value: string): string {
|
|||||||
export function parse(source: string): PageAst {
|
export function parse(source: string): PageAst {
|
||||||
const lx = new Lexer(source);
|
const lx = new Lexer(source);
|
||||||
|
|
||||||
|
const imports: string[] = [];
|
||||||
|
const importPattern = /import\s+(?:type\s+)?(?:[\s\S]*?\s+from\s+)?["'][^"'\r\n]+["']\s*;?/y;
|
||||||
|
while (true) {
|
||||||
|
while (/\s/u.test(source[lx.pos] ?? "")) lx.pos++;
|
||||||
|
if (source.startsWith("//", lx.pos)) {
|
||||||
|
while (lx.pos < source.length && source[lx.pos] !== "\n") lx.pos++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
importPattern.lastIndex = lx.pos;
|
||||||
|
const statement = importPattern.exec(source);
|
||||||
|
if (!statement) break;
|
||||||
|
imports.push(statement[0].trim());
|
||||||
|
lx.pos = importPattern.lastIndex;
|
||||||
|
}
|
||||||
|
|
||||||
const expect = (type: Token["type"]): Token => {
|
const expect = (type: Token["type"]): Token => {
|
||||||
const t = lx.next();
|
const t = lx.next();
|
||||||
if (t.type !== type) {
|
if (t.type !== type) {
|
||||||
@@ -474,6 +491,7 @@ export function parse(source: string): PageAst {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
type: "page",
|
type: "page",
|
||||||
|
imports,
|
||||||
kind,
|
kind,
|
||||||
name,
|
name,
|
||||||
layout,
|
layout,
|
||||||
|
|||||||
@@ -57,6 +57,20 @@ test("parses a page with a layout member", () => {
|
|||||||
expect(ast.layout).toBe("public");
|
expect(ast.layout).toBe("public");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("top-level imports are preserved and available to SSR view expressions", async () => {
|
||||||
|
const source = `import { posix } from "node:path";
|
||||||
|
|
||||||
|
page Home {
|
||||||
|
view { <PublicHeader signInHref="{posix.join('/sso', '/sign-in')}" /> }
|
||||||
|
}`;
|
||||||
|
const ast = parse(source);
|
||||||
|
expect(ast.imports).toEqual(['import { posix } from "node:path";']);
|
||||||
|
|
||||||
|
const mod = await compileAndImport(source);
|
||||||
|
const render = mod.default as (ctx: unknown) => string;
|
||||||
|
expect(String(await render({}))).toContain('signInHref="/sso/sign-in"');
|
||||||
|
});
|
||||||
|
|
||||||
test("parses a component with props and state", () => {
|
test("parses a component with props and state", () => {
|
||||||
const ast = parse(
|
const ast = parse(
|
||||||
`component C {\n props {\n n = 0\n }\n state count = n\n view { <b>{count}</b> }\n}`,
|
`component C {\n props {\n n = 0\n }\n state count = n\n view { <b>{count}</b> }\n}`,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/core",
|
"name": "@wrnexus/core",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/csr",
|
"name": "@wrnexus/csr",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/db",
|
"name": "@wrnexus/db",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/dev-server",
|
"name": "@wrnexus/dev-server",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/encryption",
|
"name": "@wrnexus/encryption",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/helpers",
|
"name": "@wrnexus/helpers",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/i18n",
|
"name": "@wrnexus/i18n",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/jwt",
|
"name": "@wrnexus/jwt",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/mobile",
|
"name": "@wrnexus/mobile",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/native",
|
"name": "@wrnexus/native",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/oauth",
|
"name": "@wrnexus/oauth",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/pubsub",
|
"name": "@wrnexus/pubsub",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/queue",
|
"name": "@wrnexus/queue",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/reactive",
|
"name": "@wrnexus/reactive",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/router",
|
"name": "@wrnexus/router",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ssr",
|
"name": "@wrnexus/ssr",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/styles",
|
"name": "@wrnexus/styles",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/test",
|
"name": "@wrnexus/test",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/tracking",
|
"name": "@wrnexus/tracking",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -3253,7 +3253,7 @@ Reusable progress bar component.
|
|||||||
Reusable public footer component.
|
Reusable public footer component.
|
||||||
|
|
||||||
- Mount: `data-component="PublicFooter"`
|
- Mount: `data-component="PublicFooter"`
|
||||||
- Props: `class: string = ""`, `homeHref: string = "/"`, `brandName: string = "WrNexus"`, `brandTagline: string = "Identity Cloud"`, `brandAriaLabel: string = "WrNexus home"`, `newsletterEyebrow: string = "WrNexus updates"`, `newsletterTitle: string = "Identity insights delivered to your inbox"`, `newsletterDescription: string = "Get product updates, security guidance, implementation strategies, and practical identity architecture resources."`, `newsletterAction: string = "/api/newsletter/subscribe"`, `newsletterButtonLabel: string = "Subscribe"`, `newsletterPlaceholder: string = "Enter your work email"`, `newsletterSuccessMessage: string = "Thanks. Please check your inbox to confirm your subscription."`, `copyrightText: string = "© 2026 WrNexus. All rights reserved."`, `attributionText: string = "Built by WorkRoot Workspace."`, `showNewsletter: boolean = true`, `showSocialLinks: boolean = true`, `showThemeToggle: boolean = true`
|
- Props: `class: string = ""`, `homeHref: string = "/"`, `brandName: string = "WrNexus"`, `brandTagline: string = "Identity Cloud"`, `brandAriaLabel: string = "WrNexus home"`, `brandIcon: string = "icon-[lucide--blocks]"`, `brandDescription: string = "Secure authentication, user management, organizations, authorization, and enterprise identity for modern applications."`, `statusLabel: string = "All systems operational"`, `statusHref: string = "/status"`, `newsletterEyebrow: string = "WrNexus updates"`, `newsletterTitle: string = "Identity insights delivered to your inbox"`, `newsletterDescription: string = "Get product updates, security guidance, implementation strategies, and practical identity architecture resources."`, `newsletterAction: string = "/api/newsletter/subscribe"`, `newsletterButtonLabel: string = "Subscribe"`, `newsletterPlaceholder: string = "Enter your work email"`, `newsletterSuccessMessage: string = "Thanks. Please check your inbox to confirm your subscription."`, `newsletterPrivacyLabel: string = "privacy policy"`, `newsletterPrivacyHref: string = "/privacy"`, `newsletterFinePrintPrefix: string = "No spam. Unsubscribe at any time. Read our"`, `newsletterFinePrintSuffix: string = "."`, `newsletterEmailLabel: string = "Work email address"`, `newsletterEmailName: string = "email"`, `copyrightText: string = "© 2026 WrNexus. All rights reserved."`, `attributionText: string = "Built by WorkRoot Workspace."`, `showNewsletter: boolean = true`, `showSocialLinks: boolean = true`, `showThemeToggle: boolean = true`, `showStatus: boolean = true`, `showCookiePreferences: boolean = true`, `cookiePreferencesLabel: string = "Cookie preferences"`, `themeLabel: string = "Theme"`, `themeToggleLabel: string = "Toggle color theme"`, `legalTitle: string = "Legal"`, `socialLinks: PublicFooterLink[] = []`, `navigationColumns: PublicFooterColumn[] = []`, `legalLinks: PublicFooterLink[] = []`, `navigationAriaLabel: string = "Footer navigation"`
|
||||||
- Slots: `navigation`
|
- Slots: `navigation`
|
||||||
- Events: `submit`
|
- Events: `submit`
|
||||||
|
|
||||||
@@ -7203,7 +7203,7 @@ Reusable product page shell component.
|
|||||||
Reusable public header component.
|
Reusable public header component.
|
||||||
|
|
||||||
- Mount: `data-component="PublicHeader"`
|
- Mount: `data-component="PublicHeader"`
|
||||||
- Props: `class: string = ""`, `homeHref: string = "/"`, `brandName: string = "WrNexus"`, `brandTagline: string = "Identity Cloud"`, `brandAriaLabel: string = "WrNexus home"`, `brandIcon: string = "icon-[lucide--blocks]"`, `pricingLabel: string = "Pricing"`, `pricingHref: string = "/pricing"`, `statusLabel: string = "All systems operational"`, `statusHref: string = "/status"`, `signInLabel: string = "Sign in"`, `signInHref: string = "/sign-in"`, `primaryLabel: string = "Start free"`, `primaryHref: string = "/sign-up"`, `showStatus: boolean = true`, `showThemeToggle: boolean = true`, `showSignIn: boolean = true`, `showPrimaryAction: boolean = true`, `productLabel: string = "Product"`, `solutionsLabel: string = "Solutions"`, `developersLabel: string = "Developers"`, `resourcesLabel: string = "Resources"`
|
- Props: `class: string = ""`, `homeHref: string = "/"`, `brandName: string = "WrNexus"`, `brandTagline: string = "Identity Cloud"`, `brandAriaLabel: string = "WrNexus home"`, `brandIcon: string = "icon-[lucide--blocks]"`, `pricingLabel: string = "Pricing"`, `pricingHref: string = "/pricing"`, `statusLabel: string = "All systems operational"`, `statusHref: string = "/status"`, `signInLabel: string = "Sign in"`, `signInHref: string = "/sign-in"`, `primaryLabel: string = "Start free"`, `primaryHref: string = "/sign-up"`, `showStatus: boolean = true`, `showThemeToggle: boolean = true`, `showSignIn: boolean = true`, `showPrimaryAction: boolean = true`, `productLabel: string = "Product"`, `solutionsLabel: string = "Solutions"`, `developersLabel: string = "Developers"`, `resourcesLabel: string = "Resources"`, `navigationAriaLabel: string = "Main navigation"`, `themeToggleLabel: string = "Toggle color theme"`, `mobileMenuOpenLabel: string = "Open navigation menu"`, `mobileMenuCloseLabel: string = "Close navigation menu"`, `navigationItems: PublicHeaderNavigationItem[] = []`, `actionItems: PublicHeaderActionItem[] = []`, `showNavigation: boolean = true`, `showMobileThemeToggle: boolean = true`
|
||||||
- Slots: `navigation`, `actions`
|
- Slots: `navigation`, `actions`
|
||||||
- Events: `click`
|
- Events: `click`
|
||||||
|
|
||||||
|
|||||||
@@ -33954,6 +33954,30 @@
|
|||||||
"required": false,
|
"required": false,
|
||||||
"default": "\"WrNexus home\""
|
"default": "\"WrNexus home\""
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "brandIcon",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"icon-[lucide--blocks]\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "brandDescription",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Secure authentication, user management, organizations, authorization, and enterprise identity for modern applications.\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "statusLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"All systems operational\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "statusHref",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"/status\""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "newsletterEyebrow",
|
"name": "newsletterEyebrow",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -33996,6 +34020,42 @@
|
|||||||
"required": false,
|
"required": false,
|
||||||
"default": "\"Thanks. Please check your inbox to confirm your subscription.\""
|
"default": "\"Thanks. Please check your inbox to confirm your subscription.\""
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterPrivacyLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"privacy policy\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterPrivacyHref",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"/privacy\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterFinePrintPrefix",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"No spam. Unsubscribe at any time. Read our\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterFinePrintSuffix",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\".\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterEmailLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Work email address\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newsletterEmailName",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"email\""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "copyrightText",
|
"name": "copyrightText",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -34025,6 +34085,66 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"required": false,
|
"required": false,
|
||||||
"default": "true"
|
"default": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "showStatus",
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false,
|
||||||
|
"default": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "showCookiePreferences",
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false,
|
||||||
|
"default": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cookiePreferencesLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Cookie preferences\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "themeLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Theme\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "themeToggleLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Toggle color theme\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "legalTitle",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Legal\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "socialLinks",
|
||||||
|
"type": "PublicFooterLink[]",
|
||||||
|
"required": false,
|
||||||
|
"default": "[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "navigationColumns",
|
||||||
|
"type": "PublicFooterColumn[]",
|
||||||
|
"required": false,
|
||||||
|
"default": "[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "legalLinks",
|
||||||
|
"type": "PublicFooterLink[]",
|
||||||
|
"required": false,
|
||||||
|
"default": "[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "navigationAriaLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Footer navigation\""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"slots": ["navigation"],
|
"slots": ["navigation"],
|
||||||
@@ -34168,6 +34288,54 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"required": false,
|
"required": false,
|
||||||
"default": "\"Resources\""
|
"default": "\"Resources\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "navigationAriaLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Main navigation\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "themeToggleLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Toggle color theme\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mobileMenuOpenLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Open navigation menu\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mobileMenuCloseLabel",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"default": "\"Close navigation menu\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "navigationItems",
|
||||||
|
"type": "PublicHeaderNavigationItem[]",
|
||||||
|
"required": false,
|
||||||
|
"default": "[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "actionItems",
|
||||||
|
"type": "PublicHeaderActionItem[]",
|
||||||
|
"required": false,
|
||||||
|
"default": "[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "showNavigation",
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false,
|
||||||
|
"default": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "showMobileThemeToggle",
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false,
|
||||||
|
"default": "true"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"slots": ["navigation", "actions"],
|
"slots": ["navigation", "actions"],
|
||||||
|
|||||||
@@ -1,10 +1,27 @@
|
|||||||
component PublicFooter {
|
component PublicFooter {
|
||||||
|
types {
|
||||||
|
interface PublicFooterLink {
|
||||||
|
label: string
|
||||||
|
href: string
|
||||||
|
ariaLabel?: string
|
||||||
|
target?: string
|
||||||
|
icon?: string
|
||||||
|
}
|
||||||
|
interface PublicFooterColumn {
|
||||||
|
title: string
|
||||||
|
links: PublicFooterLink[]
|
||||||
|
}
|
||||||
|
}
|
||||||
props {
|
props {
|
||||||
class: string = ""
|
class: string = ""
|
||||||
homeHref: string = "/"
|
homeHref: string = "/"
|
||||||
brandName: string = "WrNexus"
|
brandName: string = "WrNexus"
|
||||||
brandTagline: string = "Identity Cloud"
|
brandTagline: string = "Identity Cloud"
|
||||||
brandAriaLabel: string = "WrNexus home"
|
brandAriaLabel: string = "WrNexus home"
|
||||||
|
brandIcon: string = "icon-[lucide--blocks]"
|
||||||
|
brandDescription: string = "Secure authentication, user management, organizations, authorization, and enterprise identity for modern applications."
|
||||||
|
statusLabel: string = "All systems operational"
|
||||||
|
statusHref: string = "/status"
|
||||||
newsletterEyebrow: string = "WrNexus updates"
|
newsletterEyebrow: string = "WrNexus updates"
|
||||||
newsletterTitle: string = "Identity insights delivered to your inbox"
|
newsletterTitle: string = "Identity insights delivered to your inbox"
|
||||||
newsletterDescription: string = "Get product updates, security guidance, implementation strategies, and practical identity architecture resources."
|
newsletterDescription: string = "Get product updates, security guidance, implementation strategies, and practical identity architecture resources."
|
||||||
@@ -12,11 +29,27 @@ component PublicFooter {
|
|||||||
newsletterButtonLabel: string = "Subscribe"
|
newsletterButtonLabel: string = "Subscribe"
|
||||||
newsletterPlaceholder: string = "Enter your work email"
|
newsletterPlaceholder: string = "Enter your work email"
|
||||||
newsletterSuccessMessage: string = "Thanks. Please check your inbox to confirm your subscription."
|
newsletterSuccessMessage: string = "Thanks. Please check your inbox to confirm your subscription."
|
||||||
|
newsletterPrivacyLabel: string = "privacy policy"
|
||||||
|
newsletterPrivacyHref: string = "/privacy"
|
||||||
|
newsletterFinePrintPrefix: string = "No spam. Unsubscribe at any time. Read our"
|
||||||
|
newsletterFinePrintSuffix: string = "."
|
||||||
|
newsletterEmailLabel: string = "Work email address"
|
||||||
|
newsletterEmailName: string = "email"
|
||||||
copyrightText: string = "© 2026 WrNexus. All rights reserved."
|
copyrightText: string = "© 2026 WrNexus. All rights reserved."
|
||||||
attributionText: string = "Built by WorkRoot Workspace."
|
attributionText: string = "Built by WorkRoot Workspace."
|
||||||
showNewsletter: boolean = true
|
showNewsletter: boolean = true
|
||||||
showSocialLinks: boolean = true
|
showSocialLinks: boolean = true
|
||||||
showThemeToggle: boolean = true
|
showThemeToggle: boolean = true
|
||||||
|
showStatus: boolean = true
|
||||||
|
showCookiePreferences: boolean = true
|
||||||
|
cookiePreferencesLabel: string = "Cookie preferences"
|
||||||
|
themeLabel: string = "Theme"
|
||||||
|
themeToggleLabel: string = "Toggle color theme"
|
||||||
|
legalTitle: string = "Legal"
|
||||||
|
socialLinks: PublicFooterLink[] = []
|
||||||
|
navigationColumns: PublicFooterColumn[] = []
|
||||||
|
legalLinks: PublicFooterLink[] = []
|
||||||
|
navigationAriaLabel: string = "Footer navigation"
|
||||||
}
|
}
|
||||||
state email = ""
|
state email = ""
|
||||||
state newsletterSubmitted = false
|
state newsletterSubmitted = false
|
||||||
@@ -81,7 +114,7 @@ component PublicFooter {
|
|||||||
class="sr-only"
|
class="sr-only"
|
||||||
for="footer-newsletter-email"
|
for="footer-newsletter-email"
|
||||||
>
|
>
|
||||||
Work email address
|
{newsletterEmailLabel}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="relative min-w-0 flex-1">
|
<div class="relative min-w-0 flex-1">
|
||||||
@@ -89,7 +122,7 @@ component PublicFooter {
|
|||||||
|
|
||||||
<input
|
<input
|
||||||
id="footer-newsletter-email"
|
id="footer-newsletter-email"
|
||||||
name="email"
|
name="{newsletterEmailName}"
|
||||||
type="email"
|
type="email"
|
||||||
autocomplete="email"
|
autocomplete="email"
|
||||||
required
|
required
|
||||||
@@ -113,13 +146,13 @@ component PublicFooter {
|
|||||||
<p class="mt-3 flex items-start gap-2 text-xs leading-5 text-slate-500 dark:text-slate-400">
|
<p class="mt-3 flex items-start gap-2 text-xs leading-5 text-slate-500 dark:text-slate-400">
|
||||||
<span class="icon-[lucide--shield-check] mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true" ></span>
|
<span class="icon-[lucide--shield-check] mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true" ></span>
|
||||||
|
|
||||||
No spam. Unsubscribe at any time. Read our
|
{newsletterFinePrintPrefix}
|
||||||
<a
|
<a
|
||||||
href="/privacy"
|
href="{newsletterPrivacyHref}"
|
||||||
class="font-semibold text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="font-semibold text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
||||||
>
|
>
|
||||||
privacy policy
|
{newsletterPrivacyLabel}
|
||||||
</a>.
|
</a>{newsletterFinePrintSuffix}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -148,7 +181,7 @@ component PublicFooter {
|
|||||||
<span
|
<span
|
||||||
class="grid h-11 w-11 place-items-center rounded-2xl bg-indigo-600 text-white shadow-lg shadow-indigo-600/20 transition group-hover:-translate-y-0.5 group-hover:bg-indigo-500"
|
class="grid h-11 w-11 place-items-center rounded-2xl bg-indigo-600 text-white shadow-lg shadow-indigo-600/20 transition group-hover:-translate-y-0.5 group-hover:bg-indigo-500"
|
||||||
>
|
>
|
||||||
<span class="icon-[lucide--blocks] h-5 w-5" aria-hidden="true" ></span>
|
<span class="{brandIcon} h-5 w-5" aria-hidden="true" ></span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
@@ -165,13 +198,12 @@ component PublicFooter {
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p class="mt-5 max-w-sm text-sm leading-6 text-slate-600 dark:text-slate-400">
|
<p class="mt-5 max-w-sm text-sm leading-6 text-slate-600 dark:text-slate-400">
|
||||||
Secure authentication, user management, organizations,
|
{brandDescription}
|
||||||
authorization, and enterprise identity for modern applications.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-6 flex flex-wrap items-center gap-2">
|
<div data-show="showStatus" class="mt-6 flex flex-wrap items-center gap-2">
|
||||||
<a
|
<a
|
||||||
href="/status"
|
href="{statusHref}"
|
||||||
class="inline-flex items-center gap-2 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs font-semibold text-emerald-800 transition hover:border-emerald-300 hover:bg-emerald-100 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-300 dark:hover:border-emerald-500/30"
|
class="inline-flex items-center gap-2 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs font-semibold text-emerald-800 transition hover:border-emerald-300 hover:bg-emerald-100 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-300 dark:hover:border-emerald-500/30"
|
||||||
>
|
>
|
||||||
<span class="relative flex h-2 w-2">
|
<span class="relative flex h-2 w-2">
|
||||||
@@ -179,12 +211,17 @@ component PublicFooter {
|
|||||||
<span class="relative inline-flex h-2 w-2 rounded-full bg-emerald-500"></span>
|
<span class="relative inline-flex h-2 w-2 rounded-full bg-emerald-500"></span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
All systems operational
|
{statusLabel}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Social links -->
|
<!-- Social links -->
|
||||||
<div data-show="showSocialLinks" class="mt-6 flex items-center gap-2">
|
<div data-show="showSocialLinks" class="mt-6 flex items-center gap-2">
|
||||||
|
{#if socialLinks.length > 0}
|
||||||
|
{#each socialLinks as item}
|
||||||
|
<a href="{item.href}" target="{item.target || '_blank'}" rel="noopener noreferrer" aria-label="{item.ariaLabel || item.label}" class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-500 transition hover:border-indigo-300 hover:text-indigo-700 dark:border-white/10 dark:bg-white/5 dark:text-slate-400 dark:hover:border-indigo-500/30 dark:hover:text-indigo-300"><span class="{item.icon || 'icon-[lucide--external-link]'} h-4.5 w-4.5" aria-hidden="true"></span><span class="sr-only">{item.label}</span></a>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/wrnexus"
|
href="https://github.com/wrnexus"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -224,15 +261,21 @@ component PublicFooter {
|
|||||||
>
|
>
|
||||||
<span class="icon-[lucide--youtube] h-4.5 w-4.5" aria-hidden="true" ></span>
|
<span class="icon-[lucide--youtube] h-4.5 w-4.5" aria-hidden="true" ></span>
|
||||||
</a>
|
</a>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navigation columns -->
|
<!-- Navigation columns -->
|
||||||
<nav
|
<nav
|
||||||
aria-label="Footer navigation"
|
aria-label="{navigationAriaLabel}"
|
||||||
class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-3 xl:grid-cols-5"
|
class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-3 xl:grid-cols-5"
|
||||||
>
|
>
|
||||||
<slot name="navigation">
|
<slot name="navigation">
|
||||||
|
{#if navigationColumns.length > 0}
|
||||||
|
{#each navigationColumns as column}
|
||||||
|
<div><h3 class="text-sm font-bold text-slate-950 dark:text-white">{column.title}</h3><ul class="mt-4 space-y-3">{#each column.links as item}<li><a href="{item.href}" target="{item.target || '_self'}" aria-label="{item.ariaLabel || item.label}" class="footer-link">{item.label}</a></li>{/each}</ul></div>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
<!-- Product -->
|
<!-- Product -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-bold text-slate-950 dark:text-white">
|
<h3 class="text-sm font-bold text-slate-950 dark:text-white">
|
||||||
@@ -547,6 +590,7 @@ component PublicFooter {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</slot>
|
</slot>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -570,6 +614,9 @@ component PublicFooter {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center gap-x-5 gap-y-3">
|
<div class="flex flex-wrap items-center gap-x-5 gap-y-3">
|
||||||
|
{#if legalLinks.length > 0}
|
||||||
|
{#each legalLinks as item}<a href="{item.href}" target="{item.target || '_self'}" class="footer-bottom-link">{item.label}</a>{/each}
|
||||||
|
{:else}
|
||||||
<a
|
<a
|
||||||
href="/privacy"
|
href="/privacy"
|
||||||
class="footer-bottom-link"
|
class="footer-bottom-link"
|
||||||
@@ -595,27 +642,30 @@ component PublicFooter {
|
|||||||
href="/legal"
|
href="/legal"
|
||||||
class="footer-bottom-link"
|
class="footer-bottom-link"
|
||||||
>
|
>
|
||||||
Legal
|
{legalTitle}
|
||||||
</a>
|
</a>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<button data-show="showThemeToggle"
|
<button data-show="showCookiePreferences"
|
||||||
type="button"
|
type="button"
|
||||||
data-open-cookie-preferences
|
data-open-cookie-preferences
|
||||||
class="footer-bottom-link"
|
class="footer-bottom-link"
|
||||||
>
|
>
|
||||||
Cookie preferences
|
{cookiePreferencesLabel}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button data-show="showThemeToggle"
|
||||||
type="button"
|
type="button"
|
||||||
data-wire-theme-toggle
|
data-wire-theme-toggle
|
||||||
|
aria-label="{themeToggleLabel}"
|
||||||
|
title="{themeToggleLabel}"
|
||||||
class="inline-flex items-center gap-2 rounded-lg text-xs font-semibold text-slate-500 transition hover:text-indigo-700 dark:text-slate-400 dark:hover:text-indigo-300"
|
class="inline-flex items-center gap-2 rounded-lg text-xs font-semibold text-slate-500 transition hover:text-indigo-700 dark:text-slate-400 dark:hover:text-indigo-300"
|
||||||
>
|
>
|
||||||
<span class="icon-[lucide--moon] h-3.5 w-3.5 dark:hidden" aria-hidden="true" ></span>
|
<span class="icon-[lucide--moon] h-3.5 w-3.5 dark:hidden" aria-hidden="true" ></span>
|
||||||
|
|
||||||
<span class="icon-[lucide--sun] hidden h-3.5 w-3.5 dark:block" aria-hidden="true" ></span>
|
<span class="icon-[lucide--sun] hidden h-3.5 w-3.5 dark:block" aria-hidden="true" ></span>
|
||||||
|
|
||||||
Theme
|
{themeLabel}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
component PublicHeader {
|
component PublicHeader {
|
||||||
|
types {
|
||||||
|
interface PublicHeaderNavigationItem {
|
||||||
|
label: string
|
||||||
|
href: string
|
||||||
|
ariaLabel?: string
|
||||||
|
target?: string
|
||||||
|
}
|
||||||
|
interface PublicHeaderActionItem extends PublicHeaderNavigationItem {
|
||||||
|
variant?: "primary" | "secondary" | "ghost"
|
||||||
|
}
|
||||||
|
}
|
||||||
props {
|
props {
|
||||||
class: string = ""
|
class: string = ""
|
||||||
homeHref: string = "/"
|
homeHref: string = "/"
|
||||||
@@ -22,6 +33,14 @@ component PublicHeader {
|
|||||||
solutionsLabel: string = "Solutions"
|
solutionsLabel: string = "Solutions"
|
||||||
developersLabel: string = "Developers"
|
developersLabel: string = "Developers"
|
||||||
resourcesLabel: string = "Resources"
|
resourcesLabel: string = "Resources"
|
||||||
|
navigationAriaLabel: string = "Main navigation"
|
||||||
|
themeToggleLabel: string = "Toggle color theme"
|
||||||
|
mobileMenuOpenLabel: string = "Open navigation menu"
|
||||||
|
mobileMenuCloseLabel: string = "Close navigation menu"
|
||||||
|
navigationItems: PublicHeaderNavigationItem[] = []
|
||||||
|
actionItems: PublicHeaderActionItem[] = []
|
||||||
|
showNavigation: boolean = true
|
||||||
|
showMobileThemeToggle: boolean = true
|
||||||
}
|
}
|
||||||
state activeMenu = ""
|
state activeMenu = ""
|
||||||
state mobileMenuOpen = false
|
state mobileMenuOpen = false
|
||||||
@@ -71,10 +90,16 @@ component PublicHeader {
|
|||||||
|
|
||||||
<!-- Desktop navigation -->
|
<!-- Desktop navigation -->
|
||||||
<nav
|
<nav
|
||||||
aria-label="Main navigation"
|
aria-label="{navigationAriaLabel}"
|
||||||
|
data-show="showNavigation"
|
||||||
class="hidden items-center gap-1 lg:flex"
|
class="hidden items-center gap-1 lg:flex"
|
||||||
>
|
>
|
||||||
<slot name="navigation">
|
<slot name="navigation">
|
||||||
|
{#if navigationItems.length > 0}
|
||||||
|
{#each navigationItems as item}
|
||||||
|
<a href="{item.href}" aria-label="{item.ariaLabel || item.label}" target="{item.target || '_self'}" class="inline-flex h-10 items-center rounded-xl px-3.5 text-sm font-semibold text-slate-700 transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-300 dark:hover:bg-white/5 dark:hover:text-white">{item.label}</a>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
<!-- Product -->
|
<!-- Product -->
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -171,12 +196,18 @@ component PublicHeader {
|
|||||||
>
|
>
|
||||||
{pricingLabel}
|
{pricingLabel}
|
||||||
</a>
|
</a>
|
||||||
|
{/if}
|
||||||
</slot>
|
</slot>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Desktop actions -->
|
<!-- Desktop actions -->
|
||||||
<div class="hidden items-center gap-2 lg:flex">
|
<div class="hidden items-center gap-2 lg:flex">
|
||||||
<slot name="actions">
|
<slot name="actions">
|
||||||
|
{#if actionItems.length > 0}
|
||||||
|
{#each actionItems as item}
|
||||||
|
<a href="{item.href}" aria-label="{item.ariaLabel || item.label}" target="{item.target || '_self'}" class="wire-public-header-action wire-public-header-action--{item.variant || 'secondary'}">{item.label}{#if item.variant === 'primary'}<span aria-hidden="true">→</span>{/if}</a>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
<a data-show="showStatus"
|
<a data-show="showStatus"
|
||||||
href="{statusHref}"
|
href="{statusHref}"
|
||||||
class="mr-1 inline-flex items-center gap-2 rounded-xl px-3 py-2 text-xs font-semibold text-slate-500 transition hover:bg-slate-100 hover:text-slate-950 dark:text-slate-400 dark:hover:bg-white/5 dark:hover:text-white"
|
class="mr-1 inline-flex items-center gap-2 rounded-xl px-3 py-2 text-xs font-semibold text-slate-500 transition hover:bg-slate-100 hover:text-slate-950 dark:text-slate-400 dark:hover:bg-white/5 dark:hover:text-white"
|
||||||
@@ -192,8 +223,8 @@ component PublicHeader {
|
|||||||
<button data-show="showThemeToggle"
|
<button data-show="showThemeToggle"
|
||||||
type="button"
|
type="button"
|
||||||
data-wire-theme-toggle
|
data-wire-theme-toggle
|
||||||
aria-label="Toggle color theme"
|
aria-label="{themeToggleLabel}"
|
||||||
title="Toggle color theme"
|
title="{themeToggleLabel}"
|
||||||
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:border-slate-300 hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-300 dark:hover:border-white/20 dark:hover:bg-white/10 dark:hover:text-white"
|
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:border-slate-300 hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-300 dark:hover:border-white/20 dark:hover:bg-white/10 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<span class="icon-[lucide--moon] h-4 w-4 dark:hidden" aria-hidden="true" ></span>
|
<span class="icon-[lucide--moon] h-4 w-4 dark:hidden" aria-hidden="true" ></span>
|
||||||
@@ -201,7 +232,7 @@ component PublicHeader {
|
|||||||
<span class="icon-[lucide--sun] hidden h-4 w-4 dark:block" aria-hidden="true" ></span>
|
<span class="icon-[lucide--sun] hidden h-4 w-4 dark:block" aria-hidden="true" ></span>
|
||||||
|
|
||||||
<span class="sr-only">
|
<span class="sr-only">
|
||||||
Toggle color theme
|
{themeToggleLabel}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -220,16 +251,17 @@ component PublicHeader {
|
|||||||
|
|
||||||
<span class="icon-[lucide--arrow-right] h-4 w-4 transition-transform group-hover:translate-x-0.5" aria-hidden="true" ></span>
|
<span class="icon-[lucide--arrow-right] h-4 w-4 transition-transform group-hover:translate-x-0.5" aria-hidden="true" ></span>
|
||||||
</a>
|
</a>
|
||||||
|
{/if}
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile actions -->
|
<!-- Mobile actions -->
|
||||||
<div class="flex items-center gap-2 lg:hidden">
|
<div class="flex items-center gap-2 lg:hidden">
|
||||||
<button
|
<button data-show="showThemeToggle && showMobileThemeToggle"
|
||||||
type="button"
|
type="button"
|
||||||
data-wire-theme-toggle
|
data-wire-theme-toggle
|
||||||
aria-label="Toggle color theme"
|
aria-label="{themeToggleLabel}"
|
||||||
title="Toggle color theme"
|
title="{themeToggleLabel}"
|
||||||
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white"
|
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<span class="icon-[lucide--moon] h-4 w-4 dark:hidden" aria-hidden="true" ></span>
|
<span class="icon-[lucide--moon] h-4 w-4 dark:hidden" aria-hidden="true" ></span>
|
||||||
@@ -240,7 +272,7 @@ component PublicHeader {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="mobileMenuOpen = !mobileMenuOpen"
|
@click="mobileMenuOpen = !mobileMenuOpen"
|
||||||
aria-label="{mobileMenuOpen ? 'Close navigation menu' : 'Open navigation menu'}"
|
aria-label="{mobileMenuOpen ? mobileMenuCloseLabel : mobileMenuOpenLabel}"
|
||||||
aria-expanded="{mobileMenuOpen}"
|
aria-expanded="{mobileMenuOpen}"
|
||||||
aria-controls="public-mobile-navigation"
|
aria-controls="public-mobile-navigation"
|
||||||
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-700 shadow-sm transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10 dark:hover:text-white"
|
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-700 shadow-sm transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10 dark:hover:text-white"
|
||||||
@@ -258,7 +290,7 @@ component PublicHeader {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Outside-click backdrop -->
|
<!-- Outside-click backdrop -->
|
||||||
<button type="button" data-show="activeMenu !== ''" @click="activeMenu = ''" aria-label="Close navigation menu" class="fixed inset-0 z-40 hidden cursor-default bg-transparent lg:block" class:hidden="activeMenu === ''" class:block="activeMenu !== ''" ></button>
|
<button type="button" data-show="activeMenu !== ''" @click="activeMenu = ''" aria-label="{mobileMenuCloseLabel}" class="fixed inset-0 z-40 hidden cursor-default bg-transparent lg:block" class:hidden="activeMenu === ''" class:block="activeMenu !== ''" ></button>
|
||||||
|
|
||||||
<!-- Desktop mega menus -->
|
<!-- Desktop mega menus -->
|
||||||
<div
|
<div
|
||||||
@@ -302,7 +334,16 @@ component PublicHeader {
|
|||||||
class:hidden="!mobileMenuOpen"
|
class:hidden="!mobileMenuOpen"
|
||||||
class:block="mobileMenuOpen"
|
class:block="mobileMenuOpen"
|
||||||
>
|
>
|
||||||
|
{#if navigationItems.length > 0 || actionItems.length > 0}
|
||||||
|
<nav aria-label="{navigationAriaLabel}" class="border-t border-slate-200 bg-white px-5 py-5 shadow-xl dark:border-white/10 dark:bg-slate-950">
|
||||||
|
<div class="grid gap-2">
|
||||||
|
{#each navigationItems as item}<a href="{item.href}" target="{item.target || '_self'}" class="rounded-xl px-4 py-3 text-sm font-semibold text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-white/5">{item.label}</a>{/each}
|
||||||
|
{#each actionItems as item}<a href="{item.href}" target="{item.target || '_self'}" class="wire-public-header-action wire-public-header-action--{item.variant || 'secondary'}">{item.label}</a>{/each}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
{:else}
|
||||||
<PublicMobileNavigation />
|
<PublicMobileNavigation />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ui",
|
"name": "@wrnexus/ui",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1186,6 +1186,44 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wire-public-header-action {
|
||||||
|
display: inline-flex;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 0.625rem 1rem;
|
||||||
|
color: var(--wire-color-text);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none;
|
||||||
|
transition:
|
||||||
|
background var(--wire-motion-base),
|
||||||
|
border-color var(--wire-motion-base),
|
||||||
|
color var(--wire-motion-base),
|
||||||
|
transform var(--wire-motion-base) var(--wire-ease-emphasized);
|
||||||
|
}
|
||||||
|
.wire-public-header-action--primary {
|
||||||
|
color: var(--wire-color-primary-contrast);
|
||||||
|
background: var(--wire-color-primary);
|
||||||
|
box-shadow: 0 0.75rem 1.5rem color-mix(in srgb, var(--wire-color-primary) 20%, transparent);
|
||||||
|
}
|
||||||
|
.wire-public-header-action--primary:hover {
|
||||||
|
background: var(--wire-color-primary-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.wire-public-header-action--secondary {
|
||||||
|
border-color: var(--wire-color-border);
|
||||||
|
background: var(--wire-color-surface);
|
||||||
|
}
|
||||||
|
.wire-public-header-action--secondary:hover,
|
||||||
|
.wire-public-header-action--ghost:hover {
|
||||||
|
color: var(--wire-color-primary);
|
||||||
|
background: var(--wire-color-surface-2);
|
||||||
|
}
|
||||||
.wire-auth-field__input {
|
.wire-auth-field__input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 3.5rem;
|
min-height: 3.5rem;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/uploader",
|
"name": "@wrnexus/uploader",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/validation",
|
"name": "@wrnexus/validation",
|
||||||
"version": "0.2.73",
|
"version": "0.2.74",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
|
||||||
const version = "0.2.73";
|
const version = "0.2.74";
|
||||||
|
|
||||||
const dependencyGroups = [
|
const dependencyGroups = [
|
||||||
"dependencies",
|
"dependencies",
|
||||||
|
|||||||
Reference in New Issue
Block a user