Move experimental list components to experimental package (https://github.com/woocommerce/woocommerce-admin/pull/6950)
* Move experimental list components to experimental package * Update changelogs * Fix lint errors * Fix lint error * Update css lint * Fix test * Update package lock
This commit is contained in:
parent
820bf30e29
commit
812743d3ff
|
@ -4,8 +4,7 @@
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Icon, check } from '@wordpress/icons';
|
import { Icon, check } from '@wordpress/icons';
|
||||||
import { Button, Tooltip } from '@wordpress/components';
|
import { Button, Tooltip } from '@wordpress/components';
|
||||||
import { Text } from '@woocommerce/experimental';
|
import { Text, ListItem } from '@woocommerce/experimental';
|
||||||
import { __experimentalListItem as ListItem } from '@woocommerce/components';
|
|
||||||
import NoticeOutline from 'gridicons/dist/notice-outline';
|
import NoticeOutline from 'gridicons/dist/notice-outline';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
|
|
@ -5,16 +5,11 @@ import { __, _n, sprintf } from '@wordpress/i18n';
|
||||||
import { useEffect, useRef } from '@wordpress/element';
|
import { useEffect, useRef } from '@wordpress/element';
|
||||||
import { Button, Card, CardBody, CardHeader } from '@wordpress/components';
|
import { Button, Card, CardBody, CardHeader } from '@wordpress/components';
|
||||||
import { useSelect, useDispatch } from '@wordpress/data';
|
import { useSelect, useDispatch } from '@wordpress/data';
|
||||||
import {
|
import { EllipsisMenu, Badge } from '@woocommerce/components';
|
||||||
EllipsisMenu,
|
|
||||||
Badge,
|
|
||||||
__experimentalList as List,
|
|
||||||
__experimentalCollapsibleList as CollapsibleList,
|
|
||||||
} from '@woocommerce/components';
|
|
||||||
import { updateQueryString } from '@woocommerce/navigation';
|
import { updateQueryString } from '@woocommerce/navigation';
|
||||||
import { OPTIONS_STORE_NAME, ONBOARDING_STORE_NAME } from '@woocommerce/data';
|
import { OPTIONS_STORE_NAME, ONBOARDING_STORE_NAME } from '@woocommerce/data';
|
||||||
import { recordEvent } from '@woocommerce/tracks';
|
import { recordEvent } from '@woocommerce/tracks';
|
||||||
import { Text } from '@woocommerce/experimental';
|
import { Text, List, CollapsibleList } from '@woocommerce/experimental';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
|
|
@ -10851,13 +10851,17 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "7.14.0",
|
"@babel/runtime": "7.14.0",
|
||||||
"@wordpress/components": "10.2.0"
|
"@wordpress/components": "10.2.0",
|
||||||
|
"@wordpress/element": "2.19.0",
|
||||||
|
"@wordpress/keycodes": "2.18.0",
|
||||||
|
"classnames": "^2.3.1",
|
||||||
|
"react-transition-group": "4.4.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": {
|
"@babel/runtime": {
|
||||||
"version": "7.13.17",
|
"version": "7.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.17.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
|
||||||
"integrity": "sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==",
|
"integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
|
@ -10949,6 +10953,17 @@
|
||||||
"@babel/runtime": "7.14.0",
|
"@babel/runtime": "7.14.0",
|
||||||
"@wordpress/a11y": "2.15.2",
|
"@wordpress/a11y": "2.15.2",
|
||||||
"@wordpress/data": "4.26.1"
|
"@wordpress/data": "4.26.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
|
||||||
|
"integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.13.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@woocommerce/number": {
|
"@woocommerce/number": {
|
||||||
|
|
|
@ -3,9 +3,8 @@
|
||||||
- SelectControl: automatically scroll to selected options when list is displayed. #6906
|
- SelectControl: automatically scroll to selected options when list is displayed. #6906
|
||||||
- SelectControl: no longer auto selects on rendering list. #6906
|
- SelectControl: no longer auto selects on rendering list. #6906
|
||||||
- Make `Search` accept synchronous `autocompleter.options`. #6884
|
- Make `Search` accept synchronous `autocompleter.options`. #6884
|
||||||
- Add new (experimental) collapsible list item to collapse list items. #6869
|
|
||||||
- SelectControl: fix display of multiple selections without inline tags. #6862
|
- SelectControl: fix display of multiple selections without inline tags. #6862
|
||||||
- Add new (experimental) list, and add depreciation notice for the current list. #6787
|
- Add depreciation notice for the current list. #6787
|
||||||
- Force `<SearchListItem>` form elements id to be unique. #6871
|
- Force `<SearchListItem>` form elements id to be unique. #6871
|
||||||
- Add `controlId` and `name` props to `<SearchListItem>`. #6871
|
- Add `controlId` and `name` props to `<SearchListItem>`. #6871
|
||||||
- Minor styling tweaks and fixes to `<SearchListcontrol>`. #6871
|
- Minor styling tweaks and fixes to `<SearchListcontrol>`. #6871
|
||||||
|
|
|
@ -25,13 +25,7 @@ export { default as Gravatar } from './gravatar';
|
||||||
export { H, Section } from './section';
|
export { H, Section } from './section';
|
||||||
export { default as ImageUpload } from './image-upload';
|
export { default as ImageUpload } from './image-upload';
|
||||||
export { default as Link } from './link';
|
export { default as Link } from './link';
|
||||||
export {
|
export { default as List } from './list';
|
||||||
default as List,
|
|
||||||
ExperimentalList as __experimentalList,
|
|
||||||
ExperimentalListItem as __experimentalListItem,
|
|
||||||
ExperimentalListItemCollapse as __experimentalListItemCollapse,
|
|
||||||
ExperimentalCollapsibleList as __experimentalCollapsibleList,
|
|
||||||
} from './list';
|
|
||||||
export { default as MenuItem } from './ellipsis-menu/menu-item';
|
export { default as MenuItem } from './ellipsis-menu/menu-item';
|
||||||
export { default as MenuTitle } from './ellipsis-menu/menu-title';
|
export { default as MenuTitle } from './ellipsis-menu/menu-title';
|
||||||
export { default as OrderStatus } from './order-status';
|
export { default as OrderStatus } from './order-status';
|
||||||
|
|
|
@ -112,7 +112,3 @@ List.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default List;
|
export default List;
|
||||||
|
|
||||||
export { ExperimentalListItem } from './experimental-list-item';
|
|
||||||
export { ExperimentalList } from './experimental-list';
|
|
||||||
export { ExperimentalCollapsibleList } from './collapsible-list';
|
|
||||||
|
|
|
@ -1,278 +1,15 @@
|
||||||
jest.mock( '../list-item', () => ( {
|
|
||||||
__esModule: true,
|
|
||||||
...jest.requireActual( '../list-item' ),
|
|
||||||
handleKeyDown: jest.fn(),
|
|
||||||
} ) );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import {
|
import { render, screen } from '@testing-library/react';
|
||||||
render,
|
|
||||||
screen,
|
|
||||||
waitForElementToBeRemoved,
|
|
||||||
} from '@testing-library/react';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import List, {
|
import List from '../index';
|
||||||
ExperimentalList,
|
|
||||||
ExperimentalListItem,
|
|
||||||
ExperimentalCollapsibleList,
|
|
||||||
} from '../index';
|
|
||||||
import { handleKeyDown } from '../list-item';
|
|
||||||
|
|
||||||
describe( 'List', () => {
|
describe( 'List', () => {
|
||||||
describe( 'Experimental List', () => {
|
|
||||||
it( 'should render the new List which defaults to a ul component if items are not passed in', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ExperimentalList>
|
|
||||||
<div>Test</div>
|
|
||||||
</ExperimentalList>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect( container.querySelector( 'ul' ) ).toBeInTheDocument();
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should render children passed in', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ExperimentalList>
|
|
||||||
<div>Test</div>
|
|
||||||
</ExperimentalList>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect( container ).toHaveTextContent( 'Test' );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should allow overriding the list type, and passing in arbitrary element props', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ExperimentalList listType="ol" role="menu">
|
|
||||||
<div>Test</div>
|
|
||||||
</ExperimentalList>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect( container.querySelector( 'ol' ) ).toBeInTheDocument();
|
|
||||||
} );
|
|
||||||
|
|
||||||
describe( 'ExperimentalListItem', () => {
|
|
||||||
it( 'should render children passed in', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ExperimentalListItem>
|
|
||||||
<div>Test</div>
|
|
||||||
</ExperimentalListItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect( container ).toHaveTextContent( 'Test' );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'allows disabling the gutter styling', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ExperimentalListItem disableGutters>
|
|
||||||
<div>Test</div>
|
|
||||||
</ExperimentalListItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
container.querySelector( '.has-gutters' )
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should disable animations by default and for unsupported values', () => {
|
|
||||||
// disabled by default
|
|
||||||
const { container, rerender } = render(
|
|
||||||
<ExperimentalListItem>
|
|
||||||
<div>Test</div>
|
|
||||||
</ExperimentalListItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
container.querySelector( '.transitions-disabled' )
|
|
||||||
).toBeInTheDocument();
|
|
||||||
|
|
||||||
// invalid value
|
|
||||||
rerender(
|
|
||||||
<ExperimentalListItem animation="bounce-up-and-down">
|
|
||||||
<div>Test</div>
|
|
||||||
</ExperimentalListItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
container.querySelector( '.transitions-disabled' )
|
|
||||||
).toBeInTheDocument();
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should not disable animations if you provide a valid animation value', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ExperimentalListItem animation="slide-right">
|
|
||||||
<div>Test</div>
|
|
||||||
</ExperimentalListItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
container.querySelector( '.transitions-disabled' )
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'supports onClick on the list item, and handles keyboard events', () => {
|
|
||||||
const dummyOnClick = jest.fn();
|
|
||||||
|
|
||||||
const { container, queryByRole } = render(
|
|
||||||
<ExperimentalListItem onClick={ dummyOnClick }>
|
|
||||||
<div>Test</div>
|
|
||||||
</ExperimentalListItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
const listItem = container.querySelector(
|
|
||||||
'.woocommerce-list__item'
|
|
||||||
);
|
|
||||||
|
|
||||||
userEvent.click( listItem );
|
|
||||||
|
|
||||||
// it doesn't actually matter what key you hit here while handleKeyDown is mocked.
|
|
||||||
userEvent.type( listItem, '{enter}' );
|
|
||||||
|
|
||||||
// TODO check that the button role was added.
|
|
||||||
expect( queryByRole( 'button' ) ).toBeInTheDocument();
|
|
||||||
expect( handleKeyDown ).toHaveBeenCalled();
|
|
||||||
expect( dummyOnClick ).toHaveBeenCalled();
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'includes correct ARIA roles and a11y attributes when the item has an action', () => {
|
|
||||||
const clickHandler = jest.fn();
|
|
||||||
render(
|
|
||||||
<ExperimentalListItem onClick={ clickHandler }>
|
|
||||||
<div>Test</div>
|
|
||||||
</ExperimentalListItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
const item = screen.getByRole( 'button' );
|
|
||||||
expect( item ).toBeInTheDocument();
|
|
||||||
expect( item ).toHaveAttribute( 'role', 'button' );
|
|
||||||
expect( item ).toHaveAttribute( 'tabindex', '0' );
|
|
||||||
} );
|
|
||||||
} );
|
|
||||||
|
|
||||||
describe( 'ExperimentalListItemCollapse', () => {
|
|
||||||
it( 'should not render its children intially, but an extra list footer with show text', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ExperimentalCollapsibleList
|
|
||||||
collapseLabel="Show less"
|
|
||||||
expandLabel="Show more items"
|
|
||||||
>
|
|
||||||
<div>Test</div>
|
|
||||||
</ExperimentalCollapsibleList>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect( container ).not.toHaveTextContent( 'Test' );
|
|
||||||
expect( container ).toHaveTextContent( 'Show more items' );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should render list items when footer is clicked and trigger onExpand', () => {
|
|
||||||
const onExpand = jest.fn();
|
|
||||||
const onCollapse = jest.fn();
|
|
||||||
const { container } = render(
|
|
||||||
<ExperimentalCollapsibleList
|
|
||||||
collapseLabel="Show less"
|
|
||||||
expandLabel="Show more items"
|
|
||||||
onExpand={ onExpand }
|
|
||||||
onCollapse={ onCollapse }
|
|
||||||
>
|
|
||||||
<div>Test</div>
|
|
||||||
<div>Test 2</div>
|
|
||||||
</ExperimentalCollapsibleList>
|
|
||||||
);
|
|
||||||
|
|
||||||
const listItem = container.querySelector(
|
|
||||||
'.list-item-collapse'
|
|
||||||
);
|
|
||||||
|
|
||||||
userEvent.click( listItem );
|
|
||||||
expect( container ).toHaveTextContent( 'Test' );
|
|
||||||
expect( container ).toHaveTextContent( 'Test 2' );
|
|
||||||
expect( container ).not.toHaveTextContent( 'Show more items' );
|
|
||||||
expect( container ).toHaveTextContent( 'Show less' );
|
|
||||||
expect( onExpand ).toHaveBeenCalled();
|
|
||||||
expect( onCollapse ).not.toHaveBeenCalled();
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should render minimum children if minChildrenToShow is set and show the rest on expand', () => {
|
|
||||||
const onExpand = jest.fn();
|
|
||||||
const onCollapse = jest.fn();
|
|
||||||
const { container } = render(
|
|
||||||
<ExperimentalCollapsibleList
|
|
||||||
collapseLabel="Show less"
|
|
||||||
expandLabel="Show more items"
|
|
||||||
onExpand={ onExpand }
|
|
||||||
onCollapse={ onCollapse }
|
|
||||||
show={ 2 }
|
|
||||||
>
|
|
||||||
<div>Test</div>
|
|
||||||
<div>Test 2</div>
|
|
||||||
<div>Test 3</div>
|
|
||||||
<div>Test 4</div>
|
|
||||||
</ExperimentalCollapsibleList>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect( container ).toHaveTextContent( 'Test' );
|
|
||||||
expect( container ).toHaveTextContent( 'Test 2' );
|
|
||||||
expect( container ).not.toHaveTextContent( 'Test 3' );
|
|
||||||
expect( container ).not.toHaveTextContent( 'Test 4' );
|
|
||||||
const listItem = container.querySelector(
|
|
||||||
'.list-item-collapse'
|
|
||||||
);
|
|
||||||
|
|
||||||
userEvent.click( listItem );
|
|
||||||
expect( container ).toHaveTextContent( 'Test' );
|
|
||||||
expect( container ).toHaveTextContent( 'Test 2' );
|
|
||||||
expect( container ).toHaveTextContent( 'Test 3' );
|
|
||||||
expect( container ).toHaveTextContent( 'Test 4' );
|
|
||||||
expect( container ).not.toHaveTextContent( 'Show more items' );
|
|
||||||
expect( container ).toHaveTextContent( 'Show less' );
|
|
||||||
expect( onExpand ).toHaveBeenCalled();
|
|
||||||
expect( onCollapse ).not.toHaveBeenCalled();
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should correctly toggle the list', async () => {
|
|
||||||
const onExpand = jest.fn();
|
|
||||||
const onCollapse = jest.fn();
|
|
||||||
const { container } = render(
|
|
||||||
<ExperimentalCollapsibleList
|
|
||||||
collapseLabel="Show less"
|
|
||||||
expandLabel="Show more items"
|
|
||||||
onExpand={ onExpand }
|
|
||||||
onCollapse={ onCollapse }
|
|
||||||
>
|
|
||||||
<div id="test">Test</div>
|
|
||||||
<div>Test 2</div>
|
|
||||||
</ExperimentalCollapsibleList>
|
|
||||||
);
|
|
||||||
|
|
||||||
let listItem = container.querySelector( '.list-item-collapse' );
|
|
||||||
|
|
||||||
userEvent.click( listItem );
|
|
||||||
expect( container ).toHaveTextContent( 'Test' );
|
|
||||||
expect( container ).toHaveTextContent( 'Test 2' );
|
|
||||||
expect( container ).not.toHaveTextContent( 'Show more items' );
|
|
||||||
expect( container ).toHaveTextContent( 'Show less' );
|
|
||||||
|
|
||||||
listItem = container.querySelector( '.list-item-collapse' );
|
|
||||||
|
|
||||||
userEvent.click( listItem );
|
|
||||||
await waitForElementToBeRemoved(
|
|
||||||
container.querySelector( '#test' )
|
|
||||||
);
|
|
||||||
expect( container ).not.toHaveTextContent( 'Test' );
|
|
||||||
expect( container ).not.toHaveTextContent( 'Test 2' );
|
|
||||||
expect( container ).toHaveTextContent( 'Show more items' );
|
|
||||||
expect( container ).not.toHaveTextContent( 'Show less' );
|
|
||||||
expect( onExpand ).toHaveBeenCalledTimes( 1 );
|
|
||||||
expect( onCollapse ).toHaveBeenCalledTimes( 1 );
|
|
||||||
} );
|
|
||||||
} );
|
|
||||||
} );
|
|
||||||
|
|
||||||
describe( 'Legacy List', () => {
|
describe( 'Legacy List', () => {
|
||||||
it( 'should have aria roles for items', () => {
|
it( 'should have aria roles for items', () => {
|
||||||
const clickHandler = jest.fn();
|
const clickHandler = jest.fn();
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
@import 'gravatar/style.scss';
|
@import 'gravatar/style.scss';
|
||||||
@import 'image-upload/style.scss';
|
@import 'image-upload/style.scss';
|
||||||
@import 'list/style.scss';
|
@import 'list/style.scss';
|
||||||
@import 'list/collapsible-list/style.scss';
|
|
||||||
@import 'order-status/style.scss';
|
@import 'order-status/style.scss';
|
||||||
@import 'pagination/style.scss';
|
@import 'pagination/style.scss';
|
||||||
@import 'pill/style.scss';
|
@import 'pill/style.scss';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
- Export component ExperimentalList and ExperimentalListItem as List and ListItem.
|
- Add new (experimental) collapsible list item to collapse list items. #6869
|
||||||
|
- Add new (experimental) list. #6787
|
||||||
|
|
||||||
# 1.0.0
|
# 1.0.0
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,11 @@
|
||||||
"react-native": "src/index",
|
"react-native": "src/index",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.14.0",
|
"@babel/runtime": "7.14.0",
|
||||||
"@wordpress/components": "10.2.0"
|
"@wordpress/components": "10.2.0",
|
||||||
|
"@wordpress/element": "2.19.0",
|
||||||
|
"@wordpress/keycodes": "2.18.0",
|
||||||
|
"classnames": "^2.3.1",
|
||||||
|
"react-transition-group": "4.4.1"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.woocommerce-list__item.list-item-collapse {
|
.woocommerce-experimental-list__item.list-item-collapse {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
p {
|
p {
|
|
@ -2,12 +2,19 @@
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { CSSTransition } from 'react-transition-group';
|
import { CSSTransition } from 'react-transition-group';
|
||||||
|
import { ENTER } from '@wordpress/keycodes';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
/**
|
function handleKeyDown(
|
||||||
* Internal dependencies
|
event: React.KeyboardEvent< HTMLElement >,
|
||||||
*/
|
onClick?:
|
||||||
import { handleKeyDown } from './list-item';
|
| React.MouseEventHandler< HTMLElement >
|
||||||
|
| React.KeyboardEventHandler< HTMLElement >
|
||||||
|
) {
|
||||||
|
if ( typeof onClick === 'function' && event.keyCode === ENTER ) {
|
||||||
|
( onClick as React.KeyboardEventHandler< HTMLElement > )( event );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type CSSTransitionProps = {
|
type CSSTransitionProps = {
|
||||||
in: boolean;
|
in: boolean;
|
||||||
|
@ -73,7 +80,7 @@ export const ExperimentalListItem: React.FC< ListItemProps > = ( {
|
||||||
// spread role props first, in case it is desired to override them
|
// spread role props first, in case it is desired to override them
|
||||||
{ ...roleProps }
|
{ ...roleProps }
|
||||||
{ ...otherProps }
|
{ ...otherProps }
|
||||||
className={ `woocommerce-list__item ${ tagClasses } ${ className }` }
|
className={ `woocommerce-experimental-list__item ${ tagClasses } ${ className }` }
|
||||||
>
|
>
|
||||||
{ children }
|
{ children }
|
||||||
</li>
|
</li>
|
|
@ -26,7 +26,7 @@ export const ExperimentalList: React.FC< ListProps > = ( {
|
||||||
return (
|
return (
|
||||||
<TransitionGroup
|
<TransitionGroup
|
||||||
component={ listType }
|
component={ listType }
|
||||||
className="woocommerce-list"
|
className="woocommerce-experimental-list"
|
||||||
{ ...otherProps }
|
{ ...otherProps }
|
||||||
>
|
>
|
||||||
{ /* Wrapping all children in a CSS Transition means no invalid props are passed to children and that anything can be animated. */ }
|
{ /* Wrapping all children in a CSS Transition means no invalid props are passed to children and that anything can be animated. */ }
|
|
@ -0,0 +1,158 @@
|
||||||
|
.woocommerce-experimental-list {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.woocommerce-experimental-list__item {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-experimental-list__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&.has-gutters {
|
||||||
|
padding: $gap $gap-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-action {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: inset 0 0 0 1px $studio-wordpress-blue,
|
||||||
|
inset 0 0 0 2px $studio-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// transitions
|
||||||
|
&:not(.transitions-disabled) {
|
||||||
|
&.woocommerce-list__item-enter {
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
transform: translateX(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.woocommerce-list__item-enter-active {
|
||||||
|
opacity: 1;
|
||||||
|
max-height: 100vh;
|
||||||
|
transform: translateX(0%);
|
||||||
|
transition: opacity 500ms, transform 500ms, max-height 500ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.woocommerce-list__item-exit {
|
||||||
|
opacity: 1;
|
||||||
|
max-height: 100vh;
|
||||||
|
transform: translateX(0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.woocommerce-list__item-exit-active {
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
transform: translateX(50%);
|
||||||
|
transition: opacity 500ms, transform 500ms, max-height 500ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .woocommerce-list__item-inner {
|
||||||
|
text-decoration: none;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: $gap $gap-large;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: inset 0 0 0 1px $studio-wordpress-blue,
|
||||||
|
inset 0 0 0 2px $studio-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-title {
|
||||||
|
color: $studio-gray-90;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-content {
|
||||||
|
margin-top: $gap-smallest;
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #50575d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-before {
|
||||||
|
margin-right: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-after {
|
||||||
|
margin-left: $gap;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chevron-color: $gray-900;
|
||||||
|
$background-color: $white;
|
||||||
|
$background-color-hover: $gray-100;
|
||||||
|
$border-color: $gray-100;
|
||||||
|
$foreground-color: var(--wp-admin-theme-color);
|
||||||
|
$foreground-color-hover: var(--wp-admin-theme-color);
|
||||||
|
|
||||||
|
background-color: $background-color;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
border-top: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $background-color-hover;
|
||||||
|
|
||||||
|
.woocommerce-list__item-title {
|
||||||
|
color: $foreground-color-hover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-before > svg {
|
||||||
|
fill: $foreground-color-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-title {
|
||||||
|
color: $foreground-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-before > svg {
|
||||||
|
fill: $foreground-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-after > svg {
|
||||||
|
fill: $chevron-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-complete {
|
||||||
|
.woocommerce-task__icon {
|
||||||
|
background-color: var(--wp-admin-theme-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-title {
|
||||||
|
color: $gray-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-experimental-list__item-title {
|
||||||
|
color: $studio-gray-80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-experimental-list__item-content {
|
||||||
|
color: $studio-gray-50;
|
||||||
|
}
|
|
@ -0,0 +1,269 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
render,
|
||||||
|
screen,
|
||||||
|
waitForElementToBeRemoved,
|
||||||
|
} from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { ExperimentalList } from '../experimental-list';
|
||||||
|
import { ExperimentalListItem } from '../experimental-list-item';
|
||||||
|
import { ExperimentalCollapsibleList } from '../collapsible-list';
|
||||||
|
|
||||||
|
describe( 'Experimental List', () => {
|
||||||
|
it( 'should render the new List which defaults to a ul component if items are not passed in', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<ExperimentalList>
|
||||||
|
<div>Test</div>
|
||||||
|
</ExperimentalList>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( container.querySelector( 'ul' ) ).toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should render children passed in', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<ExperimentalList>
|
||||||
|
<div>Test</div>
|
||||||
|
</ExperimentalList>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( container ).toHaveTextContent( 'Test' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should allow overriding the list type, and passing in arbitrary element props', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<ExperimentalList listType="ol" role="menu">
|
||||||
|
<div>Test</div>
|
||||||
|
</ExperimentalList>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( container.querySelector( 'ol' ) ).toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'ExperimentalListItem', () => {
|
||||||
|
it( 'should render children passed in', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<ExperimentalListItem>
|
||||||
|
<div>Test</div>
|
||||||
|
</ExperimentalListItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( container ).toHaveTextContent( 'Test' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'allows disabling the gutter styling', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<ExperimentalListItem disableGutters>
|
||||||
|
<div>Test</div>
|
||||||
|
</ExperimentalListItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
container.querySelector( '.has-gutters' )
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should disable animations by default and for unsupported values', () => {
|
||||||
|
// disabled by default
|
||||||
|
const { container, rerender } = render(
|
||||||
|
<ExperimentalListItem>
|
||||||
|
<div>Test</div>
|
||||||
|
</ExperimentalListItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
container.querySelector( '.transitions-disabled' )
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// invalid value
|
||||||
|
rerender(
|
||||||
|
<ExperimentalListItem animation="none">
|
||||||
|
<div>Test</div>
|
||||||
|
</ExperimentalListItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
container.querySelector( '.transitions-disabled' )
|
||||||
|
).toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should not disable animations if you provide a valid animation value', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<ExperimentalListItem animation="slide-right">
|
||||||
|
<div>Test</div>
|
||||||
|
</ExperimentalListItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
container.querySelector( '.transitions-disabled' )
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'supports onClick on the list item, and handles keyboard events', () => {
|
||||||
|
const dummyOnClick = jest.fn();
|
||||||
|
|
||||||
|
const { container, queryByRole } = render(
|
||||||
|
<ExperimentalListItem onClick={ dummyOnClick }>
|
||||||
|
<div>Test</div>
|
||||||
|
</ExperimentalListItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
const listItem = container.querySelector(
|
||||||
|
'.woocommerce-experimental-list__item'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( listItem ) {
|
||||||
|
userEvent.click( listItem );
|
||||||
|
|
||||||
|
// it doesn't actually matter what key you hit here while handleKeyDown is mocked.
|
||||||
|
userEvent.type( listItem, '{enter}' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check that the button role was added.
|
||||||
|
expect( queryByRole( 'button' ) ).toBeInTheDocument();
|
||||||
|
expect( dummyOnClick ).toHaveBeenCalled();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'includes correct ARIA roles and a11y attributes when the item has an action', () => {
|
||||||
|
const clickHandler = jest.fn();
|
||||||
|
render(
|
||||||
|
<ExperimentalListItem onClick={ clickHandler }>
|
||||||
|
<div>Test</div>
|
||||||
|
</ExperimentalListItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
const item = screen.getByRole( 'button' );
|
||||||
|
expect( item ).toBeInTheDocument();
|
||||||
|
expect( item ).toHaveAttribute( 'role', 'button' );
|
||||||
|
expect( item ).toHaveAttribute( 'tabindex', '0' );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'ExperimentalListItemCollapse', () => {
|
||||||
|
it( 'should not render its children intially, but an extra list footer with show text', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<ExperimentalCollapsibleList
|
||||||
|
collapseLabel="Show less"
|
||||||
|
expandLabel="Show more items"
|
||||||
|
>
|
||||||
|
<div>Test</div>
|
||||||
|
</ExperimentalCollapsibleList>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( container ).not.toHaveTextContent( 'Test' );
|
||||||
|
expect( container ).toHaveTextContent( 'Show more items' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should render list items when footer is clicked and trigger onExpand', () => {
|
||||||
|
const onExpand = jest.fn();
|
||||||
|
const onCollapse = jest.fn();
|
||||||
|
const { container } = render(
|
||||||
|
<ExperimentalCollapsibleList
|
||||||
|
collapseLabel="Show less"
|
||||||
|
expandLabel="Show more items"
|
||||||
|
onExpand={ onExpand }
|
||||||
|
onCollapse={ onCollapse }
|
||||||
|
>
|
||||||
|
<div>Test</div>
|
||||||
|
<div>Test 2</div>
|
||||||
|
</ExperimentalCollapsibleList>
|
||||||
|
);
|
||||||
|
|
||||||
|
const listItem = container.querySelector( '.list-item-collapse' );
|
||||||
|
|
||||||
|
if ( listItem ) {
|
||||||
|
userEvent.click( listItem );
|
||||||
|
}
|
||||||
|
expect( container ).toHaveTextContent( 'Test' );
|
||||||
|
expect( container ).toHaveTextContent( 'Test 2' );
|
||||||
|
expect( container ).not.toHaveTextContent( 'Show more items' );
|
||||||
|
expect( container ).toHaveTextContent( 'Show less' );
|
||||||
|
expect( onExpand ).toHaveBeenCalled();
|
||||||
|
expect( onCollapse ).not.toHaveBeenCalled();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should render minimum children if minChildrenToShow is set and show the rest on expand', () => {
|
||||||
|
const onExpand = jest.fn();
|
||||||
|
const onCollapse = jest.fn();
|
||||||
|
const { container } = render(
|
||||||
|
<ExperimentalCollapsibleList
|
||||||
|
collapseLabel="Show less"
|
||||||
|
expandLabel="Show more items"
|
||||||
|
onExpand={ onExpand }
|
||||||
|
onCollapse={ onCollapse }
|
||||||
|
show={ 2 }
|
||||||
|
>
|
||||||
|
<div>Test</div>
|
||||||
|
<div>Test 2</div>
|
||||||
|
<div>Test 3</div>
|
||||||
|
<div>Test 4</div>
|
||||||
|
</ExperimentalCollapsibleList>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( container ).toHaveTextContent( 'Test' );
|
||||||
|
expect( container ).toHaveTextContent( 'Test 2' );
|
||||||
|
expect( container ).not.toHaveTextContent( 'Test 3' );
|
||||||
|
expect( container ).not.toHaveTextContent( 'Test 4' );
|
||||||
|
const listItem = container.querySelector( '.list-item-collapse' );
|
||||||
|
|
||||||
|
if ( listItem ) {
|
||||||
|
userEvent.click( listItem );
|
||||||
|
}
|
||||||
|
expect( container ).toHaveTextContent( 'Test' );
|
||||||
|
expect( container ).toHaveTextContent( 'Test 2' );
|
||||||
|
expect( container ).toHaveTextContent( 'Test 3' );
|
||||||
|
expect( container ).toHaveTextContent( 'Test 4' );
|
||||||
|
expect( container ).not.toHaveTextContent( 'Show more items' );
|
||||||
|
expect( container ).toHaveTextContent( 'Show less' );
|
||||||
|
expect( onExpand ).toHaveBeenCalled();
|
||||||
|
expect( onCollapse ).not.toHaveBeenCalled();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should correctly toggle the list', async () => {
|
||||||
|
const onExpand = jest.fn();
|
||||||
|
const onCollapse = jest.fn();
|
||||||
|
const { container } = render(
|
||||||
|
<ExperimentalCollapsibleList
|
||||||
|
collapseLabel="Show less"
|
||||||
|
expandLabel="Show more items"
|
||||||
|
onExpand={ onExpand }
|
||||||
|
onCollapse={ onCollapse }
|
||||||
|
>
|
||||||
|
<div id="test">Test</div>
|
||||||
|
<div>Test 2</div>
|
||||||
|
</ExperimentalCollapsibleList>
|
||||||
|
);
|
||||||
|
|
||||||
|
let listItem = container.querySelector( '.list-item-collapse' );
|
||||||
|
|
||||||
|
if ( listItem ) {
|
||||||
|
userEvent.click( listItem );
|
||||||
|
}
|
||||||
|
expect( container ).toHaveTextContent( 'Test' );
|
||||||
|
expect( container ).toHaveTextContent( 'Test 2' );
|
||||||
|
expect( container ).not.toHaveTextContent( 'Show more items' );
|
||||||
|
expect( container ).toHaveTextContent( 'Show less' );
|
||||||
|
|
||||||
|
listItem = container.querySelector( '.list-item-collapse' );
|
||||||
|
|
||||||
|
if ( listItem ) {
|
||||||
|
userEvent.click( listItem );
|
||||||
|
}
|
||||||
|
await waitForElementToBeRemoved(
|
||||||
|
container.querySelector( '#test' )
|
||||||
|
);
|
||||||
|
expect( container ).not.toHaveTextContent( 'Test' );
|
||||||
|
expect( container ).not.toHaveTextContent( 'Test 2' );
|
||||||
|
expect( container ).toHaveTextContent( 'Show more items' );
|
||||||
|
expect( container ).not.toHaveTextContent( 'Show less' );
|
||||||
|
expect( onExpand ).toHaveBeenCalledTimes( 1 );
|
||||||
|
expect( onCollapse ).toHaveBeenCalledTimes( 1 );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -32,3 +32,7 @@ export const NavigationItem =
|
||||||
NavigationItemComponent || __experimentalNavigationItem;
|
NavigationItemComponent || __experimentalNavigationItem;
|
||||||
export const Text = TextComponent || __experimentalText;
|
export const Text = TextComponent || __experimentalText;
|
||||||
export const useSlot = useSlotHook || __experimentalUseSlot;
|
export const useSlot = useSlotHook || __experimentalUseSlot;
|
||||||
|
|
||||||
|
export { ExperimentalListItem as ListItem } from './experimental-list/experimental-list-item';
|
||||||
|
export { ExperimentalList as List } from './experimental-list/experimental-list';
|
||||||
|
export { ExperimentalCollapsibleList as CollapsibleList } from './experimental-list/collapsible-list';
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
/**
|
||||||
|
* Internal Dependencies
|
||||||
|
*/
|
||||||
|
@import 'experimental-list/style.scss';
|
||||||
|
@import 'experimental-list/collapsible-list/style.scss';
|
|
@ -137,6 +137,7 @@ Release and roadmap notes are available on the [WooCommerce Developers Blog](htt
|
||||||
- Update: UI updates to Payment Task screen #6766
|
- Update: UI updates to Payment Task screen #6766
|
||||||
- Update: Adding setup required icon for non-configured payment methods #6811
|
- Update: Adding setup required icon for non-configured payment methods #6811
|
||||||
- Update: Task list component with new Experimental Task list. #6849
|
- Update: Task list component with new Experimental Task list. #6849
|
||||||
|
- Update: Experimental task list import to the experimental package. #6950
|
||||||
- Update: Redirect to WC Home after setting up a payment method #6891
|
- Update: Redirect to WC Home after setting up a payment method #6891
|
||||||
- Update: Replace marketing extension - Google Listings and Ads. #6939
|
- Update: Replace marketing extension - Google Listings and Ads. #6939
|
||||||
- Dev: Fix a bug where trying to load an asset registry causes a crash. #6951
|
- Dev: Fix a bug where trying to load an asset registry causes a crash. #6951
|
||||||
|
|
|
@ -431,6 +431,14 @@ class Loader {
|
||||||
);
|
);
|
||||||
wp_style_add_data( 'wc-customer-effort-score', 'rtl', 'replace' );
|
wp_style_add_data( 'wc-customer-effort-score', 'rtl', 'replace' );
|
||||||
|
|
||||||
|
wp_register_style(
|
||||||
|
'wc-experimental',
|
||||||
|
self::get_url( 'experimental/style', 'css' ),
|
||||||
|
array(),
|
||||||
|
$css_file_version
|
||||||
|
);
|
||||||
|
wp_style_add_data( 'wc-experimental', 'rtl', 'replace' );
|
||||||
|
|
||||||
wp_localize_script(
|
wp_localize_script(
|
||||||
WC_ADMIN_APP,
|
WC_ADMIN_APP,
|
||||||
'wcAdminAssets',
|
'wcAdminAssets',
|
||||||
|
@ -446,7 +454,7 @@ class Loader {
|
||||||
wp_register_style(
|
wp_register_style(
|
||||||
WC_ADMIN_APP,
|
WC_ADMIN_APP,
|
||||||
self::get_url( "app/style{$rtl}", 'css' ),
|
self::get_url( "app/style{$rtl}", 'css' ),
|
||||||
array( 'wc-components', 'wc-customer-effort-score', 'wp-components' ),
|
array( 'wc-components', 'wc-customer-effort-score', 'wp-components', 'wc-experimental' ),
|
||||||
$css_file_version
|
$css_file_version
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue