release: WRNexusJS 0.5.10

This commit is contained in:
2026-07-30 13:36:29 +05:30
parent 8fc6f15402
commit d1b0c55b53
159 changed files with 7509 additions and 604 deletions
+57 -10
View File
@@ -1,6 +1,6 @@
# WRNexus UI component reference
This reference is generated from the 85 packaged `.wrn` component sources. Props marked required have no default; all others show their runtime default.
This reference is generated from the 90 packaged `.wrn` component sources. Props marked required have no default; all others show their runtime default.
## Advanced-forms
@@ -330,15 +330,35 @@ Theme-aware, responsive tree view component.
- Slots: `default`
- Events: `select`, `toggle`, `expand`, `collapse`
## Blocks
### PortalDashboard
Responsive portal dashboard with metrics, actions, tasks, and updates.
- Mount: `data-component="PortalDashboard"`
- Props: `size: string = "default"`, `color: string = "primary"`, `eyebrow: string = "Overview"`, `eyebrowKey: string = ""`, `title: string = "Dashboard"`, `titleKey: string = ""`, `description: string = ""`, `descriptionKey: string = ""`, `userName: string = ""`, `metrics: string = []`, `actions: string = []`, `updates: string = []`, `tasks: string = []`, `class: string = ""`
- Slots: `hero-action`
- Events: `action`, `navigate`
## Forms
### AuthForm
Reusable authentication form for sign-in, registration, recovery, reset, and MFA.
- Mount: `data-component="AuthForm"`
- Props: `size: string = "default"`, `color: string = "primary"`, `mode: string = "sign-in"`, `action: string = "/api/auth/login"`, `method: string = "post"`, `title: string = "Sign in"`, `description: string = ""`, `returnTo: string = ""`, `schema: string = ""`, `showRemember: boolean = true`, `showName: boolean = true`, `submitLabel: string = "Continue"`, `class: string = ""`
- Slots: `default`
- Events: `submit`, `change`, `input`, `focus`, `blur`
### Checkbox
Theme-aware, responsive checkbox component.
- Mount: `data-component="Checkbox"`
- Props: `size: string = "default"`, `color: string = "primary"`, `id: string = ""`, `name: string = ""`, `label: string = "Checkbox"`, `hiddenLabel: boolean = false`, `placeholder: string = ""`, `variant: string = "normal"`, `icon: string = ""`, `iconPosition: string = "start"`, `value: string = "on"`, `values: string = []`, `options: string = []`, `checked: boolean = false`, `indeterminate: boolean = false`, `orientation: string = "vertical"`, `card: boolean = false`, `rightAligned: boolean = false`, `list: boolean = false`, `helperText: string = ""`, `cornerHint: string = ""`, `error: string = ""`, `inline: boolean = false`, `readonly: boolean = false`, `disabled: boolean = false`, `required: boolean = false`, `class: string = ""`
- Slots: None
- Slots: `default`
- Events: `input`, `change`, `focus`, `blur`, `invalid`
### ColorPicker
@@ -543,6 +563,15 @@ Theme-aware, responsive wysiwyg editor component.
## Layout
### AuthSplitLayout
Responsive 50/50 authentication layout with content and form regions.
- Mount: `data-component="AuthSplitLayout"`
- Props: `size: string = "default"`, `color: string = "primary"`, `eyebrow: string = "Secure identity"`, `title: string = "Welcome back"`, `description: string = ""`, `brand: string = "Police Management System"`, `features: string = []`, `class: string = ""`
- Slots: `aside-extra`, `form`
- Events: None
### Columns
Theme-aware, responsive columns component.
@@ -644,6 +673,15 @@ Theme-aware, responsive breadcrumb component.
- Slots: `default`
- Events: `navigate`, `click`
### Footer
Responsive application footer with structured links and pre/post content slots.
- Mount: `data-component="Footer"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Footer navigation"`, `items: string = []`, `columns: number = 3`, `maxWidth: string = "compact"`, `copyright: string = ""`, `class: string = ""`
- Slots: `pre-footer`, `post-footer`, `copyright-left`, `copyright-right`
- Events: `select`, `action`
### MegaMenu
Theme-aware, responsive mega menu component.
@@ -667,9 +705,9 @@ Theme-aware, responsive nav component.
Theme-aware, responsive navbar component.
- Mount: `data-component="Navbar"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Navbar"`, `items: string = []`, `active: string = ""`, `orientation: string = "horizontal"`, `class: string = ""`
- Slots: `default`
- Events: `toggle`, `open`, `close`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Primary navigation"`, `topbarLabel: string = "Utility navigation"`, `brand: string = {}`, `items: string = []`, `actions: string = []`, `active: string = ""`, `sticky: boolean = false`, `openOnHover: boolean = false`, `maxWidth: string = "full"`, `mobileLabel: string = "Toggle navigation"`, `class: string = ""`
- Slots: `topbar`, `actions`
- Events: `toggle`, `open`, `close`, `select`, `action`
### Pagination
@@ -680,6 +718,15 @@ Theme-aware, responsive pagination component.
- Slots: `default`
- Events: `change`, `previous`, `next`
### PreferenceSwitcher
Accessible theme, accent color, and language preference controls.
- Mount: `data-component="PreferenceSwitcher"`
- Props: `size: string = "default"`, `color: string = "primary"`, `themeLabel: string = "Theme"`, `colorLabel: string = "Accent color"`, `languageLabel: string = "Language"`, `languages: string = [`, `colors: string = [`, `compact: boolean = true`, `class: string = ""`
- Slots: None
- Events: `theme`, `color`, `language`
### Scrollspy
Theme-aware, responsive scrollspy component.
@@ -694,9 +741,9 @@ Theme-aware, responsive scrollspy component.
Theme-aware, responsive sidebar component.
- Mount: `data-component="Sidebar"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Sidebar"`, `items: string = []`, `active: string = ""`, `orientation: string = "horizontal"`, `class: string = ""`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Sidebar"`, `items: string = []`, `active: string = ""`, `orientation: string = "horizontal"`, `mobileLabel: string = "Open navigation"`, `class: string = ""`
- Slots: `default`
- Events: `toggle`, `open`, `close`
- Events: `toggle`, `open`, `close`, `select`
### Stepper
@@ -741,9 +788,9 @@ Theme-aware, responsive drawer component.
Theme-aware, responsive dropdown component.
- Mount: `data-component="Dropdown"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Dropdown"`, `description: string = ""`, `open: boolean = false`, `placement: string = "bottom"`, `closeLabel: string = "Close"`, `class: string = ""`
- Slots: `default`
- Events: `open`, `close`, `select`, `change`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Open menu"`, `items: string = []`, `placement: string = "end"`, `class: string = ""`
- Slots: `trigger`, `header`, `footer`
- Events: `toggle`, `open`, `close`, `select`
### Modal
+25
View File
@@ -191,6 +191,11 @@
"category": "navigation",
"purpose": "Theme-aware, responsive navbar component."
},
{
"name": "Footer",
"category": "navigation",
"purpose": "Responsive application footer with structured links and pre/post content slots."
},
{
"name": "MegaMenu",
"category": "navigation",
@@ -429,6 +434,26 @@
"name": "WysiwygEditor",
"category": "integrations",
"purpose": "Theme-aware, responsive wysiwyg editor component."
},
{
"name": "PreferenceSwitcher",
"category": "navigation",
"purpose": "Accessible theme, accent color, and language preference controls."
},
{
"name": "PortalDashboard",
"category": "blocks",
"purpose": "Responsive portal dashboard with metrics, actions, tasks, and updates."
},
{
"name": "AuthSplitLayout",
"category": "layout",
"purpose": "Responsive 50/50 authentication layout with content and form regions."
},
{
"name": "AuthForm",
"category": "forms",
"purpose": "Reusable authentication form for sign-in, registration, recovery, reset, and MFA."
}
]
}
+424 -27
View File
@@ -1,6 +1,6 @@
{
"generatedFrom": "packages/ui/components/*.wrn",
"count": 85,
"count": 90,
"components": [
{
"name": "Accordion",
@@ -675,6 +675,154 @@
"events": ["dismiss", "action"],
"source": "components/alert.wrn"
},
{
"name": "AuthForm",
"mount": "AuthForm",
"category": "forms",
"purpose": "Reusable authentication form for sign-in, registration, recovery, reset, and MFA.",
"props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\""
},
{
"name": "color",
"type": "string",
"required": false,
"default": "\"primary\""
},
{
"name": "mode",
"type": "string",
"required": false,
"default": "\"sign-in\""
},
{
"name": "action",
"type": "string",
"required": false,
"default": "\"/api/auth/login\""
},
{
"name": "method",
"type": "string",
"required": false,
"default": "\"post\""
},
{
"name": "title",
"type": "string",
"required": false,
"default": "\"Sign in\""
},
{
"name": "description",
"type": "string",
"required": false,
"default": "\"\""
},
{
"name": "returnTo",
"type": "string",
"required": false,
"default": "\"\""
},
{
"name": "schema",
"type": "string",
"required": false,
"default": "\"\""
},
{
"name": "showRemember",
"type": "boolean",
"required": false,
"default": "true"
},
{
"name": "showName",
"type": "boolean",
"required": false,
"default": "true"
},
{
"name": "submitLabel",
"type": "string",
"required": false,
"default": "\"Continue\""
},
{
"name": "class",
"type": "string",
"required": false,
"default": "\"\""
}
],
"slots": ["default"],
"events": ["submit", "change", "input", "focus", "blur"],
"source": "components/AuthForm.wrn"
},
{
"name": "AuthSplitLayout",
"mount": "AuthSplitLayout",
"category": "layout",
"purpose": "Responsive 50/50 authentication layout with content and form regions.",
"props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\""
},
{
"name": "color",
"type": "string",
"required": false,
"default": "\"primary\""
},
{
"name": "eyebrow",
"type": "string",
"required": false,
"default": "\"Secure identity\""
},
{
"name": "title",
"type": "string",
"required": false,
"default": "\"Welcome back\""
},
{
"name": "description",
"type": "string",
"required": false,
"default": "\"\""
},
{
"name": "brand",
"type": "string",
"required": false,
"default": "\"Police Management System\""
},
{
"name": "features",
"type": "string",
"required": false,
"default": "[]"
},
{
"name": "class",
"type": "string",
"required": false,
"default": "\"\""
}
],
"slots": ["aside-extra", "form"],
"events": [],
"source": "components/AuthSplitLayout.wrn"
},
{
"name": "Avatar",
"mount": "Avatar",
@@ -2062,7 +2210,7 @@
"default": "\"\""
}
],
"slots": [],
"slots": ["default"],
"events": ["input", "change", "focus", "blur", "invalid"],
"source": "components/Checkbox.wrn"
},
@@ -3436,34 +3584,22 @@
"default": "\"primary\""
},
{
"name": "title",
"name": "label",
"type": "string",
"required": false,
"default": "\"Dropdown\""
"default": "\"Open menu\""
},
{
"name": "description",
"name": "items",
"type": "string",
"required": false,
"default": "\"\""
},
{
"name": "open",
"type": "boolean",
"required": false,
"default": "false"
"default": "[]"
},
{
"name": "placement",
"type": "string",
"required": false,
"default": "\"bottom\""
},
{
"name": "closeLabel",
"type": "string",
"required": false,
"default": "\"Close\""
"default": "\"end\""
},
{
"name": "class",
@@ -3472,8 +3608,8 @@
"default": "\"\""
}
],
"slots": ["default"],
"events": ["open", "close", "select", "change"],
"slots": ["trigger", "header", "footer"],
"events": ["toggle", "open", "close", "select"],
"source": "components/Dropdown.wrn"
},
{
@@ -3755,6 +3891,65 @@
"events": ["cancel", "retry", "complete"],
"source": "components/FileUploadProgress.wrn"
},
{
"name": "Footer",
"mount": "Footer",
"category": "navigation",
"purpose": "Responsive application footer with structured links and pre/post content slots.",
"props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\""
},
{
"name": "color",
"type": "string",
"required": false,
"default": "\"primary\""
},
{
"name": "label",
"type": "string",
"required": false,
"default": "\"Footer navigation\""
},
{
"name": "items",
"type": "string",
"required": false,
"default": "[]"
},
{
"name": "columns",
"type": "number",
"required": false,
"default": "3"
},
{
"name": "maxWidth",
"type": "string",
"required": false,
"default": "\"compact\""
},
{
"name": "copyright",
"type": "string",
"required": false,
"default": "\"\""
},
{
"name": "class",
"type": "string",
"required": false,
"default": "\"\""
}
],
"slots": ["pre-footer", "post-footer", "copyright-left", "copyright-right"],
"events": ["select", "action"],
"source": "components/Footer.wrn"
},
{
"name": "Grid",
"mount": "Grid",
@@ -5039,7 +5234,19 @@
"name": "label",
"type": "string",
"required": false,
"default": "\"Navbar\""
"default": "\"Primary navigation\""
},
{
"name": "topbarLabel",
"type": "string",
"required": false,
"default": "\"Utility navigation\""
},
{
"name": "brand",
"type": "string",
"required": false,
"default": "{}"
},
{
"name": "items",
@@ -5047,6 +5254,12 @@
"required": false,
"default": "[]"
},
{
"name": "actions",
"type": "string",
"required": false,
"default": "[]"
},
{
"name": "active",
"type": "string",
@@ -5054,10 +5267,28 @@
"default": "\"\""
},
{
"name": "orientation",
"name": "sticky",
"type": "boolean",
"required": false,
"default": "false"
},
{
"name": "openOnHover",
"type": "boolean",
"required": false,
"default": "false"
},
{
"name": "maxWidth",
"type": "string",
"required": false,
"default": "\"horizontal\""
"default": "\"full\""
},
{
"name": "mobileLabel",
"type": "string",
"required": false,
"default": "\"Toggle navigation\""
},
{
"name": "class",
@@ -5066,8 +5297,8 @@
"default": "\"\""
}
],
"slots": ["default"],
"events": ["toggle", "open", "close"],
"slots": ["topbar", "actions"],
"events": ["toggle", "open", "close", "select", "action"],
"source": "components/Navbar.wrn"
},
{
@@ -5349,6 +5580,166 @@
"events": ["open", "close"],
"source": "components/Popover.wrn"
},
{
"name": "PortalDashboard",
"mount": "PortalDashboard",
"category": "blocks",
"purpose": "Responsive portal dashboard with metrics, actions, tasks, and updates.",
"props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\""
},
{
"name": "color",
"type": "string",
"required": false,
"default": "\"primary\""
},
{
"name": "eyebrow",
"type": "string",
"required": false,
"default": "\"Overview\""
},
{
"name": "eyebrowKey",
"type": "string",
"required": false,
"default": "\"\""
},
{
"name": "title",
"type": "string",
"required": false,
"default": "\"Dashboard\""
},
{
"name": "titleKey",
"type": "string",
"required": false,
"default": "\"\""
},
{
"name": "description",
"type": "string",
"required": false,
"default": "\"\""
},
{
"name": "descriptionKey",
"type": "string",
"required": false,
"default": "\"\""
},
{
"name": "userName",
"type": "string",
"required": false,
"default": "\"\""
},
{
"name": "metrics",
"type": "string",
"required": false,
"default": "[]"
},
{
"name": "actions",
"type": "string",
"required": false,
"default": "[]"
},
{
"name": "updates",
"type": "string",
"required": false,
"default": "[]"
},
{
"name": "tasks",
"type": "string",
"required": false,
"default": "[]"
},
{
"name": "class",
"type": "string",
"required": false,
"default": "\"\""
}
],
"slots": ["hero-action"],
"events": ["action", "navigate"],
"source": "components/PortalDashboard.wrn"
},
{
"name": "PreferenceSwitcher",
"mount": "PreferenceSwitcher",
"category": "navigation",
"purpose": "Accessible theme, accent color, and language preference controls.",
"props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\""
},
{
"name": "color",
"type": "string",
"required": false,
"default": "\"primary\""
},
{
"name": "themeLabel",
"type": "string",
"required": false,
"default": "\"Theme\""
},
{
"name": "colorLabel",
"type": "string",
"required": false,
"default": "\"Accent color\""
},
{
"name": "languageLabel",
"type": "string",
"required": false,
"default": "\"Language\""
},
{
"name": "languages",
"type": "string",
"required": false,
"default": "["
},
{
"name": "colors",
"type": "string",
"required": false,
"default": "["
},
{
"name": "compact",
"type": "boolean",
"required": false,
"default": "true"
},
{
"name": "class",
"type": "string",
"required": false,
"default": "\"\""
}
],
"slots": [],
"events": ["theme", "color", "language"],
"source": "components/PreferenceSwitcher.wrn"
},
{
"name": "Progress",
"mount": "Progress",
@@ -6110,6 +6501,12 @@
"required": false,
"default": "\"horizontal\""
},
{
"name": "mobileLabel",
"type": "string",
"required": false,
"default": "\"Open navigation\""
},
{
"name": "class",
"type": "string",
@@ -6118,7 +6515,7 @@
}
],
"slots": ["default"],
"events": ["toggle", "open", "close"],
"events": ["toggle", "open", "close", "select"],
"source": "components/Sidebar.wrn"
},
{
+40 -10
View File
@@ -53,7 +53,9 @@ component AvatarGroup {
role="group"
aria-label="Avatar group"
>
<div class="wire-next__avatar-group-members">
<div
class="wire-next__avatar-group-members"
>
{#each visibleMembers() as item}
<span
class="wire-next__avatar-group-member"
@@ -64,18 +66,35 @@ component AvatarGroup {
tabindex="{showTooltips && (item.tooltip || item.name) ? '0' : '-1'}"
aria-label="{item.name || item.alt || item.initials || 'Group member'}"
>
<span class="wire-next__avatar-group-avatar">
<span
class="wire-next__avatar-group-avatar"
>
{#if item.src}
<img src="{item.src}" alt="{item.alt || item.name || ''}" loading="lazy" />
<img
src="{item.src}"
alt="{item.alt || item.name || ''}"
loading="lazy"
/>
{:else if item.initials}
<span aria-hidden="true">{item.initials}</span>
<span
aria-hidden="true"
>
{item.initials}
</span>
{:else}
<span class="icon-[lucide--user-round]" aria-hidden="true"></span>
<span
class="icon-[lucide--user-round]"
aria-hidden="true"
>
</span>
{/if}
</span>
{#if showTooltips && (item.tooltip || item.name)}
<span class="wire-next__avatar-group-tooltip" role="tooltip">
<span
class="wire-next__avatar-group-tooltip"
role="tooltip"
>
{item.tooltip || item.name}
</span>
{/if}
@@ -83,7 +102,9 @@ component AvatarGroup {
{/each}
{#if hiddenMembers().length > 0}
<span class="wire-next__avatar-group-overflow">
<span
class="wire-next__avatar-group-overflow"
>
<button
type="button"
class="wire-next__avatar-group-overflow-button"
@@ -101,10 +122,19 @@ component AvatarGroup {
aria-hidden="{overflowOpen ? 'false' : 'true'}"
>
{#each hiddenMembers() as item}
<span class="wire-next__avatar-group-menu-item" role="menuitem">
<span class="wire-next__avatar-group-menu-avatar">
<span
class="wire-next__avatar-group-menu-item"
role="menuitem"
>
<span
class="wire-next__avatar-group-menu-avatar"
>
{#if item.src}
<img src="{item.src}" alt="" loading="lazy" />
<img
src="{item.src}"
alt=""
loading="lazy"
/>
{:else}
<span>{item.initials || '?'}</span>
{/if}
+41 -11
View File
@@ -1,23 +1,53 @@
component Dropdown {
props {
@event toggle = function
@event open = function
@event close = function
@event select = function
@event change = function
size = "default"
color = "primary"
title = "Dropdown"
description = ""
open = false
placement = "bottom"
closeLabel = "Close"
label = "Open menu"
items = []
placement = "end"
class = ""
}
functions {
function toggleMenu(event) {
$emit("toggle", { open: event.currentTarget.open })
$emit(event.currentTarget.open ? "open" : "close", { source: "trigger" })
}
function selectItem(item) {
$emit("select", { item: item, value: item.value || "" })
}
}
view {
<div data-show="{open}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--dropdown wire-next--placement-{placement} {class}" role="region" aria-modal="false" aria-label="{title}">
<header><strong>{title}</strong><button type="button" aria-label="{closeLabel}">×</button></header>
{#if description}<p>{description}</p>{/if}
<slot />
</div>
<details class="wire-dropdown wire-dropdown--{placement} wire-next--color-{color} wire-next--size-{size} {class}" data-wrn-dropdown @toggle="toggleMenu($event)">
<summary aria-label="{label}">
<slot name="trigger" />
<span class="wire-dropdown__chevron icon-[lucide--chevron-down]" aria-hidden="true"></span>
</summary>
<div class="wire-dropdown__panel" role="menu" aria-label="{label}">
<slot name="header" />
{#each items as item}
{#if item.type === "divider"}
<hr />
{:else}
{#if item.type === "header"}
<span class="wire-dropdown__heading">{item.label}</span>
{:else}
<a class="wire-dropdown__item {item.danger ? 'wire-dropdown__item--danger' : ''}" href="{item.href || '#'}" target="{item.target || ''}" rel="{item.rel || ''}" role="menuitem" @click="selectItem(item)">
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
<span><strong>{item.label}</strong>{#if item.description}<small>{item.description}</small>{/if}</span>
{#if item.external}<span class="icon-[lucide--arrow-up-right]" aria-hidden="true"></span>{/if}
</a>
{/if}
{/if}
{/each}
<slot name="footer" />
</div>
</details>
}
}
+126 -6
View File
@@ -3,18 +3,138 @@ component Navbar {
@event toggle = function
@event open = function
@event close = function
@event select = function
@event action = function
size = "default"
color = "primary"
label = "Navbar"
label = "Primary navigation"
topbarLabel = "Utility navigation"
brand = {}
items = []
actions = []
active = ""
orientation = "horizontal"
sticky = false
openOnHover = false
maxWidth = "full"
mobileLabel = "Toggle navigation"
class = ""
}
state mobileOpen = false
functions {
function toggleNavigation() {
mobileOpen = !mobileOpen
$emit("toggle", { open: mobileOpen })
$emit(mobileOpen ? "open" : "close", { source: "mobile" })
}
function selectItem(item, level) {
mobileOpen = false
$emit("select", { item: item, value: item.value || "", level: level })
}
function selectAction(item) {
mobileOpen = false
$emit("action", { item: item, value: item.value || "" })
}
function isItemActive(item) {
if (!item) return false
if (item.value && item.value === active) return true
if (!item.children || !item.children.length) return false
return item.children.some(function (child) {
return isItemActive(child)
})
}
function toggleDropdown(event, item) {
$emit(event.currentTarget.open ? "open" : "close", {
source: "menu",
item: item
})
}
}
view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--navbar wire-next--{orientation} {class}" aria-label="{label}">
{#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
<slot />
</nav>
<header class="wire-navbar wire-navbar--width-{maxWidth} wire-next--color-{color} wire-next--size-{size} {sticky ? 'wire-navbar--sticky' : ''} {class}" data-wrn-navbar data-open-on-hover="{openOnHover}">
<div class="wire-navbar__topbar" aria-label="{topbarLabel}">
<slot name="topbar" />
</div>
<div class="wire-navbar__main">
<a class="wire-navbar__brand" href="{brand.href || '/'}" aria-label="{brand.ariaLabel || brand.label || label}">
{#if brand.logo}
<img class="wire-navbar__brand-logo" src="{brand.logo}" alt="{brand.logoAlt || brand.label || ''}" width="{brand.logoWidth || ''}" height="{brand.logoHeight || ''}" />
{/if}
{#if brand.icon}
<span class="wire-navbar__brand-icon {brand.icon}" aria-hidden="true"></span>
{/if}
{#if brand.label || brand.description}
<span class="wire-navbar__brand-copy">
{#if brand.label}<strong>{brand.label}</strong>{/if}
{#if brand.description}<small>{brand.description}</small>{/if}
</span>
{/if}
</a>
<button type="button" class="wire-navbar__toggle" aria-label="{mobileLabel}" aria-expanded="{mobileOpen}" @click="toggleNavigation()">
<span aria-hidden="true"></span><span aria-hidden="true"></span><span aria-hidden="true"></span>
</button>
<div class="wire-navbar__collapse {mobileOpen ? 'is-open' : ''}">
<nav class="wire-navbar__menus" aria-label="{label}">
{#each items as item}
{#if item.children && item.children.length}
<details class="wire-navbar__dropdown wire-navbar__dropdown--{item.type || 'dropdown'}" name="wire-navbar-menu" @toggle="toggleDropdown($event, item)">
<summary aria-current="{isItemActive(item) ? 'page' : ''}">
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
<span>{item.label}</span>
<span class="wire-navbar__chevron" aria-hidden="true"></span>
</summary>
<div class="wire-navbar__panel wire-navbar__panel--columns-{item.columns || 1}">
{#if item.description}<p class="wire-navbar__panel-intro">{item.description}</p>{/if}
{#each item.children as child}
<div class="wire-navbar__group">
{#if child.children && child.children.length}
{#if child.label}<strong class="wire-navbar__group-title">{child.label}</strong>{/if}
{#if child.description}<small>{child.description}</small>{/if}
{#each child.children as nested}
<a href="{nested.href || '#'}" target="{nested.target || ''}" rel="{nested.rel || ''}" aria-current="{nested.value === active ? 'page' : ''}" @click="selectItem(nested, 3)">
{#if nested.icon}<span class="{nested.icon}" aria-hidden="true"></span>{/if}
<span><strong>{nested.label}</strong>{#if nested.description}<small>{nested.description}</small>{/if}</span>
</a>
{/each}
{:else}
<a href="{child.href || '#'}" target="{child.target || ''}" rel="{child.rel || ''}" aria-current="{child.value === active ? 'page' : ''}" @click="selectItem(child, 2)">
{#if child.icon}<span class="{child.icon}" aria-hidden="true"></span>{/if}
<span><strong>{child.label}</strong>{#if child.description}<small>{child.description}</small>{/if}</span>
</a>
{/if}
</div>
{/each}
</div>
</details>
{:else}
<a class="wire-navbar__menu-link" href="{item.href || '#'}" target="{item.target || ''}" rel="{item.rel || ''}" aria-current="{item.value === active ? 'page' : ''}" @click="selectItem(item, 1)">
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
<span>{item.label}</span>
</a>
{/if}
{/each}
</nav>
<div class="wire-navbar__actions">
{#each actions as item}
<a class="wire-navbar__action wire-navbar__action--{item.variant || 'link'}" href="{item.href || '#'}" target="{item.target || ''}" rel="{item.rel || ''}" @click="selectAction(item)">
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
<span>{item.label}</span>
</a>
{/each}
<slot name="actions" />
</div>
</div>
</div>
</header>
}
}
+63 -4
View File
@@ -3,18 +3,77 @@ component Sidebar {
@event toggle = function
@event open = function
@event close = function
@event select = function
size = "default"
color = "primary"
label = "Sidebar"
items = []
active = ""
orientation = "horizontal"
mobileLabel = "Open navigation"
class = ""
}
state mobileOpen = false
functions {
function toggleSidebar() {
mobileOpen = !mobileOpen
$emit("toggle", { open: mobileOpen })
$emit(mobileOpen ? "open" : "close", { source: "mobile" })
}
function closeSidebar() {
mobileOpen = false
$emit("close", { source: "mobile" })
}
function selectItem(item, level) {
mobileOpen = false
$emit("select", { item: item, value: item.value || "", level: level })
}
}
view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--sidebar wire-next--{orientation} {class}" aria-label="{label}">
{#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
<slot />
</nav>
<div class="wire-sidebar-shell wire-next--color-{color} wire-next--size-{size} {mobileOpen ? 'is-open' : ''} {class}">
<button type="button" class="wire-sidebar-toggle" aria-label="{mobileLabel}" aria-expanded="{mobileOpen}" @click="toggleSidebar()">
<span class="icon-[lucide--panel-left-open]" aria-hidden="true"></span>
<span>{label}</span>
</button>
<button type="button" class="wire-sidebar-backdrop" aria-label="Close navigation" @click="closeSidebar()"></button>
<nav class="wire-next wire-next--sidebar wire-next--{orientation} wire-sidebar-panel" aria-label="{label}">
<div class="wire-sidebar-panel__head">
<strong>{label}</strong>
<button type="button" aria-label="Close navigation" @click="closeSidebar()"><span class="icon-[lucide--x]" aria-hidden="true"></span></button>
</div>
<div class="wire-sidebar-items">
{#each items as item}
{#if item.children && item.children.length}
<details class="wire-sidebar-group" name="wire-sidebar-group">
<summary>
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
<span>{item.label}</span>
<span class="icon-[lucide--chevron-down]" aria-hidden="true"></span>
</summary>
<div class="wire-sidebar-group__children">
{#each item.children as child}
<a href="{child.href || '#'}" aria-current="{child.value === active ? 'page' : ''}" @click="selectItem(child, 2)">
{#if child.icon}<span class="{child.icon}" aria-hidden="true"></span>{/if}
<span>{child.label}</span>
</a>
{/each}
</div>
</details>
{:else}
<a href="{item.href || '#'}" aria-current="{item.value === active ? 'page' : ''}" @click="selectItem(item, 1)">
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
<span>{item.label}</span>
</a>
{/if}
{/each}
</div>
<slot />
</nav>
</div>
}
}
+123 -12
View File
@@ -38,25 +38,136 @@ component Checkbox {
function preventReadonly(sourceEvent) { if (readonly) sourceEvent.preventDefault() }
}
view {
<div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--choice-field wire-next--checkbox-field {class}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}" data-orientation="{orientation}" data-card="{card}" data-list="{list}" data-right-aligned="{rightAligned}">
<div class="wire-next__field-heading">{#if options.length || cornerHint}<span class="{hiddenLabel ? 'wire-next__sr-only' : ''}">{label}</span>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}{/if}</div>
<div
{...attrs}
class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--choice-field wire-next--checkbox-field {class}"
data-inline="{inline}"
data-invalid="{error ? 'true' : 'false'}"
data-orientation="{orientation}"
data-card="{card}"
data-list="{list}"
data-right-aligned="{rightAligned}"
>
<div
class="wire-next__field-heading"
>
{#if options.length || cornerHint}
<span
class="{hiddenLabel ? 'wire-next__sr-only' : ''}"
>
{label}
</span>
{#if cornerHint}
<span
class="wire-next__field-hint"
>
{cornerHint}
</span>
{/if}
{/if}
</div>
{#if options.length}
<div class="wire-next__checkbox-group" role="group" aria-label="{hiddenLabel ? label : ''}" aria-invalid="{error ? 'true' : 'false'}">
<div
class="wire-next__checkbox-group"
role="group"
aria-label="{hiddenLabel ? label : ''}"
aria-invalid="{error ? 'true' : 'false'}"
>
{#each options as option}
<label class="wire-next__choice wire-next--checkbox" for="{id || name}-{option.value}" data-variant="{variant}" data-disabled="{disabled || option.disabled}">
<input id="{id || name}-{option.value}" type="checkbox" name="{name}" value="{option.value}" checked="{values.includes(option.value) || option.checked}" disabled="{disabled || option.disabled}" required="{required}" readonly="{readonly}" @click="preventReadonly(event)" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @invalid="emitField('invalid', event)" />
<span class="wire-next__choice-copy"><strong>{option.label}</strong>{#if option.description}<small>{option.description}</small>{/if}</span>
<label
class="wire-next__choice wire-next--checkbox"
for="{id || name}-{option.value}"
data-variant="{variant}"
data-disabled="{disabled || option.disabled}"
>
<input
id="{id || name}-{option.value}"
type="checkbox"
name="{name}"
value="{option.value}"
checked="{values.includes(option.value) || option.checked}"
disabled="{disabled || option.disabled}"
required="{required}"
readonly="{readonly}"
@click="preventReadonly(event)"
@input="emitField('input', event)"
@change="emitField('change', event)"
@focus="emitField('focus', event)"
@blur="emitField('blur', event)"
@invalid="emitField('invalid', event)"
/>
<span
class="wire-next__choice-copy"
>
<strong>{option.label}</strong>
{#if option.description}
<small>{option.description}</small>
{/if}
</span>
</label>
{/each}
</div>
{:else}
<label class="wire-next__choice wire-next--checkbox" for="{id || name}" data-variant="{variant}" data-icon-position="{iconPosition}">
<input id="{id || name}" type="checkbox" name="{name}" value="{value}" checked="{checked}" disabled="{disabled}" required="{required}" readonly="{readonly}" aria-checked="{indeterminate ? 'mixed' : (checked ? 'true' : 'false')}" aria-invalid="{error ? 'true' : 'false'}" @click="preventReadonly(event)" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @invalid="emitField('invalid', event)" />
{#if icon}<span class="{icon}" aria-hidden="true"></span>{/if}
<span class="{hiddenLabel || cornerHint ? 'wire-next__sr-only' : ''}">{label}</span>
</label>
<div
class="wire-next__checkbox-content"
>
<label
class="wire-next__choice wire-next--checkbox"
for="{id || name}"
data-variant="{variant}"
data-icon-position="{iconPosition}"
>
<input
id="{id || name}"
type="checkbox"
name="{name}"
value="{value}"
checked="{checked}"
disabled="{disabled}"
required="{required}"
readonly="{readonly}"
aria-checked="{indeterminate ? 'mixed' : (checked ? 'true' : 'false')}"
aria-invalid="{error ? 'true' : 'false'}"
@click="preventReadonly(event)"
@input="emitField('input', event)"
@change="emitField('change', event)"
@focus="emitField('focus', event)"
@blur="emitField('blur', event)"
@invalid="emitField('invalid', event)"
/>
{#if icon}
<span
class="{icon}"
aria-hidden="true"
>
</span>
{/if}
<span
class="{hiddenLabel || cornerHint ? 'wire-next__sr-only' : ''}"
>
{label}
</span>
<div
class="wire-next__checkbox-slot"
>
<slot></slot>
</div>
</label>
</div>
{/if}
{#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}<small class="wire-next__field-error" data-error="{name}">{error}</small>
{#if helperText}
<small
class="wire-next__field-help"
>
{helperText}
</small>
{/if}
<small
class="wire-next__field-error"
data-error="{name}"
>
{error}
</small>
</div>
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/ui",
"version": "0.5.1",
"version": "0.5.10",
"type": "module",
"description": "@wrnexus/ui — part of the WrNexus framework.",
"license": "MIT",
@@ -26,7 +26,7 @@
"./ui.css": "./ui.css"
},
"dependencies": {
"@wrnexus/core": "^0.5.1"
"@wrnexus/core": "^0.5.10"
},
"files": [
"dist",
+1552 -14
View File
File diff suppressed because it is too large Load Diff