Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UXIT-1428 - Headless UI v2 component refactor #701

Merged
merged 12 commits into from
Oct 16, 2024
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"prepare": "husky"
},
"dependencies": {
"@headlessui/react": "^2.1.2",
"@headlessui/react": "^2.1.10",
"@headlessui/tailwindcss": "^0.2.1",
"@hookform/resolvers": "^3.9.0",
"@octokit/rest": "^21.0.1",
Expand Down
34 changes: 15 additions & 19 deletions src/app/_components/CategoryListbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,21 @@ export function CategoryListbox({

return (
<Listbox value={selected} onChange={onChange}>
{({ open }) => (
<>
<ListboxButton ariaLabel="Category options" open={open}>
<span>Category</span>
<Icon component={CaretDown} size={16} weight="bold" />
</ListboxButton>
<ListboxOptions>
{totalCategoryCount && (
<ListboxOption
option={{ id: DEFAULT_CATEGORY, name: DEFAULT_CATEGORY }}
counts={totalCategoryCount}
/>
)}
{options.map((option) => (
<ListboxOption key={option.id} option={option} counts={counts} />
))}
</ListboxOptions>
</>
)}
<ListboxButton>
<span>Category</span>
<Icon component={CaretDown} size={16} weight="bold" />
</ListboxButton>
<ListboxOptions>
{totalCategoryCount && (
<ListboxOption
option={{ id: DEFAULT_CATEGORY, name: DEFAULT_CATEGORY }}
counts={totalCategoryCount}
/>
)}
{options.map((option) => (
<ListboxOption key={option.id} option={option} counts={counts} />
))}
</ListboxOptions>
</Listbox>
)
}
19 changes: 4 additions & 15 deletions src/app/_components/ListboxButton.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
import React from 'react'

import { Listbox } from '@headlessui/react'
import { ListboxButton as HeadlessUIListboxButton } from '@headlessui/react'

type ListboxButtonProps = {
ariaLabel: string
open: boolean
children: React.ReactNode
}

export function ListboxButton({
ariaLabel,
open,
children,
}: ListboxButtonProps) {
export function ListboxButton({ children }: ListboxButtonProps) {
return (
<Listbox.Button
aria-haspopup="listbox"
aria-expanded={open}
aria-label={ariaLabel}
className="focus:brand-outline inline-flex w-full items-center justify-between gap-2 rounded-lg border border-brand-300 p-3 text-brand-300 hover:border-current hover:text-brand-400 md:min-w-40"
>
<HeadlessUIListboxButton className="inline-flex w-full items-center justify-between gap-2 rounded-lg border border-brand-300 p-3 text-brand-300 focus:brand-outline hover:border-current hover:text-brand-400 md:min-w-40">
{children}
</Listbox.Button>
</HeadlessUIListboxButton>
)
}
28 changes: 9 additions & 19 deletions src/app/_components/ListboxOption.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Fragment } from 'react'

import { Listbox } from '@headlessui/react'
import { ListboxOption as HeadlessUIListboxOption } from '@headlessui/react'
import { Check } from '@phosphor-icons/react'
import { clsx } from 'clsx'

import { type CategoryCounts } from '@/types/categoryTypes'

Expand Down Expand Up @@ -31,22 +30,13 @@ function OptionContent({ option, counts }: ListboxOptionProps) {

export function ListboxOption({ option, counts }: ListboxOptionProps) {
return (
<Listbox.Option value={option.id} as={Fragment}>
{({ active, selected }) => (
<li
className={clsx(
'flex cursor-default items-center justify-between gap-12 px-5 py-2',
{ 'bg-brand-500': active, 'bg-transparent': !active },
)}
>
<OptionContent option={option} counts={counts} />
{selected && (
<span className="mb-px">
<Icon component={Check} size={20} />
</span>
)}
</li>
)}
</Listbox.Option>
<HeadlessUIListboxOption value={option.id} as={Fragment}>
<li className="group flex cursor-default items-center justify-between gap-12 bg-transparent px-5 py-2 data-[focus]:bg-brand-500">
<OptionContent option={option} counts={counts} />
<span className="mb-px group-data-[selected]:visible">
<Icon component={Check} size={20} />
</span>
</li>
</HeadlessUIListboxOption>
)
}
7 changes: 3 additions & 4 deletions src/app/_components/ListboxOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Listbox } from '@headlessui/react'
import { ListboxOptions as HeadlessUIListboxOptions } from '@headlessui/react'
import { clsx } from 'clsx'

type ListboxOptionsProps = {
Expand All @@ -13,14 +13,13 @@ export function ListboxOptions({
const positionClass = position === 'right' ? 'right-6 md:right-auto' : ''

return (
<Listbox.Options
aria-labelledby="listbox-button"
<HeadlessUIListboxOptions
className={clsx(
'absolute z-10 mt-2 overflow-hidden rounded-lg border border-brand-100 bg-brand-800 py-2 text-brand-100 focus:brand-outline focus-within:outline-2',
positionClass,
)}
>
{children}
</Listbox.Options>
</HeadlessUIListboxOptions>
)
}
60 changes: 30 additions & 30 deletions src/app/_components/NavigationPopover.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client'

import { cloneElement, Fragment } from 'react'
import { cloneElement } from 'react'

import { Popover, Transition } from '@headlessui/react'
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
import { CaretDown } from '@phosphor-icons/react'

import { Icon } from '@/components/Icon'
Expand All @@ -14,14 +14,8 @@ type PopOverProps = {
children: React.ReactElement
}

const TransitionProps = {
enter: 'transition ease-out duration-200',
enterFrom: 'opacity-0 translate-y-1',
enterTo: 'opacity-100 translate-y-0',
leave: 'transition ease-in duration-150',
leaveFrom: 'opacity-100 translate-y-0',
leaveTo: 'opacity-0 translate-y-1',
}
const SPACE_BETWEEN_PANEL_AND_BUTTON = 24
const SPACE_BETWEEN_PANEL_AND_VIEWPORT = 8

export function NavigationPopover({
label,
Expand All @@ -31,33 +25,39 @@ export function NavigationPopover({
}: PopOverProps) {
return (
<Popover as={as}>
<Popover.Button
<PopoverButton
aria-label={`${label} (opens a navigation menu)`}
className={mainNavItemStyles}
>
<span>{label}</span>
<span className="transition-transform ui-open:rotate-180">
<Icon component={CaretDown} size={20} color="brand-400" />
</span>
</Popover.Button>
<Popover.Overlay className="fixed inset-0 -z-10" />
<Transition as={Fragment} {...TransitionProps}>
<Popover.Panel className="absolute right-0 z-10 mt-6 xl:-right-6">
{(props) => {
const clonedChildren = cloneElement(children, {
onClick: function closeOnClickWithin() {
props.close()
},
})

return (
<div className="overflow-hidden rounded-2xl border border-brand-500 bg-brand-800 p-4">
{clonedChildren}
</div>
)
}}
</Popover.Panel>
</Transition>
</PopoverButton>

<PopoverPanel
transition
className="z-10 transition duration-200 ease-out data-[closed]:translate-y-1 data-[open]:translate-y-0 data-[closed]:opacity-0 data-[open]:opacity-100"
anchor={{
to: 'bottom',
gap: SPACE_BETWEEN_PANEL_AND_BUTTON,
padding: SPACE_BETWEEN_PANEL_AND_VIEWPORT,
}}
>
{(props) => {
const clonedChildren = cloneElement(children, {
onClick: function closeOnClickWithin() {
props.close()
},
})

return (
<div className="overflow-hidden rounded-2xl border border-brand-500 bg-brand-800 p-4">
{clonedChildren}
</div>
)
}}
</PopoverPanel>
</Popover>
)
}
31 changes: 19 additions & 12 deletions src/app/_components/SlideOver.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { Fragment } from 'react'

import { Dialog, Transition } from '@headlessui/react'
import {
Dialog,
DialogPanel,
DialogBackdrop,
Transition,
TransitionChild,
type DialogProps,
} from '@headlessui/react'

type SlideOverProps = {
open: boolean
setOpen: (open: boolean) => void
open: DialogProps['open']
setOpen: DialogProps['onClose']
children: React.ReactNode
}

export function SlideOver({ open, setOpen, children }: SlideOverProps) {
return (
<Transition.Root show={open} as={Fragment}>
<Transition show={open} as={Fragment}>
<Dialog className="relative z-10" onClose={setOpen}>
<Transition.Child
<TransitionChild
CharlyMartin marked this conversation as resolved.
Show resolved Hide resolved
as={Fragment}
enter="ease-in-out duration-500 sm:duration-700"
enterFrom="opacity-0"
Expand All @@ -21,13 +28,13 @@ export function SlideOver({ open, setOpen, children }: SlideOverProps) {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 backdrop-blur-lg" />
</Transition.Child>
<DialogBackdrop className="fixed inset-0 backdrop-blur-lg" />
</TransitionChild>

<div className="fixed inset-0 overflow-hidden">
<div className="absolute inset-0 overflow-hidden">
<div className="pointer-events-none fixed inset-y-0 right-0 flex w-full max-w-[480px]">
<Transition.Child
<TransitionChild
as={Fragment}
enter="transform transition ease-in-out duration-500 sm:duration-700"
enterFrom="translate-x-full"
Expand All @@ -36,16 +43,16 @@ export function SlideOver({ open, setOpen, children }: SlideOverProps) {
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Dialog.Panel className="pointer-events-auto w-full">
<DialogPanel className="pointer-events-auto w-full">
<div className="flex h-full flex-col overflow-y-scroll bg-brand-800">
{children}
</div>
</Dialog.Panel>
</Transition.Child>
</DialogPanel>
</TransitionChild>
</div>
</div>
</div>
</Dialog>
</Transition.Root>
</Transition>
)
}
36 changes: 16 additions & 20 deletions src/app/_components/SortListbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,22 @@ export function SortListbox({ sortId, onChange, options }: SortListboxProps) {

return (
<Listbox value={sortId} onChange={onChange}>
{({ open }) => (
<>
<ListboxButton ariaLabel="Sort options" open={open}>
<div className="inline-flex items-center gap-2">
<Icon component={ArrowsDownUp} />
<span className="hidden text-nowrap md:block">
{selectedOption.name}
</span>
</div>
<span className="hidden md:block">
<Icon component={CaretDown} size={16} weight="bold" />
</span>
</ListboxButton>
<ListboxOptions position="right">
{options.map((option) => (
<ListboxOption key={option.id} option={option} />
))}
</ListboxOptions>
</>
)}
<ListboxButton>
<div className="inline-flex items-center gap-2">
<Icon component={ArrowsDownUp} />
<span className="hidden text-nowrap md:block">
{selectedOption.name}
</span>
</div>
<span className="hidden md:block">
<Icon component={CaretDown} size={16} weight="bold" />
</span>
</ListboxButton>
<ListboxOptions position="right">
{options.map((option) => (
<ListboxOption key={option.id} option={option} />
))}
</ListboxOptions>
</Listbox>
)
}
Loading