Skip to content

Commit

Permalink
fix(Select): expose generic types to allow by to pass type checks (#2008
Browse files Browse the repository at this point in the history
)

- borrow type value determination from HeadlessUI internals
- add test to make sure types used map to params
- chore: address PR suggestion
  • Loading branch information
booc0mtaco authored Jul 9, 2024
1 parent 661130b commit 421c91b
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 45 deletions.
14 changes: 14 additions & 0 deletions src/components/Select/Select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,20 @@ export const WithSelectedOption: StoryObj<typeof Select> = {
},
};

/**
* Use the `by` option to determine the selection (when using objects for the value list). This helps when you want to compare by value, not reference.
* - The type comparison can be by a named key in the object `by={'id'}` or using a comparison function
*
* See: https://headlessui.com/v1/react/listbox#listbox
*/
export const WithSelectedBy: StoryObj<typeof Select> = {
args: {
...WithSelectedOption.args,
defaultValue: { ...exampleOptions[1] },
by: 'key',
},
};

/**
* You can add a `name` prop to generate form fields for the value object.
*
Expand Down
12 changes: 9 additions & 3 deletions src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Listbox } from '@headlessui/react';
import { Listbox, type ListboxProps } from '@headlessui/react';
import clsx from 'clsx';

import React, {
useContext,
useState,
type ReactNode,
type MouseEventHandler,
type ElementType,
} from 'react';
import { createPortal } from 'react-dom';
import { usePopper } from 'react-popper';
Expand All @@ -25,7 +26,12 @@ import Text from '../Text';

import styles from './Select.module.css';

type SelectProps = ExtractProps<typeof Listbox> &
// https://github.com/tailwindlabs/headlessui/blob/%40headlessui/react%40v1.7.19/packages/%40headlessui-react/src/components/listbox/listbox.tsx#L349
type SelectProps = ListboxProps<
ElementType,
string | { [k: string]: unknown },
{ [k: string]: unknown }
> &
PopoverOptions & {
// Component API
/**
Expand Down Expand Up @@ -235,7 +241,7 @@ export function Select({
labelLayout && styles[`select--label-layout-${labelLayout}`],
className,
);
const sharedProps = {
const sharedProps: SelectProps = {
className: componentClassName,
// Provide a wrapping <div> element for the select. This is needed so that any props
// passed directly to this component have a corresponding DOM element to receive them.
Expand Down
134 changes: 92 additions & 42 deletions src/components/Select/__snapshots__/Select.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ exports[`<Select /> Generated Snapshots AdjustedWidth story renders snapshot 1`]
<label
class="label label--lg select__label"
data-headlessui-state="open"
id="headlessui-listbox-label-:r1k:"
id="headlessui-listbox-label-:r1q:"
>
Favorite Animal
</label>
</div>
<button
aria-controls="headlessui-listbox-options-:r1m:"
aria-controls="headlessui-listbox-options-:r1s:"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="headlessui-listbox-label-:r1k: headlessui-listbox-button-:r1l:"
aria-labelledby="headlessui-listbox-label-:r1q: headlessui-listbox-button-:r1r:"
class="select-button"
data-headlessui-state="open"
id="headlessui-listbox-button-:r1l:"
id="headlessui-listbox-button-:r1r:"
type="button"
>
<span
Expand Down Expand Up @@ -112,7 +112,7 @@ exports[`<Select /> Generated Snapshots Error story renders snapshot 1`] = `
<label
class="label label--lg select__label"
data-headlessui-state="open"
id="headlessui-listbox-label-:r26:"
id="headlessui-listbox-label-:r2c:"
>
Favorite Animal
</label>
Expand All @@ -123,13 +123,13 @@ exports[`<Select /> Generated Snapshots Error story renders snapshot 1`] = `
</span>
</div>
<button
aria-controls="headlessui-listbox-options-:r28:"
aria-controls="headlessui-listbox-options-:r2e:"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="headlessui-listbox-label-:r26: headlessui-listbox-button-:r27:"
aria-labelledby="headlessui-listbox-label-:r2c: headlessui-listbox-button-:r2d:"
class="select-button select-button--error"
data-headlessui-state="open"
id="headlessui-listbox-button-:r27:"
id="headlessui-listbox-button-:r2d:"
type="button"
>
<span
Expand Down Expand Up @@ -217,19 +217,19 @@ exports[`<Select /> Generated Snapshots MultipleWithTruncation story renders sna
<label
class="label label--lg select__label"
data-headlessui-state="open"
id="headlessui-listbox-label-:r1e:"
id="headlessui-listbox-label-:r1k:"
>
Favorite Animal(s)
</label>
</div>
<button
aria-controls="headlessui-listbox-options-:r1g:"
aria-controls="headlessui-listbox-options-:r1m:"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="headlessui-listbox-label-:r1e: headlessui-listbox-button-:r1f:"
aria-labelledby="headlessui-listbox-label-:r1k: headlessui-listbox-button-:r1l:"
class="select-button"
data-headlessui-state="open"
id="headlessui-listbox-button-:r1f:"
id="headlessui-listbox-button-:r1l:"
type="button"
>
<span
Expand Down Expand Up @@ -263,12 +263,12 @@ exports[`<Select /> Generated Snapshots NoVisibleLabel story renders snapshot 1`
data-testid="dropdown"
>
<button
aria-controls="headlessui-listbox-options-:r2j:"
aria-controls="headlessui-listbox-options-:r2p:"
aria-expanded="true"
aria-haspopup="listbox"
class="select-button"
data-headlessui-state="open"
id="headlessui-listbox-button-:r2i:"
id="headlessui-listbox-button-:r2o:"
type="button"
>
<span
Expand Down Expand Up @@ -306,17 +306,17 @@ exports[`<Select /> Generated Snapshots NoVisibleLabelButRequired story renders
<label
class="label label--lg select__label"
data-headlessui-state="open"
id="headlessui-listbox-label-:r2n:"
id="headlessui-listbox-label-:r2t:"
/>
</div>
<button
aria-controls="headlessui-listbox-options-:r2p:"
aria-controls="headlessui-listbox-options-:r2v:"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="headlessui-listbox-label-:r2n: headlessui-listbox-button-:r2o:"
aria-labelledby="headlessui-listbox-label-:r2t: headlessui-listbox-button-:r2u:"
class="select-button"
data-headlessui-state="open"
id="headlessui-listbox-button-:r2o:"
id="headlessui-listbox-button-:r2u:"
type="button"
>
<span
Expand Down Expand Up @@ -354,7 +354,7 @@ exports[`<Select /> Generated Snapshots Optional story renders snapshot 1`] = `
<label
class="label label--lg select__label"
data-headlessui-state="open"
id="headlessui-listbox-label-:r20:"
id="headlessui-listbox-label-:r26:"
>
Favorite Animal
</label>
Expand All @@ -365,13 +365,13 @@ exports[`<Select /> Generated Snapshots Optional story renders snapshot 1`] = `
</span>
</div>
<button
aria-controls="headlessui-listbox-options-:r22:"
aria-controls="headlessui-listbox-options-:r28:"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="headlessui-listbox-label-:r20: headlessui-listbox-button-:r21:"
aria-labelledby="headlessui-listbox-label-:r26: headlessui-listbox-button-:r27:"
class="select-button"
data-headlessui-state="open"
id="headlessui-listbox-button-:r21:"
id="headlessui-listbox-button-:r27:"
type="button"
>
<span
Expand Down Expand Up @@ -409,7 +409,7 @@ exports[`<Select /> Generated Snapshots Required story renders snapshot 1`] = `
<label
class="label label--lg select__label"
data-headlessui-state="open"
id="headlessui-listbox-label-:r1q:"
id="headlessui-listbox-label-:r20:"
>
Favorite Animal
</label>
Expand All @@ -420,13 +420,13 @@ exports[`<Select /> Generated Snapshots Required story renders snapshot 1`] = `
</span>
</div>
<button
aria-controls="headlessui-listbox-options-:r1s:"
aria-controls="headlessui-listbox-options-:r22:"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="headlessui-listbox-label-:r1q: headlessui-listbox-button-:r1r:"
aria-labelledby="headlessui-listbox-label-:r20: headlessui-listbox-button-:r21:"
class="select-button"
data-headlessui-state="open"
id="headlessui-listbox-button-:r1r:"
id="headlessui-listbox-button-:r21:"
type="button"
>
<span
Expand Down Expand Up @@ -459,12 +459,12 @@ exports[`<Select /> Generated Snapshots StyledUncontrolled story renders snapsho
data-testid="dropdown"
>
<button
aria-controls="headlessui-listbox-options-:r1a:"
aria-controls="headlessui-listbox-options-:r1g:"
aria-expanded="true"
aria-haspopup="listbox"
class="select-button"
data-headlessui-state="open"
id="headlessui-listbox-button-:r19:"
id="headlessui-listbox-button-:r1f:"
type="button"
>
<span
Expand Down Expand Up @@ -497,12 +497,12 @@ exports[`<Select /> Generated Snapshots UncontrolledHeadless story renders snaps
data-testid="dropdown"
>
<button
aria-controls="headlessui-listbox-options-:r15:"
aria-controls="headlessui-listbox-options-:r1b:"
aria-expanded="true"
aria-haspopup="listbox"
class="fpo"
data-headlessui-state="open"
id="headlessui-listbox-button-:r14:"
id="headlessui-listbox-button-:r1a:"
type="button"
>
🐶🐕🐩
Expand All @@ -522,7 +522,7 @@ exports[`<Select /> Generated Snapshots Warning story renders snapshot 1`] = `
<label
class="label label--lg select__label"
data-headlessui-state="open"
id="headlessui-listbox-label-:r2c:"
id="headlessui-listbox-label-:r2i:"
>
Favorite Animal
</label>
Expand All @@ -533,13 +533,13 @@ exports[`<Select /> Generated Snapshots Warning story renders snapshot 1`] = `
</span>
</div>
<button
aria-controls="headlessui-listbox-options-:r2e:"
aria-controls="headlessui-listbox-options-:r2k:"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="headlessui-listbox-label-:r2c: headlessui-listbox-button-:r2d:"
aria-labelledby="headlessui-listbox-label-:r2i: headlessui-listbox-button-:r2j:"
class="select-button select-button--warning"
data-headlessui-state="open"
id="headlessui-listbox-button-:r2d:"
id="headlessui-listbox-button-:r2j:"
type="button"
>
<span
Expand Down Expand Up @@ -577,19 +577,19 @@ exports[`<Select /> Generated Snapshots WithFieldName story renders snapshot 1`]
<label
class="label label--lg select__label"
data-headlessui-state="open"
id="headlessui-listbox-label-:ro:"
id="headlessui-listbox-label-:ru:"
>
Favorite Animal
</label>
</div>
<button
aria-controls="headlessui-listbox-options-:rq:"
aria-controls="headlessui-listbox-options-:r10:"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="headlessui-listbox-label-:ro: headlessui-listbox-button-:rp:"
aria-labelledby="headlessui-listbox-label-:ru: headlessui-listbox-button-:rv:"
class="select-button"
data-headlessui-state="open"
id="headlessui-listbox-button-:rp:"
id="headlessui-listbox-button-:rv:"
type="button"
>
<span
Expand Down Expand Up @@ -627,19 +627,19 @@ exports[`<Select /> Generated Snapshots WithFieldNote story renders snapshot 1`]
<label
class="label label--lg select__label"
data-headlessui-state="open"
id="headlessui-listbox-label-:ru:"
id="headlessui-listbox-label-:r14:"
>
Favorite Animal
</label>
</div>
<button
aria-controls="headlessui-listbox-options-:r10:"
aria-controls="headlessui-listbox-options-:r16:"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="headlessui-listbox-label-:ru: headlessui-listbox-button-:rv:"
aria-labelledby="headlessui-listbox-label-:r14: headlessui-listbox-button-:r15:"
class="select-button"
data-headlessui-state="open"
id="headlessui-listbox-button-:rv:"
id="headlessui-listbox-button-:r15:"
type="button"
>
<span
Expand All @@ -665,6 +665,56 @@ exports[`<Select /> Generated Snapshots WithFieldNote story renders snapshot 1`]
</div>
`;

exports[`<Select /> Generated Snapshots WithSelectedBy story renders snapshot 1`] = `
<div
class="select select--label-layout-vertical w-60"
data-headlessui-state="open"
data-testid="dropdown"
>
<div
class="select__overline"
>
<label
class="label label--lg select__label"
data-headlessui-state="open"
id="headlessui-listbox-label-:ro:"
>
Favorite Animal
</label>
</div>
<button
aria-controls="headlessui-listbox-options-:rq:"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="headlessui-listbox-label-:ro: headlessui-listbox-button-:rp:"
class="select-button"
data-headlessui-state="open"
id="headlessui-listbox-button-:rp:"
type="button"
>
<span
class=""
>
Cats
</span>
<svg
aria-hidden="true"
class="icon select-button__icon select-button__icon--reversed"
fill="currentColor"
height="1.5rem"
style="--icon-size: 1.5rem;"
viewBox="0 0 24 24"
width="1.5rem"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.88 9.29L12 13.17 8.12 9.29c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41l4.59 4.59c.39.39 1.02.39 1.41 0l4.59-4.59c.39-.39.39-1.02 0-1.41-.39-.38-1.03-.39-1.42 0z"
/>
</svg>
</button>
</div>
`;

exports[`<Select /> Generated Snapshots WithSelectedOption story renders snapshot 1`] = `
<div
class="select select--label-layout-vertical w-60"
Expand Down

0 comments on commit 421c91b

Please sign in to comment.