release: WRNexusJS 0.2.52
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
+11
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"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.51",
|
||||
"@wrnexus/router": "^0.2.51",
|
||||
"@wrnexus/csr": "^0.2.51",
|
||||
"@wrnexus/compiler": "^0.2.51",
|
||||
"@wrnexus/styles": "^0.2.51",
|
||||
"@wrnexus/dev-server": "^0.2.51",
|
||||
"@wrnexus/ui": "^0.2.51",
|
||||
"@wrnexus/validation": "^0.2.51",
|
||||
"@wrnexus/i18n": "^0.2.51",
|
||||
"@wrnexus/db": "^0.2.51"
|
||||
"@wrnexus/core": "^0.2.52",
|
||||
"@wrnexus/router": "^0.2.52",
|
||||
"@wrnexus/csr": "^0.2.52",
|
||||
"@wrnexus/compiler": "^0.2.52",
|
||||
"@wrnexus/styles": "^0.2.52",
|
||||
"@wrnexus/dev-server": "^0.2.52",
|
||||
"@wrnexus/ui": "^0.2.52",
|
||||
"@wrnexus/validation": "^0.2.52",
|
||||
"@wrnexus/i18n": "^0.2.52",
|
||||
"@wrnexus/db": "^0.2.52"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/core — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/csr — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.51"
|
||||
"@wrnexus/core": "^0.2.52"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/db — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/dev-server — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -25,18 +25,18 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.51",
|
||||
"@wrnexus/router": "^0.2.51",
|
||||
"@wrnexus/ssr": "^0.2.51",
|
||||
"@wrnexus/csr": "^0.2.51",
|
||||
"@wrnexus/compiler": "^0.2.51",
|
||||
"@wrnexus/styles": "^0.2.51",
|
||||
"@wrnexus/ui": "^0.2.51",
|
||||
"@wrnexus/validation": "^0.2.51",
|
||||
"@wrnexus/i18n": "^0.2.51",
|
||||
"@wrnexus/db": "^0.2.51",
|
||||
"@wrnexus/pubsub": "^0.2.51",
|
||||
"@wrnexus/uploader": "^0.2.51"
|
||||
"@wrnexus/core": "^0.2.52",
|
||||
"@wrnexus/router": "^0.2.52",
|
||||
"@wrnexus/ssr": "^0.2.52",
|
||||
"@wrnexus/csr": "^0.2.52",
|
||||
"@wrnexus/compiler": "^0.2.52",
|
||||
"@wrnexus/styles": "^0.2.52",
|
||||
"@wrnexus/ui": "^0.2.52",
|
||||
"@wrnexus/validation": "^0.2.52",
|
||||
"@wrnexus/i18n": "^0.2.52",
|
||||
"@wrnexus/db": "^0.2.52",
|
||||
"@wrnexus/pubsub": "^0.2.52",
|
||||
"@wrnexus/uploader": "^0.2.52"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/encryption — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"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.51"
|
||||
"@wrnexus/core": "^0.2.52"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/i18n — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.51"
|
||||
"@wrnexus/core": "^0.2.52"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/jwt — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/mobile — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/native": "^0.2.51"
|
||||
"@wrnexus/native": "^0.2.52"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/native — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/router — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,8 +21,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/compiler": "^0.2.51",
|
||||
"@wrnexus/core": "^0.2.51"
|
||||
"@wrnexus/compiler": "^0.2.52",
|
||||
"@wrnexus/core": "^0.2.52"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/ssr — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.51"
|
||||
"@wrnexus/core": "^0.2.52"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/styles — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/uploader": "^0.2.51"
|
||||
"@wrnexus/uploader": "^0.2.52"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/test — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -65,7 +65,7 @@ body from either a named prop or the default slot.
|
||||
### Additional controls & data display
|
||||
|
||||
Also shipped: `select`, `radio`, `switch`, `progress`, `tag`, `skeleton`,
|
||||
`tooltip`, and `table`.
|
||||
`tooltip`, `table`, `FAQAccordion`, `AnnouncementBar`, and `BackToTop`.
|
||||
|
||||
The authoritative, always-current list is `uiComponentNames()` (below), which reads
|
||||
the component directory at runtime.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
component AnnouncementBar {
|
||||
props {
|
||||
badge = "New"
|
||||
message = "WrNexus Organizations is now available."
|
||||
description = "Build secure multi-tenant applications with teams, roles, domains, and enterprise SSO."
|
||||
href = "/organizations"
|
||||
actionLabel = "Explore organizations"
|
||||
ariaLabel = "Announcement"
|
||||
}
|
||||
|
||||
state visible = true
|
||||
|
||||
view {
|
||||
<aside
|
||||
role="region"
|
||||
aria-label="{ariaLabel}"
|
||||
class="relative z-[70] overflow-hidden border-b border-indigo-200 bg-indigo-50 text-slate-900 dark:border-indigo-500/20 dark:bg-indigo-500/10 dark:text-white"
|
||||
class:hidden="!visible"
|
||||
>
|
||||
<!-- Decorative background -->
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute inset-0 overflow-hidden"
|
||||
>
|
||||
<div class="absolute -left-20 -top-24 h-40 w-40 rounded-full bg-indigo-300/30 blur-3xl dark:bg-indigo-500/10"></div>
|
||||
|
||||
<div class="absolute -right-20 -bottom-24 h-40 w-40 rounded-full bg-violet-300/30 blur-3xl dark:bg-violet-500/10"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative flex min-h-11 w-full items-center justify-between gap-4 px-4 py-2.5 sm:px-6 lg:px-10 xl:px-12"
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 items-center justify-center gap-3">
|
||||
<!-- Badge -->
|
||||
<span
|
||||
class="hidden shrink-0 items-center gap-1.5 rounded-full bg-indigo-600 px-2.5 py-1 text-[10px] font-bold uppercase tracking-[0.12em] text-white shadow-sm sm:inline-flex"
|
||||
>
|
||||
<span class="icon-[lucide--sparkles] h-3 w-3" aria-hidden="true" ></span>
|
||||
|
||||
{badge}
|
||||
</span>
|
||||
|
||||
<!-- Message -->
|
||||
<p
|
||||
class="min-w-0 text-center text-xs font-medium leading-5 text-slate-700 dark:text-slate-300 sm:text-sm"
|
||||
>
|
||||
<span class="font-semibold text-slate-950 dark:text-white">
|
||||
{message}
|
||||
</span>
|
||||
|
||||
<span class="hidden md:inline">
|
||||
{description}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<!-- CTA -->
|
||||
<a
|
||||
href="{href}"
|
||||
class="group hidden shrink-0 items-center gap-1.5 text-xs font-bold text-indigo-700 transition hover:text-indigo-900 dark:text-indigo-300 dark:hover:text-indigo-200 sm:inline-flex"
|
||||
>
|
||||
{actionLabel}
|
||||
|
||||
<span class="icon-[lucide--arrow-right] h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" aria-hidden="true" ></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile CTA -->
|
||||
<a
|
||||
href="{href}"
|
||||
aria-label="{actionLabel}"
|
||||
class="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-indigo-100 text-indigo-700 transition hover:bg-indigo-200 dark:bg-indigo-500/15 dark:text-indigo-300 dark:hover:bg-indigo-500/25 sm:hidden"
|
||||
>
|
||||
<span class="icon-[lucide--arrow-up-right] h-4 w-4" aria-hidden="true" ></span>
|
||||
</a>
|
||||
|
||||
<!-- Close -->
|
||||
<button
|
||||
type="button"
|
||||
@click="visible = false"
|
||||
aria-label="Dismiss announcement"
|
||||
class="grid h-8 w-8 shrink-0 place-items-center rounded-lg text-slate-500 transition hover:bg-indigo-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-400 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
<span class="icon-[lucide--x] h-4 w-4" aria-hidden="true" ></span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
component BackToTop {
|
||||
props {
|
||||
label = "Back to top"
|
||||
assistiveLabel = "Return to the top of the page"
|
||||
threshold = 500
|
||||
}
|
||||
|
||||
state visible = false
|
||||
|
||||
functions {
|
||||
function updateBackToTopVisibility() {
|
||||
visible = window.scrollY > threshold
|
||||
}
|
||||
|
||||
function scrollToTop() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: "smooth"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
mount {
|
||||
updateBackToTopVisibility()
|
||||
|
||||
window.addEventListener(
|
||||
"scroll",
|
||||
updateBackToTopVisibility,
|
||||
{ passive: true }
|
||||
)
|
||||
}
|
||||
|
||||
unmount {
|
||||
window.removeEventListener(
|
||||
"scroll",
|
||||
updateBackToTopVisibility
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
watch visible {
|
||||
console.debug(
|
||||
"Back-to-top visibility:",
|
||||
value,
|
||||
previous
|
||||
)
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
class="group fixed bottom-6 right-5 z-[70] flex items-center gap-2 transition-all duration-300 sm:right-6 lg:bottom-8 lg:right-8"
|
||||
class:pointer-events-none="!visible"
|
||||
class:translate-y-5="!visible"
|
||||
class:scale-90="!visible"
|
||||
class:opacity-0="!visible"
|
||||
class:pointer-events-auto="visible"
|
||||
class:translate-y-0="visible"
|
||||
class:scale-100="visible"
|
||||
class:opacity-100="visible"
|
||||
>
|
||||
<!-- Hover label -->
|
||||
<span
|
||||
class="pointer-events-none hidden translate-x-2 whitespace-nowrap rounded-xl border border-slate-200 bg-white/95 px-3 py-2 text-xs font-semibold text-slate-700 opacity-0 shadow-lg shadow-slate-950/10 backdrop-blur-xl transition-all duration-200 group-hover:translate-x-0 group-hover:opacity-100 dark:border-white/10 dark:bg-slate-900/95 dark:text-slate-200 dark:shadow-black/30 sm:block"
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
|
||||
<!-- Gradient border wrapper -->
|
||||
<div
|
||||
class="relative rounded-2xl bg-linear-to-br from-indigo-500 via-violet-500 to-cyan-500 p-px shadow-xl shadow-indigo-600/20 transition duration-300 group-hover:-translate-y-1 group-hover:shadow-2xl group-hover:shadow-indigo-600/30"
|
||||
>
|
||||
<!-- Glow -->
|
||||
<div aria-hidden="true" class="absolute inset-1 rounded-2xl bg-indigo-500/20 opacity-0 blur-xl transition-opacity duration-300 group-hover:opacity-100" ></div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="scrollToTop()"
|
||||
aria-label="{label}"
|
||||
title="{label}"
|
||||
class="relative grid h-12 w-12 place-items-center rounded-[15px] bg-white/95 text-indigo-700 backdrop-blur-xl transition duration-300 hover:bg-indigo-600 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 active:scale-95 dark:bg-slate-900/95 dark:text-indigo-300 dark:hover:bg-indigo-500 dark:hover:text-white dark:focus-visible:ring-offset-slate-950 sm:h-13 sm:w-13"
|
||||
>
|
||||
<!-- Arrow -->
|
||||
<span class="icon-[lucide--arrow-up] h-5 w-5 transition-transform duration-300 group-hover:-translate-y-0.5" aria-hidden="true" ></span>
|
||||
|
||||
<span class="sr-only">
|
||||
{assistiveLabel}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
component FAQAccordion {
|
||||
props {
|
||||
eyebrow = ""
|
||||
title = "Frequently asked questions"
|
||||
description = ""
|
||||
|
||||
items = []
|
||||
|
||||
centered = true
|
||||
compact = false
|
||||
|
||||
allowMultiple = false
|
||||
defaultOpenIndex = 0
|
||||
|
||||
showContact = false
|
||||
contactText = "Still have questions?"
|
||||
contactLabel = "Contact support"
|
||||
contactHref = "/support"
|
||||
}
|
||||
|
||||
state openIndexes = defaultOpenIndex >= 0 ? [defaultOpenIndex] : []
|
||||
|
||||
functions {
|
||||
function isOpen(index) {
|
||||
return openIndexes.includes(index)
|
||||
}
|
||||
|
||||
function toggleItem(index) {
|
||||
if (isOpen(index)) {
|
||||
openIndexes = openIndexes.filter(
|
||||
(itemIndex) => itemIndex !== index
|
||||
)
|
||||
} else if (allowMultiple) {
|
||||
openIndexes = [...openIndexes, index]
|
||||
} else {
|
||||
openIndexes = [index]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<section
|
||||
aria-labelledby="faq-accordion-title"
|
||||
class="w-full px-5 sm:px-8 lg:px-10 xl:px-12"
|
||||
class:py-10="compact"
|
||||
class:sm:py-12="compact"
|
||||
class:py-14="!compact"
|
||||
class:sm:py-16="!compact"
|
||||
class:lg:py-20="!compact"
|
||||
class:hidden="items.length === 0"
|
||||
>
|
||||
<div
|
||||
class="mx-auto w-full"
|
||||
class:max-w-5xl="centered"
|
||||
class:max-w-7xl="!centered"
|
||||
>
|
||||
<div
|
||||
class="gap-10"
|
||||
class:flex="centered"
|
||||
class:flex-col="centered"
|
||||
class:grid="!centered"
|
||||
class:lg:grid-cols-[minmax(260px,0.7fr)_minmax(0,1.3fr)]="!centered"
|
||||
class:lg:items-start="!centered"
|
||||
class:lg:gap-14="!centered"
|
||||
>
|
||||
<!-- Heading -->
|
||||
<div
|
||||
class="min-w-0"
|
||||
class:mx-auto="centered"
|
||||
class:max-w-3xl="centered"
|
||||
class:text-center="centered"
|
||||
class:max-w-xl="!centered"
|
||||
class:lg:sticky="!centered"
|
||||
class:lg:top-24="!centered"
|
||||
>
|
||||
<!-- Eyebrow -->
|
||||
<p
|
||||
class="text-xs font-bold uppercase tracking-[0.18em] text-indigo-600 dark:text-indigo-400"
|
||||
class:hidden="eyebrow === ''"
|
||||
>
|
||||
{eyebrow}
|
||||
</p>
|
||||
|
||||
<!-- Title -->
|
||||
<h2
|
||||
id="faq-accordion-title"
|
||||
class="font-bold leading-tight tracking-tight text-slate-950 dark:text-white"
|
||||
class:mt-3="eyebrow !== ''"
|
||||
class:text-2xl="compact"
|
||||
class:sm:text-3xl="compact"
|
||||
class:text-3xl="!compact"
|
||||
class:sm:text-4xl="!compact"
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<!-- Description -->
|
||||
<p
|
||||
class="mt-4 text-sm leading-7 text-slate-600 dark:text-slate-400"
|
||||
class:hidden="description === ''"
|
||||
class:sm:text-base="!compact"
|
||||
class:mx-auto="centered"
|
||||
class:max-w-2xl="centered"
|
||||
class:max-w-xl="!centered"
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
|
||||
<!-- Contact card -->
|
||||
<div
|
||||
class="mt-7 rounded-2xl border border-indigo-200/80 bg-linear-to-br from-indigo-50 via-white to-violet-50 p-5 shadow-sm dark:border-indigo-500/20 dark:from-indigo-500/10 dark:via-white/[0.03] dark:to-violet-500/10"
|
||||
class:hidden="!showContact"
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-4"
|
||||
class:justify-center="centered"
|
||||
>
|
||||
<span
|
||||
class="grid h-11 w-11 shrink-0 place-items-center rounded-xl bg-white text-indigo-600 shadow-sm ring-1 ring-inset ring-indigo-200 dark:bg-white/5 dark:text-indigo-300 dark:ring-indigo-500/20"
|
||||
>
|
||||
<span class="icon-[lucide--messages-square] h-5 w-5" aria-hidden="true" ></span>
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="min-w-0"
|
||||
class:text-left="centered"
|
||||
>
|
||||
<p class="text-sm font-semibold text-slate-950 dark:text-white">
|
||||
{contactText}
|
||||
</p>
|
||||
|
||||
{#if contactLabel !== '' && contactHref !== ''}
|
||||
<a
|
||||
href="{contactHref}"
|
||||
class="mt-1 inline-flex items-center gap-1.5 text-sm font-bold text-indigo-600 transition-colors duration-200 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300"
|
||||
>
|
||||
{contactLabel}
|
||||
|
||||
<span class="icon-[lucide--arrow-right] h-4 w-4" aria-hidden="true" ></span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Accordion -->
|
||||
<div class="min-w-0">
|
||||
<div class="space-y-3">
|
||||
{#each items as item, index}
|
||||
<article
|
||||
class=" overflow-hidden rounded-2xl border transition-[border-color,background-color,box-shadow,transform] duration-300 ease-out {isOpen(index) ? 'border-indigo-200 bg-indigo-50/60 shadow-md shadow-indigo-950/5 dark:border-indigo-500/20 dark:bg-indigo-500/10' : 'border-slate-200 bg-white shadow-sm hover:-translate-y-0.5 hover:border-indigo-200 hover:bg-slate-50 dark:border-white/10 dark:bg-white/[0.03] dark:hover:border-indigo-500/20 dark:hover:bg-white/5' } "
|
||||
>
|
||||
<h3>
|
||||
<button
|
||||
type="button"
|
||||
@click="toggleItem(index)"
|
||||
aria-expanded="{isOpen(index)}"
|
||||
aria-controls="faq-answer-{index}"
|
||||
class="flex w-full items-center justify-between gap-5 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500"
|
||||
class:px-4="compact"
|
||||
class:py-4="compact"
|
||||
class:px-5="!compact"
|
||||
class:py-5="!compact"
|
||||
class:sm:px-6="!compact"
|
||||
>
|
||||
<span class="flex min-w-0 items-start gap-3">
|
||||
<!-- Number -->
|
||||
<span
|
||||
class=" mt-0.5 grid h-7 w-7 shrink-0 place-items-center rounded-lg text-xs font-bold transition-[background-color,color,transform] duration-300 ease-out {isOpen(index) ? 'bg-indigo-600 text-white' : 'bg-slate-100 text-slate-500 dark:bg-white/5 dark:text-slate-400' } "
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
|
||||
<!-- Question -->
|
||||
<span
|
||||
class="font-semibold leading-6 text-slate-950 dark:text-white"
|
||||
class:text-sm="compact"
|
||||
class:text-base="!compact"
|
||||
>
|
||||
{item.question}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<!-- Toggle icon -->
|
||||
<span
|
||||
class=" grid h-8 w-8 shrink-0 place-items-center rounded-lg transition-[background-color,color,transform] duration-300 ease-out {isOpen(index) ? 'bg-indigo-100 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300' : 'bg-slate-100 text-slate-500 dark:bg-white/5 dark:text-slate-400' } "
|
||||
>
|
||||
<span class=" icon-[lucide--chevron-down] h-4 w-4 transition-transform duration-300 ease-out {isOpen(index) ? 'rotate-180' : 'rotate-0'} " aria-hidden="true" ></span>
|
||||
</span>
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
<div
|
||||
id="faq-answer-{index}"
|
||||
role="region"
|
||||
aria-hidden="{!isOpen(index)}"
|
||||
class=" grid overflow-hidden transition-[grid-template-rows,opacity] duration-300 ease-out {isOpen(index) ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0' } "
|
||||
>
|
||||
<div class="min-h-0 overflow-hidden">
|
||||
<div class="border-t border-indigo-200/60 dark:border-indigo-500/15">
|
||||
<div
|
||||
class=" text-sm leading-7 text-slate-600 transition-[transform,opacity] duration-300 ease-out dark:text-slate-400 {compact ? 'px-4 pb-4 pt-3' : 'px-5 pb-5 pt-4 sm:px-6 sm:pb-6 sm:pt-5' } {isOpen(index) ? 'translate-y-0 opacity-100' : '-translate-y-2 opacity-0' } "
|
||||
>
|
||||
<p class="m-0">
|
||||
{item.answer}
|
||||
</p>
|
||||
|
||||
{#if item.linkLabel !== '' && item.href !== ''}
|
||||
<a
|
||||
href="{item.href}"
|
||||
class=" group inline-flex items-center gap-2 text-sm font-bold text-indigo-600 transition-colors duration-200 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300 "
|
||||
>
|
||||
{item.linkLabel}
|
||||
|
||||
<span class=" icon-[lucide--arrow-up-right] h-4 w-4 transition-transform duration-200 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 " aria-hidden="true" ></span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tailwind safelist -->
|
||||
<span class=" hidden grid-rows-[0fr] grid-rows-[1fr] opacity-0 opacity-100 -translate-y-2 translate-y-0 rotate-0 rotate-180 " ></span>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"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.51"
|
||||
"@wrnexus/core": "^0.2.52"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/uploader — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.51"
|
||||
"@wrnexus/core": "^0.2.52"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/validation — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -33,7 +33,7 @@ const {
|
||||
} = require("../src/diagnostics");
|
||||
Module._load = originalLoad;
|
||||
|
||||
function mockDocument(source) {
|
||||
function mockDocument() {
|
||||
return {
|
||||
positionAt(offset) {
|
||||
return offset;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -533,6 +533,14 @@ const MIGRATIONS: Migration[] = [
|
||||
// No generated application files require automatic migration.
|
||||
},
|
||||
},
|
||||
{
|
||||
version: "0.2.52",
|
||||
id: "shared-marketing-components",
|
||||
description: "Adds reusable FAQ, announcement, and back-to-top UI components.",
|
||||
apply() {
|
||||
// No generated application files require automatic migration.
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/** Release tooling uses this to require an explicit migration entry per version. */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -65,7 +65,7 @@ body from either a named prop or the default slot.
|
||||
### Additional controls & data display
|
||||
|
||||
Also shipped: `select`, `radio`, `switch`, `progress`, `tag`, `skeleton`,
|
||||
`tooltip`, and `table`.
|
||||
`tooltip`, `table`, `FAQAccordion`, `AnnouncementBar`, and `BackToTop`.
|
||||
|
||||
The authoritative, always-current list is `uiComponentNames()` (below), which reads
|
||||
the component directory at runtime.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
component AnnouncementBar {
|
||||
props {
|
||||
badge = "New"
|
||||
message = "WrNexus Organizations is now available."
|
||||
description = "Build secure multi-tenant applications with teams, roles, domains, and enterprise SSO."
|
||||
href = "/organizations"
|
||||
actionLabel = "Explore organizations"
|
||||
ariaLabel = "Announcement"
|
||||
}
|
||||
|
||||
state visible = true
|
||||
|
||||
view {
|
||||
<aside
|
||||
role="region"
|
||||
aria-label="{ariaLabel}"
|
||||
class="relative z-[70] overflow-hidden border-b border-indigo-200 bg-indigo-50 text-slate-900 dark:border-indigo-500/20 dark:bg-indigo-500/10 dark:text-white"
|
||||
class:hidden="!visible"
|
||||
>
|
||||
<!-- Decorative background -->
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute inset-0 overflow-hidden"
|
||||
>
|
||||
<div class="absolute -left-20 -top-24 h-40 w-40 rounded-full bg-indigo-300/30 blur-3xl dark:bg-indigo-500/10"></div>
|
||||
|
||||
<div class="absolute -right-20 -bottom-24 h-40 w-40 rounded-full bg-violet-300/30 blur-3xl dark:bg-violet-500/10"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative flex min-h-11 w-full items-center justify-between gap-4 px-4 py-2.5 sm:px-6 lg:px-10 xl:px-12"
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 items-center justify-center gap-3">
|
||||
<!-- Badge -->
|
||||
<span
|
||||
class="hidden shrink-0 items-center gap-1.5 rounded-full bg-indigo-600 px-2.5 py-1 text-[10px] font-bold uppercase tracking-[0.12em] text-white shadow-sm sm:inline-flex"
|
||||
>
|
||||
<span class="icon-[lucide--sparkles] h-3 w-3" aria-hidden="true" ></span>
|
||||
|
||||
{badge}
|
||||
</span>
|
||||
|
||||
<!-- Message -->
|
||||
<p
|
||||
class="min-w-0 text-center text-xs font-medium leading-5 text-slate-700 dark:text-slate-300 sm:text-sm"
|
||||
>
|
||||
<span class="font-semibold text-slate-950 dark:text-white">
|
||||
{message}
|
||||
</span>
|
||||
|
||||
<span class="hidden md:inline">
|
||||
{description}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<!-- CTA -->
|
||||
<a
|
||||
href="{href}"
|
||||
class="group hidden shrink-0 items-center gap-1.5 text-xs font-bold text-indigo-700 transition hover:text-indigo-900 dark:text-indigo-300 dark:hover:text-indigo-200 sm:inline-flex"
|
||||
>
|
||||
{actionLabel}
|
||||
|
||||
<span class="icon-[lucide--arrow-right] h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" aria-hidden="true" ></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile CTA -->
|
||||
<a
|
||||
href="{href}"
|
||||
aria-label="{actionLabel}"
|
||||
class="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-indigo-100 text-indigo-700 transition hover:bg-indigo-200 dark:bg-indigo-500/15 dark:text-indigo-300 dark:hover:bg-indigo-500/25 sm:hidden"
|
||||
>
|
||||
<span class="icon-[lucide--arrow-up-right] h-4 w-4" aria-hidden="true" ></span>
|
||||
</a>
|
||||
|
||||
<!-- Close -->
|
||||
<button
|
||||
type="button"
|
||||
@click="visible = false"
|
||||
aria-label="Dismiss announcement"
|
||||
class="grid h-8 w-8 shrink-0 place-items-center rounded-lg text-slate-500 transition hover:bg-indigo-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-400 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
<span class="icon-[lucide--x] h-4 w-4" aria-hidden="true" ></span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
component BackToTop {
|
||||
props {
|
||||
label = "Back to top"
|
||||
assistiveLabel = "Return to the top of the page"
|
||||
threshold = 500
|
||||
}
|
||||
|
||||
state visible = false
|
||||
|
||||
functions {
|
||||
function updateBackToTopVisibility() {
|
||||
visible = window.scrollY > threshold
|
||||
}
|
||||
|
||||
function scrollToTop() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: "smooth"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
mount {
|
||||
updateBackToTopVisibility()
|
||||
|
||||
window.addEventListener(
|
||||
"scroll",
|
||||
updateBackToTopVisibility,
|
||||
{ passive: true }
|
||||
)
|
||||
}
|
||||
|
||||
unmount {
|
||||
window.removeEventListener(
|
||||
"scroll",
|
||||
updateBackToTopVisibility
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
watch visible {
|
||||
console.debug(
|
||||
"Back-to-top visibility:",
|
||||
value,
|
||||
previous
|
||||
)
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
class="group fixed bottom-6 right-5 z-[70] flex items-center gap-2 transition-all duration-300 sm:right-6 lg:bottom-8 lg:right-8"
|
||||
class:pointer-events-none="!visible"
|
||||
class:translate-y-5="!visible"
|
||||
class:scale-90="!visible"
|
||||
class:opacity-0="!visible"
|
||||
class:pointer-events-auto="visible"
|
||||
class:translate-y-0="visible"
|
||||
class:scale-100="visible"
|
||||
class:opacity-100="visible"
|
||||
>
|
||||
<!-- Hover label -->
|
||||
<span
|
||||
class="pointer-events-none hidden translate-x-2 whitespace-nowrap rounded-xl border border-slate-200 bg-white/95 px-3 py-2 text-xs font-semibold text-slate-700 opacity-0 shadow-lg shadow-slate-950/10 backdrop-blur-xl transition-all duration-200 group-hover:translate-x-0 group-hover:opacity-100 dark:border-white/10 dark:bg-slate-900/95 dark:text-slate-200 dark:shadow-black/30 sm:block"
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
|
||||
<!-- Gradient border wrapper -->
|
||||
<div
|
||||
class="relative rounded-2xl bg-linear-to-br from-indigo-500 via-violet-500 to-cyan-500 p-px shadow-xl shadow-indigo-600/20 transition duration-300 group-hover:-translate-y-1 group-hover:shadow-2xl group-hover:shadow-indigo-600/30"
|
||||
>
|
||||
<!-- Glow -->
|
||||
<div aria-hidden="true" class="absolute inset-1 rounded-2xl bg-indigo-500/20 opacity-0 blur-xl transition-opacity duration-300 group-hover:opacity-100" ></div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="scrollToTop()"
|
||||
aria-label="{label}"
|
||||
title="{label}"
|
||||
class="relative grid h-12 w-12 place-items-center rounded-[15px] bg-white/95 text-indigo-700 backdrop-blur-xl transition duration-300 hover:bg-indigo-600 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 active:scale-95 dark:bg-slate-900/95 dark:text-indigo-300 dark:hover:bg-indigo-500 dark:hover:text-white dark:focus-visible:ring-offset-slate-950 sm:h-13 sm:w-13"
|
||||
>
|
||||
<!-- Arrow -->
|
||||
<span class="icon-[lucide--arrow-up] h-5 w-5 transition-transform duration-300 group-hover:-translate-y-0.5" aria-hidden="true" ></span>
|
||||
|
||||
<span class="sr-only">
|
||||
{assistiveLabel}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
component FAQAccordion {
|
||||
props {
|
||||
eyebrow = ""
|
||||
title = "Frequently asked questions"
|
||||
description = ""
|
||||
|
||||
items = []
|
||||
|
||||
centered = true
|
||||
compact = false
|
||||
|
||||
allowMultiple = false
|
||||
defaultOpenIndex = 0
|
||||
|
||||
showContact = false
|
||||
contactText = "Still have questions?"
|
||||
contactLabel = "Contact support"
|
||||
contactHref = "/support"
|
||||
}
|
||||
|
||||
state openIndexes = defaultOpenIndex >= 0 ? [defaultOpenIndex] : []
|
||||
|
||||
functions {
|
||||
function isOpen(index) {
|
||||
return openIndexes.includes(index)
|
||||
}
|
||||
|
||||
function toggleItem(index) {
|
||||
if (isOpen(index)) {
|
||||
openIndexes = openIndexes.filter(
|
||||
(itemIndex) => itemIndex !== index
|
||||
)
|
||||
} else if (allowMultiple) {
|
||||
openIndexes = [...openIndexes, index]
|
||||
} else {
|
||||
openIndexes = [index]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<section
|
||||
aria-labelledby="faq-accordion-title"
|
||||
class="w-full px-5 sm:px-8 lg:px-10 xl:px-12"
|
||||
class:py-10="compact"
|
||||
class:sm:py-12="compact"
|
||||
class:py-14="!compact"
|
||||
class:sm:py-16="!compact"
|
||||
class:lg:py-20="!compact"
|
||||
class:hidden="items.length === 0"
|
||||
>
|
||||
<div
|
||||
class="mx-auto w-full"
|
||||
class:max-w-5xl="centered"
|
||||
class:max-w-7xl="!centered"
|
||||
>
|
||||
<div
|
||||
class="gap-10"
|
||||
class:flex="centered"
|
||||
class:flex-col="centered"
|
||||
class:grid="!centered"
|
||||
class:lg:grid-cols-[minmax(260px,0.7fr)_minmax(0,1.3fr)]="!centered"
|
||||
class:lg:items-start="!centered"
|
||||
class:lg:gap-14="!centered"
|
||||
>
|
||||
<!-- Heading -->
|
||||
<div
|
||||
class="min-w-0"
|
||||
class:mx-auto="centered"
|
||||
class:max-w-3xl="centered"
|
||||
class:text-center="centered"
|
||||
class:max-w-xl="!centered"
|
||||
class:lg:sticky="!centered"
|
||||
class:lg:top-24="!centered"
|
||||
>
|
||||
<!-- Eyebrow -->
|
||||
<p
|
||||
class="text-xs font-bold uppercase tracking-[0.18em] text-indigo-600 dark:text-indigo-400"
|
||||
class:hidden="eyebrow === ''"
|
||||
>
|
||||
{eyebrow}
|
||||
</p>
|
||||
|
||||
<!-- Title -->
|
||||
<h2
|
||||
id="faq-accordion-title"
|
||||
class="font-bold leading-tight tracking-tight text-slate-950 dark:text-white"
|
||||
class:mt-3="eyebrow !== ''"
|
||||
class:text-2xl="compact"
|
||||
class:sm:text-3xl="compact"
|
||||
class:text-3xl="!compact"
|
||||
class:sm:text-4xl="!compact"
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<!-- Description -->
|
||||
<p
|
||||
class="mt-4 text-sm leading-7 text-slate-600 dark:text-slate-400"
|
||||
class:hidden="description === ''"
|
||||
class:sm:text-base="!compact"
|
||||
class:mx-auto="centered"
|
||||
class:max-w-2xl="centered"
|
||||
class:max-w-xl="!centered"
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
|
||||
<!-- Contact card -->
|
||||
<div
|
||||
class="mt-7 rounded-2xl border border-indigo-200/80 bg-linear-to-br from-indigo-50 via-white to-violet-50 p-5 shadow-sm dark:border-indigo-500/20 dark:from-indigo-500/10 dark:via-white/[0.03] dark:to-violet-500/10"
|
||||
class:hidden="!showContact"
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-4"
|
||||
class:justify-center="centered"
|
||||
>
|
||||
<span
|
||||
class="grid h-11 w-11 shrink-0 place-items-center rounded-xl bg-white text-indigo-600 shadow-sm ring-1 ring-inset ring-indigo-200 dark:bg-white/5 dark:text-indigo-300 dark:ring-indigo-500/20"
|
||||
>
|
||||
<span class="icon-[lucide--messages-square] h-5 w-5" aria-hidden="true" ></span>
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="min-w-0"
|
||||
class:text-left="centered"
|
||||
>
|
||||
<p class="text-sm font-semibold text-slate-950 dark:text-white">
|
||||
{contactText}
|
||||
</p>
|
||||
|
||||
{#if contactLabel !== '' && contactHref !== ''}
|
||||
<a
|
||||
href="{contactHref}"
|
||||
class="mt-1 inline-flex items-center gap-1.5 text-sm font-bold text-indigo-600 transition-colors duration-200 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300"
|
||||
>
|
||||
{contactLabel}
|
||||
|
||||
<span class="icon-[lucide--arrow-right] h-4 w-4" aria-hidden="true" ></span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Accordion -->
|
||||
<div class="min-w-0">
|
||||
<div class="space-y-3">
|
||||
{#each items as item, index}
|
||||
<article
|
||||
class=" overflow-hidden rounded-2xl border transition-[border-color,background-color,box-shadow,transform] duration-300 ease-out {isOpen(index) ? 'border-indigo-200 bg-indigo-50/60 shadow-md shadow-indigo-950/5 dark:border-indigo-500/20 dark:bg-indigo-500/10' : 'border-slate-200 bg-white shadow-sm hover:-translate-y-0.5 hover:border-indigo-200 hover:bg-slate-50 dark:border-white/10 dark:bg-white/[0.03] dark:hover:border-indigo-500/20 dark:hover:bg-white/5' } "
|
||||
>
|
||||
<h3>
|
||||
<button
|
||||
type="button"
|
||||
@click="toggleItem(index)"
|
||||
aria-expanded="{isOpen(index)}"
|
||||
aria-controls="faq-answer-{index}"
|
||||
class="flex w-full items-center justify-between gap-5 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500"
|
||||
class:px-4="compact"
|
||||
class:py-4="compact"
|
||||
class:px-5="!compact"
|
||||
class:py-5="!compact"
|
||||
class:sm:px-6="!compact"
|
||||
>
|
||||
<span class="flex min-w-0 items-start gap-3">
|
||||
<!-- Number -->
|
||||
<span
|
||||
class=" mt-0.5 grid h-7 w-7 shrink-0 place-items-center rounded-lg text-xs font-bold transition-[background-color,color,transform] duration-300 ease-out {isOpen(index) ? 'bg-indigo-600 text-white' : 'bg-slate-100 text-slate-500 dark:bg-white/5 dark:text-slate-400' } "
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
|
||||
<!-- Question -->
|
||||
<span
|
||||
class="font-semibold leading-6 text-slate-950 dark:text-white"
|
||||
class:text-sm="compact"
|
||||
class:text-base="!compact"
|
||||
>
|
||||
{item.question}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<!-- Toggle icon -->
|
||||
<span
|
||||
class=" grid h-8 w-8 shrink-0 place-items-center rounded-lg transition-[background-color,color,transform] duration-300 ease-out {isOpen(index) ? 'bg-indigo-100 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300' : 'bg-slate-100 text-slate-500 dark:bg-white/5 dark:text-slate-400' } "
|
||||
>
|
||||
<span class=" icon-[lucide--chevron-down] h-4 w-4 transition-transform duration-300 ease-out {isOpen(index) ? 'rotate-180' : 'rotate-0'} " aria-hidden="true" ></span>
|
||||
</span>
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
<div
|
||||
id="faq-answer-{index}"
|
||||
role="region"
|
||||
aria-hidden="{!isOpen(index)}"
|
||||
class=" grid overflow-hidden transition-[grid-template-rows,opacity] duration-300 ease-out {isOpen(index) ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0' } "
|
||||
>
|
||||
<div class="min-h-0 overflow-hidden">
|
||||
<div class="border-t border-indigo-200/60 dark:border-indigo-500/15">
|
||||
<div
|
||||
class=" text-sm leading-7 text-slate-600 transition-[transform,opacity] duration-300 ease-out dark:text-slate-400 {compact ? 'px-4 pb-4 pt-3' : 'px-5 pb-5 pt-4 sm:px-6 sm:pb-6 sm:pt-5' } {isOpen(index) ? 'translate-y-0 opacity-100' : '-translate-y-2 opacity-0' } "
|
||||
>
|
||||
<p class="m-0">
|
||||
{item.answer}
|
||||
</p>
|
||||
|
||||
{#if item.linkLabel !== '' && item.href !== ''}
|
||||
<a
|
||||
href="{item.href}"
|
||||
class=" group inline-flex items-center gap-2 text-sm font-bold text-indigo-600 transition-colors duration-200 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300 "
|
||||
>
|
||||
{item.linkLabel}
|
||||
|
||||
<span class=" icon-[lucide--arrow-up-right] h-4 w-4 transition-transform duration-200 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 " aria-hidden="true" ></span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tailwind safelist -->
|
||||
<span class=" hidden grid-rows-[0fr] grid-rows-[1fr] opacity-0 opacity-100 -translate-y-2 translate-y-0 rotate-0 rotate-180 " ></span>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -6,6 +6,9 @@ import { uiComponentNames, uiComponentsDir, uiCss } from "../src/index.ts";
|
||||
|
||||
test("bundled UI assets are discoverable and readable", () => {
|
||||
expect(uiComponentNames()).toContain("Button");
|
||||
expect(uiComponentNames()).toContain("FAQAccordion");
|
||||
expect(uiComponentNames()).toContain("AnnouncementBar");
|
||||
expect(uiComponentNames()).toContain("BackToTop");
|
||||
expect(readFileSync(join(uiComponentsDir(), "Button.wrn"), "utf8")).toContain("component Button");
|
||||
expect(uiCss()).toContain("--wire-");
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.52",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const version = "0.2.51";
|
||||
const version = "0.2.52";
|
||||
|
||||
const dependencyGroups = [
|
||||
"dependencies",
|
||||
|
||||
Reference in New Issue
Block a user