From 015fbb87be10e908bab9c01ac267128331f9e34b Mon Sep 17 00:00:00 2001 From: johannes Date: Fri, 9 Oct 2026 11:44:23 +0200 Subject: [PATCH 1/4] feat(frontend): legg til useDebounce-hook --- frontend/src/hooks/useDebounce.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 frontend/src/hooks/useDebounce.ts diff --git a/frontend/src/hooks/useDebounce.ts b/frontend/src/hooks/useDebounce.ts new file mode 100644 index 0000000..de40783 --- /dev/null +++ b/frontend/src/hooks/useDebounce.ts @@ -0,0 +1,12 @@ +import { useEffect, useState } from 'react'; + +export function useDebounce(value: T, delay = 300): T { + const [debounced, setDebounced] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => setDebounced(value), delay); + return () => clearTimeout(timer); + }, [value, delay]); + + return debounced; +} \ No newline at end of file From 1a01103dee802cd83de89ab686eb73690d369ae0 Mon Sep 17 00:00:00 2001 From: johannes Date: Fri, 9 Oct 2026 11:44:43 +0200 Subject: [PATCH 2/4] feat(frontend): legg til SearchBar-komponent --- .../src/components/SearchBar/SearchBar.tsx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 frontend/src/components/SearchBar/SearchBar.tsx diff --git a/frontend/src/components/SearchBar/SearchBar.tsx b/frontend/src/components/SearchBar/SearchBar.tsx new file mode 100644 index 0000000..e7f504e --- /dev/null +++ b/frontend/src/components/SearchBar/SearchBar.tsx @@ -0,0 +1,30 @@ +import './SearchBar.css'; + +interface SearchBarProps { + value: string; + onChange: (value: string) => void; +} + +// Søkefeltet øverst på siden. Komponenten eier ikke state selv: +// forelderen styrer verdien og bestemmer når det faktisk søkes (debounce). +export function SearchBar({ value, onChange }: SearchBarProps) { + return ( + // role="search" gjør at skjermlesere kan hoppe rett til søket. + // preventDefault hindrer at Enter laster siden på nytt. +
e.preventDefault()}> + {/* Skjult visuelt, men lest opp av skjermlesere. Placeholder alene er ikke nok. */} + + onChange(e.target.value)} + autoComplete="off" + /> +
+ ); +} \ No newline at end of file From 567f324faf00d05246de6c25bdef8cf245d55768 Mon Sep 17 00:00:00 2001 From: johannes Date: Fri, 9 Oct 2026 11:44:56 +0200 Subject: [PATCH 3/4] =?UTF-8?q?style(frontend):=20firkantet=20s=C3=B8kefel?= =?UTF-8?q?t=20med=20skjult=20label=20og=20fokusramme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/SearchBar/SearchBar.css | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 frontend/src/components/SearchBar/SearchBar.css diff --git a/frontend/src/components/SearchBar/SearchBar.css b/frontend/src/components/SearchBar/SearchBar.css new file mode 100644 index 0000000..949cd4b --- /dev/null +++ b/frontend/src/components/SearchBar/SearchBar.css @@ -0,0 +1,22 @@ +/* Skjuler labelen visuelt, men beholder den for skjermlesere. */ +.search-bar__label { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; + border: 0; +} + +.search-bar__input { + width: 100%; + padding: 0.6rem 0.8rem; + font: inherit; + border: 1px solid #767676; + border-radius: 0; + -webkit-appearance: textfield; + appearance: textfield; +} From 05a0f6f739f2c89458a435a186d69f31329737e6 Mon Sep 17 00:00:00 2001 From: johannes Date: Fri, 9 Oct 2026 11:45:36 +0200 Subject: [PATCH 4/4] =?UTF-8?q?feat(frontend):=20koble=20s=C3=B8k=20til=20?= =?UTF-8?q?spill-lista=20(midlertidig=20klientfiltrering=20av=20mockdata)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7a02480..84c76be 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,8 @@ import { useState } from 'react'; import { GameList } from './components/GameList/GameList'; import { mockGames } from './mocks/games'; +import { SearchBar } from './components/SearchBar/SearchBar'; +import { useDebounce } from './hooks/useDebounce'; /** * Midlertidig forside: viser spillene med mockdata til GraphQL er klart. @@ -10,6 +12,16 @@ function App() { const [favorites, setFavorites] = useState>(new Set()); const [view, setView] = useState<'list' | 'grid'>('list'); + // Det brukeren skriver nå, og det vi faktisk søker på etter 300 ms pause + const [input, setInput] = useState(''); + const query = useDebounce(input.trim(), 300); + + // MIDLERTIDIG: filtrerer mockdata på klienten for å kunne teste søket. + // Når GraphQL er klart, sendes `query` til backend og filtreres i databasen. + const games = mockGames.filter((g) => + g.name.toLowerCase().includes(query.toLowerCase()), + ); + const toggleFavorite = (id: string) => { setFavorites((prev) => { const next = new Set(prev); @@ -22,6 +34,7 @@ function App() { return (

Spill

+

Søkeresultater

{/* Bytter mellom visning 1 (liste) og visning 2 (rutenett) */} @@ -35,7 +48,7 @@ function App() {