Popover
Panelet bruker nettleserens egen popover, så det havner i topplaget og legger seg over alt annet uten at noen trenger å finne en z-index som er høy nok.
Resten gjør komponenten. Panelet åpnes i manual-modus, ikke auto, siden nettleserens egen lukking rekker å lukke panelet før knappen får klikket sitt, og knappen da ikke kan brukes til å lukke igjen. Escape, klikk utenfor og fokus tilbake til knappen er derfor skrevet i komponenten.
Plasseringen er også vår. position-anchor finnes ennå ikke i alle nettlesere, så komponenten regner ut posisjonen mot knappen og setter den som to variabler på panelet.
// main.ts, kjøres én gang når appen starterimport { defineFsPopover } from "@fristil/designsystem/popover"import "@fristil/designsystem/popover.css"
defineFsPopover()En meny under en knapp
Section titled “En meny under en knapp”const boks = fs.popover({ id: "handlinger" })<fs-popover placement="bottom-end"> <button class="fs-button" data-variant="secondary" aria-expanded="false" aria-controls="handlinger">Handlinger</button>
<ul class="fs-popover" id="handlinger" popover="manual" data-variant="plain"> <li><button class="fs-button" data-variant="ghost" type="button">Arkiver saken</button></li> <li><button class="fs-button" data-variant="ghost" type="button">Last ned som PDF</button></li> </ul></fs-popover>Komponenten kjenner igjen delene på koblingen som må være der uansett: panelet er det som har popover, og knappen er den som peker på panelet med aria-controls.
Koblingen, popover-attributtet og klassen kommer fra serveren med fs.popover(). Komponenten setter open på verten, aria-expanded på knappen og posisjonen på panelet, og setter dem tilbake når en oppdatering fra serveren river dem bort. Malen din trenger ingenting ekstra.
Reparasjonen gjelder én vei: har noen bedt om at vinduet er åpent, blir det stående. Sender serveren open, åpnes det, for det er noe serveren faktisk sa.
Lukk med egenskapen, altså meny.open = false, hide() eller toggle(). Det er en beskjed komponenten kan se. Fjerner du attributtet utenfra i stedet, som med Datastars data-attr:open, er det ikke til å skille fra en oppdatering som river det bort, og da setter komponenten det tilbake. Der er server-controlled svaret. Se Markup og oppførsel.
I en app
Section titled “I en app”<fs-popover id="meny" placement="bottom-end"> <button class="fs-button" data-variant="secondary" aria-expanded="false" aria-controls="handlinger">Handlinger</button>
<ul class="fs-popover" id="handlinger" popover="manual" data-variant="plain"> <li><button class="fs-button" data-variant="ghost" type="button">Arkiver saken</button></li> <li><button class="fs-button" data-variant="ghost" type="button">Last ned som PDF</button></li> </ul></fs-popover>
<script type="module"> import { defineFsPopover } from "@fristil/designsystem/popover" defineFsPopover()
const meny = document.getElementById("meny")
// Lukk menyen når en handling er valgt meny.addEventListener("click", (hendelse) => { if (hendelse.target.closest("[aria-controls='handlinger']")) return if (hendelse.target.closest("button")) meny.hide() })</script>// main.tsx, kjøres én gang når appen starterimport { defineFsPopover } from "@fristil/designsystem/popover"import "@fristil/designsystem/popover.css"
defineFsPopover()import { useRef } from "react"import { fs } from "@fristil/designsystem/react"
export function Handlingsmeny({ påArkiver }: { påArkiver: () => void }) { const meny = useRef<HTMLElement & { hide(): void }>(null) const boks = fs.popover({ id: "handlinger" })
const velg = (handling: () => void) => () => { handling() meny.current?.hide() }
return ( <fs-popover ref={meny} placement="bottom-end" {...boks.host}> <button {...boks.trigger} {...fs.button({ variant: "secondary" })}> Handlinger </button>
<ul {...boks.panel} data-variant="plain"> <li> <button {...fs.button({ variant: "ghost" })} onClick={velg(påArkiver)}> Arkiver saken </button> </li> </ul> </fs-popover> )}Panelet åpnes og lukkes av komponenten selv, så du trenger ingen tilstand i React for det. Skal du lukke etter en handling, kaller du hide() på elementet.
---import { fs } from "@fristil/designsystem"
const boks = fs.popover({ id: "handlinger" })---
<fs-popover placement="bottom-end" {...boks.host}> <button {...boks.trigger} {...fs.button({ variant: "secondary" })}>Handlinger</button>
<ul {...boks.panel} data-variant="plain"> <li><a {...fs.link()} href="/sak/481/pdf">Last ned som PDF</a></li> <li><a {...fs.link()} href="/sak/481/arkiv">Arkiver saken</a></li> </ul></fs-popover>
<script> import { defineFsPopover } from "@fristil/designsystem/popover" defineFsPopover()</script>Er handlingene lenker, ligger de i sidekilden og kan følges av en søkemotor. Selve panelet er skjult av popover til komponenten åpner det.
<div data-signals="{apen: false}"> <fs-popover server-controlled placement="bottom-end" data-attr:open="$apen" data-on:popover-toggle="$apen = evt.detail.open" > <button class="fs-button" data-variant="secondary" aria-expanded="false" aria-controls="handlinger">Handlinger</button>
<ul class="fs-popover" id="handlinger" popover="manual" data-variant="plain"> <li> <button class="fs-button" data-variant="ghost" type="button" data-on:click="$apen = false"> Arkiver saken </button> </li> </ul> </fs-popover></div>
<script type="module"> import { defineFsPopover } from "@fristil/designsystem/popover" defineFsPopover()</script>data-attr:open setter og fjerner attributtet etter signalet, og data-on:popover-toggle fanger opp at brukeren lukket panelet med Escape eller et klikk utenfor. Uten den siste ville signalet og panelet kommet i utakt.
server-controlled hører med når signalet styrer open. Komponenten setter ellers attributtet tilbake når noe fjerner det, siden et fjernet attributt ikke er til å skille fra en oppdatering som river det bort, og da lot panelet seg ikke lukke fra signalet. Styrer du panelet fra JavaScript i stedet, med meny.open = false, trengs det ikke: en egenskap er en beskjed komponenten kan se.
Attributter
Section titled “Attributter”| Attributt | Type | Effekt |
|---|---|---|
open | boolsk | Om panelet er åpent. Speiles når brukeren åpner og lukker. |
placement | streng | bottom-start, bottom-end, top-start eller top-end |
server-controlled | boolsk | Serveren eier tilstanden. Komponenten setter ikke open tilbake etter en oppdatering. |
Hendelser og metoder
Section titled “Hendelser og metoder”| Navn | Hva det er |
|---|---|
popover-toggle | Hendelse med { open: boolean } i detail |
show(), hide(), toggle() | Metoder på elementet |
const meny = document.querySelector("fs-popover")
meny.addEventListener("popover-toggle", (hendelse) => { console.log(hendelse.detail.open ? "åpnet" : "lukket")})Panelet flytter seg med siden når brukeren ruller eller endrer vindusstørrelsen. Er det ikke plass under knappen, legger det seg over.
Klasser du kan style
Section titled “Klasser du kan style”Komponenten rendrer i vanlig DOM, så markupen din treffes av vanlige selektorer:
| Klasse | Hva det er |
|---|---|
.fs-popover | Panelet. Komponenten setter klassen på elementet du la inn. |
| Variabel | Standard |
|---|---|
--fs-popover-width | min(20rem, calc(100vw - var(--size-8))) |
--fs-popover-padding | var(--size-3) |
--fs-popover-radius | var(--size-1) |
--fs-popover-top | settes av komponenten |
--fs-popover-left | settes av komponenten |
Posisjonen settes som --fs-popover-top og --fs-popover-left på elementet. De to er regnet ut fra knappens plass på skjermen, og er de eneste verdiene komponenten skriver inn direkte. Alt annet står i stilarket, så du kan overstyre det uten !important.
Se Tilpasning for hvordan variablene og laget virker.
Tilgjengelighet
Section titled “Tilgjengelighet”- Panelet er ikke en dialog. Fokus flyttes ikke inn automatisk, og resten av siden er fortsatt i bruk. Trenger du å stoppe brukeren, bruk Dialog.
- Escape lukker og gir fokus tilbake til knappen. Uten det står fokus på noe som ikke finnes lenger.
- Innholdet i panelet skal kunne nås med Tab, i samme rekkefølge som det står i markupen.
- Ikke legg et skjema som må fylles ut i et panel som lukker seg ved klikk utenfor.