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
219 changes: 189 additions & 30 deletions frontend/src/components/header/Header.css
Original file line number Diff line number Diff line change
@@ -1,45 +1,204 @@
/* Header – fra Figma (Components → Header)
Inneholder Logo, enten SearchField eller navigasjon, og en Button (variant dark, size s). */

/* =========================================
HEADER – ISSUE #50
Felles header for Handlelappen
========================================= */

/* 1. Grunnstruktur */

.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
height: 64px;
padding: 0 40px;
background: var(--bg-page);
border-bottom: 1px solid var(--border-hairline);
display: grid;
grid-template-columns:
minmax(180px, 1fr)
minmax(0, 1.5fr)
minmax(180px, 1fr);

align-items: center;
gap: 24px;

width: 100%;
max-width: none;
min-height: 64px;
padding: 12px 40px;
box-sizing: border-box;

background: var(--bg-page);
border-bottom: 1px solid var(--border-hairline);
color: var(--text-primary);
}

.header .search-field {
flex: 1;
.header__logo {
display: flex;
align-items: center;
min-width: 0;
}

/* 2. Navigasjon – hjemmeside */

.header__nav {
display: flex;
gap: 32px;
display: flex;
justify-content: center;
align-items: center;
gap: 32px;
min-width: 0;
}

.header__link {
font: 500 11px var(--font-mono);
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-secondary);
text-decoration: none;
font: 500 11px var(--font-mono);
letter-spacing: 0.06em;
text-transform: uppercase;

color: var(--text-secondary);
text-decoration: none;
white-space: nowrap;
}

.header__link:hover {
color: var(--text-accent);
color: var(--text-accent);
}

.header__link:focus-visible {
outline: 2px solid var(--text-accent);
outline-offset: 5px;
border-radius: 2px;
}

/* 3. Søkefelt – øvrige sider */

.header__search {
display: flex;
align-items: center;
gap: 12px;

width: 100%;
max-width: 610px;
height: 40px;
padding: 0 14px;
box-sizing: border-box;

justify-self: center;

background: var(--bg-surface);
border: 1px solid transparent;
border-radius: 8px;
}

.header__search:focus-within {
border-color: var(--text-accent);
}

.header__search-icon {
display: flex;
width: 18px;
height: 18px;
flex-shrink: 0;
color: var(--text-muted);
}

.header__search-icon svg {
width: 100%;
height: 100%;
}

.header__search-input {
flex: 1;
min-width: 0;
width: 100%;
border: none;
outline: none;
background: transparent;

color: var(--text-primary);
font: 400 13px var(--font-ui);
}

/* Mobil: mindre luft på sidene */
@media (max-width: 600px) {
.header {
gap: 12px;
padding: 0 16px;
}
.header__search-input::placeholder {
color: var(--text-muted);
}

.header__search-input:disabled {
cursor: not-allowed;
}

/* 4. Handlinger */

.header__nav {
display: none;
}
}
.header__actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 12px;
min-width: 0;
}

.header__list-button {
white-space: nowrap;
justify-content: center;
}

.header__list-button:disabled {
opacity: 0.7;
cursor: not-allowed;
}

/* 5. Nettbrett */

@media (max-width: 1000px) {
.header {
grid-template-columns: auto minmax(0, 1fr) auto;
gap: 16px;
padding-left: 24px;
padding-right: 24px;
}

.header__nav {
gap: 16px;
}
}

/* 6. Mobil */

@media (max-width: 700px) {
.header {
grid-template-columns: minmax(0, 1fr) auto;
gap: 12px;
padding: 10px 16px;
}

.header__nav,
.header__search {
display: none;
}

.header__actions {
gap: 8px;
}

.header__list-button {
padding-left: 10px;
padding-right: 10px;
}
}

/* 7. Ekstra små skjermer */

@media (max-width: 360px) {
.header__logo .logo {
gap: 6px;
font-size: 15px;
}

.header__logo .logo__icon {
width: 24px;
height: 24px;
}

.header__actions {
gap: 6px;
}

.header__list-button {
font-size: 11px;
padding-left: 8px;
padding-right: 8px;
}
}
113 changes: 113 additions & 0 deletions frontend/src/components/header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@

import { useState } from "react";
import type { FormEvent } from "react";

import Logo from "../logo/Logo";
import MenuButton from "../menuButton/MenuButton";

import "../button/Button.css";
import "./Header.css";

type HeaderProps = {
variant?: "home" | "search";
searchValue?: string;
onSearch?: (value: string) => void;
onOpenList?: () => void;
onOpenMenu?: () => void;
isMenuOpen?: boolean;
listCount?: number;
};

export default function Header({
variant = "home",
searchValue = "",
onSearch,
onOpenList,
onOpenMenu,
isMenuOpen = false,
listCount = 0,
}: HeaderProps) {
const [searchInput, setSearchInput] = useState(searchValue);

const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();

const value = searchInput.trim();

if (value && onSearch) {
onSearch(value);
}
};

return (
<header className={`header header--${variant}`}>
<div className="header__logo">
<Logo />
</div>

{variant === "home" ? (
<nav className="header__nav" aria-label="Hovednavigasjon">
<span className="header__link">Katalog</span>
<span className="header__link">Mine lister</span>
<span className="header__link">Historikk</span>
</nav>
) : (
<form
className="header__search"
role="search"
onSubmit={handleSubmit}
>
<span className="header__search-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none">
<circle
cx="10.5"
cy="10.5"
r="6.5"
stroke="currentColor"
strokeWidth="1.8"
/>
<path
d="M15.5 15.5L21 21"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
/>
</svg>
</span>

<input
type="search"
className="header__search-input"
placeholder="Søk etter matvarer, f.eks. havregryn"
aria-label="Søk etter matvarer"
value={searchInput}
onChange={(event) =>
setSearchInput(event.target.value)
}
disabled={!onSearch}
/>
</form>
)}

<div className="header__actions">
<button
type="button"
className="button button--dark button--s header__list-button"
onClick={onOpenList}
disabled={!onOpenList}
title={!onOpenList ? "Handlelisten kobles til senere" : undefined}
>
Min lapp
<span className="button__count">
{String(listCount).padStart(2, "0")}
</span>
</button>

<MenuButton
onClick={onOpenMenu}
isOpen={isMenuOpen}
/>
</div>
</header>
);
}
10 changes: 10 additions & 0 deletions frontend/src/components/logo/Logo.css
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,13 @@
transition: none;
}
}

/* =========================================
UNIVERSAL UTFORMING – FOKUSMARKERING
========================================= */

.logo:focus-visible {
outline: 2px solid var(--text-accent);
outline-offset: 4px;
border-radius: 4px;
}
Loading