component Popover { outputs { toggle(payload: { open: boolean; sourceEvent: Event; reason?: object }) open(payload: { sourceEvent: Event }) close(payload: { reason: string; sourceEvent: Event }) action(payload: { href: string; sourceEvent: Event }) } props { open: boolean = false defaultOpen: boolean = false triggerLabel: string = "Open popover" triggerIcon: string = "" title: string = "" description: string = "" icon: string = "" placement: string = "bottom-start" width: string = "md" size: string = "default" color: string = "primary" variant: string = "raised" showArrow: boolean = true showClose: boolean = false closeLabel: string = "Close popover" closeOnOutside: boolean = true closeOnEscape: boolean = true actionLabel: string = "" actionHref: string = "" actionIcon: string = "" closeOnAction: boolean = true disabled: boolean = false class: string = "" } state visible = defaultOpen functions { shared function isOpen() { return open || visible } client function showPopover(sourceEvent) { if (disabled) { return } visible = true output.open({ sourceEvent: sourceEvent }) output.toggle({ open: true, sourceEvent: sourceEvent }) } client function hidePopover(reason, sourceEvent) { visible = false output.close({ reason: reason, sourceEvent: sourceEvent }) output.toggle({ open: false, reason: reason, sourceEvent: sourceEvent }) } client function togglePopover(sourceEvent) { if (isOpen()) { hidePopover("toggle", sourceEvent) } else { showPopover(sourceEvent) } } client function activateAction(sourceEvent) { output.action({ href: actionHref, sourceEvent: sourceEvent }) if (closeOnAction) { hidePopover("action", sourceEvent) } } client function handleKeydown(sourceEvent) { if (closeOnEscape && sourceEvent.key === "Escape") { sourceEvent.preventDefault() hidePopover("escape", sourceEvent) } } } view {
{#if closeOnOutside} {/if}
} style { .wire-popover { --popover-accent: var(--wire-color-primary); --popover-soft: var(--wire-color-primary-soft); --popover-contrast: var(--wire-color-primary-contrast); position: relative; display: inline-flex; } .wire-popover[data-color="secondary"] { --popover-accent: var(--wire-color-secondary); --popover-soft: var(--wire-color-secondary-soft); --popover-contrast: var(--wire-color-secondary-contrast); } .wire-popover[data-color="info"] { --popover-accent: var(--wire-color-info); --popover-soft: var(--wire-color-info-soft); --popover-contrast: var(--wire-color-info-contrast); } .wire-popover[data-color="success"] { --popover-accent: var(--wire-color-success); --popover-soft: var(--wire-color-success-soft); --popover-contrast: var(--wire-color-success-contrast); } .wire-popover[data-color="warning"] { --popover-accent: var(--wire-color-warning); --popover-soft: var(--wire-color-warning-soft); --popover-contrast: var(--wire-color-warning-text); } .wire-popover[data-color="danger"] { --popover-accent: var(--wire-color-danger); --popover-soft: var(--wire-color-danger-soft); --popover-contrast: var(--wire-color-on-danger); } .wire-popover__trigger { appearance: none; display: inline-flex; align-items: center; justify-content: center; gap: 0.55rem; min-height: 2.55rem; padding: 0.65rem 0.9rem; color: var(--wire-color-text); background: var(--wire-color-surface-raised); border: 1px solid var(--wire-color-border); border-radius: 0.78rem; font: inherit; font-size: 0.84rem; font-weight: 600; cursor: pointer; transition: color 150ms ease, border-color 150ms ease, background-color 150ms ease; } .wire-popover__trigger:hover, .wire-popover__trigger:focus-visible, .wire-popover[data-open="true"] .wire-popover__trigger { color: var(--popover-accent); background: var(--popover-soft); border-color: color-mix(in srgb, var(--popover-accent) 38%, var(--wire-color-border)); outline: none; } .wire-popover__trigger:disabled { opacity: 0.5; cursor: not-allowed; } .wire-popover[data-size="sm"] .wire-popover__trigger { min-height: 2.2rem; padding: 0.5rem 0.72rem; font-size: 0.78rem; } .wire-popover[data-size="lg"] .wire-popover__trigger { min-height: 2.9rem; padding: 0.78rem 1.05rem; font-size: 0.9rem; } .wire-popover__dismiss-layer { position: fixed; inset: 0; z-index: 1120; appearance: none; padding: 0; background: transparent; border: 0; } .wire-popover__panel { position: absolute; z-index: 1121; top: calc(100% + 0.72rem); left: 0; width: 18rem; max-width: calc(100vw - 1.5rem); color: var(--wire-color-text); background: linear-gradient(145deg, color-mix(in srgb, var(--popover-accent) 5%, transparent), transparent 62%), color-mix(in srgb, var(--wire-color-surface-raised) 97%, transparent); border: 1px solid color-mix(in srgb, var(--popover-accent) 18%, var(--wire-color-border)); border-radius: 1rem; box-shadow: 0 1px 0 color-mix(in srgb, white 5%, transparent) inset, 0 24px 64px color-mix(in srgb, black 22%, transparent); backdrop-filter: blur(18px); } .wire-popover[data-width="sm"] .wire-popover__panel { width: 14rem; } .wire-popover[data-width="lg"] .wire-popover__panel { width: 24rem; } .wire-popover[data-width="xl"] .wire-popover__panel { width: 32rem; } .wire-popover[data-width="trigger"] .wire-popover__panel { width: 100%; min-width: 100%; } .wire-popover[data-placement="bottom-end"] .wire-popover__panel { right: 0; left: auto; } .wire-popover[data-placement="top-start"] .wire-popover__panel { top: auto; bottom: calc(100% + 0.72rem); } .wire-popover[data-placement="top-end"] .wire-popover__panel { top: auto; right: 0; bottom: calc(100% + 0.72rem); left: auto; } .wire-popover[data-placement="left"] .wire-popover__panel { top: 50%; right: calc(100% + 0.72rem); left: auto; transform: translateY(-50%); } .wire-popover[data-placement="right"] .wire-popover__panel { top: 50%; left: calc(100% + 0.72rem); transform: translateY(-50%); } .wire-popover[data-variant="soft"] .wire-popover__panel { background: linear-gradient(145deg, var(--popover-soft), transparent 70%), var(--wire-color-surface-raised); box-shadow: 0 18px 48px color-mix(in srgb, black 16%, transparent); } .wire-popover[data-variant="outline"] .wire-popover__panel { background: var(--wire-color-surface-raised); box-shadow: none; } .wire-popover[data-variant="solid"] .wire-popover__panel { color: var(--popover-contrast); background: var(--popover-accent); border-color: color-mix(in srgb, white 20%, transparent); } .wire-popover__arrow { position: absolute; top: -0.35rem; left: 1.25rem; width: 0.7rem; height: 0.7rem; background: inherit; border-top: 1px solid color-mix(in srgb, var(--popover-accent) 18%, var(--wire-color-border)); border-left: 1px solid color-mix(in srgb, var(--popover-accent) 18%, var(--wire-color-border)); transform: rotate(45deg); } .wire-popover[data-placement="bottom-end"] .wire-popover__arrow { right: 1.25rem; left: auto; } .wire-popover[data-placement="top-start"] .wire-popover__arrow, .wire-popover[data-placement="top-end"] .wire-popover__arrow { top: auto; bottom: -0.35rem; transform: rotate(225deg); } .wire-popover[data-placement="left"] .wire-popover__arrow { top: 50%; right: -0.35rem; left: auto; transform: translateY(-50%) rotate(135deg); } .wire-popover[data-placement="right"] .wire-popover__arrow { top: 50%; left: -0.35rem; transform: translateY(-50%) rotate(-45deg); } .wire-popover__header { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.8rem; padding: 1rem 1rem 0.8rem; border-bottom: 1px solid var(--wire-color-border); } .wire-popover__heading { display: flex; align-items: flex-start; gap: 0.7rem; min-width: 0; } .wire-popover__icon { flex: 0 0 auto; width: 1.05rem; height: 1.05rem; margin-top: 0.15rem; color: var(--popover-accent); } .wire-popover[data-variant="solid"] .wire-popover__icon { color: currentColor; } .wire-popover__heading-copy { display: grid; gap: 0.2rem; min-width: 0; } .wire-popover__heading-copy h3, .wire-popover__heading-copy p { margin: 0; } .wire-popover__heading-copy h3 { font-size: 0.9rem; font-weight: 650; } .wire-popover__heading-copy p { color: var(--wire-color-text-muted); font-size: 0.74rem; line-height: 1.5; } .wire-popover[data-variant="solid"] .wire-popover__heading-copy p { color: color-mix(in srgb, currentColor 76%, transparent); } /* * padding is reset explicitly: an app-level `button { padding: ... }` rule * outranks the browser default and crushes the icon inside this * fixed-size button. Same trap as Modal and Drawer. */ .wire-popover__close { padding: 0; appearance: none; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: 2rem; height: 2rem; color: var(--wire-color-text-muted); background: var(--wire-color-surface-soft); border: 1px solid var(--wire-color-border); border-radius: 0.65rem; cursor: pointer; } .wire-popover__body { padding: 1rem; color: var(--wire-color-text-muted); font-size: 0.8rem; line-height: 1.6; } .wire-popover__footer { padding: 0 1rem 1rem; } .wire-popover__action { appearance: none; display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem; min-height: 2.35rem; padding: 0.58rem 0.8rem; color: var(--popover-contrast); background: var(--popover-accent); border: 0; border-radius: 0.7rem; font: inherit; font-size: 0.78rem; font-weight: 650; text-decoration: none; cursor: pointer; } @media (max-width: 639px) { .wire-popover__panel { position: fixed; right: 0.75rem; bottom: 0.75rem; left: 0.75rem; top: auto; width: auto; max-width: none; transform: none; } .wire-popover__arrow { display: none; } } } }