component Modal { outputs { open(payload: { sourceEvent: Event }) close(payload: { reason: string; sourceEvent: Event }) cancel(payload: { sourceEvent: Event }) confirm(payload: { sourceEvent: Event }) } props { open: boolean = false defaultOpen: boolean = false title: string = "Modal" description: string = "" icon: string = "" label: string = "Modal dialog" size: string = "md" placement: string = "center" color: string = "primary" variant: string = "default" showClose: boolean = true closeLabel: string = "Close modal" closeOnBackdrop: boolean = true closeOnEscape: boolean = true closeOnCancel: boolean = true closeOnConfirm: boolean = false showFooter: boolean = true cancelLabel: string = "Cancel" cancelIcon: string = "" confirmLabel: string = "Confirm" confirmIcon: string = "" confirmDisabled: boolean = false confirmLoading: boolean = false destructive: boolean = false triggerLabel: string = "" triggerIcon: string = "" scrollable: boolean = true class: string = "" } state visible = defaultOpen functions { shared function isOpen() { return open || visible } client function showModal(sourceEvent) { visible = true output.open({ sourceEvent: sourceEvent }) } client function hideModal(reason, sourceEvent) { visible = false output.close({ reason: reason, sourceEvent: sourceEvent }) } client function cancelModal(sourceEvent) { output.cancel({ sourceEvent: sourceEvent }) if (closeOnCancel) { hideModal("cancel", sourceEvent) } } client function confirmModal(sourceEvent) { if (confirmDisabled || confirmLoading) { return } output.confirm({ sourceEvent: sourceEvent }) if (closeOnConfirm) { hideModal("confirm", sourceEvent) } } client function handleKeydown(sourceEvent) { if (closeOnEscape && sourceEvent.key === "Escape") { sourceEvent.preventDefault() cancelModal(sourceEvent) } } } view {
{#if triggerLabel} {/if}
} style { .wire-modal { --modal-accent: var(--wire-color-primary); --modal-soft: var(--wire-color-primary-soft); --modal-contrast: var(--wire-color-primary-contrast); position: relative; display: inline-flex; } .wire-modal[data-color="secondary"] { --modal-accent: var(--wire-color-secondary); --modal-soft: var(--wire-color-secondary-soft); --modal-contrast: var(--wire-color-secondary-contrast); } .wire-modal[data-color="info"] { --modal-accent: var(--wire-color-info); --modal-soft: var(--wire-color-info-soft); --modal-contrast: var(--wire-color-info-contrast); } .wire-modal[data-color="success"] { --modal-accent: var(--wire-color-success); --modal-soft: var(--wire-color-success-soft); --modal-contrast: var(--wire-color-success-contrast); } .wire-modal[data-color="warning"] { --modal-accent: var(--wire-color-warning); --modal-soft: var(--wire-color-warning-soft); --modal-contrast: var(--wire-color-warning-text); } .wire-modal[data-color="danger"], .wire-modal[data-destructive="true"] { --modal-accent: var(--wire-color-danger); --modal-soft: var(--wire-color-danger-soft); --modal-contrast: var(--wire-color-on-danger); } .wire-modal__trigger, .wire-modal__trigger-slot { display: inline-flex; align-items: center; gap: 0.55rem; } .wire-modal__trigger { appearance: none; min-height: 2.55rem; padding: 0.65rem 1rem; color: var(--modal-contrast); background: var(--modal-accent); border: 0; border-radius: 0.8rem; font: inherit; font-size: 0.85rem; font-weight: 650; cursor: pointer; } .wire-modal__layer { position: fixed; inset: 0; z-index: 1250; display: flex; align-items: center; justify-content: center; padding: 1rem; } .wire-modal[data-placement="top"] .wire-modal__layer { align-items: flex-start; padding-top: clamp(1rem, 8vh, 5rem); } .wire-modal__backdrop { position: absolute; inset: 0; z-index: 0; appearance: none; padding: 0; background: color-mix(in srgb, black 62%, transparent); border: 0; backdrop-filter: blur(9px); } .wire-modal__panel { position: relative; z-index: 1; display: flex; flex-direction: column; width: min(32rem, calc(100vw - 2rem)); max-height: min(88vh, 52rem); color: var(--wire-color-text); background: radial-gradient( circle at 100% 0%, color-mix(in srgb, var(--modal-accent) 9%, transparent), transparent 34% ), var(--wire-color-surface-raised); border: 1px solid color-mix(in srgb, var(--modal-accent) 18%, var(--wire-color-border)); border-radius: 1.35rem; box-shadow: 0 1px 0 color-mix(in srgb, white 5%, transparent) inset, 0 40px 110px color-mix(in srgb, black 38%, transparent); overflow: hidden; } .wire-modal[data-size="sm"] .wire-modal__panel { width: min(25rem, calc(100vw - 2rem)); } .wire-modal[data-size="lg"] .wire-modal__panel { width: min(44rem, calc(100vw - 2rem)); } .wire-modal[data-size="xl"] .wire-modal__panel { width: min(64rem, calc(100vw - 2rem)); } .wire-modal[data-size="full"] .wire-modal__panel { width: calc(100vw - 2rem); height: calc(100vh - 2rem); max-height: none; } .wire-modal[data-variant="soft"] .wire-modal__panel { background: linear-gradient(145deg, var(--modal-soft), transparent 68%), var(--wire-color-surface-raised); } .wire-modal[data-variant="outline"] .wire-modal__panel { background: var(--wire-color-surface-raised); box-shadow: 0 24px 72px color-mix(in srgb, black 24%, transparent); } .wire-modal[data-variant="solid"] .wire-modal__panel { color: var(--modal-contrast); background: var(--modal-accent); border-color: color-mix(in srgb, white 20%, transparent); } .wire-modal__header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: 1.4rem 1.4rem 1.15rem; border-bottom: 1px solid var(--wire-color-border); } .wire-modal__heading { display: flex; align-items: flex-start; gap: 0.9rem; min-width: 0; } .wire-modal__icon { flex: 0 0 auto; width: 1.3rem; height: 1.3rem; margin-top: 0.15rem; color: var(--modal-accent); } .wire-modal[data-variant="solid"] .wire-modal__icon { color: currentColor; } .wire-modal__heading-copy { display: grid; gap: 0.3rem; min-width: 0; } .wire-modal__heading-copy h2, .wire-modal__heading-copy p { margin: 0; } .wire-modal__heading-copy h2 { font-size: 1.08rem; font-weight: 650; line-height: 1.3; } .wire-modal__heading-copy p { color: var(--wire-color-text-muted); font-size: 0.8rem; line-height: 1.55; } .wire-modal[data-variant="solid"] .wire-modal__heading-copy p { color: color-mix(in srgb, currentColor 76%, transparent); } .wire-modal__close { appearance: none; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: 2.35rem; height: 2.35rem; color: var(--wire-color-text-muted); background: var(--wire-color-surface-soft); border: 1px solid var(--wire-color-border); border-radius: 0.75rem; cursor: pointer; } .wire-modal__close:hover, .wire-modal__close:focus-visible { color: var(--modal-accent); border-color: color-mix(in srgb, var(--modal-accent) 34%, var(--wire-color-border)); outline: none; } .wire-modal__body { flex: 1 1 auto; min-height: 0; padding: 1.4rem; } .wire-modal[data-scrollable="true"] .wire-modal__body { overflow: auto; overscroll-behavior: contain; } .wire-modal__footer { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 1rem 1.4rem 1.4rem; border-top: 1px solid var(--wire-color-border); } .wire-modal__custom-footer:empty { display: none; } .wire-modal__actions { display: flex; align-items: center; justify-content: flex-end; gap: 0.65rem; margin-left: auto; } .wire-modal__button { appearance: none; display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; min-height: 2.55rem; padding: 0.65rem 1rem; border-radius: 0.78rem; font: inherit; font-size: 0.83rem; font-weight: 650; cursor: pointer; transition: transform 150ms ease, opacity 150ms ease, border-color 150ms ease; } .wire-modal__button:active { transform: scale(0.985); } .wire-modal__button--secondary { color: var(--wire-color-text); background: transparent; border: 1px solid var(--wire-color-border); } .wire-modal__button--primary { color: var(--modal-contrast); background: var(--modal-accent); border: 1px solid transparent; } .wire-modal__button:disabled { opacity: 0.55; cursor: not-allowed; } .wire-modal__spinner { width: 0.95rem; height: 0.95rem; border: 2px solid color-mix(in srgb, currentColor 35%, transparent); border-top-color: currentColor; border-radius: 50%; animation: wire-modal-spin 750ms linear infinite; } @keyframes wire-modal-spin { to { transform: rotate(360deg); } } @media (max-width: 639px) { .wire-modal__layer { align-items: flex-end; padding: 0; } .wire-modal__panel, .wire-modal[data-size="sm"] .wire-modal__panel, .wire-modal[data-size="lg"] .wire-modal__panel, .wire-modal[data-size="xl"] .wire-modal__panel { width: 100%; max-height: 92vh; border-radius: 1.25rem 1.25rem 0 0; } .wire-modal__footer { align-items: stretch; flex-direction: column; } .wire-modal__actions { width: 100%; } .wire-modal__button { flex: 1 1 0; } } @media (prefers-reduced-motion: reduce) { .wire-modal__button, .wire-modal__spinner { animation: none; transition: none; } } } }