@react-spectrum/list 3.0.0-alpha.10 → 3.0.0-alpha.11

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,66 +12,80 @@
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, 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, layout} = useContext(ListViewContext);
39
-
45
+ let {state, dragState, dropState, isListDraggable, isListDroppable, layout, dragHooks, dropHooks, loadingState} = useContext(ListViewContext);
40
46
  let {direction} = useLocale();
41
47
  let rowRef = useRef<HTMLDivElement>();
42
- let cellRef = useRef<HTMLDivElement>();
43
48
  let {
44
49
  isFocusVisible: isFocusVisibleWithin,
45
50
  focusProps: focusWithinProps
46
51
  } = useFocusRing({within: true});
47
52
  let {isFocusVisible, focusProps} = useFocusRing();
48
- let allowsInteraction = state.selectionManager.selectionMode !== 'none' || onAction;
49
- let isDisabled = !allowsInteraction || state.disabledKeys.has(item.key);
50
- let isDraggable = dragState?.isDraggable(item.key) && !isDisabled;
51
- let {hoverProps, isHovered} = useHover({isDisabled});
52
- let {pressProps, isPressed} = usePress({isDisabled});
53
- let {rowProps} = useGridRow({
53
+
54
+ let {
55
+ rowProps,
56
+ gridCellProps,
57
+ isPressed,
58
+ isSelected,
59
+ isDisabled,
60
+ allowsSelection,
61
+ hasAction
62
+ } = useListItem({
54
63
  node: item,
55
64
  isVirtualized: true,
56
- onAction: onAction ? () => onAction(item.key) : undefined,
57
65
  shouldSelectOnPressUp: isListDraggable
58
66
  }, state, rowRef);
59
- let {gridCellProps} = useGridCell({
60
- node: cellNode,
61
- focusMode: 'cell'
62
- }, 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
+
63
73
  let draggableItem: DraggableItemResult;
64
74
  if (isListDraggable) {
65
75
  // eslint-disable-next-line react-hooks/rules-of-hooks
66
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
+ if (isListDroppable) {
84
+ let target = {type: 'item', key: item.key, dropPosition: 'on'} as DropTarget;
85
+ isDropTarget = dropState.isDropTarget(target);
86
+ // eslint-disable-next-line react-hooks/rules-of-hooks
87
+ droppableItem = dropHooks.useDroppableItem({target}, dropState, rowRef);
67
88
  }
68
- const mergedProps = mergeProps(
69
- gridCellProps,
70
- hoverProps,
71
- focusWithinProps,
72
- focusProps
73
- );
74
- let {checkboxProps} = useGridSelectionCheckbox({...props, key: item.key}, state);
75
89
 
76
90
  let dragButtonRef = React.useRef();
77
91
  let {buttonProps} = useButton({
@@ -79,51 +93,90 @@ export function ListViewItem(props) {
79
93
  elementType: 'div'
80
94
  }, dragButtonRef);
81
95
 
82
- let chevron = null;
83
- if (item.props.hasChildItems) {
84
- chevron = direction === 'ltr'
85
- ? (
86
- <ChevronRightMedium
87
- aria-hidden="true"
88
- UNSAFE_className={listStyles['react-spectrum-ListViewItem-parentIndicator']} />
89
- )
90
- : (
91
- <ChevronLeftMedium
92
- aria-hidden="true"
93
- UNSAFE_className={listStyles['react-spectrum-ListViewItem-parentIndicator']} />
94
- );
95
- }
96
+ let chevron = direction === 'ltr'
97
+ ? (
98
+ <ChevronRightMedium
99
+ aria-hidden="true"
100
+ UNSAFE_className={
101
+ classNames(
102
+ listStyles,
103
+ 'react-spectrum-ListViewItem-parentIndicator',
104
+ {
105
+ 'react-spectrum-ListViewItem-parentIndicator--hasChildItems': item.props.hasChildItems,
106
+ 'is-disabled': !hasAction
107
+ }
108
+ )
109
+ } />
110
+ )
111
+ : (
112
+ <ChevronLeftMedium
113
+ aria-hidden="true"
114
+ UNSAFE_className={
115
+ classNames(
116
+ listStyles,
117
+ 'react-spectrum-ListViewItem-parentIndicator',
118
+ {
119
+ 'react-spectrum-ListViewItem-parentIndicator--hasChildItems': item.props.hasChildItems,
120
+ 'is-disabled': !hasAction
121
+ }
122
+ )
123
+ } />
124
+ );
96
125
 
97
126
  let showCheckbox = state.selectionManager.selectionMode !== 'none' && state.selectionManager.selectionBehavior === 'toggle';
98
- let isSelected = state.selectionManager.isSelected(item.key);
99
- let showDragHandle = isDraggable && isFocusVisibleWithin;
100
127
  let {visuallyHiddenProps} = useVisuallyHidden();
128
+
129
+ const mergedProps = mergeProps(
130
+ rowProps,
131
+ draggableItem?.dragProps,
132
+ isDroppable && droppableItem?.dropProps,
133
+ hoverProps,
134
+ focusWithinProps,
135
+ focusProps
136
+ );
137
+
101
138
  let isFirstRow = item.prevKey == null;
102
139
  let isLastRow = item.nextKey == null;
103
140
  // Figure out if the ListView content is equal or greater in height to the container. If so, we'll need to round the bottom
104
141
  // 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
105
142
  // with bottom border
106
143
  let isFlushWithContainerBottom = false;
107
- if (isLastRow) {
144
+ if (isLastRow && loadingState !== 'loadingMore') {
108
145
  if (layout.getContentSize()?.height >= layout.virtualizer?.getVisibleRect().height) {
109
146
  isFlushWithContainerBottom = true;
110
147
  }
111
148
  }
149
+ // previous item isn't selected
150
+ // 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
151
+ let roundTops = (!state.selectionManager.isSelected(item.prevKey)
152
+ && (state.selectionManager.focusedKey !== item.prevKey || !(isGlobalFocusVisible() && state.selectionManager.isFocused)));
153
+ let roundBottoms = (!state.selectionManager.isSelected(item.nextKey)
154
+ && (state.selectionManager.focusedKey !== item.nextKey || !(isGlobalFocusVisible() && state.selectionManager.isFocused)));
155
+
156
+ let content = typeof item.rendered === 'string' ? <Text>{item.rendered}</Text> : item.rendered;
157
+ if (isDisabled) {
158
+ content = <Provider isDisabled>{content}</Provider>;
159
+ }
112
160
 
113
161
  return (
114
162
  <div
115
- {...mergeProps(rowProps, pressProps, isDraggable && draggableItem?.dragProps)}
163
+ {...mergedProps}
116
164
  className={
117
165
  classNames(
118
166
  listStyles,
119
167
  'react-spectrum-ListView-row',
120
168
  {
121
- 'focus-ring': isFocusVisible
169
+ 'focus-ring': isFocusVisible,
170
+ 'round-tops':
171
+ roundTops || (isHovered && !isSelected && state.selectionManager.focusedKey !== item.key),
172
+ 'round-bottoms':
173
+ roundBottoms || (isHovered && !isSelected && state.selectionManager.focusedKey !== item.key)
122
174
  }
123
175
  )
124
176
  }
125
177
  ref={rowRef}>
126
178
  <div
179
+ // TODO: refactor the css here now that we are focusing the row?
127
180
  className={
128
181
  classNames(
129
182
  listStyles,
@@ -134,21 +187,23 @@ export function ListViewItem(props) {
134
187
  'focus-ring': isFocusVisible,
135
188
  'is-hovered': isHovered,
136
189
  'is-selected': isSelected,
190
+ 'is-disabled': isDisabled,
191
+ 'is-prev-selected': state.selectionManager.isSelected(item.prevKey),
137
192
  'is-next-selected': state.selectionManager.isSelected(item.nextKey),
138
193
  'react-spectrum-ListViewItem--highlightSelection': state.selectionManager.selectionBehavior === 'replace' && (isSelected || state.selectionManager.isSelected(item.nextKey)),
139
- 'react-spectrum-ListViewItem--draggable': isDraggable,
194
+ 'react-spectrum-ListViewItem--dropTarget': !!isDropTarget,
140
195
  'react-spectrum-ListViewItem--firstRow': isFirstRow,
141
196
  'react-spectrum-ListViewItem--lastRow': isLastRow,
142
- 'react-spectrum-ListViewItem--isFlushBottom': isFlushWithContainerBottom
197
+ 'react-spectrum-ListViewItem--isFlushBottom': isFlushWithContainerBottom,
198
+ 'react-spectrum-ListViewItem--hasDescription': hasDescription
143
199
  }
144
200
  )
145
201
  }
146
- ref={cellRef}
147
- {...mergedProps}>
202
+ {...gridCellProps}>
148
203
  <Grid UNSAFE_className={listStyles['react-spectrum-ListViewItem-grid']}>
149
204
  {isListDraggable &&
150
205
  <div className={listStyles['react-spectrum-ListViewItem-draghandle-container']}>
151
- {isDraggable &&
206
+ {!isDisabled &&
152
207
  <FocusRing focusRingClass={classNames(listStyles, 'focus-ring')}>
153
208
  <div
154
209
  {...buttonProps as React.HTMLAttributes<HTMLElement>}
@@ -158,7 +213,7 @@ export function ListViewItem(props) {
158
213
  'react-spectrum-ListViewItem-draghandle-button'
159
214
  )
160
215
  }
161
- style={!showDragHandle ? {...visuallyHiddenProps.style} : {}}
216
+ style={!isFocusVisibleWithin ? {...visuallyHiddenProps.style} : {}}
162
217
  ref={dragButtonRef}
163
218
  draggable="true">
164
219
  <ListGripper />
@@ -167,20 +222,29 @@ export function ListViewItem(props) {
167
222
  }
168
223
  </div>
169
224
  }
170
- {showCheckbox &&
171
- <Checkbox
172
- UNSAFE_className={listStyles['react-spectrum-ListViewItem-checkbox']}
173
- {...checkboxProps}
174
- isEmphasized={isEmphasized} />
175
- }
225
+ <CSSTransition
226
+ in={showCheckbox}
227
+ unmountOnExit
228
+ classNames={{
229
+ enter: listStyles['react-spectrum-ListViewItem-checkbox--enter'],
230
+ enterActive: listStyles['react-spectrum-ListViewItem-checkbox--enterActive'],
231
+ exit: listStyles['react-spectrum-ListViewItem-checkbox--exit'],
232
+ exitActive: listStyles['react-spectrum-ListViewItem-checkbox--exitActive']
233
+ }}
234
+ timeout={160} >
235
+ <div className={listStyles['react-spectrum-ListViewItem-checkboxWrapper']}>
236
+ <Checkbox
237
+ {...checkboxProps}
238
+ UNSAFE_className={listStyles['react-spectrum-ListViewItem-checkbox']}
239
+ isEmphasized={isEmphasized} />
240
+ </div>
241
+ </CSSTransition>
176
242
  <SlotProvider
177
243
  slots={{
178
- content: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
179
244
  text: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
180
245
  description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description']},
181
246
  icon: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-icon'], size: 'M'},
182
247
  image: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-image']},
183
- link: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content'], isQuiet: true},
184
248
  actionButton: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'], isQuiet: true},
185
249
  actionGroup: {
186
250
  UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'],
@@ -189,7 +253,7 @@ export function ListViewItem(props) {
189
253
  },
190
254
  actionMenu: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actionmenu'], isQuiet: true}
191
255
  }}>
192
- {typeof item.rendered === 'string' ? <Content>{item.rendered}</Content> : item.rendered}
256
+ {content}
193
257
  <ClearSlots>
194
258
  {chevron}
195
259
  </ClearSlots>
@@ -0,0 +1,25 @@
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 dropRef = useRef();
8
+ let {dropIndicatorProps} = dropHooks.useDropIndicator({
9
+ target: {type: 'root'}
10
+ }, dropState, dropRef);
11
+
12
+ let {visuallyHiddenProps} = useVisuallyHidden();
13
+ if (dropIndicatorProps['aria-hidden']) {
14
+ return null;
15
+ }
16
+
17
+ return (
18
+ <div
19
+ role="option"
20
+ aria-selected="false"
21
+ {...visuallyHiddenProps}
22
+ {...dropIndicatorProps}
23
+ ref={dropRef} />
24
+ );
25
+ }