refactor: update SidePeek composition

This commit is contained in:
Arvid Norlin
2024-08-12 13:27:03 +02:00
parent 04eb3c6d94
commit 68694ef914
7 changed files with 77 additions and 105 deletions

View File

@@ -1,55 +0,0 @@
"use client"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import React, { Children, cloneElement, useEffect, useState } from "react"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { SidePeekContentKey } from "@/components/TempDesignSystem/SidePeek/types"
export default function SidePeekContainer({
children,
}: React.PropsWithChildren) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const [activeSidePeek, setActiveSidePeek] =
useState<SidePeekContentKey | null>(() => {
const sidePeekParam = searchParams.get(
"open"
) as SidePeekContentKey | null
return sidePeekParam || null
})
useEffect(() => {
const sidePeekParam = searchParams.get("open") as SidePeekContentKey | null
if (sidePeekParam !== activeSidePeek) {
setActiveSidePeek(sidePeekParam)
}
}, [searchParams, activeSidePeek])
function handleClose(isOpen: boolean) {
if (!isOpen) {
setActiveSidePeek(null)
const nextSearchParams = new URLSearchParams(searchParams.toString())
nextSearchParams.delete("open")
router.push(`${pathname}?${nextSearchParams}`, { scroll: false })
}
}
const childrenWithIsActive = Children.map(children, (child) => {
if (!React.isValidElement(child)) {
return child
}
return cloneElement(child as React.ReactElement, {
isActive:
(child.props.contentKey as SidePeekContentKey) === activeSidePeek,
})
})
return (
<SidePeek activeContent={activeSidePeek} onClose={handleClose}>
{childrenWithIsActive}
</SidePeek>
)
}

View File

@@ -1,6 +1,6 @@
"use client"
import { Children, PropsWithChildren } from "react"
import { PropsWithChildren } from "react"
import { CloseIcon } from "@/components/Icons"
import { SidePeekContentProps } from "@/components/TempDesignSystem/SidePeek/types"
@@ -8,16 +8,16 @@ import Title from "@/components/TempDesignSystem/Text/Title"
import Button from "../../Button"
import styles from "./content.module.css"
import styles from "./sidePeekItem.module.css"
export default function Content({
function SidePeekItem({
title,
children,
isActive = false,
onClose,
}: PropsWithChildren<SidePeekContentProps>) {
return isActive ? (
<aside className={styles.content}>
<aside className={styles.sidePeekItem}>
<header className={styles.header}>
<Title color="burgundy" textTransform="uppercase" level="h2" as="h3">
{title}
@@ -35,3 +35,5 @@ export default function Content({
</aside>
) : null
}
export default SidePeekItem

View File

@@ -1,11 +1,11 @@
.content {
.sidePeekItem {
display: grid;
grid-template-rows: min-content auto;
gap: var(--Spacing-x4);
height: 100%;
}
.content > * {
.content>* {
padding: var(--Spacing-x3) var(--Spacing-x2);
}
@@ -20,8 +20,8 @@
justify-content: space-between;
}
@media and screen (min-width: 1367px) {
.content > * {
@media screen and (min-width: 1367px) {
.content>* {
padding: var(--Spacing-x4);
}
}
}

View File

@@ -1,7 +1,8 @@
"use client"
import { useIsSSR } from "@react-aria/ssr"
import { Children, cloneElement, PropsWithChildren } from "react"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import React, { Children, cloneElement, useEffect, useState } from "react"
import {
Dialog,
DialogTrigger,
@@ -9,15 +10,54 @@ import {
ModalOverlay,
} from "react-aria-components"
import { SidePeekProps } from "./types"
import { SidePeekContentKey } from "@/components/TempDesignSystem/SidePeek/types"
import styles from "./sidePeek.module.css"
export default function SidePeek({
children,
onClose,
activeContent,
}: PropsWithChildren<SidePeekProps>) {
function SidePeek({ children }: React.PropsWithChildren) {
// TODO: to make this more reusable,
// should the param logic be moved up/out so this component can be
// controlled by e.g. URL segments
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const [activeSidePeek, setActiveSidePeek] =
useState<SidePeekContentKey | null>(() => {
const sidePeekParam = searchParams.get(
"open"
) as SidePeekContentKey | null
return sidePeekParam || null
})
useEffect(() => {
const sidePeekParam = searchParams.get("open") as SidePeekContentKey | null
if (sidePeekParam !== activeSidePeek) {
setActiveSidePeek(sidePeekParam)
}
}, [searchParams, activeSidePeek])
function handleClose(isOpen: boolean) {
if (!isOpen) {
setActiveSidePeek(null)
const nextSearchParams = new URLSearchParams(searchParams.toString())
nextSearchParams.delete("open")
router.push(`${pathname}?${nextSearchParams}`, { scroll: false })
}
}
const sidePeekChildren = Children.map(children, (child) => {
if (!React.isValidElement(child)) {
return child
}
return cloneElement(child as React.ReactElement, {
isActive:
(child.props.contentKey as SidePeekContentKey) === activeSidePeek,
onClose: handleClose,
})
})
const isSSR = useIsSSR()
return isSSR ? (
<div>{children}</div>
@@ -25,24 +65,16 @@ export default function SidePeek({
<DialogTrigger>
<ModalOverlay
className={styles.overlay}
isOpen={!!activeContent}
onOpenChange={onClose}
isOpen={!!activeSidePeek}
onOpenChange={handleClose}
isDismissable
>
<Modal className={styles.sidePeek}>
<Dialog className={styles.dialog}>
{({ close }) => (
<>
{Children.map(children, (child) => {
return cloneElement(child as React.ReactElement, {
onClose: close,
})
})}
</>
)}
</Dialog>
<Dialog className={styles.dialog}>{sidePeekChildren}</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
)
}
export default SidePeek