Skip to content

Commit ee49fdb

Browse files
fix: Update ListView selection style and min width (adobe#9747)
* fix: Update ListView selection style and min width * fix TreeView docs * Fix focus ring being covered by next row's background --------- Co-authored-by: Robert Snow <snowystinger@gmail.com>
1 parent 2d4b728 commit ee49fdb

File tree

3 files changed

+110
-60
lines changed

3 files changed

+110
-60
lines changed

packages/@react-spectrum/s2/src/ListView.tsx

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ let InternalListViewContext = createContext<{isQuiet?: boolean, selectionStyle?:
100100

101101
const listViewWrapper = style({
102102
minHeight: 0,
103-
minWidth: 0,
103+
minWidth: 200,
104104
display: 'flex',
105105
isolation: 'isolate',
106106
disableTapHighlight: true,
@@ -347,9 +347,15 @@ const listitem = style<GridListItemRenderProps & {
347347
default: '--borderColor',
348348
isNextSelected: 'transparent',
349349
isSelected: 'transparent'
350+
},
351+
'--radius': {
352+
type: 'borderTopStartRadius',
353+
value: 'default'
350354
}
351355
});
352356

357+
const insetBorderRadius = 'calc(var(--radius) - 1px)';
358+
353359
const listRowBackground = style<GridListItemRenderProps & {
354360
isFirstItem?: boolean,
355361
isLastItem?: boolean,
@@ -360,8 +366,6 @@ const listRowBackground = style<GridListItemRenderProps & {
360366
isNextNotSelected?: boolean,
361367
selectionStyle?: 'highlight' | 'checkbox'
362368
}>({
363-
...focusRing(),
364-
outlineOffset: -2,
365369
position: 'absolute',
366370
zIndex: -1,
367371
top: {
@@ -412,51 +416,55 @@ const listRowBackground = style<GridListItemRenderProps & {
412416
}
413417
},
414418
borderTopStartRadius: {
415-
isFirstItem: {
419+
isQuiet: 'default',
420+
isSelected: 'none',
421+
isPrevNotSelected: {
416422
isSelected: {
417423
selectionStyle: {
418424
checkbox: 'none',
419-
highlight: 'default'
425+
highlight: insetBorderRadius
420426
}
421427
},
422-
isQuiet: 'default',
423-
isFocusVisible: 'default'
428+
isQuiet: 'default'
424429
}
425430
},
426431
borderTopEndRadius: {
427-
isFirstItem: {
432+
isQuiet: 'default',
433+
isSelected: 'none',
434+
isPrevNotSelected: {
428435
isSelected: {
429436
selectionStyle: {
430437
checkbox: 'none',
431-
highlight: 'default'
438+
highlight: insetBorderRadius
432439
}
433440
},
434-
isQuiet: 'default',
435-
isFocusVisible: 'default'
441+
isQuiet: 'default'
436442
}
437443
},
438444
borderBottomStartRadius: {
439-
isLastItem: {
445+
isQuiet: 'default',
446+
isSelected: 'none',
447+
isNextNotSelected: {
440448
isSelected: {
441449
selectionStyle: {
442450
checkbox: 'none',
443-
highlight: 'default'
451+
highlight: insetBorderRadius
444452
}
445453
},
446-
isQuiet: 'default',
447-
isFocusVisible: 'default'
454+
isQuiet: 'default'
448455
}
449456
},
450457
borderBottomEndRadius: {
451-
isLastItem: {
458+
isQuiet: 'default',
459+
isSelected: 'none',
460+
isNextNotSelected: {
452461
isSelected: {
453462
selectionStyle: {
454463
checkbox: 'none',
455-
highlight: 'default'
464+
highlight: insetBorderRadius
456465
}
457466
},
458-
isQuiet: 'default',
459-
isFocusVisible: 'default'
467+
isQuiet: 'default'
460468
}
461469
},
462470
borderTopWidth: {
@@ -505,6 +513,33 @@ const listRowBackground = style<GridListItemRenderProps & {
505513
}
506514
});
507515

516+
let listRowFocusRing = style({
517+
...focusRing(),
518+
outlineOffset: -2,
519+
position: 'absolute',
520+
inset: 0,
521+
top: {
522+
default: '[-1px]',
523+
isFirstItem: 0
524+
},
525+
bottom: {
526+
selectionStyle: {
527+
checkbox: {
528+
default: '[-1px]',
529+
// Avoid the next row's selected background covering this row's focus ring.
530+
isNextSelected: 0
531+
},
532+
highlight: '[-1px]'
533+
}
534+
},
535+
borderRadius: {
536+
default: insetBorderRadius,
537+
isQuiet: 'default'
538+
},
539+
zIndex: 1,
540+
pointerEvents: 'none'
541+
});
542+
508543
export let label = style({
509544
gridArea: 'label',
510545
alignSelf: 'center',
@@ -735,6 +770,20 @@ export function ListViewItem(props: ListViewItemProps): ReactNode {
735770
isLastItem: isLastItem(id, state)
736771
})
737772
} />
773+
{renderProps.isFocusVisible &&
774+
<div
775+
className={listRowFocusRing({
776+
...renderProps,
777+
selectionStyle,
778+
isQuiet,
779+
isPrevSelected: isPrevSelected(id, state),
780+
isNextSelected: isNextSelected(id, state),
781+
isPrevNotSelected: !isPrevSelected(id, state),
782+
isNextNotSelected: !isNextSelected(id, state),
783+
isFirstItem: isFirstItem(id, state),
784+
isLastItem: isLastItem(id, state)
785+
})} />
786+
}
738787
{selectionMode !== 'none' && selectionBehavior === 'toggle' && (
739788
<ListSelectionCheckbox isDisabled={isDisabled} />
740789
)}

packages/@react-spectrum/s2/src/TreeView.tsx

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const TreeRendererContext = createContext<TreeRendererContextValue>({});
7676

7777
const treeViewWrapper = style({
7878
minHeight: 0,
79-
minWidth: 0,
79+
minWidth: 160,
8080
display: 'flex',
8181
isolation: 'isolate',
8282
disableTapHighlight: true,
@@ -122,49 +122,40 @@ export const TreeView = /*#__PURE__*/ (forwardRef as forwardRefType)(function Tr
122122
}
123123

124124
let domRef = useDOMRef(ref);
125-
let scrollRef = useRef<HTMLElement | null>(null);
125+
let scrollRef = useRef<HTMLDivElement | null>(null);
126126

127127
let {selectedKeys, onSelectionChange, actionBar, actionBarHeight} = useActionBarContainer({...props, scrollRef});
128128

129-
let treeContent = (
130-
<Virtualizer
131-
layout={ListLayout}
132-
layoutOptions={{
133-
rowHeight: scale === 'large' ? 50 : 40
134-
}}>
135-
<TreeRendererContext.Provider value={{renderer}}>
136-
<Tree
137-
{...props}
138-
style={{
139-
...(!props.renderActionBar ? UNSAFE_style : {}),
140-
paddingBottom: actionBarHeight > 0 ? actionBarHeight + 8 : 0,
141-
scrollPaddingBottom: actionBarHeight > 0 ? actionBarHeight + 8 : 0
142-
}}
143-
className={renderProps => (!props.renderActionBar ? (UNSAFE_className ?? '') : '') + tree({...renderProps})}
144-
selectionBehavior="toggle"
145-
selectedKeys={selectedKeys}
146-
defaultSelectedKeys={undefined}
147-
onSelectionChange={onSelectionChange}
148-
ref={props.renderActionBar ? (scrollRef as any) : domRef}>
149-
{props.children}
150-
</Tree>
151-
</TreeRendererContext.Provider>
152-
</Virtualizer>
129+
return (
130+
<div
131+
ref={domRef}
132+
className={(UNSAFE_className ?? '') + treeViewWrapper(null, props.styles)}
133+
style={UNSAFE_style}>
134+
<Virtualizer
135+
layout={ListLayout}
136+
layoutOptions={{
137+
rowHeight: scale === 'large' ? 50 : 40
138+
}}>
139+
<TreeRendererContext.Provider value={{renderer}}>
140+
<Tree
141+
{...props}
142+
style={{
143+
paddingBottom: actionBarHeight > 0 ? actionBarHeight + 8 : 0,
144+
scrollPaddingBottom: actionBarHeight > 0 ? actionBarHeight + 8 : 0
145+
}}
146+
className={tree}
147+
selectionBehavior="toggle"
148+
selectedKeys={selectedKeys}
149+
defaultSelectedKeys={undefined}
150+
onSelectionChange={onSelectionChange}
151+
ref={scrollRef}>
152+
{props.children}
153+
</Tree>
154+
</TreeRendererContext.Provider>
155+
</Virtualizer>
156+
{actionBar}
157+
</div>
153158
);
154-
155-
if (props.renderActionBar) {
156-
return (
157-
<div
158-
ref={domRef}
159-
className={(UNSAFE_className ?? '') + treeViewWrapper(null, props.styles)}
160-
style={UNSAFE_style}>
161-
{treeContent}
162-
{actionBar}
163-
</div>
164-
);
165-
}
166-
167-
return treeContent;
168159
});
169160

170161
const rowBackgroundColor = {

packages/dev/s2-docs/pages/s2/TreeView.mdx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ export const description = 'Displays hierarchical data with selection and collap
1717
```tsx render docs={docs.exports.TreeView} links={docs.links} props={['selectionMode']} initialProps={{selectionMode: 'multiple'}} type="s2" wide
1818
"use client";
1919
import {TreeView, TreeViewItem, TreeViewItemContent} from '@react-spectrum/s2';
20+
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
2021

2122
<TreeView
2223
aria-label="Files"
2324
/* PROPS */
25+
styles={style({width: 'full'})}
2426
defaultExpandedKeys={['documents']}>
2527
<TreeViewItem id="documents" textValue="Documents">
2628
<TreeViewItemContent>Documents</TreeViewItemContent>
@@ -44,6 +46,7 @@ import {TreeView, TreeViewItem, TreeViewItemContent} from '@react-spectrum/s2';
4446
```tsx render
4547
"use client";
4648
import {TreeView, TreeViewItem, TreeViewItemContent, Collection} from '@react-spectrum/s2';
49+
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
4750

4851
///- begin collapse -///
4952
let items = [
@@ -62,6 +65,7 @@ let items = [
6265

6366
<TreeView
6467
aria-label="Files"
68+
styles={style({width: 'full'})}
6569
defaultExpandedKeys={[1, 4]}
6670
items={items}
6771
selectionMode="multiple">
@@ -92,6 +96,7 @@ import Folder from '@react-spectrum/s2/icons/Folder';
9296
import File from '@react-spectrum/s2/icons/File';
9397
import Edit from '@react-spectrum/s2/icons/Edit';
9498
import Delete from '@react-spectrum/s2/icons/Delete';
99+
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
95100

96101
///- begin collapse -///
97102
let items = [
@@ -110,6 +115,7 @@ let items = [
110115

111116
<TreeView
112117
aria-label="Files"
118+
styles={style({width: 'full'})}
113119
defaultExpandedKeys={[1, 4]}
114120
items={items}
115121
selectionMode="multiple">
@@ -193,7 +199,7 @@ function AsyncLoadingExample() {
193199
return (
194200
<TreeView
195201
aria-label="Async loading tree"
196-
styles={style({height: 300})}>
202+
styles={style({height: 300, width: 'full'})}>
197203
<TreeViewItem textValue="Pokemon">
198204
<TreeViewItemContent>Pokemon</TreeViewItemContent>
199205
<Collection items={pokemonList.items}>
@@ -236,10 +242,12 @@ Use the `href` prop on a `<TreeItem>` to create a link. See the [getting started
236242
```tsx render
237243
"use client";
238244
import {TreeView, TreeViewItem, TreeViewItemContent} from '@react-spectrum/s2';
245+
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
239246

240247
<TreeView
241248
aria-label="TreeView with links"
242249
selectionMode="multiple"
250+
styles={style({width: 'full'})}
243251
defaultExpandedKeys={['bulbasaur', 'ivysaur']}>
244252
<TreeViewItem
245253
/*- begin highlight -*/
@@ -275,9 +283,11 @@ Use `renderEmptyState` to render placeholder content when the tree is empty.
275283
"use client";
276284
import {TreeView, IllustratedMessage, Heading, Content, Link} from '@react-spectrum/s2';
277285
import FolderOpen from '@react-spectrum/s2/illustrations/linear/FolderOpen';
286+
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
278287

279288
<TreeView
280289
aria-label="Search results"
290+
styles={style({width: 'full'})}
281291
/*- begin highlight -*/
282292
renderEmptyState={() => (
283293
<IllustratedMessage>

0 commit comments

Comments
 (0)