@react-spectrum/list 3.0.0-alpha.7 → 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,77 +12,175 @@
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';
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';
19
+ import {FocusRing, useFocusRing} from '@react-aria/focus';
17
20
  import {Grid} from '@react-spectrum/layout';
18
- import listStyles from './listview.css';
21
+ import {isFocusVisible as isGlobalFocusVisible, useHover} from '@react-aria/interactions';
22
+ import ListGripper from '@spectrum-icons/ui/ListGripper';
23
+ import listStyles from './styles.css';
19
24
  import {ListViewContext} from './ListView';
20
25
  import {mergeProps} from '@react-aria/utils';
26
+ import {Provider} from '@react-spectrum/provider';
21
27
  import React, {useContext, useRef} from 'react';
22
- import {useFocusRing} from '@react-aria/focus';
23
- import {useGridCell, useGridRow, useGridSelectionCheckbox} from '@react-aria/grid';
24
- import {useHover, usePress} from '@react-aria/interactions';
28
+ import {Text} from '@react-spectrum/text';
29
+ import {useButton} from '@react-aria/button';
30
+ import {useListItem, useListSelectionCheckbox} from '@react-aria/list';
25
31
  import {useLocale} from '@react-aria/i18n';
32
+ import {useVisuallyHidden} from '@react-aria/visually-hidden';
26
33
 
27
- 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>) {
28
41
  let {
29
42
  item,
30
- onAction,
31
43
  isEmphasized
32
44
  } = props;
33
- let cellNode = [...item.childNodes][0];
34
- let {state} = useContext(ListViewContext);
45
+ let {state, dragState, dropState, isListDraggable, isListDroppable, layout, dragHooks, dropHooks, loadingState} = useContext(ListViewContext);
35
46
  let {direction} = useLocale();
36
47
  let rowRef = useRef<HTMLDivElement>();
37
- let cellRef = useRef<HTMLDivElement>();
38
48
  let {
39
49
  isFocusVisible: isFocusVisibleWithin,
40
50
  focusProps: focusWithinProps
41
51
  } = useFocusRing({within: true});
42
52
  let {isFocusVisible, focusProps} = useFocusRing();
43
- let allowsInteraction = state.selectionManager.selectionMode !== 'none' || onAction;
44
- let isDisabled = !allowsInteraction || state.disabledKeys.has(item.key);
45
- let {hoverProps, isHovered} = useHover({isDisabled});
46
- let {pressProps, isPressed} = usePress({isDisabled});
47
- let {rowProps} = useGridRow({
53
+ let {
54
+ rowProps,
55
+ gridCellProps,
56
+ isPressed,
57
+ descriptionProps,
58
+ isSelected,
59
+ isDisabled,
60
+ allowsSelection,
61
+ hasAction
62
+ } = useListItem({
48
63
  node: item,
49
64
  isVirtualized: true,
50
- onAction: onAction ? () => onAction(item.key) : undefined
65
+ shouldSelectOnPressUp: isListDraggable
51
66
  }, state, rowRef);
52
- let {gridCellProps} = useGridCell({
53
- node: cellNode,
54
- focusMode: 'cell'
55
- }, 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
+
73
+ let draggableItem: DraggableItemResult;
74
+ if (isListDraggable) {
75
+ // eslint-disable-next-line react-hooks/rules-of-hooks
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);
91
+ }
92
+
93
+ let dragButtonRef = React.useRef();
94
+ let {buttonProps} = useButton({
95
+ ...draggableItem?.dragButtonProps,
96
+ elementType: 'div'
97
+ }, dragButtonRef);
98
+
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
+ );
128
+
129
+ let showCheckbox = state.selectionManager.selectionMode !== 'none' && state.selectionManager.selectionBehavior === 'toggle';
130
+ let {visuallyHiddenProps} = useVisuallyHidden();
131
+
132
+ let dropProps = isDroppable ? droppableItem?.dropProps : {'aria-hidden': droppableItem?.dropProps['aria-hidden']};
56
133
  const mergedProps = mergeProps(
57
- gridCellProps,
134
+ rowProps,
135
+ draggableItem?.dragProps,
136
+ dropProps,
58
137
  hoverProps,
59
138
  focusWithinProps,
60
139
  focusProps
61
140
  );
62
- let {checkboxProps} = useGridSelectionCheckbox({...props, key: item.key}, state);
63
141
 
64
- let chevron = null;
65
- if (item.props.hasChildItems) {
66
- chevron = direction === 'ltr'
67
- ? (
68
- <ChevronRightMedium
69
- aria-hidden="true"
70
- UNSAFE_className={listStyles['react-spectrum-ListViewItem-parentIndicator']} />
71
- )
72
- : (
73
- <ChevronLeftMedium
74
- aria-hidden="true"
75
- UNSAFE_className={listStyles['react-spectrum-ListViewItem-parentIndicator']} />
76
- );
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>;
77
163
  }
78
164
 
79
- let showCheckbox = state.selectionManager.selectionMode !== 'none' && state.selectionManager.selectionBehavior === 'toggle';
80
- let isSelected = state.selectionManager.isSelected(item.key);
81
165
  return (
82
166
  <div
83
- {...mergeProps(rowProps, pressProps)}
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
+ }
84
181
  ref={rowRef}>
85
182
  <div
183
+ // TODO: refactor the css here now that we are focusing the row?
86
184
  className={
87
185
  classNames(
88
186
  listStyles,
@@ -93,28 +191,67 @@ export function ListViewItem(props) {
93
191
  'focus-ring': isFocusVisible,
94
192
  'is-hovered': isHovered,
95
193
  'is-selected': isSelected,
96
- 'is-previous-selected': state.selectionManager.isSelected(item.prevKey),
97
- 'react-spectrum-ListViewItem--highlightSelection': state.selectionManager.selectionBehavior === 'replace' && (isSelected || state.selectionManager.isSelected(item.nextKey))
194
+ 'is-disabled': isDisabled,
195
+ 'is-prev-selected': state.selectionManager.isSelected(item.prevKey),
196
+ 'is-next-selected': state.selectionManager.isSelected(item.nextKey),
197
+ 'react-spectrum-ListViewItem--highlightSelection': state.selectionManager.selectionBehavior === 'replace' && (isSelected || state.selectionManager.isSelected(item.nextKey)),
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
98
203
  }
99
204
  )
100
205
  }
101
- ref={cellRef}
102
- {...mergedProps}>
206
+ {...gridCellProps}>
103
207
  <Grid UNSAFE_className={listStyles['react-spectrum-ListViewItem-grid']}>
104
- {showCheckbox && (
105
- <Checkbox
106
- UNSAFE_className={listStyles['react-spectrum-ListViewItem-checkbox']}
107
- {...checkboxProps}
108
- isEmphasized={isEmphasized} />
109
- )}
208
+ {isListDraggable &&
209
+ <div className={listStyles['react-spectrum-ListViewItem-draghandle-container']}>
210
+ {!isDisabled &&
211
+ <FocusRing focusRingClass={classNames(listStyles, 'focus-ring')}>
212
+ <div
213
+ {...buttonProps as React.HTMLAttributes<HTMLElement>}
214
+ className={
215
+ classNames(
216
+ listStyles,
217
+ 'react-spectrum-ListViewItem-draghandle-button'
218
+ )
219
+ }
220
+ style={!isFocusVisibleWithin ? {...visuallyHiddenProps.style} : {}}
221
+ ref={dragButtonRef}
222
+ draggable="true">
223
+ <ListGripper />
224
+ </div>
225
+ </FocusRing>
226
+ }
227
+ </div>
228
+ }
229
+ {isDropTarget && !dropIndicator?.dropIndicatorProps['aria-hidden'] &&
230
+ <div role="button" {...visuallyHiddenProps} {...dropIndicator?.dropIndicatorProps} ref={dropIndicatorRef} />
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>
110
249
  <SlotProvider
111
250
  slots={{
112
- content: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
113
251
  text: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
114
- description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description']},
252
+ description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description'], ...descriptionProps},
115
253
  icon: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-icon'], size: 'M'},
116
254
  image: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-image']},
117
- link: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content'], isQuiet: true},
118
255
  actionButton: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'], isQuiet: true},
119
256
  actionGroup: {
120
257
  UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'],
@@ -123,7 +260,7 @@ export function ListViewItem(props) {
123
260
  },
124
261
  actionMenu: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actionmenu'], isQuiet: true}
125
262
  }}>
126
- {typeof item.rendered === 'string' ? <Content>{item.rendered}</Content> : item.rendered}
263
+ {content}
127
264
  <ClearSlots>
128
265
  {chevron}
129
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
+ }