* Use Panel for Activity Panels

* Update styling for panels in activity panels

* Remove Accordion component

* Add collapsible param to activity panel tests

* Fix errant comma

* Add bottom border to panel header toggle and remove disbled button styling

* Update empty activity card styles

* Add border between activity cards
This commit is contained in:
Joshua T Flowers 2021-01-12 16:18:51 -05:00 committed by GitHub
parent f1d649a657
commit 0b3f4d8e92
13 changed files with 106 additions and 426 deletions

View File

@ -2,8 +2,14 @@
* External dependencies
*/
import { useSelect } from '@wordpress/data';
import { Fragment } from '@wordpress/element';
import { Accordion, AccordionPanel } from '@woocommerce/components';
import { Badge } from '@woocommerce/components';
import {
Button,
Panel,
PanelBody,
PanelRow,
__experimentalText as Text,
} from '@wordpress/components';
import { getSetting } from '@woocommerce/wc-admin-settings';
/**
@ -48,32 +54,48 @@ export const ActivityPanel = () => {
}
return (
<Accordion>
<Fragment>
{ panels.map( ( panelData ) => {
const {
className,
count,
id,
initialOpen,
panel,
title,
collapsible,
} = panelData;
return (
<AccordionPanel
key={ id }
className={ className }
count={ count }
initialOpen={ initialOpen }
title={ title }
collapsible={ collapsible }
>
{ panel }
</AccordionPanel>
);
} ) }
</Fragment>
</Accordion>
<Panel className="woocommerce-activity-panel">
{ panels.map( ( panelData ) => {
const {
className,
count,
id,
initialOpen,
panel,
title,
collapsible,
} = panelData;
return collapsible ? (
<PanelBody
title={ [
<Text key={ title } variant="title.small">
{ title }
</Text>,
count !== null && <Badge count={ count } />,
] }
key={ id }
className={ className }
initialOpen={ initialOpen }
collapsible={ collapsible }
disabled={ ! collapsible }
>
<PanelRow>{ panel }</PanelRow>
</PanelBody>
) : (
<div className="components-panel__body">
<h2 className="components-panel__body-title">
<Button
className="components-panel__body-toggle"
aria-expanded={ false }
disabled={ true }
>
<Text variant="title.small">{ title }</Text>
{ count !== null && <Badge count={ count } /> }
</Button>
</h2>
</div>
);
} ) }
</Panel>
);
};

View File

@ -1,13 +1,4 @@
.woocommerce-accordion-card {
.woocommerce-empty-activity-card {
background: unset;
text-align: center;
padding: ( $fallback-gutter / 2 ) $gutter 0;
h4 {
color: $gray-900;
}
}
.woocommerce-activity-panel {
.woocommerce-order-empty__success-icon {
font-size: 36px;
}

View File

@ -1,67 +1,70 @@
@mixin accordion-header {
.woocommerce-accordion-header {
order: 1;
text-align: left;
.woocommerce-accordion-title {
margin-right: $gap;
}
}
}
.woocommerce-accordion-card {
.components-card__header,
.components-panel__body-title .components-button {
font-size: 20px;
line-height: 28px;
color: $gray-900;
&:focus {
box-shadow: unset;
outline: unset;
.woocommerce-activity-panel {
background: transparent;
border: 0;
.components-panel__body {
background: #fff;
border: 1px solid $gray-200;
&.is-opened {
margin-bottom: $gap-large;
.components-panel__body-toggle {
border-bottom: 1px solid $gray-200;
}
}
}
.components-card__header {
height: 60px;
@include accordion-header();
&.components-panel > .components-panel__body:last-child {
border-bottom: 1px solid $gray-200;
}
.components-panel__body-title {
margin: 0;
padding: $gap-small 0;
height: 60px;
.components-button {
.components-panel__row {
margin: 0 -#{$gap} -#{$gap} -#{$gap};
> div {
width: 100%;
padding-left: 24px;
font-style: normal;
font-weight: 400;
display: flex;
flex-direction: row;
@include accordion-header();
.woocommerce-accordion-header {
width: 50%;
}
& > span {
order: 2;
width: 50%;
text-align: right;
.components-panel__arrow {
width: 30px;
height: 30px;
vertical-align: text-top;
}
}
}
}
.components-panel__body-toggle {
border-radius: 0;
&:disabled {
opacity: 1;
}
}
.woocommerce-activity-panel {
margin-bottom: $gap-large;
}
.components-panel__body-title p {
margin-right: $gap;
}
.woocommerce-activity-card {
padding: $gap-largest/2 $gutter $gutter;
&:not(:last-of-type) {
border-bottom: 1px solid $gray-200;
}
&__header {
margin-bottom: $gap-small;
}
}
.woocommerce-empty-activity-card {
margin: 0;
background: unset;
text-align: center;
padding: $gap-large $gutter $gap-smallest;
h4 {
color: $gray-900;
}
}
.woocommerce-layout__activity-panel-outbound-link {
display: flex;
justify-content: space-between;
@ -110,6 +113,7 @@
.woocommerce-layout__activity-panel-empty {
border-top: 1px solid $gray-200;
border-bottom: 0;
}
.woocommerce-activity-card__button {
@ -120,9 +124,3 @@
}
}
}
.components-panel__body-title {
.woocommerce-accordion-card .is-opened & {
border-bottom: 1px solid $gray-200;
}
}

View File

@ -18,6 +18,7 @@ jest.mock( '../panels', () => {
count: 10000,
initialOpen: true,
panel: <span>Custom panel 1</span>,
collapsible: true,
},
{
id: 'custom-panel-2',
@ -25,6 +26,7 @@ jest.mock( '../panels', () => {
count: 20000,
initialOpen: false,
panel: <span>Custom panel 2</span>,
collapsible: true,
},
] ),
};

View File

@ -1,7 +1,6 @@
# Unreleased
- Change styling of `<ProductImage />`.
- Add new `<Accordion>` component.
- Remove the `showCount` prop from `<SearchListItem>`. Count will always be displayed if any of those props is not undefined/null: `countLabel` and `item.count`.
- Fix alignment of `<SearchListItem>` count bubble in newest versions of `@wordpress/components`.
- `<SearchListControl>` no longer has different styles when it's used inside a panel. Those styles are available now with the `isCompact` prop.

View File

@ -1,59 +0,0 @@
# Accordion
This is an accordion that renders the children inside. It will toggle the panel's content when the title is clicked.
## Usage
```jsx
<Accordion>
<AccordionPanel
className="class-name"
count={ 15 }
title="Panel 1"
initialOpen={ true }
>
<span>Panel 1 content</span>
</AccordionPanel>
<AccordionPanel
className="class-name"
count={ 20 }
title="Panel 2"
initialOpen={ false }
>
<span>Panel 2 content</span>
</AccordionPanel>
</Accordion>
```
### Props
| Name | Type | Default | Description |
| ----------- | ------ | ------- | ----------------------- |
| `className` | String | `null` | Additional CSS classes. |
# AccordionPanel
A component designed for use inside of the `Accordion` component.
`AccordionPanel` is used to give the panel content an accessible wrapper.
## Usage
```jsx
<AccordionPanel
className="class-name"
count={ 15 }
title="Panel 1"
initialOpen={ true }
>
<span>Panel 1 content</span>
</AccordionPanel>
```
### Props
| Name | Type | Default | Description |
| ------------- | ------- | ------- | ------------------------------------------------------------------------------------ |
| `className` | Array | `null` | A list of objects with data to set the panels. |
| `count` | Number | `null` | Number of unread elements in the panel that will be shown next to the panel's title. |
| `initialOpen` | Boolean | `true` | Whether or not the panel will start open. |
| `title` | String | `null` | The panel title. |

View File

@ -1,31 +0,0 @@
/**
* External dependencies
*/
import { Panel } from '@wordpress/components';
import PropTypes from 'prop-types';
import classnames from 'classnames';
/**
* Use `Accordion` to display an accordion that renders the children inside.
*
* @param {Object} props
* @param {string} props.children
* @param {string} props.className
* @return {Object} -
*/
const Accordion = ( { className, children } ) => {
return (
<Panel className={ classnames( className, 'woocommerce-accordion' ) }>
{ children }
</Panel>
);
};
Accordion.propTypes = {
/**
* Additional CSS classes.
*/
className: PropTypes.string,
};
export default Accordion;

View File

@ -1,111 +0,0 @@
/**
* External dependencies
*/
import { Card, CardHeader, PanelBody, PanelRow } from '@wordpress/components';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useState, useEffect } from '@wordpress/element';
/**
* Internal dependencies
*/
import { Badge } from '../badge';
/**
* `AccordionPanel` is used to give the panel content an accessible wrapper.
*
* @param {Object} props
* @param {string} props.className
* @param {string} props.count
* @param {string} props.children
* @param {string} props.title
* @param {string} props.initialOpen
* @param {boolean} props.collapsible
* @return {Object} -
*/
const AccordionPanel = ( {
className,
count,
title,
initialOpen,
collapsible,
children,
} ) => {
const [ isPanelOpen, setIsPanelOpen ] = useState( null );
useEffect( () => {
if ( ! collapsible && isPanelOpen ) {
setIsPanelOpen( ! isPanelOpen );
}
}, [ collapsible ] );
const getTitleAndCount = ( titleText, countUnread ) => {
return (
<span className="woocommerce-accordion-header">
<span className="woocommerce-accordion-title">
{ titleText }
</span>
{ countUnread !== null && <Badge count={ countUnread } /> }
</span>
);
};
const opened = isPanelOpen === null ? initialOpen : isPanelOpen;
const onToggle = () => {
setIsPanelOpen( ! opened );
};
return (
<Card
size="large"
className={ classnames( className, 'woocommerce-accordion-card', {
'is-panel-opened': opened,
} ) }
>
{ collapsible ? (
<PanelBody
title={ getTitleAndCount( title, count ) }
initialOpen={ opened }
onToggle={ onToggle }
>
<PanelRow> { children } </PanelRow>
</PanelBody>
) : (
<CardHeader size="medium">
{ getTitleAndCount( title, count ) }
</CardHeader>
) }
</Card>
);
};
AccordionPanel.propTypes = {
/**
* Additional CSS classes.
*/
className: PropTypes.string,
/**
* Number of unread elements in the panel that will be shown next to the panel's title.
*/
count: PropTypes.number,
/**
* The panel title.
*/
title: PropTypes.string,
/**
* Whether or not the panel will start open.
*/
initialOpen: PropTypes.bool,
/**
* Whether or not the panel can be collapsed or not.
*/
collapsible: PropTypes.bool,
};
AccordionPanel.defaultProps = {
initialOpen: true,
collapsible: true,
};
export default AccordionPanel;

View File

@ -1,30 +0,0 @@
/**
* External dependencies
*/
import { Accordion, AccordionPanel } from '@woocommerce/components';
export const Basic = () => (
<Accordion>
<AccordionPanel
className="class-name"
count={ 15 }
title="Panel 1"
initialOpen={ true }
>
<span>Panel 1 content</span>
</AccordionPanel>
<AccordionPanel
className="class-name"
count={ 20 }
title="Panel 2"
initialOpen={ false }
>
<span>Panel 2 content</span>
</AccordionPanel>
</Accordion>
);
export default {
title: 'WooCommerce Admin/components/Accordion',
component: Accordion,
};

View File

@ -1,26 +0,0 @@
.woocommerce-accordion {
&-badge {
background-color: $gray-100;
border-radius: 20px;
display: inline-block;
text-align: center;
font-style: normal;
font-weight: 600;
font-size: 14px;
line-height: 27px;
align-items: center;
width: 32px;
height: 28px;
margin-left: $gap;
}
&-card {
&.is-panel-opened {
&:not(:first-child) {
margin-top: 20px;
}
&:not(:last-child) {
margin-bottom: 20px;
}
}
}
}

View File

@ -1,71 +0,0 @@
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';
/**
* Internal dependencies
*/
import Accordion from '../';
import AccordionPanel from '../panel';
const panels = (
<>
<AccordionPanel
className="panel-1"
count={ 10000 }
title="panel-1"
initialOpen={ true }
>
<span>Custom panel 1</span>
</AccordionPanel>
<AccordionPanel
className="panel-2"
count={ 20000 }
title="panel-2"
initialOpen={ false }
>
<span>Custom panel 1</span>
</AccordionPanel>
</>
);
describe( 'Accordion', () => {
it( 'should render a panel with two rows', () => {
render( <Accordion> { panels } </Accordion> );
expect( screen.getByText( 'panel-1' ) ).not.toBeNull();
expect( screen.getByText( 'panel-2' ) ).not.toBeNull();
} );
it( 'should render one visible panel and one hidden panel', () => {
render( <Accordion> { panels } </Accordion> );
expect( screen.queryByText( 'Custom panel 1' ) ).toBeInTheDocument();
expect(
screen.queryByText( 'Custom panel 2' )
).not.toBeInTheDocument();
} );
it( 'should render the count of unread items', () => {
render( <Accordion> { panels } </Accordion> );
expect( screen.queryByText( '10000' ) ).toBeInTheDocument();
expect( screen.queryByText( '20000' ) ).toBeInTheDocument();
} );
it( 'should only render title if collapsible is false', () => {
render(
<Accordion>
{ ' ' }
<AccordionPanel
title="empty title"
initialOpen={ false }
collapsible={ false }
>
<span>Custom panel 1</span>
</AccordionPanel>
</Accordion>
);
expect( screen.queryByText( 'empty title' ) ).toBeInTheDocument();
expect( screen.queryByRole( 'button' ) ).toBeNull();
expect( screen.queryByText( 'Custom panel 1' ) ).toBeNull();
} );
} );

View File

@ -4,8 +4,6 @@
import 'react-dates/initialize';
// The above: Turn on react-dates classes/styles, see https://github.com/airbnb/react-dates#initialize
export { default as Accordion } from './accordion';
export { default as AccordionPanel } from './accordion/panel';
export { default as AdvancedFilters } from './advanced-filters';
export { default as AnimationSlider } from './animation-slider';
export { default as Chart } from './chart';

View File

@ -1,8 +1,6 @@
/**
* Internal Dependencies
*/
@import 'accordion/style.scss';
@import 'animation-slider/style.scss';
@import 'calendar/style.scss';
@import 'card/style.scss';
@import 'chart/style.scss';