// Modal demo page. Route: /modal
//
// Five real-world use cases for the shared @wrnexus/ui Modal component — each
// one exercises a different combination of props (color, variant, size,
// scrollable, destructive, showFooter, confirmDisabled) instead of just
// showing the same modal five times.
//
// Notifications go through the shared mounted at the bottom of
// this page. Any client expression can raise one with the runtime global —
// toast("..."), toast.success(...), toast.error(...) — so pages never
// hand-roll a status banner and never reach for a blocking alert().
//
// No import here on purpose: component tags resolve by directory scan, and
// @wrnexus/ui's index.ts intentionally has no runtime exports (registry.ts
// holds the server-only filesystem helpers instead) — an explicit
// `import { Modal } from "@wrnexus/ui"` fails to bundle since there's no
// matching export. The WRN-IMPORT-IMPLICIT warning this triggers is safe to
// ignore for component tags.
page ModalPage {
state noteText = "Remember to ship the release notes"
state deletedNote = ""
functions {
function onSaveConfirmed() {
toast.success("Changes saved", { title: "Draft published" })
}
function onDeleteConfirmed() {
toast.error("Account deleted", { title: "Gone for good", duration: 6000 })
}
// Toast actions and component state.
//
// An action callback runs LONG AFTER the function that created it
// returned, and a client function only flushes its state when its body
// ends -- so assigning noteText straight from the callback would write to
// a dead local and vanish. The callback dispatches an event instead, and
// the declarative @window binding on the actions row below handles it as
// a fresh invocation with live state. Anything that is not component
// state (calling toast(), fetch, navigation) works from the callback
// directly.
function onDeleteNote() {
deletedNote = noteText
noteText = ""
toast("Note deleted", {
title: "Deleted",
actionLabel: "Undo",
onAction: function () {
window.dispatchEvent(new CustomEvent("demo:restore-note"))
},
duration: 8000
})
}
function restoreNote() {
if (!deletedNote) {
return
}
noteText = deletedNote
deletedNote = ""
toast.success("Note restored")
}
// Two actions. The second is styled destructive and keeps the toast open
// (dismiss: false) so the choice stays on screen until it is resolved.
function onConflict() {
toast.warning("This note changed in another tab", {
title: "Conflict",
actions: [
{
label: "Keep mine",
onClick: function () { toast.success("Kept your version") }
},
{
label: "Discard",
tone: "danger",
dismiss: false,
onClick: function () {
window.dispatchEvent(new CustomEvent("demo:discard-note"))
}
}
],
duration: 0
})
}
function discardNote() {
deletedNote = noteText
noteText = ""
toast.error("Your changes were discarded")
}
// Tones carry their own icon and colour, but any toast can override it.
// The class lives in this page source, so the app Tailwind/iconify build
// sees it and emits the rule -- a class from inside @wrnexus/ui would not
// be scanned, which is why the packaged defaults are inline SVG.
function onTrialNotice() {
toast.warning("Your trial ends in 3 days", {
title: "Heads up",
icon: "icon-[lucide--hourglass]",
actionLabel: "Upgrade",
duration: 6000
})
}
function onTermsConfirmed() {
toast.success("Thanks, you are all set")
}
// The invite form is a real