Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 87 additions & 22 deletions frontend/src/components/FilterSidebar/FilterSidebar.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* Under 1024px: filtrene ligger over resultatlista */
.filter-sidebar {
padding: 24px 24px 0;
padding: 8px 24px 0;
text-align: left;
font-family: var(--mono);
font-size: 13px;
Expand All @@ -9,7 +9,7 @@
.filter-sidebar__inner {
display: flex;
flex-direction: column;
gap: 16px;
gap: 8px;
}

.filter-sidebar h2 {
Expand Down Expand Up @@ -49,17 +49,20 @@
cursor: pointer;
}

/* Slider i trykk-stil: tynn strek med Damp-symbolet som knott.
/* Slider i trykk-stil: tynn strek med turbinen fra logoen som knott.
appearance: none fjerner nettleserens egen stil, så vi kan tegne den selv */
.filter-sidebar input[type='range'] {
/* Størrelsen på knotten. Alt under regnes ut fra denne, så endre bare her */
--thumb: 20px;
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 20px; /* klikkflaten, litt høyere enn knotten */
height: calc(var(--thumb) + 4px); /* klikkflaten, litt høyere enn knotten */
margin: 0;
/* Streken tegnes her i stedet for på track: 1px høy og 16px (én knott) kortere,
/* Streken tegnes her i stedet for på track: 1px høy og én knott kortere,
så den slutter midt i knotten når den står helt ytterst */
background: linear-gradient(var(--text), var(--text)) center / calc(100% - 16px) 1px no-repeat;
background: linear-gradient(var(--text), var(--text)) center / calc(100% - var(--thumb)) 1px
no-repeat;
cursor: pointer;
}

Expand All @@ -69,42 +72,46 @@
background: transparent;
}

/* Knotten er Damp-symbolet (Chrome, Edge, Safari).
margin-top sentrerer den på streken: (1px - 16px) / 2 */
/* Knotten er turbinen (Chrome, Edge, Safari).
margin-top sentrerer den på streken: (1px - knotten) / 2 */
.filter-sidebar input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
margin-top: -7.5px;
background: url('../../assets/logo/damp-mark-on-light.svg') center / contain no-repeat;
background-color: var(--bg); /* dekker streken bak de åpne feltene i symbolet */
width: var(--thumb);
height: var(--thumb);
margin-top: calc((1px - var(--thumb)) / 2);
background: url('../../assets/drawings/turbin.webp') center / contain no-repeat;
background-color: var(--bg); /* dekker streken bak de åpne feltene i turbinen */
border-radius: 50%; /* rund bakgrunn, så den ikke blir en firkant */
border: 0;
}

/* Samme for Firefox, som bruker egne navn */
/* Samme for Firefox, som bruker egne navn. Firefox sentrerer knotten selv */
.filter-sidebar input[type='range']::-moz-range-track {
height: 1px;
background: transparent;
}

.filter-sidebar input[type='range']::-moz-range-thumb {
width: 16px;
height: 16px;
background: url('../../assets/logo/damp-mark-on-light.svg') center / contain no-repeat;
background-color: var(--bg); /* dekker streken bak de åpne feltene i symbolet */
border-radius: 50%; /* rund bakgrunn, så den ikke blir en firkant */
width: var(--thumb);
height: var(--thumb);
background: url('../../assets/drawings/turbin.webp') center / contain no-repeat;
background-color: var(--bg);
border-radius: 50%;
border: 0;
}

/* Mørkt tema: lys versjon av symbolet, som logoen i toppen */
/* Mørkt tema: turbinen er svart, så den snus til lys med invert, som logoen i toppen.
invert snur også bakgrunnen, derfor brukes den inverterte av --bg (#191b1a),
som blir #191b1a igjen etter invert */
:root[data-theme='dark'] .filter-sidebar input[type='range']::-webkit-slider-thumb {
background-image: url('../../assets/logo/damp-mark-on-dark.svg');
filter: invert(1);
background-color: #e6e4e5;
}

:root[data-theme='dark'] .filter-sidebar input[type='range']::-moz-range-thumb {
background-image: url('../../assets/logo/damp-mark-on-dark.svg');
filter: invert(1);
background-color: #e6e4e5;
}

/* Tydelig fokusramme når slideren brukes med tastatur (tilgjengelighet) */
Expand All @@ -123,6 +130,50 @@
display: none;
}

/* Smale skjermer: filtrene er skjult til man trykker på knappen */
.filter-sidebar__panel {
display: none;
flex-direction: column;
gap: 8px; /* samme som .filter-sidebar__inner */
}

.filter-sidebar__panel--open {
display: flex;
}

/* Knappen som åpner filtrene på smale skjermer: ikon og tekst uten ramme */
.filter-sidebar__toggle {
align-self: flex-start; /* ikke strekk i full bredde */
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 32px; /* over WCAG-minimum på 24px for trykkflater */
padding: 0;
font: inherit;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--text-h);
background: none;
border: 0;
cursor: pointer;
text-underline-offset: 4px;
}

/* Understrek ved hover, som de andre tekstknappene */
.filter-sidebar__toggle:hover {
text-decoration: underline;
}

.filter-sidebar__toggle:focus-visible {
outline: 2px solid var(--text-h);
outline-offset: 4px;
}

/* Smale skjermer: knappen sier allerede hva det er */
.filter-sidebar h2 {
display: none;
}

@media (min-width: 1024px) {
/* Usynlig skinne i sidefeltet, fra under logoen til bunnen av siden.
Bunnen er bunnen av <html>, som er relative i index.css */
Expand Down Expand Up @@ -168,4 +219,18 @@
mix-blend-mode: screen;
opacity: 0.6;
}

/* Brede skjermer: ingen knapp, filtrene vises alltid */
.filter-sidebar__toggle {
display: none;
}

.filter-sidebar__panel {
display: flex;
gap: 16px;
}

.filter-sidebar h2 {
display: block;
}
}
139 changes: 87 additions & 52 deletions frontend/src/components/FilterSidebar/FilterSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useRef } from 'react';
import { useRef, useState } from 'react';
import { useStickyTop } from '../../hooks/useStickyTop';
// Om filterpanelet er åpent. Brukes bare på smale skjermer, se CSS
import type { Facet } from '../../graphql/filterOptions';
import { defaultFilters, MAX_YEAR, MIN_YEAR, type GameFilters } from '../../types/filters';
import './FilterSidebar.css';
Expand Down Expand Up @@ -60,69 +61,103 @@ function CheckboxGroup({ legend, options, selected, onChange }: CheckboxGroupPro
export function FilterSidebar({ filters, onChange, genres, platforms }: FilterSidebarProps) {
const innerRef = useRef<HTMLDivElement>(null);
useStickyTop(innerRef);
const [open, setOpen] = useState(false);

return (
<aside className="filter-sidebar" aria-label="Filtre">
<div className="filter-sidebar__inner" ref={innerRef}>
<h2>Filtrer</h2>

<CheckboxGroup
legend="Sjanger"
options={genres}
selected={filters.genreIds}
onChange={(genreIds) => onChange({ ...filters, genreIds })}
/>
{/* Vises bare på smale skjermer. aria-expanded forteller skjermlesere
om panelet er åpent, aria-controls hvilket element knappen styrer */}
<button
type="button"
className="filter-sidebar__toggle"
aria-expanded={open}
aria-controls="filter-panel"
onClick={() => setOpen(!open)}
>
{/* Filterikon: tre streker som blir kortere. aria-hidden fordi teksten
ved siden av allerede sier hva knappen gjør */}
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
<path
d="M2 4h12M4 8h8M6 12h4"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
{open ? 'skjul filtre' : 'vis filtre'}
</button>

<div
id="filter-panel"
className={`filter-sidebar__panel${open ? ' filter-sidebar__panel--open' : ''}`}
>
<CheckboxGroup
legend="Sjanger"
options={genres}
selected={filters.genreIds}
onChange={(genreIds) => onChange({ ...filters, genreIds })}
/>

<CheckboxGroup
legend="Plattform"
options={platforms}
selected={filters.platformIds}
onChange={(platformIds) => onChange({ ...filters, platformIds })}
/>
<CheckboxGroup
legend="Plattform"
options={platforms}
selected={filters.platformIds}
onChange={(platformIds) => onChange({ ...filters, platformIds })}
/>

{/* Sammenleggbar som sjanger og plattform. Viser valgt intervall i overskriften
{/* Sammenleggbar som sjanger og plattform. Viser valgt intervall i overskriften
når det er endret, så det synes også når gruppen er lukket */}
<details className="filter-sidebar__group">
<summary>
Utgivelsesår
{(filters.yearFrom !== MIN_YEAR || filters.yearTo !== MAX_YEAR) &&
` (${filters.yearFrom}–${filters.yearTo})`}
</summary>
<details className="filter-sidebar__group">
<summary>
Utgivelsesår
{(filters.yearFrom !== MIN_YEAR || filters.yearTo !== MAX_YEAR) &&
` (${filters.yearFrom}–${filters.yearTo})`}
</summary>

<fieldset aria-label="Utgivelsesår">
<label htmlFor="filter-year-from">Fra: {filters.yearFrom}</label>
<input
id="filter-year-from"
type="range"
min={MIN_YEAR}
max={MAX_YEAR}
value={filters.yearFrom}
// Math.min hindrer at "fra" blir større enn "til"
onChange={(e) =>
onChange({ ...filters, yearFrom: Math.min(Number(e.target.value), filters.yearTo) })
}
/>
<fieldset aria-label="Utgivelsesår">
<label htmlFor="filter-year-from">Fra: {filters.yearFrom}</label>
<input
id="filter-year-from"
type="range"
min={MIN_YEAR}
max={MAX_YEAR}
value={filters.yearFrom}
// Math.min hindrer at "fra" blir større enn "til"
onChange={(e) =>
onChange({
...filters,
yearFrom: Math.min(Number(e.target.value), filters.yearTo),
})
}
/>

<label htmlFor="filter-year-to">Til: {filters.yearTo}</label>
<input
id="filter-year-to"
type="range"
min={MIN_YEAR}
max={MAX_YEAR}
value={filters.yearTo}
// Math.max hindrer at "til" blir mindre enn "fra"
onChange={(e) =>
onChange({ ...filters, yearTo: Math.max(Number(e.target.value), filters.yearFrom) })
}
/>
</fieldset>
</details>
<label htmlFor="filter-year-to">Til: {filters.yearTo}</label>
<input
id="filter-year-to"
type="range"
min={MIN_YEAR}
max={MAX_YEAR}
value={filters.yearTo}
// Math.max hindrer at "til" blir mindre enn "fra"
onChange={(e) =>
onChange({
...filters,
yearTo: Math.max(Number(e.target.value), filters.yearFrom),
})
}
/>
</fieldset>
</details>

<button type="button" className="text-button" onClick={() => onChange(defaultFilters)}>
nullstill filtre
</button>
{/* Pynt nederst i sidebaren. aria-hidden fordi den ikke har noe innhold for skjermlesere */}
<div className="filter-sidebar__drawing" aria-hidden="true" />
<button type="button" className="text-button" onClick={() => onChange(defaultFilters)}>
nullstill filtre
</button>
{/* Pynt nederst i sidebaren. aria-hidden fordi den ikke har noe innhold for skjermlesere */}
<div className="filter-sidebar__drawing" aria-hidden="true" />
</div>
</div>
</aside>
);
Expand Down