Skip to content

Commit

Permalink
add keyboard navigation support to Listbox
Browse files Browse the repository at this point in the history
  • Loading branch information
therealchrisrock committed Feb 3, 2024
1 parent a266436 commit 685c271
Showing 1 changed file with 72 additions and 61 deletions.
133 changes: 72 additions & 61 deletions templates/demo-store/app/routes/($locale).products.$productHandle.tsx
@@ -1,8 +1,12 @@
import {useRef, Suspense} from 'react';
import {Suspense, Fragment, useState} from 'react';
import {Disclosure, Listbox} from '@headlessui/react';
import {defer, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {useLoaderData, Await} from '@remix-run/react';
import type {ShopifyAnalyticsProduct} from '@shopify/hydrogen';
import {useLoaderData, Await, useNavigate} from '@remix-run/react';
import type {
ShopifyAnalyticsProduct,
VariantOption,
VariantOptionValue,
} from '@shopify/hydrogen';
import {
AnalyticsPageType,
Money,
Expand Down Expand Up @@ -215,8 +219,6 @@ export function ProductForm({
}) {
const {product, analytics, storeDomain} = useLoaderData<typeof loader>();

const closeRef = useRef<HTMLButtonElement>(null);

/**
* Likewise, we're defaulting to the first variant for purposes
* of add to cart if there is none returned from the loader.
Expand Down Expand Up @@ -254,62 +256,7 @@ export function ProductForm({
</Heading>
<div className="flex flex-wrap items-baseline gap-4">
{option.values.length > 7 ? (
<div className="relative w-full">
<Listbox>
{({open}) => (
<>
<Listbox.Button
ref={closeRef}
className={clsx(
'flex items-center justify-between w-full py-3 px-4 border border-primary',
open
? 'rounded-b md:rounded-t md:rounded-b-none'
: 'rounded',
)}
>
<span>{option.value}</span>
<IconCaret direction={open ? 'up' : 'down'} />
</Listbox.Button>
<Listbox.Options
className={clsx(
'border-primary bg-contrast absolute bottom-12 z-30 grid h-48 w-full overflow-y-scroll rounded-t border px-2 py-2 transition-[max-height] duration-150 sm:bottom-auto md:rounded-b md:rounded-t-none md:border-t-0 md:border-b',
open ? 'max-h-48' : 'max-h-0',
)}
>
{option.values
.filter((value) => value.isAvailable)
.map(({value, to, isActive}) => (
<Listbox.Option
key={`option-${option.name}-${value}`}
value={value}
>
{({active}) => (
<Link
to={to}
className={clsx(
'text-primary w-full p-2 transition rounded flex justify-start items-center text-left cursor-pointer',
active && 'bg-primary/10',
)}
onClick={() => {
if (!closeRef?.current) return;
closeRef.current.click();
}}
>
{value}
{isActive && (
<span className="ml-2">
<IconCheck />
</span>
)}
</Link>
)}
</Listbox.Option>
))}
</Listbox.Options>
</>
)}
</Listbox>
</div>
<ProductListbox option={option} />
) : (
option.values.map(({value, isAvailable, isActive, to}) => (
<Link
Expand Down Expand Up @@ -390,6 +337,70 @@ export function ProductForm({
);
}

function ProductListbox({option}: {option: VariantOption}) {
const [selectedOption, setSelectedOption] = useState({
value: option.value,
to: '',
});
const navigate = useNavigate();
const handleSelection = (value: Pick<VariantOptionValue, 'value' | 'to'>) => {
setSelectedOption(value);
navigate(value.to);
};

return (
<div className="relative w-full">
<Listbox value={selectedOption} onChange={handleSelection}>
{({open}) => (
<>
<Listbox.Button
className={clsx(
'flex items-center justify-between w-full py-3 px-4 border border-primary',
open ? 'rounded-b md:rounded-t md:rounded-b-none' : 'rounded',
)}
>
<span>{selectedOption.value}</span>
<IconCaret direction={open ? 'up' : 'down'} />
</Listbox.Button>
<Listbox.Options
className={clsx(
'border-primary bg-contrast absolute bottom-12 z-30 grid h-48 w-full overflow-y-scroll rounded-t border px-2 py-2 transition-[max-height] duration-150 sm:bottom-auto md:rounded-b md:rounded-t-none md:border-t-0 md:border-b',
open ? 'max-h-48' : 'max-h-0',
)}
>
{option.values
.filter((value) => value.isAvailable)
.map(({value, to, isActive}) => (
<Listbox.Option
as={Fragment}
key={`option-${option.name}-${value}`}
value={{value, to}}
>
{({active}) => (
<li
className={clsx(
'text-primary w-full p-2 transition rounded flex justify-start items-center text-left cursor-pointer',
active && 'bg-primary/10',
)}
>
{value}
{isActive && (
<span className="ml-2">
<IconCheck />
</span>
)}
</li>
)}
</Listbox.Option>
))}
</Listbox.Options>
</>
)}
</Listbox>
</div>
);
}

function ProductDetail({
title,
content,
Expand Down

0 comments on commit 685c271

Please sign in to comment.