ref(scraps): compactSelect Triggers must be a specific SelectTrigger#105040
ref(scraps): compactSelect Triggers must be a specific SelectTrigger#105040
Conversation
| ); | ||
| }, | ||
|
|
||
| Input(props: InputTriggerProps) { |
There was a problem hiding this comment.
this is mostly just a showcase, I can also delete it for now.
There was a problem hiding this comment.
I deleted this for now and made the error message better by adding an invalid type to the union: 9234a5b
| trigger={triggerProps => ( | ||
| <OverflowMenuTrigger | ||
| {...triggerProps} | ||
| size="sm" |
There was a problem hiding this comment.
size now gets inherited from CompactSelect, which gets the same size passed
| isOpen={isOpen} | ||
| size={selectProps.size} |
There was a problem hiding this comment.
both props are now applied automatically from SelectContext
| size="sm" | ||
| disabled={isLoading} | ||
| trigger={triggerProps => ( | ||
| <Button | ||
| <SelectTrigger.Button | ||
| {...triggerProps} | ||
| size="sm" | ||
| showChevron={false} |
There was a problem hiding this comment.
I intentionally moved size up to CompositeSelect because it’s not consistent to have a sm button with a md menu. I checked in the product and it looks good.
| options={LOGIC_OPERATOR_OPTIONS} | ||
| trigger={triggerProps => { | ||
| return ( | ||
| // @ts-expect-error we don't allow arbitrary buttons as triggers |
There was a problem hiding this comment.
I can take a stab in a follow-up to try and get this to work with our buttons. Maybe a SelectTrigger.Unstyled would be a good idea too if we need this more often ?
| export interface DropdownButtonProps | ||
| extends Omit<ButtonProps, 'type' | 'prefix' | 'onClick'> { | ||
| export type DropdownButtonProps = DistributedOmit< | ||
| ButtonProps, | ||
| 'type' | 'prefix' | 'onClick' | ||
| > & { |
There was a problem hiding this comment.
Note: This is a classic type-level bug with Omit: ButtonProps is a discriminated union type, and Omit destroys that. So we had instances where we passed an icon, but no children and no aria-label, which the types tried to ensure.
DistributedOmit preserve the union type, but then again interfaces can’t extend union types, so I had to switch to types. This is the main reason why I think using type everywhere is better.
|
@TkDodo mind creating a design eng ticket for this, and mark it as done? I'm going to compile a changelog for beginning of next year, and I want to make sure we don't miss this |
JonasBa
left a comment
There was a problem hiding this comment.
I think this PR captures really well the notion of composability and level of restriction that a design system should be opinionated about. Awesome work, as always!
| export type SelectTriggerProps = React.HTMLAttributes<TriggerEl> & { | ||
| ref?: React.Ref<TriggerEl>; | ||
| }; | ||
|
|
||
| export type ButtonTriggerProps = React.ComponentPropsWithoutRef<typeof DropdownButton> & { | ||
| ref?: React.Ref<TriggerEl>; | ||
| }; | ||
|
|
||
| type InputTriggerProps = React.ComponentPropsWithoutRef<typeof Input> & { | ||
| ref?: React.Ref<TriggerEl>; | ||
| }; |
There was a problem hiding this comment.
Can we export props or use an interface here? These are the kinds of types that can really slow down the type checker
There was a problem hiding this comment.
can’t use an interface because ButtonProps is a union, which you can’t extend, and if I use DropdownButtonProps directly, I need to remove ref with DistributedOmit<DropdownButtonProps, 'ref'> which I don't think is better / faster.
| * component. | ||
| */ | ||
| triggerProps?: DropdownButtonProps; | ||
| triggerProps?: Partial<DropdownButtonProps>; |
There was a problem hiding this comment.
This might not seem like much, but it makes these components so much friendlier to work with
There was a problem hiding this comment.
I mainly got some errors here because the union is now respected, some props like aria-label or children became required all of a sudden, which isn’t what we want here.
by adding a union with a type that contains the descriptive error message
ref: https://linear.app/getsentry/issue/DE-668/streamline-compactselect-triggers
This PR disallows passing arbitrary components to
triggerofcompactSelecton type level. Everything that isn’tSelectTrigger.Button(or future SelectTriggers that we can add here) will produce a type error. This has a couple of advantages:SelectTrigger.Buttonis just a regularDropdownButton, however, we can ensure that we read from theSelectContextand therefore have access to thesize,disabledandopenstate of thecompactSelect. This ensures consistency, as we can’t forget to pass those along. Forgetting to passisOpenleads to the chevron not being inverted correctly. Passingsizeexplicitly is brittle. All these things work out of the box when usingtriggerProps, but not when passing a custom trigger. Note: The final goal here is to get rid oftriggerPropsand only use thetriggerprop.Input), it’s hard to get the typings right, mostly because ofrefs. It’s actually impossible without a type assertions (we’d need fragment refs, which are not out yet). This is now hidden insideSelectTrigger.ButtonThe implications are:
Buttoninstead of aDropdownButton, we now get the chevron shown unless we passshowChevron={false}. I did that for all the places I touched, it’s especially important for buttons that only have an icon where a second chevron icon would be out of place.Buttonbecause of the ref typings, so there was only one place, which already has ats-expect-error: TheBreadcrumbDropdown:sentry/static/app/views/settings/components/settingsBreadcrumb/breadcrumbDropdown.tsx
Lines 59 to 67 in 98b67ef
I refactored this towards a button in:
We should merge that one first, and then I’ll fix the conflicts here.
searchQueryBuilder. I don’t know why there is only one element that usescompactSelect, as everything else seems to just useListBoxdirectly. I tried to replicate the current look with atransparentbutton but I couldn’t quite get it to look identical, so I added ats-expect-errorfor that place.Naming is up for debate: I didn’t want to attach it to
CompactSelect.TriggerButtonbecause we also use it forCompositeSelect.