release: WRNexusJS 0.5.12
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/auth",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/captcha",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1137,6 +1137,22 @@ const MIGRATIONS: Migration[] = [
|
||||
// Gateway, helper, validation, and UI improvements are package-managed.
|
||||
},
|
||||
},
|
||||
{
|
||||
version: "0.5.11",
|
||||
id: "latest-release-after-upgrade",
|
||||
description: "Latest New Packages and release ready packages",
|
||||
apply() {
|
||||
// Gateway, helper, validation, and UI improvements are package-managed.
|
||||
},
|
||||
},
|
||||
{
|
||||
version: "0.5.12",
|
||||
id: "latest-release-after-upgrade",
|
||||
description: "Latest New Packages and release ready packages",
|
||||
apply() {
|
||||
// Gateway, helper, validation, and UI improvements are package-managed.
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/** Release tooling uses this to require an explicit migration entry per version. */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-toolbar",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/plugin",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/syntax",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
+171
-9
@@ -1,6 +1,6 @@
|
||||
# WRNexus UI component reference
|
||||
|
||||
This reference is generated from the 90 packaged `.wrn` component sources. Props marked required have no default; all others show their runtime default.
|
||||
This reference is generated from the 108 packaged `.wrn` component sources. Props marked required have no default; all others show their runtime default.
|
||||
|
||||
## Advanced-forms
|
||||
|
||||
@@ -56,7 +56,7 @@ Theme-aware, responsive search box component.
|
||||
- Mount: `data-component="SearchBox"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Search Box"`, `name: string = ""`, `value: string = ""`, `placeholder: string = ""`, `type: string = "search"`, `min: string = ""`, `max: string = ""`, `step: string = ""`, `disabled: boolean = false`, `required: boolean = false`, `class: string = ""`
|
||||
- Slots: None
|
||||
- Events: `input`, `change`, `search`, `submit`, `clear`
|
||||
- Events: None
|
||||
|
||||
### StrongPassword
|
||||
|
||||
@@ -222,6 +222,15 @@ Theme-aware, responsive file upload progress component.
|
||||
- Slots: None
|
||||
- Events: `cancel`, `retry`, `complete`
|
||||
|
||||
### HeroActions
|
||||
|
||||
Responsive action group for hero and call-to-action sections.
|
||||
|
||||
- Mount: `data-component="HeroActions"`
|
||||
- Props: `actions: string = []`, `align: string = "left"`, `orientation: string = "horizontal"`, `stackOnMobile: boolean = true`, `fullWidthMobile: boolean = true`, `size: string = "default"`, `color: string = "primary"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Events: None
|
||||
|
||||
### LegendIndicator
|
||||
|
||||
Theme-aware, responsive legend indicator component.
|
||||
@@ -238,7 +247,7 @@ Theme-aware, responsive list component.
|
||||
- Mount: `data-component="List"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "List"`, `description: string = ""`, `items: string = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Events: `select`, `click`
|
||||
- Events: None
|
||||
|
||||
### ListGroup
|
||||
|
||||
@@ -256,7 +265,7 @@ Theme-aware, responsive marquee component.
|
||||
- Mount: `data-component="Marquee"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Marquee"`, `description: string = ""`, `items: string = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Events: `pause`, `resume`
|
||||
- Events: None
|
||||
|
||||
### Progress
|
||||
|
||||
@@ -310,7 +319,7 @@ Theme-aware, responsive timeline component.
|
||||
- Mount: `data-component="Timeline"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Timeline"`, `description: string = ""`, `items: string = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Events: `select`
|
||||
- Events: None
|
||||
|
||||
### Toast
|
||||
|
||||
@@ -332,6 +341,78 @@ Theme-aware, responsive tree view component.
|
||||
|
||||
## Blocks
|
||||
|
||||
### AnnouncementBar
|
||||
|
||||
Accessible public announcement, emergency, maintenance, or status bar.
|
||||
|
||||
- Mount: `data-component="AnnouncementBar"`
|
||||
- Props: `badge: string = ""`, `badgeIcon: string = ""`, `message: string = "Announcement"`, `description: string = ""`, `icon: string = "icon-[lucide--megaphone]"`, `actionLabel: string = ""`, `actionHref: string = ""`, `actionIcon: string = ""`, `dismissible: boolean = false`, `dismissLabel: string = "Dismiss announcement"`, `sticky: boolean = false`, `compact: boolean = false`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "soft"`, `role: string = "status"`, `live: string = "polite"`, `class: string = ""`
|
||||
- Slots: None
|
||||
- Events: None
|
||||
|
||||
### CTASection
|
||||
|
||||
Reusable closing call-to-action section with primary and secondary actions.
|
||||
|
||||
- Mount: `data-component="CTASection"`
|
||||
- Props: `eyebrow: string = ""`, `title: string = "Ready to get started?"`, `description: string = ""`, `icon: string = ""`, `align: string = "center"`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "solid"`, `primaryLabel: string = "Get started"`, `primaryHref: string = "#"`, `primaryIcon: string = ""`, `secondaryLabel: string = ""`, `secondaryHref: string = ""`, `secondaryIcon: string = ""`, `backgroundImage: string = ""`, `maxWidth: string = "xl"`, `class: string = ""`
|
||||
- Slots: `default`, `actions`, `visual`, `footer`
|
||||
- Events: None
|
||||
|
||||
### FeatureCard
|
||||
|
||||
Reusable linked feature or service card with icon, badge, description, and action.
|
||||
|
||||
- Mount: `data-component="FeatureCard"`
|
||||
- Props: `icon: string = ""`, `title: string = "Feature"`, `description: string = ""`, `href: string = ""`, `actionLabel: string = "Learn more"`, `actionIcon: string = ""`, `badge: string = ""`, `badgeColor: string = "primary"`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `hover: string = "lift"`, `align: string = "left"`, `disabled: boolean = false`, `class: string = ""`
|
||||
- Slots: `icon`, `default`, `footer`
|
||||
- Events: None
|
||||
|
||||
### FeatureIconCard
|
||||
|
||||
Feature card with a prominent themed icon treatment.
|
||||
|
||||
- Mount: `data-component="FeatureIconCard"`
|
||||
- Props: `icon: string = "icon-[lucide--sparkles]"`, `iconSize: string = "md"`, `iconVariant: string = "soft"`, `title: string = "Feature"`, `description: string = ""`, `href: string = ""`, `actionLabel: string = "Explore"`, `badge: string = ""`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `align: string = "left"`, `hover: string = "lift"`, `class: string = ""`
|
||||
- Slots: `default`, `footer`
|
||||
- Events: None
|
||||
|
||||
### Hero
|
||||
|
||||
Reusable public-page hero with actions, trust items, badges, and visual slots.
|
||||
|
||||
- Mount: `data-component="Hero"`
|
||||
- Props: `eyebrow: string = ""`, `title: string = "Build something remarkable"`, `highlight: string = ""`, `description: string = ""`, `align: string = "left"`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `primaryLabel: string = ""`, `primaryHref: string = ""`, `primaryIcon: string = ""`, `secondaryLabel: string = ""`, `secondaryHref: string = ""`, `secondaryIcon: string = ""`, `tertiaryLabel: string = ""`, `tertiaryHref: string = ""`, `badges: string = []`, `trustItems: string = []`, `maxWidth: string = "xl"`, `class: string = ""`
|
||||
- Slots: `eyebrow`, `actions`, `trust`, `default`, `visual`, `footer`
|
||||
- Events: None
|
||||
|
||||
### MarketingSectionHeader
|
||||
|
||||
Marketing section heading with an optional linked action.
|
||||
|
||||
- Mount: `data-component="MarketingSectionHeader"`
|
||||
- Props: `id: string = ""`, `eyebrow: string = ""`, `title: string = ""`, `description: string = ""`, `align: string = "split"`, `size: string = "default"`, `color: string = "primary"`, `actionLabel: string = ""`, `actionHref: string = ""`, `actionIcon: string = ""`, `actionExternal: boolean = false`, `class: string = ""`
|
||||
- Slots: `icon`, `actions`, `default`
|
||||
- Events: None
|
||||
|
||||
### MetricCard
|
||||
|
||||
Single statistic or operational metric with icon, trend, and optional action.
|
||||
|
||||
- Mount: `data-component="MetricCard"`
|
||||
- Props: `label: string = "Metric"`, `value: string = "0"`, `description: string = ""`, `icon: string = ""`, `prefix: string = ""`, `suffix: string = ""`, `trend: string = ""`, `trendLabel: string = ""`, `trendDirection: string = "neutral"`, `href: string = ""`, `actionLabel: string = ""`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `align: string = "left"`, `class: string = ""`
|
||||
- Slots: None
|
||||
- Events: None
|
||||
|
||||
### PageHeader
|
||||
|
||||
Public and application page header with breadcrumbs, metadata, and actions.
|
||||
|
||||
- Mount: `data-component="PageHeader"`
|
||||
- Props: `eyebrow: string = ""`, `title: string = ""`, `description: string = ""`, `icon: string = ""`, `align: string = "left"`, `size: string = "default"`, `compact: boolean = false`, `showBreadcrumbs: boolean = false`, `breadcrumbs: string = []`, `primaryLabel: string = ""`, `primaryHref: string = ""`, `primaryIcon: string = ""`, `secondaryLabel: string = ""`, `secondaryHref: string = ""`, `secondaryIcon: string = ""`, `color: string = "primary"`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `meta`, `actions`, `default`
|
||||
- Events: None
|
||||
|
||||
### PortalDashboard
|
||||
|
||||
Responsive portal dashboard with metrics, actions, tasks, and updates.
|
||||
@@ -341,6 +422,24 @@ Responsive portal dashboard with metrics, actions, tasks, and updates.
|
||||
- Slots: `hero-action`
|
||||
- Events: `action`, `navigate`
|
||||
|
||||
### SplitHero
|
||||
|
||||
Two-column hero with configurable content and visual placement.
|
||||
|
||||
- Mount: `data-component="SplitHero"`
|
||||
- Props: `eyebrow: string = ""`, `title: string = "A better digital experience"`, `highlight: string = ""`, `description: string = ""`, `primaryLabel: string = ""`, `primaryHref: string = ""`, `primaryIcon: string = ""`, `secondaryLabel: string = ""`, `secondaryHref: string = ""`, `secondaryIcon: string = ""`, `visualPosition: string = "right"`, `reverse: boolean = false`, `ratio: string = "balanced"`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `trustItems: string = []`, `class: string = ""`
|
||||
- Slots: `actions`, `trust`, `default`, `visual`
|
||||
- Events: None
|
||||
|
||||
### StatsBar
|
||||
|
||||
Compact responsive statistics strip.
|
||||
|
||||
- Mount: `data-component="StatsBar"`
|
||||
- Props: `items: string = []`, `columns: number = 4`, `compact: boolean = true`, `dividers: boolean = true`, `icons: boolean = true`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "raised"`, `maxWidth: string = "xl"`, `class: string = ""`
|
||||
- Slots: None
|
||||
- Events: None
|
||||
|
||||
## Forms
|
||||
|
||||
### AuthForm
|
||||
@@ -541,7 +640,7 @@ Theme-aware, responsive map component.
|
||||
- Mount: `data-component="Map"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Map"`, `description: string = ""`, `items: string = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Events: `ready`, `move`, `zoom`, `markerClick`
|
||||
- Events: None
|
||||
|
||||
### ToastNotifications
|
||||
|
||||
@@ -608,6 +707,15 @@ Theme-aware, responsive divider component.
|
||||
- Slots: None
|
||||
- Events: None
|
||||
|
||||
### FeatureGrid
|
||||
|
||||
Responsive equal-height grid for feature and service cards.
|
||||
|
||||
- Mount: `data-component="FeatureGrid"`
|
||||
- Props: `color: string = "primary"`, `size: string = "default"`, `columns: number = 3`, `tabletColumns: number = 2`, `mobileColumns: number = 1`, `gap: string = "md"`, `minItemWidth: string = ""`, `equalHeight: boolean = true`, `align: string = "stretch"`, `maxWidth: string = "full"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Events: None
|
||||
|
||||
### Grid
|
||||
|
||||
Theme-aware, responsive grid component.
|
||||
@@ -624,7 +732,7 @@ Theme-aware, responsive image component.
|
||||
- Mount: `data-component="Image"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `src: string = ""`, `alt: string = ""`, `width: string = ""`, `height: string = ""`, `loading: string = "lazy"`, `rounded: boolean = false`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Events: `load`, `error`
|
||||
- Events: None
|
||||
|
||||
### Kbd
|
||||
|
||||
@@ -651,6 +759,51 @@ Theme-aware, responsive link component.
|
||||
- Mount: `data-component="Link"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Link"`, `href: string = "#"`, `target: string = ""`, `rel: string = ""`, `external: boolean = false`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Events: None
|
||||
|
||||
### MetricGrid
|
||||
|
||||
Responsive collection of metric cards.
|
||||
|
||||
- Mount: `data-component="MetricGrid"`
|
||||
- Props: `items: string = []`, `columns: number = 4`, `tabletColumns: number = 2`, `mobileColumns: number = 1`, `gap: string = "md"`, `equalHeight: boolean = true`, `dividers: boolean = false`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Events: None
|
||||
|
||||
### PublicPageShell
|
||||
|
||||
Consistent structural wrapper for public-facing pages.
|
||||
|
||||
- Mount: `data-component="PublicPageShell"`
|
||||
- Props: `maxWidth: string = "full"`, `fullWidth: boolean = true`, `headerOffset: string = "none"`, `background: string = "default"`, `overflow: string = "clip"`, `minHeight: string = "screen"`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `before`, `default`, `after`
|
||||
- Events: None
|
||||
|
||||
### Section
|
||||
|
||||
Theme-aware page section with consistent spacing, width, background, and border controls.
|
||||
|
||||
- Mount: `data-component="Section"`
|
||||
- Props: `id: string = ""`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `spacing: string = "lg"`, `maxWidth: string = "xl"`, `fullWidth: boolean = false`, `borderTop: boolean = false`, `borderBottom: boolean = false`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Events: None
|
||||
|
||||
### SectionHeader
|
||||
|
||||
Reusable accessible section heading with eyebrow, title, description, icon, and actions.
|
||||
|
||||
- Mount: `data-component="SectionHeader"`
|
||||
- Props: `id: string = ""`, `eyebrow: string = ""`, `title: string = ""`, `description: string = ""`, `align: string = "left"`, `size: string = "default"`, `color: string = "primary"`, `headingLevel: number = 2`, `maxWidth: string = "3xl"`, `class: string = ""`
|
||||
- Slots: `icon`, `default`, `actions`
|
||||
- Events: None
|
||||
|
||||
### TextLink
|
||||
|
||||
Styled inline action link with icons, arrows, external state, and disabled handling.
|
||||
|
||||
- Mount: `data-component="TextLink"`
|
||||
- Props: `label: string = "Learn more"`, `href: string = "#"`, `target: string = ""`, `rel: string = ""`, `external: boolean = false`, `icon: string = ""`, `iconPosition: string = "start"`, `showArrow: boolean = true`, `underline: boolean = false`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `disabled: boolean = false`, `class: string = ""`
|
||||
- Slots: None
|
||||
- Events: `click`, `focus`, `blur`
|
||||
|
||||
### Typography
|
||||
@@ -664,6 +817,15 @@ Theme-aware, responsive typography component.
|
||||
|
||||
## Navigation
|
||||
|
||||
### BackToTop
|
||||
|
||||
Accessible scroll-to-top control with visibility threshold.
|
||||
|
||||
- Mount: `data-component="BackToTop"`
|
||||
- Props: `threshold: number = 500`, `label: string = "Back to top"`, `ariaLabel: string = "Scroll back to top"`, `icon: string = "icon-[lucide--arrow-up]"`, `position: string = "right"`, `offset: string = "md"`, `behavior: string = "smooth"`, `showProgress: boolean = false`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "solid"`, `class: string = ""`
|
||||
- Slots: None
|
||||
- Events: None
|
||||
|
||||
### Breadcrumb
|
||||
|
||||
Theme-aware, responsive breadcrumb component.
|
||||
@@ -679,7 +841,7 @@ 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`
|
||||
- Slots: `pre-footer`, `copyright-left`, `copyright-right`, `post-footer`
|
||||
- Events: `select`, `action`
|
||||
|
||||
### MegaMenu
|
||||
@@ -761,7 +923,7 @@ Theme-aware, responsive tabs component.
|
||||
- Mount: `data-component="Tabs"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Tabs"`, `items: string = []`, `active: string = ""`, `orientation: string = "horizontal"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Events: `change`, `select`
|
||||
- Events: None
|
||||
|
||||
## Overlays
|
||||
|
||||
|
||||
+412
-322
@@ -1,66 +1,47 @@
|
||||
{
|
||||
"sourceDocuments": ["Screenshot-directed WRNexus UI catalog reset"],
|
||||
"components": [
|
||||
{
|
||||
"name": "Container",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive container component."
|
||||
},
|
||||
{
|
||||
"name": "Columns",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive columns component."
|
||||
},
|
||||
{
|
||||
"name": "Grid",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive grid component."
|
||||
},
|
||||
{
|
||||
"name": "LayoutSplitter",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive layout splitter component."
|
||||
},
|
||||
{
|
||||
"name": "Typography",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive typography component."
|
||||
},
|
||||
{
|
||||
"name": "Image",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive image component."
|
||||
},
|
||||
{
|
||||
"name": "Link",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive link component."
|
||||
},
|
||||
{
|
||||
"name": "Divider",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive divider component."
|
||||
},
|
||||
{
|
||||
"name": "Kbd",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive kbd component."
|
||||
},
|
||||
{
|
||||
"name": "CustomScrollbar",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive custom scrollbar component."
|
||||
},
|
||||
{
|
||||
"name": "Accordion",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive accordion component."
|
||||
},
|
||||
{
|
||||
"name": "AdvancedDatePicker",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive advanced date picker component."
|
||||
},
|
||||
{
|
||||
"name": "AdvancedRangeSlider",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive advanced range slider component."
|
||||
},
|
||||
{
|
||||
"name": "AdvancedSelect",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive advanced select component.",
|
||||
"events": ["search", "select", "change", "clear", "open", "close", "load", "error"]
|
||||
},
|
||||
{
|
||||
"name": "Alert",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive alert component."
|
||||
},
|
||||
{
|
||||
"name": "AnnouncementBar",
|
||||
"category": "blocks",
|
||||
"purpose": "Accessible public announcement, emergency, maintenance, or status bar."
|
||||
},
|
||||
{
|
||||
"name": "AuthForm",
|
||||
"category": "forms",
|
||||
"purpose": "Reusable authentication form for sign-in, registration, recovery, reset, and MFA."
|
||||
},
|
||||
{
|
||||
"name": "AuthSplitLayout",
|
||||
"category": "layout",
|
||||
"purpose": "Responsive 50/50 authentication layout with content and form regions."
|
||||
},
|
||||
{
|
||||
"name": "Avatar",
|
||||
"category": "base",
|
||||
@@ -71,6 +52,11 @@
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive avatar group component."
|
||||
},
|
||||
{
|
||||
"name": "BackToTop",
|
||||
"category": "navigation",
|
||||
"purpose": "Accessible scroll-to-top control with visibility threshold."
|
||||
},
|
||||
{
|
||||
"name": "Badge",
|
||||
"category": "base",
|
||||
@@ -81,6 +67,11 @@
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive blockquote component."
|
||||
},
|
||||
{
|
||||
"name": "Breadcrumb",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive breadcrumb component."
|
||||
},
|
||||
{
|
||||
"name": "Button",
|
||||
"category": "base",
|
||||
@@ -96,165 +87,20 @@
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive card component."
|
||||
},
|
||||
{
|
||||
"name": "ChatBubble",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive chat bubble component."
|
||||
},
|
||||
{
|
||||
"name": "Carousel",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive carousel component."
|
||||
},
|
||||
{
|
||||
"name": "Collapse",
|
||||
"name": "Chart",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive chart component."
|
||||
},
|
||||
{
|
||||
"name": "ChatBubble",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive collapse component."
|
||||
},
|
||||
{
|
||||
"name": "DatePicker",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive date picker component."
|
||||
},
|
||||
{
|
||||
"name": "DeviceFrame",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive device frame component."
|
||||
},
|
||||
{
|
||||
"name": "List",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive list component."
|
||||
},
|
||||
{
|
||||
"name": "ListGroup",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive list group component."
|
||||
},
|
||||
{
|
||||
"name": "LegendIndicator",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive legend indicator component."
|
||||
},
|
||||
{
|
||||
"name": "Progress",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive progress component."
|
||||
},
|
||||
{
|
||||
"name": "FileUploadProgress",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive file upload progress component."
|
||||
},
|
||||
{
|
||||
"name": "Rating",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive rating component."
|
||||
},
|
||||
{
|
||||
"name": "Skeleton",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive skeleton component."
|
||||
},
|
||||
{
|
||||
"name": "Spinner",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive spinner component."
|
||||
},
|
||||
{
|
||||
"name": "StyledIcon",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive styled icon component."
|
||||
},
|
||||
{
|
||||
"name": "Toast",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive toast component."
|
||||
},
|
||||
{
|
||||
"name": "Timeline",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive timeline component."
|
||||
},
|
||||
{
|
||||
"name": "TreeView",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive tree view component."
|
||||
},
|
||||
{
|
||||
"name": "Marquee",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive marquee component."
|
||||
},
|
||||
{
|
||||
"name": "Navbar",
|
||||
"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",
|
||||
"purpose": "Theme-aware, responsive mega menu component."
|
||||
},
|
||||
{
|
||||
"name": "Nav",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive nav component."
|
||||
},
|
||||
{
|
||||
"name": "Tabs",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive tabs component."
|
||||
},
|
||||
{
|
||||
"name": "Sidebar",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive sidebar component."
|
||||
},
|
||||
{
|
||||
"name": "Scrollspy",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive scrollspy component."
|
||||
},
|
||||
{
|
||||
"name": "Breadcrumb",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive breadcrumb component."
|
||||
},
|
||||
{
|
||||
"name": "Pagination",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive pagination component."
|
||||
},
|
||||
{
|
||||
"name": "Stepper",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive stepper component."
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive input component."
|
||||
},
|
||||
{
|
||||
"name": "InputGroup",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive input group component."
|
||||
},
|
||||
{
|
||||
"name": "Textarea",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive textarea component."
|
||||
},
|
||||
{
|
||||
"name": "FileInput",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive file input component."
|
||||
"purpose": "Theme-aware, responsive chat bubble component."
|
||||
},
|
||||
{
|
||||
"name": "Checkbox",
|
||||
@@ -262,24 +108,14 @@
|
||||
"purpose": "Theme-aware, responsive checkbox component."
|
||||
},
|
||||
{
|
||||
"name": "Radio",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive radio component."
|
||||
"name": "Clipboard",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive clipboard component."
|
||||
},
|
||||
{
|
||||
"name": "Switch",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive switch component."
|
||||
},
|
||||
{
|
||||
"name": "Select",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive select component."
|
||||
},
|
||||
{
|
||||
"name": "RangeSlider",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive range slider component."
|
||||
"name": "Collapse",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive collapse component."
|
||||
},
|
||||
{
|
||||
"name": "ColorPicker",
|
||||
@@ -287,15 +123,9 @@
|
||||
"purpose": "Theme-aware, responsive color picker component."
|
||||
},
|
||||
{
|
||||
"name": "TimePicker",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive time picker component."
|
||||
},
|
||||
{
|
||||
"name": "AdvancedSelect",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive advanced select component.",
|
||||
"events": ["search", "select", "change", "clear", "open", "close", "load", "error"]
|
||||
"name": "Columns",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive columns component."
|
||||
},
|
||||
{
|
||||
"name": "ComboBox",
|
||||
@@ -304,46 +134,14 @@
|
||||
"events": ["search", "select", "change", "clear", "open", "close", "load", "error"]
|
||||
},
|
||||
{
|
||||
"name": "SearchBox",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive search box component."
|
||||
"name": "Confetti",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive confetti component."
|
||||
},
|
||||
{
|
||||
"name": "InputNumber",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive input number component."
|
||||
},
|
||||
{
|
||||
"name": "StrongPassword",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive strong password component."
|
||||
},
|
||||
{
|
||||
"name": "TogglePassword",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Accessible password field with optional show and hide controls.",
|
||||
"events": ["input", "change", "toggle"]
|
||||
},
|
||||
{
|
||||
"name": "ToggleCount",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive toggle count component."
|
||||
},
|
||||
{
|
||||
"name": "CopyMarkup",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive copy markup component."
|
||||
},
|
||||
{
|
||||
"name": "PinInput",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Secure multi-cell PIN and verification-code input with regex and paste support.",
|
||||
"events": ["input", "change", "complete", "paste", "clear", "error"]
|
||||
},
|
||||
{
|
||||
"name": "Dropdown",
|
||||
"category": "overlays",
|
||||
"purpose": "Theme-aware, responsive dropdown component."
|
||||
"name": "Container",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive container component."
|
||||
},
|
||||
{
|
||||
"name": "ContextMenu",
|
||||
@@ -351,54 +149,19 @@
|
||||
"purpose": "Theme-aware, responsive context menu component."
|
||||
},
|
||||
{
|
||||
"name": "Modal",
|
||||
"category": "overlays",
|
||||
"purpose": "Theme-aware, responsive modal component."
|
||||
"name": "CopyMarkup",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive copy markup component."
|
||||
},
|
||||
{
|
||||
"name": "Drawer",
|
||||
"category": "overlays",
|
||||
"purpose": "Theme-aware, responsive drawer component."
|
||||
"name": "CTASection",
|
||||
"category": "blocks",
|
||||
"purpose": "Reusable closing call-to-action section with primary and secondary actions."
|
||||
},
|
||||
{
|
||||
"name": "Popover",
|
||||
"category": "overlays",
|
||||
"purpose": "Theme-aware, responsive popover component."
|
||||
},
|
||||
{
|
||||
"name": "Tooltip",
|
||||
"category": "overlays",
|
||||
"purpose": "Theme-aware, responsive tooltip component."
|
||||
},
|
||||
{
|
||||
"name": "Table",
|
||||
"category": "tables",
|
||||
"purpose": "Theme-aware, responsive table component."
|
||||
},
|
||||
{
|
||||
"name": "AdvancedRangeSlider",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive advanced range slider component."
|
||||
},
|
||||
{
|
||||
"name": "AdvancedDatePicker",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive advanced date picker component."
|
||||
},
|
||||
{
|
||||
"name": "Chart",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive chart component."
|
||||
},
|
||||
{
|
||||
"name": "Clipboard",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive clipboard component."
|
||||
},
|
||||
{
|
||||
"name": "Confetti",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive confetti component."
|
||||
"name": "CustomScrollbar",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive custom scrollbar component."
|
||||
},
|
||||
{
|
||||
"name": "DataMap",
|
||||
@@ -410,35 +173,201 @@
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive data table component."
|
||||
},
|
||||
{
|
||||
"name": "DatePicker",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive date picker component."
|
||||
},
|
||||
{
|
||||
"name": "DeviceFrame",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive device frame component."
|
||||
},
|
||||
{
|
||||
"name": "Divider",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive divider component."
|
||||
},
|
||||
{
|
||||
"name": "DragAndDrop",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive drag and drop component."
|
||||
},
|
||||
{
|
||||
"name": "Drawer",
|
||||
"category": "overlays",
|
||||
"purpose": "Theme-aware, responsive drawer component."
|
||||
},
|
||||
{
|
||||
"name": "Dropdown",
|
||||
"category": "overlays",
|
||||
"purpose": "Theme-aware, responsive dropdown component."
|
||||
},
|
||||
{
|
||||
"name": "FeatureCard",
|
||||
"category": "blocks",
|
||||
"purpose": "Reusable linked feature or service card with icon, badge, description, and action."
|
||||
},
|
||||
{
|
||||
"name": "FeatureGrid",
|
||||
"category": "layout",
|
||||
"purpose": "Responsive equal-height grid for feature and service cards."
|
||||
},
|
||||
{
|
||||
"name": "FeatureIconCard",
|
||||
"category": "blocks",
|
||||
"purpose": "Feature card with a prominent themed icon treatment."
|
||||
},
|
||||
{
|
||||
"name": "FileInput",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive file input component."
|
||||
},
|
||||
{
|
||||
"name": "FileUpload",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive file upload component."
|
||||
},
|
||||
{
|
||||
"name": "FileUploadProgress",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive file upload progress component."
|
||||
},
|
||||
{
|
||||
"name": "Footer",
|
||||
"category": "navigation",
|
||||
"purpose": "Responsive application footer with structured links and pre/post content slots."
|
||||
},
|
||||
{
|
||||
"name": "Grid",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive grid component."
|
||||
},
|
||||
{
|
||||
"name": "Hero",
|
||||
"category": "blocks",
|
||||
"purpose": "Reusable public-page hero with actions, trust items, badges, and visual slots."
|
||||
},
|
||||
{
|
||||
"name": "HeroActions",
|
||||
"category": "base",
|
||||
"purpose": "Responsive action group for hero and call-to-action sections."
|
||||
},
|
||||
{
|
||||
"name": "Image",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive image component."
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive input component."
|
||||
},
|
||||
{
|
||||
"name": "InputGroup",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive input group component."
|
||||
},
|
||||
{
|
||||
"name": "InputNumber",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive input number component."
|
||||
},
|
||||
{
|
||||
"name": "Kbd",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive kbd component."
|
||||
},
|
||||
{
|
||||
"name": "LayoutSplitter",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive layout splitter component."
|
||||
},
|
||||
{
|
||||
"name": "LegendIndicator",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive legend indicator component."
|
||||
},
|
||||
{
|
||||
"name": "Link",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive link component."
|
||||
},
|
||||
{
|
||||
"name": "List",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive list component."
|
||||
},
|
||||
{
|
||||
"name": "ListGroup",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive list group component."
|
||||
},
|
||||
{
|
||||
"name": "Map",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive map component."
|
||||
},
|
||||
{
|
||||
"name": "ToastNotifications",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive toast notifications component."
|
||||
"name": "MarketingSectionHeader",
|
||||
"category": "blocks",
|
||||
"purpose": "Marketing section heading with an optional linked action."
|
||||
},
|
||||
{
|
||||
"name": "WysiwygEditor",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive wysiwyg editor component."
|
||||
"name": "Marquee",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive marquee component."
|
||||
},
|
||||
{
|
||||
"name": "PreferenceSwitcher",
|
||||
"name": "MegaMenu",
|
||||
"category": "navigation",
|
||||
"purpose": "Accessible theme, accent color, and language preference controls."
|
||||
"purpose": "Theme-aware, responsive mega menu component."
|
||||
},
|
||||
{
|
||||
"name": "MetricCard",
|
||||
"category": "blocks",
|
||||
"purpose": "Single statistic or operational metric with icon, trend, and optional action."
|
||||
},
|
||||
{
|
||||
"name": "MetricGrid",
|
||||
"category": "layout",
|
||||
"purpose": "Responsive collection of metric cards."
|
||||
},
|
||||
{
|
||||
"name": "Modal",
|
||||
"category": "overlays",
|
||||
"purpose": "Theme-aware, responsive modal component."
|
||||
},
|
||||
{
|
||||
"name": "Nav",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive nav component."
|
||||
},
|
||||
{
|
||||
"name": "Navbar",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive navbar component."
|
||||
},
|
||||
{
|
||||
"name": "PageHeader",
|
||||
"category": "blocks",
|
||||
"purpose": "Public and application page header with breadcrumbs, metadata, and actions."
|
||||
},
|
||||
{
|
||||
"name": "Pagination",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive pagination component."
|
||||
},
|
||||
{
|
||||
"name": "PinInput",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Secure multi-cell PIN and verification-code input with regex and paste support.",
|
||||
"events": ["input", "change", "complete", "paste", "clear", "error"]
|
||||
},
|
||||
{
|
||||
"name": "Popover",
|
||||
"category": "overlays",
|
||||
"purpose": "Theme-aware, responsive popover component."
|
||||
},
|
||||
{
|
||||
"name": "PortalDashboard",
|
||||
@@ -446,14 +375,175 @@
|
||||
"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": "PreferenceSwitcher",
|
||||
"category": "navigation",
|
||||
"purpose": "Accessible theme, accent color, and language preference controls."
|
||||
},
|
||||
{
|
||||
"name": "AuthForm",
|
||||
"name": "Progress",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive progress component."
|
||||
},
|
||||
{
|
||||
"name": "PublicPageShell",
|
||||
"category": "layout",
|
||||
"purpose": "Consistent structural wrapper for public-facing pages."
|
||||
},
|
||||
{
|
||||
"name": "Radio",
|
||||
"category": "forms",
|
||||
"purpose": "Reusable authentication form for sign-in, registration, recovery, reset, and MFA."
|
||||
"purpose": "Theme-aware, responsive radio component."
|
||||
},
|
||||
{
|
||||
"name": "RangeSlider",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive range slider component."
|
||||
},
|
||||
{
|
||||
"name": "Rating",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive rating component."
|
||||
},
|
||||
{
|
||||
"name": "Scrollspy",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive scrollspy component."
|
||||
},
|
||||
{
|
||||
"name": "SearchBox",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive search box component."
|
||||
},
|
||||
{
|
||||
"name": "Section",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware page section with consistent spacing, width, background, and border controls."
|
||||
},
|
||||
{
|
||||
"name": "SectionHeader",
|
||||
"category": "layout",
|
||||
"purpose": "Reusable accessible section heading with eyebrow, title, description, icon, and actions."
|
||||
},
|
||||
{
|
||||
"name": "Select",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive select component."
|
||||
},
|
||||
{
|
||||
"name": "Sidebar",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive sidebar component."
|
||||
},
|
||||
{
|
||||
"name": "Skeleton",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive skeleton component."
|
||||
},
|
||||
{
|
||||
"name": "Spinner",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive spinner component."
|
||||
},
|
||||
{
|
||||
"name": "SplitHero",
|
||||
"category": "blocks",
|
||||
"purpose": "Two-column hero with configurable content and visual placement."
|
||||
},
|
||||
{
|
||||
"name": "StatsBar",
|
||||
"category": "blocks",
|
||||
"purpose": "Compact responsive statistics strip."
|
||||
},
|
||||
{
|
||||
"name": "Stepper",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive stepper component."
|
||||
},
|
||||
{
|
||||
"name": "StrongPassword",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive strong password component."
|
||||
},
|
||||
{
|
||||
"name": "StyledIcon",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive styled icon component."
|
||||
},
|
||||
{
|
||||
"name": "Switch",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive switch component."
|
||||
},
|
||||
{
|
||||
"name": "Table",
|
||||
"category": "tables",
|
||||
"purpose": "Theme-aware, responsive table component."
|
||||
},
|
||||
{
|
||||
"name": "Tabs",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive tabs component."
|
||||
},
|
||||
{
|
||||
"name": "Textarea",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive textarea component."
|
||||
},
|
||||
{
|
||||
"name": "TextLink",
|
||||
"category": "layout",
|
||||
"purpose": "Styled inline action link with icons, arrows, external state, and disabled handling."
|
||||
},
|
||||
{
|
||||
"name": "Timeline",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive timeline component."
|
||||
},
|
||||
{
|
||||
"name": "TimePicker",
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive time picker component."
|
||||
},
|
||||
{
|
||||
"name": "Toast",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive toast component."
|
||||
},
|
||||
{
|
||||
"name": "ToastNotifications",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive toast notifications component."
|
||||
},
|
||||
{
|
||||
"name": "ToggleCount",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive toggle count component."
|
||||
},
|
||||
{
|
||||
"name": "TogglePassword",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Accessible password field with optional show and hide controls.",
|
||||
"events": ["input", "change", "toggle"]
|
||||
},
|
||||
{
|
||||
"name": "Tooltip",
|
||||
"category": "overlays",
|
||||
"purpose": "Theme-aware, responsive tooltip component."
|
||||
},
|
||||
{
|
||||
"name": "TreeView",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive tree view component."
|
||||
},
|
||||
{
|
||||
"name": "Typography",
|
||||
"category": "layout",
|
||||
"purpose": "Theme-aware, responsive typography component."
|
||||
},
|
||||
{
|
||||
"name": "WysiwygEditor",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive wysiwyg editor component."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,131 @@
|
||||
component AnnouncementBar {
|
||||
props {
|
||||
badge = ""
|
||||
badgeIcon = ""
|
||||
message = "Announcement"
|
||||
description = ""
|
||||
icon = "icon-[lucide--megaphone]"
|
||||
actionLabel = ""
|
||||
actionHref = ""
|
||||
actionIcon = ""
|
||||
dismissible = false
|
||||
dismissLabel = "Dismiss announcement"
|
||||
sticky = false
|
||||
compact = false
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "soft"
|
||||
role = "status"
|
||||
live = "polite"
|
||||
class = ""
|
||||
}
|
||||
|
||||
state dismissed = false
|
||||
|
||||
view {
|
||||
<aside
|
||||
data-ui-component="AnnouncementBar"
|
||||
role='{role}'
|
||||
aria-live='{live}'
|
||||
data-show='!dismissed'
|
||||
class='z-40 w-full border-y border-[var(--wire-color-border)] {class}'
|
||||
class:sticky='sticky'
|
||||
class:top-0='sticky'
|
||||
class:bg-[var(--wire-color-primary-soft)]='variant === "soft" && color === "primary"'
|
||||
class:border-[var(--wire-color-primary-muted)]='variant === "soft" && color === "primary"'
|
||||
class:bg-[var(--wire-color-info-soft)]='variant === "soft" && color === "info"'
|
||||
class:border-[var(--wire-color-info-muted)]='variant === "soft" && color === "info"'
|
||||
class:bg-[var(--wire-color-success-soft)]='variant === "soft" && color === "success"'
|
||||
class:border-[var(--wire-color-success-muted)]='variant === "soft" && color === "success"'
|
||||
class:bg-[var(--wire-color-warning-soft)]='variant === "soft" && color === "warning"'
|
||||
class:border-[var(--wire-color-warning-muted)]='variant === "soft" && color === "warning"'
|
||||
class:bg-[var(--wire-color-danger-soft)]='variant === "soft" && color === "danger"'
|
||||
class:border-[var(--wire-color-danger-muted)]='variant === "soft" && color === "danger"'
|
||||
class:bg-[var(--wire-color-primary)]='variant === "solid" && color === "primary"'
|
||||
class:text-[var(--wire-color-on-primary)]='variant === "solid" && color === "primary"'
|
||||
class:bg-[var(--wire-color-danger)]='variant === "solid" && color === "danger"'
|
||||
class:text-[var(--wire-color-on-danger)]='variant === "solid" && color === "danger"'
|
||||
class:bg-[var(--wire-color-surface-raised)]='variant === "default"'
|
||||
>
|
||||
<Container columns="1" maxWidth="xl" size="default">
|
||||
<div
|
||||
class="flex flex-col gap-4 py-4 sm:flex-row sm:items-center sm:justify-between"
|
||||
class:py-2.5='compact'
|
||||
>
|
||||
<div class="flex min-w-0 items-start gap-3 sm:items-center">
|
||||
{#if icon}
|
||||
<span
|
||||
class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-primary)] shadow-sm"
|
||||
class:size-8='compact'
|
||||
class:text-[var(--wire-color-danger)]='color === "danger"'
|
||||
class:text-[var(--wire-color-warning-text)]='color === "warning"'
|
||||
class:text-[var(--wire-color-success)]='color === "success"'
|
||||
class:text-[var(--wire-color-info)]='color === "info"'
|
||||
>
|
||||
<span class='{icon + " size-5"}' aria-hidden="true"></span>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<div class="min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
{#if badge}
|
||||
<Badge
|
||||
label='{badge}'
|
||||
icon='{badgeIcon}'
|
||||
size="sm"
|
||||
color='{color}'
|
||||
variant='{variant === "solid" ? "outline" : "solid"}'
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<p
|
||||
class="font-semibold"
|
||||
class:text-sm='compact || size === "sm"'
|
||||
class:text-base='!compact && size !== "sm"'
|
||||
class:text-[var(--wire-color-text)]='variant !== "solid"'
|
||||
>
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if description && !compact}
|
||||
<p
|
||||
class="mt-1 text-sm leading-6"
|
||||
class:text-[var(--wire-color-text-muted)]='variant !== "solid"'
|
||||
class:opacity-85='variant === "solid"'
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 items-center gap-2 sm:justify-end">
|
||||
{#if actionLabel}
|
||||
<TextLink
|
||||
label='{actionLabel}'
|
||||
href='{actionHref}'
|
||||
icon='{actionIcon}'
|
||||
iconPosition="start"
|
||||
showArrow="true"
|
||||
color='{variant === "solid" ? "neutral" : color}'
|
||||
variant='{variant === "solid" ? "button" : "default"}'
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if dismissible}
|
||||
<button
|
||||
type="button"
|
||||
aria-label='{dismissLabel}'
|
||||
class="inline-flex size-9 items-center justify-center rounded-lg text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-raised)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
|
||||
@click='dismissed = true; event.currentTarget.dispatchEvent(new CustomEvent("dismiss", { bubbles: true }))'
|
||||
>
|
||||
<span class="icon-[lucide--x] size-4" aria-hidden="true"></span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</aside>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
component BackToTop {
|
||||
props {
|
||||
threshold = 500
|
||||
label = "Back to top"
|
||||
ariaLabel = "Scroll back to top"
|
||||
icon = "icon-[lucide--arrow-up]"
|
||||
position = "right"
|
||||
offset = "md"
|
||||
behavior = "smooth"
|
||||
showProgress = false
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "solid"
|
||||
class = ""
|
||||
}
|
||||
|
||||
state visible = false
|
||||
state progress = 0
|
||||
|
||||
view {
|
||||
<button
|
||||
data-ui-component="BackToTop"
|
||||
type="button"
|
||||
aria-label='{ariaLabel}'
|
||||
title='{label}'
|
||||
data-show='visible'
|
||||
class='fixed z-50 inline-flex items-center justify-center rounded-full border border-[var(--wire-color-border)] shadow-xl backdrop-blur transition duration-200 hover:-translate-y-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--wire-color-background)] {class}'
|
||||
class:bottom-4='offset === "sm"'
|
||||
class:bottom-6='offset === "md"'
|
||||
class:bottom-8='offset === "lg"'
|
||||
class:right-4='position === "right" && offset === "sm"'
|
||||
class:right-6='position === "right" && offset === "md"'
|
||||
class:right-8='position === "right" && offset === "lg"'
|
||||
class:left-4='position === "left" && offset === "sm"'
|
||||
class:left-6='position === "left" && offset === "md"'
|
||||
class:left-8='position === "left" && offset === "lg"'
|
||||
class:left-1/2='position === "center"'
|
||||
class:-translate-x-1/2='position === "center"'
|
||||
class:size-10='size === "sm"'
|
||||
class:size-12='size === "default" || size === "md"'
|
||||
class:size-14='size === "lg"'
|
||||
class:bg-[var(--wire-color-primary)]='variant === "solid" && color === "primary"'
|
||||
class:text-[var(--wire-color-on-primary)]='variant === "solid" && color === "primary"'
|
||||
class:bg-[var(--wire-color-danger)]='variant === "solid" && color === "danger"'
|
||||
class:text-[var(--wire-color-on-danger)]='variant === "solid" && color === "danger"'
|
||||
class:bg-[var(--wire-color-surface-raised)]='variant === "outline" || variant === "soft"'
|
||||
class:text-[var(--wire-color-primary)]='variant === "outline" || variant === "soft"'
|
||||
style='--wire-back-to-top-progress: {progress}%;'
|
||||
@window:scroll='visible = window.scrollY >= threshold; progress = Math.min(100, Math.round((window.scrollY / Math.max(1, document.documentElement.scrollHeight - window.innerHeight)) * 100))'
|
||||
@click='window.scrollTo({ top: 0, behavior: behavior })'
|
||||
>
|
||||
{#if showProgress}
|
||||
<span class="wire-back-to-top-progress absolute inset-0 rounded-full" aria-hidden="true"></span>
|
||||
{/if}
|
||||
<span class='{icon + " relative z-10 size-5"}' aria-hidden="true"></span>
|
||||
<span class="sr-only">{label}</span>
|
||||
</button>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-back-to-top-progress {
|
||||
background: conic-gradient(
|
||||
currentColor var(--wire-back-to-top-progress),
|
||||
transparent var(--wire-back-to-top-progress)
|
||||
);
|
||||
opacity: 0.22;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
component CTASection {
|
||||
props {
|
||||
eyebrow = ""
|
||||
title = "Ready to get started?"
|
||||
description = ""
|
||||
icon = ""
|
||||
align = "center"
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "solid"
|
||||
primaryLabel = "Get started"
|
||||
primaryHref = "#"
|
||||
primaryIcon = ""
|
||||
secondaryLabel = ""
|
||||
secondaryHref = ""
|
||||
secondaryIcon = ""
|
||||
backgroundImage = ""
|
||||
maxWidth = "xl"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<Section
|
||||
spacing='{size === "compact" ? "md" : "lg"}'
|
||||
variant="default"
|
||||
color='{color}'
|
||||
maxWidth='{maxWidth}'
|
||||
class='{class}'
|
||||
>
|
||||
<div
|
||||
data-ui-component="CTASection"
|
||||
class="relative isolate overflow-hidden rounded-3xl border border-[var(--wire-color-border)] p-8 shadow-xl sm:p-10 lg:p-14"
|
||||
class:bg-[var(--wire-color-primary)]='variant === "solid" && color === "primary"'
|
||||
class:text-[var(--wire-color-on-primary)]='variant === "solid" && color === "primary"'
|
||||
class:bg-[var(--wire-color-danger)]='variant === "solid" && color === "danger"'
|
||||
class:text-[var(--wire-color-on-danger)]='variant === "solid" && color === "danger"'
|
||||
class:bg-[var(--wire-color-primary-soft)]='variant === "soft" && color === "primary"'
|
||||
class:bg-[var(--wire-color-success-soft)]='variant === "soft" && color === "success"'
|
||||
class:bg-[var(--wire-color-warning-soft)]='variant === "soft" && color === "warning"'
|
||||
class:bg-[var(--wire-color-danger-soft)]='variant === "soft" && color === "danger"'
|
||||
class:bg-[var(--wire-color-surface-raised)]='variant === "default" || variant === "outline"'
|
||||
class:bg-gradient-to-br='variant === "gradient"'
|
||||
class:from-[var(--wire-color-primary)]='variant === "gradient"'
|
||||
class:to-[var(--wire-color-secondary)]='variant === "gradient"'
|
||||
class:text-[var(--wire-color-on-primary)]='variant === "gradient"'
|
||||
class:text-center='align === "center"'
|
||||
>
|
||||
{#if backgroundImage}
|
||||
<img
|
||||
src='{backgroundImage}'
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="absolute inset-0 -z-20 h-full w-full object-cover opacity-15"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="pointer-events-none absolute -right-20 -top-20 -z-10 size-72 rounded-full bg-white/15 blur-3xl"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="pointer-events-none absolute -bottom-24 -left-20 -z-10 size-72 rounded-full bg-black/10 blur-3xl"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="flex flex-col gap-8"
|
||||
class:items-center='align === "center"'
|
||||
class:lg:flex-row='align === "split"'
|
||||
class:lg:items-center='align === "split"'
|
||||
class:lg:justify-between='align === "split"'
|
||||
>
|
||||
<div class="max-w-3xl">
|
||||
{#if icon}
|
||||
<span
|
||||
class="mb-5 inline-flex size-12 items-center justify-center rounded-2xl bg-white/15"
|
||||
class:bg-[var(--wire-color-primary-soft)]='variant !== "solid" && variant !== "gradient"'
|
||||
class:text-[var(--wire-color-primary)]='variant !== "solid" && variant !== "gradient"'
|
||||
>
|
||||
<span class='{icon + " size-6"}' aria-hidden="true"></span>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if eyebrow}
|
||||
<p
|
||||
class="text-xs font-bold uppercase tracking-[0.18em]"
|
||||
class:text-[var(--wire-color-primary)]='variant !== "solid" && variant !== "gradient"'
|
||||
class:opacity-80='variant === "solid" || variant === "gradient"'
|
||||
>
|
||||
{eyebrow}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<h2
|
||||
class="mt-3 text-3xl font-bold leading-tight tracking-tight sm:text-4xl lg:text-5xl"
|
||||
class:text-[var(--wire-color-text)]='variant !== "solid" && variant !== "gradient"'
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
{#if description}
|
||||
<p
|
||||
class="mt-4 text-base leading-7 sm:text-lg"
|
||||
class:text-[var(--wire-color-text-muted)]='variant !== "solid" && variant !== "gradient"'
|
||||
class:opacity-85='variant === "solid" || variant === "gradient"'
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<HeroActions
|
||||
actions='{[
|
||||
{
|
||||
label: primaryLabel,
|
||||
href: primaryHref,
|
||||
icon: primaryIcon,
|
||||
variant: "default",
|
||||
color: color,
|
||||
controlClass: variant === "solid" || variant === "gradient" ? "!bg-white !text-[var(--wire-color-primary)] hover:!bg-white/90" : ""
|
||||
},
|
||||
{
|
||||
label: secondaryLabel,
|
||||
href: secondaryHref,
|
||||
icon: secondaryIcon,
|
||||
variant: "outline",
|
||||
color: color,
|
||||
controlClass: variant === "solid" || variant === "gradient" ? "!border-white/50 !text-white hover:!bg-white/10" : ""
|
||||
}
|
||||
]}'
|
||||
align='{align === "center" ? "center" : "right"}'
|
||||
stackOnMobile="true"
|
||||
fullWidthMobile="true"
|
||||
size="lg"
|
||||
color='{color}'
|
||||
class="shrink-0"
|
||||
>
|
||||
<slot name="actions"></slot>
|
||||
</HeroActions>
|
||||
|
||||
<slot name="visual"></slot>
|
||||
</div>
|
||||
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</Section>
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,29 @@ component Columns {
|
||||
maxWidth = "xl"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--columns wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div>
|
||||
<div
|
||||
data-ui-component="Columns"
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-columns='{columns}'
|
||||
data-gap='{gap}'
|
||||
class='grid w-full grid-cols-1 items-start {class}'
|
||||
class:max-w-3xl='maxWidth === "md"'
|
||||
class:max-w-5xl='maxWidth === "lg"'
|
||||
class:max-w-7xl='maxWidth === "xl"'
|
||||
class:max-w-screen-2xl='maxWidth === "2xl"'
|
||||
class:max-w-none='maxWidth === "full"'
|
||||
class:md:grid-cols-2='columns >= 2'
|
||||
class:lg:grid-cols-3='columns === 3'
|
||||
class:lg:grid-cols-4='columns === 4'
|
||||
class:gap-3='gap === "sm"'
|
||||
class:gap-6='gap === "md"'
|
||||
class:gap-10='gap === "lg"'
|
||||
class:gap-14='gap === "xl"'
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
component FeatureCard {
|
||||
props {
|
||||
icon = ""
|
||||
title = "Feature"
|
||||
description = ""
|
||||
href = ""
|
||||
actionLabel = "Learn more"
|
||||
actionIcon = ""
|
||||
badge = ""
|
||||
badgeColor = "primary"
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "default"
|
||||
hover = "lift"
|
||||
align = "left"
|
||||
disabled = false
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<article
|
||||
data-ui-component="FeatureCard"
|
||||
aria-disabled='{disabled}'
|
||||
class='group relative flex h-full min-w-0 flex-col overflow-hidden rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] shadow-sm transition duration-200 {class}'
|
||||
class:p-4='size === "sm"'
|
||||
class:p-6='size === "default" || size === "md"'
|
||||
class:p-8='size === "lg"'
|
||||
class:bg-[var(--wire-color-primary-soft)]='variant === "soft" && color === "primary"'
|
||||
class:bg-[var(--wire-color-success-soft)]='variant === "soft" && color === "success"'
|
||||
class:bg-[var(--wire-color-warning-soft)]='variant === "soft" && color === "warning"'
|
||||
class:bg-[var(--wire-color-danger-soft)]='variant === "soft" && color === "danger"'
|
||||
class:bg-transparent='variant === "ghost"'
|
||||
class:border-transparent='variant === "ghost"'
|
||||
class:shadow-none='variant === "ghost" || variant === "outline"'
|
||||
class:hover:-translate-y-1='hover === "lift" && !disabled'
|
||||
class:hover:shadow-xl='(hover === "lift" || hover === "shadow") && !disabled'
|
||||
class:hover:border-[var(--wire-color-primary)]='(hover === "lift" || hover === "border") && !disabled'
|
||||
class:text-center='align === "center"'
|
||||
class:items-center='align === "center"'
|
||||
class:text-right='align === "right"'
|
||||
class:items-end='align === "right"'
|
||||
class:opacity-60='disabled'
|
||||
class:pointer-events-none='disabled'
|
||||
>
|
||||
<div class="flex w-full items-start justify-between gap-4">
|
||||
<div
|
||||
class="flex size-12 shrink-0 items-center justify-center rounded-2xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]"
|
||||
class:bg-[var(--wire-color-success-soft)]='color === "success"'
|
||||
class:text-[var(--wire-color-success)]='color === "success"'
|
||||
class:bg-[var(--wire-color-warning-soft)]='color === "warning"'
|
||||
class:text-[var(--wire-color-warning-text)]='color === "warning"'
|
||||
class:bg-[var(--wire-color-danger-soft)]='color === "danger"'
|
||||
class:text-[var(--wire-color-danger)]='color === "danger"'
|
||||
class:bg-[var(--wire-color-info-soft)]='color === "info"'
|
||||
class:text-[var(--wire-color-info)]='color === "info"'
|
||||
class:size-10='size === "sm"'
|
||||
class:size-14='size === "lg"'
|
||||
>
|
||||
<slot name="icon"></slot>
|
||||
{#if icon}
|
||||
<span class='{icon + " size-6"}' aria-hidden="true"></span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if badge}
|
||||
<Badge
|
||||
label='{badge}'
|
||||
size="sm"
|
||||
color='{badgeColor}'
|
||||
variant="soft"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<h3 class="mt-5 text-lg font-bold leading-tight text-[var(--wire-color-text)]" class:text-xl='size === "lg"'>
|
||||
{title}
|
||||
</h3>
|
||||
|
||||
{#if description}
|
||||
<p class="mt-3 flex-1 text-sm leading-6 text-[var(--wire-color-text-muted)]" class:text-base='size === "lg"'>
|
||||
{description}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<div class="mt-6 flex w-full items-center justify-between gap-3">
|
||||
<slot name="footer"></slot>
|
||||
|
||||
{#if href && actionLabel}
|
||||
<TextLink
|
||||
label='{actionLabel}'
|
||||
href='{href}'
|
||||
icon='{actionIcon}'
|
||||
iconPosition="start"
|
||||
showArrow="true"
|
||||
size="sm"
|
||||
color='{color}'
|
||||
class="ml-auto"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
href='{href}'
|
||||
aria-label='{title}'
|
||||
class="absolute inset-0 z-10 rounded-2xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]"
|
||||
></a>
|
||||
{/if}
|
||||
</article>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
component FeatureGrid {
|
||||
props {
|
||||
color = "primary"
|
||||
size = "default"
|
||||
columns = 3
|
||||
tabletColumns = 2
|
||||
mobileColumns = 1
|
||||
gap = "md"
|
||||
minItemWidth = ""
|
||||
equalHeight = true
|
||||
align = "stretch"
|
||||
maxWidth = "full"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
data-ui-component="FeatureGrid"
|
||||
data-columns='{columns}'
|
||||
data-tablet-columns='{tabletColumns}'
|
||||
data-mobile-columns='{mobileColumns}'
|
||||
class='grid w-full {class}'
|
||||
class:wire-feature-grid-autofit='minItemWidth !== ""'
|
||||
class:grid-cols-1='mobileColumns === 1'
|
||||
class:grid-cols-2='mobileColumns === 2'
|
||||
class:sm:grid-cols-1='tabletColumns === 1'
|
||||
class:sm:grid-cols-2='tabletColumns === 2'
|
||||
class:sm:grid-cols-3='tabletColumns === 3'
|
||||
class:lg:grid-cols-2='columns === 2'
|
||||
class:lg:grid-cols-3='columns === 3'
|
||||
class:lg:grid-cols-4='columns === 4'
|
||||
class:lg:grid-cols-5='columns === 5'
|
||||
class:lg:grid-cols-6='columns === 6'
|
||||
class:gap-3='gap === "sm"'
|
||||
class:gap-5='gap === "md"'
|
||||
class:gap-8='gap === "lg"'
|
||||
class:gap-10='gap === "xl"'
|
||||
class:items-stretch='equalHeight || align === "stretch"'
|
||||
class:items-start='align === "start"'
|
||||
class:items-center='align === "center"'
|
||||
class:max-w-5xl='maxWidth === "lg"'
|
||||
class:max-w-7xl='maxWidth === "xl"'
|
||||
class:max-w-none='maxWidth === "full"'
|
||||
style='--wire-feature-grid-min: {minItemWidth || "16rem"};'
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-feature-grid-autofit {
|
||||
grid-template-columns: repeat(auto-fit, minmax(var(--wire-feature-grid-min), 1fr));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
component FeatureIconCard {
|
||||
props {
|
||||
icon = "icon-[lucide--sparkles]"
|
||||
iconSize = "md"
|
||||
iconVariant = "soft"
|
||||
title = "Feature"
|
||||
description = ""
|
||||
href = ""
|
||||
actionLabel = "Explore"
|
||||
badge = ""
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "default"
|
||||
align = "left"
|
||||
hover = "lift"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<article
|
||||
data-ui-component="FeatureIconCard"
|
||||
class='group relative flex h-full flex-col rounded-3xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] p-6 shadow-sm transition duration-200 {class}'
|
||||
class:p-5='size === "sm"'
|
||||
class:p-8='size === "lg"'
|
||||
class:bg-[var(--wire-color-surface-soft)]='variant === "soft"'
|
||||
class:bg-transparent='variant === "ghost"'
|
||||
class:border-transparent='variant === "ghost"'
|
||||
class:shadow-none='variant === "ghost"'
|
||||
class:hover:-translate-y-1='hover === "lift"'
|
||||
class:hover:shadow-xl='hover === "lift" || hover === "shadow"'
|
||||
class:hover:border-[var(--wire-color-primary)]='hover === "lift" || hover === "border"'
|
||||
class:items-center='align === "center"'
|
||||
class:text-center='align === "center"'
|
||||
>
|
||||
<div
|
||||
class="relative flex size-14 items-center justify-center rounded-2xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)] ring-1 ring-inset ring-[var(--wire-color-primary-muted)]"
|
||||
class:size-12='iconSize === "sm"'
|
||||
class:size-16='iconSize === "lg"'
|
||||
class:bg-[var(--wire-color-primary)]='iconVariant === "solid" && color === "primary"'
|
||||
class:text-[var(--wire-color-on-primary)]='iconVariant === "solid" && color === "primary"'
|
||||
class:bg-[var(--wire-color-success-soft)]='color === "success" && iconVariant !== "solid"'
|
||||
class:text-[var(--wire-color-success)]='color === "success" && iconVariant !== "solid"'
|
||||
class:bg-[var(--wire-color-warning-soft)]='color === "warning" && iconVariant !== "solid"'
|
||||
class:text-[var(--wire-color-warning-text)]='color === "warning" && iconVariant !== "solid"'
|
||||
class:bg-[var(--wire-color-danger-soft)]='color === "danger" && iconVariant !== "solid"'
|
||||
class:text-[var(--wire-color-danger)]='color === "danger" && iconVariant !== "solid"'
|
||||
>
|
||||
<span class='{icon + " size-7"}' aria-hidden="true"></span>
|
||||
|
||||
{#if badge}
|
||||
<span class="absolute -right-2 -top-2 rounded-full bg-[var(--wire-color-surface-raised)] px-2 py-1 text-[10px] font-bold uppercase tracking-wide text-[var(--wire-color-primary)] shadow-sm ring-1 ring-[var(--wire-color-border)]">
|
||||
{badge}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<h3 class="mt-6 text-xl font-bold tracking-tight text-[var(--wire-color-text)]">{title}</h3>
|
||||
|
||||
{#if description}
|
||||
<p class="mt-3 flex-1 text-sm leading-6 text-[var(--wire-color-text-muted)]">{description}</p>
|
||||
{/if}
|
||||
|
||||
<slot></slot>
|
||||
|
||||
{#if href}
|
||||
<TextLink
|
||||
label='{actionLabel}'
|
||||
href='{href}'
|
||||
color='{color}'
|
||||
size="sm"
|
||||
class="mt-6"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<slot name="footer"></slot>
|
||||
</article>
|
||||
}
|
||||
}
|
||||
@@ -12,90 +12,133 @@ component Footer {
|
||||
class = ""
|
||||
}
|
||||
|
||||
functions {
|
||||
function groupedItems() {
|
||||
const groups = []
|
||||
let group = null
|
||||
items.forEach((item) => {
|
||||
if (item.type === "header") {
|
||||
group = {
|
||||
heading: item,
|
||||
links: []
|
||||
}
|
||||
groups.push(group)
|
||||
return
|
||||
}
|
||||
if (!group) {
|
||||
group = {
|
||||
heading: null,
|
||||
links: []
|
||||
}
|
||||
groups.push(group)
|
||||
}
|
||||
group.links.push(item)
|
||||
})
|
||||
return groups
|
||||
}
|
||||
|
||||
function selectItem(item) {
|
||||
$emit(item.type === "action" ? "action" : "select", {
|
||||
item: item,
|
||||
value: item.value || ""
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<footer class="wire-footer wire-footer--width-{maxWidth} wire-next--color-{color} wire-next--size-{size} wire-footer--columns-{columns} {class}">
|
||||
<div class="wire-footer__grid">
|
||||
<div class="wire-footer__pre">
|
||||
<slot name="pre-footer" />
|
||||
</div>
|
||||
<footer
|
||||
{...attrs}
|
||||
data-ui-component="Footer"
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
class='wire-footer border-t border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] {class}'
|
||||
>
|
||||
<slot name="pre-footer"></slot>
|
||||
|
||||
<nav class="wire-footer__links" aria-label="{label}">
|
||||
{#each groupedItems() as group}
|
||||
<section class="wire-footer__column">
|
||||
{#if group.heading}
|
||||
<strong class="wire-footer__heading">{group.heading.label}</strong>
|
||||
<div
|
||||
class="wire-footer__inner mx-auto w-full px-4 py-12 sm:px-6 lg:px-8"
|
||||
class:max-w-5xl='maxWidth === "compact"'
|
||||
class:max-w-7xl='maxWidth === "wide" || maxWidth === "xl"'
|
||||
class:max-w-screen-2xl='maxWidth === "full"'
|
||||
class:py-8='size === "compact"'
|
||||
class:py-16='size === "spacious"'
|
||||
>
|
||||
<nav aria-label='{label}'>
|
||||
<div class='wire-footer__columns wire-footer--columns-{columns}'>
|
||||
{#each items as item, itemIndex}
|
||||
{#if item.type === "header"}
|
||||
<section class="wire-footer__column" aria-labelledby='footer-heading-{itemIndex}'>
|
||||
<h2 id='footer-heading-{itemIndex}' class="wire-footer__heading">
|
||||
{item.label || item.title}
|
||||
</h2>
|
||||
|
||||
{#if item.description}
|
||||
<p class="wire-footer__description">{item.description}</p>
|
||||
{/if}
|
||||
|
||||
{#if (item.items || item.links || []).length > 0}
|
||||
<ul class="wire-footer__column-links">
|
||||
{#each item.items || item.links || [] as child, childIndex}
|
||||
<li>
|
||||
<a
|
||||
href='{child.href || "#"}'
|
||||
target='{child.target || ""}'
|
||||
rel='{child.external ? "noopener noreferrer" : (child.rel || "")}'
|
||||
class="wire-footer__link"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex } })); child.action && event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex } }))'
|
||||
>
|
||||
{#if child.icon}
|
||||
<span class='{child.icon}' aria-hidden="true"></span>
|
||||
{/if}
|
||||
<span>{child.label || child.title}</span>
|
||||
{#if child.external}
|
||||
<span class="icon-[lucide--external-link]" aria-hidden="true"></span>
|
||||
{/if}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</section>
|
||||
{:else if item.type === "link" || item.href}
|
||||
<section class="wire-footer__column">
|
||||
<ul class="wire-footer__column-links">
|
||||
<li>
|
||||
<a
|
||||
href='{item.href || "#"}'
|
||||
target='{item.target || ""}'
|
||||
rel='{item.external ? "noopener noreferrer" : (item.rel || "")}'
|
||||
class="wire-footer__link"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, itemIndex: itemIndex } })); item.action && event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { item: item, itemIndex: itemIndex } }))'
|
||||
>
|
||||
{#if item.icon}
|
||||
<span class='{item.icon}' aria-hidden="true"></span>
|
||||
{/if}
|
||||
<span>{item.label || item.title}</span>
|
||||
{#if item.external}
|
||||
<span class="icon-[lucide--external-link]" aria-hidden="true"></span>
|
||||
{/if}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
{:else}
|
||||
<section class="wire-footer__column">
|
||||
{#if item.title || item.label}
|
||||
<h2 class="wire-footer__heading">{item.title || item.label}</h2>
|
||||
{/if}
|
||||
{#if item.description}
|
||||
<p class="wire-footer__description">{item.description}</p>
|
||||
{/if}
|
||||
<ul class="wire-footer__column-links">
|
||||
{#each item.items || item.links || [] as child, childIndex}
|
||||
<li>
|
||||
<a
|
||||
href='{child.href || "#"}'
|
||||
target='{child.target || ""}'
|
||||
rel='{child.external ? "noopener noreferrer" : (child.rel || "")}'
|
||||
class="wire-footer__link"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex } })); child.action && event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex } }))'
|
||||
>
|
||||
{#if child.icon}
|
||||
<span class='{child.icon}' aria-hidden="true"></span>
|
||||
{/if}
|
||||
<span>{child.label || child.title}</span>
|
||||
{#if child.external}
|
||||
<span class="icon-[lucide--external-link]" aria-hidden="true"></span>
|
||||
{/if}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
{/if}
|
||||
<div class="wire-footer__column-links">
|
||||
{#each group.links as item}
|
||||
<a class="wire-footer__link wire-footer__link--{item.type || 'link'}" href="{item.href || '#'}" target="{item.target || ''}" rel="{item.rel || ''}" @click="selectItem(item)">
|
||||
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
|
||||
<span>{item.label}</span>
|
||||
{#if item.external}<span aria-hidden="true">↗</span>{/if}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="wire-footer__post">
|
||||
<slot name="post-footer" />
|
||||
<div class="wire-footer__copyright">
|
||||
<div class="wire-footer__copyright-left">
|
||||
<slot name="copyright-left"></slot>
|
||||
{#if copyright}
|
||||
<p>{copyright}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="wire-footer__copyright-right">
|
||||
<slot name="copyright-right"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if copyright}
|
||||
<div class="wire-footer__bottom">
|
||||
<div class="wire-footer__copyright-left">
|
||||
<slot name="copyright-left" />
|
||||
</div>
|
||||
<div class="wire-footer__copyright">{copyright}</div>
|
||||
<div class="wire-footer__copyright-right">
|
||||
<slot name="copyright-right" />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="wire-footer__bottom wire-footer__bottom--slots">
|
||||
<div class="wire-footer__copyright-left">
|
||||
<slot name="copyright-left" />
|
||||
</div>
|
||||
<div class="wire-footer__copyright-right">
|
||||
<slot name="copyright-right" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<slot name="post-footer"></slot>
|
||||
</footer>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
component Hero {
|
||||
props {
|
||||
eyebrow = ""
|
||||
title = "Build something remarkable"
|
||||
highlight = ""
|
||||
description = ""
|
||||
align = "left"
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "default"
|
||||
primaryLabel = ""
|
||||
primaryHref = ""
|
||||
primaryIcon = ""
|
||||
secondaryLabel = ""
|
||||
secondaryHref = ""
|
||||
secondaryIcon = ""
|
||||
tertiaryLabel = ""
|
||||
tertiaryHref = ""
|
||||
badges = []
|
||||
trustItems = []
|
||||
maxWidth = "xl"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<Section
|
||||
spacing='{size === "compact" ? "md" : (size === "large" ? "xl" : "lg")}'
|
||||
maxWidth='{maxWidth}'
|
||||
variant='{variant === "solid" ? "solid" : (variant === "soft" ? "soft" : "default")}'
|
||||
color='{color}'
|
||||
class='overflow-hidden {class}'
|
||||
>
|
||||
<div
|
||||
data-ui-component="Hero"
|
||||
class="relative isolate"
|
||||
class:text-center='align === "center"'
|
||||
>
|
||||
{#if variant === "gradient"}
|
||||
<div
|
||||
class="pointer-events-none absolute inset-0 -z-10 rounded-[2rem] opacity-80"
|
||||
style="background: radial-gradient(circle at 15% 20%, var(--wire-color-primary-muted), transparent 35%), radial-gradient(circle at 85% 75%, var(--wire-color-secondary-muted), transparent 36%);"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="max-w-4xl"
|
||||
class:mx-auto='align === "center"'
|
||||
class:ml-auto='align === "right"'
|
||||
>
|
||||
<slot name="eyebrow"></slot>
|
||||
|
||||
{#if eyebrow}
|
||||
<div
|
||||
class="mb-6 inline-flex items-center gap-2 rounded-full border border-[var(--wire-color-primary-muted)] bg-[var(--wire-color-primary-soft)] px-4 py-2 text-sm font-semibold text-[var(--wire-color-primary)]"
|
||||
>
|
||||
<span class="size-2 rounded-full bg-[var(--wire-color-primary)]" aria-hidden="true"></span>
|
||||
<span>{eyebrow}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<h1
|
||||
class="font-bold leading-[1.06] tracking-[-0.04em]"
|
||||
class:text-4xl='size === "compact"'
|
||||
class:sm:text-5xl='size === "compact"'
|
||||
class:text-5xl='size === "default"'
|
||||
class:sm:text-6xl='size === "default"'
|
||||
class:lg:text-7xl='size === "default" || size === "large"'
|
||||
class:text-[var(--wire-color-text)]='variant !== "solid"'
|
||||
>
|
||||
<span>{title}</span>
|
||||
{#if highlight}
|
||||
<span class="block bg-gradient-to-r from-[var(--wire-color-primary)] to-[var(--wire-color-secondary)] bg-clip-text text-transparent">
|
||||
{highlight}
|
||||
</span>
|
||||
{/if}
|
||||
</h1>
|
||||
|
||||
{#if description}
|
||||
<p
|
||||
class="mt-6 max-w-3xl text-base leading-8 text-[var(--wire-color-text-muted)] sm:text-lg"
|
||||
class:mx-auto='align === "center"'
|
||||
class:ml-auto='align === "right"'
|
||||
class:text-[var(--wire-color-on-primary)]='variant === "solid"'
|
||||
class:opacity-85='variant === "solid"'
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#if badges.length > 0}
|
||||
<div class="mt-6 flex flex-wrap gap-2" class:justify-center='align === "center"' class:justify-end='align === "right"'>
|
||||
{#each badges as badge}
|
||||
<Badge
|
||||
label='{badge.label || badge.title || badge}'
|
||||
icon='{badge.icon || ""}'
|
||||
color='{badge.color || color}'
|
||||
variant='{badge.variant || "soft"}'
|
||||
size='{badge.size || "sm"}'
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-8">
|
||||
<HeroActions
|
||||
actions='{[
|
||||
{ label: primaryLabel, href: primaryHref, icon: primaryIcon, variant: "default", color: color },
|
||||
{ label: secondaryLabel, href: secondaryHref, icon: secondaryIcon, variant: "outline", color: color },
|
||||
{ label: tertiaryLabel, href: tertiaryHref, variant: "ghost", color: color }
|
||||
]}'
|
||||
align='{align}'
|
||||
stackOnMobile="true"
|
||||
fullWidthMobile="true"
|
||||
size='{size === "compact" ? "default" : "lg"}'
|
||||
color='{color}'
|
||||
>
|
||||
<slot name="actions"></slot>
|
||||
</HeroActions>
|
||||
</div>
|
||||
|
||||
{#if trustItems.length > 0}
|
||||
<div
|
||||
class="mt-8 flex flex-wrap items-center gap-x-6 gap-y-3 text-sm text-[var(--wire-color-text-muted)]"
|
||||
class:justify-center='align === "center"'
|
||||
class:justify-end='align === "right"'
|
||||
>
|
||||
{#each trustItems as item}
|
||||
<span class="inline-flex items-center gap-2">
|
||||
<span class='{(item.icon || "icon-[lucide--check-circle-2]") + " size-4 text-[var(--wire-color-primary)]"}' aria-hidden="true"></span>
|
||||
<span>{item.label || item.title || item}</span>
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<slot name="trust"></slot>
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<div class="mt-10">
|
||||
<slot name="visual"></slot>
|
||||
</div>
|
||||
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</Section>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
component HeroActions {
|
||||
props {
|
||||
actions = []
|
||||
align = "left"
|
||||
orientation = "horizontal"
|
||||
stackOnMobile = true
|
||||
fullWidthMobile = true
|
||||
size = "default"
|
||||
color = "primary"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
data-ui-component="HeroActions"
|
||||
role="group"
|
||||
aria-label="Page actions"
|
||||
class='flex flex-wrap items-center gap-3 {class}'
|
||||
class:flex-col='orientation === "vertical" || stackOnMobile'
|
||||
class:sm:flex-row='orientation === "horizontal" && stackOnMobile'
|
||||
class:justify-center='align === "center"'
|
||||
class:justify-end='align === "right"'
|
||||
class:w-full='fullWidthMobile'
|
||||
class:sm:w-auto='fullWidthMobile'
|
||||
>
|
||||
{#each actions as action, index}
|
||||
{#if action.label}
|
||||
<Button
|
||||
label='{action.label}'
|
||||
href='{action.href || ""}'
|
||||
target='{action.target || ""}'
|
||||
rel='{action.rel || ""}'
|
||||
type='{action.type || "button"}'
|
||||
variant='{action.variant || (index === 0 ? "default" : "outline")}'
|
||||
color='{action.color || color}'
|
||||
size='{action.size || size}'
|
||||
disabled='{action.disabled || false}'
|
||||
loading='{action.loading || false}'
|
||||
icon='{action.icon || ""}'
|
||||
iconPosition='{action.iconPosition || "start"}'
|
||||
ariaLabel='{action.ariaLabel || action.label}'
|
||||
fullWidth='{fullWidthMobile}'
|
||||
controlClass='{action.controlClass || ""}'
|
||||
class='{"sm:w-auto " + (action.class || "")}'
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<slot></slot>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
component Image {
|
||||
props {
|
||||
@event load = function
|
||||
@event error = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
src = ""
|
||||
@@ -12,10 +10,38 @@ component Image {
|
||||
rounded = false
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<figure class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--image {rounded ? 'wire-next--rounded' : ''} {class}">
|
||||
<img src="{src}" alt="{alt}" width="{width}" height="{height}" loading="{loading}" />
|
||||
<slot />
|
||||
<figure
|
||||
data-ui-component="Image"
|
||||
class='relative m-0 overflow-hidden bg-[var(--wire-color-surface-soft)] {class}'
|
||||
class:rounded-2xl='rounded'
|
||||
>
|
||||
{#if src}
|
||||
<img
|
||||
src='{src}'
|
||||
alt='{alt}'
|
||||
width='{width}'
|
||||
height='{height}'
|
||||
loading='{loading}'
|
||||
decoding="async"
|
||||
class="block h-auto w-full object-cover transition duration-300"
|
||||
class:aspect-square='size === "square"'
|
||||
class:aspect-video='size === "video"'
|
||||
class:aspect-[4/3]='size === "landscape"'
|
||||
class:aspect-[3/4]='size === "portrait"'
|
||||
/>
|
||||
{:else}
|
||||
<div
|
||||
class="flex min-h-48 w-full items-center justify-center text-[var(--wire-color-text-muted)]"
|
||||
role="img"
|
||||
aria-label='{alt || "Image placeholder"}'
|
||||
>
|
||||
<span class="icon-[lucide--image] size-8" aria-hidden="true"></span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<slot></slot>
|
||||
</figure>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
component Link {
|
||||
props {
|
||||
@event click = function
|
||||
@event focus = function
|
||||
@event blur = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Link"
|
||||
@@ -12,7 +9,39 @@ component Link {
|
||||
external = false
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<a {...attrs} href="{href}" target="{target}" rel="{rel}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--link {class}"><slot>{label}</slot>{#if external}<span aria-hidden="true">↗</span>{/if}</a>
|
||||
<a
|
||||
data-ui-component="Link"
|
||||
href='{href}'
|
||||
target='{target}'
|
||||
rel='{external ? (rel || "noopener noreferrer") : rel}'
|
||||
class='inline-flex min-w-0 items-center gap-1.5 rounded-md font-semibold underline-offset-4 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--wire-color-background)] {class}'
|
||||
class:text-xs='size === "xs"'
|
||||
class:text-sm='size === "sm" || size === "default"'
|
||||
class:text-base='size === "md"'
|
||||
class:text-lg='size === "lg"'
|
||||
class:text-[var(--wire-color-primary)]='color === "primary"'
|
||||
class:hover:text-[var(--wire-color-primary-hover)]='color === "primary"'
|
||||
class:text-[var(--wire-color-secondary)]='color === "secondary"'
|
||||
class:hover:text-[var(--wire-color-secondary-hover)]='color === "secondary"'
|
||||
class:text-[var(--wire-color-success)]='color === "success"'
|
||||
class:text-[var(--wire-color-warning-text)]='color === "warning"'
|
||||
class:text-[var(--wire-color-danger)]='color === "danger"'
|
||||
class:text-[var(--wire-color-info)]='color === "info"'
|
||||
class:text-[var(--wire-color-text)]='color === "neutral"'
|
||||
class:hover:underline='external === false'
|
||||
>
|
||||
<span class="truncate">{label}</span>
|
||||
|
||||
{#if external}
|
||||
<span
|
||||
class="icon-[lucide--external-link] size-3.5 shrink-0"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
{/if}
|
||||
|
||||
<slot></slot>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
component List {
|
||||
props {
|
||||
@event select = function
|
||||
@event click = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "List"
|
||||
@@ -10,12 +8,100 @@ component List {
|
||||
variant = "default"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--list wire-next--variant-{variant} {class}">
|
||||
{#if title}<strong>{title}</strong>{/if}
|
||||
{#if description}<p>{description}</p>{/if}
|
||||
{#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
|
||||
<slot />
|
||||
<section
|
||||
data-ui-component="List"
|
||||
aria-label='{title}'
|
||||
class='w-full {class}'
|
||||
>
|
||||
{#if title || description}
|
||||
<header class="mb-4">
|
||||
{#if title}
|
||||
<h3 class="text-lg font-bold text-[var(--wire-color-text)]">{title}</h3>
|
||||
{/if}
|
||||
{#if description}
|
||||
<p class="mt-1 text-sm leading-6 text-[var(--wire-color-text-muted)]">{description}</p>
|
||||
{/if}
|
||||
</header>
|
||||
{/if}
|
||||
|
||||
<ul
|
||||
class="overflow-hidden"
|
||||
class:divide-y='variant === "default" || variant === "divided"'
|
||||
class:divide-[var(--wire-color-border)]='variant === "default" || variant === "divided"'
|
||||
class:rounded-2xl='variant === "card"'
|
||||
class:border='variant === "card"'
|
||||
class:border-[var(--wire-color-border)]='variant === "card"'
|
||||
class:bg-[var(--wire-color-surface-raised)]='variant === "card"'
|
||||
class:space-y-3='variant === "separated"'
|
||||
>
|
||||
{#each items as item, index}
|
||||
<li
|
||||
class="group min-w-0"
|
||||
class:rounded-xl='variant === "separated"'
|
||||
class:border='variant === "separated"'
|
||||
class:border-[var(--wire-color-border)]='variant === "separated"'
|
||||
class:bg-[var(--wire-color-surface-raised)]='variant === "separated"'
|
||||
>
|
||||
{#if item.href}
|
||||
<a
|
||||
href='{item.href}'
|
||||
class="flex min-w-0 items-start gap-3 px-4 py-3 outline-none transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]"
|
||||
class:px-3='size === "sm"'
|
||||
class:py-2.5='size === "sm"'
|
||||
class:px-5='size === "lg"'
|
||||
class:py-4='size === "lg"'
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, index: index } }))'
|
||||
>
|
||||
{#if item.icon}
|
||||
<span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]">
|
||||
<span class='{item.icon + " size-5"}' aria-hidden="true"></span>
|
||||
</span>
|
||||
{/if}
|
||||
{#if item.imageSrc}
|
||||
<img src='{item.imageSrc}' alt='{item.imageAlt || ""}' class="size-12 shrink-0 rounded-xl object-cover" loading="lazy" />
|
||||
{/if}
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<p class="truncate font-semibold text-[var(--wire-color-text)]">{item.title || item.label}</p>
|
||||
{#if item.meta}
|
||||
<span class="shrink-0 text-xs text-[var(--wire-color-text-muted)]">{item.meta}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if item.description}
|
||||
<p class="mt-1 line-clamp-2 text-sm leading-5 text-[var(--wire-color-text-muted)]">{item.description}</p>
|
||||
{/if}
|
||||
{#if item.badge}
|
||||
<span class="mt-2 inline-flex rounded-full bg-[var(--wire-color-primary-soft)] px-2.5 py-1 text-xs font-semibold text-[var(--wire-color-primary)]">{item.badge}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="icon-[lucide--chevron-right] mt-1 size-4 shrink-0 text-[var(--wire-color-text-muted)] transition group-hover:translate-x-0.5 group-hover:text-[var(--wire-color-primary)]" aria-hidden="true"></span>
|
||||
</a>
|
||||
{:else}
|
||||
<div class="flex min-w-0 items-start gap-3 px-4 py-3">
|
||||
{#if item.icon}
|
||||
<span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]">
|
||||
<span class='{item.icon + " size-5"}' aria-hidden="true"></span>
|
||||
</span>
|
||||
{/if}
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="font-semibold text-[var(--wire-color-text)]">{item.title || item.label}</p>
|
||||
{#if item.description}
|
||||
<p class="mt-1 text-sm leading-5 text-[var(--wire-color-text-muted)]">{item.description}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</li>
|
||||
{:empty}
|
||||
<li class="rounded-xl border border-dashed border-[var(--wire-color-border)] p-6 text-center text-sm text-[var(--wire-color-text-muted)]">
|
||||
No items available.
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<slot></slot>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
component Map {
|
||||
props {
|
||||
@event ready = function
|
||||
@event move = function
|
||||
@event zoom = function
|
||||
@event markerClick = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Map"
|
||||
@@ -12,12 +8,78 @@ component Map {
|
||||
variant = "default"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--map wire-next--variant-{variant} {class}">
|
||||
{#if title}<strong>{title}</strong>{/if}
|
||||
{#if description}<p>{description}</p>{/if}
|
||||
{#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
|
||||
<slot />
|
||||
<section
|
||||
data-ui-component="Map"
|
||||
aria-label='{title}'
|
||||
class='overflow-hidden rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] shadow-sm {class}'
|
||||
>
|
||||
{#if title || description}
|
||||
<header class="flex items-start justify-between gap-4 border-b border-[var(--wire-color-border)] p-5">
|
||||
<div>
|
||||
{#if title}
|
||||
<h3 class="text-lg font-bold text-[var(--wire-color-text)]">{title}</h3>
|
||||
{/if}
|
||||
{#if description}
|
||||
<p class="mt-1 text-sm leading-6 text-[var(--wire-color-text-muted)]">{description}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]">
|
||||
<span class="icon-[lucide--map] size-5" aria-hidden="true"></span>
|
||||
</span>
|
||||
</header>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="relative isolate overflow-hidden bg-[var(--wire-color-surface-soft)]"
|
||||
class:h-64='size === "sm"'
|
||||
class:h-80='size === "default" || size === "md"'
|
||||
class:h-[28rem]='size === "lg"'
|
||||
>
|
||||
<div
|
||||
class="pointer-events-none absolute inset-0 opacity-50"
|
||||
style="background-image: linear-gradient(var(--wire-color-border) 1px, transparent 1px), linear-gradient(90deg, var(--wire-color-border) 1px, transparent 1px); background-size: 32px 32px;"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
|
||||
<div class="absolute inset-0 flex items-center justify-center p-6">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
{#if items.length > 0}
|
||||
{#each items as item, index}
|
||||
<button
|
||||
type="button"
|
||||
aria-label='{item.label || item.title || "Map marker"}'
|
||||
class="absolute inline-flex size-10 items-center justify-center rounded-full border-4 border-[var(--wire-color-surface-raised)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-lg transition hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
|
||||
style='left: {item.x || (20 + index * 12)}%; top: {item.y || (30 + (index % 3) * 18)}%;'
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("markerClick", { bubbles: true, detail: item })); event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: item }))'
|
||||
>
|
||||
<span class='{item.icon || "icon-[lucide--map-pin]"}' aria-hidden="true"></span>
|
||||
</button>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<div class="absolute bottom-4 right-4 flex flex-col gap-2">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Zoom in"
|
||||
class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("zoom", { bubbles: true, detail: { direction: "in" } }))'
|
||||
>
|
||||
<span class="icon-[lucide--plus] size-4" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Zoom out"
|
||||
class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("zoom", { bubbles: true, detail: { direction: "out" } }))'
|
||||
>
|
||||
<span class="icon-[lucide--minus] size-4" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
component MarketingSectionHeader {
|
||||
props {
|
||||
id = ""
|
||||
eyebrow = ""
|
||||
title = ""
|
||||
description = ""
|
||||
align = "split"
|
||||
size = "default"
|
||||
color = "primary"
|
||||
actionLabel = ""
|
||||
actionHref = ""
|
||||
actionIcon = ""
|
||||
actionExternal = false
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="MarketingSectionHeader"
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
class='{class}'
|
||||
>
|
||||
<SectionHeader
|
||||
id='{id}'
|
||||
eyebrow='{eyebrow}'
|
||||
title='{title}'
|
||||
description='{description}'
|
||||
align='{align}'
|
||||
size='{size}'
|
||||
color='{color}'
|
||||
headingLevel="2"
|
||||
maxWidth="3xl"
|
||||
>
|
||||
<div data-slot="icon">
|
||||
<slot name="icon"></slot>
|
||||
</div>
|
||||
|
||||
<div data-slot="actions">
|
||||
{#if actionLabel}
|
||||
<TextLink
|
||||
label='{actionLabel}'
|
||||
href='{actionHref}'
|
||||
external='{actionExternal}'
|
||||
icon='{actionIcon}'
|
||||
iconPosition="start"
|
||||
showArrow="true"
|
||||
color='{color}'
|
||||
/>
|
||||
{/if}
|
||||
<slot name="actions"></slot>
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
</SectionHeader>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
component Marquee {
|
||||
props {
|
||||
@event pause = function
|
||||
@event resume = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Marquee"
|
||||
@@ -10,12 +8,104 @@ component Marquee {
|
||||
variant = "default"
|
||||
class = ""
|
||||
}
|
||||
|
||||
state paused = false
|
||||
|
||||
view {
|
||||
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--marquee wire-next--variant-{variant} {class}">
|
||||
{#if title}<strong>{title}</strong>{/if}
|
||||
{#if description}<p>{description}</p>{/if}
|
||||
{#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
|
||||
<slot />
|
||||
<section
|
||||
data-ui-component="Marquee"
|
||||
aria-label='{title}'
|
||||
class='overflow-hidden border-y border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] {class}'
|
||||
>
|
||||
<div class="flex items-stretch">
|
||||
{#if title}
|
||||
<div class="relative z-10 flex shrink-0 items-center gap-2 border-r border-[var(--wire-color-border)] bg-[var(--wire-color-primary)] px-4 py-3 text-[var(--wire-color-on-primary)] sm:px-5">
|
||||
<span class="icon-[lucide--megaphone] size-4" aria-hidden="true"></span>
|
||||
<span class="text-sm font-bold">{title}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="group relative min-w-0 flex-1 overflow-hidden"
|
||||
@mouseenter='paused = true; event.currentTarget.dispatchEvent(new CustomEvent("pause", { bubbles: true }))'
|
||||
@mouseleave='paused = false; event.currentTarget.dispatchEvent(new CustomEvent("resume", { bubbles: true }))'
|
||||
@focusin='paused = true'
|
||||
@focusout='paused = false'
|
||||
>
|
||||
<div
|
||||
class="wire-marquee-track flex w-max min-w-full items-center"
|
||||
class:wire-marquee-paused='paused'
|
||||
>
|
||||
{#each items as item}
|
||||
<a
|
||||
href='{item.href || "#"}'
|
||||
class="flex shrink-0 items-center gap-3 px-5 py-3 text-sm font-medium text-[var(--wire-color-text)] transition hover:bg-[var(--wire-color-primary-soft)] hover:text-[var(--wire-color-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]"
|
||||
>
|
||||
<span
|
||||
class="size-2 shrink-0 rounded-full bg-[var(--wire-color-primary)]"
|
||||
class:bg-[var(--wire-color-danger)]='item.color === "danger"'
|
||||
class:bg-[var(--wire-color-warning)]='item.color === "warning"'
|
||||
class:bg-[var(--wire-color-success)]='item.color === "success"'
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span>{item.label || item.title}</span>
|
||||
{#if item.meta}
|
||||
<span class="text-xs text-[var(--wire-color-text-muted)]">{item.meta}</span>
|
||||
{/if}
|
||||
</a>
|
||||
{:empty}
|
||||
<p class="px-5 py-3 text-sm text-[var(--wire-color-text-muted)]">{description || "No announcements available."}</p>
|
||||
{/each}
|
||||
|
||||
{#if items.length > 0}
|
||||
{#each items as item}
|
||||
<a
|
||||
href='{item.href || "#"}'
|
||||
aria-hidden="true"
|
||||
tabindex="-1"
|
||||
class="flex shrink-0 items-center gap-3 px-5 py-3 text-sm font-medium text-[var(--wire-color-text)]"
|
||||
>
|
||||
<span class="size-2 shrink-0 rounded-full bg-[var(--wire-color-primary)]" aria-hidden="true"></span>
|
||||
<span>{item.label || item.title}</span>
|
||||
</a>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label='{paused ? "Resume announcements" : "Pause announcements"}'
|
||||
class="flex shrink-0 items-center justify-center border-l border-[var(--wire-color-border)] px-4 text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]"
|
||||
@click='paused = !paused; event.currentTarget.dispatchEvent(new CustomEvent(paused ? "pause" : "resume", { bubbles: true }))'
|
||||
>
|
||||
<span class="icon-[lucide--pause] size-4" data-show='!paused' aria-hidden="true"></span>
|
||||
<span class="icon-[lucide--play] size-4" data-show='paused' aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
</section>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-marquee-track {
|
||||
animation: wire-marquee 32s linear infinite;
|
||||
}
|
||||
|
||||
.wire-marquee-paused {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
@keyframes wire-marquee {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.wire-marquee-track {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
component MetricCard {
|
||||
props {
|
||||
label = "Metric"
|
||||
value = "0"
|
||||
description = ""
|
||||
icon = ""
|
||||
prefix = ""
|
||||
suffix = ""
|
||||
trend = ""
|
||||
trendLabel = ""
|
||||
trendDirection = "neutral"
|
||||
href = ""
|
||||
actionLabel = ""
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "default"
|
||||
align = "left"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<article
|
||||
data-ui-component="MetricCard"
|
||||
class='group relative flex h-full flex-col rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] p-5 shadow-sm transition {class}'
|
||||
class:p-4='size === "sm"'
|
||||
class:p-6='size === "lg"'
|
||||
class:bg-[var(--wire-color-primary-soft)]='variant === "soft" && color === "primary"'
|
||||
class:bg-[var(--wire-color-success-soft)]='variant === "soft" && color === "success"'
|
||||
class:bg-[var(--wire-color-warning-soft)]='variant === "soft" && color === "warning"'
|
||||
class:bg-[var(--wire-color-danger-soft)]='variant === "soft" && color === "danger"'
|
||||
class:bg-transparent='variant === "minimal"'
|
||||
class:border-transparent='variant === "minimal"'
|
||||
class:shadow-none='variant === "minimal"'
|
||||
class:text-center='align === "center"'
|
||||
class:items-center='align === "center"'
|
||||
class:text-right='align === "right"'
|
||||
class:items-end='align === "right"'
|
||||
class:hover:border-[var(--wire-color-primary)]='href !== ""'
|
||||
class:hover:shadow-lg='href !== ""'
|
||||
>
|
||||
<div class="flex w-full items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-[var(--wire-color-text-muted)]">{label}</p>
|
||||
<p class="mt-2 text-3xl font-bold tracking-tight text-[var(--wire-color-text)]" class:text-4xl='size === "lg"'>
|
||||
<span>{prefix}</span><span>{value}</span><span>{suffix}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if icon}
|
||||
<span
|
||||
class="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]"
|
||||
class:bg-[var(--wire-color-success-soft)]='color === "success"'
|
||||
class:text-[var(--wire-color-success)]='color === "success"'
|
||||
class:bg-[var(--wire-color-warning-soft)]='color === "warning"'
|
||||
class:text-[var(--wire-color-warning-text)]='color === "warning"'
|
||||
class:bg-[var(--wire-color-danger-soft)]='color === "danger"'
|
||||
class:text-[var(--wire-color-danger)]='color === "danger"'
|
||||
class:bg-[var(--wire-color-info-soft)]='color === "info"'
|
||||
class:text-[var(--wire-color-info)]='color === "info"'
|
||||
>
|
||||
<span class='{icon + " size-5"}' aria-hidden="true"></span>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if description}
|
||||
<p class="mt-3 text-sm leading-6 text-[var(--wire-color-text-muted)]">{description}</p>
|
||||
{/if}
|
||||
|
||||
{#if trend || trendLabel}
|
||||
<div class="mt-4 inline-flex items-center gap-2 text-sm font-semibold">
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded-full px-2.5 py-1"
|
||||
class:bg-[var(--wire-color-success-soft)]='trendDirection === "up" || trendDirection === "positive"'
|
||||
class:text-[var(--wire-color-success)]='trendDirection === "up" || trendDirection === "positive"'
|
||||
class:bg-[var(--wire-color-danger-soft)]='trendDirection === "down" || trendDirection === "negative"'
|
||||
class:text-[var(--wire-color-danger)]='trendDirection === "down" || trendDirection === "negative"'
|
||||
class:bg-[var(--wire-color-surface-soft)]='trendDirection === "neutral"'
|
||||
class:text-[var(--wire-color-text-muted)]='trendDirection === "neutral"'
|
||||
>
|
||||
{#if trendDirection === "up" || trendDirection === "positive"}
|
||||
<span class="icon-[lucide--trending-up] size-4" aria-hidden="true"></span>
|
||||
{:else if trendDirection === "down" || trendDirection === "negative"}
|
||||
<span class="icon-[lucide--trending-down] size-4" aria-hidden="true"></span>
|
||||
{:else}
|
||||
<span class="icon-[lucide--minus] size-4" aria-hidden="true"></span>
|
||||
{/if}
|
||||
<span>{trend}</span>
|
||||
</span>
|
||||
{#if trendLabel}
|
||||
<span class="text-[var(--wire-color-text-muted)]">{trendLabel}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if href}
|
||||
<TextLink
|
||||
label='{actionLabel || "View details"}'
|
||||
href='{href}'
|
||||
color='{color}'
|
||||
size="sm"
|
||||
class="mt-5"
|
||||
/>
|
||||
{/if}
|
||||
</article>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
component MetricGrid {
|
||||
props {
|
||||
items = []
|
||||
columns = 4
|
||||
tabletColumns = 2
|
||||
mobileColumns = 1
|
||||
gap = "md"
|
||||
equalHeight = true
|
||||
dividers = false
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "default"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
data-ui-component="MetricGrid"
|
||||
class='grid w-full grid-cols-1 {class}'
|
||||
class:grid-cols-2='mobileColumns === 2'
|
||||
class:sm:grid-cols-2='tabletColumns === 2'
|
||||
class:sm:grid-cols-3='tabletColumns === 3'
|
||||
class:lg:grid-cols-2='columns === 2'
|
||||
class:lg:grid-cols-3='columns === 3'
|
||||
class:lg:grid-cols-4='columns === 4'
|
||||
class:lg:grid-cols-5='columns === 5'
|
||||
class:lg:grid-cols-6='columns === 6'
|
||||
class:gap-3='gap === "sm"'
|
||||
class:gap-5='gap === "md"'
|
||||
class:gap-8='gap === "lg"'
|
||||
class:divide-y='dividers'
|
||||
class:sm:divide-y-0='dividers'
|
||||
class:sm:divide-x='dividers'
|
||||
class:divide-[var(--wire-color-border)]='dividers'
|
||||
>
|
||||
{#each items as item}
|
||||
<MetricCard
|
||||
label='{item.label || item.title}'
|
||||
value='{item.value}'
|
||||
description='{item.description || ""}'
|
||||
icon='{item.icon || ""}'
|
||||
prefix='{item.prefix || ""}'
|
||||
suffix='{item.suffix || ""}'
|
||||
trend='{item.trend || ""}'
|
||||
trendLabel='{item.trendLabel || ""}'
|
||||
trendDirection='{item.trendDirection || "neutral"}'
|
||||
href='{item.href || ""}'
|
||||
actionLabel='{item.actionLabel || ""}'
|
||||
size='{item.size || size}'
|
||||
color='{item.color || color}'
|
||||
variant='{dividers ? "minimal" : (item.variant || variant)}'
|
||||
align='{item.align || "left"}'
|
||||
class="h-full"
|
||||
/>
|
||||
{:empty}
|
||||
<slot></slot>
|
||||
{/each}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
component PageHeader {
|
||||
props {
|
||||
eyebrow = ""
|
||||
title = ""
|
||||
description = ""
|
||||
icon = ""
|
||||
align = "left"
|
||||
size = "default"
|
||||
compact = false
|
||||
showBreadcrumbs = false
|
||||
breadcrumbs = []
|
||||
primaryLabel = ""
|
||||
primaryHref = ""
|
||||
primaryIcon = ""
|
||||
secondaryLabel = ""
|
||||
secondaryHref = ""
|
||||
secondaryIcon = ""
|
||||
color = "primary"
|
||||
variant = "default"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="PageHeader"
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
class='{class}'
|
||||
>
|
||||
<Section
|
||||
spacing='{compact || size === "compact" ? "md" : "lg"}'
|
||||
variant='{variant}'
|
||||
color='{color}'
|
||||
borderBottom="true"
|
||||
>
|
||||
{#if showBreadcrumbs && breadcrumbs.length > 0}
|
||||
<Breadcrumb
|
||||
label="Breadcrumb"
|
||||
items='{breadcrumbs}'
|
||||
active='{breadcrumbs[breadcrumbs.length - 1]?.value || breadcrumbs[breadcrumbs.length - 1]?.label || ""}'
|
||||
color='{color}'
|
||||
class="mb-6"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="flex flex-col gap-6"
|
||||
class:items-center='align === "center"'
|
||||
class:text-center='align === "center"'
|
||||
class:lg:flex-row='align !== "center"'
|
||||
class:lg:items-end='align !== "center"'
|
||||
class:lg:justify-between='align !== "center"'
|
||||
>
|
||||
<div class="max-w-4xl">
|
||||
{#if icon}
|
||||
<span class="mb-5 inline-flex size-12 items-center justify-center rounded-2xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]">
|
||||
<span class='{icon + " size-6"}' aria-hidden="true"></span>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<SectionHeader
|
||||
eyebrow='{eyebrow}'
|
||||
title='{title}'
|
||||
description='{description}'
|
||||
align='{align === "center" ? "center" : "left"}'
|
||||
size='{compact || size === "compact" ? "sm" : "lg"}'
|
||||
color='{color}'
|
||||
headingLevel="1"
|
||||
maxWidth="4xl"
|
||||
>
|
||||
<slot name="meta"></slot>
|
||||
</SectionHeader>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3" class:justify-center='align === "center"'>
|
||||
{#if secondaryLabel}
|
||||
<Button
|
||||
label='{secondaryLabel}'
|
||||
href='{secondaryHref}'
|
||||
icon='{secondaryIcon}'
|
||||
variant="outline"
|
||||
color='{color}'
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if primaryLabel}
|
||||
<Button
|
||||
label='{primaryLabel}'
|
||||
href='{primaryHref}'
|
||||
icon='{primaryIcon}'
|
||||
variant="default"
|
||||
color='{color}'
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<slot name="actions"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
</Section>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
component PublicPageShell {
|
||||
props {
|
||||
maxWidth = "full"
|
||||
fullWidth = true
|
||||
headerOffset = "none"
|
||||
background = "default"
|
||||
overflow = "clip"
|
||||
minHeight = "screen"
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "default"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
data-ui-component="PublicPageShell"
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-variant='{variant}'
|
||||
class='relative isolate w-full text-[var(--wire-color-text)] {class}'
|
||||
class:min-h-screen='minHeight === "screen"'
|
||||
class:min-h-dvh='minHeight === "dvh"'
|
||||
class:overflow-x-clip='overflow === "clip"'
|
||||
class:overflow-x-hidden='overflow === "hidden"'
|
||||
class:bg-[var(--wire-color-background)]='background === "default"'
|
||||
class:bg-[var(--wire-color-surface-soft)]='background === "soft"'
|
||||
class:bg-[var(--wire-color-surface-raised)]='background === "raised"'
|
||||
class:pt-16='headerOffset === "sm"'
|
||||
class:pt-20='headerOffset === "md"'
|
||||
class:pt-24='headerOffset === "lg"'
|
||||
>
|
||||
<slot name="before"></slot>
|
||||
|
||||
{#if fullWidth}
|
||||
<main class="w-full">
|
||||
<slot></slot>
|
||||
</main>
|
||||
{:else}
|
||||
<main
|
||||
class="mx-auto w-full px-4 sm:px-6 lg:px-8"
|
||||
class:max-w-5xl='maxWidth === "lg"'
|
||||
class:max-w-7xl='maxWidth === "xl"'
|
||||
class:max-w-screen-2xl='maxWidth === "2xl"'
|
||||
>
|
||||
<slot></slot>
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
<slot name="after"></slot>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,5 @@
|
||||
component SearchBox {
|
||||
props {
|
||||
@event input = function
|
||||
@event change = function
|
||||
@event search = function
|
||||
@event submit = function
|
||||
@event clear = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Search Box"
|
||||
@@ -19,7 +14,67 @@ component SearchBox {
|
||||
required = false
|
||||
class = ""
|
||||
}
|
||||
|
||||
state query = value
|
||||
|
||||
view {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--search-box {class}"><span>{label}</span><input {...attrs} type="{type}" name="{name}" value="{value}" placeholder="{placeholder}" min="{min}" max="{max}" step="{step}" disabled="{disabled}" required="{required}" /></label>
|
||||
<form
|
||||
data-ui-component="SearchBox"
|
||||
role="search"
|
||||
class='w-full {class}'
|
||||
@submit='event.preventDefault(); event.currentTarget.dispatchEvent(new CustomEvent("search", { bubbles: true, detail: { value: query, name: name } }))'
|
||||
>
|
||||
<label
|
||||
class="mb-2 block text-sm font-semibold text-[var(--wire-color-text)]"
|
||||
class:sr-only='label === ""'
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
|
||||
<div class="relative flex items-center">
|
||||
<span
|
||||
class="icon-[lucide--search] pointer-events-none absolute left-4 size-5 text-[var(--wire-color-input-placeholder)]"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
|
||||
<input
|
||||
name='{name}'
|
||||
type='{type}'
|
||||
value='{query}'
|
||||
placeholder='{placeholder}'
|
||||
min='{min}'
|
||||
max='{max}'
|
||||
step='{step}'
|
||||
disabled='{disabled}'
|
||||
required='{required}'
|
||||
autocomplete="off"
|
||||
class="w-full rounded-xl border border-[var(--wire-color-input-border)] bg-[var(--wire-color-input-background)] pl-12 pr-24 text-[var(--wire-color-input-text)] outline-none transition placeholder:text-[var(--wire-color-input-placeholder)] hover:border-[var(--wire-color-input-border-hover)] focus:border-[var(--wire-color-input-border-focus)] focus:ring-2 focus:ring-[var(--wire-color-focus)] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
class:h-10='size === "sm"'
|
||||
class:h-12='size === "default" || size === "md"'
|
||||
class:h-14='size === "lg"'
|
||||
@input='query = event.target.value'
|
||||
@change='query = event.target.value'
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Clear search"
|
||||
data-show='query.length > 0 && !disabled'
|
||||
class="absolute right-12 inline-flex size-8 items-center justify-center rounded-lg text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
|
||||
@click='query = ""; event.currentTarget.parentElement.querySelector("input")?.focus(); event.currentTarget.dispatchEvent(new CustomEvent("clear", { bubbles: true, detail: { value: "", name: name } }))'
|
||||
>
|
||||
<span class="icon-[lucide--x] size-4" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
aria-label='{label || "Search"}'
|
||||
disabled='{disabled}'
|
||||
class="absolute right-2 inline-flex size-9 items-center justify-center rounded-lg bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] transition hover:bg-[var(--wire-color-primary-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
<span class="icon-[lucide--arrow-right] size-4" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
component Section {
|
||||
props {
|
||||
id = ""
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "default"
|
||||
spacing = "lg"
|
||||
maxWidth = "xl"
|
||||
fullWidth = false
|
||||
borderTop = false
|
||||
borderBottom = false
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<section
|
||||
data-ui-component="Section"
|
||||
id='{id}'
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-variant='{variant}'
|
||||
class='relative isolate w-full {class}'
|
||||
class:py-8='spacing === "sm"'
|
||||
class:py-12='spacing === "md"'
|
||||
class:py-16='spacing === "lg"'
|
||||
class:sm:py-20='spacing === "lg"'
|
||||
class:py-20='spacing === "xl"'
|
||||
class:sm:py-24='spacing === "xl"'
|
||||
class:bg-[var(--wire-color-background)]='variant === "default"'
|
||||
class:bg-[var(--wire-color-surface-soft)]='variant === "soft"'
|
||||
class:bg-[var(--wire-color-surface-raised)]='variant === "raised"'
|
||||
class:bg-[var(--wire-color-primary-soft)]='variant === "tinted" && color === "primary"'
|
||||
class:bg-[var(--wire-color-success-soft)]='variant === "tinted" && color === "success"'
|
||||
class:bg-[var(--wire-color-warning-soft)]='variant === "tinted" && color === "warning"'
|
||||
class:bg-[var(--wire-color-danger-soft)]='variant === "tinted" && color === "danger"'
|
||||
class:bg-[var(--wire-color-info-soft)]='variant === "tinted" && color === "info"'
|
||||
class:bg-[var(--wire-color-primary)]='variant === "solid" && color === "primary"'
|
||||
class:text-[var(--wire-color-on-primary)]='variant === "solid" && color === "primary"'
|
||||
class:bg-[var(--wire-color-danger)]='variant === "solid" && color === "danger"'
|
||||
class:text-[var(--wire-color-on-danger)]='variant === "solid" && color === "danger"'
|
||||
class:border-t='borderTop'
|
||||
class:border-b='borderBottom'
|
||||
class:border-[var(--wire-color-border)]='borderTop || borderBottom'
|
||||
>
|
||||
{#if fullWidth}
|
||||
<slot></slot>
|
||||
{:else}
|
||||
<Container
|
||||
columns="1"
|
||||
gap="md"
|
||||
maxWidth='{maxWidth}'
|
||||
size='{size}'
|
||||
color='{color}'
|
||||
>
|
||||
<slot></slot>
|
||||
</Container>
|
||||
{/if}
|
||||
</section>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
component SectionHeader {
|
||||
props {
|
||||
id = ""
|
||||
eyebrow = ""
|
||||
title = ""
|
||||
description = ""
|
||||
align = "left"
|
||||
size = "default"
|
||||
color = "primary"
|
||||
headingLevel = 2
|
||||
maxWidth = "3xl"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<header
|
||||
data-ui-component="SectionHeader"
|
||||
class='flex min-w-0 flex-col gap-5 {class}'
|
||||
class:items-center='align === "center"'
|
||||
class:text-center='align === "center"'
|
||||
class:items-end='align === "right"'
|
||||
class:text-right='align === "right"'
|
||||
class:lg:flex-row='align === "split"'
|
||||
class:lg:items-end='align === "split"'
|
||||
class:lg:justify-between='align === "split"'
|
||||
>
|
||||
<div
|
||||
class="min-w-0"
|
||||
class:max-w-xl='maxWidth === "xl"'
|
||||
class:max-w-2xl='maxWidth === "2xl"'
|
||||
class:max-w-3xl='maxWidth === "3xl"'
|
||||
class:max-w-4xl='maxWidth === "4xl"'
|
||||
class:mx-auto='align === "center"'
|
||||
>
|
||||
<div class="mb-3 flex items-center gap-3" class:justify-center='align === "center"' class:justify-end='align === "right"'>
|
||||
<slot name="icon"></slot>
|
||||
|
||||
{#if eyebrow}
|
||||
<p
|
||||
class="text-xs font-bold uppercase tracking-[0.18em]"
|
||||
class:text-sm='size === "lg"'
|
||||
class:text-[var(--wire-color-primary)]='color === "primary"'
|
||||
class:text-[var(--wire-color-secondary)]='color === "secondary"'
|
||||
class:text-[var(--wire-color-success)]='color === "success"'
|
||||
class:text-[var(--wire-color-warning-text)]='color === "warning"'
|
||||
class:text-[var(--wire-color-danger)]='color === "danger"'
|
||||
class:text-[var(--wire-color-info)]='color === "info"'
|
||||
>
|
||||
{eyebrow}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if headingLevel === 1}
|
||||
<h1
|
||||
id='{id}'
|
||||
class="font-bold leading-tight tracking-tight text-[var(--wire-color-text)]"
|
||||
class:text-3xl='size === "sm"'
|
||||
class:text-4xl='size === "default" || size === "md"'
|
||||
class:sm:text-5xl='size === "default" || size === "md"'
|
||||
class:text-5xl='size === "lg"'
|
||||
class:sm:text-6xl='size === "lg"'
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
{:else if headingLevel === 3}
|
||||
<h3
|
||||
id='{id}'
|
||||
class="font-bold leading-tight tracking-tight text-[var(--wire-color-text)]"
|
||||
class:text-xl='size === "sm"'
|
||||
class:text-2xl='size === "default" || size === "md"'
|
||||
class:text-3xl='size === "lg"'
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
{:else}
|
||||
<h2
|
||||
id='{id}'
|
||||
class="font-bold leading-tight tracking-tight text-[var(--wire-color-text)]"
|
||||
class:text-2xl='size === "sm"'
|
||||
class:text-3xl='size === "default" || size === "md"'
|
||||
class:sm:text-4xl='size === "default" || size === "md"'
|
||||
class:text-4xl='size === "lg"'
|
||||
class:sm:text-5xl='size === "lg"'
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
{/if}
|
||||
|
||||
{#if description}
|
||||
<p
|
||||
class="mt-4 leading-7 text-[var(--wire-color-text-muted)]"
|
||||
class:text-sm='size === "sm"'
|
||||
class:text-base='size === "default" || size === "md"'
|
||||
class:text-lg='size === "lg"'
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex shrink-0 flex-wrap items-center gap-3"
|
||||
class:justify-center='align === "center"'
|
||||
class:justify-end='align === "right" || align === "split"'
|
||||
>
|
||||
<slot name="actions"></slot>
|
||||
</div>
|
||||
</header>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
component SplitHero {
|
||||
props {
|
||||
eyebrow = ""
|
||||
title = "A better digital experience"
|
||||
highlight = ""
|
||||
description = ""
|
||||
primaryLabel = ""
|
||||
primaryHref = ""
|
||||
primaryIcon = ""
|
||||
secondaryLabel = ""
|
||||
secondaryHref = ""
|
||||
secondaryIcon = ""
|
||||
visualPosition = "right"
|
||||
reverse = false
|
||||
ratio = "balanced"
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "default"
|
||||
trustItems = []
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<Section
|
||||
spacing='{size === "compact" ? "md" : "xl"}'
|
||||
variant='{variant === "soft" ? "soft" : "default"}'
|
||||
color='{color}'
|
||||
class='overflow-hidden {class}'
|
||||
>
|
||||
<div
|
||||
data-ui-component="SplitHero"
|
||||
class="grid items-center gap-10 lg:grid-cols-2 lg:gap-16"
|
||||
class:lg:grid-cols-[1.15fr_0.85fr]='ratio === "content"'
|
||||
class:lg:grid-cols-[0.85fr_1.15fr]='ratio === "visual"'
|
||||
>
|
||||
<div
|
||||
class="max-w-3xl"
|
||||
class:lg:order-2='reverse || visualPosition === "left"'
|
||||
>
|
||||
{#if eyebrow}
|
||||
<div class="mb-6 inline-flex items-center gap-2 rounded-full border border-[var(--wire-color-primary-muted)] bg-[var(--wire-color-primary-soft)] px-4 py-2 text-sm font-semibold text-[var(--wire-color-primary)]">
|
||||
<span class="icon-[lucide--sparkles] size-4" aria-hidden="true"></span>
|
||||
<span>{eyebrow}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<h1 class="text-4xl font-bold leading-[1.08] tracking-[-0.035em] text-[var(--wire-color-text)] sm:text-5xl lg:text-6xl">
|
||||
<span>{title}</span>
|
||||
{#if highlight}
|
||||
<span class="block text-[var(--wire-color-primary)]">{highlight}</span>
|
||||
{/if}
|
||||
</h1>
|
||||
|
||||
{#if description}
|
||||
<p class="mt-6 text-base leading-8 text-[var(--wire-color-text-muted)] sm:text-lg">{description}</p>
|
||||
{/if}
|
||||
|
||||
<HeroActions
|
||||
actions='{[
|
||||
{ label: primaryLabel, href: primaryHref, icon: primaryIcon, variant: "default", color: color },
|
||||
{ label: secondaryLabel, href: secondaryHref, icon: secondaryIcon, variant: "outline", color: color }
|
||||
]}'
|
||||
align="left"
|
||||
stackOnMobile="true"
|
||||
fullWidthMobile="true"
|
||||
size="lg"
|
||||
color='{color}'
|
||||
class="mt-8"
|
||||
>
|
||||
<slot name="actions"></slot>
|
||||
</HeroActions>
|
||||
|
||||
{#if trustItems.length > 0}
|
||||
<div class="mt-8 flex flex-wrap gap-x-6 gap-y-3 text-sm text-[var(--wire-color-text-muted)]">
|
||||
{#each trustItems as item}
|
||||
<span class="inline-flex items-center gap-2">
|
||||
<span class='{(item.icon || "icon-[lucide--check-circle-2]") + " size-4 text-[var(--wire-color-primary)]"}' aria-hidden="true"></span>
|
||||
<span>{item.label || item.title || item}</span>
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<slot name="trust"></slot>
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative min-w-0"
|
||||
class:lg:order-1='reverse || visualPosition === "left"'
|
||||
>
|
||||
<div class="absolute -inset-8 -z-10 rounded-[2.5rem] bg-[var(--wire-color-primary-soft)] blur-2xl" aria-hidden="true"></div>
|
||||
<slot name="visual"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
component StatsBar {
|
||||
props {
|
||||
items = []
|
||||
columns = 4
|
||||
compact = true
|
||||
dividers = true
|
||||
icons = true
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "raised"
|
||||
maxWidth = "xl"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<Section
|
||||
spacing='{compact ? "sm" : "md"}'
|
||||
variant='{variant}'
|
||||
color='{color}'
|
||||
maxWidth='{maxWidth}'
|
||||
borderTop="true"
|
||||
borderBottom="true"
|
||||
class='{class}'
|
||||
>
|
||||
<div data-ui-component="StatsBar">
|
||||
<MetricGrid
|
||||
items='{items.map((item) => ({ ...item, icon: icons ? item.icon : "", variant: "minimal", size: compact ? "sm" : size }))}'
|
||||
columns='{columns}'
|
||||
tabletColumns="2"
|
||||
mobileColumns="1"
|
||||
gap='{compact ? "sm" : "md"}'
|
||||
dividers='{dividers}'
|
||||
size='{compact ? "sm" : size}'
|
||||
color='{color}'
|
||||
variant="minimal"
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
component Tabs {
|
||||
props {
|
||||
@event change = function
|
||||
@event select = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Tabs"
|
||||
@@ -10,10 +8,83 @@ component Tabs {
|
||||
orientation = "horizontal"
|
||||
class = ""
|
||||
}
|
||||
|
||||
state activeValue = active || (items[0] ? (items[0].value || items[0].id || "0") : "")
|
||||
|
||||
view {
|
||||
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--tabs 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>
|
||||
<section
|
||||
data-ui-component="Tabs"
|
||||
class='w-full {class}'
|
||||
class:flex='orientation === "vertical"'
|
||||
class:items-start='orientation === "vertical"'
|
||||
class:gap-6='orientation === "vertical"'
|
||||
>
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label='{label}'
|
||||
aria-orientation='{orientation}'
|
||||
class="flex min-w-0 gap-1 overflow-x-auto rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-soft)] p-1"
|
||||
class:flex-col='orientation === "vertical"'
|
||||
class:w-56='orientation === "vertical"'
|
||||
class:shrink-0='orientation === "vertical"'
|
||||
>
|
||||
{#each items as item, index}
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
id='tab-{item.value || item.id || index}'
|
||||
aria-selected='{activeValue === (item.value || item.id || String(index))}'
|
||||
aria-controls='panel-{item.value || item.id || index}'
|
||||
tabindex='{activeValue === (item.value || item.id || String(index)) ? "0" : "-1"}'
|
||||
disabled='{item.disabled || false}'
|
||||
class="inline-flex min-w-max flex-1 items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-semibold outline-none transition disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class:bg-[var(--wire-color-surface-raised)]='activeValue === (item.value || item.id || String(index))'
|
||||
class:text-[var(--wire-color-primary)]='activeValue === (item.value || item.id || String(index))'
|
||||
class:shadow-sm='activeValue === (item.value || item.id || String(index))'
|
||||
class:text-[var(--wire-color-text-muted)]='activeValue !== (item.value || item.id || String(index))'
|
||||
class:hover:text-[var(--wire-color-text)]='activeValue !== (item.value || item.id || String(index))'
|
||||
class:justify-start='orientation === "vertical"'
|
||||
class:px-3='size === "sm"'
|
||||
class:py-2='size === "sm"'
|
||||
class:px-5='size === "lg"'
|
||||
class:py-3='size === "lg"'
|
||||
@click='activeValue = item.value || item.id || String(index); event.currentTarget.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: { item: item, index: index, value: activeValue } })); event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, index: index, value: activeValue } }))'
|
||||
>
|
||||
{#if item.icon}
|
||||
<span class='{item.icon + " size-4"}' aria-hidden="true"></span>
|
||||
{/if}
|
||||
<span>{item.label || item.title}</span>
|
||||
{#if item.badge}
|
||||
<span class="rounded-full bg-[var(--wire-color-primary-soft)] px-2 py-0.5 text-xs text-[var(--wire-color-primary)]">{item.badge}</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1 pt-4" class:pt-0='orientation === "vertical"'>
|
||||
{#each items as item, index}
|
||||
<div
|
||||
role="tabpanel"
|
||||
id='panel-{item.value || item.id || index}'
|
||||
aria-labelledby='tab-{item.value || item.id || index}'
|
||||
tabindex="0"
|
||||
data-show='activeValue === (item.value || item.id || String(index))'
|
||||
class="rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
|
||||
>
|
||||
{#if item.title && item.title !== item.label}
|
||||
<h3 class="text-lg font-bold text-[var(--wire-color-text)]">{item.title}</h3>
|
||||
{/if}
|
||||
{#if item.description}
|
||||
<p class="mt-2 leading-7 text-[var(--wire-color-text-muted)]">{item.description}</p>
|
||||
{/if}
|
||||
{#if item.content}
|
||||
<div class="mt-4 text-[var(--wire-color-text)]">{item.content}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<slot></slot>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
component TextLink {
|
||||
props {
|
||||
@event click = function
|
||||
@event focus = function
|
||||
@event blur = function
|
||||
label = "Learn more"
|
||||
href = "#"
|
||||
target = ""
|
||||
rel = ""
|
||||
external = false
|
||||
icon = ""
|
||||
iconPosition = "start"
|
||||
showArrow = true
|
||||
underline = false
|
||||
size = "default"
|
||||
color = "primary"
|
||||
variant = "default"
|
||||
disabled = false
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<span
|
||||
{...attrs}
|
||||
data-ui-component="TextLink"
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-variant='{variant}'
|
||||
class='inline-flex min-w-0 {class}'
|
||||
>
|
||||
{#if disabled}
|
||||
<span
|
||||
aria-disabled="true"
|
||||
class="inline-flex cursor-not-allowed items-center gap-2 font-semibold opacity-50"
|
||||
class:text-sm='size === "sm" || size === "default"'
|
||||
class:text-base='size === "md"'
|
||||
class:text-lg='size === "lg"'
|
||||
>
|
||||
{#if icon && iconPosition === "start"}
|
||||
<span class='{icon + " size-4 shrink-0"}' aria-hidden="true"></span>
|
||||
{/if}
|
||||
<span>{label}</span>
|
||||
</span>
|
||||
{:else}
|
||||
<a
|
||||
href='{href}'
|
||||
target='{target}'
|
||||
rel='{external ? (rel || "noopener noreferrer") : rel}'
|
||||
class="group inline-flex items-center gap-2 rounded-md font-semibold outline-none transition focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--wire-color-background)]"
|
||||
class:text-sm='size === "sm" || size === "default"'
|
||||
class:text-base='size === "md"'
|
||||
class:text-lg='size === "lg"'
|
||||
class:text-[var(--wire-color-primary)]='color === "primary"'
|
||||
class:hover:text-[var(--wire-color-primary-hover)]='color === "primary"'
|
||||
class:text-[var(--wire-color-secondary)]='color === "secondary"'
|
||||
class:text-[var(--wire-color-success)]='color === "success"'
|
||||
class:text-[var(--wire-color-warning-text)]='color === "warning"'
|
||||
class:text-[var(--wire-color-danger)]='color === "danger"'
|
||||
class:text-[var(--wire-color-info)]='color === "info"'
|
||||
class:text-[var(--wire-color-text)]='color === "neutral"'
|
||||
class:underline='underline'
|
||||
class:underline-offset-4='underline'
|
||||
class:rounded-lg='variant === "button"'
|
||||
class:bg-[var(--wire-color-primary-soft)]='variant === "button" && color === "primary"'
|
||||
class:px-3='variant === "button"'
|
||||
class:py-2='variant === "button"'
|
||||
>
|
||||
{#if icon && iconPosition === "start"}
|
||||
<span class='{icon + " size-4 shrink-0"}' aria-hidden="true"></span>
|
||||
{/if}
|
||||
|
||||
<span>{label}</span>
|
||||
|
||||
{#if icon && iconPosition === "end"}
|
||||
<span class='{icon + " size-4 shrink-0"}' aria-hidden="true"></span>
|
||||
{/if}
|
||||
|
||||
{#if external}
|
||||
<span class="icon-[lucide--external-link] size-4 shrink-0" aria-hidden="true"></span>
|
||||
{:else if showArrow}
|
||||
<span class="icon-[lucide--arrow-right] size-4 shrink-0 transition-transform group-hover:translate-x-1" aria-hidden="true"></span>
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
component Timeline {
|
||||
props {
|
||||
@event select = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Timeline"
|
||||
@@ -9,12 +8,76 @@ component Timeline {
|
||||
variant = "default"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--timeline wire-next--variant-{variant} {class}">
|
||||
{#if title}<strong>{title}</strong>{/if}
|
||||
{#if description}<p>{description}</p>{/if}
|
||||
{#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
|
||||
<slot />
|
||||
<section
|
||||
data-ui-component="Timeline"
|
||||
aria-label='{title}'
|
||||
class='w-full {class}'
|
||||
>
|
||||
{#if title || description}
|
||||
<header class="mb-6">
|
||||
{#if title}
|
||||
<h3 class="text-xl font-bold text-[var(--wire-color-text)]">{title}</h3>
|
||||
{/if}
|
||||
{#if description}
|
||||
<p class="mt-2 max-w-2xl leading-7 text-[var(--wire-color-text-muted)]">{description}</p>
|
||||
{/if}
|
||||
</header>
|
||||
{/if}
|
||||
|
||||
<ol class="relative ml-4 border-l border-[var(--wire-color-border)]">
|
||||
{#each items as item, index}
|
||||
<li
|
||||
class="relative pb-8 pl-8 last:pb-0"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, index: index } }))'
|
||||
>
|
||||
<span
|
||||
class="absolute -left-[1.05rem] top-0 flex size-8 items-center justify-center rounded-full border-4 border-[var(--wire-color-background)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-sm"
|
||||
class:bg-[var(--wire-color-success)]='item.status === "complete" || item.status === "success"'
|
||||
class:bg-[var(--wire-color-warning)]='item.status === "warning" || item.status === "pending"'
|
||||
class:bg-[var(--wire-color-danger)]='item.status === "danger" || item.status === "failed"'
|
||||
>
|
||||
<span class='{item.icon || "icon-[lucide--circle]"}' aria-hidden="true"></span>
|
||||
</span>
|
||||
|
||||
<article
|
||||
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] p-5 shadow-sm"
|
||||
class:border-transparent='variant === "minimal"'
|
||||
class:bg-transparent='variant === "minimal"'
|
||||
class:p-0='variant === "minimal"'
|
||||
class:shadow-none='variant === "minimal"'
|
||||
>
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h4 class="font-bold text-[var(--wire-color-text)]">{item.title || item.label}</h4>
|
||||
{#if item.subtitle}
|
||||
<p class="mt-1 text-sm font-medium text-[var(--wire-color-primary)]">{item.subtitle}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if item.date || item.meta}
|
||||
<time class="shrink-0 text-sm text-[var(--wire-color-text-muted)]">{item.date || item.meta}</time>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if item.description}
|
||||
<p class="mt-3 text-sm leading-6 text-[var(--wire-color-text-muted)]">{item.description}</p>
|
||||
{/if}
|
||||
|
||||
{#if item.href}
|
||||
<a href='{item.href}' class="mt-4 inline-flex items-center gap-2 text-sm font-semibold text-[var(--wire-color-primary)] hover:text-[var(--wire-color-primary-hover)]">
|
||||
<span>{item.actionLabel || "View details"}</span>
|
||||
<span class="icon-[lucide--arrow-right] size-4" aria-hidden="true"></span>
|
||||
</a>
|
||||
{/if}
|
||||
</article>
|
||||
</li>
|
||||
{:empty}
|
||||
<li class="pl-8 text-sm text-[var(--wire-color-text-muted)]">No timeline items available.</li>
|
||||
{/each}
|
||||
</ol>
|
||||
|
||||
<slot></slot>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,69 @@ component Typography {
|
||||
maxWidth = "xl"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<article class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--typography wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></article>
|
||||
<div
|
||||
data-ui-component="Typography"
|
||||
class='wire-typography text-[var(--wire-color-text)] {class}'
|
||||
class:max-w-3xl='maxWidth === "md"'
|
||||
class:max-w-5xl='maxWidth === "lg"'
|
||||
class:max-w-7xl='maxWidth === "xl"'
|
||||
class:max-w-none='maxWidth === "full"'
|
||||
class:text-sm='size === "sm"'
|
||||
class:text-base='size === "default" || size === "md"'
|
||||
class:text-lg='size === "lg"'
|
||||
class:columns-1='columns === 1'
|
||||
class:md:columns-2='columns === 2'
|
||||
class:lg:columns-3='columns === 3'
|
||||
class:gap-4='gap === "sm"'
|
||||
class:gap-8='gap === "md"'
|
||||
class:gap-12='gap === "lg"'
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-typography :where(h1, h2, h3, h4) {
|
||||
color: var(--wire-color-text);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.2;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
.wire-typography :where(h1) { font-size: clamp(2rem, 4vw, 3.5rem); margin: 0 0 1.5rem; }
|
||||
.wire-typography :where(h2) { font-size: clamp(1.5rem, 3vw, 2.25rem); margin: 2.5rem 0 1rem; }
|
||||
.wire-typography :where(h3) { font-size: 1.35rem; margin: 2rem 0 0.75rem; }
|
||||
.wire-typography :where(p, ul, ol, blockquote, pre, table) { margin: 1rem 0; }
|
||||
.wire-typography :where(p, li) { color: var(--wire-color-text-muted); line-height: 1.8; }
|
||||
.wire-typography :where(a) { color: var(--wire-color-primary); font-weight: 600; text-underline-offset: 0.2em; }
|
||||
.wire-typography :where(a:hover) { color: var(--wire-color-primary-hover); text-decoration: underline; }
|
||||
.wire-typography :where(ul, ol) { padding-left: 1.4rem; }
|
||||
.wire-typography :where(ul) { list-style: disc; }
|
||||
.wire-typography :where(ol) { list-style: decimal; }
|
||||
.wire-typography :where(blockquote) {
|
||||
border-left: 4px solid var(--wire-color-primary);
|
||||
background: var(--wire-color-primary-soft);
|
||||
border-radius: 0 0.75rem 0.75rem 0;
|
||||
padding: 1rem 1.25rem;
|
||||
color: var(--wire-color-text);
|
||||
}
|
||||
.wire-typography :where(code) {
|
||||
border-radius: 0.35rem;
|
||||
background: var(--wire-color-surface-soft);
|
||||
padding: 0.15rem 0.35rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.wire-typography :where(pre) {
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: 1rem;
|
||||
background: var(--wire-color-surface-raised);
|
||||
padding: 1rem;
|
||||
}
|
||||
.wire-typography :where(img) { border-radius: 1rem; }
|
||||
.wire-typography :where(hr) { border-color: var(--wire-color-border); margin: 2rem 0; }
|
||||
}
|
||||
}
|
||||
|
||||
+222
-132
@@ -39,221 +39,311 @@ component Card {
|
||||
class = ""
|
||||
}
|
||||
|
||||
state visible = true
|
||||
state selectedNav = activeNav
|
||||
state dismissed = false
|
||||
|
||||
functions {
|
||||
function chooseNavigation(item, index) {
|
||||
previousValue = selectedNav
|
||||
selectedNav = item.value || item.label
|
||||
$emit("navigate", {
|
||||
value: selectedNav,
|
||||
previousValue: previousValue,
|
||||
function dispatchCardNavigation(sourceEvent, item, index, root, customEvent) {
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
|
||||
|
||||
if (!root) {
|
||||
return
|
||||
}
|
||||
|
||||
customEvent = document.createEvent("CustomEvent")
|
||||
customEvent.initCustomEvent("navigate", true, false, {
|
||||
component: "Card",
|
||||
item: item,
|
||||
index: index
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
}
|
||||
|
||||
function chooseMobileNavigation(sourceEvent) {
|
||||
previousValue = selectedNav
|
||||
selectedNav = sourceEvent.target.value
|
||||
$emit("navigate", {
|
||||
value: selectedNav,
|
||||
previousValue: previousValue
|
||||
function dispatchCardNavigationValue(sourceEvent, root, customEvent) {
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
|
||||
|
||||
if (!root) {
|
||||
return
|
||||
}
|
||||
|
||||
customEvent = document.createEvent("CustomEvent")
|
||||
customEvent.initCustomEvent("navigate", true, false, {
|
||||
component: "Card",
|
||||
value: sourceEvent.currentTarget.value
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
}
|
||||
|
||||
function chooseAction(action, index) {
|
||||
$emit("action", {
|
||||
function dispatchCardHeaderAction(sourceEvent, action, index, root, customEvent) {
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
|
||||
|
||||
if (!root) {
|
||||
return
|
||||
}
|
||||
|
||||
customEvent = document.createEvent("CustomEvent")
|
||||
customEvent.initCustomEvent("action", true, false, {
|
||||
component: "Card",
|
||||
action: action,
|
||||
index: index
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
}
|
||||
|
||||
function dismissCard(sourceEvent) {
|
||||
panel = sourceEvent.currentTarget.closest(".wire-next__card-panel")
|
||||
if (panel) {
|
||||
panel.setAttribute("hidden", "")
|
||||
function dismissCard(sourceEvent, root, customEvent) {
|
||||
dismissed = true
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
|
||||
|
||||
if (!root) {
|
||||
return
|
||||
}
|
||||
visible = false
|
||||
$emit("dismiss", {
|
||||
|
||||
customEvent = document.createEvent("CustomEvent")
|
||||
customEvent.initCustomEvent("dismiss", true, false, {
|
||||
component: "Card",
|
||||
title: title
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<section
|
||||
<div
|
||||
{...attrs}
|
||||
class="wire-next wire-next--card {class}"
|
||||
data-size="{size}"
|
||||
data-color="{color}"
|
||||
data-variant="{variant}"
|
||||
data-layout="{layout}"
|
||||
data-align="{align}"
|
||||
data-hover="{hover}"
|
||||
data-image-position="{imagePosition}"
|
||||
data-group="{items.length > 0}"
|
||||
aria-label="{ariaLabel || title}"
|
||||
data-ui-component="Card"
|
||||
data-wrn-card
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-variant='{variant}'
|
||||
data-layout='{layout}'
|
||||
data-hover='{hover}'
|
||||
data-align='{align}'
|
||||
class='wire-next wire-next--card wire-next--color-{color} wire-next--size-{size} wire-next--variant-{variant} {class}'
|
||||
>
|
||||
{#if items.length > 0}
|
||||
<div class="wire-next__card-group">
|
||||
{#each items as item}
|
||||
<article class="wire-next__card-panel">
|
||||
{#if item.imageSrc}
|
||||
{#each items as item, itemIndex}
|
||||
<article
|
||||
class="wire-next__card-panel"
|
||||
aria-label='{item.ariaLabel || item.title || item.label || "Card item"}'
|
||||
data-index='{itemIndex}'
|
||||
>
|
||||
{#if item.imageSrc || item.image}
|
||||
<div class="wire-next__card-media">
|
||||
<img src="{item.imageSrc}" alt="{item.imageAlt || ''}" loading="lazy" />
|
||||
<img
|
||||
src='{item.imageSrc || item.image}'
|
||||
alt='{item.imageAlt || item.alt || ""}'
|
||||
loading='{item.loading || "lazy"}'
|
||||
decoding="async"
|
||||
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { item: item, index: itemIndex } }))'
|
||||
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { item: item, index: itemIndex } }))'
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="wire-next__card-body">
|
||||
{#if item.title}<h3>{item.title}</h3>{/if}
|
||||
{#if item.description}<p>{item.description}</p>{/if}
|
||||
|
||||
<div class="wire-next__card-content">
|
||||
{#if item.title || item.label}
|
||||
<h3>{item.title || item.label}</h3>
|
||||
{/if}
|
||||
{#if item.subtitle}
|
||||
<p class="wire-next__card-subtitle">{item.subtitle}</p>
|
||||
{/if}
|
||||
{#if item.description}
|
||||
<p class="wire-next__card-description">{item.description}</p>
|
||||
{/if}
|
||||
{#if item.actionLabel || item.href}
|
||||
<a
|
||||
href='{item.actionHref || item.href || "#"}'
|
||||
class="wire-next__card-action"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { item: item, index: itemIndex } }))'
|
||||
>
|
||||
<span>{item.actionLabel || "Learn more"}</span>
|
||||
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{#if item.footer}<footer class="wire-next__card-footer">{item.footer}</footer>{/if}
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if visible}
|
||||
<article class="wire-next__card-panel">
|
||||
{#if imagePosition === "overlay" && imageSrc}
|
||||
<div class="wire-next__card-media wire-next__card-media--overlay">
|
||||
{:else}
|
||||
<article
|
||||
class="wire-next__card-panel"
|
||||
aria-label='{ariaLabel || title || "Card"}'
|
||||
hidden='{dismissed}'
|
||||
>
|
||||
{#if imageSrc && imagePosition === "overlay"}
|
||||
<div class="wire-next__card-overlay">
|
||||
<img
|
||||
src="{imageSrc}"
|
||||
alt="{imageAlt}"
|
||||
src='{imageSrc}'
|
||||
alt='{imageAlt}'
|
||||
loading="lazy"
|
||||
@load="$emit('load', { src: imageSrc })"
|
||||
@error="$emit('error', { src: imageSrc })"
|
||||
decoding="async"
|
||||
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
/>
|
||||
<div class="wire-next__card-overlay">
|
||||
{#if title}<h3>{title}</h3>{/if}
|
||||
{#if subtitle}<span class="wire-next__card-subtitle">{subtitle}</span>{/if}
|
||||
{#if description}<p>{description}</p>{/if}
|
||||
{#if footer}<small>{footer}</small>{/if}
|
||||
</div>
|
||||
<div class="wire-next__card-overlay-shade" aria-hidden="true"></div>
|
||||
</div>
|
||||
{:else}
|
||||
{#if header || headerActions.length > 0 || dismissible}
|
||||
{/if}
|
||||
|
||||
{#if imageSrc && (imagePosition === "top" || imagePosition === "left")}
|
||||
<div class="wire-next__card-media wire-next__card-media--{imagePosition}">
|
||||
<img
|
||||
src='{imageSrc}'
|
||||
alt='{imageAlt}'
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="wire-next__card-content">
|
||||
{#if header || title || subtitle || headerActions.length > 0 || dismissible}
|
||||
<header class="wire-next__card-header">
|
||||
<span>{header}</span>
|
||||
<div class="wire-next__card-heading">
|
||||
{#if header}
|
||||
<p class="wire-next__card-eyebrow">{header}</p>
|
||||
{/if}
|
||||
{#if title}
|
||||
<h3>{title}</h3>
|
||||
{/if}
|
||||
{#if subtitle}
|
||||
<p class="wire-next__card-subtitle">{subtitle}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if headerActions.length > 0 || dismissible}
|
||||
<span class="wire-next__card-header-actions">
|
||||
{#each headerActions as action, index}
|
||||
<div class="wire-next__card-header-actions">
|
||||
{#each headerActions as headerAction, actionIndex}
|
||||
<button
|
||||
type="button"
|
||||
aria-label="{action.ariaLabel || action.label}"
|
||||
title="{action.label}"
|
||||
@click="chooseAction(action, index)"
|
||||
aria-label='{headerAction.ariaLabel || headerAction.label || "Card action"}'
|
||||
@click='dispatchCardHeaderAction(event, headerAction, actionIndex)'
|
||||
>
|
||||
{#if action.icon}<span class="{action.icon}" aria-hidden="true"></span>{/if}
|
||||
{#if !action.icon}<span>{action.label}</span>{/if}
|
||||
{#if headerAction.icon}
|
||||
<span class='{headerAction.icon}' aria-hidden="true"></span>
|
||||
{/if}
|
||||
{#if headerAction.label && !headerAction.icon}
|
||||
<span>{headerAction.label}</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
{#if dismissible}
|
||||
<button type="button" aria-label="Close card" @click="dismissCard(event)">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Dismiss card"
|
||||
@click='dismissCard(event)'
|
||||
>
|
||||
<span class="icon-[lucide--x]" aria-hidden="true"></span>
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
{/if}
|
||||
|
||||
{#if navigation.length > 0}
|
||||
<nav class="wire-next__card-navigation" aria-label="{ariaLabel || title}">
|
||||
<div class="wire-next__card-tabs">
|
||||
{#each navigation as item, index}
|
||||
<button
|
||||
type="button"
|
||||
data-active="{selectedNav === (item.value || item.label)}"
|
||||
aria-current="{selectedNav === (item.value || item.label) ? 'page' : ''}"
|
||||
@click="chooseNavigation(item, index)"
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<nav aria-label='{ariaLabel || title || "Card navigation"}' class="wire-next__card-navigation">
|
||||
{#if mobileNavigation}
|
||||
<select
|
||||
class="wire-next__card-navigation-select"
|
||||
aria-label="{ariaLabel || title}"
|
||||
value="{selectedNav}"
|
||||
@change="chooseMobileNavigation(event)"
|
||||
aria-label='{ariaLabel || title || "Card navigation"}'
|
||||
@change='dispatchCardNavigationValue(event)'
|
||||
>
|
||||
{#each navigation as item}
|
||||
{#each navigation as navItem}
|
||||
<option
|
||||
value="{item.value || item.label}"
|
||||
selected="{selectedNav === (item.value || item.label)}"
|
||||
value='{navItem.value || navItem.href || navItem.label}'
|
||||
selected='{activeNav === navItem.value || activeNav === navItem.href}'
|
||||
>
|
||||
{item.label}
|
||||
{navItem.label}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
|
||||
<div class="wire-next__card-tabs" data-mobile-navigation='{mobileNavigation}'>
|
||||
{#each navigation as navItem, navIndex}
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed='{activeNav === navItem.value || activeNav === navItem.href}'
|
||||
@click='dispatchCardNavigation(event, navItem, navIndex)'
|
||||
>
|
||||
{#if navItem.icon}
|
||||
<span class='{navItem.icon}' aria-hidden="true"></span>
|
||||
{/if}
|
||||
<span>{navItem.label}</span>
|
||||
{#if navItem.badge}
|
||||
<span class="wire-next__card-tab-badge">{navItem.badge}</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</nav>
|
||||
{/if}
|
||||
|
||||
{#if imageSrc && imagePosition !== "bottom"}
|
||||
<div class="wire-next__card-media">
|
||||
<img
|
||||
src="{imageSrc}"
|
||||
alt="{imageAlt}"
|
||||
loading="lazy"
|
||||
@load="$emit('load', { src: imageSrc })"
|
||||
@error="$emit('error', { src: imageSrc })"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if alertTitle || alertDescription}
|
||||
<aside class="wire-next__card-alert" role="status">
|
||||
{#if alertTitle}<strong>{alertTitle}</strong>{/if}
|
||||
{#if alertDescription}<span>{alertDescription}</span>{/if}
|
||||
</aside>
|
||||
<div class="wire-next__card-alert" role="status">
|
||||
{#if alertTitle}
|
||||
<strong>{alertTitle}</strong>
|
||||
{/if}
|
||||
{#if alertDescription}
|
||||
<p>{alertDescription}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="wire-next__card-body"
|
||||
data-scrollable="{scrollable}"
|
||||
style="--wire-card-max-height: {maxHeight}"
|
||||
data-scrollable='{scrollable}'
|
||||
style='max-height: {scrollable ? maxHeight : "none"};'
|
||||
>
|
||||
{#if empty}
|
||||
<div class="wire-next__card-empty">
|
||||
<span class="{emptyIcon}" aria-hidden="true"></span>
|
||||
<strong>{emptyTitle}</strong>
|
||||
<span class='{emptyIcon}' aria-hidden="true"></span>
|
||||
<p>{emptyTitle}</p>
|
||||
</div>
|
||||
{:else}
|
||||
{#if title}<h3>{title}</h3>{/if}
|
||||
{#if subtitle}<span class="wire-next__card-subtitle">{subtitle}</span>{/if}
|
||||
{#if description}<p>{description}</p>{/if}
|
||||
<slot />
|
||||
{#if actionLabel}
|
||||
<a
|
||||
class="wire-next__card-action"
|
||||
href="{actionHref || '#'}"
|
||||
@click="$emit('action', { label: actionLabel, href: actionHref })"
|
||||
>
|
||||
{actionLabel}<span class="icon-[lucide--chevron-right]" aria-hidden="true"></span>
|
||||
</a>
|
||||
{#if description}
|
||||
<p class="wire-next__card-description">{description}</p>
|
||||
{/if}
|
||||
<slot></slot>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if footer}<footer class="wire-next__card-footer">{footer}</footer>{/if}
|
||||
|
||||
{#if imageSrc && imagePosition === "bottom"}
|
||||
<div class="wire-next__card-media">
|
||||
<img
|
||||
src="{imageSrc}"
|
||||
alt="{imageAlt}"
|
||||
loading="lazy"
|
||||
@load="$emit('load', { src: imageSrc })"
|
||||
@error="$emit('error', { src: imageSrc })"
|
||||
/>
|
||||
</div>
|
||||
{#if footer || actionLabel}
|
||||
<footer class="wire-next__card-footer">
|
||||
{#if footer}
|
||||
<span>{footer}</span>
|
||||
{/if}
|
||||
{#if actionLabel}
|
||||
<a
|
||||
href='{actionHref || "#"}'
|
||||
class="wire-next__card-action"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { href: actionHref, label: actionLabel } }))'
|
||||
>
|
||||
<span>{actionLabel}</span>
|
||||
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
|
||||
</a>
|
||||
{/if}
|
||||
</footer>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if imageSrc && (imagePosition === "bottom" || imagePosition === "right")}
|
||||
<div class="wire-next__card-media wire-next__card-media--{imagePosition}">
|
||||
<img
|
||||
src='{imageSrc}'
|
||||
alt='{imageAlt}'
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</article>
|
||||
{/if}
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,40 @@ component Container {
|
||||
maxWidth = "xl"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--container wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div>
|
||||
<div
|
||||
data-ui-component="Container"
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-columns='{columns}'
|
||||
data-gap='{gap}'
|
||||
data-max-width='{maxWidth}'
|
||||
class='mx-auto w-full {class}'
|
||||
class:max-w-3xl='maxWidth === "md"'
|
||||
class:max-w-5xl='maxWidth === "lg"'
|
||||
class:max-w-7xl='maxWidth === "xl" || maxWidth === "wide"'
|
||||
class:max-w-screen-2xl='maxWidth === "2xl"'
|
||||
class:max-w-none='maxWidth === "full"'
|
||||
class:px-4='size === "compact" || size === "default"'
|
||||
class:px-5='size === "comfortable"'
|
||||
class:px-6='size === "spacious"'
|
||||
class:sm:px-6='size !== "compact"'
|
||||
class:lg:px-8='size === "default" || size === "comfortable" || size === "spacious"'
|
||||
class:grid='columns > 1'
|
||||
class:grid-cols-1='columns > 1'
|
||||
class:sm:grid-cols-2='columns === 2 || columns === 3 || columns === 4 || columns === 5 || columns === 6'
|
||||
class:lg:grid-cols-3='columns === 3'
|
||||
class:lg:grid-cols-4='columns === 4'
|
||||
class:lg:grid-cols-5='columns === 5'
|
||||
class:lg:grid-cols-6='columns === 6'
|
||||
class:gap-2='gap === "xs"'
|
||||
class:gap-3='gap === "sm"'
|
||||
class:gap-5='gap === "md"'
|
||||
class:gap-8='gap === "lg"'
|
||||
class:gap-10='gap === "xl"'
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,64 @@ component Divider {
|
||||
orientation = "horizontal"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--divider wire-next--divider-{orientation} {class}" role="separator" aria-orientation="{orientation}">{#if label}<span>{label}</span>{/if}</div>
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="Divider"
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-orientation='{orientation}'
|
||||
class='w-full {class}'
|
||||
class:h-full='orientation === "vertical"'
|
||||
class:w-auto='orientation === "vertical"'
|
||||
>
|
||||
{#if orientation === "vertical"}
|
||||
<div
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
class="mx-3 h-full min-h-6 border-l border-[var(--wire-color-border)]"
|
||||
class:border-l-2='size === "lg"'
|
||||
class:border-[var(--wire-color-primary)]='color === "primary"'
|
||||
class:border-[var(--wire-color-secondary)]='color === "secondary"'
|
||||
class:border-[var(--wire-color-success)]='color === "success"'
|
||||
class:border-[var(--wire-color-warning)]='color === "warning"'
|
||||
class:border-[var(--wire-color-danger)]='color === "danger"'
|
||||
></div>
|
||||
{:else}
|
||||
<div
|
||||
role="separator"
|
||||
aria-orientation="horizontal"
|
||||
class="flex w-full items-center gap-4"
|
||||
>
|
||||
<span
|
||||
class="h-px flex-1 bg-[var(--wire-color-border)]"
|
||||
class:h-0.5='size === "lg"'
|
||||
class:bg-[var(--wire-color-primary)]='color === "primary" && label !== ""'
|
||||
class:bg-[var(--wire-color-secondary)]='color === "secondary" && label !== ""'
|
||||
class:bg-[var(--wire-color-success)]='color === "success" && label !== ""'
|
||||
class:bg-[var(--wire-color-warning)]='color === "warning" && label !== ""'
|
||||
class:bg-[var(--wire-color-danger)]='color === "danger" && label !== ""'
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
|
||||
{#if label}
|
||||
<span class="shrink-0 text-xs font-bold uppercase tracking-[0.16em] text-[var(--wire-color-text-muted)]">
|
||||
{label}
|
||||
</span>
|
||||
<span
|
||||
class="h-px flex-1 bg-[var(--wire-color-border)]"
|
||||
class:h-0.5='size === "lg"'
|
||||
class:bg-[var(--wire-color-primary)]='color === "primary"'
|
||||
class:bg-[var(--wire-color-secondary)]='color === "secondary"'
|
||||
class:bg-[var(--wire-color-success)]='color === "success"'
|
||||
class:bg-[var(--wire-color-warning)]='color === "warning"'
|
||||
class:bg-[var(--wire-color-danger)]='color === "danger"'
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,34 @@ component Grid {
|
||||
maxWidth = "xl"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--grid wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div>
|
||||
<div
|
||||
data-ui-component="Grid"
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-columns='{columns}'
|
||||
data-gap='{gap}'
|
||||
class='grid w-full grid-cols-1 {class}'
|
||||
class:max-w-3xl='maxWidth === "md"'
|
||||
class:max-w-5xl='maxWidth === "lg"'
|
||||
class:max-w-7xl='maxWidth === "xl"'
|
||||
class:max-w-screen-2xl='maxWidth === "2xl"'
|
||||
class:max-w-none='maxWidth === "full"'
|
||||
class:sm:grid-cols-2='columns >= 2'
|
||||
class:lg:grid-cols-3='columns === 3'
|
||||
class:lg:grid-cols-4='columns === 4'
|
||||
class:lg:grid-cols-5='columns === 5'
|
||||
class:lg:grid-cols-6='columns === 6'
|
||||
class:gap-2='gap === "xs"'
|
||||
class:gap-3='gap === "sm"'
|
||||
class:gap-5='gap === "md"'
|
||||
class:gap-8='gap === "lg"'
|
||||
class:gap-10='gap === "xl"'
|
||||
class:items-start='size === "compact"'
|
||||
class:items-stretch='size !== "compact"'
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
const componentsDir = join(import.meta.dir, "..", "components");
|
||||
|
||||
const replacementComponents = [
|
||||
"Container",
|
||||
"Grid",
|
||||
"Columns",
|
||||
"Card",
|
||||
"Link",
|
||||
"Image",
|
||||
"SearchBox",
|
||||
"Map",
|
||||
"List",
|
||||
"Marquee",
|
||||
"Tabs",
|
||||
"Timeline",
|
||||
"Footer",
|
||||
"Typography",
|
||||
"Divider",
|
||||
] as const;
|
||||
|
||||
const newComponents = [
|
||||
"Section",
|
||||
"SectionHeader",
|
||||
"MarketingSectionHeader",
|
||||
"PageHeader",
|
||||
"TextLink",
|
||||
"FeatureGrid",
|
||||
"FeatureCard",
|
||||
"FeatureIconCard",
|
||||
"Hero",
|
||||
"SplitHero",
|
||||
"HeroActions",
|
||||
"AnnouncementBar",
|
||||
"MetricCard",
|
||||
"MetricGrid",
|
||||
"StatsBar",
|
||||
"CTASection",
|
||||
"BackToTop",
|
||||
"PublicPageShell",
|
||||
] as const;
|
||||
|
||||
const allComponents = [...replacementComponents, ...newComponents];
|
||||
|
||||
function openingTags(source: string): string[] {
|
||||
const tags: string[] = [];
|
||||
let index = 0;
|
||||
|
||||
while (index < source.length) {
|
||||
const start = source.indexOf("<", index);
|
||||
if (start === -1) break;
|
||||
|
||||
let cursor = start + 1;
|
||||
let quote: "'" | '"' | null = null;
|
||||
|
||||
while (cursor < source.length) {
|
||||
const char = source[cursor];
|
||||
if (quote) {
|
||||
if (char === quote) quote = null;
|
||||
} else if (char === "'" || char === '"') {
|
||||
quote = char;
|
||||
} else if (char === ">") {
|
||||
tags.push(source.slice(start, cursor + 1));
|
||||
cursor += 1;
|
||||
break;
|
||||
}
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
index = cursor;
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
describe("@wrnexus/ui redesign component pack", () => {
|
||||
test("contains every replacement and new component", async () => {
|
||||
const files = new Set(await readdir(componentsDir));
|
||||
|
||||
for (const name of allComponents) {
|
||||
expect(files.has(`${name}.wrn`)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
for (const name of allComponents) {
|
||||
test(`${name} has a valid component shell`, async () => {
|
||||
const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");
|
||||
|
||||
expect(source).toContain(`component ${name} {`);
|
||||
expect(source).toContain("props {");
|
||||
expect(source).toContain("view {");
|
||||
expect(source).toContain(`data-ui-component="${name}"`);
|
||||
expect(source).not.toContain("React");
|
||||
expect(source).not.toContain("useState");
|
||||
expect(source).not.toContain("<script");
|
||||
});
|
||||
}
|
||||
|
||||
test("opening tags do not contain duplicate class attributes", async () => {
|
||||
for (const name of allComponents) {
|
||||
const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");
|
||||
|
||||
for (const tag of openingTags(source)) {
|
||||
const plainClassAttributes = tag.match(/(?<![:\\w-])class\\s*=/g) ?? [];
|
||||
expect(plainClassAttributes.length).toBeLessThanOrEqual(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("dynamic attribute expressions stay single quoted", async () => {
|
||||
for (const name of allComponents) {
|
||||
const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");
|
||||
expect(source).not.toMatch(/=\\"\{[^\n]*\}\\"/);
|
||||
}
|
||||
});
|
||||
|
||||
test("existing public defaults remain release compatible", async () => {
|
||||
const expectations: Record<string, string[]> = {
|
||||
Container: [
|
||||
'size = "default"',
|
||||
'color = "primary"',
|
||||
"columns = 2",
|
||||
'gap = "md"',
|
||||
'maxWidth = "xl"',
|
||||
],
|
||||
Grid: [
|
||||
'size = "default"',
|
||||
'color = "primary"',
|
||||
"columns = 2",
|
||||
'gap = "md"',
|
||||
'maxWidth = "xl"',
|
||||
],
|
||||
Columns: [
|
||||
'size = "default"',
|
||||
'color = "primary"',
|
||||
"columns = 2",
|
||||
'gap = "md"',
|
||||
'maxWidth = "xl"',
|
||||
],
|
||||
Link: ['label = "Link"', 'href = "#"', "external = false"],
|
||||
Image: ['src = ""', 'alt = ""', 'loading = "lazy"', "rounded = false"],
|
||||
SearchBox: [
|
||||
'label = "Search Box"',
|
||||
'type = "search"',
|
||||
"disabled = false",
|
||||
"required = false",
|
||||
],
|
||||
Map: ['title = "Map"', "items = []", 'variant = "default"'],
|
||||
List: ['title = "List"', "items = []", 'variant = "default"'],
|
||||
Marquee: ['title = "Marquee"', "items = []", 'variant = "default"'],
|
||||
Tabs: ['label = "Tabs"', "items = []", 'active = ""', 'orientation = "horizontal"'],
|
||||
Timeline: ['title = "Timeline"', "items = []", 'variant = "default"'],
|
||||
Footer: ['label = "Footer navigation"', "items = []", "columns = 3", 'maxWidth = "compact"'],
|
||||
Typography: [
|
||||
'size = "default"',
|
||||
'color = "primary"',
|
||||
"columns = 2",
|
||||
'gap = "md"',
|
||||
'maxWidth = "xl"',
|
||||
],
|
||||
Divider: ['label = ""', 'orientation = "horizontal"'],
|
||||
};
|
||||
|
||||
for (const [name, tokens] of Object.entries(expectations)) {
|
||||
const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");
|
||||
for (const token of tokens) expect(source).toContain(token);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,7 @@ test("generated component reference documents every bundled component and its pr
|
||||
readFileSync(join(uiComponentsDir(), "..", "component-catalog.json"), "utf8"),
|
||||
) as { components: Array<{ name: string }> };
|
||||
expect(reference.count).toBe(uiComponentNames().length);
|
||||
expect(reference.count).toBe(90);
|
||||
expect(reference.count).toBe(108);
|
||||
expect(reference.components.map((component) => component.name)).toEqual(
|
||||
expect.arrayContaining(uiComponentNames()),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
Reference in New Issue
Block a user