diff --git a/.publish/ai/package.json b/.publish/ai/package.json index c149eadb..3495f26e 100644 --- a/.publish/ai/package.json +++ b/.publish/ai/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ai", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "license": "MIT", diff --git a/.publish/authz/package.json b/.publish/authz/package.json index f05f7f03..038576f6 100644 --- a/.publish/authz/package.json +++ b/.publish/authz/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/authz", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/authz — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/cli/package.json b/.publish/cli/package.json index 2d7ae69c..a526e480 100644 --- a/.publish/cli/package.json +++ b/.publish/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/cli — part of the WrNexus framework.", "license": "MIT", @@ -28,16 +28,16 @@ "wrnexus": "./dist/index.js" }, "dependencies": { - "@wrnexus/core": "^0.2.53", - "@wrnexus/router": "^0.2.53", - "@wrnexus/csr": "^0.2.53", - "@wrnexus/compiler": "^0.2.53", - "@wrnexus/styles": "^0.2.53", - "@wrnexus/dev-server": "^0.2.53", - "@wrnexus/ui": "^0.2.53", - "@wrnexus/validation": "^0.2.53", - "@wrnexus/i18n": "^0.2.53", - "@wrnexus/db": "^0.2.53" + "@wrnexus/core": "^0.2.54", + "@wrnexus/router": "^0.2.54", + "@wrnexus/csr": "^0.2.54", + "@wrnexus/compiler": "^0.2.54", + "@wrnexus/styles": "^0.2.54", + "@wrnexus/dev-server": "^0.2.54", + "@wrnexus/ui": "^0.2.54", + "@wrnexus/validation": "^0.2.54", + "@wrnexus/i18n": "^0.2.54", + "@wrnexus/db": "^0.2.54" }, "files": [ "dist" diff --git a/.publish/compiler/package.json b/.publish/compiler/package.json index 5c9567d6..d2cfa1b4 100644 --- a/.publish/compiler/package.json +++ b/.publish/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/compiler — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/core/package.json b/.publish/core/package.json index 1bafedd5..af3ce3a6 100644 --- a/.publish/core/package.json +++ b/.publish/core/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/core", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/core — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/csr/package.json b/.publish/csr/package.json index 8f79170b..d468d030 100644 --- a/.publish/csr/package.json +++ b/.publish/csr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/csr", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/csr — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.53" + "@wrnexus/core": "^0.2.54" }, "files": [ "dist" diff --git a/.publish/db/package.json b/.publish/db/package.json index a9e85426..bb1f9c7c 100644 --- a/.publish/db/package.json +++ b/.publish/db/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/db", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/db — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/dev-server/package.json b/.publish/dev-server/package.json index 5ed6def9..31ad5ea1 100644 --- a/.publish/dev-server/package.json +++ b/.publish/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/dev-server — part of the WrNexus framework.", "license": "MIT", @@ -25,18 +25,18 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.53", - "@wrnexus/router": "^0.2.53", - "@wrnexus/ssr": "^0.2.53", - "@wrnexus/csr": "^0.2.53", - "@wrnexus/compiler": "^0.2.53", - "@wrnexus/styles": "^0.2.53", - "@wrnexus/ui": "^0.2.53", - "@wrnexus/validation": "^0.2.53", - "@wrnexus/i18n": "^0.2.53", - "@wrnexus/db": "^0.2.53", - "@wrnexus/pubsub": "^0.2.53", - "@wrnexus/uploader": "^0.2.53" + "@wrnexus/core": "^0.2.54", + "@wrnexus/router": "^0.2.54", + "@wrnexus/ssr": "^0.2.54", + "@wrnexus/csr": "^0.2.54", + "@wrnexus/compiler": "^0.2.54", + "@wrnexus/styles": "^0.2.54", + "@wrnexus/ui": "^0.2.54", + "@wrnexus/validation": "^0.2.54", + "@wrnexus/i18n": "^0.2.54", + "@wrnexus/db": "^0.2.54", + "@wrnexus/pubsub": "^0.2.54", + "@wrnexus/uploader": "^0.2.54" }, "files": [ "dist" diff --git a/.publish/encryption/package.json b/.publish/encryption/package.json index 1071e7b1..b8a63bd2 100644 --- a/.publish/encryption/package.json +++ b/.publish/encryption/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/encryption", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/encryption — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/helpers/package.json b/.publish/helpers/package.json index 7e17fac7..f29221e7 100644 --- a/.publish/helpers/package.json +++ b/.publish/helpers/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/helpers", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.53" + "@wrnexus/core": "^0.2.54" }, "files": [ "dist" diff --git a/.publish/i18n/package.json b/.publish/i18n/package.json index 97b15e64..c10233be 100644 --- a/.publish/i18n/package.json +++ b/.publish/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/i18n", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/i18n — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.53" + "@wrnexus/core": "^0.2.54" }, "files": [ "dist" diff --git a/.publish/jwt/package.json b/.publish/jwt/package.json index 640c1809..e3b26928 100644 --- a/.publish/jwt/package.json +++ b/.publish/jwt/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/jwt", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/jwt — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/mobile/package.json b/.publish/mobile/package.json index 426f5c45..3d260921 100644 --- a/.publish/mobile/package.json +++ b/.publish/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/mobile", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/mobile — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/native": "^0.2.53" + "@wrnexus/native": "^0.2.54" }, "files": [ "dist" diff --git a/.publish/native/package.json b/.publish/native/package.json index 51e66ca2..671bff22 100644 --- a/.publish/native/package.json +++ b/.publish/native/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/native", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/native — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/oauth/package.json b/.publish/oauth/package.json index 971edb7c..995abdf0 100644 --- a/.publish/oauth/package.json +++ b/.publish/oauth/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/oauth", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/oauth — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/pubsub/package.json b/.publish/pubsub/package.json index 31591a2a..81b6a2de 100644 --- a/.publish/pubsub/package.json +++ b/.publish/pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/pubsub", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/pubsub — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/queue/package.json b/.publish/queue/package.json index e0787245..7240898b 100644 --- a/.publish/queue/package.json +++ b/.publish/queue/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/queue", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/queue — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/reactive/package.json b/.publish/reactive/package.json index 9c30a71a..82cba7a6 100644 --- a/.publish/reactive/package.json +++ b/.publish/reactive/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/reactive", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/reactive — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/router/package.json b/.publish/router/package.json index b8532455..05d881ca 100644 --- a/.publish/router/package.json +++ b/.publish/router/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/router", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/router — part of the WrNexus framework.", "license": "MIT", @@ -21,8 +21,8 @@ } }, "dependencies": { - "@wrnexus/compiler": "^0.2.53", - "@wrnexus/core": "^0.2.53" + "@wrnexus/compiler": "^0.2.54", + "@wrnexus/core": "^0.2.54" }, "files": [ "dist" diff --git a/.publish/ssr/package.json b/.publish/ssr/package.json index 1b4d67b4..22f455be 100644 --- a/.publish/ssr/package.json +++ b/.publish/ssr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ssr", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/ssr — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.53" + "@wrnexus/core": "^0.2.54" }, "files": [ "dist" diff --git a/.publish/styles/package.json b/.publish/styles/package.json index 0c7468e6..5709774b 100644 --- a/.publish/styles/package.json +++ b/.publish/styles/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/styles", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/styles — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/uploader": "^0.2.53" + "@wrnexus/uploader": "^0.2.54" }, "files": [ "dist" diff --git a/.publish/test/package.json b/.publish/test/package.json index e78b22de..e4e0bebe 100644 --- a/.publish/test/package.json +++ b/.publish/test/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/test", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/test — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/tracking/package.json b/.publish/tracking/package.json index b9f98355..9e5ac3e9 100644 --- a/.publish/tracking/package.json +++ b/.publish/tracking/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/tracking", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/tracking — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/ui/README.md b/.publish/ui/README.md index 2c59dba4..65caf61b 100644 --- a/.publish/ui/README.md +++ b/.publish/ui/README.md @@ -67,6 +67,17 @@ body from either a named prop or the default slot. Also shipped: `select`, `radio`, `switch`, `progress`, `tag`, `skeleton`, `tooltip`, `table`, `FAQAccordion`, `AnnouncementBar`, and `BackToTop`. +The PDF-defined minimum release and essential build-first set also includes +typed typography, form primitives, loading actions, combobox and multi-select, +time/date-time and recurring schedule controls, confirmation dialogs, data +tables, filters, desktop/mobile navigation, mega menus, marketing/product/legal +page shells, product and metric cards, FAQ composition, pricing comparison, +SDK tabs, legal navigation, and cookie preferences. + +`Seo` and `StructuredData` remain framework/page concerns rather than body +components: use the native page `seo { ... }` block and document-head APIs so +metadata is emitted in `` instead of invalid component markup. + The authoritative, always-current list is `uiComponentNames()` (below), which reads the component directory at runtime. diff --git a/.publish/ui/components/Combobox.wrn b/.publish/ui/components/Combobox.wrn new file mode 100644 index 00000000..486bb9c8 --- /dev/null +++ b/.publish/ui/components/Combobox.wrn @@ -0,0 +1,15 @@ +component Combobox { + props { + id = "combobox" + name = "" + label = "Choose an option" + placeholder = "Search options" + options = [] + required = false + disabled = false + class = "" + } + view { +
{#each options as option}{/each}
+ } +} diff --git a/.publish/ui/components/ConfirmationDialog.wrn b/.publish/ui/components/ConfirmationDialog.wrn new file mode 100644 index 00000000..9f74ed29 --- /dev/null +++ b/.publish/ui/components/ConfirmationDialog.wrn @@ -0,0 +1,15 @@ +component ConfirmationDialog { + props { + title = "Confirm action" + description = "Are you sure you want to continue?" + confirmLabel = "Confirm" + cancelLabel = "Cancel" + danger = false + open = false + class = "" + } + state visible = open + view { + + } +} diff --git a/.publish/ui/components/CookiePreferencesDialog.wrn b/.publish/ui/components/CookiePreferencesDialog.wrn new file mode 100644 index 00000000..a74888fd --- /dev/null +++ b/.publish/ui/components/CookiePreferencesDialog.wrn @@ -0,0 +1,17 @@ +component CookiePreferencesDialog { + props { + title = "Cookie preferences" + description = "Choose which optional cookies you allow. Essential cookies are always enabled." + saveLabel = "Save preferences" + acceptLabel = "Accept all" + rejectLabel = "Reject optional" + open = false + class = "" + } + state visible = open + state analytics = false + state marketing = false + view { + + } +} diff --git a/.publish/ui/components/DataTable.wrn b/.publish/ui/components/DataTable.wrn new file mode 100644 index 00000000..a6220a9c --- /dev/null +++ b/.publish/ui/components/DataTable.wrn @@ -0,0 +1,13 @@ +component DataTable { + props { + caption = "Data table" + columns = [] + rows = [] + emptyMessage = "No data available." + striped = false + class = "" + } + view { +
{#each columns as column}{/each}{#each rows as row}{#each columns as column}{/each}{:empty}{/each}
{caption}
{column.label}
{row[column.key]}
{emptyMessage}
+ } +} diff --git a/.publish/ui/components/DateTimePicker.wrn b/.publish/ui/components/DateTimePicker.wrn new file mode 100644 index 00000000..341f5464 --- /dev/null +++ b/.publish/ui/components/DateTimePicker.wrn @@ -0,0 +1,14 @@ +component DateTimePicker { + props { + id = "datetime-picker" + name = "datetime" + label = "Date and time" + value = "" + min = "" + max = "" + required = false + disabled = false + class = "" + } + view {
} +} diff --git a/.publish/ui/components/DesktopNavigation.wrn b/.publish/ui/components/DesktopNavigation.wrn new file mode 100644 index 00000000..cf9b53b7 --- /dev/null +++ b/.publish/ui/components/DesktopNavigation.wrn @@ -0,0 +1,8 @@ +component DesktopNavigation { + props { + label = "Primary navigation" + items = [] + class = "" + } + view { } +} diff --git a/.publish/ui/components/FAQ.wrn b/.publish/ui/components/FAQ.wrn new file mode 100644 index 00000000..f5b96261 --- /dev/null +++ b/.publish/ui/components/FAQ.wrn @@ -0,0 +1,12 @@ +component FAQ { + props { + eyebrow = "" + title = "Frequently asked questions" + description = "" + items = [] + centered = true + allowMultiple = false + defaultOpenIndex = 0 + } + view { } +} diff --git a/.publish/ui/components/FilterBar.wrn b/.publish/ui/components/FilterBar.wrn new file mode 100644 index 00000000..26b93a9e --- /dev/null +++ b/.publish/ui/components/FilterBar.wrn @@ -0,0 +1,9 @@ +component FilterBar { + props { + label = "Filters" + clearLabel = "Clear filters" + showClear = true + class = "" + } + view {
} +} diff --git a/.publish/ui/components/Form.wrn b/.publish/ui/components/Form.wrn new file mode 100644 index 00000000..fccdbcc9 --- /dev/null +++ b/.publish/ui/components/Form.wrn @@ -0,0 +1,12 @@ +component Form { + props { + action = "" + method = "post" + name = "" + loading = false + class = "" + } + view { +
+ } +} diff --git a/.publish/ui/components/FormError.wrn b/.publish/ui/components/FormError.wrn new file mode 100644 index 00000000..c4ba8430 --- /dev/null +++ b/.publish/ui/components/FormError.wrn @@ -0,0 +1,8 @@ +component FormError { + props { + id = "" + message = "" + class = "" + } + view { } +} diff --git a/.publish/ui/components/FormHelpText.wrn b/.publish/ui/components/FormHelpText.wrn new file mode 100644 index 00000000..6d7bfdd1 --- /dev/null +++ b/.publish/ui/components/FormHelpText.wrn @@ -0,0 +1,8 @@ +component FormHelpText { + props { + id = "" + text = "" + class = "" + } + view {

{text}

} +} diff --git a/.publish/ui/components/FormLabel.wrn b/.publish/ui/components/FormLabel.wrn new file mode 100644 index 00000000..9f181812 --- /dev/null +++ b/.publish/ui/components/FormLabel.wrn @@ -0,0 +1,12 @@ +component FormLabel { + props { + for = "" + label = "Label" + required = false + optional = false + class = "" + } + view { + + } +} diff --git a/.publish/ui/components/LegalPageShell.wrn b/.publish/ui/components/LegalPageShell.wrn new file mode 100644 index 00000000..0c2cbc4e --- /dev/null +++ b/.publish/ui/components/LegalPageShell.wrn @@ -0,0 +1,6 @@ +component LegalPageShell { + props { + class = "" + } + view { } +} diff --git a/.publish/ui/components/LegalTableOfContents.wrn b/.publish/ui/components/LegalTableOfContents.wrn new file mode 100644 index 00000000..d3aaaf24 --- /dev/null +++ b/.publish/ui/components/LegalTableOfContents.wrn @@ -0,0 +1,8 @@ +component LegalTableOfContents { + props { + title = "On this page" + items = [] + class = "" + } + view { } +} diff --git a/.publish/ui/components/LoadingButton.wrn b/.publish/ui/components/LoadingButton.wrn new file mode 100644 index 00000000..c54574c2 --- /dev/null +++ b/.publish/ui/components/LoadingButton.wrn @@ -0,0 +1,14 @@ +component LoadingButton { + props { + label = "Continue" + loadingLabel = "Loading…" + loading = false + disabled = false + type = "button" + variant = "primary" + class = "" + } + view { + + } +} diff --git a/.publish/ui/components/MarketingPageShell.wrn b/.publish/ui/components/MarketingPageShell.wrn new file mode 100644 index 00000000..cea8ffcf --- /dev/null +++ b/.publish/ui/components/MarketingPageShell.wrn @@ -0,0 +1,6 @@ +component MarketingPageShell { + props { + class = "" + } + view {
} +} diff --git a/.publish/ui/components/MegaMenu.wrn b/.publish/ui/components/MegaMenu.wrn new file mode 100644 index 00000000..b6f158e3 --- /dev/null +++ b/.publish/ui/components/MegaMenu.wrn @@ -0,0 +1,11 @@ +component MegaMenu { + props { + label = "Explore" + sections = [] + class = "" + } + state open = false + view { +
{#each sections as section}

{section.title}

{/each}
+ } +} diff --git a/.publish/ui/components/MetricGrid.wrn b/.publish/ui/components/MetricGrid.wrn new file mode 100644 index 00000000..9edac08b --- /dev/null +++ b/.publish/ui/components/MetricGrid.wrn @@ -0,0 +1,8 @@ +component MetricGrid { + props { + label = "Key metrics" + metrics = [] + class = "" + } + view {
{#each metrics as metric}

{metric.value}

{metric.label}

{metric.description}

{/each}
} +} diff --git a/.publish/ui/components/MobileNavigation.wrn b/.publish/ui/components/MobileNavigation.wrn new file mode 100644 index 00000000..c47c4ea4 --- /dev/null +++ b/.publish/ui/components/MobileNavigation.wrn @@ -0,0 +1,12 @@ +component MobileNavigation { + props { + label = "Menu" + closeLabel = "Close menu" + items = [] + class = "" + } + state open = false + view { +
+ } +} diff --git a/.publish/ui/components/MultiSelect.wrn b/.publish/ui/components/MultiSelect.wrn new file mode 100644 index 00000000..ec9d25b5 --- /dev/null +++ b/.publish/ui/components/MultiSelect.wrn @@ -0,0 +1,15 @@ +component MultiSelect { + props { + id = "multi-select" + name = "" + label = "Choose options" + options = [] + required = false + disabled = false + size = 5 + class = "" + } + view { +
+ } +} diff --git a/.publish/ui/components/PricingComparisonTable.wrn b/.publish/ui/components/PricingComparisonTable.wrn new file mode 100644 index 00000000..39b53112 --- /dev/null +++ b/.publish/ui/components/PricingComparisonTable.wrn @@ -0,0 +1,9 @@ +component PricingComparisonTable { + props { + caption = "Plan comparison" + plans = [] + features = [] + class = "" + } + view {
{#each plans as plan}{/each}{#each features as feature}{#each feature.values as value}{/each}{/each}
{caption}
Feature{plan.name}
{feature.label}{value}
} +} diff --git a/.publish/ui/components/ProductCard.wrn b/.publish/ui/components/ProductCard.wrn new file mode 100644 index 00000000..a752772a --- /dev/null +++ b/.publish/ui/components/ProductCard.wrn @@ -0,0 +1,12 @@ +component ProductCard { + props { + eyebrow = "Product" + title = "Product name" + description = "" + href = "#" + actionLabel = "Learn more" + status = "" + class = "" + } + view { } +} diff --git a/.publish/ui/components/ProductPageShell.wrn b/.publish/ui/components/ProductPageShell.wrn new file mode 100644 index 00000000..66989798 --- /dev/null +++ b/.publish/ui/components/ProductPageShell.wrn @@ -0,0 +1,6 @@ +component ProductPageShell { + props { + class = "" + } + view {
} +} diff --git a/.publish/ui/components/ProductsMegaMenu.wrn b/.publish/ui/components/ProductsMegaMenu.wrn new file mode 100644 index 00000000..bfa7aeea --- /dev/null +++ b/.publish/ui/components/ProductsMegaMenu.wrn @@ -0,0 +1,8 @@ +component ProductsMegaMenu { + props { + label = "Products" + sections = [] + class = "" + } + view { } +} diff --git a/.publish/ui/components/QuietHoursPicker.wrn b/.publish/ui/components/QuietHoursPicker.wrn new file mode 100644 index 00000000..479fd396 --- /dev/null +++ b/.publish/ui/components/QuietHoursPicker.wrn @@ -0,0 +1,15 @@ +component QuietHoursPicker { + props { + label = "Quiet hours" + startName = "quietStart" + endName = "quietEnd" + start = "22:00" + end = "08:00" + timezone = "UTC" + disabled = false + class = "" + } + view { +
{label}

Timezone: {timezone}

+ } +} diff --git a/.publish/ui/components/RecurringSchedulePicker.wrn b/.publish/ui/components/RecurringSchedulePicker.wrn new file mode 100644 index 00000000..d3a137a4 --- /dev/null +++ b/.publish/ui/components/RecurringSchedulePicker.wrn @@ -0,0 +1,13 @@ +component RecurringSchedulePicker { + props { + id = "recurring-schedule" + name = "recurrence" + label = "Repeat" + value = "none" + disabled = false + class = "" + } + view { +
+ } +} diff --git a/.publish/ui/components/SDKTabs.wrn b/.publish/ui/components/SDKTabs.wrn new file mode 100644 index 00000000..7e8af688 --- /dev/null +++ b/.publish/ui/components/SDKTabs.wrn @@ -0,0 +1,10 @@ +component SDKTabs { + props { + label = "SDK languages" + tabs = [] + defaultIndex = 0 + class = "" + } + state active = defaultIndex + view {
{#each tabs as tab, index}{/each}
{#each tabs as tab, index}
{tab.code}
{/each}
} +} diff --git a/.publish/ui/components/TimePicker.wrn b/.publish/ui/components/TimePicker.wrn new file mode 100644 index 00000000..56335ad6 --- /dev/null +++ b/.publish/ui/components/TimePicker.wrn @@ -0,0 +1,15 @@ +component TimePicker { + props { + id = "time-picker" + name = "time" + label = "Time" + value = "" + min = "" + max = "" + step = 60 + required = false + disabled = false + class = "" + } + view {
} +} diff --git a/.publish/ui/components/Typography.wrn b/.publish/ui/components/Typography.wrn new file mode 100644 index 00000000..4e27647f --- /dev/null +++ b/.publish/ui/components/Typography.wrn @@ -0,0 +1,11 @@ +component Typography { + props { + as = "p" + variant = "body" + align = "start" + class = "" + } + view { +
+ } +} diff --git a/.publish/ui/package.json b/.publish/ui/package.json index c826228c..8a605d48 100644 --- a/.publish/ui/package.json +++ b/.publish/ui/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ui", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/ui — part of the WrNexus framework.", "license": "MIT", @@ -23,7 +23,7 @@ "./ui.css": "./ui.css" }, "dependencies": { - "@wrnexus/core": "^0.2.53" + "@wrnexus/core": "^0.2.54" }, "files": [ "dist", diff --git a/.publish/ui/ui.css b/.publish/ui/ui.css index 3d2f296c..5997650e 100644 --- a/.publish/ui/ui.css +++ b/.publish/ui/ui.css @@ -537,3 +537,418 @@ outline: 2px solid rgb(124 58 237); outline-offset: 2px; } + +/* --- Component-system foundations --------------------------------------- */ +.wire-type { + margin: 0; + color: var(--wire-color-text); +} +.wire-type--display { + font-size: clamp(2.25rem, 7vw, 4.75rem); + line-height: 1.02; + font-weight: 800; + letter-spacing: -0.04em; +} +.wire-type--heading { + font-size: clamp(1.75rem, 4vw, 3rem); + line-height: 1.1; + font-weight: 750; + letter-spacing: -0.025em; +} +.wire-type--lead { + font-size: clamp(1.05rem, 2vw, 1.25rem); + line-height: 1.65; + color: var(--wire-color-muted); +} +.wire-type--body { + font-size: 1rem; + line-height: 1.65; +} +.wire-type--small { + font-size: 0.875rem; + line-height: 1.5; +} +.wire-text--center { + text-align: center; +} +.wire-text--end { + text-align: end; +} + +.wire-form { + display: grid; + gap: 1.25rem; + width: 100%; +} +.wire-form-row { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; +} +.wire-field, +.wire-fieldset { + display: grid; + gap: 0.45rem; + min-width: 0; + border: 0; + padding: 0; + margin: 0; +} +.wire-label { + color: var(--wire-color-text); + font-size: 0.875rem; + font-weight: 650; + line-height: 1.4; +} +.wire-required, +.wire-field-message--error { + color: var(--wire-color-danger); +} +.wire-optional, +.wire-field-message--help { + color: var(--wire-color-muted); + font-weight: 400; +} +.wire-field-message { + margin: 0; + font-size: 0.8125rem; + line-height: 1.5; +} +.wire-input:disabled, +.wire-btn:disabled { + cursor: not-allowed; + opacity: 0.58; +} +.wire-input[aria-invalid="true"] { + border-color: var(--wire-color-danger); +} +.wire-multiselect { + min-height: 8rem; +} +.wire-loading-button { + min-height: 44px; +} +.wire-spinner--inline { + width: 1rem; + height: 1rem; +} + +.wire-overlay { + position: fixed; + inset: 0; + z-index: 1000; + display: grid; + place-items: center; + padding: 1rem; + background: rgb(0 0 0 / 0.55); + backdrop-filter: blur(4px); +} +.wire-dialog { + width: min(100%, 32rem); + max-height: calc(100dvh - 2rem); + overflow: auto; + padding: clamp(1.25rem, 4vw, 2rem); + color: var(--wire-color-text); + background: var(--wire-color-surface); + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius); + box-shadow: var(--wire-shadow-1); +} +.wire-dialog__title { + margin: 0; + font-size: 1.35rem; + line-height: 1.25; +} +.wire-dialog__description { + margin: 0.75rem 0 0; + color: var(--wire-color-muted); + line-height: 1.6; +} +.wire-dialog__actions { + display: flex; + justify-content: flex-end; + gap: 0.75rem; + margin-top: 1.5rem; +} +.wire-dialog__actions--wrap { + flex-wrap: wrap; +} + +.wire-filter-bar { + display: flex; + align-items: end; + justify-content: space-between; + gap: 1rem; + padding: 1rem; + background: var(--wire-color-surface); + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius); +} +.wire-filter-bar__controls { + display: flex; + flex: 1; + flex-wrap: wrap; + align-items: end; + gap: 0.75rem; +} +.wire-table--striped tbody tr:nth-child(even) { + background: var(--wire-color-surface-2); +} +.wire-comparison-table { + min-width: 42rem; +} + +.wire-desktop-nav > ul, +.wire-mobile-nav ul, +.wire-mega-menu ul { + list-style: none; + padding: 0; + margin: 0; +} +.wire-desktop-nav > ul { + display: flex; + align-items: center; + gap: 0.25rem; +} +.wire-desktop-nav a, +.wire-mega-menu__trigger { + display: inline-flex; + min-height: 44px; + align-items: center; + gap: 0.35rem; + padding: 0.6rem 0.8rem; + color: var(--wire-color-text); + background: transparent; + border: 0; + border-radius: var(--wire-radius-sm); + text-decoration: none; + font: inherit; + cursor: pointer; +} +.wire-desktop-nav a:hover, +.wire-mega-menu__trigger:hover { + background: var(--wire-color-surface-2); +} +.wire-mega-menu { + position: relative; +} +.wire-mega-menu__panel { + position: absolute; + z-index: 100; + top: calc(100% + 0.5rem); + left: 0; + display: grid; + grid-template-columns: repeat(3, minmax(10rem, 1fr)); + gap: 1.25rem; + width: min(46rem, calc(100vw - 2rem)); + padding: 1.25rem; + color: var(--wire-color-text); + background: var(--wire-color-surface); + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius); + box-shadow: var(--wire-shadow-1); +} +.wire-mega-menu__panel h3 { + margin: 0 0 0.5rem; + font-size: 0.875rem; +} +.wire-mega-menu__panel a, +.wire-mobile-nav__panel a { + display: flex; + min-height: 44px; + align-items: center; + color: var(--wire-color-text); + text-decoration: none; +} +.wire-mobile-nav { + display: none; +} +.wire-mobile-nav__panel { + position: fixed; + inset: 4rem 1rem auto; + z-index: 100; + max-height: calc(100dvh - 5rem); + overflow: auto; + padding: 1rem; + background: var(--wire-color-surface); + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius); + box-shadow: var(--wire-shadow-1); +} + +.wire-page-shell { + width: 100%; + min-width: 0; + color: var(--wire-color-text); + background: var(--wire-color-bg); +} +.wire-page-shell--marketing, +.wire-page-shell--product { + overflow: clip; +} +.wire-page-shell--legal { + display: grid; + grid-template-columns: minmax(14rem, 18rem) minmax(0, 1fr); + gap: clamp(2rem, 5vw, 5rem); + max-width: 80rem; + margin-inline: auto; + padding: clamp(1rem, 4vw, 3rem); +} +.wire-page-shell__aside { + position: sticky; + top: 1rem; + align-self: start; +} +.wire-page-shell__main { + min-width: 0; + max-width: 52rem; +} + +.wire-product-card, +.wire-metric { + padding: clamp(1rem, 3vw, 1.5rem); + color: var(--wire-color-text); + background: var(--wire-color-surface); + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius); + box-shadow: var(--wire-shadow-1); +} +.wire-product-card { + display: grid; + gap: 0.8rem; +} +.wire-product-card__meta { + display: flex; + justify-content: space-between; + gap: 0.75rem; +} +.wire-product-card h3, +.wire-product-card p { + margin: 0; +} +.wire-product-card p { + color: var(--wire-color-muted); + line-height: 1.6; +} +.wire-product-card__link { + color: var(--wire-color-primary); + font-weight: 650; + text-decoration: none; +} +.wire-eyebrow { + color: var(--wire-color-primary); + font-size: 0.75rem; + font-weight: 750; + letter-spacing: 0.1em; + text-transform: uppercase; +} +.wire-metric-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); + gap: 1rem; +} +.wire-metric__value { + margin: 0; + font-size: clamp(1.75rem, 4vw, 2.5rem); + font-weight: 800; + letter-spacing: -0.03em; +} +.wire-metric__label { + margin: 0.35rem 0 0; + font-weight: 650; +} +.wire-metric__description { + margin: 0.35rem 0 0; + color: var(--wire-color-muted); + font-size: 0.875rem; + line-height: 1.5; +} + +.wire-sdk-tabs__panel { + overflow-x: auto; + border: 1px solid var(--wire-color-border); + border-radius: 0 0 var(--wire-radius) var(--wire-radius); + background: var(--wire-color-surface-2); +} +.wire-sdk-tabs__panel pre { + margin: 0; + min-width: max-content; + padding: 1rem; +} +.wire-legal-toc { + padding: 1rem; + border-left: 2px solid var(--wire-color-border); +} +.wire-legal-toc h2 { + margin: 0 0 0.75rem; + font-size: 0.875rem; +} +.wire-legal-toc ol { + display: grid; + gap: 0.5rem; + margin: 0; + padding-left: 1.25rem; +} +.wire-legal-toc a { + color: var(--wire-color-muted); + text-decoration: none; +} +.wire-cookie-list { + display: grid; + gap: 0.75rem; + margin-top: 1.25rem; +} +.wire-cookie-option { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + min-height: 44px; + padding: 0.75rem; + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius-sm); +} +.wire-cookie-option span { + display: grid; + gap: 0.25rem; +} +.wire-cookie-option small { + color: var(--wire-color-muted); + line-height: 1.4; +} + +@media (max-width: 768px) { + .wire-desktop-nav { + display: none; + } + .wire-mobile-nav { + display: block; + } + .wire-mega-menu__panel { + position: static; + grid-template-columns: 1fr; + width: 100%; + box-shadow: none; + } + .wire-page-shell--legal { + grid-template-columns: 1fr; + } + .wire-page-shell__aside { + position: static; + } +} +@media (max-width: 480px) { + .wire-form-row { + grid-template-columns: 1fr; + } + .wire-filter-bar { + align-items: stretch; + flex-direction: column; + } + .wire-filter-bar__controls { + display: grid; + } + .wire-dialog__actions > * { + flex: 1 1 100%; + } +} diff --git a/.publish/uploader/package.json b/.publish/uploader/package.json index 6d7db001..5f98a498 100644 --- a/.publish/uploader/package.json +++ b/.publish/uploader/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/uploader", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/uploader — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.53" + "@wrnexus/core": "^0.2.54" }, "files": [ "dist" diff --git a/.publish/validation/package.json b/.publish/validation/package.json index 3f3dca55..e99fbff3 100644 --- a/.publish/validation/package.json +++ b/.publish/validation/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/validation", - "version": "0.2.53", + "version": "0.2.54", "type": "module", "description": "@wrnexus/validation — part of the WrNexus framework.", "license": "MIT", diff --git a/docs/UI-COMPONENT-INVENTORY.md b/docs/UI-COMPONENT-INVENTORY.md new file mode 100644 index 00000000..98ed18cc --- /dev/null +++ b/docs/UI-COMPONENT-INVENTORY.md @@ -0,0 +1,31 @@ +# WRNexus UI component inventory + +This inventory maps the two approved v1.0 PDF checklists to implementation +stages. The source documents are **WRNexus Essential Component Inventory v1.0** +and **WRNexus Public SaaS Component System Checklist v1.0**. + +## Release boundary + +The current `@wrnexus/ui` release targets the documents' combined **minimum +first-release component set** and **essential components to build first**. These +are implemented as real components with theme tokens, responsive rules, +keyboard-native controls, visible focus, reduced-motion handling, and mobile +touch targets—not name-only aliases. + +SEO and structured data are intentionally handled by WRNexus page/head APIs. +Rendering them as body components would produce invalid or ineffective HTML. + +## Subsequent staged packages + +The remaining expanded inventory is retained for staged implementation in the +dependency order prescribed by the PDFs: + +1. `design-tokens` and `icons` +2. `ui-forms`, `ui-calendar`, `ui-feedback`, `ui-overlays` +3. `ui-navigation`, `ui-data`, `ui-charts`, `ui-editors`, `ui-media` +4. `ui-shell`, `ui-marketing`, `ui-commerce`, `ui-developers` +5. `ui-content`, `ui-legal`, `ui-status` + +Each stage must satisfy rendering, keyboard, focus, semantics, disabled/loading +states, mobile layout from 320 px, light/dark themes, reduced motion, no +horizontal overflow, documentation, and production-build tests before release. diff --git a/editors/vscode/CHANGELOG.md b/editors/vscode/CHANGELOG.md index 0ec47898..0b7b4d83 100644 --- a/editors/vscode/CHANGELOG.md +++ b/editors/vscode/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.2.12 + +- Added component-aware prop completions, hover tables, required-prop checks, + type mismatch errors, and unknown/invalid-option warnings. + ## 0.2.11 - Fixed `.wrn` files falling back to Plain Text when a legacy `wire` file diff --git a/editors/vscode/README.md b/editors/vscode/README.md index ad7cfff8..f649b511 100644 --- a/editors/vscode/README.md +++ b/editors/vscode/README.md @@ -94,6 +94,13 @@ realtime chat { } ``` +Put `// @required` immediately above a prop when callers must provide it while +retaining a typed runtime fallback. `propName = undefined` is also treated as +required when no fallback is appropriate. The editor shows required props first +in completion lists and reports an error when a component mount omits one. Prop +types are inferred from defaults, and string options used in component comparisons +are offered as completion choices. + Embedded language highlighting: - `view { ... }` uses HTML highlighting. diff --git a/editors/vscode/package-lock.json b/editors/vscode/package-lock.json index 9e755324..b2070c71 100644 --- a/editors/vscode/package-lock.json +++ b/editors/vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "wrnexus", - "version": "0.2.11", + "version": "0.2.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wrnexus", - "version": "0.2.11", + "version": "0.2.12", "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@vscode/vsce": "^3.9.2" diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 3262c011..4d9fdf09 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -2,7 +2,7 @@ "name": "wrnexus", "displayName": "WRNexus Language Support", "description": "Complete language support for WRNexus .wrn files, including highlighting, formatting, diagnostics, snippets, lifecycle hooks, state watchers, component functions, completions, and definition navigation.", - "version": "0.2.11", + "version": "0.2.12", "publisher": "wrnexus", "private": true, "license": "SEE LICENSE IN LICENSE", diff --git a/editors/vscode/src/component-intelligence.js b/editors/vscode/src/component-intelligence.js new file mode 100644 index 00000000..5266fd31 --- /dev/null +++ b/editors/vscode/src/component-intelligence.js @@ -0,0 +1,244 @@ +"use strict"; + +const vscode = require("vscode"); +const { + parseComponentMetadata, + parseComponentTags, + validateComponentTags, +} = require("./component-metadata"); + +let catalogPromise = null; + +async function readMetadata(uri) { + try { + const document = await vscode.workspace.openTextDocument(uri); + return parseComponentMetadata(document.getText(), uri); + } catch { + return null; + } +} + +async function installedUiUris() { + const uris = []; + for (const folder of vscode.workspace.workspaceFolders || []) { + const directory = vscode.Uri.joinPath( + folder.uri, + "node_modules", + "@wrnexus", + "ui", + "components", + ); + try { + for (const [name, type] of await vscode.workspace.fs.readDirectory(directory)) { + if (type === vscode.FileType.File && name.endsWith(".wrn")) { + uris.push(vscode.Uri.joinPath(directory, name)); + } + } + } catch { + // The UI package is optional; app-local components still work. + } + } + return uris; +} + +async function loadComponentCatalog() { + const localUris = await vscode.workspace.findFiles( + "**/*.wrn", + "**/{node_modules,dist,.wrnexus,.git}/**", + ); + const uiUris = await installedUiUris(); + const metadata = await Promise.all([...uiUris, ...localUris].map(readMetadata)); + const catalog = new Map(); + for (const component of metadata) { + if (component && (component.kind === "component" || component.kind === "layout")) { + catalog.set(component.name, component); + } + } + return catalog; +} + +function componentCatalog() { + catalogPromise ||= loadComponentCatalog(); + return catalogPromise; +} + +function invalidateCatalog() { + catalogPromise = null; +} + +function openingTagAt(source, offset) { + const opening = source.lastIndexOf("<", offset); + const closing = source.lastIndexOf(">", offset); + if (opening <= closing) return null; + const fragment = source.slice(opening, offset); + const match = /^<([A-Z][A-Za-z0-9_$]*)\b/.exec(fragment); + return match ? { name: match[1], fragment } : null; +} + +function propSnippet(prop) { + if (prop.type === "boolean") return `${prop.name}="{\${1:false}}"`; + if (prop.type === "number") return `${prop.name}="{\${1:0}}"`; + if (prop.type === "array") return `${prop.name}="{[\${1}]}"`; + if (prop.type === "object") return `${prop.name}="{{ \${1} }}"`; + if (prop.options.length > 0) return `${prop.name}="\${1|${prop.options.join(",")}|}"`; + return `${prop.name}="\${1}"`; +} + +function propDocumentation(prop) { + const lines = [ + `**${prop.name}** — \`${prop.type}\` ${prop.required ? "**required**" : "optional"}`, + ]; + if (!prop.required) lines.push(`Default: \`${prop.defaultValue}\``); + if (prop.options.length > 0) + lines.push(`Allowed: ${prop.options.map((v) => `\`${v}\``).join(", ")}`); + return lines.join("\n\n"); +} + +async function provideComponentCompletions(document, position) { + const source = document.getText(); + const offset = document.offsetAt(position); + const tag = openingTagAt(source, offset); + const catalog = await componentCatalog(); + const items = []; + + if (tag) { + const component = catalog.get(tag.name); + if (!component) return items; + const used = new Set([...tag.fragment.matchAll(/\s([^\s=/>]+)\s*=/g)].map((m) => m[1])); + for (const prop of component.props) { + if (used.has(prop.name)) continue; + const item = new vscode.CompletionItem(prop.name, vscode.CompletionItemKind.Property); + item.detail = `${prop.required ? "required" : "optional"} · ${prop.type}`; + item.documentation = new vscode.MarkdownString(propDocumentation(prop)); + item.insertText = new vscode.SnippetString(propSnippet(prop)); + item.sortText = `${prop.required ? "0" : "1"}-${prop.name}`; + items.push(item); + } + return items; + } + + const prefix = source.slice(Math.max(0, offset - 80), offset); + if (!/<[A-Za-z0-9_$]*$/.test(prefix)) return items; + for (const component of catalog.values()) { + const item = new vscode.CompletionItem(component.name, vscode.CompletionItemKind.Class); + const required = component.props.filter((prop) => prop.required); + item.detail = `WRN ${component.kind} · ${component.props.length} props`; + item.documentation = new vscode.MarkdownString(componentMarkdown(component)); + item.insertText = new vscode.SnippetString( + `${component.name}${required.map((prop, index) => ` ${prop.name}="\${${index + 1}}"`).join("")} />`, + ); + items.push(item); + } + return items; +} + +function componentMarkdown(component) { + const lines = [ + `### <${component.name}>`, + "", + "| Prop | Type | Required | Default / options |", + "|---|---|---:|---|", + ]; + for (const prop of component.props) { + const detail = + prop.options.length > 0 + ? prop.options.join(" \\| ") + : prop.required + ? "—" + : prop.defaultValue; + lines.push( + `| \`${prop.name}\` | \`${prop.type}\` | ${prop.required ? "yes" : "no"} | ${detail} |`, + ); + } + return lines.join("\n"); +} + +async function provideComponentHover(document, position) { + const source = document.getText(); + const offset = document.offsetAt(position); + const catalog = await componentCatalog(); + const tag = parseComponentTags(source).find( + (candidate) => offset >= candidate.start && offset <= candidate.end, + ); + if (!tag) return null; + const component = catalog.get(tag.name); + if (!component) return null; + const attribute = tag.attributes.find( + (candidate) => offset >= candidate.nameStart && offset <= candidate.nameEnd, + ); + const markdown = attribute + ? propDocumentation( + component.props.find((prop) => prop.name === attribute.name) || { + name: attribute.name, + type: "unknown", + required: false, + defaultValue: "unknown", + options: [], + }, + ) + : componentMarkdown(component); + return new vscode.Hover(new vscode.MarkdownString(markdown)); +} + +function registerComponentIntelligence(context) { + const diagnostics = vscode.languages.createDiagnosticCollection("wrnexus-components"); + + const update = async (document) => { + if (document.languageId !== "wrn") return; + const catalog = await componentCatalog(); + const results = validateComponentTags(document.getText(), catalog).map((result) => { + const diagnostic = new vscode.Diagnostic( + new vscode.Range(document.positionAt(result.start), document.positionAt(result.end)), + result.message, + result.severity === "error" + ? vscode.DiagnosticSeverity.Error + : vscode.DiagnosticSeverity.Warning, + ); + diagnostic.source = "WRNexus Components"; + diagnostic.code = result.code; + return diagnostic; + }); + diagnostics.set(document.uri, results); + }; + + const watcher = vscode.workspace.createFileSystemWatcher("**/*.wrn"); + const refreshOpenDocuments = () => { + invalidateCatalog(); + for (const document of vscode.workspace.textDocuments) void update(document); + }; + + context.subscriptions.push( + diagnostics, + watcher, + watcher.onDidCreate(refreshOpenDocuments), + watcher.onDidChange(refreshOpenDocuments), + watcher.onDidDelete(refreshOpenDocuments), + vscode.workspace.onDidOpenTextDocument(update), + vscode.workspace.onDidChangeTextDocument((event) => void update(event.document)), + vscode.workspace.onDidSaveTextDocument((document) => { + invalidateCatalog(); + void update(document); + }), + vscode.workspace.onDidCloseTextDocument((document) => diagnostics.delete(document.uri)), + vscode.languages.registerCompletionItemProvider( + { language: "wrn" }, + { provideCompletionItems: provideComponentCompletions }, + "<", + " ", + ), + vscode.languages.registerHoverProvider( + { language: "wrn" }, + { provideHover: provideComponentHover }, + ), + ); + + for (const document of vscode.workspace.textDocuments) void update(document); +} + +module.exports = { + componentMarkdown, + openingTagAt, + propDocumentation, + propSnippet, + registerComponentIntelligence, +}; diff --git a/editors/vscode/src/component-metadata.js b/editors/vscode/src/component-metadata.js new file mode 100644 index 00000000..7fae5a0a --- /dev/null +++ b/editors/vscode/src/component-metadata.js @@ -0,0 +1,201 @@ +"use strict"; + +const COMPONENT_DECLARATION = /\b(component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/; + +function findMatchingBrace(source, openingBrace) { + let depth = 0; + let quote = null; + let escaped = false; + + for (let index = openingBrace; index < source.length; index += 1) { + const character = source[index]; + if (quote) { + if (escaped) escaped = false; + else if (character === "\\") escaped = true; + else if (character === quote) quote = null; + continue; + } + if (character === '"' || character === "'" || character === "`") quote = character; + else if (character === "{") depth += 1; + else if (character === "}" && --depth === 0) return index; + } + + return -1; +} + +function inferType(defaultValue) { + const value = defaultValue.trim(); + if (value === "undefined") return "unknown"; + if (/^(?:true|false)$/.test(value)) return "boolean"; + if (/^-?(?:\d+\.?\d*|\.\d+)$/.test(value)) return "number"; + if (/^["'`]/.test(value)) return "string"; + if (value.startsWith("[")) return "array"; + if (value.startsWith("{")) return "object"; + if (value === "null") return "null"; + return "unknown"; +} + +function stringLiteral(value) { + const match = /^(?:"([\s\S]*)"|'([\s\S]*)'|`([\s\S]*)`)$/.exec(value.trim()); + return match ? (match[1] ?? match[2] ?? match[3]) : null; +} + +function inferOptions(source, propName) { + const escaped = propName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const options = new Set(); + const comparisons = new RegExp(`\\b${escaped}\\s*(?:===|!==|==|!=)\\s*(["'])(.*?)\\1`, "g"); + let match; + while ((match = comparisons.exec(source)) !== null) options.add(match[2]); + return [...options].sort(); +} + +function parseComponentMetadata(source, uri = null) { + const declaration = COMPONENT_DECLARATION.exec(source); + if (!declaration) return null; + + const propsKeyword = /\bprops\s*\{/.exec(source.slice(declaration.index)); + const props = []; + if (propsKeyword) { + const start = declaration.index + propsKeyword.index; + const openingBrace = source.indexOf("{", start); + const closingBrace = findMatchingBrace(source, openingBrace); + const bodyEnd = closingBrace === -1 ? source.length : closingBrace; + const body = source.slice(openingBrace + 1, bodyEnd); + const linePattern = /^(?:\s*\/\/\s*@required\s*\r?\n)?\s*([A-Za-z_$][\w$]*)\s*=\s*(.*?)\s*$/gm; + let propMatch; + while ((propMatch = linePattern.exec(body)) !== null) { + const defaultValue = propMatch[2]; + const name = propMatch[1]; + props.push({ + name, + defaultValue, + required: defaultValue.trim() === "undefined" || /^\s*\/\/\s*@required/m.test(propMatch[0]), + type: inferType(defaultValue), + options: inferOptions(source, name), + }); + } + } + + return { kind: declaration[1], name: declaration[2], props, uri }; +} + +function unwrapAttributeValue(value) { + const trimmed = value.trim(); + if (trimmed.startsWith("{") && trimmed.endsWith("}")) return trimmed.slice(1, -1).trim(); + return trimmed; +} + +function attributeValueType(value) { + const unwrapped = unwrapAttributeValue(value); + if (/^(?:true|false)$/.test(unwrapped)) return "boolean"; + if (/^-?(?:\d+\.?\d*|\.\d+)$/.test(unwrapped)) return "number"; + if (unwrapped.startsWith("[")) return "array"; + if (unwrapped.startsWith("{")) return "object"; + return "string"; +} + +function isTypeCompatible(prop, value) { + if (prop.type === "unknown" || prop.type === "null") return true; + const actual = attributeValueType(value); + if (prop.type === actual) return true; + if (prop.type === "number" && actual === "string") return Number.isFinite(Number(value)); + if (prop.type === "boolean" && actual === "string") + return /^(?:true|false|1|0|yes|no|on|off)?$/i.test(value); + return false; +} + +function parseComponentTags(source) { + const tags = []; + const pattern = /<([A-Z][A-Za-z0-9_$]*)(\s[\s\S]*?)?\s*\/?>/g; + let match; + while ((match = pattern.exec(source)) !== null) { + const attributes = []; + const attributeSource = match[2] || ""; + const attributeOffset = match.index + match[0].indexOf(attributeSource); + const attributePattern = /([^\s=/>]+)\s*=\s*(["'])([\s\S]*?)\2/g; + let attributeMatch; + while ((attributeMatch = attributePattern.exec(attributeSource)) !== null) { + const nameStart = attributeOffset + attributeMatch.index; + attributes.push({ + name: attributeMatch[1], + value: attributeMatch[3], + nameStart, + nameEnd: nameStart + attributeMatch[1].length, + }); + } + tags.push({ + name: match[1], + start: match.index, + end: match.index + match[0].length, + nameStart: match.index + 1, + nameEnd: match.index + 1 + match[1].length, + attributes, + }); + } + return tags; +} + +function validateComponentTags(source, components) { + const diagnostics = []; + for (const tag of parseComponentTags(source)) { + const component = components.get(tag.name); + if (!component) continue; + const provided = new Map(tag.attributes.map((attribute) => [attribute.name, attribute])); + const declared = new Map(component.props.map((prop) => [prop.name, prop])); + + for (const prop of component.props) { + if (prop.required && !provided.has(prop.name)) { + diagnostics.push({ + severity: "error", + code: "wrn-missing-component-prop", + message: `<${tag.name}> requires prop \`${prop.name}\` (${prop.type}).`, + start: tag.nameStart, + end: tag.nameEnd, + }); + } + } + + for (const attribute of tag.attributes) { + const prop = declared.get(attribute.name); + if (!prop) { + diagnostics.push({ + severity: "warning", + code: "wrn-unknown-component-prop", + message: `Unknown prop \`${attribute.name}\` on <${tag.name}>.`, + start: attribute.nameStart, + end: attribute.nameEnd, + }); + continue; + } + if (!isTypeCompatible(prop, attribute.value)) { + diagnostics.push({ + severity: "error", + code: "wrn-component-prop-type", + message: `Prop \`${attribute.name}\` on <${tag.name}> expects ${prop.type}, but received ${attributeValueType(attribute.value)}.`, + start: attribute.nameStart, + end: attribute.nameEnd, + }); + } + const literal = stringLiteral(attribute.value) ?? attribute.value; + if (prop.options.length > 0 && !prop.options.includes(literal)) { + diagnostics.push({ + severity: "warning", + code: "wrn-component-prop-option", + message: `Prop \`${attribute.name}\` should be one of: ${prop.options.join(", ")}.`, + start: attribute.nameStart, + end: attribute.nameEnd, + }); + } + } + } + return diagnostics; +} + +module.exports = { + attributeValueType, + inferType, + isTypeCompatible, + parseComponentMetadata, + parseComponentTags, + validateComponentTags, +}; diff --git a/editors/vscode/src/extension.js b/editors/vscode/src/extension.js index 828ebf72..6f00faad 100644 --- a/editors/vscode/src/extension.js +++ b/editors/vscode/src/extension.js @@ -9,6 +9,8 @@ const { registerCompletionProvider } = require("./completion"); const { registerDefinitionProvider } = require("./definition"); +const { registerComponentIntelligence } = require("./component-intelligence"); + const { registerDiagnostics } = require("./diagnostics"); /** @@ -337,6 +339,7 @@ function activate(context) { registerCompilerDiagnostics(context); registerCompletionProvider(context); registerDefinitionProvider(context); + registerComponentIntelligence(context); registerFormatter(context); registerSemanticTokens(context); } diff --git a/editors/vscode/test/component-metadata.test.js b/editors/vscode/test/component-metadata.test.js new file mode 100644 index 00000000..3c729553 --- /dev/null +++ b/editors/vscode/test/component-metadata.test.js @@ -0,0 +1,82 @@ +"use strict"; + +const assert = require("node:assert"); +const { test } = require("node:test"); +const { + attributeValueType, + parseComponentMetadata, + parseComponentTags, + validateComponentTags, +} = require("../src/component-metadata"); + +const source = `component Button { + props { + // @required + label = "" + variant = "primary" + disabled = false + count = 0 + items = [] + } + view { + + } +}`; + +test("extracts component prop types, required props, defaults, and options", () => { + const component = parseComponentMetadata(source); + + assert.equal(component.name, "Button"); + assert.deepEqual( + component.props.map(({ name, type, required, options }) => ({ name, type, required, options })), + [ + { name: "label", type: "string", required: true, options: [] }, + { name: "variant", type: "string", required: false, options: ["danger", "primary"] }, + { name: "disabled", type: "boolean", required: false, options: [] }, + { name: "count", type: "number", required: false, options: [] }, + { name: "items", type: "array", required: false, options: [] }, + ], + ); +}); + +test("parses multiline component tags and their attribute locations", () => { + const page = ` + } +} diff --git a/packages/ui/components/CookiePreferencesDialog.wrn b/packages/ui/components/CookiePreferencesDialog.wrn new file mode 100644 index 00000000..a74888fd --- /dev/null +++ b/packages/ui/components/CookiePreferencesDialog.wrn @@ -0,0 +1,17 @@ +component CookiePreferencesDialog { + props { + title = "Cookie preferences" + description = "Choose which optional cookies you allow. Essential cookies are always enabled." + saveLabel = "Save preferences" + acceptLabel = "Accept all" + rejectLabel = "Reject optional" + open = false + class = "" + } + state visible = open + state analytics = false + state marketing = false + view { + + } +} diff --git a/packages/ui/components/DataTable.wrn b/packages/ui/components/DataTable.wrn new file mode 100644 index 00000000..a6220a9c --- /dev/null +++ b/packages/ui/components/DataTable.wrn @@ -0,0 +1,13 @@ +component DataTable { + props { + caption = "Data table" + columns = [] + rows = [] + emptyMessage = "No data available." + striped = false + class = "" + } + view { +
{#each columns as column}{/each}{#each rows as row}{#each columns as column}{/each}{:empty}{/each}
{caption}
{column.label}
{row[column.key]}
{emptyMessage}
+ } +} diff --git a/packages/ui/components/DateTimePicker.wrn b/packages/ui/components/DateTimePicker.wrn new file mode 100644 index 00000000..341f5464 --- /dev/null +++ b/packages/ui/components/DateTimePicker.wrn @@ -0,0 +1,14 @@ +component DateTimePicker { + props { + id = "datetime-picker" + name = "datetime" + label = "Date and time" + value = "" + min = "" + max = "" + required = false + disabled = false + class = "" + } + view {
} +} diff --git a/packages/ui/components/DesktopNavigation.wrn b/packages/ui/components/DesktopNavigation.wrn new file mode 100644 index 00000000..cf9b53b7 --- /dev/null +++ b/packages/ui/components/DesktopNavigation.wrn @@ -0,0 +1,8 @@ +component DesktopNavigation { + props { + label = "Primary navigation" + items = [] + class = "" + } + view { } +} diff --git a/packages/ui/components/FAQ.wrn b/packages/ui/components/FAQ.wrn new file mode 100644 index 00000000..f5b96261 --- /dev/null +++ b/packages/ui/components/FAQ.wrn @@ -0,0 +1,12 @@ +component FAQ { + props { + eyebrow = "" + title = "Frequently asked questions" + description = "" + items = [] + centered = true + allowMultiple = false + defaultOpenIndex = 0 + } + view { } +} diff --git a/packages/ui/components/FilterBar.wrn b/packages/ui/components/FilterBar.wrn new file mode 100644 index 00000000..26b93a9e --- /dev/null +++ b/packages/ui/components/FilterBar.wrn @@ -0,0 +1,9 @@ +component FilterBar { + props { + label = "Filters" + clearLabel = "Clear filters" + showClear = true + class = "" + } + view {
} +} diff --git a/packages/ui/components/Form.wrn b/packages/ui/components/Form.wrn new file mode 100644 index 00000000..fccdbcc9 --- /dev/null +++ b/packages/ui/components/Form.wrn @@ -0,0 +1,12 @@ +component Form { + props { + action = "" + method = "post" + name = "" + loading = false + class = "" + } + view { +
+ } +} diff --git a/packages/ui/components/FormError.wrn b/packages/ui/components/FormError.wrn new file mode 100644 index 00000000..c4ba8430 --- /dev/null +++ b/packages/ui/components/FormError.wrn @@ -0,0 +1,8 @@ +component FormError { + props { + id = "" + message = "" + class = "" + } + view { } +} diff --git a/packages/ui/components/FormHelpText.wrn b/packages/ui/components/FormHelpText.wrn new file mode 100644 index 00000000..6d7bfdd1 --- /dev/null +++ b/packages/ui/components/FormHelpText.wrn @@ -0,0 +1,8 @@ +component FormHelpText { + props { + id = "" + text = "" + class = "" + } + view {

{text}

} +} diff --git a/packages/ui/components/FormLabel.wrn b/packages/ui/components/FormLabel.wrn new file mode 100644 index 00000000..9f181812 --- /dev/null +++ b/packages/ui/components/FormLabel.wrn @@ -0,0 +1,12 @@ +component FormLabel { + props { + for = "" + label = "Label" + required = false + optional = false + class = "" + } + view { + + } +} diff --git a/packages/ui/components/LegalPageShell.wrn b/packages/ui/components/LegalPageShell.wrn new file mode 100644 index 00000000..0c2cbc4e --- /dev/null +++ b/packages/ui/components/LegalPageShell.wrn @@ -0,0 +1,6 @@ +component LegalPageShell { + props { + class = "" + } + view { } +} diff --git a/packages/ui/components/LegalTableOfContents.wrn b/packages/ui/components/LegalTableOfContents.wrn new file mode 100644 index 00000000..d3aaaf24 --- /dev/null +++ b/packages/ui/components/LegalTableOfContents.wrn @@ -0,0 +1,8 @@ +component LegalTableOfContents { + props { + title = "On this page" + items = [] + class = "" + } + view { } +} diff --git a/packages/ui/components/LoadingButton.wrn b/packages/ui/components/LoadingButton.wrn new file mode 100644 index 00000000..c54574c2 --- /dev/null +++ b/packages/ui/components/LoadingButton.wrn @@ -0,0 +1,14 @@ +component LoadingButton { + props { + label = "Continue" + loadingLabel = "Loading…" + loading = false + disabled = false + type = "button" + variant = "primary" + class = "" + } + view { + + } +} diff --git a/packages/ui/components/MarketingPageShell.wrn b/packages/ui/components/MarketingPageShell.wrn new file mode 100644 index 00000000..cea8ffcf --- /dev/null +++ b/packages/ui/components/MarketingPageShell.wrn @@ -0,0 +1,6 @@ +component MarketingPageShell { + props { + class = "" + } + view {
} +} diff --git a/packages/ui/components/MegaMenu.wrn b/packages/ui/components/MegaMenu.wrn new file mode 100644 index 00000000..b6f158e3 --- /dev/null +++ b/packages/ui/components/MegaMenu.wrn @@ -0,0 +1,11 @@ +component MegaMenu { + props { + label = "Explore" + sections = [] + class = "" + } + state open = false + view { +
{#each sections as section}

{section.title}

{/each}
+ } +} diff --git a/packages/ui/components/MetricGrid.wrn b/packages/ui/components/MetricGrid.wrn new file mode 100644 index 00000000..9edac08b --- /dev/null +++ b/packages/ui/components/MetricGrid.wrn @@ -0,0 +1,8 @@ +component MetricGrid { + props { + label = "Key metrics" + metrics = [] + class = "" + } + view {
{#each metrics as metric}

{metric.value}

{metric.label}

{metric.description}

{/each}
} +} diff --git a/packages/ui/components/MobileNavigation.wrn b/packages/ui/components/MobileNavigation.wrn new file mode 100644 index 00000000..c47c4ea4 --- /dev/null +++ b/packages/ui/components/MobileNavigation.wrn @@ -0,0 +1,12 @@ +component MobileNavigation { + props { + label = "Menu" + closeLabel = "Close menu" + items = [] + class = "" + } + state open = false + view { +
+ } +} diff --git a/packages/ui/components/MultiSelect.wrn b/packages/ui/components/MultiSelect.wrn new file mode 100644 index 00000000..ec9d25b5 --- /dev/null +++ b/packages/ui/components/MultiSelect.wrn @@ -0,0 +1,15 @@ +component MultiSelect { + props { + id = "multi-select" + name = "" + label = "Choose options" + options = [] + required = false + disabled = false + size = 5 + class = "" + } + view { +
+ } +} diff --git a/packages/ui/components/PricingComparisonTable.wrn b/packages/ui/components/PricingComparisonTable.wrn new file mode 100644 index 00000000..39b53112 --- /dev/null +++ b/packages/ui/components/PricingComparisonTable.wrn @@ -0,0 +1,9 @@ +component PricingComparisonTable { + props { + caption = "Plan comparison" + plans = [] + features = [] + class = "" + } + view {
{#each plans as plan}{/each}{#each features as feature}{#each feature.values as value}{/each}{/each}
{caption}
Feature{plan.name}
{feature.label}{value}
} +} diff --git a/packages/ui/components/ProductCard.wrn b/packages/ui/components/ProductCard.wrn new file mode 100644 index 00000000..a752772a --- /dev/null +++ b/packages/ui/components/ProductCard.wrn @@ -0,0 +1,12 @@ +component ProductCard { + props { + eyebrow = "Product" + title = "Product name" + description = "" + href = "#" + actionLabel = "Learn more" + status = "" + class = "" + } + view { } +} diff --git a/packages/ui/components/ProductPageShell.wrn b/packages/ui/components/ProductPageShell.wrn new file mode 100644 index 00000000..66989798 --- /dev/null +++ b/packages/ui/components/ProductPageShell.wrn @@ -0,0 +1,6 @@ +component ProductPageShell { + props { + class = "" + } + view {
} +} diff --git a/packages/ui/components/ProductsMegaMenu.wrn b/packages/ui/components/ProductsMegaMenu.wrn new file mode 100644 index 00000000..bfa7aeea --- /dev/null +++ b/packages/ui/components/ProductsMegaMenu.wrn @@ -0,0 +1,8 @@ +component ProductsMegaMenu { + props { + label = "Products" + sections = [] + class = "" + } + view { } +} diff --git a/packages/ui/components/QuietHoursPicker.wrn b/packages/ui/components/QuietHoursPicker.wrn new file mode 100644 index 00000000..479fd396 --- /dev/null +++ b/packages/ui/components/QuietHoursPicker.wrn @@ -0,0 +1,15 @@ +component QuietHoursPicker { + props { + label = "Quiet hours" + startName = "quietStart" + endName = "quietEnd" + start = "22:00" + end = "08:00" + timezone = "UTC" + disabled = false + class = "" + } + view { +
{label}

Timezone: {timezone}

+ } +} diff --git a/packages/ui/components/RecurringSchedulePicker.wrn b/packages/ui/components/RecurringSchedulePicker.wrn new file mode 100644 index 00000000..d3a137a4 --- /dev/null +++ b/packages/ui/components/RecurringSchedulePicker.wrn @@ -0,0 +1,13 @@ +component RecurringSchedulePicker { + props { + id = "recurring-schedule" + name = "recurrence" + label = "Repeat" + value = "none" + disabled = false + class = "" + } + view { +
+ } +} diff --git a/packages/ui/components/SDKTabs.wrn b/packages/ui/components/SDKTabs.wrn new file mode 100644 index 00000000..7e8af688 --- /dev/null +++ b/packages/ui/components/SDKTabs.wrn @@ -0,0 +1,10 @@ +component SDKTabs { + props { + label = "SDK languages" + tabs = [] + defaultIndex = 0 + class = "" + } + state active = defaultIndex + view {
{#each tabs as tab, index}{/each}
{#each tabs as tab, index}
{tab.code}
{/each}
} +} diff --git a/packages/ui/components/TimePicker.wrn b/packages/ui/components/TimePicker.wrn new file mode 100644 index 00000000..56335ad6 --- /dev/null +++ b/packages/ui/components/TimePicker.wrn @@ -0,0 +1,15 @@ +component TimePicker { + props { + id = "time-picker" + name = "time" + label = "Time" + value = "" + min = "" + max = "" + step = 60 + required = false + disabled = false + class = "" + } + view {
} +} diff --git a/packages/ui/components/Typography.wrn b/packages/ui/components/Typography.wrn new file mode 100644 index 00000000..4e27647f --- /dev/null +++ b/packages/ui/components/Typography.wrn @@ -0,0 +1,11 @@ +component Typography { + props { + as = "p" + variant = "body" + align = "start" + class = "" + } + view { +
+ } +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 38672812..24f17e58 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ui", - "version": "0.2.53", + "version": "0.2.54", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/ui/test/ui.test.ts b/packages/ui/test/ui.test.ts index 1222f640..729c7d21 100644 --- a/packages/ui/test/ui.test.ts +++ b/packages/ui/test/ui.test.ts @@ -13,6 +13,52 @@ test("bundled UI assets are discoverable and readable", () => { expect(uiCss()).toContain("--wire-"); }); +test("PDF minimum-release and essential build-first components are bundled", () => { + const required = [ + "Typography", + "Form", + "FormLabel", + "FormHelpText", + "FormError", + "LoadingButton", + "Combobox", + "MultiSelect", + "TimePicker", + "DateTimePicker", + "RecurringSchedulePicker", + "QuietHoursPicker", + "ConfirmationDialog", + "DataTable", + "FilterBar", + "MegaMenu", + "DesktopNavigation", + "ProductsMegaMenu", + "MobileNavigation", + "MarketingPageShell", + "ProductPageShell", + "LegalPageShell", + "ProductCard", + "MetricGrid", + "FAQ", + "PricingComparisonTable", + "SDKTabs", + "LegalTableOfContents", + "CookiePreferencesDialog", + ]; + + expect(uiComponentNames()).toEqual(expect.arrayContaining(required)); +}); + +test("component-system CSS includes responsive, theme-token, focus, and reduced-motion rules", () => { + const css = uiCss(); + expect(css).toContain("@media (max-width: 768px)"); + expect(css).toContain("@media (max-width: 480px)"); + expect(css).toContain("@media (prefers-reduced-motion: reduce)"); + expect(css).toContain(":focus-visible"); + expect(css).toContain("var(--wire-color-surface)"); + expect(css).toContain("min-height: 44px"); +}); + test.each(uiComponentNames())("bundled UI component %s compiles", (name) => { const path = join(uiComponentsDir(), `${name}.wrn`); expect(() => compileWireFile(readFileSync(path, "utf8"), path)).not.toThrow(); diff --git a/packages/ui/ui.css b/packages/ui/ui.css index 3d2f296c..5997650e 100644 --- a/packages/ui/ui.css +++ b/packages/ui/ui.css @@ -537,3 +537,418 @@ outline: 2px solid rgb(124 58 237); outline-offset: 2px; } + +/* --- Component-system foundations --------------------------------------- */ +.wire-type { + margin: 0; + color: var(--wire-color-text); +} +.wire-type--display { + font-size: clamp(2.25rem, 7vw, 4.75rem); + line-height: 1.02; + font-weight: 800; + letter-spacing: -0.04em; +} +.wire-type--heading { + font-size: clamp(1.75rem, 4vw, 3rem); + line-height: 1.1; + font-weight: 750; + letter-spacing: -0.025em; +} +.wire-type--lead { + font-size: clamp(1.05rem, 2vw, 1.25rem); + line-height: 1.65; + color: var(--wire-color-muted); +} +.wire-type--body { + font-size: 1rem; + line-height: 1.65; +} +.wire-type--small { + font-size: 0.875rem; + line-height: 1.5; +} +.wire-text--center { + text-align: center; +} +.wire-text--end { + text-align: end; +} + +.wire-form { + display: grid; + gap: 1.25rem; + width: 100%; +} +.wire-form-row { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; +} +.wire-field, +.wire-fieldset { + display: grid; + gap: 0.45rem; + min-width: 0; + border: 0; + padding: 0; + margin: 0; +} +.wire-label { + color: var(--wire-color-text); + font-size: 0.875rem; + font-weight: 650; + line-height: 1.4; +} +.wire-required, +.wire-field-message--error { + color: var(--wire-color-danger); +} +.wire-optional, +.wire-field-message--help { + color: var(--wire-color-muted); + font-weight: 400; +} +.wire-field-message { + margin: 0; + font-size: 0.8125rem; + line-height: 1.5; +} +.wire-input:disabled, +.wire-btn:disabled { + cursor: not-allowed; + opacity: 0.58; +} +.wire-input[aria-invalid="true"] { + border-color: var(--wire-color-danger); +} +.wire-multiselect { + min-height: 8rem; +} +.wire-loading-button { + min-height: 44px; +} +.wire-spinner--inline { + width: 1rem; + height: 1rem; +} + +.wire-overlay { + position: fixed; + inset: 0; + z-index: 1000; + display: grid; + place-items: center; + padding: 1rem; + background: rgb(0 0 0 / 0.55); + backdrop-filter: blur(4px); +} +.wire-dialog { + width: min(100%, 32rem); + max-height: calc(100dvh - 2rem); + overflow: auto; + padding: clamp(1.25rem, 4vw, 2rem); + color: var(--wire-color-text); + background: var(--wire-color-surface); + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius); + box-shadow: var(--wire-shadow-1); +} +.wire-dialog__title { + margin: 0; + font-size: 1.35rem; + line-height: 1.25; +} +.wire-dialog__description { + margin: 0.75rem 0 0; + color: var(--wire-color-muted); + line-height: 1.6; +} +.wire-dialog__actions { + display: flex; + justify-content: flex-end; + gap: 0.75rem; + margin-top: 1.5rem; +} +.wire-dialog__actions--wrap { + flex-wrap: wrap; +} + +.wire-filter-bar { + display: flex; + align-items: end; + justify-content: space-between; + gap: 1rem; + padding: 1rem; + background: var(--wire-color-surface); + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius); +} +.wire-filter-bar__controls { + display: flex; + flex: 1; + flex-wrap: wrap; + align-items: end; + gap: 0.75rem; +} +.wire-table--striped tbody tr:nth-child(even) { + background: var(--wire-color-surface-2); +} +.wire-comparison-table { + min-width: 42rem; +} + +.wire-desktop-nav > ul, +.wire-mobile-nav ul, +.wire-mega-menu ul { + list-style: none; + padding: 0; + margin: 0; +} +.wire-desktop-nav > ul { + display: flex; + align-items: center; + gap: 0.25rem; +} +.wire-desktop-nav a, +.wire-mega-menu__trigger { + display: inline-flex; + min-height: 44px; + align-items: center; + gap: 0.35rem; + padding: 0.6rem 0.8rem; + color: var(--wire-color-text); + background: transparent; + border: 0; + border-radius: var(--wire-radius-sm); + text-decoration: none; + font: inherit; + cursor: pointer; +} +.wire-desktop-nav a:hover, +.wire-mega-menu__trigger:hover { + background: var(--wire-color-surface-2); +} +.wire-mega-menu { + position: relative; +} +.wire-mega-menu__panel { + position: absolute; + z-index: 100; + top: calc(100% + 0.5rem); + left: 0; + display: grid; + grid-template-columns: repeat(3, minmax(10rem, 1fr)); + gap: 1.25rem; + width: min(46rem, calc(100vw - 2rem)); + padding: 1.25rem; + color: var(--wire-color-text); + background: var(--wire-color-surface); + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius); + box-shadow: var(--wire-shadow-1); +} +.wire-mega-menu__panel h3 { + margin: 0 0 0.5rem; + font-size: 0.875rem; +} +.wire-mega-menu__panel a, +.wire-mobile-nav__panel a { + display: flex; + min-height: 44px; + align-items: center; + color: var(--wire-color-text); + text-decoration: none; +} +.wire-mobile-nav { + display: none; +} +.wire-mobile-nav__panel { + position: fixed; + inset: 4rem 1rem auto; + z-index: 100; + max-height: calc(100dvh - 5rem); + overflow: auto; + padding: 1rem; + background: var(--wire-color-surface); + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius); + box-shadow: var(--wire-shadow-1); +} + +.wire-page-shell { + width: 100%; + min-width: 0; + color: var(--wire-color-text); + background: var(--wire-color-bg); +} +.wire-page-shell--marketing, +.wire-page-shell--product { + overflow: clip; +} +.wire-page-shell--legal { + display: grid; + grid-template-columns: minmax(14rem, 18rem) minmax(0, 1fr); + gap: clamp(2rem, 5vw, 5rem); + max-width: 80rem; + margin-inline: auto; + padding: clamp(1rem, 4vw, 3rem); +} +.wire-page-shell__aside { + position: sticky; + top: 1rem; + align-self: start; +} +.wire-page-shell__main { + min-width: 0; + max-width: 52rem; +} + +.wire-product-card, +.wire-metric { + padding: clamp(1rem, 3vw, 1.5rem); + color: var(--wire-color-text); + background: var(--wire-color-surface); + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius); + box-shadow: var(--wire-shadow-1); +} +.wire-product-card { + display: grid; + gap: 0.8rem; +} +.wire-product-card__meta { + display: flex; + justify-content: space-between; + gap: 0.75rem; +} +.wire-product-card h3, +.wire-product-card p { + margin: 0; +} +.wire-product-card p { + color: var(--wire-color-muted); + line-height: 1.6; +} +.wire-product-card__link { + color: var(--wire-color-primary); + font-weight: 650; + text-decoration: none; +} +.wire-eyebrow { + color: var(--wire-color-primary); + font-size: 0.75rem; + font-weight: 750; + letter-spacing: 0.1em; + text-transform: uppercase; +} +.wire-metric-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); + gap: 1rem; +} +.wire-metric__value { + margin: 0; + font-size: clamp(1.75rem, 4vw, 2.5rem); + font-weight: 800; + letter-spacing: -0.03em; +} +.wire-metric__label { + margin: 0.35rem 0 0; + font-weight: 650; +} +.wire-metric__description { + margin: 0.35rem 0 0; + color: var(--wire-color-muted); + font-size: 0.875rem; + line-height: 1.5; +} + +.wire-sdk-tabs__panel { + overflow-x: auto; + border: 1px solid var(--wire-color-border); + border-radius: 0 0 var(--wire-radius) var(--wire-radius); + background: var(--wire-color-surface-2); +} +.wire-sdk-tabs__panel pre { + margin: 0; + min-width: max-content; + padding: 1rem; +} +.wire-legal-toc { + padding: 1rem; + border-left: 2px solid var(--wire-color-border); +} +.wire-legal-toc h2 { + margin: 0 0 0.75rem; + font-size: 0.875rem; +} +.wire-legal-toc ol { + display: grid; + gap: 0.5rem; + margin: 0; + padding-left: 1.25rem; +} +.wire-legal-toc a { + color: var(--wire-color-muted); + text-decoration: none; +} +.wire-cookie-list { + display: grid; + gap: 0.75rem; + margin-top: 1.25rem; +} +.wire-cookie-option { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + min-height: 44px; + padding: 0.75rem; + border: 1px solid var(--wire-color-border); + border-radius: var(--wire-radius-sm); +} +.wire-cookie-option span { + display: grid; + gap: 0.25rem; +} +.wire-cookie-option small { + color: var(--wire-color-muted); + line-height: 1.4; +} + +@media (max-width: 768px) { + .wire-desktop-nav { + display: none; + } + .wire-mobile-nav { + display: block; + } + .wire-mega-menu__panel { + position: static; + grid-template-columns: 1fr; + width: 100%; + box-shadow: none; + } + .wire-page-shell--legal { + grid-template-columns: 1fr; + } + .wire-page-shell__aside { + position: static; + } +} +@media (max-width: 480px) { + .wire-form-row { + grid-template-columns: 1fr; + } + .wire-filter-bar { + align-items: stretch; + flex-direction: column; + } + .wire-filter-bar__controls { + display: grid; + } + .wire-dialog__actions > * { + flex: 1 1 100%; + } +} diff --git a/packages/uploader/package.json b/packages/uploader/package.json index dbae99ab..60ce838f 100644 --- a/packages/uploader/package.json +++ b/packages/uploader/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/uploader", - "version": "0.2.53", + "version": "0.2.54", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/validation/package.json b/packages/validation/package.json index 9d4501f8..22a50e1f 100644 --- a/packages/validation/package.json +++ b/packages/validation/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/validation", - "version": "0.2.53", + "version": "0.2.54", "private": true, "type": "module", "main": "src/index.ts", diff --git a/update-package-versions.mjs b/update-package-versions.mjs index f6fae603..8fd31352 100644 --- a/update-package-versions.mjs +++ b/update-package-versions.mjs @@ -1,7 +1,7 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; -const version = "0.2.53"; +const version = "0.2.54"; const dependencyGroups = [ "dependencies",