tailwindlabs / headlessui

Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.
https://headlessui.com
MIT License
25.81k stars 1.07k forks source link

Add value type generic to ComboboxOptionsProp #3394

Open kradical opened 2 months ago

kradical commented 2 months ago

Hi I'm back :wave:, another small typescript tweak, no functionality change.

Summary

Adding a generic here lets consuming code specify a type for an option. This is useful when operating in virtual mode and passing a callback as a child. To get it to be well typed consumers would do something like:

type Person = { name: string };

<ComboboxOptions<'div', Person>>
  {({ option }) => <div>{option.name}</div>}
</ComboboxOptions>

Getting nice strong typing on option. Slightly nicer to specify a generic type then force a cast in the callback imo. Also in situations where somebody is combining multiple combobox components in some generic component they can very logically apply TValue here and in other places too.

Notes

I defaulted this new generic type to unknown which is definitely a type "breaking" change. someone upgrading will possibly experience a compiler error. As the old type for option was any. imo unknown is definitely the correct choice here to force the consumer to make a decision on what their thing is.. but I can also appreciate not wanting to break backwards compatibility. So feel free to update it to default to any (in both places) or I can, if that is what you want.

We have this in our codebase right now:

/**
 * Slightly better version of headless ui's internal OptionsRenderPropArg type. This way we can
 * provide a generic type parameter
 */
interface ComboboxOptionRenderProps<T> {
  open: boolean;
  option: T;
}
type ComboboxOptionsProps<TValue = unknown> = Omit<
  HeadlessComboboxOptionsProps,
  "children"
> & {
  children?:
    | ReactNode
    | ((bag: ComboboxOptionRenderProps<TValue>) => ReactElement);
};

And it would be pretty nice to support this in headlessui-react! thank you!

vercel[bot] commented 2 months ago

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
headlessui-react ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 25, 2024 4:39pm
headlessui-vue ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 25, 2024 4:39pm
RobinMalfait commented 2 months ago

Hey!

Thanks for the PR, I think we should use any instead of unknown because we recently changed it such that users can override the option in the render prop (see: https://github.com/tailwindlabs/headlessui/pull/3327)

The additional type is definitely a breaking change because the default value will only work if you don't pass anything. So if you currently have ComboboxOptionsProps<'div'> then you will get a type error. (Or maybe that's only in older versions of TypeScript now thta I think about it)

What do you think about this (breaking) change, @thecrypticace?

I don't hate this change honestly, but the only thing that might not be as ideal is that the type exists but it is only used when in virtual mode, which is why I like typing it inside of the render prop function directly. But I can be convinced of this change though!

kradical commented 2 months ago

Sounds good RE: any vs. unknown, thanks for the link. Edit: updated PR with any

The additional generic type should be fine for backwards compatibility, it'll pick up the default "any" in the situation where a consumer has ComboboxOptionsProps<'thing'>. See this ts playground: https://www.typescriptlang.org/play/?#code/C4TwDgpgBAKg7gewOIQHYQE4EsDGBnAHhigF4oByAEywDdyAaKAVVKgENUQA+VgbwCgoQ9gC5Y9QcIBGYphIC+-fjgSo8wKMAjqAjGPjI0mXHj6iK1OoxlQdAJgDMUeQG5lq9Zu3A7+xCnRsfAJyVARgS3IeMl5zUPDI6zF7J1d3NQ0tdQc-Q0CTELCI2gYoVABXAFspTABtAF1oqFi2MXjiqygbWpT65zcgA

interesting, I see what you're saying. I think long term unifying the usage pattern between virtual and non virtual could be good. Or maybe allow non-virtual combobox'es to pass options + template function instead of rendering option nodes. But I'm totally content with how it works now.

kradical commented 1 month ago

Hey @RobinMalfait I was wondering if you had a chance to review my updates? I swapped it to default to any so it should be completely backwards compatible (as I kind of showed in the typescript playground linked above).

mktcode commented 1 month ago

Already closed but I'm linking this issue, because it's what I found first and almost forgot to check the PRs. https://github.com/tailwindlabs/headlessui/issues/1332