-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: add virtualization to S2 combobox and picker #8110
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
); | ||
} | ||
|
||
const Separator = /*#__PURE__*/ createLeafComponent('separator', function Separator(props: SeparatorProps & {size?: 'S' | 'M' | 'L' | 'XL'}, ref: ForwardedRef<HTMLElement>) { |
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.
it does feel a little overkill to import separator separately but it's at least a pretty simple component ...i can probably simplify the code since this isn't being used anywhere else except for picker + combobox. given that it's not interactive and just a visual thing, i don't think it has any accessibility requirements.
otherwise, i experimented a little bit with adding a bottom border which would work i think? i would need to play around with the linear-gradient so that the visible border aligns with the item text and it'd require some additional targeting of first-child/last-child but i think it should be possible. that said, not sure if it's worth the time figuring this out if this current solution works
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.
What is the reason we can't use the S2 Divider which already renders the RAC Separator which is a LeafComponent?
Is it just the extra size requirement?
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.
No, it was that it wasn't measuring the height correctly. We had some additional padding on the divider that wasn't being accounted for when measuring the layout. By copying over the S2 Divider, wrapping it in a div, we could account for the height correctly. The reason I had to copy it over was because if I just wrapped the S2 Divider in a div, it would throw an error. I had to do it inside the createLeafComponent
.
I tried updating the ListLayout to have a separatorHeight but the reason that didn't work out was because even if we had display: 'none' set on the last separator, the collection would still account for the separatorHeight so there was additional empty space on the bottom that shouldn't have been there. In that case, we would need it to be an estimatedSeparatorHeight and involve slightly different logic similar to buildItem but I didn't test that out.
Overall, we decided that it was probably not worth our time spending too much time on just a separator so this is what we came up with.
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.
what was the reason for the extra div? where was the extra padding coming from?
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.
actually I think ListBox doesn't even allow separators. aXe currently fails on S2 prod storybook. In v3 we overrode the role to presentation, but RAC doesn't support that. Any reason it needs to be a separate element at all? Can it just be a border-bottom on the section?
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.
Or actually it was margins, not padding. It was coming from here. The extra div was so I could set an explicit height.
We could potentially use border-bottom instead, but in order to align the separator with the margins we have on the menu items + heading on the left + right, we'd probably need something like border-image. For example, if we added a border (this just added it to the top, very basic, no styling, for demonstration purposes), understandably, the length of it is the width of the section. However, the separator has a length that is less than the width of the section. Right now, I'm able to account for that by adding a margin with edgeToText(). I suppose we could just calculate it with a percentage tho?

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.
Since it doesn't need to be an actual divider, can we make a stripped down version like this?
const dividerStyle = style({
backgroundColor: {
default: 'gray-200',
forcedColors: 'ButtonBorder'
},
borderRadius: 'full',
height: '[2px]',
width: 'full'
});
export const Divider = /*#__PURE__*/ createLeafComponent('separator', function Divider({size}: {size?: 'S' | 'M' | 'L' | 'XL'}, ref: ForwardedRef<HTMLDivElement>) {
return (
<div className={separatorWrapper({size})}>
<div ref={ref} className={dividerStyle} />
</div>
);
});
needs alignItems: 'center'
in separatorWrapper
too
…ectrum into s2-combobox-picker-virtualizer
setPressed(false); | ||
}, {once: true, capture: true}); | ||
}; | ||
let layout = new ListLayout({estimatedRowHeight: 32, estimatedHeadingHeight: 50, padding: 8}); |
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.
Interesting that this had to be pulled out of the Virtualizer layout
and layoutOptions
since now this means we generate a new ListLayout every render. I assume this was to fix the scroll into view behavior?
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.
Yeah, this was to fix the scroll into view. Without the change, the view would only scroll when we were closing the popover which isn't very useful.
It seemed like what was happening prior to this change was that when we initially open the picker without any selected item, we call layoutIfNeeded
as we scroll to select an item which sets this.requestedRect
. We want to preserve that value even when we close the popover but was happening was that we were basically resetting this value every time we closed the popover so this.requestedRect
didn't have a size (everything was just zero). Then when we called this.buildCollection
, at this line we would exit the loop early so we only had one node when we called getVisibleLayoutInfos
Might have some gaps in my understanding but that's what I found if that's helpful at all
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.
Can we memo the ListLayout? looks like it doesn't have any dependencies
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.
hmm this also goes against our common pattern for Virtualizer now
Could we instead move the Virtualizer up to wrap the PopoverBase?
@@ -143,6 +143,7 @@ describe('SearchAutocomplete', function () { | |||
user = userEvent.setup({delay: null, pointerMap}); | |||
jest.spyOn(window.HTMLElement.prototype, 'clientWidth', 'get').mockImplementation(() => 1000); | |||
jest.spyOn(window.HTMLElement.prototype, 'clientHeight', 'get').mockImplementation(() => 1000); | |||
jest.spyOn(window.HTMLElement.prototype, 'scrollHeight', 'get').mockImplementation(() => 50); |
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.
is this going to be problematic for users of v3 in their own tests?
will we need to call out in release?
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.
Probably something we'd like to call out but we also we have this section in are docs where we do mock the scrollHeight for virtualized components
width: 'full', | ||
boxSizing: 'border-box', | ||
maxHeight: '[inherit]', | ||
// TODO: Might help with horizontal scrolling happening on Windows, will need to check somehow. Otherwise, revert back to overflow: auto |
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.
what is the todo here? was something else being tried?
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.
It was something that I thought might fix the issue with the horizontal scrollbar but I'm unable to see if it actually works since I can't reproduce the original issue
} | ||
}, | ||
position: 'relative', | ||
// each menu item should take up the entire width, the subgrid will handle within the item |
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.
no longer relevant comment?
'. checkmark icon label .', | ||
'. . . description .' |
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.
'. checkmark icon label .', | |
'. . . description .' | |
'. checkmark icon label .', | |
'. . . description .' |
gridTemplateRows: { | ||
// min-content prevents second row from 'auto'ing to a size larger then 0 when empty | ||
default: 'auto minmax(0, min-content)', | ||
':has([slot=description])': 'auto auto' |
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.
pull out into constant? seems to be the common pattern we're adopting
); | ||
} | ||
|
||
const Separator = /*#__PURE__*/ createLeafComponent('separator', function Separator(props: SeparatorProps & {size?: 'S' | 'M' | 'L' | 'XL'}, ref: ForwardedRef<HTMLElement>) { |
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.
What is the reason we can't use the S2 Divider which already renders the RAC Separator which is a LeafComponent?
Is it just the extra size requirement?
<Separator | ||
{...props} | ||
className={mergeStyles( | ||
divider({ |
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.
divider
only supports 'S' | 'M' | 'L'
, if XL is sent in, it'll treat it like M
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 it should be fine? Since we are passing the size as 'M', it should always use the 'M' styles regardless of ComboBox or Picker
setPressed(false); | ||
}, {once: true, capture: true}); | ||
}; | ||
let layout = new ListLayout({estimatedRowHeight: 32, estimatedHeadingHeight: 50, padding: 8}); |
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.
Can we memo the ListLayout? looks like it doesn't have any dependencies
setPressed(false); | ||
}, {once: true, capture: true}); | ||
}; | ||
let layout = new ListLayout({estimatedRowHeight: 32, estimatedHeadingHeight: 50, padding: 8}); |
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.
hmm this also goes against our common pattern for Virtualizer now
Could we instead move the Virtualizer up to wrap the PopoverBase?
@@ -194,6 +194,9 @@ export class S2TableLayout<T> extends TableLayout<T> { | |||
|
|||
protected buildCollection(): LayoutNode[] { | |||
let [header, body] = super.buildCollection(); | |||
if (!header) { |
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.
@LFDanLu can you explain when this happens?
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.
If I remember correctly, now that we are rendering the contents of the virtualized content even when we haven't fully processed the collection (aka the changes in Virtualizer and rect size check changes) there is a moment where we haven't processed the collection and thus can't perform build the header/body yet
@@ -148,6 +149,114 @@ const iconStyles = style({ | |||
} | |||
}); | |||
|
|||
export let listbox = style<{size: 'S' | 'M' | 'L' | 'XL'}>({ |
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.
What are the differences between the styles here and the ones in menu? A bit unfortunate to duplicate them
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.
Mainly that menu has some additional styles that Combobox and Picker didn't need but it wasn't actually affecting anything. I could probably revert the change and just use menu but I'd need to double check
); | ||
} | ||
|
||
const Separator = /*#__PURE__*/ createLeafComponent('separator', function Separator(props: SeparatorProps & {size?: 'S' | 'M' | 'L' | 'XL'}, ref: ForwardedRef<HTMLElement>) { |
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.
what was the reason for the extra div? where was the extra padding coming from?
Closes
✅ Pull Request Checklist:
📝 Test Instructions:
🧢 Your Project: