release: WRNexusJS 0.6.0

This commit is contained in:
2026-08-01 01:09:58 +05:30
parent 3e565e8d03
commit 687d345882
502 changed files with 33038 additions and 11358 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/authz — part of the WrNexus framework.", "description": "@wrnexus/authz — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+13 -13
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/cli — part of the WrNexus framework.", "description": "@wrnexus/cli — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -28,18 +28,18 @@
"wrnexus": "./dist/index.js" "wrnexus": "./dist/index.js"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.14", "@wrnexus/core": "^0.6.0",
"@wrnexus/router": "^0.5.14", "@wrnexus/router": "^0.6.0",
"@wrnexus/csr": "^0.5.14", "@wrnexus/csr": "^0.6.0",
"@wrnexus/compiler": "^0.5.14", "@wrnexus/compiler": "^0.6.0",
"@wrnexus/styles": "^0.5.14", "@wrnexus/styles": "^0.6.0",
"@wrnexus/dev-server": "^0.5.14", "@wrnexus/dev-server": "^0.6.0",
"@wrnexus/ui": "^0.5.14", "@wrnexus/ui": "^0.6.0",
"@wrnexus/validation": "^0.5.14", "@wrnexus/validation": "^0.6.0",
"@wrnexus/i18n": "^0.5.14", "@wrnexus/i18n": "^0.6.0",
"@wrnexus/db": "^0.5.14", "@wrnexus/db": "^0.6.0",
"@wrnexus/plugin": "^0.5.14", "@wrnexus/plugin": "^0.6.0",
"@wrnexus/syntax": "^0.5.14" "@wrnexus/syntax": "^0.6.0"
}, },
"files": [ "files": [
"dist" "dist"
+3 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/compiler — part of the WrNexus framework.", "description": "@wrnexus/compiler — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/syntax": "^0.5.14" "@wrnexus/syntax": "^0.6.0",
"@wrnexus/store": "^0.6.0"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/core — part of the WrNexus framework.", "description": "@wrnexus/core — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/csr — part of the WrNexus framework.", "description": "@wrnexus/csr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.14" "@wrnexus/core": "^0.6.0"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/db — part of the WrNexus framework.", "description": "@wrnexus/db — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+16 -15
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/dev-server — part of the WrNexus framework.", "description": "@wrnexus/dev-server — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -25,20 +25,21 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.14", "@wrnexus/core": "^0.6.0",
"@wrnexus/dev-toolbar": "^0.5.14", "@wrnexus/dev-toolbar": "^0.6.0",
"@wrnexus/router": "^0.5.14", "@wrnexus/router": "^0.6.0",
"@wrnexus/ssr": "^0.5.14", "@wrnexus/ssr": "^0.6.0",
"@wrnexus/csr": "^0.5.14", "@wrnexus/csr": "^0.6.0",
"@wrnexus/compiler": "^0.5.14", "@wrnexus/compiler": "^0.6.0",
"@wrnexus/styles": "^0.5.14", "@wrnexus/styles": "^0.6.0",
"@wrnexus/ui": "^0.5.14", "@wrnexus/ui": "^0.6.0",
"@wrnexus/validation": "^0.5.14", "@wrnexus/validation": "^0.6.0",
"@wrnexus/i18n": "^0.5.14", "@wrnexus/i18n": "^0.6.0",
"@wrnexus/db": "^0.5.14", "@wrnexus/db": "^0.6.0",
"@wrnexus/pubsub": "^0.5.14", "@wrnexus/pubsub": "^0.6.0",
"@wrnexus/uploader": "^0.5.14", "@wrnexus/uploader": "^0.6.0",
"@wrnexus/plugin": "^0.5.14" "@wrnexus/plugin": "^0.6.0",
"@wrnexus/store": "^0.6.0"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/dev-toolbar — part of the WrNexus framework.", "description": "@wrnexus/dev-toolbar — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/encryption — part of the WrNexus framework.", "description": "@wrnexus/encryption — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.14" "@wrnexus/core": "^0.6.0"
}, },
"files": [ "files": [
"dist" "dist"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/i18n — part of the WrNexus framework.", "description": "@wrnexus/i18n — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.14" "@wrnexus/core": "^0.6.0"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/jwt — part of the WrNexus framework.", "description": "@wrnexus/jwt — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/mobile — part of the WrNexus framework.", "description": "@wrnexus/mobile — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/native": "^0.5.14" "@wrnexus/native": "^0.6.0"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/native — part of the WrNexus framework.", "description": "@wrnexus/native — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/oauth — part of the WrNexus framework.", "description": "@wrnexus/oauth — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/plugin", "name": "@wrnexus/plugin",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/plugin — part of the WrNexus framework.", "description": "@wrnexus/plugin — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -33,7 +33,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/syntax": "^0.5.14" "@wrnexus/syntax": "^0.6.0"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/pubsub — part of the WrNexus framework.", "description": "@wrnexus/pubsub — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/queue — part of the WrNexus framework.", "description": "@wrnexus/queue — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/reactive — part of the WrNexus framework.", "description": "@wrnexus/reactive — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/router — part of the WrNexus framework.", "description": "@wrnexus/router — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,8 +21,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/compiler": "^0.5.14", "@wrnexus/compiler": "^0.6.0",
"@wrnexus/core": "^0.5.14" "@wrnexus/core": "^0.6.0"
}, },
"files": [ "files": [
"dist" "dist"
+11 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/ssr — part of the WrNexus framework.", "description": "@wrnexus/ssr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -18,10 +18,19 @@
".": { ".": {
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/index.js" "import": "./dist/index.js"
},
"./rpc": {
"types": "./dist/rpc.d.ts",
"import": "./dist/rpc.js"
},
"./store-context": {
"types": "./dist/store-context.d.ts",
"import": "./dist/store-context.js"
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.14" "@wrnexus/core": "^0.6.0",
"@wrnexus/store": "^0.6.0"
}, },
"files": [ "files": [
"dist" "dist"
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/styles — part of the WrNexus framework.", "description": "@wrnexus/styles — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,9 +21,9 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/uploader": "^0.5.14", "@wrnexus/uploader": "^0.6.0",
"@wrnexus/core": "^0.5.14", "@wrnexus/core": "^0.6.0",
"@wrnexus/plugin": "^0.5.14" "@wrnexus/plugin": "^0.6.0"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/syntax — part of the WrNexus framework.", "description": "@wrnexus/syntax — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/test — part of the WrNexus framework.", "description": "@wrnexus/test — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.5.14", "version": "0.6.0",
"type": "module", "type": "module",
"description": "@wrnexus/tracking — part of the WrNexus framework.", "description": "@wrnexus/tracking — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+348 -344
View File
File diff suppressed because it is too large Load Diff
+67 -71
View File
@@ -19,8 +19,7 @@
{ {
"name": "AdvancedSelect", "name": "AdvancedSelect",
"category": "advanced-forms", "category": "advanced-forms",
"purpose": "Theme-aware, responsive advanced select component.", "purpose": "Theme-aware, responsive advanced select component."
"events": ["search", "select", "change", "clear", "open", "close", "load", "error"]
}, },
{ {
"name": "Alert", "name": "Alert",
@@ -29,18 +28,18 @@
}, },
{ {
"name": "AnnouncementBar", "name": "AnnouncementBar",
"category": "blocks", "category": "marketing",
"purpose": "Accessible public announcement, emergency, maintenance, or status bar." "purpose": "Publish a responsive notice with badge, icon, supporting copy, action, dismiss behavior, and width controls."
}, },
{ {
"name": "AuthForm", "name": "AuthForm",
"category": "forms", "category": "core",
"purpose": "Reusable authentication form for sign-in, registration, recovery, reset, and MFA." "purpose": "Reusable auth form component."
}, },
{ {
"name": "AuthSplitLayout", "name": "AuthSplitLayout",
"category": "layout", "category": "core",
"purpose": "Responsive 50/50 authentication layout with content and form regions." "purpose": "Reusable auth split layout component."
}, },
{ {
"name": "Avatar", "name": "Avatar",
@@ -55,7 +54,7 @@
{ {
"name": "BackToTop", "name": "BackToTop",
"category": "navigation", "category": "navigation",
"purpose": "Accessible scroll-to-top control with visibility threshold." "purpose": "Provide a responsive floating control that returns long pages to the top and can show scroll progress."
}, },
{ {
"name": "Badge", "name": "Badge",
@@ -70,7 +69,7 @@
{ {
"name": "Breadcrumb", "name": "Breadcrumb",
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive breadcrumb component." "purpose": "Show responsive hierarchical navigation with home support, separators, current-page state, sizes, and selection events."
}, },
{ {
"name": "Button", "name": "Button",
@@ -85,7 +84,7 @@
{ {
"name": "Card", "name": "Card",
"category": "base", "category": "base",
"purpose": "Theme-aware, responsive card component." "purpose": "Group related content in a responsive themed surface with title, description, content, and supporting slots."
}, },
{ {
"name": "Carousel", "name": "Carousel",
@@ -125,13 +124,12 @@
{ {
"name": "Columns", "name": "Columns",
"category": "layout", "category": "layout",
"purpose": "Theme-aware, responsive columns component." "purpose": "Create responsive balanced content columns with configurable count, gap, density, and maximum width."
}, },
{ {
"name": "ComboBox", "name": "ComboBox",
"category": "advanced-forms", "category": "advanced-forms",
"purpose": "Editable autocomplete combobox with local and remote suggestions.", "purpose": "Editable autocomplete combobox with local and remote suggestions."
"events": ["search", "select", "change", "clear", "open", "close", "load", "error"]
}, },
{ {
"name": "Confetti", "name": "Confetti",
@@ -141,12 +139,12 @@
{ {
"name": "Container", "name": "Container",
"category": "layout", "category": "layout",
"purpose": "Theme-aware, responsive container component." "purpose": "Constrain and align page content with responsive gutters and compact, wide, or full width options."
}, },
{ {
"name": "ContextMenu", "name": "ContextMenu",
"category": "overlays", "category": "overlays",
"purpose": "Theme-aware, responsive context menu component." "purpose": "Open an accessible keyboard-aware action menu from pointer or keyboard context interactions."
}, },
{ {
"name": "CopyMarkup", "name": "CopyMarkup",
@@ -155,8 +153,8 @@
}, },
{ {
"name": "CTASection", "name": "CTASection",
"category": "blocks", "category": "marketing",
"purpose": "Reusable closing call-to-action section with primary and secondary actions." "purpose": "Close a page or major section with conversion-focused copy, actions, and optional supporting visual content."
}, },
{ {
"name": "CustomScrollbar", "name": "CustomScrollbar",
@@ -186,7 +184,7 @@
{ {
"name": "Divider", "name": "Divider",
"category": "layout", "category": "layout",
"purpose": "Theme-aware, responsive divider component." "purpose": "Separate related horizontal or vertical content with optional labels, sizes, and semantic colors."
}, },
{ {
"name": "DragAndDrop", "name": "DragAndDrop",
@@ -196,27 +194,27 @@
{ {
"name": "Drawer", "name": "Drawer",
"category": "overlays", "category": "overlays",
"purpose": "Theme-aware, responsive drawer component." "purpose": "Present responsive modal side or bottom content with focus management, backdrop behavior, slots, and close events."
}, },
{ {
"name": "Dropdown", "name": "Dropdown",
"category": "overlays", "category": "overlays",
"purpose": "Theme-aware, responsive dropdown component." "purpose": "Open an accessible anchored menu with keyboard navigation, item selection, actions, and responsive placement."
}, },
{ {
"name": "FeatureCard", "name": "FeatureCard",
"category": "blocks", "category": "marketing",
"purpose": "Reusable linked feature or service card with icon, badge, description, and action." "purpose": "Present one linked feature or service with media, icon, badge, description, and action."
}, },
{ {
"name": "FeatureGrid", "name": "FeatureGrid",
"category": "layout", "category": "layout",
"purpose": "Responsive equal-height grid for feature and service cards." "purpose": "Arrange feature cards, services, solutions, or benefits in a responsive equal-height grid."
}, },
{ {
"name": "FeatureIconCard", "name": "FeatureIconCard",
"category": "blocks", "category": "marketing",
"purpose": "Feature card with a prominent themed icon treatment." "purpose": "Present a compact feature or benefit with a styled icon, title, description, badge, and optional link."
}, },
{ {
"name": "FileInput", "name": "FileInput",
@@ -235,28 +233,28 @@
}, },
{ {
"name": "Footer", "name": "Footer",
"category": "navigation", "category": "layout",
"purpose": "Responsive application footer with structured links and pre/post content slots." "purpose": "Render structured responsive footer navigation, pre and post content, copyright content, links, and public events."
}, },
{ {
"name": "Grid", "name": "Grid",
"category": "layout", "category": "layout",
"purpose": "Theme-aware, responsive grid component." "purpose": "Arrange arbitrary content in a responsive configurable CSS grid with stable columns, gaps, alignment, and width."
}, },
{ {
"name": "Hero", "name": "Hero",
"category": "blocks", "category": "marketing",
"purpose": "Reusable public-page hero with actions, trust items, badges, and visual slots." "purpose": "Build a full-width responsive hero with constrained content, actions, trust signals, and a structured or custom visual panel."
}, },
{ {
"name": "HeroActions", "name": "HeroActions",
"category": "base", "category": "marketing",
"purpose": "Responsive action group for hero and call-to-action sections." "purpose": "Group hero and campaign actions with consistent alignment, orientation, sizing, and responsive mobile stacking."
}, },
{ {
"name": "Image", "name": "Image",
"category": "layout", "category": "layout",
"purpose": "Theme-aware, responsive image component." "purpose": "Render a responsive image with explicit dimensions, loading behavior, alternative text, sizing, and rounded treatment."
}, },
{ {
"name": "Input", "name": "Input",
@@ -291,12 +289,12 @@
{ {
"name": "Link", "name": "Link",
"category": "layout", "category": "layout",
"purpose": "Theme-aware, responsive link component." "purpose": "Render an accessible internal or external link with target, relation, size, color, and public focus or click events."
}, },
{ {
"name": "List", "name": "List",
"category": "base", "category": "base",
"purpose": "Theme-aware, responsive list component." "purpose": "Present structured responsive linked or status items with icons, descriptions, actions, and selection events."
}, },
{ {
"name": "ListGroup", "name": "ListGroup",
@@ -306,17 +304,17 @@
{ {
"name": "Map", "name": "Map",
"category": "integrations", "category": "integrations",
"purpose": "Theme-aware, responsive map component." "purpose": "Present responsive location information and markers with map-ready metadata and movement or marker events."
}, },
{ {
"name": "MarketingSectionHeader", "name": "MarketingSectionHeader",
"category": "blocks", "category": "marketing",
"purpose": "Marketing section heading with an optional linked action." "purpose": "Introduce marketing content with an eyebrow, title, description, and optional linked action."
}, },
{ {
"name": "Marquee", "name": "Marquee",
"category": "base", "category": "base",
"purpose": "Theme-aware, responsive marquee component." "purpose": "Continuously present responsive labels, partners, notices, or capabilities with pause and resume behavior."
}, },
{ {
"name": "MegaMenu", "name": "MegaMenu",
@@ -325,18 +323,18 @@
}, },
{ {
"name": "MetricCard", "name": "MetricCard",
"category": "blocks", "category": "data",
"purpose": "Single statistic or operational metric with icon, trend, and optional action." "purpose": "Display one operational metric with value, suffix, description, icon, trend, progress, and optional action."
}, },
{ {
"name": "MetricGrid", "name": "MetricGrid",
"category": "layout", "category": "data",
"purpose": "Responsive collection of metric cards." "purpose": "Arrange operational metrics, KPIs, public statistics, or service indicators in a responsive equal-height grid."
}, },
{ {
"name": "Modal", "name": "Modal",
"category": "overlays", "category": "overlays",
"purpose": "Theme-aware, responsive modal component." "purpose": "Present an accessible modal dialog with focus management, confirmation, cancellation, slots, and responsive sizing."
}, },
{ {
"name": "Nav", "name": "Nav",
@@ -350,8 +348,8 @@
}, },
{ {
"name": "PageHeader", "name": "PageHeader",
"category": "blocks", "category": "marketing",
"purpose": "Public and application page header with breadcrumbs, metadata, and actions." "purpose": "Introduce an internal or public page with breadcrumbs, icon, title, description, and primary or secondary actions."
}, },
{ {
"name": "Pagination", "name": "Pagination",
@@ -361,23 +359,22 @@
{ {
"name": "PinInput", "name": "PinInput",
"category": "advanced-forms", "category": "advanced-forms",
"purpose": "Secure multi-cell PIN and verification-code input with regex and paste support.", "purpose": "Secure multi-cell PIN and verification-code input with regex and paste support."
"events": ["input", "change", "complete", "paste", "clear", "error"]
}, },
{ {
"name": "Popover", "name": "Popover",
"category": "overlays", "category": "overlays",
"purpose": "Theme-aware, responsive popover component." "purpose": "Display anchored supporting content with configurable trigger, placement, responsive sizing, and open or close events."
}, },
{ {
"name": "PortalDashboard", "name": "PortalDashboard",
"category": "blocks", "category": "core",
"purpose": "Responsive portal dashboard with metrics, actions, tasks, and updates." "purpose": "Reusable portal dashboard component."
}, },
{ {
"name": "PreferenceSwitcher", "name": "PreferenceSwitcher",
"category": "navigation", "category": "core",
"purpose": "Accessible theme, accent color, and language preference controls." "purpose": "Reusable preference switcher component."
}, },
{ {
"name": "Progress", "name": "Progress",
@@ -387,7 +384,7 @@
{ {
"name": "PublicPageShell", "name": "PublicPageShell",
"category": "layout", "category": "layout",
"purpose": "Consistent structural wrapper for public-facing pages." "purpose": "Provide the outer responsive structure, width, background, slots, overflow, and minimum-height behavior for public pages."
}, },
{ {
"name": "Radio", "name": "Radio",
@@ -411,18 +408,18 @@
}, },
{ {
"name": "SearchBox", "name": "SearchBox",
"category": "advanced-forms", "category": "forms",
"purpose": "Theme-aware, responsive search box component." "purpose": "Provide an accessible responsive search field with labels, validation states, sizes, and input or change events."
}, },
{ {
"name": "Section", "name": "Section",
"category": "layout", "category": "layout",
"purpose": "Theme-aware page section with consistent spacing, width, background, and border controls." "purpose": "Create a responsive themed page section with controlled spacing, width, borders, and surface treatment."
}, },
{ {
"name": "SectionHeader", "name": "SectionHeader",
"category": "layout", "category": "layout",
"purpose": "Reusable accessible section heading with eyebrow, title, description, icon, and actions." "purpose": "Introduce a section with an eyebrow, title, description, alignment, and responsive heading hierarchy."
}, },
{ {
"name": "Select", "name": "Select",
@@ -446,13 +443,13 @@
}, },
{ {
"name": "SplitHero", "name": "SplitHero",
"category": "blocks", "category": "marketing",
"purpose": "Two-column hero with configurable content and visual placement." "purpose": "Build a responsive two-column introduction balancing descriptive content with a visual, image, or structured data panel."
}, },
{ {
"name": "StatsBar", "name": "StatsBar",
"category": "blocks", "category": "data",
"purpose": "Compact responsive statistics strip." "purpose": "Present a compact responsive strip of key facts, counts, performance indicators, or trust signals."
}, },
{ {
"name": "Stepper", "name": "Stepper",
@@ -482,7 +479,7 @@
{ {
"name": "Tabs", "name": "Tabs",
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive tabs component." "purpose": "Switch between related responsive content panels with horizontal or vertical orientation and selection events."
}, },
{ {
"name": "Textarea", "name": "Textarea",
@@ -491,13 +488,13 @@
}, },
{ {
"name": "TextLink", "name": "TextLink",
"category": "layout", "category": "marketing",
"purpose": "Styled inline action link with icons, arrows, external state, and disabled handling." "purpose": "Render an accessible text action with optional icon, arrow, underline, external state, and semantic styling."
}, },
{ {
"name": "Timeline", "name": "Timeline",
"category": "base", "category": "base",
"purpose": "Theme-aware, responsive timeline component." "purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata."
}, },
{ {
"name": "TimePicker", "name": "TimePicker",
@@ -522,13 +519,12 @@
{ {
"name": "TogglePassword", "name": "TogglePassword",
"category": "advanced-forms", "category": "advanced-forms",
"purpose": "Accessible password field with optional show and hide controls.", "purpose": "Accessible password field with optional show and hide controls."
"events": ["input", "change", "toggle"]
}, },
{ {
"name": "Tooltip", "name": "Tooltip",
"category": "overlays", "category": "overlays",
"purpose": "Theme-aware, responsive tooltip component." "purpose": "Show concise accessible contextual help on hover, focus, click, or controlled open state."
}, },
{ {
"name": "TreeView", "name": "TreeView",
@@ -538,7 +534,7 @@
{ {
"name": "Typography", "name": "Typography",
"category": "layout", "category": "layout",
"purpose": "Theme-aware, responsive typography component." "purpose": "Apply consistent readable responsive typography, widths, columns, spacing, and editorial hierarchy."
}, },
{ {
"name": "WysiwygEditor", "name": "WysiwygEditor",
File diff suppressed because it is too large Load Diff
+33 -30
View File
@@ -1,52 +1,55 @@
component Accordion { component Accordion {
props { outputs {
size = "default" change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
color = "primary" open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
variant = "default" close(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
class = ""
id = "accordion"
items = []
defaultOpen = []
multiple = false
alwaysOpen = false
disabled = false
indicator = "plus"
indicatorPosition = "start"
showIndicator = true
bordered = false
separated = false
flush = false
contentItalic = false
@event change = function
@event open = function
@event close = function
} }
props {
size: string = "default"
color: string = "primary"
variant: string = "default"
class: string = ""
id: string = "accordion"
items: unknown[] = []
defaultOpen: unknown[] = []
multiple: boolean = false
alwaysOpen: boolean = false
disabled: boolean = false
indicator: string = "plus"
indicatorPosition: string = "start"
showIndicator: boolean = true
bordered: boolean = false
separated: boolean = false
flush: boolean = false
contentItalic: boolean = false
}
state openValues = defaultOpen state openValues = defaultOpen
functions { functions {
function itemValue(item, index) { shared function itemValue(item, index) {
return item.value !== undefined && item.value !== "" return item.value !== undefined && item.value !== ""
? String(item.value) ? String(item.value)
: String(index) : String(index)
} }
function nestedValue(parent, parentIndex, item, index) { shared function nestedValue(parent, parentIndex, item, index) {
return itemValue(parent, parentIndex) + "." + itemValue(item, index) return itemValue(parent, parentIndex) + "." + itemValue(item, index)
} }
function isOpen(value) { shared function isOpen(value) {
return openValues.includes(value) return openValues.includes(value)
} }
function allowsMultiple() { shared function allowsMultiple() {
return multiple || alwaysOpen return multiple || alwaysOpen
} }
function dispatchAccordionEvent(sourceEvent, eventName, value, item, root, customEvent) { client function dispatchAccordionEvent(sourceEvent, eventName, value, item, root, customEvent) {
root = sourceEvent.currentTarget.closest("[data-wrn-accordion]") root = sourceEvent.currentTarget.closest("[data-wrn-accordion]")
if (!root) { if (!root) {
@@ -64,7 +67,7 @@ component Accordion {
root.dispatchEvent(customEvent) root.dispatchEvent(customEvent)
} }
function toggleItem(sourceEvent, value, item, wasOpen) { client function toggleItem(sourceEvent, value, item, wasOpen) {
if (disabled || item.disabled) { if (disabled || item.disabled) {
return return
} }
+15 -12
View File
@@ -1,17 +1,20 @@
component AdvancedDatePicker { component AdvancedDatePicker {
outputs {
input(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
close(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
clear(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
}
props { props {
@event input = function size: string = "default"
@event change = function color: string = "primary"
@event open = function title: string = "Advanced Date Picker"
@event close = function description: string = ""
@event clear = function items: unknown[] = []
size = "default" variant: string = "default"
color = "primary" class: string = ""
title = "Advanced Date Picker"
description = ""
items = []
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--advanced-date-picker wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--advanced-date-picker wire-next--variant-{variant} {class}">
+14 -11
View File
@@ -1,16 +1,19 @@
component AdvancedRangeSlider { component AdvancedRangeSlider {
outputs {
input(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
start(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
end(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event input = function size: string = "default"
@event change = function color: string = "primary"
@event start = function title: string = "Advanced Range Slider"
@event end = function description: string = ""
size = "default" items: unknown[] = []
color = "primary" variant: string = "default"
title = "Advanced Range Slider" class: string = ""
description = ""
items = []
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--advanced-range-slider wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--advanced-range-slider wire-next--variant-{variant} {class}">
+85 -82
View File
@@ -1,82 +1,85 @@
component AdvancedSelect { component AdvancedSelect {
outputs {
search(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
clear(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
close(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
load(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
error(payload: { error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)
}
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
label = "Advanced Select" label: string = "Advanced Select"
name = "" name: string = ""
value = "" value: string = ""
values = [] values: unknown[] = []
options = [] options: unknown[] = []
groups = [] groups: unknown[] = []
placeholder = "Select an option" placeholder: string = "Select an option"
placeholderIcon = "" placeholderIcon: string = ""
searchPlaceholder = "Search options…" searchPlaceholder: string = "Search options…"
multiple = false multiple: boolean = false
searchable = true searchable: boolean = true
defaultOpen = false defaultOpen: boolean = false
clearable = true clearable: boolean = true
allowEmpty = true allowEmpty: boolean = true
tags = false tags: boolean = false
disabled = false disabled: boolean = false
required = false required: boolean = false
invalid = false invalid: boolean = false
validationMessage = "" validationMessage: string = ""
helpText = "" helpText: string = ""
loading = false loading: boolean = false
loadingLabel = "Loading options…" loadingLabel: string = "Loading options…"
emptyLabel = "No options found" emptyLabel: string = "No options found"
selectedOptionsLabel = "Selected options" selectedOptionsLabel: string = "Selected options"
clearLabel = "Clear selection" clearLabel: string = "Clear selection"
createLabel = "Create" createLabel: string = "Create"
loadMoreLabel = "Load more" loadMoreLabel: string = "Load more"
searchMode = "contains" searchMode: string = "contains"
searchFields = "label,description" searchFields: string = "label,description"
minSearchLength = 0 minSearchLength: number = 0
searchResultLimit = 0 searchResultLimit: number = 0
maxSelections = 0 maxSelections: number = 0
showCounter = false showCounter: boolean = false
counterTemplate = "{selected} selected" counterTemplate: string = "{selected} selected"
optionTemplate = "default" optionTemplate: string = "default"
selectedTemplate = "default" selectedTemplate: string = "default"
closeOnSelect = true closeOnSelect: boolean = true
scrollToSelected = true scrollToSelected: boolean = true
fixed = false fixed: boolean = false
placement = "bottom" placement: string = "bottom"
remote = false remote: boolean = false
remoteUrl = "" remoteUrl: string = ""
remoteQueryParam = "q" remoteQueryParam: string = "q"
remoteDebounce = 250 remoteDebounce: number = 250
remoteAutoLoad = true remoteAutoLoad: boolean = true
infinite = false infinite: boolean = false
hasMore = false hasMore: boolean = false
page = 1 page: number = 1
class = "" class: string = ""
@event search = function
@event select = function
@event change = function
@event clear = function
@event open = function
@event close = function
@event load = function
@event error = function
} }
state open = defaultOpen state open = defaultOpen
state query = "" state query: string = ""
state activeIndex = -1 state activeIndex: number = -1
state selectedValue = value state selectedValue = value
state selectedValues = values state selectedValues = values
functions { functions {
function allOptions() { shared function allOptions() {
return [...groups.flatMap((group) => group.options || []), ...options] return [...groups.flatMap((group) => group.options || []), ...options]
} }
function searchableText(option) { shared function searchableText(option) {
return ((option.label || "") + " " + (option.description || "")).toLowerCase() return ((option.label || "") + " " + (option.description || "")).toLowerCase()
} }
function matches(option) { shared function matches(option) {
if (!query || query.length < Number(minSearchLength || 0)) { if (!query || query.length < Number(minSearchLength || 0)) {
return true return true
} }
@@ -89,22 +92,22 @@ component AdvancedSelect {
return searchableText(option).includes(query.toLowerCase()) return searchableText(option).includes(query.toLowerCase())
} }
function matchingOptions(list) { shared function matchingOptions(list) {
return list.filter((option) => matches(option)) return list.filter((option) => matches(option))
} }
function visibleOptions(list) { shared function visibleOptions(list) {
if (searchResultLimit > 0) { if (searchResultLimit > 0) {
return matchingOptions(list).slice(0, Number(searchResultLimit)) return matchingOptions(list).slice(0, Number(searchResultLimit))
} }
return matchingOptions(list) return matchingOptions(list)
} }
function flatVisibleOptions() { shared function flatVisibleOptions() {
return visibleOptions(allOptions()) return visibleOptions(allOptions())
} }
function isSelected(option) { shared function isSelected(option) {
return ( return (
multiple multiple
? selectedValues.includes(option.value) ? selectedValues.includes(option.value)
@@ -112,15 +115,15 @@ component AdvancedSelect {
) )
} }
function selectedOptions() { shared function selectedOptions() {
return allOptions().filter((option) => isSelected(option)) return allOptions().filter((option) => isSelected(option))
} }
function selectedCount() { shared function selectedCount() {
return multiple ? selectedValues.length : selectedValue ? 1 : 0 return multiple ? selectedValues.length : selectedValue ? 1 : 0
} }
function counterText() { shared function counterText() {
return ( return (
maxSelections maxSelections
? selectedCount() + " / " + maxSelections + " selected" ? selectedCount() + " / " + maxSelections + " selected"
@@ -128,7 +131,7 @@ component AdvancedSelect {
) )
} }
function selectedText() { shared function selectedText() {
return ( return (
multiple multiple
? selectedValues.join(", ") ? selectedValues.join(", ")
@@ -138,11 +141,11 @@ component AdvancedSelect {
) )
} }
function triggerText() { shared function triggerText() {
return selectedCount() ? selectedText() : placeholder return selectedCount() ? selectedText() : placeholder
} }
function canSelect(option) { shared function canSelect(option) {
if (option.disabled) { if (option.disabled) {
return false return false
} }
@@ -155,7 +158,7 @@ component AdvancedSelect {
return selectedCount() < maxSelections return selectedCount() < maxSelections
} }
function chooseSingle(option) { shared function chooseSingle(option) {
if (!canSelect(option)) { if (!canSelect(option)) {
return return
} }
@@ -166,7 +169,7 @@ component AdvancedSelect {
} }
} }
function chooseMultiple(option) { shared function chooseMultiple(option) {
if (!canSelect(option)) { if (!canSelect(option)) {
return return
} }
@@ -178,7 +181,7 @@ component AdvancedSelect {
query = "" query = ""
} }
function chooseOption(option) { shared function chooseOption(option) {
if (multiple) { if (multiple) {
chooseMultiple(option) chooseMultiple(option)
return return
@@ -186,7 +189,7 @@ component AdvancedSelect {
chooseSingle(option) chooseSingle(option)
} }
function clearSelection(event) { client function clearSelection(event) {
event.stopPropagation() event.stopPropagation()
selectedValue = "" selectedValue = ""
selectedValues = [] selectedValues = []
@@ -194,7 +197,7 @@ component AdvancedSelect {
open = false open = false
} }
function toggle() { shared function toggle() {
if (disabled) { if (disabled) {
return; return;
}; };
@@ -202,14 +205,14 @@ component AdvancedSelect {
activeIndex = open && flatVisibleOptions().length ? 0 : -1; activeIndex = open && flatVisibleOptions().length ? 0 : -1;
} }
function moveActive(direction) { shared function moveActive(direction) {
if (!flatVisibleOptions().length) { if (!flatVisibleOptions().length) {
return return
} }
activeIndex = (activeIndex + direction + flatVisibleOptions().length) % flatVisibleOptions().length activeIndex = (activeIndex + direction + flatVisibleOptions().length) % flatVisibleOptions().length
} }
function handleKeydown(event) { client function handleKeydown(event) {
if (disabled) { if (disabled) {
return return
} }
@@ -243,7 +246,7 @@ component AdvancedSelect {
} }
} }
function optionIndex(option) { shared function optionIndex(option) {
return flatVisibleOptions().findIndex((item) => item.value === option.value) return flatVisibleOptions().findIndex((item) => item.value === option.value)
} }
} }
+22 -19
View File
@@ -1,33 +1,36 @@
component AvatarGroup { component AvatarGroup {
props { outputs {
items = [] overflow(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
size = "md"
color = "primary"
variant = "solid"
shape = "circle"
layout = "stack"
maxVisible = 4
columns = 3
borderColor = ""
showTooltips = true
overflowLabel = "Show remaining members"
class = ""
@event overflow = function
} }
state overflowOpen = false props {
items: unknown[] = []
size: string = "md"
color: string = "primary"
variant: string = "solid"
shape: string = "circle"
layout: string = "stack"
maxVisible: number = 4
columns: number = 3
borderColor: string = ""
showTooltips: boolean = true
overflowLabel: string = "Show remaining members"
class: string = ""
}
state overflowOpen: boolean = false
functions { functions {
function visibleMembers() { shared function visibleMembers() {
return items.slice(0, Number(maxVisible)) return items.slice(0, Number(maxVisible))
} }
function hiddenMembers() { shared function hiddenMembers() {
return items.slice(Number(maxVisible)) return items.slice(Number(maxVisible))
} }
function toggleOverflow(sourceEvent, root, customEvent) { client function toggleOverflow(sourceEvent, root, customEvent) {
overflowOpen = !overflowOpen overflowOpen = !overflowOpen
root = sourceEvent.currentTarget.closest("[data-wrn-avatar-group]") root = sourceEvent.currentTarget.closest("[data-wrn-avatar-group]")
+13 -13
View File
@@ -1,18 +1,18 @@
component Blockquote { component Blockquote {
props { props {
quote = "I just wanted to say that I'm very happy with my purchase so far. The documentation is outstanding - clear and detailed." quote: string = "I just wanted to say that I'm very happy with my purchase so far. The documentation is outstanding - clear and detailed."
citation = "" citation: string = ""
citationTitle = "" citationTitle: string = ""
citationUrl = "" citationUrl: string = ""
avatarSrc = "" avatarSrc: string = ""
avatarAlt = "" avatarAlt: string = ""
size = "md" size: string = "md"
color = "primary" color: string = "primary"
align = "left" align: string = "left"
variant = "default" variant: string = "default"
quoteMark = true quoteMark: boolean = true
italic = true italic: boolean = true
class = "" class: string = ""
} }
view { view {
+317 -13
View File
@@ -1,19 +1,323 @@
component Breadcrumb { component Breadcrumb {
props { outputs {
@event navigate = function select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
@event click = function
size = "default"
color = "primary"
label = "Breadcrumb"
items = []
active = ""
orientation = "horizontal"
class = ""
} }
props {
label: string = "Breadcrumb"
items: unknown[] = []
active: string = ""
separator: string = "chevron"
showHome: boolean = false
homeLabel: string = "Home"
homeHref: string = "/"
homeIcon: string = "icon-[lucide--house]"
size: string = "default"
color: string = "primary"
variant: string = "minimal"
class: string = ""
}
view { view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--breadcrumb wire-next--{orientation} {class}" aria-label="{label}"> <nav
{#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each} {...attrs}
<slot /> data-ui-component="Breadcrumb"
data-size='{size}'
data-color='{color}'
data-variant='{variant}'
aria-label='{label}'
class='wire-breadcrumb {class}'
>
<ol class="wire-breadcrumb__list">
{#if showHome}
<li class="wire-breadcrumb__item wire-breadcrumb__item--home">
<a
href='{homeHref || "/"}'
class="wire-breadcrumb__link wire-breadcrumb__home"
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: { label: homeLabel, href: homeHref || "/", value: "home" }, itemIndex: -1 } }))'
>
{#if homeIcon === "icon-[lucide--house]"}
<span class="icon-[lucide--house] wire-breadcrumb__icon" aria-hidden="true"></span>
{:else if homeIcon}
<span class='{homeIcon} wire-breadcrumb__icon' aria-hidden="true"></span>
{/if}
<span class="wire-breadcrumb__home-label">{homeLabel}</span>
</a>
</li>
{/if}
{#each items as item, itemIndex}
<li
class="wire-breadcrumb__item"
data-current='{item.active || item.current || (active && active === (item.value || item.label || item.title)) || (!active && itemIndex === items.length - 1) ? "true" : "false"}'
>
{#if showHome || itemIndex > 0}
<span class="wire-breadcrumb__separator" aria-hidden="true">
{#if separator === "slash"}
<span>/</span>
{:else if separator === "dot"}
<span>•</span>
{:else if separator === "arrow"}
<span>→</span>
{:else}
<span class="icon-[lucide--chevron-right] wire-breadcrumb__separator-icon"></span>
{/if}
</span>
{/if}
{#if item.href && !item.disabled && !(item.active || item.current || (active && active === (item.value || item.label || item.title)) || (!active && itemIndex === items.length - 1))}
<a
href='{item.href}'
target='{item.target || ""}'
rel='{item.external ? "noopener noreferrer" : (item.rel || "")}'
class="wire-breadcrumb__link"
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, itemIndex: itemIndex } }))'
>
{#if item.icon}
<span class='{item.icon} wire-breadcrumb__icon' aria-hidden="true"></span>
{/if}
<span class="wire-breadcrumb__label">{item.label || item.title}</span>
{#if item.external}
<span class="icon-[lucide--arrow-up-right] wire-breadcrumb__external" aria-hidden="true"></span>
{/if}
</a>
{:else}
<span
class="wire-breadcrumb__current"
aria-current='{item.active || item.current || (active && active === (item.value || item.label || item.title)) || (!active && itemIndex === items.length - 1) ? "page" : ""}'
aria-disabled='{item.disabled ? "true" : "false"}'
>
{#if item.icon}
<span class='{item.icon} wire-breadcrumb__icon' aria-hidden="true"></span>
{/if}
<span class="wire-breadcrumb__label">{item.label || item.title}</span>
</span>
{/if}
</li>
{/each}
</ol>
</nav> </nav>
} }
style {
.wire-breadcrumb {
--breadcrumb-accent: var(--wire-color-primary);
--breadcrumb-soft: var(--wire-color-primary-soft);
--breadcrumb-muted: var(--wire-color-primary-muted);
--breadcrumb-text: var(--wire-color-primary-text);
min-width: 0;
color: var(--wire-color-text-muted);
}
.wire-breadcrumb[data-color="secondary"] {
--breadcrumb-accent: var(--wire-color-secondary);
--breadcrumb-soft: var(--wire-color-secondary-soft);
--breadcrumb-muted: var(--wire-color-secondary-muted);
--breadcrumb-text: var(--wire-color-secondary-text);
}
.wire-breadcrumb[data-color="info"] {
--breadcrumb-accent: var(--wire-color-info);
--breadcrumb-soft: var(--wire-color-info-soft);
--breadcrumb-muted: var(--wire-color-info-muted);
--breadcrumb-text: var(--wire-color-info-text);
}
.wire-breadcrumb[data-color="success"] {
--breadcrumb-accent: var(--wire-color-success);
--breadcrumb-soft: var(--wire-color-success-soft);
--breadcrumb-muted: var(--wire-color-success-muted);
--breadcrumb-text: var(--wire-color-success-text);
}
.wire-breadcrumb[data-color="warning"] {
--breadcrumb-accent: var(--wire-color-warning);
--breadcrumb-soft: var(--wire-color-warning-soft);
--breadcrumb-muted: var(--wire-color-warning-muted);
--breadcrumb-text: var(--wire-color-warning-text);
}
.wire-breadcrumb[data-color="danger"] {
--breadcrumb-accent: var(--wire-color-danger);
--breadcrumb-soft: var(--wire-color-danger-soft);
--breadcrumb-muted: var(--wire-color-danger-muted);
--breadcrumb-text: var(--wire-color-danger-text);
}
.wire-breadcrumb[data-variant="soft"] {
width: fit-content;
max-width: 100%;
padding: 0.45rem 0.65rem;
background: var(--breadcrumb-soft);
border: 1px solid var(--breadcrumb-muted);
border-radius: 0.75rem;
}
.wire-breadcrumb[data-variant="outline"] {
width: fit-content;
max-width: 100%;
padding: 0.45rem 0.65rem;
background: var(--wire-color-surface-raised);
border: 1px solid var(--wire-color-border);
border-radius: 0.75rem;
box-shadow: var(--wire-shadow-sm);
}
.wire-breadcrumb[data-variant="contrast"] {
color: rgba(255, 255, 255, 0.78);
}
.wire-breadcrumb__list {
display: flex;
align-items: center;
gap: 0;
min-width: 0;
margin: 0;
padding: 0;
overflow-x: auto;
list-style: none;
scrollbar-width: none;
white-space: nowrap;
}
.wire-breadcrumb__list::-webkit-scrollbar {
display: none;
}
.wire-breadcrumb__item {
display: inline-flex;
align-items: center;
min-width: 0;
flex: 0 0 auto;
}
.wire-breadcrumb__separator {
display: inline-flex;
align-items: center;
justify-content: center;
margin-inline: 0.45rem;
color: var(--wire-color-text-subtle);
font-size: 0.75rem;
opacity: 0.72;
}
.wire-breadcrumb__separator-icon {
width: 0.9rem;
height: 0.9rem;
}
.wire-breadcrumb__link,
.wire-breadcrumb__current {
display: inline-flex;
align-items: center;
gap: 0.4rem;
min-width: 0;
min-height: 2rem;
padding: 0.25rem 0.4rem;
border-radius: 0.55rem;
font-size: 0.8125rem;
line-height: 1.25rem;
text-decoration: none;
}
.wire-breadcrumb[data-size="sm"] .wire-breadcrumb__link,
.wire-breadcrumb[data-size="sm"] .wire-breadcrumb__current {
min-height: 1.75rem;
font-size: 0.75rem;
}
.wire-breadcrumb[data-size="lg"] .wire-breadcrumb__link,
.wire-breadcrumb[data-size="lg"] .wire-breadcrumb__current {
min-height: 2.25rem;
font-size: 0.875rem;
}
.wire-breadcrumb__link {
color: var(--wire-color-text-muted);
font-weight: 600;
transition:
color 160ms ease,
background 160ms ease;
}
.wire-breadcrumb__link:hover {
color: var(--breadcrumb-accent);
background: var(--breadcrumb-soft);
}
.wire-breadcrumb__link:focus-visible {
color: var(--breadcrumb-accent);
outline: 2px solid var(--breadcrumb-accent);
outline-offset: 2px;
}
.wire-breadcrumb__current {
max-width: 22rem;
color: var(--wire-color-text);
font-weight: 750;
}
.wire-breadcrumb__item[data-current="true"] .wire-breadcrumb__current {
color: var(--breadcrumb-text);
background: var(--breadcrumb-soft);
}
.wire-breadcrumb__current[aria-disabled="true"] {
opacity: 0.58;
}
.wire-breadcrumb__icon,
.wire-breadcrumb__external {
flex: 0 0 auto;
width: 0.95rem;
height: 0.95rem;
}
.wire-breadcrumb__home {
color: var(--breadcrumb-accent);
}
.wire-breadcrumb__label {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.wire-breadcrumb[data-variant="contrast"] .wire-breadcrumb__link,
.wire-breadcrumb[data-variant="contrast"] .wire-breadcrumb__current,
.wire-breadcrumb[data-variant="contrast"] .wire-breadcrumb__home {
color: inherit;
}
.wire-breadcrumb[data-variant="contrast"] .wire-breadcrumb__link:hover,
.wire-breadcrumb[data-variant="contrast"] .wire-breadcrumb__item[data-current="true"] .wire-breadcrumb__current {
color: #ffffff;
background: rgba(255, 255, 255, 0.12);
}
.wire-breadcrumb[data-variant="contrast"] .wire-breadcrumb__separator {
color: rgba(255, 255, 255, 0.62);
}
@media (max-width: 639px) {
.wire-breadcrumb__home-label {
display: none;
}
.wire-breadcrumb__current {
max-width: 13rem;
}
}
@media (prefers-reduced-motion: reduce) {
.wire-breadcrumb__link {
transition: none;
}
}
}
} }
+23 -20
View File
@@ -1,35 +1,38 @@
component ButtonGroup { component ButtonGroup {
outputs {
click(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
select(payload: { value: string | number | boolean | null | object; previousValue: string | number | boolean | null | object; item: string | number | boolean | null | object; index: number })
change(payload: { value: string | number | boolean | null | object; previousValue: string | number | boolean | null | object; item: string | number | boolean | null | object; index: number })
}
props { props {
@event click = function items: unknown[] = []
@event select = function value: string = ""
@event change = function size: string = "md"
items = [] color: string = "primary"
value = "" variant: string = "default"
size = "md" orientation: string = "horizontal"
color = "primary" responsive: boolean = false
variant = "default" attached: boolean = true
orientation = "horizontal" selectable: boolean = false
responsive = false toolbar: boolean = false
attached = true disabled: boolean = false
selectable = false ariaLabel: string = "Button group"
toolbar = false class: string = ""
disabled = false
ariaLabel = "Button group"
class = ""
} }
state selectedValue = value state selectedValue = value
functions { functions {
function itemValue(item, index) { shared function itemValue(item, index) {
return item.value || item.label || String(index) return item.value || item.label || String(index)
} }
function selectItem(item, index) { client function selectItem(item, index) {
previousValue = selectedValue previousValue = selectedValue
nextValue = itemValue(item, index) nextValue = itemValue(item, index)
$emit("select", { output.select({
value: nextValue, value: nextValue,
previousValue: previousValue, previousValue: previousValue,
item: item, item: item,
@@ -38,7 +41,7 @@ component ButtonGroup {
if (selectable && previousValue !== nextValue) { if (selectable && previousValue !== nextValue) {
selectedValue = nextValue selectedValue = nextValue
$emit("change", { output.change({
value: nextValue, value: nextValue,
previousValue: previousValue, previousValue: previousValue,
item: item, item: item,
+65 -62
View File
@@ -1,53 +1,56 @@
// Interactive, hydration-safe content carousel. // Interactive, hydration-safe content carousel.
component Carousel { component Carousel {
outputs {
initialize(payload: { index: number; count: number })
change(payload: { index: number; previousIndex: number; item: string | number | boolean | null | object; reason: string | boolean })
previous(payload: { index: number; previousIndex: number; item: string | number | boolean | null | object })
next(payload: { index: number; previousIndex: number; item: string | number | boolean | null | object })
play(payload: { index: number; interval: number })
pause(payload: { index: number })
reachStart(payload: { index: number })
reachEnd(payload: { index: number })
dragStart(payload: { index: number; x: null })
dragEnd(payload: { index: number; distance: number })
}
props { props {
@event initialize = function size: string = "default"
@event change = function color: string = "primary"
@event previous = function title: string = ""
@event next = function description: string = ""
@event play = function items: unknown[] = []
@event pause = function activeIndex: number = 0
@event reachStart = function slidesPerView: number = 1
@event reachEnd = function gap: string = "0.75rem"
@event dragStart = function showPagination: boolean = false
@event dragEnd = function isAutoPlay: boolean = false
size = "default" autoplayInterval: number = 4000
color = "primary" isInfiniteLoop: boolean = false
title = "" isRTL: boolean = false
description = "" isCentered: boolean = false
items = [] isDraggable: boolean = false
activeIndex = 0 isAutoHeight: boolean = false
slidesPerView = 1 isSnap: boolean = false
gap = "0.75rem" showCounter: boolean = false
showPagination = false thumbnails: string = "none"
isAutoPlay = false ariaLabel: string = "Content carousel"
autoplayInterval = 4000 variant: string = "default"
isInfiniteLoop = false class: string = ""
isRTL = false
isCentered = false
isDraggable = false
isAutoHeight = false
isSnap = false
showCounter = false
thumbnails = "none"
ariaLabel = "Content carousel"
variant = "default"
class = ""
} }
state currentIndex = activeIndex state currentIndex = activeIndex
state playing = false state playing: boolean = false
state dragOrigin = null state dragOrigin = null
state dragOffset = 0 state dragOffset: number = 0
state autoplayTimer = null state autoplayTimer = null
state carouselRoot = null state carouselRoot = null
functions { functions {
function slideCount() { shared function slideCount() {
return items.length return items.length
} }
function maximumIndex(count, visible) { shared function maximumIndex(count, visible) {
count = slideCount() count = slideCount()
visible = Math.max(1, Number(slidesPerView) || 1) visible = Math.max(1, Number(slidesPerView) || 1)
if (isCentered || isSnap) { if (isCentered || isSnap) {
@@ -56,7 +59,7 @@ component Carousel {
return Math.max(0, count - visible) return Math.max(0, count - visible)
} }
function normalizedIndex(index, maximum) { shared function normalizedIndex(index, maximum) {
maximum = maximumIndex() maximum = maximumIndex()
if (slideCount() === 0) { if (slideCount() === 0) {
return 0 return 0
@@ -72,7 +75,7 @@ component Carousel {
return Math.max(0, Math.min(index, maximum)) return Math.max(0, Math.min(index, maximum))
} }
function rememberRoot(sourceEvent, root) { client function rememberRoot(sourceEvent, root) {
if (!sourceEvent || !sourceEvent.currentTarget) { if (!sourceEvent || !sourceEvent.currentTarget) {
return return
} }
@@ -82,7 +85,7 @@ component Carousel {
} }
} }
function syncNavigation(sourceEvent, index, root, viewport, slides, slide, thumbnails, thumbnail, rail) { client function syncNavigation(sourceEvent, index, root, viewport, slides, slide, thumbnails, thumbnail, rail) {
rememberRoot(sourceEvent) rememberRoot(sourceEvent)
root = carouselRoot root = carouselRoot
if (!root) { if (!root) {
@@ -111,7 +114,7 @@ component Carousel {
} }
} }
function handleSnapScroll(sourceEvent, slides, viewport, viewportCenter, closestIndex, closestDistance, slide, slideCenter, distance, previousIndex) { client function handleSnapScroll(sourceEvent, slides, viewport, viewportCenter, closestIndex, closestDistance, slide, slideCenter, distance, previousIndex) {
if (!isSnap) { if (!isSnap) {
return return
} }
@@ -133,7 +136,7 @@ component Carousel {
if (closestIndex !== currentIndex) { if (closestIndex !== currentIndex) {
previousIndex = currentIndex previousIndex = currentIndex
currentIndex = closestIndex currentIndex = closestIndex
$emit("change", { output.change({
index: currentIndex, index: currentIndex,
previousIndex: previousIndex, previousIndex: previousIndex,
item: items[currentIndex], item: items[currentIndex],
@@ -152,7 +155,7 @@ component Carousel {
} }
} }
function selectSlide(index, reason, sourceEvent, previousIndex) { client function selectSlide(index, reason, sourceEvent, previousIndex) {
if (slideCount() === 0) { if (slideCount() === 0) {
return return
} }
@@ -162,41 +165,41 @@ component Carousel {
if (previousIndex === currentIndex && reason !== "initialize") { if (previousIndex === currentIndex && reason !== "initialize") {
return return
} }
$emit("change", { output.change({
index: currentIndex, index: currentIndex,
previousIndex: previousIndex, previousIndex: previousIndex,
item: items[currentIndex], item: items[currentIndex],
reason: reason || "select" reason: reason || "select"
}) })
if (currentIndex === 0) { if (currentIndex === 0) {
$emit("reachStart", { index: currentIndex }) output.reachStart({ index: currentIndex })
} }
if (currentIndex === maximumIndex()) { if (currentIndex === maximumIndex()) {
$emit("reachEnd", { index: currentIndex }) output.reachEnd({ index: currentIndex })
} }
} }
function previousSlide(sourceEvent, previousIndex) { client function previousSlide(sourceEvent, previousIndex) {
previousIndex = currentIndex previousIndex = currentIndex
selectSlide(currentIndex - 1, "previous", sourceEvent) selectSlide(currentIndex - 1, "previous", sourceEvent)
$emit("previous", { output.previous({
index: currentIndex, index: currentIndex,
previousIndex: previousIndex, previousIndex: previousIndex,
item: items[currentIndex] item: items[currentIndex]
}) })
} }
function nextSlide(sourceEvent, previousIndex) { client function nextSlide(sourceEvent, previousIndex) {
previousIndex = currentIndex previousIndex = currentIndex
selectSlide(currentIndex + 1, "next", sourceEvent) selectSlide(currentIndex + 1, "next", sourceEvent)
$emit("next", { output.next({
index: currentIndex, index: currentIndex,
previousIndex: previousIndex, previousIndex: previousIndex,
item: items[currentIndex] item: items[currentIndex]
}) })
} }
function handleKeydown(sourceEvent) { client function handleKeydown(sourceEvent) {
if (sourceEvent.key === "ArrowLeft") { if (sourceEvent.key === "ArrowLeft") {
sourceEvent.preventDefault() sourceEvent.preventDefault()
if (isRTL) { if (isRTL) {
@@ -223,7 +226,7 @@ component Carousel {
} }
} }
function beginDrag(sourceEvent) { client function beginDrag(sourceEvent) {
if (!isDraggable || isSnap) { if (!isDraggable || isSnap) {
return return
} }
@@ -231,10 +234,10 @@ component Carousel {
dragOrigin = sourceEvent.clientX dragOrigin = sourceEvent.clientX
dragOffset = 0 dragOffset = 0
sourceEvent.currentTarget.setPointerCapture(sourceEvent.pointerId) sourceEvent.currentTarget.setPointerCapture(sourceEvent.pointerId)
$emit("dragStart", { index: currentIndex, x: dragOrigin }) output.dragStart({ index: currentIndex, x: dragOrigin })
} }
function moveDrag(sourceEvent) { client function moveDrag(sourceEvent) {
if (!isDraggable || isSnap || dragOrigin === null) { if (!isDraggable || isSnap || dragOrigin === null) {
return return
} }
@@ -242,12 +245,12 @@ component Carousel {
dragOffset = sourceEvent.clientX - dragOrigin dragOffset = sourceEvent.clientX - dragOrigin
} }
function cancelDrag() { shared function cancelDrag() {
dragOrigin = null dragOrigin = null
dragOffset = 0 dragOffset = 0
} }
function endDrag(sourceEvent, distance) { client function endDrag(sourceEvent, distance) {
if (!isDraggable || isSnap || dragOrigin === null) { if (!isDraggable || isSnap || dragOrigin === null) {
return return
} }
@@ -262,13 +265,13 @@ component Carousel {
previousSlide(sourceEvent) previousSlide(sourceEvent)
} }
} }
$emit("dragEnd", { output.dragEnd({
index: currentIndex, index: currentIndex,
distance: distance distance: distance
}) })
} }
function advanceAutoplay() { shared function advanceAutoplay() {
if (currentIndex >= maximumIndex()) { if (currentIndex >= maximumIndex()) {
selectSlide(0, "autoplay") selectSlide(0, "autoplay")
} else { } else {
@@ -276,22 +279,22 @@ component Carousel {
} }
} }
function startAutoplay() { client function startAutoplay() {
if (!isAutoPlay || playing || slideCount() < 2) { if (!isAutoPlay || playing || slideCount() < 2) {
return return
} }
playing = true playing = true
autoplayTimer = setInterval(advanceAutoplay, Math.max(1000, Number(autoplayInterval))) autoplayTimer = setInterval(advanceAutoplay, Math.max(1000, Number(autoplayInterval)))
$emit("play", { index: currentIndex, interval: autoplayInterval }) output.play({ index: currentIndex, interval: autoplayInterval })
} }
function pauseAutoplay() { client function pauseAutoplay() {
if (autoplayTimer) { if (autoplayTimer) {
clearInterval(autoplayTimer) clearInterval(autoplayTimer)
} }
autoplayTimer = null autoplayTimer = null
if (playing) { if (playing) {
$emit("pause", { index: currentIndex }) output.pause({ index: currentIndex })
} }
playing = false playing = false
} }
@@ -300,7 +303,7 @@ component Carousel {
lifecycle { lifecycle {
mount { mount {
$emit("initialize", { index: currentIndex, count: slideCount() }) output.initialize({ index: currentIndex, count: slideCount() })
startAutoplay() startAutoplay()
} }
unmount { unmount {
+13 -10
View File
@@ -1,15 +1,18 @@
component Chart { component Chart {
outputs {
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
dataPointClick(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
legendToggle(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
}
props { props {
@event select = function size: string = "default"
@event dataPointClick = function color: string = "primary"
@event legendToggle = function title: string = "Chart"
size = "default" description: string = ""
color = "primary" items: unknown[] = []
title = "Chart" variant: string = "default"
description = "" class: string = ""
items = []
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--chart wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--chart wire-next--variant-{variant} {class}">
+27 -24
View File
@@ -1,56 +1,59 @@
component ChatBubble { component ChatBubble {
outputs {
action(payload: { action: boolean; item: string | number | boolean | null | object; index: number })
messageClick(payload: { item: string | number | boolean | null | object; index: number; direction: string })
avatarClick(payload: { item: string | number | boolean | null | object; index: number; direction: string })
linkClick(payload: { link: string | number | boolean | null | object; item: string | number | boolean | null | object; index: number })
}
props { props {
@event action = function size: string = "default"
@event messageClick = function color: string = "primary"
@event avatarClick = function title: string = ""
@event linkClick = function description: string = ""
size = "default" items: unknown[] = []
color = "primary" oneSided: boolean = false
title = "" showAvatars: boolean = false
description = "" showMetadata: boolean = false
items = [] ariaLabel: string = "Conversation"
oneSided = false variant: string = "default"
showAvatars = false class: string = ""
showMetadata = false
ariaLabel = "Conversation"
variant = "default"
class = ""
} }
functions { functions {
function messageDirection(item) { shared function messageDirection(item) {
return item.direction === "outgoing" ? "outgoing" : "incoming" return item.direction === "outgoing" ? "outgoing" : "incoming"
} }
function selectMessage(item, index) { client function selectMessage(item, index) {
$emit("messageClick", { output.messageClick({
item: item, item: item,
index: index, index: index,
direction: messageDirection(item) direction: messageDirection(item)
}) })
} }
function selectAvatar(sourceEvent, item, index) { client function selectAvatar(sourceEvent, item, index) {
sourceEvent.stopPropagation() sourceEvent.stopPropagation()
$emit("avatarClick", { output.avatarClick({
item: item, item: item,
index: index, index: index,
direction: messageDirection(item) direction: messageDirection(item)
}) })
} }
function selectLink(sourceEvent, link, item, index) { client function selectLink(sourceEvent, link, item, index) {
sourceEvent.stopPropagation() sourceEvent.stopPropagation()
$emit("linkClick", { output.linkClick({
link: link, link: link,
item: item, item: item,
index: index index: index
}) })
} }
function selectAction(sourceEvent, item, index) { client function selectAction(sourceEvent, item, index) {
sourceEvent.stopPropagation() sourceEvent.stopPropagation()
$emit("action", { output.action({
action: item.action || "retry", action: item.action || "retry",
item: item, item: item,
index: index index: index
+13 -10
View File
@@ -1,15 +1,18 @@
component Clipboard { component Clipboard {
outputs {
copy(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
success(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
error(payload: { error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)
}
props { props {
@event copy = function size: string = "default"
@event success = function color: string = "primary"
@event error = function title: string = "Clipboard"
size = "default" description: string = ""
color = "primary" items: unknown[] = []
title = "Clipboard" variant: string = "default"
description = "" class: string = ""
items = []
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--clipboard wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--clipboard wire-next--variant-{variant} {class}">
+19 -16
View File
@@ -1,26 +1,29 @@
component Collapse { component Collapse {
outputs {
toggle(payload: { index: number; item: string | number | boolean | null | object; open: boolean; openIndexes: number[] })
open(payload: { index: number; item: string | number | boolean | null | object })
close(payload: { index: number; item: string | number | boolean | null | object })
}
props { props {
@event toggle = function size: string = "default"
@event open = function color: string = "primary"
@event close = function items: unknown[] = []
size = "default" multiple: boolean = false
color = "primary" mode: string = "panel"
items = [] initialOpenIndexes: unknown[] = []
multiple = false ariaLabel: string = "Collapsible content"
mode = "panel" class: string = ""
initialOpenIndexes = []
ariaLabel = "Collapsible content"
class = ""
} }
state openIndexes = initialOpenIndexes state openIndexes = initialOpenIndexes
functions { functions {
function isOpen(index) { shared function isOpen(index) {
return openIndexes.includes(index) return openIndexes.includes(index)
} }
function toggleItem(index, item, opening) { client function toggleItem(index, item, opening) {
if (item.disabled) { if (item.disabled) {
return return
} }
@@ -31,12 +34,12 @@ component Collapse {
} else { } else {
openIndexes = [index] openIndexes = [index]
} }
$emit("open", { index: index, item: item }) output.open({ index: index, item: item })
} else { } else {
openIndexes = openIndexes.filter((openIndex) => openIndex !== index) openIndexes = openIndexes.filter((openIndex) => openIndex !== index)
$emit("close", { index: index, item: item }) output.close({ index: index, item: item })
} }
$emit("toggle", { output.toggle({
index: index, index: index,
item: item, item: item,
open: opening, open: opening,
+28 -25
View File
@@ -1,32 +1,35 @@
component ColorPicker { component ColorPicker {
outputs {
input(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
focus(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
blur(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event input = function size: string = "default"
@event change = function color: string = "primary"
@event focus = function id: string = ""
@event blur = function name: string = ""
size = "default" label: string = "Color"
color = "primary" hiddenLabel: boolean = false
id = "" placeholder: string = ""
name = "" value: string = "#2563eb"
label = "Color" icon: string = ""
hiddenLabel = false iconPosition: string = "start"
placeholder = "" helperText: string = ""
value = "#2563eb" cornerHint: string = ""
icon = "" error: string = ""
iconPosition = "start" inline: boolean = false
helperText = "" variant: string = "normal"
cornerHint = "" readonly: boolean = false
error = "" disabled: boolean = false
inline = false required: boolean = false
variant = "normal" class: string = ""
readonly = false
disabled = false
required = false
class = ""
} }
functions { functions {
function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); $emit(nameEvent, { value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) } client function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); output[nameEvent]({ value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) }
function preventReadonly(sourceEvent) { if (readonly) sourceEvent.preventDefault() } client function preventReadonly(sourceEvent) { if (readonly) sourceEvent.preventDefault() }
} }
view { view {
<div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--color-picker {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}"> <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--color-picker {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
+6 -6
View File
@@ -1,11 +1,11 @@
component Columns { component Columns {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
columns = 2 columns: number = 2
gap = "md" gap: string = "md"
maxWidth = "xl" maxWidth: string = "xl"
class = "" class: string = ""
} }
view { view {
+76 -73
View File
@@ -1,71 +1,74 @@
component ComboBox { component ComboBox {
outputs {
search(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
clear(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
close(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
load(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
error(payload: { error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)
}
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
label = "ComboBox" label: string = "ComboBox"
name = "" name: string = ""
value = "" value: string = ""
options = [] options: unknown[] = []
groups = [] groups: unknown[] = []
placeholder = "Search or select an option" placeholder: string = "Search or select an option"
searchPlaceholder = "Start typing…" searchPlaceholder: string = "Start typing…"
clearable = true clearable: boolean = true
allowCustomValue = false allowCustomValue: boolean = false
disabled = false disabled: boolean = false
required = false required: boolean = false
invalid = false invalid: boolean = false
validationMessage = "" validationMessage: string = ""
helpText = "" helpText: string = ""
loading = false loading: boolean = false
loadingLabel = "Loading suggestions…" loadingLabel: string = "Loading suggestions…"
emptyLabel = "No matching options" emptyLabel: string = "No matching options"
clearLabel = "Clear value" clearLabel: string = "Clear value"
toggleLabel = "Toggle suggestions" toggleLabel: string = "Toggle suggestions"
searchMode = "contains" searchMode: string = "contains"
searchFields = "label,description" searchFields: string = "label,description"
minSearchLength = 0 minSearchLength: number = 0
searchResultLimit = 0 searchResultLimit: number = 0
optionTemplate = "default" optionTemplate: string = "default"
defaultOpen = false defaultOpen: boolean = false
closeOnSelect = true closeOnSelect: boolean = true
fixed = false fixed: boolean = false
placement = "bottom" placement: string = "bottom"
autocomplete = "off" autocomplete: string = "off"
remote = false remote: boolean = false
remoteUrl = "" remoteUrl: string = ""
remoteQueryParam = "q" remoteQueryParam: string = "q"
remoteDebounce = 250 remoteDebounce: number = 250
remoteAutoLoad = true remoteAutoLoad: boolean = true
infinite = false infinite: boolean = false
hasMore = false hasMore: boolean = false
page = 1 page: number = 1
loadMoreLabel = "Load more" loadMoreLabel: string = "Load more"
class = "" class: string = ""
@event search = function
@event select = function
@event change = function
@event clear = function
@event open = function
@event close = function
@event load = function
@event error = function
} }
state open = defaultOpen state open = defaultOpen
state query = "" state query: string = ""
state selectedValue = value state selectedValue = value
state activeIndex = -1 state activeIndex: number = -1
functions { functions {
function allOptions() { shared function allOptions() {
return [...groups.flatMap((group) => group.options || []), ...options] return [...groups.flatMap((group) => group.options || []), ...options]
} }
function searchableText(option) { shared function searchableText(option) {
return ((option.label || "") + " " + (option.description || "")).toLowerCase() return ((option.label || "") + " " + (option.description || "")).toLowerCase()
} }
function matches(option) { shared function matches(option) {
if (!query || query.length < minSearchLength) { if (!query || query.length < minSearchLength) {
return true return true
} }
@@ -78,38 +81,38 @@ component ComboBox {
return searchableText(option).includes(query.toLowerCase()) return searchableText(option).includes(query.toLowerCase())
} }
function matchingOptions(list) { shared function matchingOptions(list) {
return list.filter((option) => matches(option)) return list.filter((option) => matches(option))
} }
function visibleOptions(list) { shared function visibleOptions(list) {
if (searchResultLimit > 0) { if (searchResultLimit > 0) {
return matchingOptions(list).slice(0, searchResultLimit) return matchingOptions(list).slice(0, searchResultLimit)
} }
return matchingOptions(list) return matchingOptions(list)
} }
function flatVisibleOptions() { shared function flatVisibleOptions() {
return visibleOptions(allOptions()) return visibleOptions(allOptions())
} }
function isSelected(option) { shared function isSelected(option) {
return selectedValue === option.value return selectedValue === option.value
} }
function selectedOptions() { shared function selectedOptions() {
return allOptions().filter((option) => isSelected(option)) return allOptions().filter((option) => isSelected(option))
} }
function selectedText() { shared function selectedText() {
return selectedOptions().length ? selectedOptions()[0].label : selectedValue return selectedOptions().length ? selectedOptions()[0].label : selectedValue
} }
function inputText() { shared function inputText() {
return query || selectedText() return query || selectedText()
} }
function shouldShowDropdown() { shared function shouldShowDropdown() {
if (!open) { if (!open) {
return false return false
} }
@@ -125,11 +128,11 @@ component ComboBox {
return infinite && hasMore return infinite && hasMore
} }
function canSelect(option) { shared function canSelect(option) {
return !option.disabled return !option.disabled
} }
function chooseOption(option) { shared function chooseOption(option) {
if (!canSelect(option)) { if (!canSelect(option)) {
return return
} }
@@ -141,14 +144,14 @@ component ComboBox {
} }
} }
function updateQuery(event) { client function updateQuery(event) {
query = event.target.value query = event.target.value
selectedValue = allowCustomValue ? event.target.value : "" selectedValue = allowCustomValue ? event.target.value : ""
open = true open = true
activeIndex = flatVisibleOptions().length ? 0 : -1 activeIndex = flatVisibleOptions().length ? 0 : -1
} }
function clearValue(event) { client function clearValue(event) {
if (event) { if (event) {
event.stopPropagation() event.stopPropagation()
} }
@@ -158,18 +161,18 @@ component ComboBox {
open = false open = false
} }
function openDropdown() { shared function openDropdown() {
if (!disabled) { if (!disabled) {
open = true open = true
activeIndex = flatVisibleOptions().length ? 0 : -1 activeIndex = flatVisibleOptions().length ? 0 : -1
} }
} }
function closeDropdown() { shared function closeDropdown() {
open = false open = false
} }
function toggle() { shared function toggle() {
if (open) { if (open) {
closeDropdown() closeDropdown()
} else { } else {
@@ -177,19 +180,19 @@ component ComboBox {
} }
} }
function setValue(nextValue) { shared function setValue(nextValue) {
selectedValue = nextValue selectedValue = nextValue
query = "" query = ""
} }
function moveActive(direction) { shared function moveActive(direction) {
if (!flatVisibleOptions().length) { if (!flatVisibleOptions().length) {
return return
} }
activeIndex = (activeIndex + direction + flatVisibleOptions().length) % flatVisibleOptions().length activeIndex = (activeIndex + direction + flatVisibleOptions().length) % flatVisibleOptions().length
} }
function handleKeydown(event) { client function handleKeydown(event) {
if (disabled) { if (disabled) {
return return
} }
@@ -215,7 +218,7 @@ component ComboBox {
} }
} }
function optionIndex(option) { shared function optionIndex(option) {
return flatVisibleOptions().findIndex((item) => item.value === option.value) return flatVisibleOptions().findIndex((item) => item.value === option.value)
} }
} }
+12 -9
View File
@@ -1,14 +1,17 @@
component Confetti { component Confetti {
outputs {
start(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
complete(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event start = function size: string = "default"
@event complete = function color: string = "primary"
size = "default" title: string = "Confetti"
color = "primary" description: string = ""
title = "Confetti" items: unknown[] = []
description = "" variant: string = "default"
items = [] class: string = ""
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--confetti wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--confetti wire-next--variant-{variant} {class}">
+515 -16
View File
@@ -1,22 +1,521 @@
component ContextMenu { component ContextMenu {
props { outputs {
@event open = function open(payload: { x: number; y: number; trigger: string; sourceEvent: Event })
@event close = function close(payload: { reason: string; sourceEvent: Event })
@event select = function select(payload: { item: string | number | boolean | null | object; itemIndex: number; value: string | number | boolean; sourceEvent: Event })
size = "default" action(payload: { item: string | number | boolean | null | object; itemIndex: number; value: string | number | boolean; sourceEvent: Event })
color = "primary"
title = "Context Menu"
description = ""
open = false
placement = "bottom"
closeLabel = "Close"
class = ""
} }
props {
items: unknown[] = []
open: boolean = false
defaultOpen: boolean = false
trigger: string = "contextmenu"
placement: string = "pointer"
align: string = "start"
size: string = "default"
color: string = "primary"
variant: string = "raised"
title: string = ""
description: string = ""
label: string = "Context menu"
closeOnSelect: boolean = true
closeOnOutside: boolean = true
disabled: boolean = false
minWidth: string = "14rem"
maxWidth: string = "20rem"
class: string = ""
}
state visible = defaultOpen
state positionX: number = 16
state positionY: number = 16
functions {
shared function isOpen() {
return open || visible
}
client function showMenu(sourceEvent) {
if (disabled) {
return
}
if (sourceEvent && sourceEvent.type === "contextmenu") {
sourceEvent.preventDefault()
}
if (placement === "pointer" && sourceEvent) {
positionX = Math.max(12, Math.min(sourceEvent.clientX || 12, window.innerWidth - 340))
positionY = Math.max(12, Math.min(sourceEvent.clientY || 12, window.innerHeight - 420))
}
visible = true
output.open({
x: positionX,
y: positionY,
trigger: trigger,
sourceEvent: sourceEvent
})
}
client function hideMenu(reason, sourceEvent) {
visible = false
output.close({
reason: reason,
sourceEvent: sourceEvent
})
}
client function toggleMenu(sourceEvent) {
if (isOpen()) {
hideMenu("toggle", sourceEvent)
} else {
showMenu(sourceEvent)
}
}
client function chooseItem(item, itemIndex, sourceEvent) {
if (disabled || item.disabled || item.type === "header" || item.type === "divider") {
sourceEvent.preventDefault()
return
}
output.select({
item: item,
itemIndex: itemIndex,
value: item.value || "",
sourceEvent: sourceEvent
})
if (item.action) {
output.action({
item: item,
itemIndex: itemIndex,
value: item.value || "",
sourceEvent: sourceEvent
})
}
if (closeOnSelect) {
hideMenu("select", sourceEvent)
}
}
client function moveFocus(sourceEvent, direction) {
const root = sourceEvent.currentTarget.closest(".wire-context-menu") || sourceEvent.currentTarget
const options = [...root.querySelectorAll("[data-context-menu-item]:not([disabled])")]
if (!options.length) {
return
}
const activeIndex = options.indexOf(document.activeElement)
const nextIndex = (activeIndex + direction + options.length) % options.length
options[nextIndex].focus()
}
client function handleKeydown(sourceEvent) {
if (sourceEvent.key === "Escape") {
sourceEvent.preventDefault()
hideMenu("escape", sourceEvent)
} else if (sourceEvent.key === "ArrowDown") {
sourceEvent.preventDefault()
moveFocus(sourceEvent, 1)
} else if (sourceEvent.key === "ArrowUp") {
sourceEvent.preventDefault()
moveFocus(sourceEvent, -1)
}
}
}
view { view {
<div data-show="{open}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--context-menu wire-next--placement-{placement} {class}" role="region" aria-modal="false" aria-label="{title}"> <div
<header><strong>{title}</strong><button type="button" aria-label="{closeLabel}">×</button></header> {...attrs}
{#if description}<p>{description}</p>{/if} data-ui-component="ContextMenu"
<slot /> data-open='{open || visible ? "true" : "false"}'
data-trigger='{trigger}'
data-placement='{placement}'
data-align='{align}'
data-size='{size}'
data-color='{color}'
data-variant='{variant}'
class='wire-context-menu {class}'
style='--wire-context-x:{positionX}px;--wire-context-y:{positionY}px;--wire-context-min-width:{minWidth};--wire-context-max-width:{maxWidth};'
@keydown='handleKeydown(event)'
>
<div
class="wire-context-menu__trigger"
tabindex='{disabled ? "-1" : "0"}'
aria-haspopup="menu"
aria-expanded='{open || visible ? "true" : "false"}'
@contextmenu='if (trigger === "contextmenu" || trigger === "both") { showMenu(event) }'
@click='if (trigger === "click" || trigger === "both") { toggleMenu(event) }'
@keydown='if (event.key === "Enter" || event.key === " ") { event.preventDefault(); toggleMenu(event) }'
>
<slot name="trigger"></slot>
</div>
{#if closeOnOutside}
<button
type="button"
class="wire-context-menu__dismiss-layer"
data-show='{open || visible}'
aria-label="Close context menu"
@click='hideMenu("outside", event)'
></button>
{/if}
<div
class="wire-context-menu__panel"
data-show='{open || visible}'
role="menu"
aria-label='{label}'
>
{#if title || description}
<div class="wire-context-menu__header">
{#if title}
<strong>{title}</strong>
{/if}
{#if description}
<span>{description}</span>
{/if}
</div>
{/if}
<slot name="header"></slot>
<div class="wire-context-menu__items">
{#each items as item, itemIndex}
{#if item.type === "divider"}
<div class="wire-context-menu__divider" role="separator"></div>
{:else if item.type === "header"}
<div class="wire-context-menu__section-label">{item.label || item.title}</div>
{:else if item.href}
<a
href='{item.href}'
target='{item.target || ""}'
rel='{item.external || item.target === "_blank" ? "noopener noreferrer" : (item.rel || "")}'
role="menuitem"
data-context-menu-item
data-danger='{item.danger ? "true" : "false"}'
data-selected='{item.selected || item.checked ? "true" : "false"}'
aria-disabled='{item.disabled ? "true" : "false"}'
class="wire-context-menu__item"
@click='chooseItem(item, itemIndex, event)'
>
{#if item.icon}
<span class='wire-context-menu__icon {item.icon}' aria-hidden="true"></span>
{/if}
<span class="wire-context-menu__copy">
<strong>{item.label || item.title}</strong>
{#if item.description}
<small>{item.description}</small>
{/if}
</span>
{#if item.shortcut}
<kbd>{item.shortcut}</kbd>
{:else if item.checked || item.selected}
<span class="icon-[lucide--check] wire-context-menu__status" aria-hidden="true"></span>
{:else if item.external}
<span class="icon-[lucide--arrow-up-right] wire-context-menu__status" aria-hidden="true"></span>
{/if}
</a>
{:else}
<button
type="button"
role="menuitem"
data-context-menu-item
data-danger='{item.danger ? "true" : "false"}'
data-selected='{item.selected || item.checked ? "true" : "false"}'
disabled='{item.disabled}'
class="wire-context-menu__item"
@click='chooseItem(item, itemIndex, event)'
>
{#if item.icon}
<span class='wire-context-menu__icon {item.icon}' aria-hidden="true"></span>
{/if}
<span class="wire-context-menu__copy">
<strong>{item.label || item.title}</strong>
{#if item.description}
<small>{item.description}</small>
{/if}
</span>
{#if item.shortcut}
<kbd>{item.shortcut}</kbd>
{:else if item.checked || item.selected}
<span class="icon-[lucide--check] wire-context-menu__status" aria-hidden="true"></span>
{/if}
</button>
{/if}
{/each}
</div>
<slot></slot>
<slot name="footer"></slot>
</div>
</div> </div>
} }
style {
.wire-context-menu {
--context-accent: var(--wire-color-primary);
--context-soft: var(--wire-color-primary-soft);
position: relative;
display: block;
min-width: 0;
}
.wire-context-menu[data-color="secondary"] {
--context-accent: var(--wire-color-secondary);
--context-soft: var(--wire-color-secondary-soft);
}
.wire-context-menu[data-color="info"] {
--context-accent: var(--wire-color-info);
--context-soft: var(--wire-color-info-soft);
}
.wire-context-menu[data-color="success"] {
--context-accent: var(--wire-color-success);
--context-soft: var(--wire-color-success-soft);
}
.wire-context-menu[data-color="warning"] {
--context-accent: var(--wire-color-warning);
--context-soft: var(--wire-color-warning-soft);
}
.wire-context-menu[data-color="danger"] {
--context-accent: var(--wire-color-danger);
--context-soft: var(--wire-color-danger-soft);
}
.wire-context-menu__trigger {
display: block;
min-width: 0;
outline: none;
}
.wire-context-menu__trigger:focus-visible {
border-radius: 0.6rem;
outline: 2px solid var(--wire-color-focus);
outline-offset: 3px;
}
.wire-context-menu__dismiss-layer {
position: fixed;
inset: 0;
z-index: 1090;
appearance: none;
padding: 0;
background: transparent;
border: 0;
}
.wire-context-menu__panel {
position: absolute;
z-index: 1091;
top: calc(100% + 0.5rem);
left: 0;
width: max-content;
min-width: var(--wire-context-min-width);
max-width: min(var(--wire-context-max-width), calc(100vw - 1.5rem));
padding: 0.45rem;
color: var(--wire-color-text);
background:
linear-gradient(145deg, color-mix(in srgb, var(--context-accent) 5%, transparent), transparent 58%),
color-mix(in srgb, var(--wire-color-surface-raised) 96%, transparent);
border: 1px solid color-mix(in srgb, var(--context-accent) 18%, var(--wire-color-border));
border-radius: 1rem;
box-shadow:
0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
0 24px 64px color-mix(in srgb, black 24%, transparent);
backdrop-filter: blur(18px);
transform-origin: top left;
}
.wire-context-menu[data-placement="pointer"] .wire-context-menu__panel {
position: fixed;
top: var(--wire-context-y);
left: var(--wire-context-x);
}
.wire-context-menu[data-placement="bottom-end"] .wire-context-menu__panel {
right: 0;
left: auto;
transform-origin: top right;
}
.wire-context-menu[data-placement="top-start"] .wire-context-menu__panel {
top: auto;
bottom: calc(100% + 0.5rem);
transform-origin: bottom left;
}
.wire-context-menu[data-placement="top-end"] .wire-context-menu__panel {
top: auto;
right: 0;
bottom: calc(100% + 0.5rem);
left: auto;
transform-origin: bottom right;
}
.wire-context-menu[data-variant="soft"] .wire-context-menu__panel {
background:
linear-gradient(145deg, var(--context-soft), transparent 72%),
var(--wire-color-surface-raised);
box-shadow: 0 18px 48px color-mix(in srgb, black 16%, transparent);
}
.wire-context-menu[data-variant="outline"] .wire-context-menu__panel {
background: var(--wire-color-surface-raised);
box-shadow: none;
}
.wire-context-menu__header {
display: grid;
gap: 0.2rem;
padding: 0.65rem 0.75rem 0.75rem;
border-bottom: 1px solid var(--wire-color-border);
}
.wire-context-menu__header strong {
font-size: 0.86rem;
font-weight: 650;
}
.wire-context-menu__header span {
color: var(--wire-color-text-muted);
font-size: 0.75rem;
line-height: 1.45;
}
.wire-context-menu__items {
display: grid;
gap: 0.15rem;
padding-block: 0.25rem;
}
.wire-context-menu__section-label {
padding: 0.55rem 0.75rem 0.3rem;
color: var(--wire-color-text-muted);
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.wire-context-menu__divider {
height: 1px;
margin: 0.3rem 0.45rem;
background: var(--wire-color-border);
}
.wire-context-menu__item {
appearance: none;
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 0.7rem;
width: 100%;
min-width: 0;
padding: 0.65rem 0.7rem;
color: var(--wire-color-text);
background: transparent;
border: 0;
border-radius: 0.72rem;
font: inherit;
text-align: left;
text-decoration: none;
cursor: pointer;
transition: background-color 150ms ease, color 150ms ease, transform 150ms ease;
}
.wire-context-menu__item:hover,
.wire-context-menu__item:focus-visible,
.wire-context-menu__item[data-selected="true"] {
color: var(--context-accent);
background: var(--context-soft);
outline: none;
}
.wire-context-menu__item:active {
transform: scale(0.985);
}
.wire-context-menu__item[data-danger="true"] {
color: var(--wire-color-danger);
}
.wire-context-menu__item[data-danger="true"]:hover,
.wire-context-menu__item[data-danger="true"]:focus-visible {
background: var(--wire-color-danger-soft);
}
.wire-context-menu__item[disabled],
.wire-context-menu__item[aria-disabled="true"] {
opacity: 0.48;
pointer-events: none;
}
.wire-context-menu__icon,
.wire-context-menu__status {
width: 1rem;
height: 1rem;
color: currentColor;
}
.wire-context-menu__copy {
display: grid;
gap: 0.12rem;
min-width: 0;
}
.wire-context-menu__copy strong {
overflow: hidden;
font-size: 0.82rem;
font-weight: 600;
line-height: 1.3;
text-overflow: ellipsis;
white-space: nowrap;
}
.wire-context-menu__copy small {
overflow: hidden;
color: var(--wire-color-text-muted);
font-size: 0.7rem;
line-height: 1.35;
text-overflow: ellipsis;
white-space: nowrap;
}
.wire-context-menu kbd {
padding: 0.16rem 0.38rem;
color: var(--wire-color-text-muted);
background: var(--wire-color-surface-soft);
border: 1px solid var(--wire-color-border);
border-radius: 0.36rem;
font-size: 0.64rem;
font-family: inherit;
}
.wire-context-menu[data-size="sm"] .wire-context-menu__item {
padding: 0.52rem 0.6rem;
}
.wire-context-menu[data-size="lg"] .wire-context-menu__item {
padding: 0.78rem 0.82rem;
}
@media (max-width: 639px) {
.wire-context-menu[data-placement="pointer"] .wire-context-menu__panel {
right: 0.75rem;
bottom: 0.75rem;
left: 0.75rem;
top: auto;
width: auto;
max-width: none;
}
}
@media (prefers-reduced-motion: reduce) {
.wire-context-menu__item {
transition: none;
}
}
}
} }
+19 -16
View File
@@ -1,21 +1,24 @@
component CopyMarkup { component CopyMarkup {
outputs {
copy(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
success(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
error(payload: { error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)
}
props { props {
@event copy = function size: string = "default"
@event success = function color: string = "primary"
@event error = function label: string = "Copy Markup"
size = "default" name: string = ""
color = "primary" value: string = ""
label = "Copy Markup" placeholder: string = ""
name = "" type: string = "text"
value = "" min: string = ""
placeholder = "" max: string = ""
type = "text" step: string = ""
min = "" disabled: boolean = false
max = "" required: boolean = false
step = "" class: string = ""
disabled = false
required = false
class = ""
} }
view { view {
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--copy-markup {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> <label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--copy-markup {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>
+10 -7
View File
@@ -1,12 +1,15 @@
component CustomScrollbar { component CustomScrollbar {
outputs {
scroll(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
}
props { props {
@event scroll = function size: string = "default"
size = "default" color: string = "primary"
color = "primary" columns: number = 2
columns = 2 gap: string = "md"
gap = "md" maxWidth: string = "xl"
maxWidth = "xl" class: string = ""
class = ""
} }
view { view {
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--custom-scrollbar wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div> <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--custom-scrollbar wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div>
+12 -9
View File
@@ -1,14 +1,17 @@
component DataMap { component DataMap {
outputs {
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
}
props { props {
@event select = function size: string = "default"
@event change = function color: string = "primary"
size = "default" title: string = "Data Map"
color = "primary" description: string = ""
title = "Data Map" items: unknown[] = []
description = "" variant: string = "default"
items = [] class: string = ""
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--data-map wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--data-map wire-next--variant-{variant} {class}">
+15 -12
View File
@@ -1,17 +1,20 @@
component DataTable { component DataTable {
outputs {
sort(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
rowClick(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
pageChange(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
}
props { props {
@event sort = function size: string = "default"
@event select = function color: string = "primary"
@event change = function caption: string = "Data Table"
@event rowClick = function columns: unknown[] = []
@event pageChange = function rows: unknown[] = []
size = "default" striped: boolean = true
color = "primary" class: string = ""
caption = "Data Table"
columns = []
rows = []
striped = true
class = ""
} }
view { view {
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--table {class}"><table><caption>{caption}</caption><thead><tr>{#each columns as column}<th>{column.label}</th>{/each}</tr></thead><tbody>{#each rows as row}<tr>{#each columns as column}<td>{row[column.key]}</td>{/each}</tr>{/each}</tbody></table><slot /></div> <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--table {class}"><table><caption>{caption}</caption><thead><tr>{#each columns as column}<th>{column.label}</th>{/each}</tr></thead><tbody>{#each rows as row}<tr>{#each columns as column}<td>{row[column.key]}</td>{/each}</tr>{/each}</tbody></table><slot /></div>
+41 -38
View File
@@ -1,52 +1,55 @@
component DatePicker { component DatePicker {
outputs {
input(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
change(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
open(payload: { value: string; name: string; sourceEvent: Event })
close(payload: { value: string; name: string; sourceEvent: Event })
focus(payload: { value: string; name: string; sourceEvent: Event })
blur(payload: { value: string; name: string; sourceEvent: Event })
invalid(payload: { name: string; message: string; sourceEvent: Event })
}
props { props {
@event input = function size: string = "default"
@event change = function color: string = "primary"
@event open = function label: string = "Date Picker"
@event close = function id: string = ""
@event focus = function name: string = ""
@event blur = function value: string = ""
@event invalid = function placeholder: string = ""
size = "default" type: string = "date"
color = "primary" locale: string = "en-US"
label = "Date Picker" firstDayOfWeek: number = 0
id = ""
name = ""
value = ""
placeholder = ""
type = "date"
locale = "en-US"
firstDayOfWeek = 0
months = [{ label: "Jan", value: "01" }, { label: "Feb", value: "02" }, { label: "Mar", value: "03" }, { label: "Apr", value: "04" }, { label: "May", value: "05" }, { label: "Jun", value: "06" }, { label: "Jul", value: "07" }, { label: "Aug", value: "08" }, { label: "Sep", value: "09" }, { label: "Oct", value: "10" }, { label: "Nov", value: "11" }, { label: "Dec", value: "12" }] months = [{ label: "Jan", value: "01" }, { label: "Feb", value: "02" }, { label: "Mar", value: "03" }, { label: "Apr", value: "04" }, { label: "May", value: "05" }, { label: "Jun", value: "06" }, { label: "Jul", value: "07" }, { label: "Aug", value: "08" }, { label: "Sep", value: "09" }, { label: "Oct", value: "10" }, { label: "Nov", value: "11" }, { label: "Dec", value: "12" }]
days = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31] days = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
years = [2024, 2025, 2026, 2027, 2028, 2029, 2030] years = [2024, 2025, 2026, 2027, 2028, 2029, 2030]
min = "" min: string = ""
max = "" max: string = ""
step = "" step: string = ""
helperText = "" helperText: string = ""
cornerHint = "" cornerHint: string = ""
error = "" error: string = ""
variant = "normal" variant: string = "normal"
inline = false inline: boolean = false
readonly = false readonly: boolean = false
disabled = false disabled: boolean = false
required = false required: boolean = false
class = "" class: string = ""
} }
state currentValue = value state currentValue = value
state selectedYear = value ? value.split("-")[0] : "" state selectedYear = value ? value.split("-")[0] : ""
state selectedMonth = value ? value.split("-")[1] : "" state selectedMonth = value ? value.split("-")[1] : ""
state selectedDay = value ? value.split("-")[2] : "" state selectedDay = value ? value.split("-")[2] : ""
state expanded = false state expanded: boolean = false
functions { functions {
function openCalendar(sourceEvent) { expanded = true; $emit("open", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function openCalendar(sourceEvent) { expanded = true; output.open({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function updateDate(part, nextValue, sourceEvent, nextDate, detail) { if (part === "year") { selectedYear = String(nextValue) } if (part === "month") { selectedMonth = String(nextValue).padStart(2, "0") } if (part === "day") { selectedDay = String(nextValue).padStart(2, "0") } if (!selectedYear || !selectedMonth || !selectedDay) { return } nextDate = selectedYear + "-" + selectedMonth + "-" + selectedDay; if ((min && nextDate < min) || (max && nextDate > max)) { return } currentValue = nextDate; detail = { value: currentValue, year: selectedYear, month: selectedMonth, day: selectedDay, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) } client function updateDate(part, nextValue, sourceEvent, nextDate, detail) { if (part === "year") { selectedYear = String(nextValue) } if (part === "month") { selectedMonth = String(nextValue).padStart(2, "0") } if (part === "day") { selectedDay = String(nextValue).padStart(2, "0") } if (!selectedYear || !selectedMonth || !selectedDay) { return } nextDate = selectedYear + "-" + selectedMonth + "-" + selectedDay; if ((min && nextDate < min) || (max && nextDate > max)) { return } currentValue = nextDate; detail = { value: currentValue, year: selectedYear, month: selectedMonth, day: selectedDay, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
function finishDate(sourceEvent) { if (!currentValue) { return } expanded = false; $emit("close", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function finishDate(sourceEvent) { if (!currentValue) { return } expanded = false; output.close({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function selectDay(sourceEvent, root, input, nextDate, trigger, detail) { root = sourceEvent.currentTarget.closest(".wire-next--date-picker"); input = root.querySelector(".wire-next__picker-value"); nextDate = input.getAttribute("value").slice(0, 5) + root.querySelector("select").getAttribute("value") + "-" + sourceEvent.currentTarget.dataset.value; currentValue = nextDate; input.setAttribute("value", nextDate); trigger = root.querySelector(".wire-next__date-trigger span"); if (trigger) { trigger.replaceChildren(nextDate) } sourceEvent.currentTarget.setAttribute("aria-pressed", "true"); detail = { value: nextDate, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) } client function selectDay(sourceEvent, root, input, nextDate, trigger, detail) { root = sourceEvent.currentTarget.closest(".wire-next--date-picker"); input = root.querySelector(".wire-next__picker-value"); nextDate = input.getAttribute("value").slice(0, 5) + root.querySelector("select").getAttribute("value") + "-" + sourceEvent.currentTarget.dataset.value; currentValue = nextDate; input.setAttribute("value", nextDate); trigger = root.querySelector(".wire-next__date-trigger span"); if (trigger) { trigger.replaceChildren(nextDate) } sourceEvent.currentTarget.setAttribute("aria-pressed", "true"); detail = { value: nextDate, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
function clearDate(sourceEvent, detail) { if (disabled || readonly) { return } currentValue = ""; detail = { value: currentValue, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) } client function clearDate(sourceEvent, detail) { if (disabled || readonly) { return } currentValue = ""; detail = { value: currentValue, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
function handleFocus(sourceEvent) { $emit("focus", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function handleFocus(sourceEvent) { output.focus({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function handleBlur(sourceEvent) { $emit("blur", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function handleBlur(sourceEvent) { output.blur({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function handleInvalid(sourceEvent) { $emit("invalid", { name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent }) } client function handleInvalid(sourceEvent) { output.invalid({ name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent }) }
} }
view { view {
<div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--date-picker {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}" data-expanded="{expanded}"> <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--date-picker {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}" data-expanded="{expanded}">
+21 -18
View File
@@ -1,26 +1,29 @@
component DeviceFrame { component DeviceFrame {
outputs {
change(payload: { device: string; orientation: string; sourceEvent: Event })
rotate(payload: { device: string; orientation: string; sourceEvent: Event })
}
props { props {
@event change = function size: string = "default"
@event rotate = function color: string = "primary"
size = "default" title: string = "Device Frame"
color = "primary" description: string = ""
title = "Device Frame" items: unknown[] = []
description = "" variant: string = "default"
items = [] device: string = "phone"
variant = "default" orientation: string = "portrait"
device = "phone" src: string = ""
orientation = "portrait" srcdoc: string = ""
src = "" frameTitle: string = "Device preview"
srcdoc = "" showToolbar: boolean = true
frameTitle = "Device preview" allow: string = ""
showToolbar = true class: string = ""
allow = ""
class = ""
} }
state currentOrientation = orientation state currentOrientation = orientation
functions { functions {
function setDevice(nextDevice, sourceEvent) { $emit("change", { device: nextDevice, orientation: currentOrientation, sourceEvent: sourceEvent }) } client function setDevice(nextDevice, sourceEvent) { output.change({ device: nextDevice, orientation: currentOrientation, sourceEvent: sourceEvent }) }
function rotateFrame(sourceEvent) { currentOrientation = currentOrientation === "portrait" ? "landscape" : "portrait"; $emit("rotate", { device: device, orientation: currentOrientation, sourceEvent: sourceEvent }); $emit("change", { device: device, orientation: currentOrientation, sourceEvent: sourceEvent }) } client function rotateFrame(sourceEvent) { currentOrientation = currentOrientation === "portrait" ? "landscape" : "portrait"; output.rotate({ device: device, orientation: currentOrientation, sourceEvent: sourceEvent }); output.change({ device: device, orientation: currentOrientation, sourceEvent: sourceEvent }) }
} }
view { view {
<section {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--device-frame wire-next--variant-{variant} {class}" data-device="{device}" data-orientation="{currentOrientation}"> <section {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--device-frame wire-next--variant-{variant} {class}" data-device="{device}" data-orientation="{currentOrientation}">
+16 -13
View File
@@ -1,18 +1,21 @@
component DragAndDrop { component DragAndDrop {
outputs {
dragStart(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
dragEnd(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
dragEnter(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
dragLeave(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
drop(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
}
props { props {
@event dragStart = function size: string = "default"
@event dragEnd = function color: string = "primary"
@event dragEnter = function title: string = "Drag And Drop"
@event dragLeave = function description: string = ""
@event drop = function items: unknown[] = []
@event change = function variant: string = "default"
size = "default" class: string = ""
color = "primary"
title = "Drag And Drop"
description = ""
items = []
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--drag-and-drop wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--drag-and-drop wire-next--variant-{variant} {class}">
+447 -15
View File
@@ -1,21 +1,453 @@
component Drawer { component Drawer {
props { outputs {
@event open = function open(payload: { placement: string; sourceEvent: Event })
@event close = function close(payload: { reason: string; placement: string; sourceEvent: Event })
size = "default" cancel(payload: { placement: string; sourceEvent: Event })
color = "primary"
title = "Drawer"
description = ""
open = false
placement = "bottom"
closeLabel = "Close"
class = ""
} }
props {
open: boolean = false
defaultOpen: boolean = false
placement: string = "right"
size: string = "md"
color: string = "primary"
variant: string = "default"
title: string = "Drawer"
description: string = ""
icon: string = ""
label: string = "Drawer"
closeLabel: string = "Close drawer"
showClose: boolean = true
closeOnBackdrop: boolean = true
closeOnEscape: boolean = true
overlay: boolean = true
scrollable: boolean = true
triggerLabel: string = ""
triggerIcon: string = ""
class: string = ""
}
state visible = defaultOpen
functions {
shared function isOpen() {
return open || visible
}
client function showDrawer(sourceEvent) {
visible = true
output.open({
placement: placement,
sourceEvent: sourceEvent
})
}
client function hideDrawer(reason, sourceEvent) {
visible = false
output.close({
reason: reason,
placement: placement,
sourceEvent: sourceEvent
})
}
client function cancelDrawer(sourceEvent) {
output.cancel({
placement: placement,
sourceEvent: sourceEvent
})
hideDrawer("cancel", sourceEvent)
}
client function handleKeydown(sourceEvent) {
if (closeOnEscape && sourceEvent.key === "Escape") {
sourceEvent.preventDefault()
cancelDrawer(sourceEvent)
}
}
}
view { view {
<div data-show="{open}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--drawer wire-next--placement-{placement} {class}" role="dialog" aria-modal="true" aria-label="{title}"> <div
<header><strong>{title}</strong><button type="button" aria-label="{closeLabel}">×</button></header> {...attrs}
{#if description}<p>{description}</p>{/if} data-ui-component="Drawer"
<slot /> data-open='{open || visible ? "true" : "false"}'
data-placement='{placement}'
data-size='{size}'
data-color='{color}'
data-variant='{variant}'
data-overlay='{overlay ? "true" : "false"}'
data-scrollable='{scrollable ? "true" : "false"}'
class='wire-drawer {class}'
>
{#if triggerLabel}
<button
type="button"
class="wire-drawer__trigger"
aria-haspopup="dialog"
aria-expanded='{open || visible ? "true" : "false"}'
@click='showDrawer(event)'
>
{#if triggerIcon}
<span class='{triggerIcon}' aria-hidden="true"></span>
{/if}
<span>{triggerLabel}</span>
</button>
{/if}
<span class="wire-drawer__trigger-slot" @click='showDrawer(event)'>
<slot name="trigger"></slot>
</span>
<div
class="wire-drawer__layer"
data-show='{open || visible}'
role="presentation"
@keydown='handleKeydown(event)'
>
{#if overlay}
<button
type="button"
class="wire-drawer__backdrop"
aria-label='{closeLabel}'
@click='if (closeOnBackdrop) { cancelDrawer(event) }'
></button>
{/if}
<section
class="wire-drawer__panel"
role="dialog"
aria-modal="true"
aria-label='{label || title}'
tabindex="-1"
>
<div class="wire-drawer__handle" aria-hidden="true"></div>
<header class="wire-drawer__header">
<div class="wire-drawer__heading">
{#if icon}
<span class='wire-drawer__icon {icon}' aria-hidden="true"></span>
{/if}
<div class="wire-drawer__heading-copy">
<slot name="header"></slot>
{#if title}
<h2>{title}</h2>
{/if}
{#if description}
<p>{description}</p>
{/if}
</div>
</div>
{#if showClose}
<button
type="button"
class="wire-drawer__close"
aria-label='{closeLabel}'
@click='hideDrawer("close-button", event)'
>
<span class="icon-[lucide--x]" aria-hidden="true"></span>
</button>
{/if}
</header>
<div class="wire-drawer__body">
<slot></slot>
</div>
<footer class="wire-drawer__footer">
<slot name="footer"></slot>
</footer>
</section>
</div>
</div> </div>
} }
style {
.wire-drawer {
--drawer-accent: var(--wire-color-primary);
--drawer-soft: var(--wire-color-primary-soft);
position: relative;
display: inline-flex;
}
.wire-drawer[data-color="secondary"] {
--drawer-accent: var(--wire-color-secondary);
--drawer-soft: var(--wire-color-secondary-soft);
}
.wire-drawer[data-color="info"] {
--drawer-accent: var(--wire-color-info);
--drawer-soft: var(--wire-color-info-soft);
}
.wire-drawer[data-color="success"] {
--drawer-accent: var(--wire-color-success);
--drawer-soft: var(--wire-color-success-soft);
}
.wire-drawer[data-color="warning"] {
--drawer-accent: var(--wire-color-warning);
--drawer-soft: var(--wire-color-warning-soft);
}
.wire-drawer[data-color="danger"] {
--drawer-accent: var(--wire-color-danger);
--drawer-soft: var(--wire-color-danger-soft);
}
.wire-drawer__trigger,
.wire-drawer__trigger-slot {
display: inline-flex;
align-items: center;
gap: 0.55rem;
}
.wire-drawer__trigger {
appearance: none;
min-height: 2.55rem;
padding: 0.65rem 1rem;
color: var(--wire-color-primary-contrast);
background: var(--drawer-accent);
border: 0;
border-radius: 0.8rem;
font: inherit;
font-size: 0.85rem;
font-weight: 650;
cursor: pointer;
}
.wire-drawer__layer {
position: fixed;
inset: 0;
z-index: 1200;
display: flex;
pointer-events: none;
}
.wire-drawer__backdrop {
position: absolute;
inset: 0;
z-index: 0;
appearance: none;
padding: 0;
background: color-mix(in srgb, black 56%, transparent);
border: 0;
backdrop-filter: blur(7px);
pointer-events: auto;
}
.wire-drawer[data-overlay="false"] .wire-drawer__backdrop {
display: none;
}
.wire-drawer__panel {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
width: min(28rem, calc(100vw - 1rem));
max-height: 100%;
margin-left: auto;
color: var(--wire-color-text);
background:
radial-gradient(
circle at 100% 0%,
color-mix(in srgb, var(--drawer-accent) 8%, transparent),
transparent 34%
),
var(--wire-color-surface-raised);
border-left: 1px solid color-mix(in srgb, var(--drawer-accent) 18%, var(--wire-color-border));
box-shadow: -28px 0 80px color-mix(in srgb, black 28%, transparent);
pointer-events: auto;
overflow: hidden;
}
.wire-drawer[data-size="sm"] .wire-drawer__panel {
width: min(22rem, calc(100vw - 1rem));
}
.wire-drawer[data-size="lg"] .wire-drawer__panel {
width: min(38rem, calc(100vw - 1rem));
}
.wire-drawer[data-size="xl"] .wire-drawer__panel {
width: min(52rem, calc(100vw - 1rem));
}
.wire-drawer[data-size="full"] .wire-drawer__panel {
width: 100vw;
}
.wire-drawer[data-placement="left"] .wire-drawer__panel {
margin-right: auto;
margin-left: 0;
border-right: 1px solid color-mix(in srgb, var(--drawer-accent) 18%, var(--wire-color-border));
border-left: 0;
box-shadow: 28px 0 80px color-mix(in srgb, black 28%, transparent);
}
.wire-drawer[data-placement="top"] .wire-drawer__layer,
.wire-drawer[data-placement="bottom"] .wire-drawer__layer {
align-items: flex-start;
}
.wire-drawer[data-placement="top"] .wire-drawer__panel,
.wire-drawer[data-placement="bottom"] .wire-drawer__panel {
width: 100%;
max-height: min(80vh, 44rem);
margin: 0;
border: 0;
box-shadow: 0 24px 80px color-mix(in srgb, black 28%, transparent);
}
.wire-drawer[data-placement="top"] .wire-drawer__panel {
border-bottom: 1px solid color-mix(in srgb, var(--drawer-accent) 18%, var(--wire-color-border));
}
.wire-drawer[data-placement="bottom"] .wire-drawer__layer {
align-items: flex-end;
}
.wire-drawer[data-placement="bottom"] .wire-drawer__panel {
border-top: 1px solid color-mix(in srgb, var(--drawer-accent) 18%, var(--wire-color-border));
border-radius: 1.3rem 1.3rem 0 0;
}
.wire-drawer[data-variant="soft"] .wire-drawer__panel {
background:
linear-gradient(145deg, var(--drawer-soft), transparent 65%),
var(--wire-color-surface-raised);
}
.wire-drawer[data-variant="solid"] .wire-drawer__panel {
color: var(--wire-color-primary-contrast);
background: var(--drawer-accent);
border-color: color-mix(in srgb, white 18%, transparent);
}
.wire-drawer__handle {
display: none;
width: 2.75rem;
height: 0.28rem;
margin: 0.55rem auto 0;
background: color-mix(in srgb, var(--wire-color-text-muted) 48%, transparent);
border-radius: 999px;
}
.wire-drawer[data-placement="bottom"] .wire-drawer__handle {
display: block;
}
.wire-drawer__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
padding: 1.35rem 1.35rem 1.1rem;
border-bottom: 1px solid var(--wire-color-border);
}
.wire-drawer__heading {
display: flex;
align-items: flex-start;
gap: 0.85rem;
min-width: 0;
}
.wire-drawer__icon {
flex: 0 0 auto;
width: 1.25rem;
height: 1.25rem;
margin-top: 0.15rem;
color: var(--drawer-accent);
}
.wire-drawer[data-variant="solid"] .wire-drawer__icon {
color: currentColor;
}
.wire-drawer__heading-copy {
display: grid;
gap: 0.3rem;
min-width: 0;
}
.wire-drawer__heading-copy h2,
.wire-drawer__heading-copy p {
margin: 0;
}
.wire-drawer__heading-copy h2 {
font-size: 1.05rem;
font-weight: 650;
line-height: 1.3;
}
.wire-drawer__heading-copy p {
color: var(--wire-color-text-muted);
font-size: 0.8rem;
line-height: 1.55;
}
.wire-drawer[data-variant="solid"] .wire-drawer__heading-copy p {
color: color-mix(in srgb, currentColor 76%, transparent);
}
.wire-drawer__close {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 2.35rem;
height: 2.35rem;
color: var(--wire-color-text-muted);
background: var(--wire-color-surface-soft);
border: 1px solid var(--wire-color-border);
border-radius: 0.75rem;
cursor: pointer;
}
.wire-drawer__close:hover,
.wire-drawer__close:focus-visible {
color: var(--drawer-accent);
border-color: color-mix(in srgb, var(--drawer-accent) 34%, var(--wire-color-border));
outline: none;
}
.wire-drawer__body {
flex: 1 1 auto;
min-height: 0;
padding: 1.35rem;
}
.wire-drawer[data-scrollable="true"] .wire-drawer__body {
overflow: auto;
overscroll-behavior: contain;
}
.wire-drawer__footer {
padding: 1rem 1.35rem 1.35rem;
border-top: 1px solid var(--wire-color-border);
}
.wire-drawer__footer:empty {
display: none;
}
@media (max-width: 639px) {
.wire-drawer[data-placement="right"] .wire-drawer__panel,
.wire-drawer[data-placement="left"] .wire-drawer__panel {
width: min(24rem, 100vw);
}
.wire-drawer__header,
.wire-drawer__body,
.wire-drawer__footer {
padding-inline: 1rem;
}
}
}
} }
+557 -38
View File
@@ -1,53 +1,572 @@
component Dropdown { component Dropdown {
props { outputs {
@event toggle = function toggle(payload: { open: boolean; sourceEvent: Event; reason?: object })
@event open = function open(payload: { sourceEvent: Event })
@event close = function close(payload: { reason: string; sourceEvent: Event })
@event select = function select(payload: { item: string | number | boolean | null | object; itemIndex: number; value: string | number | boolean; sourceEvent: Event })
size = "default" action(payload: { item: string | number | boolean | null | object; itemIndex: number; value: string | number | boolean; sourceEvent: Event })
color = "primary"
label = "Open menu"
items = []
placement = "end"
class = ""
} }
props {
items: unknown[] = []
open: boolean = false
defaultOpen: boolean = false
label: string = "Open menu"
icon: string = ""
showChevron: boolean = true
menuLabel: string = "Dropdown menu"
placement: string = "bottom-start"
width: string = "md"
size: string = "default"
color: string = "primary"
variant: string = "raised"
closeOnSelect: boolean = true
closeOnOutside: boolean = true
disabled: boolean = false
emptyLabel: string = "No menu items"
class: string = ""
}
state visible = defaultOpen
functions { functions {
function toggleMenu(event) { shared function isOpen() {
$emit("toggle", { open: event.currentTarget.open }) return open || visible
$emit(event.currentTarget.open ? "open" : "close", { source: "trigger" })
} }
function selectItem(item) { client function showMenu(sourceEvent) {
$emit("select", { item: item, value: item.value || "" }) if (disabled) {
return
}
visible = true
output.open({ sourceEvent: sourceEvent })
output.toggle({ open: true, sourceEvent: sourceEvent })
}
client function hideMenu(reason, sourceEvent) {
visible = false
output.close({ reason: reason, sourceEvent: sourceEvent })
output.toggle({ open: false, reason: reason, sourceEvent: sourceEvent })
}
client function toggleMenu(sourceEvent) {
if (isOpen()) {
hideMenu("toggle", sourceEvent)
} else {
showMenu(sourceEvent)
}
}
client function chooseItem(item, itemIndex, sourceEvent) {
if (disabled || item.disabled || item.type === "divider" || item.type === "header") {
sourceEvent.preventDefault()
return
}
output.select({
item: item,
itemIndex: itemIndex,
value: item.value || "",
sourceEvent: sourceEvent
})
if (item.action) {
output.action({
item: item,
itemIndex: itemIndex,
value: item.value || "",
sourceEvent: sourceEvent
})
}
if (closeOnSelect) {
hideMenu("select", sourceEvent)
}
}
client function moveFocus(sourceEvent, direction) {
const root = sourceEvent.currentTarget.closest(".wire-dropdown") || sourceEvent.currentTarget
const options = [...root.querySelectorAll("[data-dropdown-item]:not([disabled])")]
if (!options.length) {
return
}
const activeIndex = options.indexOf(document.activeElement)
const nextIndex = (activeIndex + direction + options.length) % options.length
options[nextIndex].focus()
}
client function handleKeydown(sourceEvent) {
if (sourceEvent.key === "Escape") {
sourceEvent.preventDefault()
hideMenu("escape", sourceEvent)
} else if (sourceEvent.key === "ArrowDown") {
sourceEvent.preventDefault()
if (!isOpen()) {
showMenu(sourceEvent)
}
moveFocus(sourceEvent, 1)
} else if (sourceEvent.key === "ArrowUp") {
sourceEvent.preventDefault()
if (!isOpen()) {
showMenu(sourceEvent)
}
moveFocus(sourceEvent, -1)
} else if (sourceEvent.key === "Home" && isOpen()) {
sourceEvent.preventDefault()
const root = sourceEvent.currentTarget.closest(".wire-dropdown") || sourceEvent.currentTarget
const first = root.querySelector("[data-dropdown-item]:not([disabled])")
if (first) {
first.focus()
}
} else if (sourceEvent.key === "End" && isOpen()) {
sourceEvent.preventDefault()
const root = sourceEvent.currentTarget.closest(".wire-dropdown") || sourceEvent.currentTarget
const options = [...root.querySelectorAll("[data-dropdown-item]:not([disabled])")]
if (options.length) {
options[options.length - 1].focus()
}
}
} }
} }
view { view {
<details class="wire-dropdown wire-dropdown--{placement} wire-next--color-{color} wire-next--size-{size} {class}" data-wrn-dropdown @toggle="toggleMenu($event)"> <div
<summary aria-label="{label}"> {...attrs}
<slot name="trigger" /> data-ui-component="Dropdown"
<span class="wire-dropdown__chevron icon-[lucide--chevron-down]" aria-hidden="true"></span> data-open='{open || visible ? "true" : "false"}'
</summary> data-placement='{placement}'
<div class="wire-dropdown__panel" role="menu" aria-label="{label}"> data-width='{width}'
<slot name="header" /> data-size='{size}'
{#each items as item} data-color='{color}'
{#if item.type === "divider"} data-variant='{variant}'
<hr /> class='wire-dropdown {class}'
{:else} @keydown='handleKeydown(event)'
{#if item.type === "header"} >
<span class="wire-dropdown__heading">{item.label}</span> <button
{:else} type="button"
<a class="wire-dropdown__item {item.danger ? 'wire-dropdown__item--danger' : ''}" href="{item.href || '#'}" target="{item.target || ''}" rel="{item.rel || ''}" role="menuitem" @click="selectItem(item)"> class="wire-dropdown__trigger"
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if} disabled='{disabled}'
<span><strong>{item.label}</strong>{#if item.description}<small>{item.description}</small>{/if}</span> aria-haspopup="menu"
{#if item.external}<span class="icon-[lucide--arrow-up-right]" aria-hidden="true"></span>{/if} aria-expanded='{open || visible ? "true" : "false"}'
@click='toggleMenu(event)'
>
<slot name="trigger"></slot>
{#if icon}
<span class='wire-dropdown__trigger-icon {icon}' aria-hidden="true"></span>
{/if}
{#if label}
<span class="wire-dropdown__trigger-label">{label}</span>
{/if}
{#if showChevron}
<span class="icon-[lucide--chevron-down] wire-dropdown__chevron" aria-hidden="true"></span>
{/if}
</button>
{#if closeOnOutside}
<button
type="button"
class="wire-dropdown__dismiss-layer"
data-show='{open || visible}'
aria-label="Close menu"
@click='hideMenu("outside", event)'
></button>
{/if}
<div
class="wire-dropdown__panel"
data-show='{open || visible}'
role="menu"
aria-label='{menuLabel}'
>
<slot name="header"></slot>
<div class="wire-dropdown__items">
{#each items as item, itemIndex}
{#if item.type === "divider"}
<div class="wire-dropdown__divider" role="separator"></div>
{:else if item.type === "header"}
<div class="wire-dropdown__section-label">{item.label || item.title}</div>
{:else if item.href}
<a
href='{item.href}'
target='{item.target || ""}'
rel='{item.external || item.target === "_blank" ? "noopener noreferrer" : (item.rel || "")}'
role="menuitem"
data-dropdown-item
data-danger='{item.danger ? "true" : "false"}'
data-selected='{item.selected || item.checked ? "true" : "false"}'
aria-disabled='{item.disabled ? "true" : "false"}'
class="wire-dropdown__item"
@click='chooseItem(item, itemIndex, event)'
>
{#if item.icon}
<span class='wire-dropdown__item-icon {item.icon}' aria-hidden="true"></span>
{/if}
<span class="wire-dropdown__copy">
<strong>{item.label || item.title}</strong>
{#if item.description}
<small>{item.description}</small>
{/if}
</span>
{#if item.badge}
<span class="wire-dropdown__badge">{item.badge}</span>
{:else if item.shortcut}
<kbd>{item.shortcut}</kbd>
{:else if item.checked || item.selected}
<span class="icon-[lucide--check] wire-dropdown__status" aria-hidden="true"></span>
{:else if item.external}
<span class="icon-[lucide--arrow-up-right] wire-dropdown__status" aria-hidden="true"></span>
{/if}
</a> </a>
{:else}
<button
type="button"
role="menuitem"
data-dropdown-item
data-danger='{item.danger ? "true" : "false"}'
data-selected='{item.selected || item.checked ? "true" : "false"}'
disabled='{item.disabled}'
class="wire-dropdown__item"
@click='chooseItem(item, itemIndex, event)'
>
{#if item.icon}
<span class='wire-dropdown__item-icon {item.icon}' aria-hidden="true"></span>
{/if}
<span class="wire-dropdown__copy">
<strong>{item.label || item.title}</strong>
{#if item.description}
<small>{item.description}</small>
{/if}
</span>
{#if item.badge}
<span class="wire-dropdown__badge">{item.badge}</span>
{:else if item.shortcut}
<kbd>{item.shortcut}</kbd>
{:else if item.checked || item.selected}
<span class="icon-[lucide--check] wire-dropdown__status" aria-hidden="true"></span>
{/if}
</button>
{/if} {/if}
{/if} {:empty}
{/each} <div class="wire-dropdown__empty">{emptyLabel}</div>
<slot name="footer" /> {/each}
</div>
<slot></slot>
<slot name="footer"></slot>
</div> </div>
</details> </div>
}
style {
.wire-dropdown {
--dropdown-accent: var(--wire-color-primary);
--dropdown-soft: var(--wire-color-primary-soft);
position: relative;
display: inline-flex;
min-width: 0;
}
.wire-dropdown[data-color="secondary"] {
--dropdown-accent: var(--wire-color-secondary);
--dropdown-soft: var(--wire-color-secondary-soft);
}
.wire-dropdown[data-color="info"] {
--dropdown-accent: var(--wire-color-info);
--dropdown-soft: var(--wire-color-info-soft);
}
.wire-dropdown[data-color="success"] {
--dropdown-accent: var(--wire-color-success);
--dropdown-soft: var(--wire-color-success-soft);
}
.wire-dropdown[data-color="warning"] {
--dropdown-accent: var(--wire-color-warning);
--dropdown-soft: var(--wire-color-warning-soft);
}
.wire-dropdown[data-color="danger"] {
--dropdown-accent: var(--wire-color-danger);
--dropdown-soft: var(--wire-color-danger-soft);
}
.wire-dropdown__trigger {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.55rem;
min-height: 2.55rem;
padding: 0.65rem 0.9rem;
color: var(--wire-color-text);
background: var(--wire-color-surface-raised);
border: 1px solid var(--wire-color-border);
border-radius: 0.78rem;
font: inherit;
font-size: 0.84rem;
font-weight: 600;
cursor: pointer;
transition: border-color 150ms ease, background-color 150ms ease, color 150ms ease;
}
.wire-dropdown__trigger:hover,
.wire-dropdown__trigger:focus-visible,
.wire-dropdown[data-open="true"] .wire-dropdown__trigger {
color: var(--dropdown-accent);
background: var(--dropdown-soft);
border-color: color-mix(in srgb, var(--dropdown-accent) 38%, var(--wire-color-border));
outline: none;
}
.wire-dropdown__trigger:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.wire-dropdown__trigger-icon,
.wire-dropdown__chevron {
width: 1rem;
height: 1rem;
}
.wire-dropdown__chevron {
transition: transform 160ms ease;
}
.wire-dropdown[data-open="true"] .wire-dropdown__chevron {
transform: rotate(180deg);
}
.wire-dropdown[data-size="sm"] .wire-dropdown__trigger {
min-height: 2.2rem;
padding: 0.5rem 0.72rem;
font-size: 0.78rem;
}
.wire-dropdown[data-size="lg"] .wire-dropdown__trigger {
min-height: 2.9rem;
padding: 0.78rem 1.05rem;
font-size: 0.9rem;
}
.wire-dropdown__dismiss-layer {
position: fixed;
inset: 0;
z-index: 1100;
appearance: none;
padding: 0;
background: transparent;
border: 0;
}
.wire-dropdown__panel {
position: absolute;
z-index: 1101;
top: calc(100% + 0.55rem);
left: 0;
width: max-content;
min-width: 12rem;
max-width: min(22rem, calc(100vw - 1.5rem));
padding: 0.45rem;
color: var(--wire-color-text);
background:
linear-gradient(145deg, color-mix(in srgb, var(--dropdown-accent) 5%, transparent), transparent 60%),
color-mix(in srgb, var(--wire-color-surface-raised) 97%, transparent);
border: 1px solid color-mix(in srgb, var(--dropdown-accent) 18%, var(--wire-color-border));
border-radius: 1rem;
box-shadow:
0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
0 24px 64px color-mix(in srgb, black 22%, transparent);
backdrop-filter: blur(18px);
}
.wire-dropdown[data-width="trigger"] .wire-dropdown__panel {
width: 100%;
min-width: 100%;
}
.wire-dropdown[data-width="sm"] .wire-dropdown__panel {
width: 12rem;
}
.wire-dropdown[data-width="md"] .wire-dropdown__panel {
width: 17rem;
}
.wire-dropdown[data-width="lg"] .wire-dropdown__panel {
width: 22rem;
}
.wire-dropdown[data-placement="bottom-end"] .wire-dropdown__panel {
right: 0;
left: auto;
}
.wire-dropdown[data-placement="top-start"] .wire-dropdown__panel {
top: auto;
bottom: calc(100% + 0.55rem);
}
.wire-dropdown[data-placement="top-end"] .wire-dropdown__panel {
top: auto;
right: 0;
bottom: calc(100% + 0.55rem);
left: auto;
}
.wire-dropdown[data-variant="soft"] .wire-dropdown__panel {
background:
linear-gradient(145deg, var(--dropdown-soft), transparent 70%),
var(--wire-color-surface-raised);
box-shadow: 0 18px 48px color-mix(in srgb, black 16%, transparent);
}
.wire-dropdown[data-variant="outline"] .wire-dropdown__panel {
background: var(--wire-color-surface-raised);
box-shadow: none;
}
.wire-dropdown__items {
display: grid;
gap: 0.15rem;
}
.wire-dropdown__section-label {
padding: 0.55rem 0.72rem 0.3rem;
color: var(--wire-color-text-muted);
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.wire-dropdown__divider {
height: 1px;
margin: 0.3rem 0.4rem;
background: var(--wire-color-border);
}
.wire-dropdown__item {
appearance: none;
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 0.7rem;
width: 100%;
min-width: 0;
padding: 0.68rem 0.72rem;
color: var(--wire-color-text);
background: transparent;
border: 0;
border-radius: 0.72rem;
font: inherit;
text-align: left;
text-decoration: none;
cursor: pointer;
transition: background-color 150ms ease, color 150ms ease, transform 150ms ease;
}
.wire-dropdown__item:hover,
.wire-dropdown__item:focus-visible,
.wire-dropdown__item[data-selected="true"] {
color: var(--dropdown-accent);
background: var(--dropdown-soft);
outline: none;
}
.wire-dropdown__item:active {
transform: scale(0.985);
}
.wire-dropdown__item[data-danger="true"] {
color: var(--wire-color-danger);
}
.wire-dropdown__item[data-danger="true"]:hover,
.wire-dropdown__item[data-danger="true"]:focus-visible {
background: var(--wire-color-danger-soft);
}
.wire-dropdown__item[disabled],
.wire-dropdown__item[aria-disabled="true"] {
opacity: 0.48;
pointer-events: none;
}
.wire-dropdown__item-icon,
.wire-dropdown__status {
width: 1rem;
height: 1rem;
}
.wire-dropdown__copy {
display: grid;
gap: 0.12rem;
min-width: 0;
}
.wire-dropdown__copy strong {
overflow: hidden;
font-size: 0.82rem;
font-weight: 600;
line-height: 1.3;
text-overflow: ellipsis;
white-space: nowrap;
}
.wire-dropdown__copy small {
overflow: hidden;
color: var(--wire-color-text-muted);
font-size: 0.7rem;
line-height: 1.35;
text-overflow: ellipsis;
white-space: nowrap;
}
.wire-dropdown__badge {
padding: 0.18rem 0.45rem;
color: var(--dropdown-accent);
background: var(--dropdown-soft);
border-radius: 999px;
font-size: 0.64rem;
font-weight: 700;
}
.wire-dropdown kbd {
padding: 0.16rem 0.38rem;
color: var(--wire-color-text-muted);
background: var(--wire-color-surface-soft);
border: 1px solid var(--wire-color-border);
border-radius: 0.36rem;
font-size: 0.64rem;
font-family: inherit;
}
.wire-dropdown__empty {
padding: 1rem;
color: var(--wire-color-text-muted);
font-size: 0.8rem;
text-align: center;
}
@media (max-width: 639px) {
.wire-dropdown__panel {
position: fixed;
right: 0.75rem;
bottom: 0.75rem;
left: 0.75rem;
top: auto;
width: auto;
min-width: 0;
max-width: none;
}
}
@media (prefers-reduced-motion: reduce) {
.wire-dropdown__trigger,
.wire-dropdown__chevron,
.wire-dropdown__item {
transition: none;
}
}
} }
} }
+40 -37
View File
@@ -1,46 +1,49 @@
component FileInput { component FileInput {
props { outputs {
@event input = function input(payload: { files: File[]; name: string; sourceEvent: Event })
@event change = function change(payload: { files: File[]; name: string; sourceEvent: Event })
@event focus = function focus(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
@event blur = function blur(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
@event select = function select(payload: { files: File[]; name: string; sourceEvent: Event })
@event clear = function clear(payload: { name: string; sourceEvent: Event })
@event invalid = function invalid(payload: { message?: string; value: string | number | boolean | null | object; sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
size = "default"
color = "primary"
id = ""
name = ""
label = "File"
hiddenLabel = false
placeholder = "Choose a file"
value = ""
icon = "icon-[lucide--upload]"
iconPosition = "start"
accept = ""
multiple = false
helperText = ""
cornerHint = ""
error = ""
inline = false
variant = "normal"
readonly = false
disabled = false
required = false
class = ""
} }
state selectedName = ""
props {
size: string = "default"
color: string = "primary"
id: string = ""
name: string = ""
label: string = "File"
hiddenLabel: boolean = false
placeholder: string = "Choose a file"
value: string = ""
icon: string = "icon-[lucide--upload]"
iconPosition: string = "start"
accept: string = ""
multiple: boolean = false
helperText: string = ""
cornerHint: string = ""
error: string = ""
inline: boolean = false
variant: string = "normal"
readonly: boolean = false
disabled: boolean = false
required: boolean = false
class: string = ""
}
state selectedName: string = ""
functions { functions {
function handleChange(sourceEvent) { client function handleChange(sourceEvent) {
sourceEvent.stopPropagation() sourceEvent.stopPropagation()
selectedName = sourceEvent.currentTarget.files && sourceEvent.currentTarget.files.length ? sourceEvent.currentTarget.files[0].name : "" selectedName = sourceEvent.currentTarget.files && sourceEvent.currentTarget.files.length ? sourceEvent.currentTarget.files[0].name : ""
$emit("input", { files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent }) output.input({ files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent })
$emit("change", { files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent }) output.change({ files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent })
if (selectedName) $emit("select", { files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent }) if (selectedName) output.select({ files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent })
else $emit("clear", { name: name, sourceEvent: sourceEvent }) else output.clear({ name: name, sourceEvent: sourceEvent })
} }
function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); $emit(nameEvent, { name: name, sourceEvent: sourceEvent }) } client function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); output[nameEvent]({ name: name, sourceEvent: sourceEvent }) }
function preventReadonly(sourceEvent) { if (readonly) sourceEvent.preventDefault() } client function preventReadonly(sourceEvent) { if (readonly) sourceEvent.preventDefault() }
} }
view { view {
<div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--file-input {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}"> <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--file-input {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
+17 -14
View File
@@ -1,19 +1,22 @@
component FileUpload { component FileUpload {
outputs {
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
upload(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
progress(payload: { progress: number; [key: string]: string | number | boolean | null | object } | number)
success(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
error(payload: { error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)
cancel(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
remove(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event select = function size: string = "default"
@event upload = function color: string = "primary"
@event progress = function title: string = "File Upload"
@event success = function description: string = ""
@event error = function items: unknown[] = []
@event cancel = function variant: string = "default"
@event remove = function class: string = ""
size = "default"
color = "primary"
title = "File Upload"
description = ""
items = []
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--file-upload wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--file-upload wire-next--variant-{variant} {class}">
+24 -21
View File
@@ -1,30 +1,33 @@
component FileUploadProgress { component FileUploadProgress {
outputs {
cancel(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
retry(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
complete(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
}
props { props {
@event cancel = function size: string = "default"
@event retry = function color: string = "primary"
@event complete = function label: string = "Progress"
size = "default" value: number = 50
color = "primary" max: number = 100
label = "Progress" showValue: boolean = true
value = 50 fileName: string = ""
max = 100 fileSize: string = ""
showValue = true uploadedSize: string = ""
fileName = "" status: string = "uploading"
fileSize = "" cancelLabel: string = "Cancel upload"
uploadedSize = "" retryLabel: string = "Retry upload"
status = "uploading" class: string = ""
cancelLabel = "Cancel upload"
retryLabel = "Retry upload"
class = ""
} }
state currentValue = value state currentValue = value
state currentStatus = status state currentStatus = status
functions { functions {
function percent() { if (!Number(max)) { return 0 } return Math.max(0, Math.min(100, Math.round(Number(currentValue) / Number(max) * 100))) } shared function percent() { if (!Number(max)) { return 0 } return Math.max(0, Math.min(100, Math.round(Number(currentValue) / Number(max) * 100))) }
function detail(sourceEvent) { return { value: currentValue, max: max, percent: percent(), status: currentStatus, fileName: fileName, sourceEvent: sourceEvent } } client function detail(sourceEvent) { return { value: currentValue, max: max, percent: percent(), status: currentStatus, fileName: fileName, sourceEvent: sourceEvent } }
function cancelUpload(sourceEvent) { currentStatus = "cancelled"; $emit("cancel", detail(sourceEvent)) } client function cancelUpload(sourceEvent) { currentStatus = "cancelled"; output.cancel(detail(sourceEvent)) }
function retryUpload(sourceEvent) { currentValue = 0; currentStatus = "uploading"; $emit("retry", detail(sourceEvent)) } client function retryUpload(sourceEvent) { currentValue = 0; currentStatus = "uploading"; output.retry(detail(sourceEvent)) }
function completeUpload(sourceEvent) { currentValue = max; currentStatus = "complete"; $emit("complete", detail(sourceEvent)) } client function completeUpload(sourceEvent) { currentValue = max; currentStatus = "complete"; output.complete(detail(sourceEvent)) }
} }
view { view {
<div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--file-upload-progress {class}" data-status="{currentStatus}" aria-live="polite"> <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--file-upload-progress {class}" data-status="{currentStatus}" aria-live="polite">
+9 -9
View File
@@ -1,14 +1,14 @@
component Image { component Image {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
src = "" src: string = ""
alt = "" alt: string = ""
width = "" width: string = ""
height = "" height: string = ""
loading = "lazy" loading: string = "lazy"
rounded = false rounded: boolean = false
class = "" class: string = ""
} }
view { view {
+35 -32
View File
@@ -1,39 +1,42 @@
component InputGroup { component InputGroup {
outputs {
input(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
focus(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
blur(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
submit(payload: { value: string | number | boolean | null | object; name: string; sourceEvent: Event })
action(payload: { name: string; sourceEvent: Event })
}
props { props {
@event input = function size: string = "default"
@event change = function color: string = "primary"
@event focus = function id: string = ""
@event blur = function name: string = ""
@event submit = function label: string = "Input group"
@event action = function hiddenLabel: boolean = false
size = "default" value: string = ""
color = "primary" placeholder: string = ""
id = "" type: string = "text"
name = "" startText: string = ""
label = "Input group" endText: string = ""
hiddenLabel = false icon: string = ""
value = "" iconPosition: string = "start"
placeholder = "" actionLabel: string = ""
type = "text" helperText: string = ""
startText = "" cornerHint: string = ""
endText = "" error: string = ""
icon = "" inline: boolean = false
iconPosition = "start" variant: string = "normal"
actionLabel = "" readonly: boolean = false
helperText = "" disabled: boolean = false
cornerHint = "" required: boolean = false
error = "" class: string = ""
inline = false
variant = "normal"
readonly = false
disabled = false
required = false
class = ""
} }
functions { functions {
function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); $emit(nameEvent, { value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) } client function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); output[nameEvent]({ value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) }
function handleKeydown(sourceEvent) { if (sourceEvent.key === "Enter") { sourceEvent.stopPropagation(); $emit("submit", { value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) } } client function handleKeydown(sourceEvent) { if (sourceEvent.key === "Enter") { sourceEvent.stopPropagation(); output.submit({ value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) } }
function handleAction(sourceEvent) { $emit("action", { name: name, sourceEvent: sourceEvent }) } client function handleAction(sourceEvent) { output.action({ name: name, sourceEvent: sourceEvent }) }
} }
view { view {
<div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--input-group {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}"> <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--input-group {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
+75 -72
View File
@@ -1,62 +1,65 @@
component InputNumber { component InputNumber {
outputs {
input(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
increment(payload: { value: number; previousValue?: number; sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
decrement(payload: { value: number; previousValue?: number; sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
variant = "default" variant: string = "default"
class = "" class: string = ""
id = "" id: string = ""
name = "quantity" name: string = "quantity"
value = 0 value: number = 0
min = "" min: string = ""
max = "" max: string = ""
step = 1 step: number = 1
precision = "auto" precision: string = "auto"
label = "" label: string = ""
description = "" description: string = ""
helpText = "" helpText: string = ""
error = "" error: string = ""
invalid = false invalid: boolean = false
prefix = "" prefix: string = ""
suffix = "" suffix: string = ""
placeholder = "" placeholder: string = ""
autocomplete = "off" autocomplete: string = "off"
inputMode = "decimal" inputMode: string = "decimal"
ariaLabel = "" ariaLabel: string = ""
required = false required: boolean = false
disabled = false disabled: boolean = false
inputDisabled = false inputDisabled: boolean = false
buttonsDisabled = false buttonsDisabled: boolean = false
readonly = false readonly: boolean = false
allowInput = true allowInput: boolean = true
keyboard = true keyboard: boolean = true
wheel = false wheel: boolean = false
clamp = true clamp: boolean = true
fullWidth = false fullWidth: boolean = false
showButtons = true showButtons: boolean = true
showValidationMessage = true showValidationMessage: boolean = true
decrementLabel = "Decrease value" decrementLabel: string = "Decrease value"
incrementLabel = "Increase value" incrementLabel: string = "Increase value"
controlsLabel = "Quantity controls" controlsLabel: string = "Quantity controls"
requiredMessage = "A value is required." requiredMessage: string = "A value is required."
minMessage = "Value is below the minimum." minMessage: string = "Value is below the minimum."
maxMessage = "Value is above the maximum." maxMessage: string = "Value is above the maximum."
@event input = function
@event change = function
@event increment = function
@event decrement = function
} }
state currentValue = value state currentValue = value
state committedValue = value state committedValue = value
functions { functions {
function inputId() { shared function inputId() {
if (id !== "") { if (id !== "") {
return id return id
} }
@@ -68,15 +71,15 @@ component InputNumber {
return "input-number" return "input-number"
} }
function descriptionId() { shared function descriptionId() {
return inputId() + "-description" return inputId() + "-description"
} }
function messageId() { shared function messageId() {
return inputId() + "-message" return inputId() + "-message"
} }
function componentColor() { shared function componentColor() {
if (color === "secondary") { if (color === "secondary") {
return "var(--wire-color-secondary)" return "var(--wire-color-secondary)"
} }
@@ -100,23 +103,23 @@ component InputNumber {
return "var(--wire-color-primary)" return "var(--wire-color-primary)"
} }
function hasMin() { shared function hasMin() {
return min !== "" && min !== null && min !== undefined return min !== "" && min !== null && min !== undefined
} }
function hasMax() { shared function hasMax() {
return max !== "" && max !== null && max !== undefined return max !== "" && max !== null && max !== undefined
} }
function isBlank() { shared function isBlank() {
return currentValue === "" || currentValue === null || currentValue === undefined return currentValue === "" || currentValue === null || currentValue === undefined
} }
function normalizedStep() { shared function normalizedStep() {
return Number(step) > 0 ? Number(step) : 1 return Number(step) > 0 ? Number(step) : 1
} }
function inferredPrecision() { shared function inferredPrecision() {
if (precision !== "auto" && precision !== "") { if (precision !== "auto" && precision !== "") {
return Math.max(0, Number(precision) || 0) return Math.max(0, Number(precision) || 0)
} }
@@ -128,15 +131,15 @@ component InputNumber {
return 0 return 0
} }
function precisionFactor() { shared function precisionFactor() {
return Math.pow(10, inferredPrecision()) return Math.pow(10, inferredPrecision())
} }
function roundValue(nextValue) { shared function roundValue(nextValue) {
return Math.round(Number(nextValue) * precisionFactor()) / precisionFactor() return Math.round(Number(nextValue) * precisionFactor()) / precisionFactor()
} }
function clampValue(nextValue) { shared function clampValue(nextValue) {
nextValue = Number(nextValue) nextValue = Number(nextValue)
if (hasMin() && nextValue < Number(min)) { if (hasMin() && nextValue < Number(min)) {
@@ -150,15 +153,15 @@ component InputNumber {
return roundValue(nextValue) return roundValue(nextValue)
} }
function isBelowMin() { shared function isBelowMin() {
return !isBlank() && hasMin() && Number(currentValue) < Number(min) return !isBlank() && hasMin() && Number(currentValue) < Number(min)
} }
function isAboveMax() { shared function isAboveMax() {
return !isBlank() && hasMax() && Number(currentValue) > Number(max) return !isBlank() && hasMax() && Number(currentValue) > Number(max)
} }
function isInvalid() { shared function isInvalid() {
return ( return (
invalid || invalid ||
error !== "" || error !== "" ||
@@ -168,7 +171,7 @@ component InputNumber {
) )
} }
function validationMessage() { shared function validationMessage() {
if (error !== "") { if (error !== "") {
return error return error
} }
@@ -188,14 +191,14 @@ component InputNumber {
return "" return ""
} }
function hasMessage() { shared function hasMessage() {
return ( return (
helpText !== "" || helpText !== "" ||
(showValidationMessage && isInvalid() && validationMessage() !== "") (showValidationMessage && isInvalid() && validationMessage() !== "")
) )
} }
function describedBy() { shared function describedBy() {
if (description !== "" && hasMessage()) { if (description !== "" && hasMessage()) {
return descriptionId() + " " + messageId() return descriptionId() + " " + messageId()
} }
@@ -211,7 +214,7 @@ component InputNumber {
return "" return ""
} }
function decrementDisabled() { shared function decrementDisabled() {
return ( return (
disabled || disabled ||
readonly || readonly ||
@@ -220,7 +223,7 @@ component InputNumber {
) )
} }
function incrementDisabled() { shared function incrementDisabled() {
return ( return (
disabled || disabled ||
readonly || readonly ||
@@ -229,7 +232,7 @@ component InputNumber {
) )
} }
function dispatchInputNumberEvent( client function dispatchInputNumberEvent(
sourceEvent, sourceEvent,
eventName, eventName,
action, action,
@@ -262,7 +265,7 @@ component InputNumber {
root.dispatchEvent(customEvent) root.dispatchEvent(customEvent)
} }
function applyControlValue(nextValue, action, sourceEvent, previousValue) { client function applyControlValue(nextValue, action, sourceEvent, previousValue) {
previousValue = currentValue previousValue = currentValue
currentValue = clampValue(nextValue) currentValue = clampValue(nextValue)
committedValue = currentValue committedValue = currentValue
@@ -287,7 +290,7 @@ component InputNumber {
) )
} }
function incrementValue(sourceEvent, nextValue) { client function incrementValue(sourceEvent, nextValue) {
if (incrementDisabled()) { if (incrementDisabled()) {
return return
} }
@@ -303,7 +306,7 @@ component InputNumber {
applyControlValue(nextValue, "increment", sourceEvent) applyControlValue(nextValue, "increment", sourceEvent)
} }
function decrementValue(sourceEvent, nextValue) { client function decrementValue(sourceEvent, nextValue) {
if (decrementDisabled()) { if (decrementDisabled()) {
return return
} }
@@ -319,7 +322,7 @@ component InputNumber {
applyControlValue(nextValue, "decrement", sourceEvent) applyControlValue(nextValue, "decrement", sourceEvent)
} }
function handleInput(sourceEvent, previousValue, nextValue) { client function handleInput(sourceEvent, previousValue, nextValue) {
sourceEvent.stopPropagation() sourceEvent.stopPropagation()
previousValue = currentValue previousValue = currentValue
nextValue = sourceEvent.target.value nextValue = sourceEvent.target.value
@@ -338,7 +341,7 @@ component InputNumber {
) )
} }
function handleChange(sourceEvent, previousValue) { client function handleChange(sourceEvent, previousValue) {
sourceEvent.stopPropagation() sourceEvent.stopPropagation()
previousValue = committedValue previousValue = committedValue
@@ -358,7 +361,7 @@ component InputNumber {
) )
} }
function handleKeydown(sourceEvent) { client function handleKeydown(sourceEvent) {
if ( if (
!keyboard || !keyboard ||
disabled || disabled ||
@@ -384,7 +387,7 @@ component InputNumber {
} }
} }
function handleWheel(sourceEvent) { client function handleWheel(sourceEvent) {
if ( if (
!wheel || !wheel ||
disabled || disabled ||
+4 -4
View File
@@ -1,9 +1,9 @@
component Kbd { component Kbd {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
label = "⌘ K" label: string = "⌘ K"
class = "" class: string = ""
} }
view { <kbd class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--kbd {class}"><slot>{label}</slot></kbd> } view { <kbd class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--kbd {class}"><slot>{label}</slot></kbd> }
} }
+12 -9
View File
@@ -1,14 +1,17 @@
component LayoutSplitter { component LayoutSplitter {
outputs {
resizeStart(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
resize(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
resizeEnd(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
}
props { props {
@event resizeStart = function size: string = "default"
@event resize = function color: string = "primary"
@event resizeEnd = function columns: number = 2
size = "default" gap: string = "md"
color = "primary" maxWidth: string = "xl"
columns = 2 class: string = ""
gap = "md"
maxWidth = "xl"
class = ""
} }
view { view {
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--layout-splitter wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div> <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--layout-splitter wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div>
+12 -9
View File
@@ -1,14 +1,17 @@
component LegendIndicator { component LegendIndicator {
outputs {
toggle(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
}
props { props {
@event toggle = function size: string = "default"
@event select = function color: string = "primary"
size = "default" title: string = "Legend Indicator"
color = "primary" description: string = ""
title = "Legend Indicator" items: unknown[] = []
description = "" variant: string = "default"
items = [] class: string = ""
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--legend-indicator wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--legend-indicator wire-next--variant-{variant} {class}">
+8 -8
View File
@@ -1,13 +1,13 @@
component Link { component Link {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
label = "Link" label: string = "Link"
href = "#" href: string = "#"
target = "" target: string = ""
rel = "" rel: string = ""
external = false external: boolean = false
class = "" class: string = ""
} }
view { view {
+7 -7
View File
@@ -1,12 +1,12 @@
component List { component List {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
title = "List" title: string = "List"
description = "" description: string = ""
items = [] items: unknown[] = []
variant = "default" variant: string = "default"
class = "" class: string = ""
} }
view { view {
+12 -9
View File
@@ -1,14 +1,17 @@
component ListGroup { component ListGroup {
outputs {
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
}
props { props {
@event select = function size: string = "default"
@event change = function color: string = "primary"
size = "default" title: string = "List Group"
color = "primary" description: string = ""
title = "List Group" items: unknown[] = []
description = "" variant: string = "default"
items = [] class: string = ""
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--list-group wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--list-group wire-next--variant-{variant} {class}">
+7 -7
View File
@@ -1,12 +1,12 @@
component Map { component Map {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
title = "Map" title: string = "Map"
description = "" description: string = ""
items = [] items: unknown[] = []
variant = "default" variant: string = "default"
class = "" class: string = ""
} }
view { view {
+8 -8
View File
@@ -1,15 +1,15 @@
component Marquee { component Marquee {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
title = "Marquee" title: string = "Marquee"
description = "" description: string = ""
items = [] items: unknown[] = []
variant = "default" variant: string = "default"
class = "" class: string = ""
} }
state paused = false state paused: boolean = false
view { view {
<section <section
+13 -10
View File
@@ -1,15 +1,18 @@
component MegaMenu { component MegaMenu {
outputs {
open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
close(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
}
props { props {
@event open = function size: string = "default"
@event close = function color: string = "primary"
@event select = function label: string = "Mega Menu"
size = "default" items: unknown[] = []
color = "primary" active: string = ""
label = "Mega Menu" orientation: string = "horizontal"
items = [] class: string = ""
active = ""
orientation = "horizontal"
class = ""
} }
view { view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--mega-menu wire-next--{orientation} {class}" aria-label="{label}"> <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--mega-menu wire-next--{orientation} {class}" aria-label="{label}">
+602 -17
View File
@@ -1,23 +1,608 @@
component Modal { component Modal {
props { outputs {
@event open = function open(payload: { sourceEvent: Event })
@event close = function close(payload: { reason: string; sourceEvent: Event })
@event cancel = function cancel(payload: { sourceEvent: Event })
@event confirm = function confirm(payload: { sourceEvent: Event })
size = "default"
color = "primary"
title = "Modal"
description = ""
open = false
placement = "bottom"
closeLabel = "Close"
class = ""
} }
props {
open: boolean = false
defaultOpen: boolean = false
title: string = "Modal"
description: string = ""
icon: string = ""
label: string = "Modal dialog"
size: string = "md"
placement: string = "center"
color: string = "primary"
variant: string = "default"
showClose: boolean = true
closeLabel: string = "Close modal"
closeOnBackdrop: boolean = true
closeOnEscape: boolean = true
closeOnCancel: boolean = true
closeOnConfirm: boolean = false
showFooter: boolean = true
cancelLabel: string = "Cancel"
cancelIcon: string = ""
confirmLabel: string = "Confirm"
confirmIcon: string = ""
confirmDisabled: boolean = false
confirmLoading: boolean = false
destructive: boolean = false
triggerLabel: string = ""
triggerIcon: string = ""
scrollable: boolean = true
class: string = ""
}
state visible = defaultOpen
functions {
shared function isOpen() {
return open || visible
}
client function showModal(sourceEvent) {
visible = true
output.open({ sourceEvent: sourceEvent })
}
client function hideModal(reason, sourceEvent) {
visible = false
output.close({
reason: reason,
sourceEvent: sourceEvent
})
}
client function cancelModal(sourceEvent) {
output.cancel({ sourceEvent: sourceEvent })
if (closeOnCancel) {
hideModal("cancel", sourceEvent)
}
}
client function confirmModal(sourceEvent) {
if (confirmDisabled || confirmLoading) {
return
}
output.confirm({ sourceEvent: sourceEvent })
if (closeOnConfirm) {
hideModal("confirm", sourceEvent)
}
}
client function handleKeydown(sourceEvent) {
if (closeOnEscape && sourceEvent.key === "Escape") {
sourceEvent.preventDefault()
cancelModal(sourceEvent)
}
}
}
view { view {
<div data-show="{open}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--modal wire-next--placement-{placement} {class}" role="dialog" aria-modal="true" aria-label="{title}"> <div
<header><strong>{title}</strong><button type="button" aria-label="{closeLabel}">×</button></header> {...attrs}
{#if description}<p>{description}</p>{/if} data-ui-component="Modal"
<slot /> data-open='{open || visible ? "true" : "false"}'
data-size='{size}'
data-placement='{placement}'
data-color='{color}'
data-variant='{variant}'
data-scrollable='{scrollable ? "true" : "false"}'
data-destructive='{destructive ? "true" : "false"}'
class='wire-modal {class}'
>
{#if triggerLabel}
<button
type="button"
class="wire-modal__trigger"
aria-haspopup="dialog"
aria-expanded='{open || visible ? "true" : "false"}'
@click='showModal(event)'
>
{#if triggerIcon}
<span
class='{triggerIcon}'
aria-hidden="true"
>
</span>
{/if}
<span>{triggerLabel}</span>
</button>
{/if}
<span
class="wire-modal__trigger-slot"
@click='showModal(event)'
>
<slot
name="trigger"
>
</slot>
</span>
<div
class="wire-modal__layer"
data-show='{open || visible}'
role="presentation"
@keydown='handleKeydown(event)'
>
<button
type="button"
class="wire-modal__backdrop"
aria-label='{closeLabel}'
@click='if (closeOnBackdrop) { cancelModal(event) }'
>
</button>
<section
class="wire-modal__panel"
role="dialog"
aria-modal="true"
aria-label='{label || title}'
tabindex="-1"
>
<header
class="wire-modal__header"
>
<div
class="wire-modal__heading"
>
{#if icon}
<span
class='wire-modal__icon {icon}'
aria-hidden="true"
>
</span>
{/if}
<div
class="wire-modal__heading-copy"
>
<slot
name="header"
>
</slot>
{#if title}
<h2>{title}</h2>
{/if}
{#if description}
<p>{description}</p>
{/if}
</div>
</div>
{#if showClose}
<button
type="button"
class="wire-modal__close"
aria-label='{closeLabel}'
@click='hideModal("close-button", event)'
>
<span
class="icon-[lucide--x]"
aria-hidden="true"
>
</span>
</button>
{/if}
</header>
<div
class="wire-modal__body"
>
<slot></slot>
</div>
{#if showFooter}
<footer
class="wire-modal__footer"
>
<div
class="wire-modal__custom-footer"
>
<slot
name="footer"
>
</slot>
</div>
<div
class="wire-modal__actions"
>
{#if cancelLabel}
<button
type="button"
class="wire-modal__button wire-modal__button--secondary"
@click='cancelModal(event)'
>
{#if cancelIcon}
<span
class='{cancelIcon}'
aria-hidden="true"
>
</span>
{/if}
<span>{cancelLabel}</span>
</button>
{/if}
{#if confirmLabel}
<button
type="button"
class="wire-modal__button wire-modal__button--primary"
disabled='{confirmDisabled || confirmLoading}'
@click='confirmModal(event)'
>
{#if confirmLoading}
<span
class="wire-modal__spinner"
aria-hidden="true"
>
</span>
{:else if confirmIcon}
<span
class='{confirmIcon}'
aria-hidden="true"
>
</span>
{/if}
<span>{confirmLabel}</span>
</button>
{/if}
</div>
</footer>
{/if}
</section>
</div>
</div> </div>
} }
style {
.wire-modal {
--modal-accent: var(--wire-color-primary);
--modal-soft: var(--wire-color-primary-soft);
--modal-contrast: var(--wire-color-primary-contrast);
position: relative;
display: inline-flex;
}
.wire-modal[data-color="secondary"] {
--modal-accent: var(--wire-color-secondary);
--modal-soft: var(--wire-color-secondary-soft);
--modal-contrast: var(--wire-color-secondary-contrast);
}
.wire-modal[data-color="info"] {
--modal-accent: var(--wire-color-info);
--modal-soft: var(--wire-color-info-soft);
--modal-contrast: var(--wire-color-info-contrast);
}
.wire-modal[data-color="success"] {
--modal-accent: var(--wire-color-success);
--modal-soft: var(--wire-color-success-soft);
--modal-contrast: var(--wire-color-success-contrast);
}
.wire-modal[data-color="warning"] {
--modal-accent: var(--wire-color-warning);
--modal-soft: var(--wire-color-warning-soft);
--modal-contrast: var(--wire-color-warning-text);
}
.wire-modal[data-color="danger"],
.wire-modal[data-destructive="true"] {
--modal-accent: var(--wire-color-danger);
--modal-soft: var(--wire-color-danger-soft);
--modal-contrast: var(--wire-color-on-danger);
}
.wire-modal__trigger,
.wire-modal__trigger-slot {
display: inline-flex;
align-items: center;
gap: 0.55rem;
}
.wire-modal__trigger {
appearance: none;
min-height: 2.55rem;
padding: 0.65rem 1rem;
color: var(--modal-contrast);
background: var(--modal-accent);
border: 0;
border-radius: 0.8rem;
font: inherit;
font-size: 0.85rem;
font-weight: 650;
cursor: pointer;
}
.wire-modal__layer {
position: fixed;
inset: 0;
z-index: 1250;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.wire-modal[data-placement="top"] .wire-modal__layer {
align-items: flex-start;
padding-top: clamp(1rem, 8vh, 5rem);
}
.wire-modal__backdrop {
position: absolute;
inset: 0;
z-index: 0;
appearance: none;
padding: 0;
background: color-mix(in srgb, black 62%, transparent);
border: 0;
backdrop-filter: blur(9px);
}
.wire-modal__panel {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
width: min(32rem, calc(100vw - 2rem));
max-height: min(88vh, 52rem);
color: var(--wire-color-text);
background:
radial-gradient(
circle at 100% 0%,
color-mix(in srgb, var(--modal-accent) 9%, transparent),
transparent 34%
),
var(--wire-color-surface-raised);
border: 1px solid color-mix(in srgb, var(--modal-accent) 18%, var(--wire-color-border));
border-radius: 1.35rem;
box-shadow:
0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
0 40px 110px color-mix(in srgb, black 38%, transparent);
overflow: hidden;
}
.wire-modal[data-size="sm"] .wire-modal__panel {
width: min(25rem, calc(100vw - 2rem));
}
.wire-modal[data-size="lg"] .wire-modal__panel {
width: min(44rem, calc(100vw - 2rem));
}
.wire-modal[data-size="xl"] .wire-modal__panel {
width: min(64rem, calc(100vw - 2rem));
}
.wire-modal[data-size="full"] .wire-modal__panel {
width: calc(100vw - 2rem);
height: calc(100vh - 2rem);
max-height: none;
}
.wire-modal[data-variant="soft"] .wire-modal__panel {
background:
linear-gradient(145deg, var(--modal-soft), transparent 68%),
var(--wire-color-surface-raised);
}
.wire-modal[data-variant="outline"] .wire-modal__panel {
background: var(--wire-color-surface-raised);
box-shadow: 0 24px 72px color-mix(in srgb, black 24%, transparent);
}
.wire-modal[data-variant="solid"] .wire-modal__panel {
color: var(--modal-contrast);
background: var(--modal-accent);
border-color: color-mix(in srgb, white 20%, transparent);
}
.wire-modal__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
padding: 1.4rem 1.4rem 1.15rem;
border-bottom: 1px solid var(--wire-color-border);
}
.wire-modal__heading {
display: flex;
align-items: flex-start;
gap: 0.9rem;
min-width: 0;
}
.wire-modal__icon {
flex: 0 0 auto;
width: 1.3rem;
height: 1.3rem;
margin-top: 0.15rem;
color: var(--modal-accent);
}
.wire-modal[data-variant="solid"] .wire-modal__icon {
color: currentColor;
}
.wire-modal__heading-copy {
display: grid;
gap: 0.3rem;
min-width: 0;
}
.wire-modal__heading-copy h2,
.wire-modal__heading-copy p {
margin: 0;
}
.wire-modal__heading-copy h2 {
font-size: 1.08rem;
font-weight: 650;
line-height: 1.3;
}
.wire-modal__heading-copy p {
color: var(--wire-color-text-muted);
font-size: 0.8rem;
line-height: 1.55;
}
.wire-modal[data-variant="solid"] .wire-modal__heading-copy p {
color: color-mix(in srgb, currentColor 76%, transparent);
}
.wire-modal__close {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 2.35rem;
height: 2.35rem;
color: var(--wire-color-text-muted);
background: var(--wire-color-surface-soft);
border: 1px solid var(--wire-color-border);
border-radius: 0.75rem;
cursor: pointer;
}
.wire-modal__close:hover,
.wire-modal__close:focus-visible {
color: var(--modal-accent);
border-color: color-mix(in srgb, var(--modal-accent) 34%, var(--wire-color-border));
outline: none;
}
.wire-modal__body {
flex: 1 1 auto;
min-height: 0;
padding: 1.4rem;
}
.wire-modal[data-scrollable="true"] .wire-modal__body {
overflow: auto;
overscroll-behavior: contain;
}
.wire-modal__footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1rem 1.4rem 1.4rem;
border-top: 1px solid var(--wire-color-border);
}
.wire-modal__custom-footer:empty {
display: none;
}
.wire-modal__actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.65rem;
margin-left: auto;
}
.wire-modal__button {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
min-height: 2.55rem;
padding: 0.65rem 1rem;
border-radius: 0.78rem;
font: inherit;
font-size: 0.83rem;
font-weight: 650;
cursor: pointer;
transition: transform 150ms ease, opacity 150ms ease, border-color 150ms ease;
}
.wire-modal__button:active {
transform: scale(0.985);
}
.wire-modal__button--secondary {
color: var(--wire-color-text);
background: transparent;
border: 1px solid var(--wire-color-border);
}
.wire-modal__button--primary {
color: var(--modal-contrast);
background: var(--modal-accent);
border: 1px solid transparent;
}
.wire-modal__button:disabled {
opacity: 0.55;
cursor: not-allowed;
}
.wire-modal__spinner {
width: 0.95rem;
height: 0.95rem;
border: 2px solid color-mix(in srgb, currentColor 35%, transparent);
border-top-color: currentColor;
border-radius: 50%;
animation: wire-modal-spin 750ms linear infinite;
}
@keyframes wire-modal-spin {
to {
transform: rotate(360deg);
}
}
@media (max-width: 639px) {
.wire-modal__layer {
align-items: flex-end;
padding: 0;
}
.wire-modal__panel,
.wire-modal[data-size="sm"] .wire-modal__panel,
.wire-modal[data-size="lg"] .wire-modal__panel,
.wire-modal[data-size="xl"] .wire-modal__panel {
width: 100%;
max-height: 92vh;
border-radius: 1.25rem 1.25rem 0 0;
}
.wire-modal__footer {
align-items: stretch;
flex-direction: column;
}
.wire-modal__actions {
width: 100%;
}
.wire-modal__button {
flex: 1 1 0;
}
}
@media (prefers-reduced-motion: reduce) {
.wire-modal__button,
.wire-modal__spinner {
animation: none;
transition: none;
}
}
}
} }
+12 -9
View File
@@ -1,14 +1,17 @@
component Nav { component Nav {
outputs {
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
}
props { props {
@event select = function size: string = "default"
@event change = function color: string = "primary"
size = "default" label: string = "Nav"
color = "primary" items: unknown[] = []
label = "Nav" active: string = ""
items = [] orientation: string = "horizontal"
active = "" class: string = ""
orientation = "horizontal"
class = ""
} }
view { view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--nav wire-next--{orientation} {class}" aria-label="{label}"> <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--nav wire-next--{orientation} {class}" aria-label="{label}">
+34 -31
View File
@@ -1,45 +1,48 @@
component Navbar { component Navbar {
props { outputs {
@event toggle = function toggle(payload: { open: boolean })
@event open = function open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
@event close = function close(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
@event select = function select(payload: { item: string | number | boolean | null | object; value: string | number | boolean; level: string })
@event action = function action(payload: { item: string | number | boolean | null | object; value: string | number | boolean })
size = "default"
color = "primary"
label = "Primary navigation"
topbarLabel = "Utility navigation"
brand = {}
items = []
actions = []
active = ""
sticky = false
openOnHover = false
maxWidth = "full"
mobileLabel = "Toggle navigation"
class = ""
} }
state mobileOpen = false props {
size: string = "default"
color: string = "primary"
label: string = "Primary navigation"
topbarLabel: string = "Utility navigation"
brand: Record<string, unknown> = {}
items: unknown[] = []
actions: unknown[] = []
active: string = ""
sticky: boolean = false
openOnHover: boolean = false
maxWidth: string = "full"
mobileLabel: string = "Toggle navigation"
class: string = ""
}
state mobileOpen: boolean = false
functions { functions {
function toggleNavigation() { client function toggleNavigation() {
mobileOpen = !mobileOpen mobileOpen = !mobileOpen
$emit("toggle", { open: mobileOpen }) output.toggle({ open: mobileOpen })
$emit(mobileOpen ? "open" : "close", { source: "mobile" }) output[mobileOpen ? "open" : "close"]({ source: "mobile" })
} }
function selectItem(item, level) { client function selectItem(item, level) {
mobileOpen = false mobileOpen = false
$emit("select", { item: item, value: item.value || "", level: level }) output.select({ item: item, value: item.value || "", level: level })
} }
function selectAction(item) { client function selectAction(item) {
mobileOpen = false mobileOpen = false
$emit("action", { item: item, value: item.value || "" }) output.action({ item: item, value: item.value || "" })
} }
function isItemActive(item) { shared function isItemActive(item) {
if (!item) return false if (!item) return false
if (item.value && item.value === active) return true if (item.value && item.value === active) return true
if (!item.children || !item.children.length) return false if (!item.children || !item.children.length) return false
@@ -48,8 +51,8 @@ component Navbar {
}) })
} }
function toggleDropdown(event, item) { client function toggleDropdown(event, item) {
$emit(event.currentTarget.open ? "open" : "close", { output[event.currentTarget.open ? "open" : "close"]({
source: "menu", source: "menu",
item: item item: item
}) })
@@ -86,7 +89,7 @@ component Navbar {
<nav class="wire-navbar__menus" aria-label="{label}"> <nav class="wire-navbar__menus" aria-label="{label}">
{#each items as item} {#each items as item}
{#if item.children && item.children.length} {#if item.children && item.children.length}
<details class="wire-navbar__dropdown wire-navbar__dropdown--{item.type || 'dropdown'}" name="wire-navbar-menu" @toggle="toggleDropdown($event, item)"> <details class="wire-navbar__dropdown wire-navbar__dropdown--{item.type || 'dropdown'}" name="wire-navbar-menu" @toggle="toggleDropdown(event, item)">
<summary aria-current="{isItemActive(item) ? 'page' : ''}"> <summary aria-current="{isItemActive(item) ? 'page' : ''}">
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if} {#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
<span>{item.label}</span> <span>{item.label}</span>
+13 -10
View File
@@ -1,15 +1,18 @@
component Pagination { component Pagination {
outputs {
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
previous(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
next(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event change = function size: string = "default"
@event previous = function color: string = "primary"
@event next = function label: string = "Pagination"
size = "default" items: unknown[] = []
color = "primary" active: string = ""
label = "Pagination" orientation: string = "horizontal"
items = [] class: string = ""
active = ""
orientation = "horizontal"
class = ""
} }
view { view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--pagination wire-next--{orientation} {class}" aria-label="{label}"> <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--pagination wire-next--{orientation} {class}" aria-label="{label}">
+36 -33
View File
@@ -1,41 +1,44 @@
component PinInput { component PinInput {
outputs {
input(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
complete(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
paste(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
clear(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
error(payload: { error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)
}
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
label = "Verification code" label: string = "Verification code"
name = "pin" name: string = "pin"
value = "" value: string = ""
length = 4 length: number = 4
pattern = "[0-9]" pattern: string = "[0-9]"
type = "text" type: string = "text"
inputMode = "numeric" inputMode: string = "numeric"
placeholder = "○" placeholder: string = "○"
autocomplete = "one-time-code" autocomplete: string = "one-time-code"
masked = false masked: boolean = false
disabled = false disabled: boolean = false
readonly = false readonly: boolean = false
required = false required: boolean = false
autoFocus = false autoFocus: boolean = false
autoSubmit = false autoSubmit: boolean = false
allowPaste = true allowPaste: boolean = true
clearable = true clearable: boolean = true
clearLabel = "Clear code" clearLabel: string = "Clear code"
separator = "" separator: string = ""
groupSize = 0 groupSize: number = 0
helpText = "" helpText: string = ""
invalid = false invalid: boolean = false
validationMessage = "" validationMessage: string = ""
class = "" class: string = ""
@event input = function
@event change = function
@event complete = function
@event paste = function
@event clear = function
@event error = function
} }
functions { functions {
function inputIndexes() { shared function inputIndexes() {
return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].slice( return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].slice(
0, 0,
Math.max(1, Math.min(12, Number(length))) Math.max(1, Math.min(12, Number(length)))
+530 -15
View File
@@ -1,21 +1,536 @@
component Popover { component Popover {
props { outputs {
@event open = function toggle(payload: { open: boolean; sourceEvent: Event; reason?: object })
@event close = function open(payload: { sourceEvent: Event })
size = "default" close(payload: { reason: string; sourceEvent: Event })
color = "primary" action(payload: { href: string; sourceEvent: Event })
title = "Popover"
description = ""
open = false
placement = "bottom"
closeLabel = "Close"
class = ""
} }
props {
open: boolean = false
defaultOpen: boolean = false
triggerLabel: string = "Open popover"
triggerIcon: string = ""
title: string = ""
description: string = ""
icon: string = ""
placement: string = "bottom-start"
width: string = "md"
size: string = "default"
color: string = "primary"
variant: string = "raised"
showArrow: boolean = true
showClose: boolean = false
closeLabel: string = "Close popover"
closeOnOutside: boolean = true
closeOnEscape: boolean = true
actionLabel: string = ""
actionHref: string = ""
actionIcon: string = ""
closeOnAction: boolean = true
disabled: boolean = false
class: string = ""
}
state visible = defaultOpen
functions {
shared function isOpen() {
return open || visible
}
client function showPopover(sourceEvent) {
if (disabled) {
return
}
visible = true
output.open({ sourceEvent: sourceEvent })
output.toggle({ open: true, sourceEvent: sourceEvent })
}
client function hidePopover(reason, sourceEvent) {
visible = false
output.close({ reason: reason, sourceEvent: sourceEvent })
output.toggle({ open: false, reason: reason, sourceEvent: sourceEvent })
}
client function togglePopover(sourceEvent) {
if (isOpen()) {
hidePopover("toggle", sourceEvent)
} else {
showPopover(sourceEvent)
}
}
client function activateAction(sourceEvent) {
output.action({
href: actionHref,
sourceEvent: sourceEvent
})
if (closeOnAction) {
hidePopover("action", sourceEvent)
}
}
client function handleKeydown(sourceEvent) {
if (closeOnEscape && sourceEvent.key === "Escape") {
sourceEvent.preventDefault()
hidePopover("escape", sourceEvent)
}
}
}
view { view {
<div data-show="{open}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--popover wire-next--placement-{placement} {class}" role="region" aria-modal="false" aria-label="{title}"> <div
<header><strong>{title}</strong><button type="button" aria-label="{closeLabel}">×</button></header> {...attrs}
{#if description}<p>{description}</p>{/if} data-ui-component="Popover"
<slot /> data-open='{open || visible ? "true" : "false"}'
data-placement='{placement}'
data-width='{width}'
data-size='{size}'
data-color='{color}'
data-variant='{variant}'
class='wire-popover {class}'
@keydown='handleKeydown(event)'
>
<button
type="button"
class="wire-popover__trigger"
disabled='{disabled}'
aria-haspopup="dialog"
aria-expanded='{open || visible ? "true" : "false"}'
@click='togglePopover(event)'
>
<slot name="trigger"></slot>
{#if triggerIcon}
<span class='{triggerIcon}' aria-hidden="true"></span>
{/if}
{#if triggerLabel}
<span>{triggerLabel}</span>
{/if}
</button>
{#if closeOnOutside}
<button
type="button"
class="wire-popover__dismiss-layer"
data-show='{open || visible}'
aria-label='{closeLabel}'
@click='hidePopover("outside", event)'
></button>
{/if}
<section
class="wire-popover__panel"
data-show='{open || visible}'
role="dialog"
aria-label='{title || triggerLabel}'
>
{#if showArrow}
<span class="wire-popover__arrow" aria-hidden="true"></span>
{/if}
{#if title || description || icon || showClose}
<header class="wire-popover__header">
<div class="wire-popover__heading">
{#if icon}
<span class='wire-popover__icon {icon}' aria-hidden="true"></span>
{/if}
<div class="wire-popover__heading-copy">
<slot name="header"></slot>
{#if title}
<h3>{title}</h3>
{/if}
{#if description}
<p>{description}</p>
{/if}
</div>
</div>
{#if showClose}
<button
type="button"
class="wire-popover__close"
aria-label='{closeLabel}'
@click='hidePopover("close-button", event)'
>
<span class="icon-[lucide--x]" aria-hidden="true"></span>
</button>
{/if}
</header>
{/if}
<div class="wire-popover__body">
<slot></slot>
</div>
{#if actionLabel}
<footer class="wire-popover__footer">
{#if actionHref}
<a
href='{actionHref}'
class="wire-popover__action"
@click='activateAction(event)'
>
<span>{actionLabel}</span>
{#if actionIcon}
<span class='{actionIcon}' aria-hidden="true"></span>
{:else}
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
{/if}
</a>
{:else}
<button
type="button"
class="wire-popover__action"
@click='activateAction(event)'
>
<span>{actionLabel}</span>
{#if actionIcon}
<span class='{actionIcon}' aria-hidden="true"></span>
{:else}
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
{/if}
</button>
{/if}
</footer>
{/if}
<slot name="footer"></slot>
</section>
</div> </div>
} }
style {
.wire-popover {
--popover-accent: var(--wire-color-primary);
--popover-soft: var(--wire-color-primary-soft);
--popover-contrast: var(--wire-color-primary-contrast);
position: relative;
display: inline-flex;
}
.wire-popover[data-color="secondary"] {
--popover-accent: var(--wire-color-secondary);
--popover-soft: var(--wire-color-secondary-soft);
--popover-contrast: var(--wire-color-secondary-contrast);
}
.wire-popover[data-color="info"] {
--popover-accent: var(--wire-color-info);
--popover-soft: var(--wire-color-info-soft);
--popover-contrast: var(--wire-color-info-contrast);
}
.wire-popover[data-color="success"] {
--popover-accent: var(--wire-color-success);
--popover-soft: var(--wire-color-success-soft);
--popover-contrast: var(--wire-color-success-contrast);
}
.wire-popover[data-color="warning"] {
--popover-accent: var(--wire-color-warning);
--popover-soft: var(--wire-color-warning-soft);
--popover-contrast: var(--wire-color-warning-text);
}
.wire-popover[data-color="danger"] {
--popover-accent: var(--wire-color-danger);
--popover-soft: var(--wire-color-danger-soft);
--popover-contrast: var(--wire-color-on-danger);
}
.wire-popover__trigger {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.55rem;
min-height: 2.55rem;
padding: 0.65rem 0.9rem;
color: var(--wire-color-text);
background: var(--wire-color-surface-raised);
border: 1px solid var(--wire-color-border);
border-radius: 0.78rem;
font: inherit;
font-size: 0.84rem;
font-weight: 600;
cursor: pointer;
transition: color 150ms ease, border-color 150ms ease, background-color 150ms ease;
}
.wire-popover__trigger:hover,
.wire-popover__trigger:focus-visible,
.wire-popover[data-open="true"] .wire-popover__trigger {
color: var(--popover-accent);
background: var(--popover-soft);
border-color: color-mix(in srgb, var(--popover-accent) 38%, var(--wire-color-border));
outline: none;
}
.wire-popover__trigger:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.wire-popover[data-size="sm"] .wire-popover__trigger {
min-height: 2.2rem;
padding: 0.5rem 0.72rem;
font-size: 0.78rem;
}
.wire-popover[data-size="lg"] .wire-popover__trigger {
min-height: 2.9rem;
padding: 0.78rem 1.05rem;
font-size: 0.9rem;
}
.wire-popover__dismiss-layer {
position: fixed;
inset: 0;
z-index: 1120;
appearance: none;
padding: 0;
background: transparent;
border: 0;
}
.wire-popover__panel {
position: absolute;
z-index: 1121;
top: calc(100% + 0.72rem);
left: 0;
width: 18rem;
max-width: calc(100vw - 1.5rem);
color: var(--wire-color-text);
background:
linear-gradient(145deg, color-mix(in srgb, var(--popover-accent) 5%, transparent), transparent 62%),
color-mix(in srgb, var(--wire-color-surface-raised) 97%, transparent);
border: 1px solid color-mix(in srgb, var(--popover-accent) 18%, var(--wire-color-border));
border-radius: 1rem;
box-shadow:
0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
0 24px 64px color-mix(in srgb, black 22%, transparent);
backdrop-filter: blur(18px);
}
.wire-popover[data-width="sm"] .wire-popover__panel {
width: 14rem;
}
.wire-popover[data-width="lg"] .wire-popover__panel {
width: 24rem;
}
.wire-popover[data-width="xl"] .wire-popover__panel {
width: 32rem;
}
.wire-popover[data-width="trigger"] .wire-popover__panel {
width: 100%;
min-width: 100%;
}
.wire-popover[data-placement="bottom-end"] .wire-popover__panel {
right: 0;
left: auto;
}
.wire-popover[data-placement="top-start"] .wire-popover__panel {
top: auto;
bottom: calc(100% + 0.72rem);
}
.wire-popover[data-placement="top-end"] .wire-popover__panel {
top: auto;
right: 0;
bottom: calc(100% + 0.72rem);
left: auto;
}
.wire-popover[data-placement="left"] .wire-popover__panel {
top: 50%;
right: calc(100% + 0.72rem);
left: auto;
transform: translateY(-50%);
}
.wire-popover[data-placement="right"] .wire-popover__panel {
top: 50%;
left: calc(100% + 0.72rem);
transform: translateY(-50%);
}
.wire-popover[data-variant="soft"] .wire-popover__panel {
background:
linear-gradient(145deg, var(--popover-soft), transparent 70%),
var(--wire-color-surface-raised);
box-shadow: 0 18px 48px color-mix(in srgb, black 16%, transparent);
}
.wire-popover[data-variant="outline"] .wire-popover__panel {
background: var(--wire-color-surface-raised);
box-shadow: none;
}
.wire-popover[data-variant="solid"] .wire-popover__panel {
color: var(--popover-contrast);
background: var(--popover-accent);
border-color: color-mix(in srgb, white 20%, transparent);
}
.wire-popover__arrow {
position: absolute;
top: -0.35rem;
left: 1.25rem;
width: 0.7rem;
height: 0.7rem;
background: inherit;
border-top: 1px solid color-mix(in srgb, var(--popover-accent) 18%, var(--wire-color-border));
border-left: 1px solid color-mix(in srgb, var(--popover-accent) 18%, var(--wire-color-border));
transform: rotate(45deg);
}
.wire-popover[data-placement="bottom-end"] .wire-popover__arrow {
right: 1.25rem;
left: auto;
}
.wire-popover[data-placement="top-start"] .wire-popover__arrow,
.wire-popover[data-placement="top-end"] .wire-popover__arrow {
top: auto;
bottom: -0.35rem;
transform: rotate(225deg);
}
.wire-popover[data-placement="left"] .wire-popover__arrow {
top: 50%;
right: -0.35rem;
left: auto;
transform: translateY(-50%) rotate(135deg);
}
.wire-popover[data-placement="right"] .wire-popover__arrow {
top: 50%;
left: -0.35rem;
transform: translateY(-50%) rotate(-45deg);
}
.wire-popover__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.8rem;
padding: 1rem 1rem 0.8rem;
border-bottom: 1px solid var(--wire-color-border);
}
.wire-popover__heading {
display: flex;
align-items: flex-start;
gap: 0.7rem;
min-width: 0;
}
.wire-popover__icon {
flex: 0 0 auto;
width: 1.05rem;
height: 1.05rem;
margin-top: 0.15rem;
color: var(--popover-accent);
}
.wire-popover[data-variant="solid"] .wire-popover__icon {
color: currentColor;
}
.wire-popover__heading-copy {
display: grid;
gap: 0.2rem;
min-width: 0;
}
.wire-popover__heading-copy h3,
.wire-popover__heading-copy p {
margin: 0;
}
.wire-popover__heading-copy h3 {
font-size: 0.9rem;
font-weight: 650;
}
.wire-popover__heading-copy p {
color: var(--wire-color-text-muted);
font-size: 0.74rem;
line-height: 1.5;
}
.wire-popover[data-variant="solid"] .wire-popover__heading-copy p {
color: color-mix(in srgb, currentColor 76%, transparent);
}
.wire-popover__close {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 2rem;
height: 2rem;
color: var(--wire-color-text-muted);
background: var(--wire-color-surface-soft);
border: 1px solid var(--wire-color-border);
border-radius: 0.65rem;
cursor: pointer;
}
.wire-popover__body {
padding: 1rem;
color: var(--wire-color-text-muted);
font-size: 0.8rem;
line-height: 1.6;
}
.wire-popover__footer {
padding: 0 1rem 1rem;
}
.wire-popover__action {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.45rem;
min-height: 2.35rem;
padding: 0.58rem 0.8rem;
color: var(--popover-contrast);
background: var(--popover-accent);
border: 0;
border-radius: 0.7rem;
font: inherit;
font-size: 0.78rem;
font-weight: 650;
text-decoration: none;
cursor: pointer;
}
@media (max-width: 639px) {
.wire-popover__panel {
position: fixed;
right: 0.75rem;
bottom: 0.75rem;
left: 0.75rem;
top: auto;
width: auto;
max-width: none;
transform: none;
}
.wire-popover__arrow {
display: none;
}
}
}
} }
+44 -41
View File
@@ -1,49 +1,52 @@
component RangeSlider { component RangeSlider {
outputs {
input(payload: ({ value: number; name: string; sourceEvent: Event }) | ({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[]))
change(payload: ({ value: number; name: string; sourceEvent: Event }) | ({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[]))
focus(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
blur(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event input = function size: string = "default"
@event change = function color: string = "primary"
@event focus = function id: string = ""
@event blur = function name: string = ""
size = "default" label: string = "Range"
color = "primary" hiddenLabel: boolean = false
id = "" placeholder: string = ""
name = "" variant: string = "normal"
label = "Range" icon: string = ""
hiddenLabel = false iconPosition: string = "start"
placeholder = "" value: number = 50
variant = "normal" min: number = 0
icon = "" max: number = 100
iconPosition = "start" step: number = 1
value = 50 showValue: boolean = true
min = 0 showBounds: boolean = true
max = 100 showSteps: boolean = false
step = 1 marks: unknown[] = []
showValue = true helperText: string = ""
showBounds = true cornerHint: string = ""
showSteps = false error: string = ""
marks = [] inline: boolean = false
helperText = "" readonly: boolean = false
cornerHint = "" disabled: boolean = false
error = "" required: boolean = false
inline = false class: string = ""
readonly = false
disabled = false
required = false
class = ""
} }
state currentValue = value state currentValue = value
functions { functions {
function percent() { if (Number(max) === Number(min)) { return 0 } return (Number(currentValue) - Number(min)) / (Number(max) - Number(min)) * 100 } shared function percent() { if (Number(max) === Number(min)) { return 0 } return (Number(currentValue) - Number(min)) / (Number(max) - Number(min)) * 100 }
function setValue(nextValue, sourceEvent, commit, detail, root, slider, input, output, progressValue) { if (readonly || disabled) { return } currentValue = Number(nextValue); root = sourceEvent.currentTarget.closest(".wire-next--range-slider"); if (root) { slider = root.querySelector("[role=slider]"); input = root.querySelector("input[name='" + name + "']"); output = root.querySelector("output"); progressValue = (currentValue - Number(min)) / (Number(max) - Number(min)) * 100; if (slider) { slider.setAttribute("aria-valuenow", String(currentValue)); slider.style.setProperty("--wire-range-progress", progressValue + "%") } if (input) { input.value = String(currentValue) } if (output) { output.textContent = String(currentValue) } } detail = { value: currentValue, min: min, max: max, step: step, name: name, sourceEvent: sourceEvent }; $emit("input", detail); if (commit) { $emit("change", detail) } } client function setValue(nextValue, sourceEvent, commit, detail, root, slider, input, outputElement, progressValue) { if (readonly || disabled) { return } currentValue = Number(nextValue); root = sourceEvent.currentTarget.closest(".wire-next--range-slider"); if (root) { slider = root.querySelector("[role=slider]"); input = root.querySelector("input[name='" + name + "']"); outputElement = root.querySelector("output"); progressValue = (currentValue - Number(min)) / (Number(max) - Number(min)) * 100; if (slider) { slider.setAttribute("aria-valuenow", String(currentValue)); slider.style.setProperty("--wire-range-progress", progressValue + "%") } if (input) { input.value = String(currentValue) } if (outputElement) { outputElement.textContent = String(currentValue) } } detail = { value: currentValue, min: min, max: max, step: step, name: name, sourceEvent: sourceEvent }; output.input(detail); if (commit) { output.change(detail) } }
function valueFromPointer(sourceEvent, rect, ratio) { rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); return Number(min) + ratio * (Number(max) - Number(min)) } client function valueFromPointer(sourceEvent, rect, ratio) { rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); return Number(min) + ratio * (Number(max) - Number(min)) }
function beginSlide(sourceEvent, rect, ratio, rawValue) { if (readonly || disabled) { return } sourceEvent.currentTarget.setPointerCapture(sourceEvent.pointerId); rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function beginSlide(sourceEvent, rect, ratio, rawValue) { if (readonly || disabled) { return } sourceEvent.currentTarget.setPointerCapture(sourceEvent.pointerId); rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function moveSlide(sourceEvent, rect, ratio, rawValue) { if (!sourceEvent.currentTarget.hasPointerCapture(sourceEvent.pointerId)) { return } rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function moveSlide(sourceEvent, rect, ratio, rawValue) { if (!sourceEvent.currentTarget.hasPointerCapture(sourceEvent.pointerId)) { return } rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function endSlide(sourceEvent, rect, ratio, rawValue) { rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); if (sourceEvent.currentTarget.hasPointerCapture(sourceEvent.pointerId)) { sourceEvent.currentTarget.releasePointerCapture(sourceEvent.pointerId) } $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function endSlide(sourceEvent, rect, ratio, rawValue) { rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); if (sourceEvent.currentTarget.hasPointerCapture(sourceEvent.pointerId)) { sourceEvent.currentTarget.releasePointerCapture(sourceEvent.pointerId) } output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function selectMark(sourceEvent, root, slider, input, output, nextValue, progressValue, detail) { if (readonly || disabled) { return } root = sourceEvent.currentTarget.closest(".wire-next--range-slider"); slider = root.querySelector(".wire-next__custom-slider"); input = root.querySelector(".wire-next__range-value"); output = root.querySelector("output"); nextValue = Number(sourceEvent.currentTarget.dataset.value); currentValue = nextValue; progressValue = (nextValue - Number(min)) / (Number(max) - Number(min)) * 100; slider.setAttribute("aria-valuenow", String(nextValue)); slider.style.setProperty("--wire-range-progress", progressValue + "%"); input.setAttribute("value", String(nextValue)); output.replaceChildren(String(nextValue)); detail = { value: nextValue, min: min, max: max, step: step, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) } client function selectMark(sourceEvent, root, slider, input, outputElement, nextValue, progressValue, detail) { if (readonly || disabled) { return } root = sourceEvent.currentTarget.closest(".wire-next--range-slider"); slider = root.querySelector(".wire-next__custom-slider"); input = root.querySelector(".wire-next__range-value"); outputElement = root.querySelector("output"); nextValue = Number(sourceEvent.currentTarget.dataset.value); currentValue = nextValue; progressValue = (nextValue - Number(min)) / (Number(max) - Number(min)) * 100; slider.setAttribute("aria-valuenow", String(nextValue)); slider.style.setProperty("--wire-range-progress", progressValue + "%"); input.setAttribute("value", String(nextValue)); outputElement.replaceChildren(String(nextValue)); detail = { value: nextValue, min: min, max: max, step: step, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
function handleKey(sourceEvent) { if (sourceEvent.key === "ArrowRight") { sourceEvent.preventDefault(); currentValue = Math.min(Number(max), Number(currentValue) + Number(step)); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "ArrowLeft") { sourceEvent.preventDefault(); currentValue = Math.max(Number(min), Number(currentValue) - Number(step)); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "Home") { sourceEvent.preventDefault(); currentValue = Number(min); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "End") { sourceEvent.preventDefault(); currentValue = Number(max); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) } } client function handleKey(sourceEvent) { if (sourceEvent.key === "ArrowRight") { sourceEvent.preventDefault(); currentValue = Math.min(Number(max), Number(currentValue) + Number(step)); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "ArrowLeft") { sourceEvent.preventDefault(); currentValue = Math.max(Number(min), Number(currentValue) - Number(step)); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "Home") { sourceEvent.preventDefault(); currentValue = Number(min); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "End") { sourceEvent.preventDefault(); currentValue = Number(max); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) } }
function emitFocus(nameEvent, sourceEvent) { $emit(nameEvent, { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function emitFocus(nameEvent, sourceEvent) { output[nameEvent]({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function decreaseValue(sourceEvent) { currentValue = Math.max(Number(min), Number(currentValue) - Number(step)); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function decreaseValue(sourceEvent) { currentValue = Math.max(Number(min), Number(currentValue) - Number(step)); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function increaseValue(sourceEvent) { currentValue = Math.min(Number(max), Number(currentValue) + Number(step)); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function increaseValue(sourceEvent) { currentValue = Math.min(Number(max), Number(currentValue) + Number(step)); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
} }
view { view {
<div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--range-slider {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}"> <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--range-slider {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
+12 -9
View File
@@ -1,14 +1,17 @@
component Rating { component Rating {
outputs {
input(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
}
props { props {
@event input = function size: string = "default"
@event change = function color: string = "primary"
size = "default" title: string = "Rating"
color = "primary" description: string = ""
title = "Rating" items: unknown[] = []
description = "" variant: string = "default"
items = [] class: string = ""
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--rating wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--rating wire-next--variant-{variant} {class}">
+11 -8
View File
@@ -1,13 +1,16 @@
component Scrollspy { component Scrollspy {
outputs {
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
}
props { props {
@event change = function size: string = "default"
size = "default" color: string = "primary"
color = "primary" label: string = "Scrollspy"
label = "Scrollspy" items: unknown[] = []
items = [] active: string = ""
active = "" orientation: string = "horizontal"
orientation = "horizontal" class: string = ""
class = ""
} }
view { view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--scrollspy wire-next--{orientation} {class}" aria-label="{label}"> <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--scrollspy wire-next--{orientation} {class}" aria-label="{label}">
+13 -13
View File
@@ -1,18 +1,18 @@
component SearchBox { component SearchBox {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
label = "Search Box" label: string = "Search Box"
name = "" name: string = ""
value = "" value: string = ""
placeholder = "" placeholder: string = ""
type = "search" type: string = "search"
min = "" min: string = ""
max = "" max: string = ""
step = "" step: string = ""
disabled = false disabled: boolean = false
required = false required: boolean = false
class = "" class: string = ""
} }
state query = value state query = value
+24 -21
View File
@@ -1,36 +1,39 @@
component Sidebar { component Sidebar {
props { outputs {
@event toggle = function toggle(payload: { open: boolean })
@event open = function open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
@event close = function close(payload: { source: string })
@event select = function select(payload: { item: string | number | boolean | null | object; value: string | number | boolean; level: string })
size = "default"
color = "primary"
label = "Sidebar"
items = []
active = ""
orientation = "horizontal"
mobileLabel = "Open navigation"
class = ""
} }
state mobileOpen = false props {
size: string = "default"
color: string = "primary"
label: string = "Sidebar"
items: unknown[] = []
active: string = ""
orientation: string = "horizontal"
mobileLabel: string = "Open navigation"
class: string = ""
}
state mobileOpen: boolean = false
functions { functions {
function toggleSidebar() { client function toggleSidebar() {
mobileOpen = !mobileOpen mobileOpen = !mobileOpen
$emit("toggle", { open: mobileOpen }) output.toggle({ open: mobileOpen })
$emit(mobileOpen ? "open" : "close", { source: "mobile" }) output[mobileOpen ? "open" : "close"]({ source: "mobile" })
} }
function closeSidebar() { client function closeSidebar() {
mobileOpen = false mobileOpen = false
$emit("close", { source: "mobile" }) output.close({ source: "mobile" })
} }
function selectItem(item, level) { client function selectItem(item, level) {
mobileOpen = false mobileOpen = false
$emit("select", { item: item, value: item.value || "", level: level }) output.select({ item: item, value: item.value || "", level: level })
} }
} }
+14 -11
View File
@@ -1,16 +1,19 @@
component Stepper { component Stepper {
outputs {
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
previous(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
next(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
complete(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event change = function size: string = "default"
@event previous = function color: string = "primary"
@event next = function label: string = "Stepper"
@event complete = function items: unknown[] = []
size = "default" active: string = ""
color = "primary" orientation: string = "horizontal"
label = "Stepper" class: string = ""
items = []
active = ""
orientation = "horizontal"
class = ""
} }
view { view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--stepper wire-next--{orientation} {class}" aria-label="{label}"> <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--stepper wire-next--{orientation} {class}" aria-label="{label}">
+48 -45
View File
@@ -1,62 +1,65 @@
component StrongPassword { component StrongPassword {
props { outputs {
size = "default" input(payload: { value: string; score: number; maximumScore: number; percent: number; level: string })
color = "primary" change(payload: { value: string; score: number; maximumScore: number; percent: number; level: string })
label = "Password" strength(payload: { value: string; score: number; maximumScore: number; percent: number; level: string })
name = "password"
value = ""
placeholder = "Create a strong password"
autocomplete = "new-password"
minLength = 8
specialCharactersSet = "!@#$%^&*()_+-=[]{}|;:,.<>?"
requireLowercase = true
requireUppercase = true
requireNumber = true
requireSpecialCharacter = true
showRequirements = true
presentation = "inline"
hintText = "Use a unique password you do not use elsewhere."
emptyLabel = "Enter a password"
weakLabel = "Weak"
fairLabel = "Fair"
goodLabel = "Good"
strongLabel = "Strong"
disabled = false
readonly = false
required = false
invalid = false
validationMessage = ""
class = ""
@event input = function
@event change = function
@event strength = function
} }
props {
size: string = "default"
color: string = "primary"
label: string = "Password"
name: string = "password"
value: string = ""
placeholder: string = "Create a strong password"
autocomplete: string = "new-password"
minLength: number = 8
specialCharactersSet: string = "!@#$%^&*()_+-=[]{}|;:,.<>?"
requireLowercase: boolean = true
requireUppercase: boolean = true
requireNumber: boolean = true
requireSpecialCharacter: boolean = true
showRequirements: boolean = true
presentation: string = "inline"
hintText: string = "Use a unique password you do not use elsewhere."
emptyLabel: string = "Enter a password"
weakLabel: string = "Weak"
fairLabel: string = "Fair"
goodLabel: string = "Good"
strongLabel: string = "Strong"
disabled: boolean = false
readonly: boolean = false
required: boolean = false
invalid: boolean = false
validationMessage: string = ""
class: string = ""
}
state password = value state password = value
state detailsOpen = false state detailsOpen: boolean = false
functions { functions {
function hasMinimumLength() { shared function hasMinimumLength() {
return password.length >= Number(minLength) return password.length >= Number(minLength)
} }
function hasLowercase() { shared function hasLowercase() {
return password !== password.toUpperCase() return password !== password.toUpperCase()
} }
function hasUppercase() { shared function hasUppercase() {
return password !== password.toLowerCase() return password !== password.toLowerCase()
} }
function hasNumber() { shared function hasNumber() {
return containsCharacterAt("0123456789", 0) return containsCharacterAt("0123456789", 0)
} }
function hasSpecialCharacter() { shared function hasSpecialCharacter() {
return containsCharacterAt(specialCharactersSet, 0) return containsCharacterAt(specialCharactersSet, 0)
} }
function containsCharacterAt(characters, index) { shared function containsCharacterAt(characters, index) {
if (index >= characters.length) { if (index >= characters.length) {
return false return false
} }
@@ -66,19 +69,19 @@ component StrongPassword {
return containsCharacterAt(characters, index + 1) return containsCharacterAt(characters, index + 1)
} }
function score() { shared function score() {
return (hasMinimumLength() ? 1 : 0) + (requireLowercase && hasLowercase() ? 1 : 0) + (requireUppercase && hasUppercase() ? 1 : 0) + (requireNumber && hasNumber() ? 1 : 0) + (requireSpecialCharacter && hasSpecialCharacter() ? 1 : 0) return (hasMinimumLength() ? 1 : 0) + (requireLowercase && hasLowercase() ? 1 : 0) + (requireUppercase && hasUppercase() ? 1 : 0) + (requireNumber && hasNumber() ? 1 : 0) + (requireSpecialCharacter && hasSpecialCharacter() ? 1 : 0)
} }
function maximumScore() { shared function maximumScore() {
return 1 + (requireLowercase ? 1 : 0) + (requireUppercase ? 1 : 0) + (requireNumber ? 1 : 0) + (requireSpecialCharacter ? 1 : 0) return 1 + (requireLowercase ? 1 : 0) + (requireUppercase ? 1 : 0) + (requireNumber ? 1 : 0) + (requireSpecialCharacter ? 1 : 0)
} }
function strengthPercent() { shared function strengthPercent() {
return Math.round(score() / maximumScore() * 100) return Math.round(score() / maximumScore() * 100)
} }
function strengthLevel() { shared function strengthLevel() {
if (!password) { if (!password) {
return "empty" return "empty"
} }
@@ -94,7 +97,7 @@ component StrongPassword {
return "strong" return "strong"
} }
function strengthLabel() { shared function strengthLabel() {
if (strengthLevel() === "weak") { if (strengthLevel() === "weak") {
return weakLabel return weakLabel
} }
@@ -137,8 +140,8 @@ component StrongPassword {
aria-describedby="{validationMessage ? name + '-validation' : hintText ? name + '-hint' : ''}" aria-describedby="{validationMessage ? name + '-validation' : hintText ? name + '-hint' : ''}"
@focus="detailsOpen = true" @focus="detailsOpen = true"
@blur="detailsOpen = false" @blur="detailsOpen = false"
@input="password = event.target.value; $emit('input', { value: password, score: score(), maximumScore: maximumScore(), percent: strengthPercent(), level: strengthLevel() }); $emit('strength', { value: password, score: score(), maximumScore: maximumScore(), percent: strengthPercent(), level: strengthLevel() })" @input="password = event.target.value; output.input({ value: password, score: score(), maximumScore: maximumScore(), percent: strengthPercent(), level: strengthLevel() }); output.strength({ value: password, score: score(), maximumScore: maximumScore(), percent: strengthPercent(), level: strengthLevel() })"
@change="$emit('change', { value: password, score: score(), maximumScore: maximumScore(), percent: strengthPercent(), level: strengthLevel() })" @change="output.change({ value: password, score: score(), maximumScore: maximumScore(), percent: strengthPercent(), level: strengthLevel() })"
/> />
{#if presentation === "popover"} {#if presentation === "popover"}
+7 -7
View File
@@ -1,12 +1,12 @@
component StyledIcon { component StyledIcon {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
title = "Styled Icon" title: string = "Styled Icon"
description = "" description: string = ""
items = [] items: unknown[] = []
variant = "default" variant: string = "default"
class = "" class: string = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--styled-icon wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--styled-icon wire-next--variant-{variant} {class}">
+7 -7
View File
@@ -1,12 +1,12 @@
component Tabs { component Tabs {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
label = "Tabs" label: string = "Tabs"
items = [] items: unknown[] = []
active = "" active: string = ""
orientation = "horizontal" orientation: string = "horizontal"
class = "" class: string = ""
} }
state activeValue = active || (items[0] ? (items[0].value || items[0].id || "0") : "") state activeValue = active || (items[0] ? (items[0].value || items[0].id || "0") : "")
+42 -39
View File
@@ -1,51 +1,54 @@
component TimePicker { component TimePicker {
outputs {
input(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
change(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
focus(payload: { value: string; name: string; sourceEvent: Event })
blur(payload: { value: string; name: string; sourceEvent: Event })
open(payload: { value: string; name: string; sourceEvent: Event })
close(payload: { value: string; name: string; sourceEvent: Event })
invalid(payload: { name: string; message: string; sourceEvent: Event })
}
props { props {
@event input = function size: string = "default"
@event change = function color: string = "primary"
@event focus = function id: string = ""
@event blur = function name: string = ""
@event open = function label: string = "Time"
@event close = function hiddenLabel: boolean = false
@event invalid = function value: string = ""
size = "default" placeholder: string = ""
color = "primary" variant: string = "normal"
id = "" icon: string = "icon-[lucide--clock-3]"
name = "" iconPosition: string = "end"
label = "Time" helperText: string = ""
hiddenLabel = false cornerHint: string = ""
value = "" error: string = ""
placeholder = "" inline: boolean = false
variant = "normal" min: string = ""
icon = "icon-[lucide--clock-3]" max: string = ""
iconPosition = "end" step: string = ""
helperText = "" format: string = "24"
cornerHint = "" minuteStep: number = 5
error = ""
inline = false
min = ""
max = ""
step = ""
format = "24"
minuteStep = 5
hours = ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"] hours = ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"]
minutes = ["00", "05", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55"] minutes = ["00", "05", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55"]
readonly = false readonly: boolean = false
disabled = false disabled: boolean = false
required = false required: boolean = false
class = "" class: string = ""
} }
state currentValue = value state currentValue = value
state selectedHour = value ? value.split(":")[0] : "00" state selectedHour = value ? value.split(":")[0] : "00"
state selectedMinute = value ? value.split(":")[1] : "00" state selectedMinute = value ? value.split(":")[1] : "00"
state expanded = false state expanded: boolean = false
functions { functions {
function commit(part, nextValue, sourceEvent, next, detail) { if (part === "hour") { selectedHour = String(nextValue).padStart(2, "0") } if (part === "minute") { selectedMinute = String(nextValue).padStart(2, "0") } next = selectedHour + ":" + selectedMinute; if ((min && next < min) || (max && next > max)) { return } currentValue = next; detail = { value: currentValue, hour: selectedHour, minute: selectedMinute, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) } client function commit(part, nextValue, sourceEvent, next, detail) { if (part === "hour") { selectedHour = String(nextValue).padStart(2, "0") } if (part === "minute") { selectedMinute = String(nextValue).padStart(2, "0") } next = selectedHour + ":" + selectedMinute; if ((min && next < min) || (max && next > max)) { return } currentValue = next; detail = { value: currentValue, hour: selectedHour, minute: selectedMinute, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
function selectTimePart(sourceEvent, root, input, parts, hourValue, minuteValue, next, trigger, detail) { root = sourceEvent.currentTarget.closest(".wire-next--time-picker"); input = root.querySelector(".wire-next__time-value"); parts = String(input.value || value || "00:00").split(":"); hourValue = sourceEvent.currentTarget.dataset.part === "hour" ? sourceEvent.currentTarget.dataset.value : parts[0]; minuteValue = sourceEvent.currentTarget.dataset.part === "minute" ? sourceEvent.currentTarget.dataset.value : parts[1]; next = hourValue + ":" + minuteValue; if ((min && next < min) || (max && next > max)) { return } currentValue = next; input.setAttribute("value", next); trigger = root.querySelector(".wire-next__time-trigger span"); if (trigger) { trigger.replaceChildren(next) } sourceEvent.currentTarget.setAttribute("aria-pressed", "true"); detail = { value: next, hour: hourValue, minute: minuteValue, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) } client function selectTimePart(sourceEvent, root, input, parts, hourValue, minuteValue, next, trigger, detail) { root = sourceEvent.currentTarget.closest(".wire-next--time-picker"); input = root.querySelector(".wire-next__time-value"); parts = String(input.value || value || "00:00").split(":"); hourValue = sourceEvent.currentTarget.dataset.part === "hour" ? sourceEvent.currentTarget.dataset.value : parts[0]; minuteValue = sourceEvent.currentTarget.dataset.part === "minute" ? sourceEvent.currentTarget.dataset.value : parts[1]; next = hourValue + ":" + minuteValue; if ((min && next < min) || (max && next > max)) { return } currentValue = next; input.setAttribute("value", next); trigger = root.querySelector(".wire-next__time-trigger span"); if (trigger) { trigger.replaceChildren(next) } sourceEvent.currentTarget.setAttribute("aria-pressed", "true"); detail = { value: next, hour: hourValue, minute: minuteValue, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
function openPicker(sourceEvent) { expanded = true; $emit("open", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function openPicker(sourceEvent) { expanded = true; output.open({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function closePicker(sourceEvent) { expanded = false; $emit("close", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function closePicker(sourceEvent) { expanded = false; output.close({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function handleFocus(sourceEvent) { $emit("focus", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function handleFocus(sourceEvent) { output.focus({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function handleBlur(sourceEvent) { $emit("blur", { value: currentValue, name: name, sourceEvent: sourceEvent }) } client function handleBlur(sourceEvent) { output.blur({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
function handleInvalid(sourceEvent) { $emit("invalid", { name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent }) } client function handleInvalid(sourceEvent) { output.invalid({ name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent }) }
} }
view { view {
<div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--time-picker {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error ? 'true' : 'false'}" data-expanded="{expanded}"> <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--time-picker {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error ? 'true' : 'false'}" data-expanded="{expanded}">
+7 -7
View File
@@ -1,12 +1,12 @@
component Timeline { component Timeline {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
title = "Timeline" title: string = "Timeline"
description = "" description: string = ""
items = [] items: unknown[] = []
variant = "default" variant: string = "default"
class = "" class: string = ""
} }
view { view {
+12 -9
View File
@@ -1,14 +1,17 @@
component Toast { component Toast {
outputs {
dismiss(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
action(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event dismiss = function size: string = "default"
@event action = function color: string = "primary"
size = "default" title: string = "Toast"
color = "primary" description: string = ""
title = "Toast" items: unknown[] = []
description = "" variant: string = "default"
items = [] class: string = ""
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--toast wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--toast wire-next--variant-{variant} {class}">
+14 -11
View File
@@ -1,16 +1,19 @@
component ToastNotifications { component ToastNotifications {
outputs {
add(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
dismiss(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
clear(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
action(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event add = function size: string = "default"
@event dismiss = function color: string = "primary"
@event clear = function title: string = "Toast Notifications"
@event action = function description: string = ""
size = "default" items: unknown[] = []
color = "primary" variant: string = "default"
title = "Toast Notifications" class: string = ""
description = ""
items = []
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--toast-notifications wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--toast-notifications wire-next--variant-{variant} {class}">
+108 -45
View File
@@ -1,48 +1,51 @@
component ToggleCount { component ToggleCount {
outputs {
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
toggle(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
variant = "segmented" variant: string = "segmented"
class = "" class: string = ""
name = "billing-cycle" name: string = "billing-cycle"
value = "monthly" value: string = "monthly"
firstValue = "monthly" firstValue: string = "monthly"
firstLabel = "Monthly" firstLabel: string = "Monthly"
secondValue = "annual" secondValue: string = "annual"
secondLabel = "Annual" secondLabel: string = "Annual"
ariaLabel = "Billing frequency" ariaLabel: string = "Billing frequency"
items = [] items: unknown[] = []
currency = "$" currency: string = "$"
suffix = "" suffix: string = ""
firstValueKey = "monthly" firstValueKey: string = "monthly"
secondValueKey = "annual" secondValueKey: string = "annual"
emptyValue = "—" emptyValue: string = "—"
align = "end" align: string = "end"
fullWidth = true fullWidth: boolean = true
disabled = false disabled: boolean = false
animate = true animate: boolean = true
animationDuration = 450 animationDuration: number = 450
animationSteps = 18 animationSteps: number = 18
@event change = function
@event toggle = function
} }
state selectedValue = value state selectedValue = value
functions { functions {
function isFirstSelected() { shared function isFirstSelected() {
return selectedValue === firstValue return selectedValue === firstValue
} }
function isSecondSelected() { shared function isSecondSelected() {
return selectedValue === secondValue return selectedValue === secondValue
} }
function displayValue(item) { shared function displayValue(item) {
if (isSecondSelected()) { if (isSecondSelected()) {
return item[secondValueKey] !== undefined return item[secondValueKey] !== undefined
? item[secondValueKey] ? item[secondValueKey]
@@ -54,7 +57,7 @@ component ToggleCount {
: emptyValue : emptyValue
} }
function dispatchToggleEvent(sourceEvent, previousValue, root, customEvent) { client function dispatchToggleEvent(sourceEvent, previousValue, root, customEvent) {
root = sourceEvent.currentTarget.closest("[data-wrn-toggle-count]") root = sourceEvent.currentTarget.closest("[data-wrn-toggle-count]")
if (!root) { if (!root) {
@@ -84,7 +87,7 @@ component ToggleCount {
root.dispatchEvent(customEvent) root.dispatchEvent(customEvent)
} }
function selectValue(sourceEvent, nextValue, previousValue) { client function selectValue(sourceEvent, nextValue, previousValue) {
if (disabled || selectedValue === nextValue) { if (disabled || selectedValue === nextValue) {
return return
} }
@@ -102,18 +105,29 @@ component ToggleCount {
dispatchToggleEvent(sourceEvent, previousValue) dispatchToggleEvent(sourceEvent, previousValue)
} }
function animateDisplayedValues(sourceEvent, previousValue, root, nodes) { client function animateDisplayedValues(sourceEvent, previousValue, root, nodes, animationToken) {
root = sourceEvent.currentTarget.closest("[data-wrn-toggle-count]") root = sourceEvent.currentTarget.closest("[data-wrn-toggle-count]")
if (!root) { if (!root) {
return return
} }
animationToken = String(Date.now()) + ":" + String(selectedValue)
root.setAttribute("data-animation-token", animationToken)
nodes = root.querySelectorAll("[data-toggle-count-value]") nodes = root.querySelectorAll("[data-toggle-count-value]")
animateValueAt(nodes, 0, previousValue) animateValueAt(nodes, 0, previousValue, root, animationToken)
} }
function animateValueAt(nodes, index, previousValue, node, fromValue, toValue) { client function animateValueAt(
nodes,
index,
previousValue,
root,
animationToken,
node,
fromValue,
toValue
) {
if (index >= nodes.length) { if (index >= nodes.length) {
return return
} }
@@ -131,17 +145,40 @@ component ToggleCount {
) )
if (!Number.isNaN(fromValue) && !Number.isNaN(toValue)) { if (!Number.isNaN(fromValue) && !Number.isNaN(toValue)) {
animateValueFrame(node, fromValue, toValue, 0) node.replaceChildren(String(fromValue))
scheduleValueFrame(
node,
fromValue,
toValue,
1,
Math.max(1, Number(animationSteps) || 1),
Math.max(1, Number(animationDuration) || 1),
root,
animationToken
)
} }
animateValueAt(nodes, index + 1, previousValue) animateValueAt(
nodes,
index + 1,
previousValue,
root,
animationToken
)
} }
function animateValueFrame(node, fromValue, toValue, frame, totalFrames, nextValue) { client function scheduleValueFrame(
totalFrames = Math.max(1, Number(animationSteps) || 1) node,
fromValue,
if (frame >= totalFrames) { toValue,
node.replaceChildren(String(toValue)) frame,
totalFrames,
duration,
root,
animationToken,
nextValue
) {
if (frame > totalFrames) {
return return
} }
@@ -149,19 +186,45 @@ component ToggleCount {
fromValue + fromValue +
(toValue - fromValue) * (frame / totalFrames) (toValue - fromValue) * (frame / totalFrames)
) )
node.replaceChildren(String(nextValue))
setTimeout( setTimeout(
animateValueFrame, applyAnimatedValue,
Math.max(1, Number(animationDuration) / totalFrames), Math.max(1, duration * frame / totalFrames),
node,
nextValue,
root,
animationToken
)
scheduleValueFrame(
node, node,
fromValue, fromValue,
toValue, toValue,
frame + 1 frame + 1,
totalFrames,
duration,
root,
animationToken
) )
} }
function toggleValue(sourceEvent) { client function applyAnimatedValue(
node,
nextValue,
root,
animationToken
) {
if (
root &&
root.getAttribute("data-animation-token") !== animationToken
) {
return
}
node.replaceChildren(String(nextValue))
}
client function toggleValue(sourceEvent) {
selectValue( selectValue(
sourceEvent, sourceEvent,
isFirstSelected() ? secondValue : firstValue isFirstSelected() ? secondValue : firstValue
+41 -38
View File
@@ -1,49 +1,52 @@
component TogglePassword { component TogglePassword {
props { outputs {
size = "default" input(payload: { name?: string; value: string | number | boolean | null | object; visible: boolean })
color = "primary" change(payload: { name?: string; value: string | number | boolean | null | object; visible: boolean })
label = "Password" toggle(payload: { visible: boolean })
name = "password"
value = ""
placeholder = "Enter your password"
autocomplete = "current-password"
minlength = ""
maxlength = ""
pattern = "(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9]).{8,}"
fields = []
visible = false
toggleable = true
toggleMode = "button"
checkboxLabel = "Show password"
showLabel = "Show password"
hideLabel = "Hide password"
disabled = false
readonly = false
required = false
invalid = false
helpText = ""
validationMessage = ""
class = ""
@event input = function
@event change = function
@event toggle = function
} }
props {
size: string = "default"
color: string = "primary"
label: string = "Password"
name: string = "password"
value: string = ""
placeholder: string = "Enter your password"
autocomplete: string = "current-password"
minlength: string = ""
maxlength: string = ""
pattern: string = "(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9]).{8,}"
fields: unknown[] = []
visible: boolean = false
toggleable: boolean = true
toggleMode: string = "button"
checkboxLabel: string = "Show password"
showLabel: string = "Show password"
hideLabel: string = "Hide password"
disabled: boolean = false
readonly: boolean = false
required: boolean = false
invalid: boolean = false
helpText: string = ""
validationMessage: string = ""
class: string = ""
}
state revealed = visible state revealed = visible
functions { functions {
function toggleVisibility() { shared function toggleVisibility() {
if (disabled || readonly || !toggleable) { if (disabled || readonly || !toggleable) {
return return
} }
revealed = !revealed revealed = !revealed
} }
function fieldId(field, index) { shared function fieldId(field, index) {
return field.id || field.name || name + "-" + index return field.id || field.name || name + "-" + index
} }
function fieldName(field, index) { shared function fieldName(field, index) {
return field.name || name + "-" + index return field.name || name + "-" + index
} }
} }
@@ -74,8 +77,8 @@ component TogglePassword {
readonly="{readonly || field.readonly}" readonly="{readonly || field.readonly}"
required="{required || field.required}" required="{required || field.required}"
aria-invalid="{invalid ? 'true' : 'false'}" aria-invalid="{invalid ? 'true' : 'false'}"
@input="$emit('input', { name: event.target.name, value: event.target.value, visible: revealed })" @input="output.input({ name: event.target.name, value: event.target.value, visible: revealed })"
@change="$emit('change', { name: event.target.name, value: event.target.value, visible: revealed })" @change="output.change({ name: event.target.name, value: event.target.value, visible: revealed })"
/> />
{#if toggleable && toggleMode === "button"} {#if toggleable && toggleMode === "button"}
<button <button
@@ -85,7 +88,7 @@ component TogglePassword {
title="{revealed ? hideLabel : showLabel}" title="{revealed ? hideLabel : showLabel}"
aria-pressed="{revealed ? 'true' : 'false'}" aria-pressed="{revealed ? 'true' : 'false'}"
disabled="{disabled || readonly}" disabled="{disabled || readonly}"
@click="toggleVisibility(); $emit('toggle', { visible: revealed })" @click="toggleVisibility(); output.toggle({ visible: revealed })"
> >
<span class="wire-next__password-icon wire-next__password-icon--show" aria-hidden="true"></span> <span class="wire-next__password-icon wire-next__password-icon--show" aria-hidden="true"></span>
</button> </button>
@@ -113,8 +116,8 @@ component TogglePassword {
required="{required}" required="{required}"
aria-invalid="{invalid ? 'true' : 'false'}" aria-invalid="{invalid ? 'true' : 'false'}"
aria-describedby="{validationMessage ? name + '-validation' : helpText ? name + '-help' : ''}" aria-describedby="{validationMessage ? name + '-validation' : helpText ? name + '-help' : ''}"
@input="$emit('input', { value: event.target.value, visible: revealed })" @input="output.input({ value: event.target.value, visible: revealed })"
@change="$emit('change', { value: event.target.value, visible: revealed })" @change="output.change({ value: event.target.value, visible: revealed })"
/> />
{#if toggleable && toggleMode === "button"} {#if toggleable && toggleMode === "button"}
<button <button
@@ -124,7 +127,7 @@ component TogglePassword {
title="{revealed ? hideLabel : showLabel}" title="{revealed ? hideLabel : showLabel}"
aria-pressed="{revealed ? 'true' : 'false'}" aria-pressed="{revealed ? 'true' : 'false'}"
disabled="{disabled || readonly}" disabled="{disabled || readonly}"
@click="toggleVisibility(); $emit('toggle', { visible: revealed })" @click="toggleVisibility(); output.toggle({ visible: revealed })"
> >
<span class="wire-next__password-icon wire-next__password-icon--show" aria-hidden="true"></span> <span class="wire-next__password-icon wire-next__password-icon--show" aria-hidden="true"></span>
</button> </button>
@@ -137,7 +140,7 @@ component TogglePassword {
type="checkbox" type="checkbox"
checked="{revealed}" checked="{revealed}"
disabled="{disabled || readonly}" disabled="{disabled || readonly}"
@change="revealed = event.target.checked; $emit('toggle', { visible: revealed })" @change="revealed = event.target.checked; output.toggle({ visible: revealed })"
/> />
<span>{checkboxLabel}</span> <span>{checkboxLabel}</span>
</label> </label>
+14 -11
View File
@@ -1,16 +1,19 @@
component TreeView { component TreeView {
outputs {
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
toggle(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
expand(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
collapse(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event select = function size: string = "default"
@event toggle = function color: string = "primary"
@event expand = function title: string = "Tree View"
@event collapse = function description: string = ""
size = "default" items: unknown[] = []
color = "primary" variant: string = "default"
title = "Tree View" class: string = ""
description = ""
items = []
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--tree-view wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--tree-view wire-next--variant-{variant} {class}">
+6 -6
View File
@@ -1,11 +1,11 @@
component Typography { component Typography {
props { props {
size = "default" size: string = "default"
color = "primary" color: string = "primary"
columns = 2 columns: number = 2
gap = "md" gap: string = "md"
maxWidth = "xl" maxWidth: string = "xl"
class = "" class: string = ""
} }
view { view {
+14 -11
View File
@@ -1,16 +1,19 @@
component WysiwygEditor { component WysiwygEditor {
outputs {
input(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
focus(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
blur(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event input = function size: string = "default"
@event change = function color: string = "primary"
@event focus = function title: string = "Wysiwyg Editor"
@event blur = function description: string = ""
size = "default" items: unknown[] = []
color = "primary" variant: string = "default"
title = "Wysiwyg Editor" class: string = ""
description = ""
items = []
variant = "default"
class = ""
} }
view { view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--wysiwyg-editor wire-next--variant-{variant} {class}"> <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--wysiwyg-editor wire-next--variant-{variant} {class}">
+35 -32
View File
@@ -1,38 +1,41 @@
component Alert { component Alert {
props { outputs {
size = "default" dismiss(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
color = "info" action(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
variant = "soft"
class = ""
radius = "md"
shadow = "sm"
title = "Alert"
description = ""
items = []
actions = []
showIcon = false
icon = ""
dismissible = false
dismissLabel = "Dismiss alert"
role = "alert"
live = "polite"
linkLabel = ""
linkHref = ""
actionLabel = ""
actionHref = ""
compact = false
@event dismiss = function
@event action = function
} }
state visible = true props {
size: string = "default"
color: string = "info"
variant: string = "soft"
class: string = ""
radius: string = "md"
shadow: string = "sm"
title: string = "Alert"
description: string = ""
items: unknown[] = []
actions: unknown[] = []
showIcon: boolean = false
icon: string = ""
dismissible: boolean = false
dismissLabel: string = "Dismiss alert"
role: string = "alert"
live: string = "polite"
linkLabel: string = ""
linkHref: string = ""
actionLabel: string = ""
actionHref: string = ""
compact: boolean = false
}
state visible: boolean = true
functions { functions {
function dispatchAlertEvent(sourceEvent, eventName, action, root, customEvent) { client function dispatchAlertEvent(sourceEvent, eventName, action, root, customEvent) {
root = sourceEvent.currentTarget.closest("[data-wrn-alert]") root = sourceEvent.currentTarget.closest("[data-wrn-alert]")
if (!root) { if (!root) {
@@ -50,12 +53,12 @@ component Alert {
root.dispatchEvent(customEvent) root.dispatchEvent(customEvent)
} }
function dismissAlert(sourceEvent) { client function dismissAlert(sourceEvent) {
visible = false visible = false
dispatchAlertEvent(sourceEvent, "dismiss", null) dispatchAlertEvent(sourceEvent, "dismiss", null)
} }
function selectAction(sourceEvent, action) { client function selectAction(sourceEvent, action) {
dispatchAlertEvent(sourceEvent, "action", action) dispatchAlertEvent(sourceEvent, "action", action)
} }
} }
+24 -21
View File
@@ -1,29 +1,32 @@
component Avatar { component Avatar {
outputs {
load(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
error(payload: { error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)
click(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event load = function src: string = ""
@event error = function alt: string = ""
@event click = function initials: string = ""
src = "" size: string = "md"
alt = "" color: string = "primary"
initials = "" variant: string = "solid"
size = "md" shape: string = "circle"
color = "primary"
variant = "solid"
shape = "circle"
status = "" status: string = ""
statusLabel = "" statusLabel: string = ""
statusPosition = "bottom" statusPosition: string = "bottom"
badge = "" badge: string = ""
badgeIcon = "" badgeIcon: string = ""
badgeLabel = "" badgeLabel: string = ""
tooltip = "" tooltip: string = ""
name = "" name: string = ""
description = "" description: string = ""
loading = "lazy" loading: string = "lazy"
class = "" class: string = ""
} }
view { view {
+33 -30
View File
@@ -1,38 +1,41 @@
component Badge { component Badge {
props { outputs {
label = "Badge" dismiss(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
size = "md"
color = "primary"
variant = "solid"
shape = "pill"
class = ""
icon = ""
iconPosition = "start"
dot = false
dotOnly = false
dotLabel = "Status"
animated = false
avatarSrc = ""
avatarAlt = ""
dismissible = false
dismissLabel = "Remove badge"
truncate = false
maxWidth = "12rem"
anchorLabel = ""
anchorIcon = ""
placement = "inline"
anchorLabelText = "Badge anchor"
@event dismiss = function
} }
state visible = true props {
label: string = "Badge"
size: string = "md"
color: string = "primary"
variant: string = "solid"
shape: string = "pill"
class: string = ""
icon: string = ""
iconPosition: string = "start"
dot: boolean = false
dotOnly: boolean = false
dotLabel: string = "Status"
animated: boolean = false
avatarSrc: string = ""
avatarAlt: string = ""
dismissible: boolean = false
dismissLabel: string = "Remove badge"
truncate: boolean = false
maxWidth: string = "12rem"
anchorLabel: string = ""
anchorIcon: string = ""
placement: string = "inline"
anchorLabelText: string = "Badge anchor"
}
state visible: boolean = true
functions { functions {
function dismissBadge(sourceEvent, root, customEvent) { client function dismissBadge(sourceEvent, root, customEvent) {
visible = false visible = false
root = sourceEvent.currentTarget.closest("[data-wrn-badge]") root = sourceEvent.currentTarget.closest("[data-wrn-badge]")
customEvent = document.createEvent("CustomEvent") customEvent = document.createEvent("CustomEvent")
+31 -28
View File
@@ -1,33 +1,36 @@
component Button { component Button {
outputs {
click(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
focus(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
blur(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
@event click = function label: string = "Button"
@event focus = function loadingLabel: string = "Loading…"
@event blur = function description: string = ""
label = "Button" as: string = ""
loadingLabel = "Loading…" href: string = ""
description = "" target: string = ""
as = "" rel: string = ""
href = "" type: string = "button"
target = "" variant: string = "default"
rel = "" color: string = "primary"
type = "button" size: string = "default"
variant = "default" disabled: boolean = false
color = "primary" loading: boolean = false
size = "default" pill: boolean = false
disabled = false fullWidth: boolean = false
loading = false icon: string = ""
pill = false iconPosition: string = "start"
fullWidth = false ariaLabel: string = ""
icon = "" ariaPressed: string = ""
iconPosition = "start" ariaExpanded: string = ""
ariaLabel = "" ariaControls: string = ""
ariaPressed = "" title: string = ""
ariaExpanded = "" autofocus: boolean = false
ariaControls = "" controlClass: string = ""
title = "" class: string = ""
autofocus = false
controlClass = ""
class = ""
} }
view { view {
+46 -43
View File
@@ -1,48 +1,51 @@
component Card { component Card {
props { outputs {
@event click = function click(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
@event action = function action(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
@event navigate = function navigate(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
@event dismiss = function dismiss(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
@event load = function load(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
@event error = function error(payload: { error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)
title = "Card title"
subtitle = ""
description = ""
header = ""
footer = ""
imageSrc = ""
imageAlt = ""
imagePosition = "top"
actionLabel = ""
actionHref = ""
headerActions = []
navigation = []
activeNav = ""
mobileNavigation = false
alertTitle = ""
alertDescription = ""
empty = false
emptyTitle = "No data to show"
emptyIcon = "icon-[lucide--inbox]"
items = []
size = "md"
color = "primary"
variant = "default"
layout = "vertical"
align = "left"
hover = "none"
scrollable = false
maxHeight = "18rem"
dismissible = false
ariaLabel = ""
class = ""
} }
state dismissed = false props {
title: string = "Card title"
subtitle: string = ""
description: string = ""
header: string = ""
footer: string = ""
imageSrc: string = ""
imageAlt: string = ""
imagePosition: string = "top"
actionLabel: string = ""
actionHref: string = ""
headerActions: unknown[] = []
navigation: unknown[] = []
activeNav: string = ""
mobileNavigation: boolean = false
alertTitle: string = ""
alertDescription: string = ""
empty: boolean = false
emptyTitle: string = "No data to show"
emptyIcon: string = "icon-[lucide--inbox]"
items: unknown[] = []
size: string = "md"
color: string = "primary"
variant: string = "default"
layout: string = "vertical"
align: string = "left"
hover: string = "none"
scrollable: boolean = false
maxHeight: string = "18rem"
dismissible: boolean = false
ariaLabel: string = ""
class: string = ""
}
state dismissed: boolean = false
functions { functions {
function dispatchCardNavigation(sourceEvent, item, index, root, customEvent) { client function dispatchCardNavigation(sourceEvent, item, index, root, customEvent) {
root = sourceEvent.currentTarget.closest("[data-wrn-card]") root = sourceEvent.currentTarget.closest("[data-wrn-card]")
if (!root) { if (!root) {
@@ -58,7 +61,7 @@ component Card {
root.dispatchEvent(customEvent) root.dispatchEvent(customEvent)
} }
function dispatchCardNavigationValue(sourceEvent, root, customEvent) { client function dispatchCardNavigationValue(sourceEvent, root, customEvent) {
root = sourceEvent.currentTarget.closest("[data-wrn-card]") root = sourceEvent.currentTarget.closest("[data-wrn-card]")
if (!root) { if (!root) {
@@ -73,7 +76,7 @@ component Card {
root.dispatchEvent(customEvent) root.dispatchEvent(customEvent)
} }
function dispatchCardHeaderAction(sourceEvent, action, index, root, customEvent) { client function dispatchCardHeaderAction(sourceEvent, action, index, root, customEvent) {
root = sourceEvent.currentTarget.closest("[data-wrn-card]") root = sourceEvent.currentTarget.closest("[data-wrn-card]")
if (!root) { if (!root) {
@@ -89,7 +92,7 @@ component Card {
root.dispatchEvent(customEvent) root.dispatchEvent(customEvent)
} }
function dismissCard(sourceEvent, root, customEvent) { client function dismissCard(sourceEvent, root, customEvent) {
dismissed = true dismissed = true
root = sourceEvent.currentTarget.closest("[data-wrn-card]") root = sourceEvent.currentTarget.closest("[data-wrn-card]")

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