component DeviceFrame { outputs { change(payload: { device: string; orientation: string; sourceEvent: Event }) rotate(payload: { device: string; orientation: string; sourceEvent: Event }) } props { size: string = "default" color: string = "primary" title: string = "Device Frame" description: string = "" items: unknown[] = [] variant: string = "default" device: string = "phone" orientation: string = "portrait" src: string = "" srcdoc: string = "" frameTitle: string = "Device preview" showToolbar: boolean = true allow: string = "" class: string = "" } state currentOrientation = orientation functions { client function setDevice(nextDevice, sourceEvent) { output.change({ device: nextDevice, orientation: currentOrientation, sourceEvent: sourceEvent }) } client function rotateFrame(sourceEvent) { currentOrientation = currentOrientation === "portrait" ? "landscape" : "portrait"; output.rotate({ device: device, orientation: currentOrientation, sourceEvent: sourceEvent }); output.change({ device: device, orientation: currentOrientation, sourceEvent: sourceEvent }) } } view {
{#if title}{title}{/if}{#if description}

{description}

{/if}
{#if showToolbar}{/if}
{#if src || srcdoc}{:else}
{/if}
} style { .wrn-next--device-frame { display: grid; gap: 0.75rem; padding: 1rem; border: 1px solid var(--wrn-color-border); border-radius: var(--wrn-radius-md); background: var(--wrn-color-surface); box-shadow: var(--wrn-shadow-1); } .wrn-next--device-frame > header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; } .wrn-next__device-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; } .wrn-next__device-actions button { min-height: 2.25rem; padding-inline: 0.7rem; border: 1px solid var(--wrn-color-border); border-radius: var(--wrn-radius-sm); background: var(--wrn-color-surface-2); color: inherit; } .wrn-next__device-shell { overflow: hidden; width: min(100%, 24rem); min-height: 28rem; margin-inline: auto; border: 0.65rem solid var(--wrn-color-text); border-radius: 2rem; background: var(--wrn-color-background); transition: width var(--wrn-motion-normal) var(--wrn-ease-standard); } .wrn-next--device-frame[data-device="tablet"] .wrn-next__device-shell { width: min(100%, 42rem); } .wrn-next--device-frame[data-device="desktop"] .wrn-next__device-shell { width: 100%; border-width: 0.4rem; border-radius: var(--wrn-radius-md); } .wrn-next--device-frame[data-orientation="landscape"] .wrn-next__device-shell { width: min(100%, 48rem); min-height: 20rem; } .wrn-next__device-shell :is(iframe, .wrn-next__device-content) { width: 100%; min-height: inherit; border: 0; } @media (max-width: 640px) { .wrn-next--device-frame > header { align-items: stretch; flex-direction: column; } } /* Shared component foundation. */ .wrn-component { --wrn-component-color: var(--wrn-color-primary); --wrn-component-scale: 1; margin: 0; color: var(--wrn-color-text); font-family: var(--wrn-font-sans, inherit); } .wrn-component--color-primary { --wrn-component-color: var(--wrn-color-primary); } .wrn-component--color-secondary { --wrn-component-color: var(--wrn-color-secondary); } .wrn-component--color-success { --wrn-component-color: var(--wrn-color-success); } .wrn-component--color-warning { --wrn-component-color: var(--wrn-color-warning); } .wrn-component--color-danger { --wrn-component-color: var(--wrn-color-danger); } .wrn-component--color-info { --wrn-component-color: var(--wrn-color-info); } .wrn-component--size-xs { --wrn-component-scale: 0.78; } .wrn-component--size-sm { --wrn-component-scale: 0.88; } .wrn-component--size-default, .wrn-component--size-md { --wrn-component-scale: 1; } .wrn-component--size-lg { --wrn-component-scale: 1.14; } .wrn-component--size-xl { --wrn-component-scale: 1.28; } .wrn-component:not(.wrn-btn) { font-size: calc(1em * var(--wrn-component-scale)); } .wrn-component :is(a, button, input, select, textarea):focus-visible { outline-color: var(--wrn-component-color); } .wrn-component p { margin: 0; color: var(--wrn-color-muted); line-height: 1.6; } } }