@react-spectrum/card 3.0.0-alpha.0 → 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.
- package/dist/main.css +1 -2
- package/dist/main.js +1395 -1580
- package/dist/main.js.map +1 -1
- package/dist/module.js +1385 -1488
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +30 -38
- package/dist/types.d.ts.map +1 -1
- package/package.json +22 -21
- package/src/BaseLayout.tsx +13 -1
- package/src/Card.tsx +5 -8
- package/src/CardBase.tsx +24 -64
- package/src/CardView.tsx +52 -23
- package/src/GalleryLayout.tsx +94 -99
- package/src/GridLayout.tsx +40 -29
- package/src/WaterfallLayout.tsx +6 -20
- package/src/index.ts +10 -5
- package/dist/main.css.map +0 -1
- package/src/cardview.css +0 -16
package/src/CardView.tsx
CHANGED
|
@@ -12,17 +12,18 @@
|
|
|
12
12
|
|
|
13
13
|
import {CardBase} from './CardBase';
|
|
14
14
|
import {CardViewContext, useCardViewContext} from './CardViewContext';
|
|
15
|
-
import cardViewStyles from './cardview.css';
|
|
16
15
|
import {classNames, useDOMRef, useStyleProps, useUnwrapDOMRef} from '@react-spectrum/utils';
|
|
17
16
|
import {DOMRef, DOMRefValue, Node} from '@react-types/shared';
|
|
18
17
|
import {GridCollection, useGridState} from '@react-stately/grid';
|
|
19
18
|
// @ts-ignore
|
|
20
19
|
import intlMessages from '../intl/*.json';
|
|
20
|
+
import {mergeProps} from '@react-aria/utils';
|
|
21
21
|
import {ProgressCircle} from '@react-spectrum/progress';
|
|
22
|
-
import React, {ReactElement, useMemo, useRef} from 'react';
|
|
22
|
+
import React, {ReactElement, useCallback, useMemo, useRef} from 'react';
|
|
23
23
|
import {ReusableView} from '@react-stately/virtualizer';
|
|
24
24
|
import {SpectrumCardViewProps} from '@react-types/card';
|
|
25
|
-
import
|
|
25
|
+
import styles from '@adobe/spectrum-css-temp/components/card/vars.css';
|
|
26
|
+
import {useCollator, useLocale, useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
26
27
|
import {useGrid, useGridCell, useGridRow} from '@react-aria/grid';
|
|
27
28
|
import {useListState} from '@react-stately/list';
|
|
28
29
|
import {useProvider} from '@react-spectrum/provider';
|
|
@@ -37,22 +38,16 @@ function CardView<T extends object>(props: SpectrumCardViewProps<T>, ref: DOMRef
|
|
|
37
38
|
renderEmptyState,
|
|
38
39
|
layout,
|
|
39
40
|
loadingState,
|
|
40
|
-
onLoadMore
|
|
41
|
+
onLoadMore,
|
|
42
|
+
cardOrientation = 'vertical'
|
|
41
43
|
} = props;
|
|
44
|
+
|
|
42
45
|
let collator = useCollator({usage: 'search', sensitivity: 'base'});
|
|
43
46
|
let isLoading = loadingState === 'loading' || loadingState === 'loadingMore';
|
|
44
|
-
let cardViewLayout = useMemo(() => typeof layout === 'function' ? new layout({collator}) : layout, [layout, collator]);
|
|
47
|
+
let cardViewLayout = useMemo(() => typeof layout === 'function' ? new layout({collator, cardOrientation, scale}) : layout, [layout, collator, cardOrientation, scale]);
|
|
45
48
|
let layoutType = cardViewLayout.layoutType;
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
if (layoutType === 'grid') {
|
|
49
|
-
cardViewLayout.itemPadding = scale === 'large' ? 116 : 95;
|
|
50
|
-
} else if (layoutType === 'gallery') {
|
|
51
|
-
cardViewLayout.itemPadding = scale === 'large' ? 143 : 114;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let formatMessage = useMessageFormatter(intlMessages);
|
|
50
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
56
51
|
let {direction} = useLocale();
|
|
57
52
|
let {collection} = useListState(props);
|
|
58
53
|
|
|
@@ -77,7 +72,9 @@ function CardView<T extends object>(props: SpectrumCardViewProps<T>, ref: DOMRef
|
|
|
77
72
|
|
|
78
73
|
let state = useGridState({
|
|
79
74
|
...props,
|
|
80
|
-
|
|
75
|
+
selectionMode: cardOrientation === 'horizontal' && layoutType === 'grid' ? 'none' : props.selectionMode,
|
|
76
|
+
collection: gridCollection,
|
|
77
|
+
focusMode: 'cell'
|
|
81
78
|
});
|
|
82
79
|
|
|
83
80
|
cardViewLayout.collection = gridCollection;
|
|
@@ -94,7 +91,6 @@ function CardView<T extends object>(props: SpectrumCardViewProps<T>, ref: DOMRef
|
|
|
94
91
|
type View = ReusableView<Node<T>, unknown>;
|
|
95
92
|
let renderWrapper = (parent: View, reusableView: View) => (
|
|
96
93
|
<VirtualizerItem
|
|
97
|
-
className={classNames(cardViewStyles, 'react-spectrum-CardView-CardWrapper')}
|
|
98
94
|
key={reusableView.key}
|
|
99
95
|
reusableView={reusableView}
|
|
100
96
|
parent={parent} />
|
|
@@ -106,13 +102,22 @@ function CardView<T extends object>(props: SpectrumCardViewProps<T>, ref: DOMRef
|
|
|
106
102
|
focusedKey = focusedItem.parentKey;
|
|
107
103
|
}
|
|
108
104
|
|
|
105
|
+
let margin = cardViewLayout.margin || 0;
|
|
106
|
+
let virtualizer = cardViewLayout.virtualizer;
|
|
107
|
+
let scrollToItem = useCallback((key) => {
|
|
108
|
+
virtualizer && virtualizer.scrollToItem(key, {
|
|
109
|
+
duration: 0,
|
|
110
|
+
offsetY: margin
|
|
111
|
+
});
|
|
112
|
+
}, [margin, virtualizer]);
|
|
113
|
+
|
|
109
114
|
// TODO: does aria-row count and aria-col count need to be modified? Perhaps aria-col count needs to be omitted
|
|
110
115
|
return (
|
|
111
|
-
<CardViewContext.Provider value={{state, isQuiet, layout: cardViewLayout}}>
|
|
116
|
+
<CardViewContext.Provider value={{state, isQuiet, layout: cardViewLayout, cardOrientation}}>
|
|
112
117
|
<Virtualizer
|
|
113
118
|
{...gridProps}
|
|
114
119
|
{...styleProps}
|
|
115
|
-
className={classNames(
|
|
120
|
+
className={classNames(styles, 'spectrum-CardView')}
|
|
116
121
|
ref={domRef}
|
|
117
122
|
focusedKey={focusedKey}
|
|
118
123
|
scrollDirection="vertical"
|
|
@@ -120,7 +125,9 @@ function CardView<T extends object>(props: SpectrumCardViewProps<T>, ref: DOMRef
|
|
|
120
125
|
collection={gridCollection}
|
|
121
126
|
isLoading={isLoading}
|
|
122
127
|
onLoadMore={onLoadMore}
|
|
123
|
-
renderWrapper={renderWrapper}
|
|
128
|
+
renderWrapper={renderWrapper}
|
|
129
|
+
transitionDuration={isLoading ? 160 : 220}
|
|
130
|
+
scrollToItem={scrollToItem}>
|
|
124
131
|
{(type, item) => {
|
|
125
132
|
if (type === 'item') {
|
|
126
133
|
return (
|
|
@@ -131,7 +138,7 @@ function CardView<T extends object>(props: SpectrumCardViewProps<T>, ref: DOMRef
|
|
|
131
138
|
<CenteredWrapper>
|
|
132
139
|
<ProgressCircle
|
|
133
140
|
isIndeterminate
|
|
134
|
-
aria-label={state.collection.size > 0 ?
|
|
141
|
+
aria-label={state.collection.size > 0 ? stringFormatter.format('loadingMore') : stringFormatter.format('loading')} />
|
|
135
142
|
</CenteredWrapper>
|
|
136
143
|
);
|
|
137
144
|
} else if (type === 'placeholder') {
|
|
@@ -159,7 +166,7 @@ function CenteredWrapper({children}) {
|
|
|
159
166
|
<div
|
|
160
167
|
role="row"
|
|
161
168
|
aria-rowindex={state.collection.size + 1}
|
|
162
|
-
className={classNames(
|
|
169
|
+
className={classNames(styles, 'spectrum-CardView-centeredWrapper')}>
|
|
163
170
|
<div role="gridcell">
|
|
164
171
|
{children}
|
|
165
172
|
</div>
|
|
@@ -179,7 +186,7 @@ function InternalCard(props) {
|
|
|
179
186
|
let cellRef = useRef<DOMRefValue<HTMLDivElement>>();
|
|
180
187
|
let unwrappedRef = useUnwrapDOMRef(cellRef);
|
|
181
188
|
|
|
182
|
-
let {rowProps} = useGridRow({
|
|
189
|
+
let {rowProps: gridRowProps} = useGridRow({
|
|
183
190
|
node: item,
|
|
184
191
|
isVirtualized: true
|
|
185
192
|
}, state, rowRef);
|
|
@@ -189,13 +196,35 @@ function InternalCard(props) {
|
|
|
189
196
|
focusMode: 'cell'
|
|
190
197
|
}, state, unwrappedRef);
|
|
191
198
|
|
|
199
|
+
// Prevent space key from scrolling the CardView if triggered on a disabled item or on a Card in a selectionMode="none" CardView.
|
|
200
|
+
let allowsInteraction = state.selectionManager.selectionMode !== 'none';
|
|
201
|
+
let isDisabled = !allowsInteraction || state.disabledKeys.has(item.key);
|
|
202
|
+
|
|
203
|
+
let onKeyDown = (e) => {
|
|
204
|
+
if (e.key === ' ' && isDisabled) {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
let rowProps = mergeProps(
|
|
210
|
+
gridRowProps,
|
|
211
|
+
{onKeyDown}
|
|
212
|
+
);
|
|
192
213
|
|
|
193
214
|
if (layoutType === 'grid' || layoutType === 'gallery') {
|
|
194
215
|
isQuiet = true;
|
|
195
216
|
}
|
|
196
217
|
|
|
218
|
+
if (layoutType !== 'grid') {
|
|
219
|
+
cardOrientation = 'vertical';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// We don't want to focus the checkbox (or any other focusable elements) within the Card
|
|
223
|
+
// when pressing the arrow keys so we delete the key down handler here. Arrow key navigation between
|
|
224
|
+
// the cards in the CardView is handled by useGrid => useSelectableCollection instead.
|
|
225
|
+
delete gridCellProps.onKeyDownCapture;
|
|
197
226
|
return (
|
|
198
|
-
<div {...rowProps} ref={rowRef}
|
|
227
|
+
<div {...rowProps} ref={rowRef} className={classNames(styles, 'spectrum-CardView-row')}>
|
|
199
228
|
<CardBase
|
|
200
229
|
ref={cellRef}
|
|
201
230
|
articleProps={gridCellProps}
|
package/src/GalleryLayout.tsx
CHANGED
|
@@ -30,7 +30,7 @@ export interface GalleryLayoutOptions extends BaseLayoutOptions {
|
|
|
30
30
|
itemSpacing?: Size,
|
|
31
31
|
/**
|
|
32
32
|
* The vertical padding for an item.
|
|
33
|
-
* @default
|
|
33
|
+
* @default 78
|
|
34
34
|
*/
|
|
35
35
|
itemPadding?: number,
|
|
36
36
|
/**
|
|
@@ -43,22 +43,14 @@ export interface GalleryLayoutOptions extends BaseLayoutOptions {
|
|
|
43
43
|
* will be targeted.
|
|
44
44
|
* @type {number}
|
|
45
45
|
*/
|
|
46
|
-
threshold?: number
|
|
47
|
-
/**
|
|
48
|
-
* The margin around the grid view between the edges and the items.
|
|
49
|
-
* @default 24
|
|
50
|
-
*/
|
|
51
|
-
margin?: number // TODO: Perhaps should accept Responsive<DimensionValue>
|
|
46
|
+
threshold?: number
|
|
52
47
|
}
|
|
53
48
|
|
|
54
|
-
// TODO: copied from V2, update this with the proper spectrum values
|
|
55
|
-
// Should these be affected by Scale as well?
|
|
56
49
|
const DEFAULT_OPTIONS = {
|
|
57
50
|
S: {
|
|
58
51
|
idealRowHeight: 112,
|
|
59
52
|
minItemSize: new Size(96, 96),
|
|
60
53
|
itemSpacing: new Size(8, 16),
|
|
61
|
-
// TODO: will need to update as well
|
|
62
54
|
itemPadding: 24,
|
|
63
55
|
dropSpacing: 50,
|
|
64
56
|
margin: 8
|
|
@@ -67,8 +59,10 @@ const DEFAULT_OPTIONS = {
|
|
|
67
59
|
idealRowHeight: 208,
|
|
68
60
|
minItemSize: new Size(136, 136),
|
|
69
61
|
itemSpacing: new Size(18, 18),
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
itemPadding: {
|
|
63
|
+
'medium': 78,
|
|
64
|
+
'large': 99
|
|
65
|
+
},
|
|
72
66
|
dropSpacing: 100,
|
|
73
67
|
margin: 24
|
|
74
68
|
}
|
|
@@ -76,7 +70,6 @@ const DEFAULT_OPTIONS = {
|
|
|
76
70
|
|
|
77
71
|
export class GalleryLayout<T> extends BaseLayout<T> {
|
|
78
72
|
protected idealRowHeight: number;
|
|
79
|
-
protected margin: number;
|
|
80
73
|
protected itemSpacing: Size;
|
|
81
74
|
itemPadding: number;
|
|
82
75
|
protected minItemSize: Size;
|
|
@@ -84,11 +77,10 @@ export class GalleryLayout<T> extends BaseLayout<T> {
|
|
|
84
77
|
|
|
85
78
|
constructor(options: GalleryLayoutOptions = {}) {
|
|
86
79
|
super(options);
|
|
87
|
-
// TODO: restore cardSize option when we support different size cards
|
|
88
80
|
let cardSize = 'L';
|
|
89
81
|
this.idealRowHeight = options.idealRowHeight || DEFAULT_OPTIONS[cardSize].idealRowHeight;
|
|
90
82
|
this.itemSpacing = options.itemSpacing || DEFAULT_OPTIONS[cardSize].itemSpacing;
|
|
91
|
-
this.itemPadding = options.itemPadding != null ? options.itemPadding : DEFAULT_OPTIONS[cardSize].itemPadding;
|
|
83
|
+
this.itemPadding = options.itemPadding != null ? options.itemPadding : DEFAULT_OPTIONS[cardSize].itemPadding[this.scale];
|
|
92
84
|
this.minItemSize = options.minItemSize || DEFAULT_OPTIONS[cardSize].minItemSize;
|
|
93
85
|
this.threshold = options.threshold || 1;
|
|
94
86
|
this.margin = options.margin != null ? options.margin : DEFAULT_OPTIONS[cardSize].margin;
|
|
@@ -146,104 +138,107 @@ export class GalleryLayout<T> extends BaseLayout<T> {
|
|
|
146
138
|
let y = this.margin;
|
|
147
139
|
let availableWidth = visibleWidth - this.margin * 2;
|
|
148
140
|
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
ratio =
|
|
159
|
-
|
|
160
|
-
|
|
141
|
+
// If avaliable width is not greater than 0, skip node layout calculations
|
|
142
|
+
if (availableWidth > 0) {
|
|
143
|
+
// Compute aspect ratios for all of the items, and the total width if all items were on in a single row.
|
|
144
|
+
let ratios = [];
|
|
145
|
+
let totalWidth = 0;
|
|
146
|
+
let minRatio = this.minItemSize.width / this.minItemSize.height;
|
|
147
|
+
let maxRatio = availableWidth / this.minItemSize.height;
|
|
148
|
+
|
|
149
|
+
for (let node of this.collection) {
|
|
150
|
+
let ratio = node.props.width / node.props.height;
|
|
151
|
+
if (ratio < minRatio) {
|
|
152
|
+
ratio = minRatio;
|
|
153
|
+
} else if (ratio > maxRatio && ratio !== minRatio) {
|
|
154
|
+
ratio = maxRatio;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let itemWidth = ratio * this.minItemSize.height;
|
|
158
|
+
ratios.push(ratio);
|
|
159
|
+
totalWidth += itemWidth;
|
|
161
160
|
}
|
|
162
161
|
|
|
163
|
-
|
|
164
|
-
ratios.push(ratio);
|
|
165
|
-
totalWidth += itemWidth;
|
|
166
|
-
}
|
|
162
|
+
totalWidth += this.itemSpacing.width * (this.collection.size - 1);
|
|
167
163
|
|
|
168
|
-
|
|
164
|
+
// Determine how many rows we'll need, and partition the items into rows
|
|
165
|
+
// using the aspect ratios as weights.
|
|
166
|
+
let rows = Math.max(1, Math.ceil(totalWidth / availableWidth));
|
|
167
|
+
// if the available width can't hold two items, then every item will get its own row
|
|
168
|
+
// this leads to a faster run through linear partition and more dependable output for small row widths
|
|
169
|
+
if (availableWidth <= (this.minItemSize.width * 2) + (this.itemPadding * 2)) {
|
|
170
|
+
rows = this.collection.size;
|
|
171
|
+
}
|
|
169
172
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
let rows = Math.max(1, Math.ceil(totalWidth / availableWidth));
|
|
173
|
-
// if the available width can't hold two items, then every item will get its own row
|
|
174
|
-
// this leads to a faster run through linear partition and more dependable output for small row widths
|
|
175
|
-
if (availableWidth <= (this.minItemSize.width * 2) + (this.itemPadding * 2)) {
|
|
176
|
-
rows = this.collection.size;
|
|
177
|
-
}
|
|
173
|
+
let weightedRatios = ratios.map(ratio => ratio < this.threshold ? ratio + (0.5 * (1 / ratio)) : ratio);
|
|
174
|
+
let partition = linearPartition(weightedRatios, rows);
|
|
178
175
|
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
let index = 0;
|
|
177
|
+
for (let row of partition) {
|
|
178
|
+
// Compute the total weight for this row
|
|
179
|
+
let totalWeight = 0;
|
|
180
|
+
for (let j = index; j < index + row.length; j++) {
|
|
181
|
+
totalWeight += ratios[j];
|
|
182
|
+
}
|
|
181
183
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// Compute the total weight for this row
|
|
185
|
-
let totalWeight = 0;
|
|
186
|
-
for (let j = index; j < index + row.length; j++) {
|
|
187
|
-
totalWeight += ratios[j];
|
|
188
|
-
}
|
|
184
|
+
// Determine the row height based on the total available width and weight of this row.
|
|
185
|
+
let bestRowHeight = (availableWidth - (row.length - 1) * this.itemSpacing.width) / totalWeight;
|
|
189
186
|
|
|
190
|
-
|
|
191
|
-
|
|
187
|
+
// if this is the last row and the row height is >2x the ideal row height, then cap to the ideal height
|
|
188
|
+
// probably doing this because if the last row has one extremely tall image, then the row becomes huge
|
|
189
|
+
// though that can happen anywhere if a row has lots of tall images... so i'm not sure why this one matters
|
|
190
|
+
if (row === partition[partition.length - 1] && bestRowHeight > this.idealRowHeight * 2) {
|
|
191
|
+
bestRowHeight = this.idealRowHeight;
|
|
192
|
+
}
|
|
193
|
+
let itemHeight = Math.round(bestRowHeight) + this.itemPadding;
|
|
194
|
+
let x = this.margin;
|
|
195
|
+
|
|
196
|
+
// if any items are going to end up too small, add a bit of width to them and subtract it from wider objects
|
|
197
|
+
let widths = [];
|
|
198
|
+
for (let j = index; j < index + row.length; j++) {
|
|
199
|
+
let width = Math.round(bestRowHeight * ratios[j]);
|
|
200
|
+
widths.push([j - index, width]);
|
|
201
|
+
}
|
|
202
|
+
this._distributeWidths(widths);
|
|
203
|
+
|
|
204
|
+
// Create items for this row.
|
|
205
|
+
for (let j = index; j < index + row.length; j++) {
|
|
206
|
+
let node = this.collection.rows[j];
|
|
207
|
+
let itemWidth = Math.max(widths[j - index][1], this.minItemSize.width);
|
|
208
|
+
let rect = new Rect(x, y, itemWidth, itemHeight);
|
|
209
|
+
let layoutInfo = new LayoutInfo(node.type, node.key, rect);
|
|
210
|
+
layoutInfo.allowOverflow = true;
|
|
211
|
+
this.layoutInfos.set(node.key, layoutInfo);
|
|
212
|
+
x += itemWidth + this.itemSpacing.width;
|
|
213
|
+
}
|
|
192
214
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// though that can happen anywhere if a row has lots of tall images... so i'm not sure why this one matters
|
|
196
|
-
if (row === partition[partition.length - 1] && bestRowHeight > this.idealRowHeight * 2) {
|
|
197
|
-
bestRowHeight = this.idealRowHeight;
|
|
198
|
-
}
|
|
199
|
-
let itemHeight = Math.round(bestRowHeight) + this.itemPadding;
|
|
200
|
-
let x = this.margin;
|
|
201
|
-
|
|
202
|
-
// if any items are going to end up too small, add a bit of width to them and subtract it from wider objects
|
|
203
|
-
let widths = [];
|
|
204
|
-
for (let j = index; j < index + row.length; j++) {
|
|
205
|
-
let width = Math.round(bestRowHeight * ratios[j]);
|
|
206
|
-
widths.push([j - index, width]);
|
|
207
|
-
}
|
|
208
|
-
this._distributeWidths(widths);
|
|
209
|
-
|
|
210
|
-
// Create items for this row.
|
|
211
|
-
for (let j = index; j < index + row.length; j++) {
|
|
212
|
-
let node = this.collection.rows[j];
|
|
213
|
-
let itemWidth = Math.max(widths[j - index][1], this.minItemSize.width);
|
|
214
|
-
let rect = new Rect(x, y, itemWidth, itemHeight);
|
|
215
|
-
let layoutInfo = new LayoutInfo(node.type, node.key, rect);
|
|
216
|
-
this.layoutInfos.set(node.key, layoutInfo);
|
|
217
|
-
x += itemWidth + this.itemSpacing.width;
|
|
215
|
+
y += itemHeight + this.itemSpacing.height;
|
|
216
|
+
index += row.length;
|
|
218
217
|
}
|
|
219
218
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
if (this.isLoading) {
|
|
220
|
+
let loaderY = y;
|
|
221
|
+
let loaderHeight = 60;
|
|
222
|
+
// If there aren't any items, make loader take all avaliable room and remove margin from y calculation
|
|
223
|
+
// so it doesn't scroll
|
|
224
|
+
if (this.collection.size === 0) {
|
|
225
|
+
loaderY = 0;
|
|
226
|
+
loaderHeight = visibleHeight || 60;
|
|
227
|
+
}
|
|
223
228
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// so it doesn't scroll
|
|
229
|
-
if (this.collection.size === 0) {
|
|
230
|
-
loaderY = 0;
|
|
231
|
-
loaderHeight = visibleHeight || 60;
|
|
229
|
+
let rect = new Rect(0, loaderY, visibleWidth, loaderHeight);
|
|
230
|
+
let loader = new LayoutInfo('loader', 'loader', rect);
|
|
231
|
+
this.layoutInfos.set('loader', loader);
|
|
232
|
+
y = loader.rect.maxY;
|
|
232
233
|
}
|
|
233
234
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (this.collection.size === 0 && !this.isLoading) {
|
|
241
|
-
let rect = new Rect(0, 0, visibleWidth, visibleHeight);
|
|
242
|
-
let placeholder = new LayoutInfo('placeholder', 'placeholder', rect);
|
|
243
|
-
this.layoutInfos.set('placeholder', placeholder);
|
|
244
|
-
y = placeholder.rect.maxY;
|
|
235
|
+
if (this.collection.size === 0 && !this.isLoading) {
|
|
236
|
+
let rect = new Rect(0, 0, visibleWidth, visibleHeight);
|
|
237
|
+
let placeholder = new LayoutInfo('placeholder', 'placeholder', rect);
|
|
238
|
+
this.layoutInfos.set('placeholder', placeholder);
|
|
239
|
+
y = placeholder.rect.maxY;
|
|
240
|
+
}
|
|
245
241
|
}
|
|
246
|
-
|
|
247
242
|
this.contentSize = new Size(visibleWidth, y);
|
|
248
243
|
}
|
|
249
244
|
}
|
package/src/GridLayout.tsx
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import {BaseLayout, BaseLayoutOptions} from './BaseLayout';
|
|
14
14
|
import {Key} from 'react';
|
|
15
15
|
import {LayoutInfo, Rect, Size} from '@react-stately/virtualizer';
|
|
16
|
-
import {Node} from '@react-types/shared';
|
|
16
|
+
import {Node, Orientation} from '@react-types/shared';
|
|
17
17
|
|
|
18
18
|
export interface GridLayoutOptions extends BaseLayoutOptions {
|
|
19
19
|
// /**
|
|
@@ -22,7 +22,7 @@ export interface GridLayoutOptions extends BaseLayoutOptions {
|
|
|
22
22
|
// cardSize?: 'S' | 'M' | 'L',
|
|
23
23
|
/**
|
|
24
24
|
* The minimum item size.
|
|
25
|
-
* @default 208 x 208
|
|
25
|
+
* @default 208 x 208 for horizontal card orientation. 102 x 102 for vertical card orientation.
|
|
26
26
|
*/
|
|
27
27
|
minItemSize?: Size,
|
|
28
28
|
/**
|
|
@@ -30,11 +30,6 @@ export interface GridLayoutOptions extends BaseLayoutOptions {
|
|
|
30
30
|
* @default Infinity
|
|
31
31
|
*/
|
|
32
32
|
maxItemSize?: Size,
|
|
33
|
-
/**
|
|
34
|
-
* The margin around the grid view between the edges and the items.
|
|
35
|
-
* @default 24
|
|
36
|
-
*/
|
|
37
|
-
margin?: number, // TODO: Perhaps should accept Responsive<DimensionValue>
|
|
38
33
|
/**
|
|
39
34
|
* The minimum space required between items.
|
|
40
35
|
* @default 18 x 18
|
|
@@ -46,18 +41,23 @@ export interface GridLayoutOptions extends BaseLayoutOptions {
|
|
|
46
41
|
*/
|
|
47
42
|
maxColumns?: number,
|
|
48
43
|
/**
|
|
49
|
-
* The
|
|
44
|
+
* The additional padding along the card's main axis. Affects the sizing of the content area following the card image.
|
|
50
45
|
* @default 95
|
|
51
46
|
*/
|
|
52
|
-
itemPadding?: number
|
|
47
|
+
itemPadding?: number,
|
|
48
|
+
/**
|
|
49
|
+
* The orientation of the cards withn the grid.
|
|
50
|
+
* @default vertical
|
|
51
|
+
*/
|
|
52
|
+
cardOrientation?: Orientation
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
// TODO: copied from V2, update this with the proper spectrum values
|
|
56
|
-
// Should these be affected by Scale as well?
|
|
57
55
|
const DEFAULT_OPTIONS = {
|
|
58
56
|
S: {
|
|
59
57
|
itemPadding: 20,
|
|
60
|
-
minItemSize:
|
|
58
|
+
minItemSize: {
|
|
59
|
+
'vertical': new Size(96, 96)
|
|
60
|
+
},
|
|
61
61
|
maxItemSize: new Size(Infinity, Infinity),
|
|
62
62
|
margin: 8,
|
|
63
63
|
minSpace: new Size(6, 6),
|
|
@@ -65,10 +65,20 @@ const DEFAULT_OPTIONS = {
|
|
|
65
65
|
dropSpacing: 50
|
|
66
66
|
},
|
|
67
67
|
L: {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
itemPadding: {
|
|
69
|
+
'vertical': {
|
|
70
|
+
'medium': 78,
|
|
71
|
+
'large': 98
|
|
72
|
+
},
|
|
73
|
+
'horizontal': {
|
|
74
|
+
'medium': 150,
|
|
75
|
+
'large': 170
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
minItemSize: {
|
|
79
|
+
'vertical': new Size(208, 208),
|
|
80
|
+
'horizontal': new Size(102, 102)
|
|
81
|
+
},
|
|
72
82
|
maxItemSize: new Size(Infinity, Infinity),
|
|
73
83
|
margin: 24,
|
|
74
84
|
minSpace: new Size(18, 18),
|
|
@@ -80,10 +90,10 @@ const DEFAULT_OPTIONS = {
|
|
|
80
90
|
export class GridLayout<T> extends BaseLayout<T> {
|
|
81
91
|
protected minItemSize: Size;
|
|
82
92
|
protected maxItemSize: Size;
|
|
83
|
-
protected margin: number;
|
|
84
93
|
protected minSpace: Size;
|
|
85
94
|
protected maxColumns: number;
|
|
86
95
|
itemPadding: number;
|
|
96
|
+
cardOrientation: Orientation;
|
|
87
97
|
protected itemSize: Size;
|
|
88
98
|
protected numColumns: number;
|
|
89
99
|
protected numRows: number;
|
|
@@ -91,14 +101,14 @@ export class GridLayout<T> extends BaseLayout<T> {
|
|
|
91
101
|
|
|
92
102
|
constructor(options: GridLayoutOptions = {}) {
|
|
93
103
|
super(options);
|
|
94
|
-
// TODO: restore cardSize option when we support different size cards
|
|
95
104
|
let cardSize = 'L';
|
|
96
|
-
this.
|
|
105
|
+
this.cardOrientation = options.cardOrientation || 'vertical';
|
|
106
|
+
this.minItemSize = options.minItemSize || DEFAULT_OPTIONS[cardSize].minItemSize[this.cardOrientation];
|
|
97
107
|
this.maxItemSize = options.maxItemSize || DEFAULT_OPTIONS[cardSize].maxItemSize;
|
|
98
108
|
this.margin = options.margin != null ? options.margin : DEFAULT_OPTIONS[cardSize].margin;
|
|
99
109
|
this.minSpace = options.minSpace || DEFAULT_OPTIONS[cardSize].minSpace;
|
|
100
110
|
this.maxColumns = options.maxColumns || DEFAULT_OPTIONS[cardSize].maxColumns;
|
|
101
|
-
this.itemPadding = options.itemPadding != null ? options.itemPadding : DEFAULT_OPTIONS[cardSize].itemPadding;
|
|
111
|
+
this.itemPadding = options.itemPadding != null ? options.itemPadding : DEFAULT_OPTIONS[cardSize].itemPadding[this.cardOrientation][this.scale];
|
|
102
112
|
this.itemSize = null;
|
|
103
113
|
this.numColumns = 0;
|
|
104
114
|
this.numRows = 0;
|
|
@@ -109,7 +119,6 @@ export class GridLayout<T> extends BaseLayout<T> {
|
|
|
109
119
|
return 'grid';
|
|
110
120
|
}
|
|
111
121
|
|
|
112
|
-
// TODO: Below functions From V2 Maybe don't need this? Might be a short cut for getting all visible rects since otherwise we'd have to iterate across all nodes
|
|
113
122
|
getIndexAtPoint(x, y, allowInsertingAtEnd = false) {
|
|
114
123
|
let itemHeight = this.itemSize.height + this.minSpace.height;
|
|
115
124
|
let itemWidth = this.itemSize.width + this.horizontalSpacing;
|
|
@@ -141,7 +150,7 @@ export class GridLayout<T> extends BaseLayout<T> {
|
|
|
141
150
|
for (let index = firstVisibleItem; index <= lastVisibleItem; index++) {
|
|
142
151
|
let keyFromIndex = this.collection.rows[index].key;
|
|
143
152
|
let layoutInfo = this.layoutInfos.get(keyFromIndex);
|
|
144
|
-
if (this.isVisible(layoutInfo, rect)) {
|
|
153
|
+
if (layoutInfo && this.isVisible(layoutInfo, rect)) {
|
|
145
154
|
res.push(layoutInfo);
|
|
146
155
|
}
|
|
147
156
|
}
|
|
@@ -159,10 +168,13 @@ export class GridLayout<T> extends BaseLayout<T> {
|
|
|
159
168
|
buildCollection() {
|
|
160
169
|
let visibleWidth = this.virtualizer.visibleRect.width;
|
|
161
170
|
let visibleHeight = this.virtualizer.visibleRect.height;
|
|
171
|
+
let horizontalItemPadding = this.cardOrientation === 'horizontal' ? this.itemPadding : 0;
|
|
172
|
+
let verticalItemPadding = this.cardOrientation === 'vertical' ? this.itemPadding : 0;
|
|
173
|
+
let minCardWidth = this.minItemSize.width + horizontalItemPadding;
|
|
162
174
|
|
|
163
175
|
// Compute the number of rows and columns needed to display the content
|
|
164
176
|
let availableWidth = visibleWidth - this.margin * 2;
|
|
165
|
-
let columns = Math.floor((availableWidth + this.minSpace.width) / (
|
|
177
|
+
let columns = Math.floor((availableWidth + this.minSpace.width) / (minCardWidth + this.minSpace.width));
|
|
166
178
|
this.numColumns = Math.max(1, Math.min(this.maxColumns, columns));
|
|
167
179
|
this.numRows = Math.ceil(this.collection.size / this.numColumns);
|
|
168
180
|
|
|
@@ -171,13 +183,11 @@ export class GridLayout<T> extends BaseLayout<T> {
|
|
|
171
183
|
|
|
172
184
|
// Compute the item width based on the space available
|
|
173
185
|
let itemWidth = Math.floor(width / this.numColumns);
|
|
174
|
-
itemWidth = Math.max(
|
|
175
|
-
|
|
186
|
+
itemWidth = Math.max(minCardWidth, Math.min(this.maxItemSize.width, itemWidth));
|
|
176
187
|
// Compute the item height, which is proportional to the item width
|
|
177
|
-
let t = ((itemWidth -
|
|
178
|
-
let itemHeight = this.minItemSize.height + this.minItemSize.height * t;
|
|
179
|
-
itemHeight = Math.max(this.minItemSize.height, Math.min(this.maxItemSize.height, itemHeight)) +
|
|
180
|
-
|
|
188
|
+
let t = ((itemWidth - minCardWidth) / minCardWidth);
|
|
189
|
+
let itemHeight = Math.floor(this.minItemSize.height + this.minItemSize.height * t);
|
|
190
|
+
itemHeight = Math.max(this.minItemSize.height, Math.min(this.maxItemSize.height, itemHeight)) + verticalItemPadding;
|
|
181
191
|
this.itemSize = new Size(itemWidth, itemHeight);
|
|
182
192
|
|
|
183
193
|
// Compute the horizontal spacing and content height
|
|
@@ -226,6 +236,7 @@ export class GridLayout<T> extends BaseLayout<T> {
|
|
|
226
236
|
let rect = new Rect(x, y, this.itemSize.width, this.itemSize.height);
|
|
227
237
|
// TODO: Perhaps have it so that the child key for each row is stored with the layoutInfo?
|
|
228
238
|
let layoutInfo = new LayoutInfo(node.type, node.key, rect);
|
|
239
|
+
layoutInfo.allowOverflow = true;
|
|
229
240
|
this.layoutInfos.set(node.key, layoutInfo);
|
|
230
241
|
return layoutInfo;
|
|
231
242
|
}
|
package/src/WaterfallLayout.tsx
CHANGED
|
@@ -26,11 +26,6 @@ export interface WaterfallLayoutOptions extends BaseLayoutOptions {
|
|
|
26
26
|
* @default Infinity
|
|
27
27
|
*/
|
|
28
28
|
maxItemSize?: Size,
|
|
29
|
-
/**
|
|
30
|
-
* The margin around the grid view between the edges and the items.
|
|
31
|
-
* @default 24
|
|
32
|
-
*/
|
|
33
|
-
margin?: number, // TODO: Perhaps should accept Responsive<DimensionValue>
|
|
34
29
|
/**
|
|
35
30
|
* The minimum space required between items.
|
|
36
31
|
* @default 18 x 18
|
|
@@ -40,22 +35,15 @@ export interface WaterfallLayoutOptions extends BaseLayoutOptions {
|
|
|
40
35
|
* The maximum number of columns.
|
|
41
36
|
* @default Infinity
|
|
42
37
|
*/
|
|
43
|
-
maxColumns?: number
|
|
44
|
-
/**
|
|
45
|
-
* The vertical padding for an item.
|
|
46
|
-
* @default 56
|
|
47
|
-
*/
|
|
48
|
-
itemPadding?: number
|
|
38
|
+
maxColumns?: number
|
|
49
39
|
}
|
|
50
40
|
|
|
51
41
|
// TODO: this didn't have any options that varied with card size, should it have?
|
|
52
42
|
export class WaterfallLayout<T> extends BaseLayout<T> implements KeyboardDelegate {
|
|
53
43
|
protected minItemSize: Size;
|
|
54
44
|
protected maxItemSize: Size;
|
|
55
|
-
protected margin: number;
|
|
56
45
|
protected minSpace: Size;
|
|
57
46
|
protected maxColumns: number;
|
|
58
|
-
itemPadding: number;
|
|
59
47
|
protected numColumns: number;
|
|
60
48
|
protected itemWidth: number;
|
|
61
49
|
protected horizontalSpacing: number;
|
|
@@ -69,8 +57,6 @@ export class WaterfallLayout<T> extends BaseLayout<T> implements KeyboardDelegat
|
|
|
69
57
|
this.margin = options.margin != null ? options.margin : 24;
|
|
70
58
|
this.minSpace = options.minSpace || new Size(18, 18);
|
|
71
59
|
this.maxColumns = options.maxColumns || Infinity;
|
|
72
|
-
// TODO: not entirely sure what this is for since the layout will automatically shift itself to the correct vertical space for the card
|
|
73
|
-
this.itemPadding = options.itemPadding != null ? options.itemPadding : 56;
|
|
74
60
|
|
|
75
61
|
this.itemWidth = 0;
|
|
76
62
|
this.numColumns = 0;
|
|
@@ -119,8 +105,8 @@ export class WaterfallLayout<T> extends BaseLayout<T> implements KeyboardDelegat
|
|
|
119
105
|
} else if (node.props.width && node.props.height) {
|
|
120
106
|
let nodeWidth = node.props.width;
|
|
121
107
|
let nodeHeight = node.props.height;
|
|
122
|
-
let scaledHeight = Math.round(
|
|
123
|
-
height = Math.max(this.minItemSize.height, Math.min(this.maxItemSize.height, scaledHeight))
|
|
108
|
+
let scaledHeight = Math.round(nodeHeight * ((itemWidth) / nodeWidth));
|
|
109
|
+
height = Math.max(this.minItemSize.height, Math.min(this.maxItemSize.height, scaledHeight));
|
|
124
110
|
} else {
|
|
125
111
|
height = itemWidth;
|
|
126
112
|
}
|
|
@@ -133,6 +119,7 @@ export class WaterfallLayout<T> extends BaseLayout<T> implements KeyboardDelegat
|
|
|
133
119
|
let rect = new Rect(x, y, itemWidth, height);
|
|
134
120
|
let layoutInfo = new LayoutInfo(node.type, key, rect);
|
|
135
121
|
layoutInfo.estimatedSize = estimatedSize;
|
|
122
|
+
layoutInfo.allowOverflow = true;
|
|
136
123
|
this.layoutInfos.set(key, layoutInfo);
|
|
137
124
|
|
|
138
125
|
// TODO: From v2 figure out this bit, when does this get called and what to replace this.collectionView._transaction with?
|
|
@@ -185,10 +172,9 @@ export class WaterfallLayout<T> extends BaseLayout<T> implements KeyboardDelegat
|
|
|
185
172
|
// TODO: also not sure about copying layout info vs mutating it. Listlayout does the below
|
|
186
173
|
// but I feel that is because it actually maintained a layoutNode map cache which this doesn't have
|
|
187
174
|
let newLayoutInfo = layoutInfo.copy();
|
|
188
|
-
newLayoutInfo.rect.height = size.height;
|
|
175
|
+
newLayoutInfo.rect.height = size.height < 600 ? size.height : 600;
|
|
176
|
+
newLayoutInfo.estimatedSize = false;
|
|
189
177
|
this.layoutInfos.set(key, newLayoutInfo);
|
|
190
|
-
// TODO: v2 had layoutInfo.estimatedSize = view.estimatedSize || false; but we can't do the same here?
|
|
191
|
-
layoutInfo.estimatedSize = false;
|
|
192
178
|
return true;
|
|
193
179
|
}
|
|
194
180
|
|