From 1b9127a406cdb4bd80733fb375ebf698d596095c Mon Sep 17 00:00:00 2001 From: Bryce Osterhaus Date: Wed, 1 May 2019 15:42:39 -0700 Subject: [PATCH] Fixes #1941 - Implement panel component with collapse ability --- migrate-the-clay-components-from-v2-to-v3.md | 51 +++++- packages/clay-panel/package.json | 15 +- packages/clay-panel/src/Body.tsx | 24 +++ packages/clay-panel/src/Footer.tsx | 24 +++ packages/clay-panel/src/Group.tsx | 62 +++++++ packages/clay-panel/src/Header.tsx | 24 +++ .../__tests__/__snapshots__/index.tsx.snap | 157 ++++++++++++++++++ packages/clay-panel/src/__tests__/index.tsx | 84 +++++++++- packages/clay-panel/src/index.tsx | 103 +++++++++++- packages/clay-panel/stories/index.tsx | 82 ++++++++- 10 files changed, 607 insertions(+), 19 deletions(-) create mode 100644 packages/clay-panel/src/Body.tsx create mode 100644 packages/clay-panel/src/Footer.tsx create mode 100644 packages/clay-panel/src/Group.tsx create mode 100644 packages/clay-panel/src/Header.tsx diff --git a/migrate-the-clay-components-from-v2-to-v3.md b/migrate-the-clay-components-from-v2-to-v3.md index 3803c01e74..2b16e03975 100644 --- a/migrate-the-clay-components-from-v2-to-v3.md +++ b/migrate-the-clay-components-from-v2-to-v3.md @@ -153,15 +153,15 @@ To get to the behavior of having a ClayLink with image, use the composition with ### API Changes -- `data` deprecated -- `defaultEventHandler` deprecated -- `elementClasses` renamed to `className` -- `icon` deprecated -- `imageAlt` deprecated -- `imageSrc` deprecated -- `label` deprecated in favor of `children` -- `spritemap` deprecated -- `style` renamed to `displayType` +- `data` deprecated +- `defaultEventHandler` deprecated +- `elementClasses` renamed to `className` +- `icon` deprecated +- `imageAlt` deprecated +- `imageSrc` deprecated +- `label` deprecated in favor of `children` +- `spritemap` deprecated +- `style` renamed to `displayType` ### Compositions @@ -249,3 +249,36 @@ For example: - `status` removed in favor of `warn` - `feedback` added to determine if `progress-group-feedback` is used, default value is false unless value is 100. - `warn` added to indicate `progress-warning` class + +## ClayCollapse -> ClayPanel + +ClayCollapse has been renamed to ClayPanel due to collapse being just a part of the panel functionality. ClayPanel is now simpler as it now manages its own expanded state internally. ClayPanel also is now created via composition with ``, ``, `` and ``. + +For example: + +```jsx + + {'Header!'} + {'Body!'} + {'Footer!'} + + +// or + + + {'One!'} + + + {'Two!'} + + +``` + +### API Changes + +- `closedClasses` removed and uses clay's classes of `collapse` and `show` +- `collapsed` renamed to `defaultExpanded` which will only take effect on first render. +- `content` removed in favor of `children` prop +- `headers` removed in favor of composing with `` +- `openClasses` removed and uses clay's class of `collapse` +- `transitionClasses` removed and manages transitions internally. diff --git a/packages/clay-panel/package.json b/packages/clay-panel/package.json index 373962dc0c..4271402eee 100644 --- a/packages/clay-panel/package.json +++ b/packages/clay-panel/package.json @@ -11,20 +11,29 @@ "main": "lib/index.js", "module": "src/index.tsx", "jsnext:main": "src/index.tsx", - "files": ["lib", "src"], + "files": [ + "lib", + "src" + ], "scripts": { "build": "cross-env NODE_ENV=production babel src --root-mode upward --out-dir lib --extensions .ts,.tsx", "build:types": "cross-env NODE_ENV=production tsc --project ./tsconfig.declarations.json", "prepublishOnly": "npm run build && npm run build:types", "test": "jest --config ../../jest.config.js" }, - "keywords": ["clay", "react"], + "keywords": [ + "clay", + "react" + ], "dependencies": { + "@clayui/icon": "^3.0.0", "classnames": "^2.2.6" }, "peerDependencies": { "react": "^16.8.1", "react-dom": "^16.8.1" }, - "browserslist": ["extends browserslist-config-clay"] + "browserslist": [ + "extends browserslist-config-clay" + ] } diff --git a/packages/clay-panel/src/Body.tsx b/packages/clay-panel/src/Body.tsx new file mode 100644 index 0000000000..73e24cf571 --- /dev/null +++ b/packages/clay-panel/src/Body.tsx @@ -0,0 +1,24 @@ +/** + * © 2019 Liferay, Inc. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +import * as React from 'react'; +import classNames from 'classnames'; + +interface Props extends React.HTMLAttributes {} + +const ClayPanelBody: React.FunctionComponent = ({ + children, + className, + ...otherProps +}) => { + return ( +
+ {children} +
+ ); +}; + +export default ClayPanelBody; diff --git a/packages/clay-panel/src/Footer.tsx b/packages/clay-panel/src/Footer.tsx new file mode 100644 index 0000000000..cb2b59b4da --- /dev/null +++ b/packages/clay-panel/src/Footer.tsx @@ -0,0 +1,24 @@ +/** + * © 2019 Liferay, Inc. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +import * as React from 'react'; +import classNames from 'classnames'; + +interface Props extends React.HTMLAttributes {} + +const ClayPanelFooter: React.FunctionComponent = ({ + children, + className, + ...otherProps +}) => { + return ( +
+ {children} +
+ ); +}; + +export default ClayPanelFooter; diff --git a/packages/clay-panel/src/Group.tsx b/packages/clay-panel/src/Group.tsx new file mode 100644 index 0000000000..67d29e6a86 --- /dev/null +++ b/packages/clay-panel/src/Group.tsx @@ -0,0 +1,62 @@ +/** + * © 2019 Liferay, Inc. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +import * as React from 'react'; +import classNames from 'classnames'; + +interface Props extends React.HTMLAttributes { + /** + * Flag to signify that `panel-group-fluid-first` class should be added. + * This class generally should be used inside card or sheet + */ + fluidFirst?: boolean; + + /** + * Flag to signify that `panel-group-fluid-last` class should be added. + * This class generally should be used inside card or sheet + */ + fluidLast?: boolean; + + /** + * Flag to signify that `panel-group-fluid` class should be added. + * This class generally should be used inside card or sheet + */ + fluid?: boolean; + + /** + * Flag to signify that `panel-group-flush` class should be added. + * This class generally should be used inside card, sheet, or a type of padded container. + */ + flush?: boolean; +} + +const ClayPanelGroup: React.FunctionComponent = ({ + children, + className, + fluid, + fluidFirst, + fluidLast, + flush, + ...otherProps +}) => { + return ( +
+ {children} +
+ ); +}; + +export default ClayPanelGroup; diff --git a/packages/clay-panel/src/Header.tsx b/packages/clay-panel/src/Header.tsx new file mode 100644 index 0000000000..9c62298243 --- /dev/null +++ b/packages/clay-panel/src/Header.tsx @@ -0,0 +1,24 @@ +/** + * © 2019 Liferay, Inc. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +import * as React from 'react'; +import classNames from 'classnames'; + +interface Props extends React.HTMLAttributes {} + +const ClayPanelHeader: React.FunctionComponent = ({ + children, + className, + ...otherProps +}) => { + return ( +
+ {children} +
+ ); +}; + +export default ClayPanelHeader; diff --git a/packages/clay-panel/src/__tests__/__snapshots__/index.tsx.snap b/packages/clay-panel/src/__tests__/__snapshots__/index.tsx.snap index d1e5c85a7a..c76926c95b 100644 --- a/packages/clay-panel/src/__tests__/__snapshots__/index.tsx.snap +++ b/packages/clay-panel/src/__tests__/__snapshots__/index.tsx.snap @@ -1 +1,158 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ClayPanel renders 1`] = ` +
+
+ + Display Title + +
+
+ Header! +
+
+ Body! +
+
+ Footer! +
+
+`; + +exports[`ClayPanel renders with different displayType 1`] = ` +
+
+ + Display Title + +
+
+ Header! +
+
+ Body! +
+
+ Footer! +
+
+`; + +exports[`ClayPanel renders with multiple panels 1`] = ` +
+
+
+ + Display Title + +
+
+ Body! +
+
+
+
+ + Display Title + +
+
+ Body! +
+
+
+ +
+
+ Body! +
+
+
+
+`; diff --git a/packages/clay-panel/src/__tests__/index.tsx b/packages/clay-panel/src/__tests__/index.tsx index adad92d26d..751846c2b2 100644 --- a/packages/clay-panel/src/__tests__/index.tsx +++ b/packages/clay-panel/src/__tests__/index.tsx @@ -7,13 +7,95 @@ import * as React from 'react'; import * as TestRenderer from 'react-test-renderer'; import ClayPanel from '..'; +import {cleanup, fireEvent, render} from 'react-testing-library'; describe('ClayPanel', () => { it('renders', () => { const testRenderer = TestRenderer.create( - + + {'Header!'} + {'Body!'} + {'Footer!'} + ); expect(testRenderer.toJSON()).toMatchSnapshot(); }); + + it('renders with different displayType', () => { + const testRenderer = TestRenderer.create( + + {'Header!'} + {'Body!'} + {'Footer!'} + + ); + + expect(testRenderer.toJSON()).toMatchSnapshot(); + }); + + it('renders with multiple panels', () => { + const testRenderer = TestRenderer.create( + + + {'Body!'} + + + + {'Body!'} + + + + {'Body!'} + + + ); + + expect(testRenderer.toJSON()).toMatchSnapshot(); + }); +}); + +describe('ClayPanel Interactions', () => { + afterEach(cleanup); + + it('clicking the title should expand and close the content', () => { + const {container} = render( + + {'Header!'} + {'Body!'} + {'Footer!'} + + ); + + const closeButton = container.querySelector('.panel-header'); + + expect(container.querySelector('.panel-collapse.show')).toBeFalsy(); + expect( + container.querySelector('.panel-collapse.collapse') + ).toBeTruthy(); + + fireEvent.click(closeButton as HTMLButtonElement, {}); + + expect(container.querySelector('.panel-collapse.show')).toBeTruthy(); + expect(container.querySelector('.panel-collapse.collapse')).toBeFalsy(); + + fireEvent.click(closeButton as HTMLButtonElement, {}); + + expect(container.querySelector('.panel-collapse.show')).toBeFalsy(); + expect( + container.querySelector('.panel-collapse.collapse') + ).toBeTruthy(); + }); }); diff --git a/packages/clay-panel/src/index.tsx b/packages/clay-panel/src/index.tsx index bab7a77212..608f820420 100644 --- a/packages/clay-panel/src/index.tsx +++ b/packages/clay-panel/src/index.tsx @@ -6,16 +6,113 @@ import * as React from 'react'; import classNames from 'classnames'; +import ClayIcon from '@clayui/icon'; +import ClayPanelBody from './Body'; +import ClayPanelFooter from './Footer'; +import ClayPanelGroup from './Group'; +import ClayPanelHeader from './Header'; -interface Props extends React.HTMLAttributes {} +interface Props extends React.HTMLAttributes { + collapsable?: boolean; + collapseClassNames?: string; + defaultExpanded?: boolean; + displayTitle?: React.ReactText; + displayType?: 'unstyled' | 'secondary'; + showCollapseIcon?: boolean; + spritemap?: string; +} -const ClayPanel: React.FunctionComponent = ({ +const ClayPanel: React.FunctionComponent & { + Body: typeof ClayPanelBody; + Footer: typeof ClayPanelFooter; + Group: typeof ClayPanelGroup; + Header: typeof ClayPanelHeader; +} = ({ + children, className, + collapsable, + collapseClassNames, + defaultExpanded = false, + displayTitle, + displayType, + showCollapseIcon = true, + spritemap, ...otherProps }) => { + const [expanded, setExpaned] = React.useState(defaultExpanded); + return ( -
{'ClayPanel'}
+
+ {!collapsable && ( + <> + {displayTitle && ( + + {displayTitle} + + )} + + {children} + + )} + + {collapsable && ( + <> + + +
+ {children} +
+ + )} +
); }; +ClayPanel.Body = ClayPanelBody; +ClayPanel.Group = ClayPanelGroup; +ClayPanel.Footer = ClayPanelFooter; +ClayPanel.Header = ClayPanelHeader; + export default ClayPanel; diff --git a/packages/clay-panel/stories/index.tsx b/packages/clay-panel/stories/index.tsx index 34cb1ea5ab..846a62422f 100644 --- a/packages/clay-panel/stories/index.tsx +++ b/packages/clay-panel/stories/index.tsx @@ -3,14 +3,90 @@ * * SPDX-License-Identifier: BSD-3-Clause */ + +import ClayPanel from '../src'; import React from 'react'; import {boolean, select, text} from '@storybook/addon-knobs'; import {storiesOf} from '@storybook/react'; -import ClayPanel from '../src'; import 'clay-css/lib/css/atlas.css'; +const spritemap = require('clay-css/lib/images/icons/icons.svg'); + storiesOf('ClayPanel', module) .add('default', () => ( - - )); \ No newline at end of file + + {'Header!'} + {'Body!'} + {'Footer!'} + + )) + .add('collapsable', () => ( + + {'Here is some content inside.'} + + )) + .add('groups', () => ( + + {['One', 'Two', 'Three'].map(item => ( + + + {`Here is some content inside for number ${item}`} + + + ))} + + )) + .add('w/ sheet', () => ( +
+ + {['One', 'Two', 'Three'].map(item => ( + + + {`Here is some content inside for number ${item}`} + + + ))} + +
+ ));