-
Notifications
You must be signed in to change notification settings - Fork 23
[KZN-3415] uses native popover api in SingleSelect #5885
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
base: main
Are you sure you want to change the base?
Conversation
|
onSelectionChange={(key) => | ||
handleOnSelectionChange(key != null ? new Set([key]) : new Set()) | ||
} | ||
className={classNameOverride} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One this I noticed was that we're using classNameOverride
here instead of className
. While this was the pattern for our older components, for recent ones like Button
, Link
and LinkButton
we've allowed for className
to be used so folks can leverage the RACs render props (see RAC styling section).
Looking at some of the sub-components that use RAC like ListItem
they have className
available so was just wondering if we are selectively choosing which ones have access to this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for calling this out 😄 I never remember whether classNameOverride
is new or old pattern given that it's still being used in some of our next components. I've removed it now and written myself a note 🧠
interface TriggerProps { | ||
buttonRef: React.RefObject<HTMLButtonElement> | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that were using an interface
for TriggerProps
and was wandering if there's a specifc reason for chosing this over type
?
In the past we made a decision to lean on type
over interface
unless we foresaw the need to extend / merge a type. The main reasoning for where we landed was to simplify the decision process of whether something should be an interface
or type
, which has lead to most of our codebase using type
.
@@ -0,0 +1,9 @@ | |||
export type SelectItem = { | |||
label: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question on the types here, is the item always going to have a string label
or is will it need to be a React node / element to be able handle Avatars, icons and such?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely not set in stone, and will most likely change. My thoughts are that we would support a Key: number or string. Passing in items will be mostly used for handling state and async behaviour. If users want to render a bespoke item they can do so by mapping over their own ListItem
and passing it as children.
@@ -12,7 +13,9 @@ export default meta | |||
type Story = StoryObj<typeof meta> | |||
|
|||
export const Playground: Story = { | |||
args: {}, | |||
args: { | |||
items: singleMockItems, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
zIndex: 'none', | ||
margin: '0', | ||
boxSizing: 'border-box', | ||
width: buttonRef.current?.getBoundingClientRect().width ?? '200px', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this would mean whatever the trigger button size is there popover box will be. Is there plans to give the consumers the ability to control the trigger styles a little more, ie: either a custom trigger button or giving them a className prop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment these styles are still temporary, as we don't have the designs for the popover yet.
placeholder="" | ||
{...restProps} | ||
> | ||
<Trigger buttonRef={buttonRef} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The trigger at the moment is controlled entirely by our implementation with only the buttonRef exposed. Have we considered any potential needs for our consumers to either have a custom trigger or to be able to pass in data attributes that they may used in testing or for programmatically shifting focus?
The main thing that gets me thinking about that is this recent support question
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the callout. I totally appreciate that our users might need more flexibility here. I've written a ticket so that we capture the work on exposing the trigger & popover, trying to give our users as much flexibility as possible https://cultureamp.atlassian.net/browse/KZN-3462
describe('renders', () => { | ||
it('a basic select component', () => { | ||
const { getByRole } = render(<SingleSelectWrapper />) | ||
const select = getByRole('button') | ||
expect(select).toBeInTheDocument() | ||
}) | ||
}) | ||
|
||
describe('popover interactions', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I keen to get thoughts about potentially moving the interaction tests over to Storybook's play tests 🤔
I don't think we have a hard and fast rule about when to use one over the other so might also be worth discussing as a team when we're all back :)
shouldFlip: true, | ||
}) | ||
return ( | ||
<RACPopover trigger="manual" isOpen={isOpen} onOpenChange={setOpen} ref={racPopoverRef}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'll need to give this an aria-labelledby
to ensure the element with the role dialog has a title
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing we might also need to consider is the keyboard behaviour for arrow keys to open the popover when focused on the trigger. We received this feedback in the recent Intopia audit that elements with aria-haspopup="listbox"
imply that arrow keys can be used to open dialogs.
packages/components/src/__alpha__/SingleSelect/SingleSelect.tsx
Outdated
Show resolved
Hide resolved
packages/components/src/__alpha__/SingleSelect/_docs/SingleSelect.stories.tsx
Outdated
Show resolved
Hide resolved
packages/components/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.tsx
Outdated
Show resolved
Hide resolved
packages/components/src/__alpha__/SingleSelect/utils/useFixedOverlayPosition.ts
Outdated
Show resolved
Hide resolved
packages/components/src/__alpha__/SingleSelect/utils/useFixedOverlayPosition.ts
Outdated
Show resolved
Hide resolved
packages/components/src/__alpha__/SingleSelect/utils/useFixedOverlayPosition.ts
Outdated
Show resolved
Hide resolved
packages/components/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.tsx
Outdated
Show resolved
Hide resolved
packages/components/src/__alpha__/SingleSelect/utils/useFixedOverlayPosition.ts
Show resolved
Hide resolved
packages/components/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.tsx
Outdated
Show resolved
Hide resolved
return { '--position-anchor': anchorName } as React.CSSProperties | ||
} | ||
return getAnchorPositioningStyles(anchorName, positionData) | ||
}, [hasAnchorSupport, anchorName, positionData]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would invert this function and check if hasAnchorSupport
is true and fallback to manual positioning:
if(!!hasAnchorSupport) {
return getAnchorPositioningStyles(anchorName, positionData)
}
return getManualPositioningStyles(positionData)
useEffect(() => { | ||
if (typeof window === 'undefined') return // SSR safety | ||
// This effect only runs on the client after hydration | ||
if (typeof window === 'undefined') return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd definitely want to useLayoutEffect
here as this will run before painting and I would go back to the client hook but make it check if window/document exists and just bail e.g.
function useClientLayoutEffect( ...args) {
if (typeof document === "undefined") return;
useLayoutEffect(...args);
}
This way if this gets SSR'd then it'll just not run anything and the name is clear that its client side only
/** | ||
* Hook to calculate and update the position of an overlay rendered in the top layer. | ||
*/ | ||
export function useFixedOverlayPosition({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can simplify this in the case where the browser supports anchor positioning and we would only need to keep track of maxHeight adjustments otherwise anchor positioning can do everything else.
This is the CSS snippet I have on my local, maybe we pair on this.
@layer kz-components {
.popover {
// ...
@supports (anchor-name: --anchor) {
position-area: bottom center;
position-try-fallbacks: flip-block;
margin-block-start: 4px;
width: anchor-size(width);
}
}
}
https://cultureamp.atlassian.net/browse/KZN-3415
What
usesuseOverlayPosition
to handle the popover positioning instead of using css anchor and having to manually handle the resizing & flipping of the popoverPositioning supports:
Notes: Because we now handle the popover visibility manually via the Select component state we have to wire up the selected state, and clone the children passing the appropriate props.
Why
Reducing any future risk of z-index and layout issues. These were a regular occurance in the past
Initially positioning fallback was meant to be done with RAC - however RAC was not working in RTL layouts due to quirks of positioning in the css top-layer that RAC doesn't account for.