diff --git a/packages/@react-aria/dnd/src/DragPreview.tsx b/packages/@react-aria/dnd/src/DragPreview.tsx
index 8b49156cbb5..7684602e547 100644
--- a/packages/@react-aria/dnd/src/DragPreview.tsx
+++ b/packages/@react-aria/dnd/src/DragPreview.tsx
@@ -74,7 +74,7 @@ React.forwardRef(function DragPreview(props: DragPreviewProps, ref: ForwardedRef
}
return (
-
+
{children}
);
diff --git a/packages/@react-aria/dnd/test/dnd.test.js b/packages/@react-aria/dnd/test/dnd.test.js
index e470f91ae33..36f984c5179 100644
--- a/packages/@react-aria/dnd/test/dnd.test.js
+++ b/packages/@react-aria/dnd/test/dnd.test.js
@@ -1337,8 +1337,8 @@ describe('useDrag and useDrop', function () {
top: 0,
x: 0,
y: 0,
- width: this.style.position === 'absolute' ? 20 : 100,
- height: this.style.position === 'absolute' ? 20 : 50
+ width: this.style.position === 'fixed' ? 20 : 100,
+ height: this.style.position === 'fixed' ? 20 : 50
};
});
@@ -1364,8 +1364,8 @@ describe('useDrag and useDrop', function () {
top: 0,
x: 0,
y: 0,
- width: this.style.position === 'absolute' ? 20 : 100,
- height: this.style.position === 'absolute' ? 20 : 50
+ width: this.style.position === 'fixed' ? 20 : 100,
+ height: this.style.position === 'fixed' ? 20 : 50
};
});
@@ -1391,8 +1391,8 @@ describe('useDrag and useDrop', function () {
top: 0,
x: 0,
y: 0,
- width: this.style.position === 'absolute' ? 20 : 100,
- height: this.style.position === 'absolute' ? 20 : 50
+ width: this.style.position === 'fixed' ? 20 : 100,
+ height: this.style.position === 'fixed' ? 20 : 50
};
});
diff --git a/packages/@react-spectrum/s2/src/TableView.tsx b/packages/@react-spectrum/s2/src/TableView.tsx
index b7af9870fc1..38540ac0b9e 100644
--- a/packages/@react-spectrum/s2/src/TableView.tsx
+++ b/packages/@react-spectrum/s2/src/TableView.tsx
@@ -10,27 +10,21 @@
* governing permissions and limitations under the License.
*/
-import {ActionButton, ActionButtonContext} from './ActionButton';
import {baseColor, colorMix, focusRing, fontRelative, lightDark, space, style} from '../style' with {type: 'macro'};
import {
Button,
- ButtonContext,
CellRenderProps,
Collection,
ColumnRenderProps,
ColumnResizer,
ContextValue,
- DEFAULT_SLOT,
- Form,
Key,
- OverlayTriggerStateContext,
Provider,
Cell as RACCell,
CellProps as RACCellProps,
CheckboxContext as RACCheckboxContext,
Column as RACColumn,
ColumnProps as RACColumnProps,
- Popover as RACPopover,
Row as RACRow,
RowProps as RACRowProps,
Table as RACTable,
@@ -50,16 +44,11 @@ import {
useTableOptions,
Virtualizer
} from 'react-aria-components';
-import {ButtonGroup} from './ButtonGroup';
-import {centerPadding, colorScheme, controlFont, getAllowedOverrides, StylesPropWithHeight, UnsafeStyles} from './style-utils' with {type: 'macro'};
+import {centerPadding, controlFont, getAllowedOverrides, StylesPropWithHeight, UnsafeStyles} from './style-utils' with {type: 'macro'};
import {Checkbox} from './Checkbox';
-import Checkmark from '../s2wf-icons/S2_Icon_Checkmark_20_N.svg';
import Chevron from '../ui-icons/Chevron';
-import Close from '../s2wf-icons/S2_Icon_Close_20_N.svg';
import {ColumnSize} from '@react-types/table';
-import {CustomDialog, DialogContainer} from '..';
import {DOMRef, DOMRefValue, forwardRefType, GlobalDOMAttributes, LoadingState, Node} from '@react-types/shared';
-import {getActiveElement, getOwnerDocument, useLayoutEffect, useObjectRef} from '@react-aria/utils';
import {GridNode} from '@react-types/grid';
import {IconContext} from './Icon';
// @ts-ignore
@@ -69,12 +58,11 @@ import {Menu, MenuItem, MenuSection, MenuTrigger} from './Menu';
import Nubbin from '../ui-icons/S2_MoveHorizontalTableWidget.svg';
import {ProgressCircle} from './ProgressCircle';
import {raw} from '../style/style-macro' with {type: 'macro'};
-import React, {createContext, CSSProperties, FormEvent, FormHTMLAttributes, ForwardedRef, forwardRef, ReactElement, ReactNode, RefObject, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
+import React, {createContext, forwardRef, ReactElement, ReactNode, useCallback, useContext, useMemo, useRef, useState} from 'react';
import SortDownArrow from '../s2wf-icons/S2_Icon_SortDown_20_N.svg';
import SortUpArrow from '../s2wf-icons/S2_Icon_SortUp_20_N.svg';
-import {Button as SpectrumButton} from './Button';
import {useActionBarContainer} from './ActionBar';
-import {useDOMRef, useMediaQuery} from '@react-spectrum/utils';
+import {useDOMRef} from '@react-spectrum/utils';
import {useLocalizedStringFormatter} from '@react-aria/i18n';
import {useScale} from './utils';
import {useSpectrumContextProps} from './useSpectrumContextProps';
@@ -1059,308 +1047,6 @@ export const Cell = forwardRef(function Cell(props: CellProps, ref: DOMRef
({
- ...commonCellStyles,
- color: {
- default: baseColor('neutral'),
- isSaving: baseColor('neutral-subdued')
- },
- paddingY: centerPadding(),
- boxSizing: 'border-box',
- height: 'calc(100% - 1px)', // so we don't overlap the border of the next cell
- width: 'full',
- fontSize: controlFont(),
- alignItems: 'center',
- display: 'flex',
- borderStyle: {
- default: 'none',
- isDivider: 'solid'
- },
- borderEndWidth: {
- default: 0,
- isDivider: 1
- },
- borderColor: {
- default: 'gray-300',
- forcedColors: 'ButtonBorder'
- }
-});
-
-let editPopover = style({
- ...colorScheme(),
- '--s2-container-bg': {
- type: 'backgroundColor',
- value: 'layer-2'
- },
- backgroundColor: '--s2-container-bg',
- borderBottomRadius: 'default',
- // Use box-shadow instead of filter when an arrow is not shown.
- // This fixes the shadow stacking problem with submenus.
- boxShadow: 'elevated',
- borderStyle: 'solid',
- borderWidth: 1,
- borderColor: {
- default: 'gray-200',
- forcedColors: 'ButtonBorder'
- },
- boxSizing: 'content-box',
- isolation: 'isolate',
- pointerEvents: {
- isExiting: 'none'
- },
- outlineStyle: 'none',
- minWidth: '--trigger-width',
- padding: 8,
- display: 'flex',
- alignItems: 'center'
-}, getAllowedOverrides());
-
-interface EditableCellProps extends Omit {
- /** The component which will handle editing the cell. For example, a `TextField` or a `Picker`. */
- renderEditing: () => ReactNode,
- /** Whether the cell is currently being saved. */
- isSaving?: boolean,
- /** Handler that is called when the value has been changed and is ready to be saved. */
- onSubmit?: (e: FormEvent) => void,
- /** Handler that is called when the user cancels the edit. */
- onCancel?: () => void,
- /** The action to submit the form to. Only available in React 19+. */
- action?: string | FormHTMLAttributes['action']
-}
-
-/**
- * An editable cell within a table row.
- */
-export const EditableCell = forwardRef(function EditableCell(props: EditableCellProps, ref: ForwardedRef) {
- let {children, showDivider = false, textValue, isSaving, ...otherProps} = props;
- let tableVisualOptions = useContext(InternalTableContext);
- let domRef = useObjectRef(ref);
- textValue ||= typeof children === 'string' ? children : undefined;
-
- return (
- editableCell({
- ...renderProps,
- ...tableVisualOptions,
- isDivider: showDivider,
- isSaving
- })}
- textValue={textValue}
- {...otherProps}>
- {({isFocusVisible}) => (
- } />
- )}
-
- );
-});
-
-const nonTextInputTypes = new Set([
- 'checkbox',
- 'radio',
- 'range',
- 'color',
- 'file',
- 'image',
- 'button',
- 'submit',
- 'reset'
-]);
-
-function EditableCellInner(props: EditableCellProps & {isFocusVisible: boolean, cellRef: RefObject}) {
- let {children, align, renderEditing, isSaving, onSubmit, isFocusVisible, cellRef, action, onCancel} = props;
- let [isOpen, setIsOpen] = useState(false);
- let popoverRef = useRef(null);
- let formRef = useRef(null);
- let [triggerWidth, setTriggerWidth] = useState(0);
- let [tableWidth, setTableWidth] = useState(0);
- let [verticalOffset, setVerticalOffset] = useState(0);
- let tableVisualOptions = useContext(InternalTableContext);
- let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');
- let dialogRef = useRef>(null);
-
- let {density} = useContext(InternalTableContext);
- let size: 'XS' | 'S' | 'M' | 'L' | 'XL' | undefined = 'M';
- if (density === 'compact') {
- size = 'S';
- } else if (density === 'spacious') {
- size = 'L';
- }
-
- // Popover positioning
- useLayoutEffect(() => {
- if (!isOpen) {
- return;
- }
- let width = cellRef.current?.clientWidth || 0;
- let cell = cellRef.current;
- let boundingRect = cell?.parentElement?.getBoundingClientRect();
- let verticalOffset = (boundingRect?.top ?? 0) - (boundingRect?.bottom ?? 0);
-
- let tableWidth = cellRef.current?.closest('[role="grid"]')?.clientWidth || 0;
- setTriggerWidth(width);
- setVerticalOffset(verticalOffset);
- setTableWidth(tableWidth);
- }, [cellRef, density, isOpen]);
-
- // Auto select the entire text range of the autofocused input on overlay opening
- // Maybe replace with FocusScope or one of those utilities
- useEffect(() => {
- if (isOpen) {
- let activeElement = getActiveElement(getOwnerDocument(formRef.current));
- if (activeElement
- && formRef.current?.contains(activeElement)
- // not going to handle contenteditable https://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element
- // seems like an edge case anyways
- && (
- (activeElement instanceof HTMLInputElement && !nonTextInputTypes.has(activeElement.type))
- || activeElement instanceof HTMLTextAreaElement)
- && typeof activeElement.select === 'function') {
- activeElement.select();
- }
- }
- }, [isOpen]);
-
- let cancel = useCallback(() => {
- setIsOpen(false);
- onCancel?.();
- }, [onCancel]);
-
- let isMobile = !useMediaQuery('(hover: hover) and (pointer: fine)');
- // Can't differentiate between Dialog click outside dismissal and Escape key dismissal
- let prevIsOpen = useRef(isOpen);
- useEffect(() => {
- let dialog = dialogRef.current?.UNSAFE_getDOMNode();
- if (isOpen && dialog && !prevIsOpen.current) {
- let handler = (e: KeyboardEvent) => {
- if (e.key === 'Escape') {
- cancel();
- e.stopPropagation();
- e.preventDefault();
- }
- };
- dialog.addEventListener('keydown', handler);
- prevIsOpen.current = isOpen;
- return () => {
- dialog.removeEventListener('keydown', handler);
- };
- }
- prevIsOpen.current = isOpen;
- }, [isOpen, cancel]);
-
- return (
- setIsOpen(true),
- isPending: isSaving,
- isQuiet: !isSaving,
- size,
- excludeFromTabOrder: true,
- styles: style({
- // TODO: really need access to display here instead, but not possible right now
- // will be addressable with displayOuter
- // Could use `hidden` attribute instead of css, but I don't have access to much of this state at the moment
- visibility: {
- default: 'hidden',
- isForcedVisible: 'visible',
- ':is([role="row"]:hover *)': 'visible',
- ':is([role="row"][data-focus-visible-within] *)': 'visible',
- '@media not ((hover: hover) and (pointer: fine))': 'visible'
- }
- })({isForcedVisible: isOpen || !!isSaving})
- }
- }
- }]
- ]}>
- {children}
- {isFocusVisible && }
-
-
- {!isMobile && (
- {
- if (!popoverRef.current?.contains(document.activeElement)) {
- return false;
- }
- formRef.current?.requestSubmit();
- return false;
- }}
- triggerRef={cellRef}
- aria-label={props['aria-label'] ?? stringFormatter.format('table.editCell')}
- offset={verticalOffset}
- placement="bottom start"
- style={{
- minWidth: `min(${triggerWidth}px, ${tableWidth}px)`,
- maxWidth: `${tableWidth}px`,
- // Override default z-index from useOverlayPosition. We use isolation: isolate instead.
- zIndex: undefined
- }}
- className={editPopover}>
-
-
-
-
- )}
- {isMobile && (
- formRef.current?.requestSubmit()}>
- {isOpen && (
-
-
-
- )}
-
- )}
-
-
- );
-};
-
// Use color-mix instead of transparency so sticky cells work correctly.
const selectedBackground = lightDark(colorMix('gray-25', 'informative-900', 10), colorMix('gray-25', 'informative-700', 10));
const selectedActiveBackground = lightDark(colorMix('gray-25', 'informative-900', 15), colorMix('gray-25', 'informative-700', 15));
diff --git a/packages/@react-spectrum/s2/src/index.ts b/packages/@react-spectrum/s2/src/index.ts
index 25629e375f2..6fbe68b7e52 100644
--- a/packages/@react-spectrum/s2/src/index.ts
+++ b/packages/@react-spectrum/s2/src/index.ts
@@ -78,7 +78,7 @@ export {Skeleton, useIsSkeleton} from './Skeleton';
export {SkeletonCollection} from './SkeletonCollection';
export {StatusLight, StatusLightContext} from './StatusLight';
export {Switch, SwitchContext} from './Switch';
-export {TableView, TableHeader, TableBody, Row, Cell, Column, TableContext, EditableCell} from './TableView';
+export {TableView, TableHeader, TableBody, Row, Cell, Column, TableContext} from './TableView';
export {Tabs, TabList, Tab, TabPanel, TabsContext} from './Tabs';
export {TagGroup, Tag, TagGroupContext} from './TagGroup';
export {TextArea, TextField, TextAreaContext, TextFieldContext} from './TextField';
diff --git a/packages/@react-spectrum/s2/stories/TableView.stories.tsx b/packages/@react-spectrum/s2/stories/TableView.stories.tsx
index c637a16981f..891eeceaa42 100644
--- a/packages/@react-spectrum/s2/stories/TableView.stories.tsx
+++ b/packages/@react-spectrum/s2/stories/TableView.stories.tsx
@@ -15,37 +15,27 @@ import {
ActionButton,
Cell,
Column,
- ColumnProps,
Content,
- EditableCell,
Heading,
IllustratedMessage,
Link,
MenuItem,
MenuSection,
- Picker,
- PickerItem,
Row,
- StatusLight,
TableBody,
TableHeader,
TableView,
TableViewProps,
- Text,
- TextField
+ Text
} from '../src';
import {categorizeArgTypes, getActionArgs} from './utils';
-import Edit from '../s2wf-icons/S2_Icon_Edit_20_N.svg';
import Filter from '../s2wf-icons/S2_Icon_Filter_20_N.svg';
import FolderOpen from '../spectrum-illustrations/linear/FolderOpen';
-import {Key} from '@react-types/shared';
import type {Meta, StoryObj} from '@storybook/react';
-import React, {ReactElement, useCallback, useEffect, useRef, useState} from 'react';
+import React, {ReactElement, useState} from 'react';
import {SortDescriptor} from 'react-aria-components';
import {style} from '../style/spectrum-theme' with {type: 'macro'};
-import {useAsyncList, useListData} from '@react-stately/data';
-import {useEffectEvent} from '@react-aria/utils';
-import User from '../s2wf-icons/S2_Icon_User_20_N.svg';
+import {useAsyncList} from '@react-stately/data';
let onActionFunc = action('onAction');
let noOnAction = null;
@@ -1395,305 +1385,3 @@ const ResizableTable = () => {
}
}
};
-
-let defaultItems = [
- {id: 1,
- fruits: 'Apples', task: 'Collect', status: 'Pending', farmer: 'Eva',
- isSaving: {},
- intermediateValue: {}
- },
- {id: 2,
- fruits: 'Oranges', task: 'Collect', status: 'Pending', farmer: 'Steven',
- isSaving: {},
- intermediateValue: {}
- },
- {id: 3,
- fruits: 'Pears', task: 'Collect', status: 'Pending', farmer: 'Michael',
- isSaving: {},
- intermediateValue: {}
- },
- {id: 4,
- fruits: 'Cherries', task: 'Collect', status: 'Pending', farmer: 'Sara',
- isSaving: {},
- intermediateValue: {}
- },
- {id: 5,
- fruits: 'Dates', task: 'Collect', status: 'Pending', farmer: 'Karina',
- isSaving: {},
- intermediateValue: {}
- },
- {id: 6,
- fruits: 'Bananas', task: 'Collect', status: 'Pending', farmer: 'Otto',
- isSaving: {},
- intermediateValue: {}
- },
- {id: 7,
- fruits: 'Melons', task: 'Collect', status: 'Pending', farmer: 'Matt',
- isSaving: {},
- intermediateValue: {}
- },
- {id: 8,
- fruits: 'Figs', task: 'Collect', status: 'Pending', farmer: 'Emily',
- isSaving: {},
- intermediateValue: {}
- },
- {id: 9,
- fruits: 'Blueberries', task: 'Collect', status: 'Pending', farmer: 'Amelia',
- isSaving: {},
- intermediateValue: {}
- },
- {id: 10,
- fruits: 'Blackberries', task: 'Collect', status: 'Pending', farmer: 'Isla',
- isSaving: {},
- intermediateValue: {}
- }
-];
-
-let editableColumns: Array & {name: string}> = [
- {name: 'Fruits', id: 'fruits', isRowHeader: true, width: '6fr', minWidth: 300},
- {name: 'Task', id: 'task', width: '2fr', minWidth: 100},
- {name: 'Status', id: 'status', width: '2fr', showDivider: true, minWidth: 100},
- {name: 'Farmer', id: 'farmer', width: '2fr', minWidth: 150}
-];
-
-interface EditableTableProps extends TableViewProps {}
-
-export const EditableTable: StoryObj = {
- render: function EditableTable(props) {
- let columns = editableColumns;
- let data = useListData({initialItems: defaultItems});
-
- let onChange = useCallback((id: Key, columnId: Key, values: any) => {
- let value = values[columnId];
- if (value === null) {
- return;
- }
- data.update(id, (prevItem) => ({...prevItem, [columnId]: value}));
- }, [data]);
-
- return (
-
-
-
- {(column) => (
- {column.name}
- )}
-
-
- {item => (
-
- {(column) => {
- if (column.id === 'fruits') {
- return (
- {
- e.preventDefault();
- let formData = new FormData(e.target as HTMLFormElement);
- let values = Object.fromEntries(formData.entries());
- onChange(item.id, column.id!, values);
- }}
- isSaving={item.isSaving[column.id!]}
- renderEditing={() => (
- value.length > 0 ? null : 'Fruit name is required'}
- styles={style({flexGrow: 1, flexShrink: 1, minWidth: 0})}
- defaultValue={item[column.id!]} />
- )}>
-
- {item[column.id]}
-
-
-
-
- );
- }
- if (column.id === 'farmer') {
- return (
- {
- e.preventDefault();
- let formData = new FormData(e.target as HTMLFormElement);
- let values = Object.fromEntries(formData.entries());
- onChange(item.id, column.id!, values);
- }}
- isSaving={item.isSaving[column.id!]}
- renderEditing={() => (
-
- Eva
- Steven
- Michael
- Sara
- Karina
- Otto
- Matt
- Emily
- Amelia
- Isla
-
- )}>
-
-
- );
- }
- if (column.id === 'status') {
- return (
-
- {item[column.id]}
- |
- );
- }
- return {item[column.id!]} | ;
- }}
-
- )}
-
-
-
- );
- }
-};
-
-export const EditableTableWithAsyncSaving: StoryObj = {
- render: function EditableTable(props) {
- let delay = 5000;
- let columns = editableColumns;
- let data = useListData({initialItems: defaultItems});
-
- let saveItem = useEffectEvent((id: Key, columnId: Key) => {
- let prevItem = data.getItem(id)!;
- data.update(id, {...prevItem, isSaving: {...prevItem.isSaving, [columnId]: false}});
- currentRequests.current.delete(id);
- });
- let currentRequests = useRef