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. Skriver du popover uten verdi for hånd, er panelet auto, og da følger komponenten med når nettleseren lukker det.
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/tokens.css"import "@fristil/designsystem/popover.css"
defineFsPopover()Slik tar du den i bruk
Section titled “Slik tar du den i bruk”<!-- Stilarkene siden trenger. Uten byggesteg er det `<link>`, ikke `import`: en nettleser slår ikke opp et pakkenavn. --><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/tokens/tokens.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/components/ramme/popover/popover.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/components/css/button/button.css"><!-- Eller alt i én fil: https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/dist/fristil.css --><!-- `popover`, id-en og `aria-controls` setter komponenten. --><fs-popover id="meny" placement="bottom-end"> <button class="fs-button" data-variant="secondary">Handlinger</button>
<ul class="fs-popover"> <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>
<!-- Én gang for hele appen, i sidemalen. Ikke én gang per komponent. --><script type="module"> import { defineFsPopover } from "https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/dist/components/ramme/popover/fs-popover.js"
defineFsPopover()</script>
<script type="module"> const meny = document.getElementById("meny") const panel = meny.querySelector(".fs-popover")
// Lukk menyen når en handling er valgt meny.addEventListener("click", (hendelse) => { if (panel.contains(hendelse.target) && hendelse.target.closest("button")) meny.hide() })</script>// src/fristil.d.ts, gir <fs-popover> typer i JSXimport "@fristil/designsystem/react-jsx"// main.tsx, kjøres én gang når appen starterimport { defineFsPopover } from "@fristil/designsystem/popover"import "@fristil/designsystem/popover.css"import "@fristil/designsystem/button.css"
defineFsPopover()import { useRef } from "react"import { fs } from "@fristil/designsystem/react"import "@fristil/designsystem/tokens.css"
export function Handlingsmeny({ påArkiver }: { påArkiver: () => void }) { const meny = useRef<HTMLElement & { hide(): void }>(null) const boks = fs.popover({ id: "handlinger", placement: "bottom-end" })
const velg = (handling: () => void) => () => { handling() meny.current?.hide() }
return ( <fs-popover ref={meny} {...boks.host}> <button {...boks.trigger} {...fs.button({ variant: "secondary" })}> Handlinger </button>
<ul {...boks.panel}> <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"import "@fristil/designsystem/button.css"import "@fristil/designsystem/link.css"import "@fristil/designsystem/tokens.css"import "@fristil/designsystem/popover.css"
const boks = fs.popover({ id: "handlinger", placement: "bottom-end" })
---
<fs-popover {...boks.host}> <button {...boks.trigger} {...fs.button({ variant: "secondary" })}>Handlinger</button>
<ul {...boks.panel}> <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.
<!-- Datastar selv. Uten den gjør `data-`-attributtene under ingenting. --><script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar.js"></script>
<!-- Stilarkene siden trenger. Uten byggesteg er det `<link>`, ikke `import`: en nettleser slår ikke opp et pakkenavn. --><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/tokens/tokens.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/components/ramme/popover/popover.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/components/css/button/button.css"><!-- Eller alt i én fil: https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/dist/fristil.css -->
<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">Handlinger</button>
<ul class="fs-popover"> <li> <button class="fs-button" data-variant="ghost" type="button" data-on:click="$apen = false"> Arkiver saken </button> </li> </ul> </fs-popover></div>
<!-- Én gang for hele appen, i sidemalen. Ikke én gang per komponent. --><script type="module"> import { defineFsPopover } from "https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/dist/components/ramme/popover/fs-popover.js"
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.
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"> <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 panelet på popover eller klassen fs-popover, og knappen på aria-controls som peker på panelet, ellers på at den er den første knappen utenfor panelet som ikke peker på noe annet med aria-controls.
Lages markupen med JavaScript, skriver fs.popover() koblingen, id-en, klassen og popover. Kommer den fra en mal uten JavaScript, setter komponenten popover="manual", id-en og aria-controls, og lar det serveren har skrevet stå. Komponenten setter dessuten open på verten, aria-expanded på knappen og posisjonen på panelet. Alt den har skrevet setter den tilbake når en oppdatering fra serveren river det 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.
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. start og end følger leseretningen, så i en side med dir="rtl" er start høyre kant. Er det ikke plass langs kanten, skyves panelet inn i vinduet framfor å bytte andre. fs.popover({ id, placement }) skriver det på verten, fs.popover.isPlacement sjekker en verdi, og popoverPlacements fra hovedinngangen lister dem |
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 kjenner det igjen på klassen eller på popover, og fyller inn den andre |
| Variabel | Standard |
|---|---|
--fs-popover-width | min(20rem, calc(100vw - var(--fs-spacing-8))) |
--fs-popover-padding | var(--fs-spacing-3) |
--fs-popover-radius | var(--fs-spacing-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.