Skip to content

Commit

Permalink
Merge pull request #27 from danskernesdigitalebibliotek/DDFBRA-127-mo…
Browse files Browse the repository at this point in the history
…bile-view

Filters - mobile view
  • Loading branch information
Adamik10 authored Nov 6, 2024
2 parents cc7b1e5 + 41e2691 commit f54b338
Show file tree
Hide file tree
Showing 21 changed files with 458 additions and 117 deletions.
56 changes: 56 additions & 0 deletions components/accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client"

import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDownIcon } from "@radix-ui/react-icons"
import * as React from "react"

import { cn } from "@/lib/helpers/helper.cn"

const Accordion = AccordionPrimitive.Root

const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Item ref={ref} className={cn(className)} {...props}>
{children}
<hr className="mb-1 mt-1 w-full border-foreground opacity-10" />
</AccordionPrimitive.Item>
))
AccordionItem.displayName = "AccordionItem"

const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
`text-sm flex flex-1 items-center justify-between rounded-md p-4 text-left text-typo-subtitle-md
font-medium transition-all hover:bg-background-overlay [&[data-state=open]>svg]:rotate-180`,
className
)}
{...props}>
{children}
<ChevronDownIcon className="h-8 w-8 shrink-0 text-muted-foreground transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName

const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down
overflow-hidden p-4 pt-1"
{...props}>
<div className={cn(className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
4 changes: 2 additions & 2 deletions components/global/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function Header() {
return (
<div>
<div className="dark-mode-transition flex h-navigation-top-height items-center justify-center bg-background-overlay">
<p className="text-typo-caption">Biblioterernes ebøger og lyderbøger</p>
<p className="text-typo-caption">Biblioterernes ebøger og lydbøger</p>
</div>
<div className="content-container grid h-navigation-height grid-cols-3 items-center">
<div className="flex-0">
Expand All @@ -20,7 +20,7 @@ function Header() {
<div className="flex flex-1 justify-center">
<DarkModeToggle />
</div>
<div className="flex-0 flex justify-end space-x-4">
<div className="flex-0 flex justify-end space-x-4 pr-1">
<Button variant="icon" aria-label="Tilgå hjælpesiden">
<Icon className="h-[24px] w-[24px]" name="question-mark" />
</Button>
Expand Down
14 changes: 8 additions & 6 deletions components/global/header/ProfileButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useRouter } from "next/navigation"
import React, { MouseEvent } from "react"

import { Button } from "@/components/shared/button/Button"
import { Button, buttonVariants } from "@/components/shared/button/Button"
import Icon from "@/components/shared/icon/Icon"
import {
Sheet,
Expand All @@ -12,7 +12,7 @@ import {
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/shared/sheet/LoginSheet"
} from "@/components/shared/sheet/Sheet"
import useSession from "@/hooks/useSession"

const HeaderButton = ({
Expand Down Expand Up @@ -42,13 +42,15 @@ function ProfileButton() {
if (!session || !session.isLoggedIn) {
return (
<Sheet>
<SheetTrigger>
<HeaderButton />
<SheetTrigger
aria-label="Login / Tilgå profilsiden"
className={buttonVariants({ variant: "icon" })}>
<Icon className="h-[24px] w-[24px]" name="profile" />
</SheetTrigger>
<SheetContent className="p-grid-edge w-full max-w-[560px]">
<SheetContent className="w-full max-w-[560px] p-grid-edge">
<SheetHeader>
<SheetTitle className="mb-space-y text-typo-heading-3">Log ind</SheetTitle>
<div className="py-space-y flex flex-col items-center justify-center rounded-sm bg-background-overlay">
<div className="flex flex-col items-center justify-center rounded-sm bg-background-overlay py-space-y">
<SheetDescription className="mb-4 text-typo-heading-4 font-bold text-foreground">
Log ind med UNI•Login
</SheetDescription>
Expand Down
4 changes: 3 additions & 1 deletion components/pages/searchPageLayout/SearchPageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ const SearchPageLayout = ({ searchQuery }: { searchQuery?: string }) => {

return (
<div className="content-container">
<h1 className="mt-[88px] text-typo-heading-2">{`Viser resultater for "${q}" ${hitcount ? "(" + hitcount + ")" : ""}`}</h1>
<h1 className="mt-8 text-typo-heading-3 lg:mt-[88px] lg:text-typo-heading-2">
{`Viser resultater for "${q}" ${hitcount ? "(" + hitcount + ")" : ""}`}
</h1>
{/* TODO: add ghost loading and cleanup the code below */}
{isLoadingFacets && <p>isLoadingFacets...</p>}
{isNoFilters && <p>Ingen filter</p>}
Expand Down
27 changes: 27 additions & 0 deletions components/shared/badge/BadgeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react"

import { cn } from "@/lib/helpers/helper.cn"

type BadgeButtonProps = {
onClick: () => void
isActive?: boolean
classNames?: string
children: React.ReactNode
}

const BadgeButton = ({ onClick, isActive = false, classNames, children }: BadgeButtonProps) => {
return (
<button
onClick={onClick}
className={cn(
`focus-visible h-[29px] w-auto self-start whitespace-nowrap rounded-full bg-background-overlay px-4
py-2 text-typo-caption hover:animate-wiggle`,
isActive && "bg-foreground text-background",
classNames,
)}>
{children}
</button>
)
}

export default BadgeButton
3 changes: 1 addition & 2 deletions components/shared/button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { cn } from "@/lib/helpers/helper.cn"

const buttonVariants = cva(
`inline-flex border border-foreground uppercase text-typo-button-lg text-foreground shadow-button rounded-full items-center justify-center
whitespace-nowrap focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground
focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:translate-x-[1px] hover:translate-y-[1px] transition
whitespace-nowrap focus-visible disabled:pointer-events-none disabled:opacity-50 hover:translate-x-[1px] hover:translate-y-[1px] transition
hover:shadow-buttonHover active:translate-x-[4px] active:translate-y-[4px] active:shadow-none`,
{
variants: {
Expand Down
5 changes: 4 additions & 1 deletion components/shared/darkModeToggle/DarkModeToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ function DarkModeToggle() {
const { theme, toggleTheme } = useThemeStore()

return (
<button onClick={toggleTheme} aria-label="Skift mellem 'light mode' og 'dark mode'">
<button
onClick={toggleTheme}
aria-label="Skift mellem 'light mode' og 'dark mode'"
className="focus-visible rounded-full">
<Icon
className="h-[40px] hover:translate-x-[2px] hover:translate-y-[2px]"
name={theme === "light" ? "toggle-day" : "toggle-night"}
Expand Down
8 changes: 5 additions & 3 deletions components/shared/searchFilters/SearchFilterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React, { Suspense, useState } from "react"
import { SearchFacetFragment } from "@/lib/graphql/generated/fbi/graphql"

import SearchFiltersColumn from "./SearchFiltersColumn"
import SearchFiltersMobile from "./SearchFiltersMobile"

type SearchFilterBarProps = {
facets: SearchFacetFragment[]
Expand All @@ -15,8 +16,9 @@ const SearchFilterBar = ({ facets }: SearchFilterBarProps) => {

return (
<>
{/* TODO: add mobile filter functionality and UI */}
<div className="xl:hidden">Mobile Filters</div>
<div className="mt-3 xl:hidden">
<SearchFiltersMobile facets={facets} />
</div>
<div className="mt-10 hidden flex-row gap-4 xl:flex">
{facets.map((facet, index) => {
const isLast = index === facets.length - 1
Expand All @@ -32,7 +34,7 @@ const SearchFilterBar = ({ facets }: SearchFilterBarProps) => {
)
})}
</div>
<hr className="-mx-grid-edge my-3 w-screen border-black opacity-10 md:mx-auto md:mb-12 md:mt-6 md:w-full" />
<hr className="-mx-grid-edge mb-3 mt-8 w-screen border-black opacity-10 md:mx-auto md:mb-12 md:mt-6 md:w-full" />
</>
)
}
Expand Down
58 changes: 26 additions & 32 deletions components/shared/searchFilters/SearchFiltersColumn.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { useRouter, useSearchParams } from "next/navigation"
import React, { useEffect, useRef, useState } from "react"

import BadgeButton from "@/components/shared/badge/BadgeButton"
import Icon from "@/components/shared/icon/Icon"
import {
getFacetTranslation,
sortByActiveFacets,
toggleFilter,
} from "@/components/shared/searchFilters/helper"
import { SearchFacetFragment, SearchFiltersInput } from "@/lib/graphql/generated/fbi/graphql"
import { cn } from "@/lib/helpers/helper.cn"

import Icon from "../icon/Icon"
import { getFacetTranslation, sortByActiveFacets, toggleFilter } from "./helper"

type SearchFiltersColumnProps = {
facet: SearchFacetFragment
isLast: boolean
Expand Down Expand Up @@ -44,48 +48,38 @@ const SearchFiltersColumn = ({
<>
<div
key={facet.name}
className={cn(["relative", !isLast && "min-w-32 flex-1", isLast && "flex-2"])}>
<h3 className="mb-2 text-typo-caption uppercase">{getFacetTranslation(facetFilter)}</h3>
className={cn("relative ml-[-4px]", !isLast && "min-w-32 flex-1", isLast && "flex-2")}>
<h3 className="mb-2 pl-2 text-typo-caption uppercase">
{getFacetTranslation(facetFilter)}
</h3>
<div
className={cn([
"flex gap-1 text-typo-caption",
className={cn(
"flex gap-1 px-1 pt-2 text-typo-caption",
!isLast && "flex-col",
isLast && "flex-row flex-wrap",
!isExpanded && "h-[98px] overflow-hidden",
])}
isLast && "flex-row flex-wrap content-start",
!isExpanded && "h-[107px] overflow-hidden"
)}
ref={elementRef}>
{facet.values.map((value, index) => (
<button
<BadgeButton
onClick={() => toggleFilter(facet.name, value.term, router)}
className={cn([
`h-[29px] w-auto self-start whitespace-nowrap rounded-full bg-background-overlay px-4 py-2
hover:animate-wiggle`,
searchParams.getAll(facet.name).includes(value.term) &&
"bg-foreground text-background",
])}
isActive={!!searchParams.getAll(facet.name).includes(value.term)}
key={index}>
{value.term}
</button>
</BadgeButton>
))}
</div>
{hasOverflow && (
<div
className="h-9 w-9 cursor-pointer"
<BadgeButton
classNames={cn(`pl-3 w-auto flex flex-row items-center self-start ml-1`)}
onClick={() => {
setIsExpanded(prev => !prev)
}}>
<button
className={cn(
`flex h-[29px] w-auto flex-row items-center self-start whitespace-nowrap rounded-full
bg-background-overlay pl-2 pr-4 text-typo-caption hover:animate-wiggle`,
isExpanded && "mt-1"
)}>
<Icon className={cn("h-8 w-8", isExpanded && "rotate-180")} name="arrow-down" />
<p>
{!isExpanded && "Flere"} {isExpanded && "Skjul"}
</p>
</button>
</div>
<Icon className={cn("h-8 w-8", isExpanded ? "rotate-180" : "")} name="arrow-down" />
<p>
{!isExpanded && "Flere"} {isExpanded && "Skjul"}
</p>
</BadgeButton>
)}
</div>
</>
Expand Down
91 changes: 91 additions & 0 deletions components/shared/searchFilters/SearchFiltersMobile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { useRouter, useSearchParams } from "next/navigation"
import React, { useState } from "react"

import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/accordion/Accordion"
import BadgeButton from "@/components/shared/badge/BadgeButton"
import Icon from "@/components/shared/icon/Icon"
import {
getActiveFilters,
getFacetTranslation,
shouldShowActiveFilters,
toggleFilter,
} from "@/components/shared/searchFilters/helper"
import { Sheet, SheetContent, SheetTrigger } from "@/components/shared/sheet/Sheet"
import { SearchFacetFragment, SearchFiltersInput } from "@/lib/graphql/generated/fbi/graphql"

type SearchFiltersMobileProps = {
facets: SearchFacetFragment[]
}

const SearchFiltersMobile = ({ facets }: SearchFiltersMobileProps) => {
const router = useRouter()
const searchParams = useSearchParams()
const [isSheetOpen, setIsSheetOpen] = useState(false)

return (
<Sheet open={isSheetOpen} onOpenChange={setIsSheetOpen}>
<SheetTrigger
aria-label="Vis filtreringsmuligheder"
onClick={() => setIsSheetOpen(!isSheetOpen)}
className="flex flex-row items-center gap-1 pr-4 text-typo-link hover:bg-background-overlay">
<Icon name="adjust" className="h-[40px]" />
VIS FILTRE
</SheetTrigger>

{/* Show currently selected filters */}
{shouldShowActiveFilters(facets, searchParams) && (
<div className="flex flex-row flex-wrap gap-1 pt-2">
{getActiveFilters(facets, searchParams).map(facet => {
return facet.values.map(value => {
return (
<BadgeButton
onClick={() => {
toggleFilter(facet.name, value.term, router)
}}
key={value.term}
isActive
classNames="flex flex-row items-center pr-1">
{value.term}
<Icon name="close" className="w-[25px]" />
</BadgeButton>
)
})
})}
</div>
)}

<SheetContent className="w-full p-grid-edge pt-20" side="bottom">
<Accordion type="multiple" defaultValue={facets.map(facet => facet.name)}>
{facets.map(facet => {
const facetName = facet.name as keyof SearchFiltersInput
return (
<AccordionItem key={facetName} value={facetName}>
<AccordionTrigger>{getFacetTranslation(facetName)}</AccordionTrigger>
<AccordionContent className="flex flex-wrap gap-1">
{facet.values.map((value, index) => (
<BadgeButton
onClick={() => {
setIsSheetOpen(false)
toggleFilter(facet.name, value.term, router)
}}
isActive={!!searchParams.getAll(facet.name).includes(value.term)}
key={index}>
{value.term}
</BadgeButton>
))}
</AccordionContent>
</AccordionItem>
)
})}
</Accordion>
</SheetContent>
</Sheet>
)
}

export default SearchFiltersMobile
Loading

0 comments on commit f54b338

Please sign in to comment.