@react-spectrum/list 3.0.0-alpha.9 → 3.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,65 +12,83 @@
12
12
  import {Checkbox} from '@react-spectrum/checkbox';
13
13
  import ChevronLeftMedium from '@spectrum-icons/ui/ChevronLeftMedium';
14
14
  import ChevronRightMedium from '@spectrum-icons/ui/ChevronRightMedium';
15
- import {classNames, ClearSlots, SlotProvider} from '@react-spectrum/utils';
16
- import {Content} from '@react-spectrum/view';
17
- import type {DraggableItemResult} from '@react-aria/dnd';
15
+ import {classNames, ClearSlots, SlotProvider, useHasChild} from '@react-spectrum/utils';
16
+ import {CSSTransition} from 'react-transition-group';
17
+ import type {DraggableItemResult, DropIndicatorAria, DroppableItemResult} from '@react-aria/dnd';
18
+ import {DropTarget, Node} from '@react-types/shared';
18
19
  import {FocusRing, useFocusRing} from '@react-aria/focus';
19
20
  import {Grid} from '@react-spectrum/layout';
21
+ import {isFocusVisible as isGlobalFocusVisible, useHover} from '@react-aria/interactions';
20
22
  import ListGripper from '@spectrum-icons/ui/ListGripper';
21
- import listStyles from './listview.css';
23
+ import listStyles from './styles.css';
22
24
  import {ListViewContext} from './ListView';
23
25
  import {mergeProps} from '@react-aria/utils';
26
+ import {Provider} from '@react-spectrum/provider';
24
27
  import React, {useContext, useRef} from 'react';
28
+ import {Text} from '@react-spectrum/text';
25
29
  import {useButton} from '@react-aria/button';
26
- import {useGridCell, useGridRow, useGridSelectionCheckbox} from '@react-aria/grid';
27
- import {useHover, usePress} from '@react-aria/interactions';
30
+ import {useListItem, useListSelectionCheckbox} from '@react-aria/list';
28
31
  import {useLocale} from '@react-aria/i18n';
29
32
  import {useVisuallyHidden} from '@react-aria/visually-hidden';
30
33
 
31
- export function ListViewItem(props) {
34
+ interface ListViewItemProps<T> {
35
+ item: Node<T>,
36
+ isEmphasized: boolean,
37
+ hasActions: boolean
38
+ }
39
+
40
+ export function ListViewItem<T>(props: ListViewItemProps<T>) {
32
41
  let {
33
42
  item,
34
- isEmphasized,
35
- dragHooks
43
+ isEmphasized
36
44
  } = props;
37
- let cellNode = [...item.childNodes][0];
38
- let {state, dragState, onAction, isListDraggable} = useContext(ListViewContext);
45
+ let {state, dragState, dropState, isListDraggable, isListDroppable, layout, dragHooks, dropHooks, loadingState} = useContext(ListViewContext);
39
46
  let {direction} = useLocale();
40
47
  let rowRef = useRef<HTMLDivElement>();
41
- let cellRef = useRef<HTMLDivElement>();
42
48
  let {
43
49
  isFocusVisible: isFocusVisibleWithin,
44
50
  focusProps: focusWithinProps
45
51
  } = useFocusRing({within: true});
46
52
  let {isFocusVisible, focusProps} = useFocusRing();
47
- let allowsInteraction = state.selectionManager.selectionMode !== 'none' || onAction;
48
- let isDisabled = !allowsInteraction || state.disabledKeys.has(item.key);
49
- let isDraggable = dragState?.isDraggable(item.key) && !isDisabled;
50
- let {hoverProps, isHovered} = useHover({isDisabled});
51
- let {pressProps, isPressed} = usePress({isDisabled});
52
- let {rowProps} = useGridRow({
53
+ let {
54
+ rowProps,
55
+ gridCellProps,
56
+ isPressed,
57
+ descriptionProps,
58
+ isSelected,
59
+ isDisabled,
60
+ allowsSelection,
61
+ hasAction
62
+ } = useListItem({
53
63
  node: item,
54
64
  isVirtualized: true,
55
- onAction: onAction ? () => onAction(item.key) : undefined,
56
65
  shouldSelectOnPressUp: isListDraggable
57
66
  }, state, rowRef);
58
- let {gridCellProps} = useGridCell({
59
- node: cellNode,
60
- focusMode: 'cell'
61
- }, state, cellRef);
67
+ let isDroppable = isListDroppable && !isDisabled;
68
+ let {hoverProps, isHovered} = useHover({isDisabled: !allowsSelection && !hasAction});
69
+
70
+ let {checkboxProps} = useListSelectionCheckbox({key: item.key}, state);
71
+ let hasDescription = useHasChild(`.${listStyles['react-spectrum-ListViewItem-description']}`, rowRef);
72
+
62
73
  let draggableItem: DraggableItemResult;
63
74
  if (isListDraggable) {
64
75
  // eslint-disable-next-line react-hooks/rules-of-hooks
65
76
  draggableItem = dragHooks.useDraggableItem({key: item.key}, dragState);
77
+ if (isDisabled) {
78
+ draggableItem = null;
79
+ }
80
+ }
81
+ let droppableItem: DroppableItemResult;
82
+ let isDropTarget: boolean;
83
+ let dropIndicator: DropIndicatorAria;
84
+ let dropIndicatorRef = useRef();
85
+ if (isListDroppable) {
86
+ let target = {type: 'item', key: item.key, dropPosition: 'on'} as DropTarget;
87
+ isDropTarget = dropState.isDropTarget(target);
88
+ // eslint-disable-next-line react-hooks/rules-of-hooks
89
+ droppableItem = dropHooks.useDroppableItem({target}, dropState, rowRef);
90
+ dropIndicator = dropHooks.useDropIndicator({target}, dropState, dropIndicatorRef);
66
91
  }
67
- const mergedProps = mergeProps(
68
- gridCellProps,
69
- hoverProps,
70
- focusWithinProps,
71
- focusProps
72
- );
73
- let {checkboxProps} = useGridSelectionCheckbox({...props, key: item.key}, state);
74
92
 
75
93
  let dragButtonRef = React.useRef();
76
94
  let {buttonProps} = useButton({
@@ -78,30 +96,91 @@ export function ListViewItem(props) {
78
96
  elementType: 'div'
79
97
  }, dragButtonRef);
80
98
 
81
- let chevron = null;
82
- if (item.props.hasChildItems) {
83
- chevron = direction === 'ltr'
84
- ? (
85
- <ChevronRightMedium
86
- aria-hidden="true"
87
- UNSAFE_className={listStyles['react-spectrum-ListViewItem-parentIndicator']} />
88
- )
89
- : (
90
- <ChevronLeftMedium
91
- aria-hidden="true"
92
- UNSAFE_className={listStyles['react-spectrum-ListViewItem-parentIndicator']} />
93
- );
94
- }
99
+ let chevron = direction === 'ltr'
100
+ ? (
101
+ <ChevronRightMedium
102
+ aria-hidden="true"
103
+ UNSAFE_className={
104
+ classNames(
105
+ listStyles,
106
+ 'react-spectrum-ListViewItem-parentIndicator',
107
+ {
108
+ 'react-spectrum-ListViewItem-parentIndicator--hasChildItems': item.props.hasChildItems,
109
+ 'is-disabled': !hasAction
110
+ }
111
+ )
112
+ } />
113
+ )
114
+ : (
115
+ <ChevronLeftMedium
116
+ aria-hidden="true"
117
+ UNSAFE_className={
118
+ classNames(
119
+ listStyles,
120
+ 'react-spectrum-ListViewItem-parentIndicator',
121
+ {
122
+ 'react-spectrum-ListViewItem-parentIndicator--hasChildItems': item.props.hasChildItems,
123
+ 'is-disabled': !hasAction
124
+ }
125
+ )
126
+ } />
127
+ );
95
128
 
96
129
  let showCheckbox = state.selectionManager.selectionMode !== 'none' && state.selectionManager.selectionBehavior === 'toggle';
97
- let isSelected = state.selectionManager.isSelected(item.key);
98
- let showDragHandle = isDraggable && (isFocusVisibleWithin || isHovered || isPressed);
99
130
  let {visuallyHiddenProps} = useVisuallyHidden();
131
+
132
+ let dropProps = isDroppable ? droppableItem?.dropProps : {'aria-hidden': droppableItem?.dropProps['aria-hidden']};
133
+ const mergedProps = mergeProps(
134
+ rowProps,
135
+ draggableItem?.dragProps,
136
+ dropProps,
137
+ hoverProps,
138
+ focusWithinProps,
139
+ focusProps
140
+ );
141
+
142
+ let isFirstRow = item.prevKey == null;
143
+ let isLastRow = item.nextKey == null;
144
+ // Figure out if the ListView content is equal or greater in height to the container. If so, we'll need to round the bottom
145
+ // border corners of the last row when selected and we can get rid of the bottom border if it isn't selected to avoid border overlap
146
+ // with bottom border
147
+ let isFlushWithContainerBottom = false;
148
+ if (isLastRow && loadingState !== 'loadingMore') {
149
+ if (layout.getContentSize()?.height >= layout.virtualizer?.getVisibleRect().height) {
150
+ isFlushWithContainerBottom = true;
151
+ }
152
+ }
153
+ // previous item isn't selected
154
+ // and the previous item isn't focused or, if it is focused, then if focus globally isn't visible or just focus isn't in the listview
155
+ let roundTops = (!state.selectionManager.isSelected(item.prevKey)
156
+ && (state.selectionManager.focusedKey !== item.prevKey || !(isGlobalFocusVisible() && state.selectionManager.isFocused)));
157
+ let roundBottoms = (!state.selectionManager.isSelected(item.nextKey)
158
+ && (state.selectionManager.focusedKey !== item.nextKey || !(isGlobalFocusVisible() && state.selectionManager.isFocused)));
159
+
160
+ let content = typeof item.rendered === 'string' ? <Text>{item.rendered}</Text> : item.rendered;
161
+ if (isDisabled) {
162
+ content = <Provider isDisabled>{content}</Provider>;
163
+ }
164
+
100
165
  return (
101
166
  <div
102
- {...mergeProps(rowProps, pressProps, isDraggable && draggableItem?.dragProps)}
167
+ {...mergedProps}
168
+ className={
169
+ classNames(
170
+ listStyles,
171
+ 'react-spectrum-ListView-row',
172
+ {
173
+ 'focus-ring': isFocusVisible,
174
+ 'round-tops':
175
+ roundTops || (isHovered && !isSelected && state.selectionManager.focusedKey !== item.key),
176
+ 'round-bottoms':
177
+ roundBottoms || (isHovered && !isSelected && state.selectionManager.focusedKey !== item.key)
178
+ }
179
+ )
180
+ }
103
181
  ref={rowRef}>
104
182
  <div
183
+ // TODO: refactor the css here now that we are focusing the row?
105
184
  className={
106
185
  classNames(
107
186
  listStyles,
@@ -112,18 +191,23 @@ export function ListViewItem(props) {
112
191
  'focus-ring': isFocusVisible,
113
192
  'is-hovered': isHovered,
114
193
  'is-selected': isSelected,
115
- 'is-previous-selected': state.selectionManager.isSelected(item.prevKey),
194
+ 'is-disabled': isDisabled,
195
+ 'is-prev-selected': state.selectionManager.isSelected(item.prevKey),
196
+ 'is-next-selected': state.selectionManager.isSelected(item.nextKey),
116
197
  'react-spectrum-ListViewItem--highlightSelection': state.selectionManager.selectionBehavior === 'replace' && (isSelected || state.selectionManager.isSelected(item.nextKey)),
117
- 'react-spectrum-ListViewItem--draggable': isDraggable
198
+ 'react-spectrum-ListViewItem--dropTarget': !!isDropTarget,
199
+ 'react-spectrum-ListViewItem--firstRow': isFirstRow,
200
+ 'react-spectrum-ListViewItem--lastRow': isLastRow,
201
+ 'react-spectrum-ListViewItem--isFlushBottom': isFlushWithContainerBottom,
202
+ 'react-spectrum-ListViewItem--hasDescription': hasDescription
118
203
  }
119
204
  )
120
205
  }
121
- ref={cellRef}
122
- {...mergedProps}>
206
+ {...gridCellProps}>
123
207
  <Grid UNSAFE_className={listStyles['react-spectrum-ListViewItem-grid']}>
124
208
  {isListDraggable &&
125
209
  <div className={listStyles['react-spectrum-ListViewItem-draghandle-container']}>
126
- {isDraggable &&
210
+ {!isDisabled &&
127
211
  <FocusRing focusRingClass={classNames(listStyles, 'focus-ring')}>
128
212
  <div
129
213
  {...buttonProps as React.HTMLAttributes<HTMLElement>}
@@ -133,7 +217,7 @@ export function ListViewItem(props) {
133
217
  'react-spectrum-ListViewItem-draghandle-button'
134
218
  )
135
219
  }
136
- style={!showDragHandle ? {...visuallyHiddenProps.style} : {}}
220
+ style={!isFocusVisibleWithin ? {...visuallyHiddenProps.style} : {}}
137
221
  ref={dragButtonRef}
138
222
  draggable="true">
139
223
  <ListGripper />
@@ -142,20 +226,32 @@ export function ListViewItem(props) {
142
226
  }
143
227
  </div>
144
228
  }
145
- {showCheckbox &&
146
- <Checkbox
147
- UNSAFE_className={listStyles['react-spectrum-ListViewItem-checkbox']}
148
- {...checkboxProps}
149
- isEmphasized={isEmphasized} />
229
+ {isDropTarget && !dropIndicator?.dropIndicatorProps['aria-hidden'] &&
230
+ <div role="button" {...visuallyHiddenProps} {...dropIndicator?.dropIndicatorProps} ref={dropIndicatorRef} />
150
231
  }
232
+ <CSSTransition
233
+ in={showCheckbox}
234
+ unmountOnExit
235
+ classNames={{
236
+ enter: listStyles['react-spectrum-ListViewItem-checkbox--enter'],
237
+ enterActive: listStyles['react-spectrum-ListViewItem-checkbox--enterActive'],
238
+ exit: listStyles['react-spectrum-ListViewItem-checkbox--exit'],
239
+ exitActive: listStyles['react-spectrum-ListViewItem-checkbox--exitActive']
240
+ }}
241
+ timeout={160} >
242
+ <div className={listStyles['react-spectrum-ListViewItem-checkboxWrapper']}>
243
+ <Checkbox
244
+ {...checkboxProps}
245
+ UNSAFE_className={listStyles['react-spectrum-ListViewItem-checkbox']}
246
+ isEmphasized={isEmphasized} />
247
+ </div>
248
+ </CSSTransition>
151
249
  <SlotProvider
152
250
  slots={{
153
- content: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
154
251
  text: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
155
- description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description']},
252
+ description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description'], ...descriptionProps},
156
253
  icon: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-icon'], size: 'M'},
157
254
  image: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-image']},
158
- link: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content'], isQuiet: true},
159
255
  actionButton: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'], isQuiet: true},
160
256
  actionGroup: {
161
257
  UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'],
@@ -164,7 +260,7 @@ export function ListViewItem(props) {
164
260
  },
165
261
  actionMenu: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actionmenu'], isQuiet: true}
166
262
  }}>
167
- {typeof item.rendered === 'string' ? <Content>{item.rendered}</Content> : item.rendered}
263
+ {content}
168
264
  <ClearSlots>
169
265
  {chevron}
170
266
  </ClearSlots>
@@ -0,0 +1,28 @@
1
+ import {ListViewContext} from './ListView';
2
+ import React, {useContext, useRef} from 'react';
3
+ import {useVisuallyHidden} from '@react-aria/visually-hidden';
4
+
5
+ export default function RootDropIndicator() {
6
+ let {dropState, dropHooks} = useContext(ListViewContext);
7
+ let ref = useRef();
8
+ let {dropIndicatorProps} = dropHooks.useDropIndicator({
9
+ target: {type: 'root'}
10
+ }, dropState, ref);
11
+ let isDropTarget = dropState.isDropTarget({type: 'root'});
12
+ let {visuallyHiddenProps} = useVisuallyHidden();
13
+
14
+ if (!isDropTarget && dropIndicatorProps['aria-hidden']) {
15
+ return null;
16
+ }
17
+
18
+ return (
19
+ <div role="row" aria-hidden={dropIndicatorProps['aria-hidden']}>
20
+ <div
21
+ role="gridcell"
22
+ aria-selected="false"
23
+ {...visuallyHiddenProps}>
24
+ <div role="button" {...dropIndicatorProps} ref={ref} />
25
+ </div>
26
+ </div>
27
+ );
28
+ }