From c173fd624e785a33ba5d35d813fe74506ad8fd08 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Thu, 20 Aug 2020 22:02:35 -0700 Subject: [PATCH 01/49] feat(side-panel): Init Labs Side Panel --- modules/_labs/side-panel/react/LICENSE | 52 ++++++++ modules/_labs/side-panel/react/README.md | 36 +++++ modules/_labs/side-panel/react/index.ts | 5 + .../_labs/side-panel/react/lib/SidePanel.tsx | 63 +++++++++ modules/_labs/side-panel/react/package.json | 53 ++++++++ .../_labs/side-panel/react/spec/SSR.spec.tsx | 13 ++ .../side-panel/react/stories/stories.tsx | 123 ++++++++++++++++++ .../react/stories/stories_VisualTesting.tsx | 56 ++++++++ .../_labs/side-panel/react/tsconfig.cjs.json | 10 ++ .../_labs/side-panel/react/tsconfig.es6.json | 8 ++ modules/_labs/side-panel/react/tsconfig.json | 4 + 11 files changed, 423 insertions(+) create mode 100644 modules/_labs/side-panel/react/LICENSE create mode 100644 modules/_labs/side-panel/react/README.md create mode 100644 modules/_labs/side-panel/react/index.ts create mode 100644 modules/_labs/side-panel/react/lib/SidePanel.tsx create mode 100644 modules/_labs/side-panel/react/package.json create mode 100644 modules/_labs/side-panel/react/spec/SSR.spec.tsx create mode 100644 modules/_labs/side-panel/react/stories/stories.tsx create mode 100644 modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx create mode 100644 modules/_labs/side-panel/react/tsconfig.cjs.json create mode 100644 modules/_labs/side-panel/react/tsconfig.es6.json create mode 100644 modules/_labs/side-panel/react/tsconfig.json diff --git a/modules/_labs/side-panel/react/LICENSE b/modules/_labs/side-panel/react/LICENSE new file mode 100644 index 0000000000..594eb66af8 --- /dev/null +++ b/modules/_labs/side-panel/react/LICENSE @@ -0,0 +1,52 @@ +Apache License, Version 2.0 Apache License Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. +You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: +You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. +Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. +This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. +Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. +In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. +While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +©2020. Workday, Inc. All rights reserved. Workday and the Workday logo are registered trademarks of Workday, Inc. All other brand and product names are trademarks or registered trademarks of their respective holders. + + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/modules/_labs/side-panel/react/README.md b/modules/_labs/side-panel/react/README.md new file mode 100644 index 0000000000..7db27cddbe --- /dev/null +++ b/modules/_labs/side-panel/react/README.md @@ -0,0 +1,36 @@ +# Canvas Kit React Side Panel + + + LABS: Alpha + This component is work in progress and currently in pre-release. + +A collapsable side panel + +## Installation + +```sh +yarn add @workday/canvas-kit-labs-react-side-panel +``` + +## Usage + +```tsx +import * as React from 'react'; +import SidePanel from '@workday/canvas-kit-labs-react-side-panel'; + +; +``` + +## Static Properties + +> None + +## Component Props + +### Required + +> None + +### Optional + +> None diff --git a/modules/_labs/side-panel/react/index.ts b/modules/_labs/side-panel/react/index.ts new file mode 100644 index 0000000000..3ccf10db9f --- /dev/null +++ b/modules/_labs/side-panel/react/index.ts @@ -0,0 +1,5 @@ +import SidePanel from './lib/SidePanel'; + +export default SidePanel; +export {SidePanel}; +export * from './lib/SidePanel'; diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx new file mode 100644 index 0000000000..262c8701c1 --- /dev/null +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -0,0 +1,63 @@ +import * as React from 'react'; +import styled from '@emotion/styled'; +import {colors} from '@workday/canvas-kit-react-core'; + +export interface SidePanelProps extends React.HTMLAttributes { + /** + * Specifies the state the side panel is in (collapsed or expanded) as well as its width. If true, width is set to the `collapsedWidth` prop. Otherwise, it is set to the `width` prop + * + * @defualt false + */ + collapsed?: boolean; + /** + * The width of the component (in `px` if it's a `number`) when it is collapsed. + * + * @default 64 + */ + collapsedWidth?: number | string; + /** + * The height of the component (in `px` if it's a `number`). + * + * @default 400 + */ + height?: number | string; + /** + * The width of the component (in `px if it's a `number`) when it is expanded. + */ + width?: number | string; +} + +const Container = styled('div')( + { + overflow: 'hidden', + position: 'relative', + backgroundColor: colors.soap100, + boxSizing: 'border-box', + transition: 'min-width 0.2s, max-width 0.2s, width 0.2s ease-out 0s', + }, + ({collapsed, collapsedWidth, width, height}) => ({ + width: collapsed ? collapsedWidth : width, + maxWidth: collapsed ? collapsedWidth : width, + minWidth: collapsed ? collapsedWidth : width, + height: height, + maxHeight: height, + minHeight: height, + }) +); + +const SidePanel: React.FC = props => { + const {width = 320, collapsedWidth = 64, height = 400, collapsed = false, ...elemProps} = props; + + return ( + + ); +}; + +export default SidePanel; diff --git a/modules/_labs/side-panel/react/package.json b/modules/_labs/side-panel/react/package.json new file mode 100644 index 0000000000..ec2ec26a76 --- /dev/null +++ b/modules/_labs/side-panel/react/package.json @@ -0,0 +1,53 @@ + +{ + "name": "@workday/canvas-kit-labs-react-side-panel", + "version": "0.0.0", + "description": "A collapsable side panel", + "author": "Workday, Inc. (https://www.workday.com)", + "license": "Apache-2.0", + "main": "dist/commonjs/index.js", + "module": "dist/es6/index.js", + "sideEffects": false, + "types": "dist/es6/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/Workday/canvas-kit/tree/master/modules/_labs/side-panel/react" + }, + "files": [ + "dist/", + "lib/", + "index.ts", + "ts3.5/**/*" + ], + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + }, + "scripts": { + "watch": "yarn build:es6 -w", + "clean": "rimraf dist && rimraf ts3.5 && rimraf .build-info && mkdirp dist && mkdirp ts3.5/dist", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build:es6": "tsc -p tsconfig.es6.json", + "build:rebuild": "npm-run-all clean build", + "build:downlevel-dts": "yarn run downlevel-dts dist ts3.5/dist", + "build": "npm-run-all --parallel build:cjs build:es6 --sequential build:downlevel-dts", + "depcheck": "node ../../../../utils/check-dependencies-exist.js" + }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "canvas", + "canvas-kit", + "react", + "components", + "workday", + "side-panel" + ], + "peerDependencies": { + "react": ">= 16.8 < 17" + } +} diff --git a/modules/_labs/side-panel/react/spec/SSR.spec.tsx b/modules/_labs/side-panel/react/spec/SSR.spec.tsx new file mode 100644 index 0000000000..f373f64fca --- /dev/null +++ b/modules/_labs/side-panel/react/spec/SSR.spec.tsx @@ -0,0 +1,13 @@ +/** + * @jest-environment node + */ +import React from 'react'; +import {renderToString} from 'react-dom/server'; +import SidePanel from '../'; + +describe('SidePanel', () => { + it('should render on a server without crashing', () => { + const ssrRender = () => renderToString(); + expect(ssrRender).not.toThrow(); + }); +}); diff --git a/modules/_labs/side-panel/react/stories/stories.tsx b/modules/_labs/side-panel/react/stories/stories.tsx new file mode 100644 index 0000000000..8f1ea6c2b1 --- /dev/null +++ b/modules/_labs/side-panel/react/stories/stories.tsx @@ -0,0 +1,123 @@ +/// +import * as React from 'react'; +import withReadme from 'storybook-readme/with-readme'; + +import styled from '@emotion/styled'; +import {spacing} from '@workday/canvas-kit-react-core'; +import {type} from '@workday/canvas-kit-labs-react-core'; +import {IconButton} from '@workday/canvas-kit-react-button'; +import {AccentIcon} from '@workday/canvas-kit-react-icon'; +import {rocketIcon} from '@workday/canvas-accent-icons-web'; +import {transformationImportIcon} from '@workday/canvas-system-icons-web'; + +import SidePanel, {SidePanelProps} from '../index'; +import README from '../README.md'; + +export default { + title: 'Labs|Side Panel/React', + decorators: [withReadme(README)], + component: SidePanel, +}; + +export const LeftSidePanel = () => { + const [collapsed, setCollapsed] = React.useState(false); + + const SidePanelHeader = styled('div')>( + { + display: 'flex', + }, + ({collapsed}) => ({ + alignItems: collapsed ? undefined : 'center', + justifyContent: collapsed ? 'center' : 'space-between', + padding: collapsed + ? `${spacing.s} 0 0 0` + : `${spacing.m} ${spacing.xxs} ${spacing.m} ${spacing.s}`, + left: collapsed ? 0 : undefined, + right: collapsed ? 0 : undefined, + margin: collapsed ? 'auto' : undefined, + }) + ); + + const heightOffset = 40; + + const buttonStyle = { + transform: `rotate(${collapsed ? 0 : 180}deg)`, + }; + + const headingStyle = { + ...type.h4, + flex: 1, + margin: `0 ${spacing.s}`, + }; + + return ( + + + {collapsed ? null : } + {collapsed ? null :

Left Side Panel

} + { + setCollapsed(!collapsed); + }} + /> +
+ +
+ ); +}; + +export const RightSidePanel = () => { + const [collapsed, setCollapsed] = React.useState(false); + + const SidePanelHeader = styled('div')>( + { + display: 'flex', + }, + ({collapsed}) => ({ + alignItems: collapsed ? undefined : 'center', + justifyContent: collapsed ? 'center' : 'space-between', + padding: collapsed + ? `${spacing.s} 0 0 0` + : `${spacing.m} ${spacing.s} ${spacing.m} ${spacing.xxs}`, + left: collapsed ? 0 : undefined, + right: collapsed ? 0 : undefined, + margin: collapsed ? 'auto' : undefined, + }) + ); + + const heightOffset = 40; + + const buttonStyle = { + transform: `rotate(${collapsed ? 180 : 0}deg)`, + }; + + const headingStyle = { + ...type.h4, + flex: 1, + margin: `0 ${spacing.s}`, + }; + + return ( + + + { + setCollapsed(!collapsed); + }} + /> + {collapsed ? null :

Right Side Panel

} +
+ +
+ ); +}; diff --git a/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx b/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx new file mode 100644 index 0000000000..8ef5c61839 --- /dev/null +++ b/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx @@ -0,0 +1,56 @@ +/// +/** @jsx jsx */ +import {jsx} from '@emotion/core'; +import {StaticStates} from '@workday/canvas-kit-labs-react-core'; +import { + ComponentStatesTable, + permutateProps, + withSnapshotsEnabled, +} from '../../../../../utils/storybook'; +import SidePanel from '../index'; + +export default withSnapshotsEnabled({ + title: 'Testing|React/Labs/Side Panel', + component: SidePanel, +}); + +export const SidePanelStates = () => ( + // + // { + // if (props.disabled && !['', 'hover'].includes(props.className)) { + // return false; + // } + // return true; + // } + // )} + // > + // {props => ( + // + // )} + // + // + +); diff --git a/modules/_labs/side-panel/react/tsconfig.cjs.json b/modules/_labs/side-panel/react/tsconfig.cjs.json new file mode 100644 index 0000000000..e266cb9a95 --- /dev/null +++ b/modules/_labs/side-panel/react/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "module": "commonjs", + "outDir": "dist/commonjs", + "skipLibCheck": true, + "tsBuildInfoFile": "./.build-info/tsconfig.cjs.tsbuildinfo" + } +} diff --git a/modules/_labs/side-panel/react/tsconfig.es6.json b/modules/_labs/side-panel/react/tsconfig.es6.json new file mode 100644 index 0000000000..601797a826 --- /dev/null +++ b/modules/_labs/side-panel/react/tsconfig.es6.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "dist/es6", + "tsBuildInfoFile": "./.build-info/tsconfig.es6.tsbuildinfo" + } +} diff --git a/modules/_labs/side-panel/react/tsconfig.json b/modules/_labs/side-panel/react/tsconfig.json new file mode 100644 index 0000000000..efd4804f08 --- /dev/null +++ b/modules/_labs/side-panel/react/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../../tsconfig.json", + "exclude": ["node_modules", "ts-tmp", "dist", "spec", "stories"] +} From 1ab9273a9c9b84cb86a7e73947c1690845ef347c Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Sun, 23 Aug 2020 22:41:52 -0700 Subject: [PATCH 02/49] feat(side-panel): Add todo item --- modules/_labs/side-panel/react/lib/SidePanel.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index 262c8701c1..4b02735bca 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -48,6 +48,8 @@ const Container = styled('div')( const SidePanel: React.FC = props => { const {width = 320, collapsedWidth = 64, height = 400, collapsed = false, ...elemProps} = props; + // TODO: if we don't ship an IconButton with the side panel we should + // ship a hook that spreads aria-controls and aria-expanded (how?) return ( Date: Fri, 18 Sep 2020 16:08:35 -0700 Subject: [PATCH 03/49] feat(side-panel): Init Side Panel --- .../_labs/side-panel/react/lib/SidePanel.tsx | 220 ++++++++++++++--- modules/_labs/side-panel/react/lib/hooks.ts | 23 ++ modules/_labs/side-panel/react/package.json | 3 +- .../side-panel/react/stories/stories.tsx | 172 ++++++------- .../react/stories/stories_VisualTesting.tsx | 84 ++++--- yarn.lock | 227 +++++++++++++++++- 6 files changed, 555 insertions(+), 174 deletions(-) create mode 100644 modules/_labs/side-panel/react/lib/hooks.ts diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index 4b02735bca..67738dcfdf 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -1,20 +1,29 @@ +/** @jsx jsx */ import * as React from 'react'; -import styled from '@emotion/styled'; -import {colors} from '@workday/canvas-kit-react-core'; +import {css, jsx, keyframes, CSSObject} from '@emotion/core'; +import {useUniqueId} from '@workday/canvas-kit-react-common'; +import {IconButton} from '@workday/canvas-kit-react-button'; +import {spacing, colors, depth} from '@workday/canvas-kit-react-core'; +import {transformationImportIcon} from '@workday/canvas-system-icons-web'; + +import {useControllableState} from './hooks'; + +export type SidePanelVariant = 'standard' | 'alternate'; +export type SidePanelInternalStates = 'collapsed' | 'collapsing' | 'expanded' | 'expanding'; export interface SidePanelProps extends React.HTMLAttributes { /** - * Specifies the state the side panel is in (collapsed or expanded) as well as its width. If true, width is set to the `collapsedWidth` prop. Otherwise, it is set to the `width` prop + * Specifies the _controlled_ state the side panel is in. Leave undefined if you want to keep this an _uncontrolled_ component. * - * @defualt false + * @default false */ collapsed?: boolean; /** - * The width of the component (in `px` if it's a `number`) when it is collapsed. + * Specifies default state (collapsed or expanded) if the component is uncontrolled. * - * @default 64 + * @default false */ - collapsedWidth?: number | string; + defaultCollapsed?: boolean; /** * The height of the component (in `px` if it's a `number`). * @@ -22,43 +31,184 @@ export interface SidePanelProps extends React.HTMLAttributes { */ height?: number | string; /** - * The width of the component (in `px if it's a `number`) when it is expanded. + * The width of the component (in `px` if it's a `number`) when it is expanded. + * + * @default 320 */ width?: number | string; + /** + * The width of the component (in `px` if it's a `number`) when it is collapsed. + * + * @default 64 + */ + collapsedWidth?: number | string; + /** + * Specifies which side the side panel is meant to originate from. + * + * @default 'left' + */ + origin?: 'left' | 'right'; + /** + * Fired when the side panel state changes + * + * @param boolean + */ + onCollapsedChange?: (collapsed?: boolean, animationState?: SidePanelInternalStates) => void; + /** + * Style variants of the side panel + * + * @default 'grey' + */ + variant?: SidePanelVariant; } -const Container = styled('div')( - { - overflow: 'hidden', - position: 'relative', +const createKeyframes = (from: number | string, to: number | string) => { + const normalized = { + from: typeof from === 'number' ? from + 'px' : from, + to: typeof to === 'number' ? to + 'px' : to, + }; + + return keyframes` + from { + width: ${normalized.from}; + min-width: ${normalized.from}; + max-width: ${normalized.from}; + } to { + width: ${normalized.to}; + min-width: ${normalized.to}; + max-width: ${normalized.to}; + } + `; +}; + +const containerVariantStyle: {[K in SidePanelVariant]: CSSObject} = { + alternate: { + backgroundColor: colors.frenchVanilla100, + ...depth[3], + }, + standard: { backgroundColor: colors.soap100, - boxSizing: 'border-box', - transition: 'min-width 0.2s, max-width 0.2s, width 0.2s ease-out 0s', }, - ({collapsed, collapsedWidth, width, height}) => ({ - width: collapsed ? collapsedWidth : width, - maxWidth: collapsed ? collapsedWidth : width, - minWidth: collapsed ? collapsedWidth : width, - height: height, - maxHeight: height, - minHeight: height, - }) -); - -const SidePanel: React.FC = props => { - const {width = 320, collapsedWidth = 64, height = 400, collapsed = false, ...elemProps} = props; - - // TODO: if we don't ship an IconButton with the side panel we should - // ship a hook that spreads aria-controls and aria-expanded (how?) +}; + +const SidePanel = ({ + id, + children, + collapsed: collapsedProp, + collapsedWidth = 64, + defaultCollapsed = false, + height = 400, + onAnimationEnd, + onAnimationStart, + // eslint-disable-next-line no-empty-function + onCollapsedChange, + origin = 'left', + variant = 'standard', + width = 320, + ...elemProps +}: SidePanelProps) => { + const sidePanelId = useUniqueId(id); + const mounted = React.useRef(false); + const [collapsed, setCollapsed] = useControllableState( + collapsedProp, + defaultCollapsed + ); + const [internalState, setInternalState] = React.useState( + collapsed ? 'collapsed' : 'expanded' + ); + // This is meant to prevent animations when the component renders for the first time. + // mounted.current will only be false on the first pass + React.useEffect(() => { + mounted.current = true; + }, []); + + // Call our onCollapsedChange cb when either collapsed prop or internalState changes + React.useEffect(() => { + if (typeof onCollapsedChange !== 'undefined') { + onCollapsedChange(collapsed, internalState); + } + }, [collapsed, internalState, onCollapsedChange]); + + const motion = { + collapse: createKeyframes(collapsedWidth, width), + expand: createKeyframes(width, collapsedWidth), + }; + + const handleClick = () => { + setCollapsed(!collapsed); + }; + + const handleAnimationEnd = (event: React.AnimationEvent) => { + if (event.currentTarget === event.target) { + setInternalState(collapsed ? 'collapsed' : 'expanded'); + } + + if (typeof onAnimationEnd !== 'undefined') { + onAnimationEnd(event); + } + }; + + const handleAnimationStart = (event: React.AnimationEvent) => { + if (event.currentTarget === event.target) { + setInternalState(collapsed ? 'collapsing' : 'expanding'); + } + + if (typeof onAnimationStart !== 'undefined') { + onAnimationStart(event); + } + }; + + // Note: Depending on the collapsed width, the button could "jump" to it's final position. + const buttonStyle = css({ + position: 'absolute', + top: spacing.l, + right: internalState === 'collapsed' ? 0 : origin === 'left' ? spacing.s : undefined, + left: internalState === 'collapsed' ? 0 : origin === 'right' ? spacing.s : undefined, + margin: internalState === 'collapsed' ? 'auto' : undefined, + transform: collapsed + ? `scaleX(${origin === 'left' ? '1' : '-1'})` + : `scaleX(${origin === 'left' ? '-1' : '1'})`, + }); + + // TODO: if we're in controlled mode should we ship a hook that spreads aria-controls and aria-expanded return ( - + > + {typeof collapsedProp === 'undefined' ? ( + + ) : null} + {children} + ); }; diff --git a/modules/_labs/side-panel/react/lib/hooks.ts b/modules/_labs/side-panel/react/lib/hooks.ts new file mode 100644 index 0000000000..690577e5e7 --- /dev/null +++ b/modules/_labs/side-panel/react/lib/hooks.ts @@ -0,0 +1,23 @@ +import * as React from 'react'; + +// TODO: From our friend NicholasBoll with slight modifications: https://codesandbox.io/s/controllable-tooltip-sfnus?file=/src/useControllableState.ts +export const useControllableState = ( + value: T, + defaultValue: T | (() => T) +): [T, (value: T) => void] => { + const [valueState, setValueState] = React.useState(defaultValue); + const isControlled = value !== undefined; + + // TODO: warn about switching between controlled and uncontrolled + // TODO: warn about changing the default value + + const effectiveValue = isControlled ? value : valueState; + + const updateValue = (newValue: T) => { + if (!isControlled) { + setValueState(newValue); + } + }; + + return [effectiveValue, updateValue]; +}; diff --git a/modules/_labs/side-panel/react/package.json b/modules/_labs/side-panel/react/package.json index ec2ec26a76..7dd3103f3e 100644 --- a/modules/_labs/side-panel/react/package.json +++ b/modules/_labs/side-panel/react/package.json @@ -1,4 +1,3 @@ - { "name": "@workday/canvas-kit-labs-react-side-panel", "version": "0.0.0", @@ -35,7 +34,7 @@ "build:downlevel-dts": "yarn run downlevel-dts dist ts3.5/dist", "build": "npm-run-all --parallel build:cjs build:es6 --sequential build:downlevel-dts", "depcheck": "node ../../../../utils/check-dependencies-exist.js" - }, + }, "publishConfig": { "access": "public" }, diff --git a/modules/_labs/side-panel/react/stories/stories.tsx b/modules/_labs/side-panel/react/stories/stories.tsx index 8f1ea6c2b1..710e35bd7a 100644 --- a/modules/_labs/side-panel/react/stories/stories.tsx +++ b/modules/_labs/side-panel/react/stories/stories.tsx @@ -2,15 +2,10 @@ import * as React from 'react'; import withReadme from 'storybook-readme/with-readme'; -import styled from '@emotion/styled'; -import {spacing} from '@workday/canvas-kit-react-core'; -import {type} from '@workday/canvas-kit-labs-react-core'; -import {IconButton} from '@workday/canvas-kit-react-button'; -import {AccentIcon} from '@workday/canvas-kit-react-icon'; -import {rocketIcon} from '@workday/canvas-accent-icons-web'; -import {transformationImportIcon} from '@workday/canvas-system-icons-web'; +import SidePanel from '../index'; +import {Button} from '@workday/canvas-kit-react-button'; +import {colors, depth} from '@workday/canvas-kit-react-core'; -import SidePanel, {SidePanelProps} from '../index'; import README from '../README.md'; export default { @@ -19,105 +14,92 @@ export default { component: SidePanel, }; -export const LeftSidePanel = () => { - const [collapsed, setCollapsed] = React.useState(false); - - const SidePanelHeader = styled('div')>( - { - display: 'flex', - }, - ({collapsed}) => ({ - alignItems: collapsed ? undefined : 'center', - justifyContent: collapsed ? 'center' : 'space-between', - padding: collapsed - ? `${spacing.s} 0 0 0` - : `${spacing.m} ${spacing.xxs} ${spacing.m} ${spacing.s}`, - left: collapsed ? 0 : undefined, - right: collapsed ? 0 : undefined, - margin: collapsed ? 'auto' : undefined, - }) - ); - +export const Default = () => { const heightOffset = 40; - const buttonStyle = { - transform: `rotate(${collapsed ? 0 : 180}deg)`, - }; - - const headingStyle = { - ...type.h4, - flex: 1, - margin: `0 ${spacing.s}`, - }; - return ( - - - {collapsed ? null : } - {collapsed ? null :

Left Side Panel

} - { - setCollapsed(!collapsed); - }} - /> -
- -
+ { + console.log( + `${ + collapsed ? 'Collapsed state: ' : 'Expanded state: ' + } Side Panel is (internally) ${animation}` + ); + }} + > ); }; export const RightSidePanel = () => { - const [collapsed, setCollapsed] = React.useState(false); + const heightOffset = 40; - const SidePanelHeader = styled('div')>( - { - display: 'flex', - }, - ({collapsed}) => ({ - alignItems: collapsed ? undefined : 'center', - justifyContent: collapsed ? 'center' : 'space-between', - padding: collapsed - ? `${spacing.s} 0 0 0` - : `${spacing.m} ${spacing.s} ${spacing.m} ${spacing.xxs}`, - left: collapsed ? 0 : undefined, - right: collapsed ? 0 : undefined, - margin: collapsed ? 'auto' : undefined, - }) + return ( +
+ { + console.log( + `${ + collapsed ? 'Collapsed state: ' : 'Expanded state: ' + } Side Panel is (internally) ${animation}` + ); + }} + > +
); +}; +export const ControlledSidePanel = () => { + const [collapsed, setCollapsed] = React.useState(false); const heightOffset = 40; - - const buttonStyle = { - transform: `rotate(${collapsed ? 180 : 0}deg)`, - }; - - const headingStyle = { - ...type.h4, - flex: 1, - margin: `0 ${spacing.s}`, - }; - return ( - - - { - setCollapsed(!collapsed); +
+
+ { + console.log( + `${ + collapsed ? 'Collapsed state: ' : 'Expanded state: ' + } Side Panel is (internally) ${animation}` + ); + }} + > +
+
+
- {collapsed ? null :

Right Side Panel

} - - - + > +

Controlled Side Panel

+ +
+
+
); }; diff --git a/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx b/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx index 8ef5c61839..4bcfb52799 100644 --- a/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx +++ b/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx @@ -15,42 +15,50 @@ export default withSnapshotsEnabled({ }); export const SidePanelStates = () => ( - // - // { - // if (props.disabled && !['', 'hover'].includes(props.className)) { - // return false; - // } - // return true; - // } - // )} - // > - // {props => ( - // - // )} - // - // - + + { + // Don't show permutations of both values being defined (collapsed prop always wins over defaultCollapsed) + if (collapsed !== undefined && defaultCollapsed !== undefined) { + return false; + } + // Don't show if both are undefined + if (collapsed === undefined && defaultCollapsed === undefined) { + return false; + } + return true; + } + )} + // columnProps={[ + // { label: 'Controlled Collapsed', props: { collapsed: true } }, + // { label: 'Controlled Expanded', props: { collapsed: false } }, + // { label: 'Uncontrolled Collapsed', props: { defaultCollapsed: true } }, + // { label: 'Uncontrolled Expanded', props: { defaultCollapsed: false } }, + + // ]} + > + {props => } + + ); diff --git a/yarn.lock b/yarn.lock index 236128443d..f932cc794d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1243,7 +1243,7 @@ "@emotion/utils" "0.11.3" babel-plugin-emotion "^10.0.27" -"@emotion/hash@0.8.0": +"@emotion/hash@0.8.0", "@emotion/hash@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== @@ -2508,6 +2508,70 @@ mkdirp "^0.5.1" rimraf "^2.5.2" +"@material-ui/core@^4.9.7": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.11.0.tgz#b69b26e4553c9e53f2bfaf1053e216a0af9be15a" + integrity sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/styles" "^4.10.0" + "@material-ui/system" "^4.9.14" + "@material-ui/types" "^5.1.0" + "@material-ui/utils" "^4.10.2" + "@types/react-transition-group" "^4.2.0" + clsx "^1.0.4" + hoist-non-react-statics "^3.3.2" + popper.js "1.16.1-lts" + prop-types "^15.7.2" + react-is "^16.8.0" + react-transition-group "^4.4.0" + +"@material-ui/styles@^4.10.0": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.10.0.tgz#2406dc23aa358217aa8cc772e6237bd7f0544071" + integrity sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q== + dependencies: + "@babel/runtime" "^7.4.4" + "@emotion/hash" "^0.8.0" + "@material-ui/types" "^5.1.0" + "@material-ui/utils" "^4.9.6" + clsx "^1.0.4" + csstype "^2.5.2" + hoist-non-react-statics "^3.3.2" + jss "^10.0.3" + jss-plugin-camel-case "^10.0.3" + jss-plugin-default-unit "^10.0.3" + jss-plugin-global "^10.0.3" + jss-plugin-nested "^10.0.3" + jss-plugin-props-sort "^10.0.3" + jss-plugin-rule-value-function "^10.0.3" + jss-plugin-vendor-prefixer "^10.0.3" + prop-types "^15.7.2" + +"@material-ui/system@^4.9.14": + version "4.9.14" + resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.9.14.tgz#4b00c48b569340cefb2036d0596b93ac6c587a5f" + integrity sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/utils" "^4.9.6" + csstype "^2.5.2" + prop-types "^15.7.2" + +"@material-ui/types@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2" + integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A== + +"@material-ui/utils@^4.10.2", "@material-ui/utils@^4.9.6": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.10.2.tgz#3fd5470ca61b7341f1e0468ac8f29a70bf6df321" + integrity sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw== + dependencies: + "@babel/runtime" "^7.4.4" + prop-types "^15.7.2" + react-is "^16.8.0" + "@mdx-js/loader@^1.5.1": version "1.5.5" resolved "https://registry.yarnpkg.com/@mdx-js/loader/-/loader-1.5.5.tgz#b658534153b3faab8f93ffc790c868dacc5b43d3" @@ -3571,6 +3635,18 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A== +"@types/lodash.flatten@^4.4.6": + version "4.4.6" + resolved "https://registry.yarnpkg.com/@types/lodash.flatten/-/lodash.flatten-4.4.6.tgz#b74c3267c87e44e603137d4621e8a9396b6551f5" + integrity sha512-omCBl4M8EJSmf2DZqh4/zwjgXQpzC7YO/PXTcG8rI9r7xun8CohrHeNx8HZRkqWc61uJfIaZop9MwJEXPVssHw== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.161" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18" + integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA== + "@types/lodash@4.14.149", "@types/lodash@^4.14.136": version "4.14.149" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" @@ -3693,6 +3769,13 @@ dependencies: "@types/react" "*" +"@types/react-transition-group@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d" + integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^16.9.5": version "16.9.5" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.5.tgz#079dabd918b19b32118c25fd00a786bb6d0d5e51" @@ -6011,6 +6094,11 @@ clone@^2.1.2: resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= +clsx@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" + integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -6645,6 +6733,14 @@ css-url-regex@0.0.1: resolved "https://registry.yarnpkg.com/css-url-regex/-/css-url-regex-0.0.1.tgz#e05af8c6c290d451ef1632b455ea5c81b4b1395c" integrity sha1-4Fr4xsKQ1FHvFjK0VepcgbSxOVw= +css-vendor@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d" + integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ== + dependencies: + "@babel/runtime" "^7.8.3" + is-in-browser "^1.0.2" + css-what@2.1, css-what@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" @@ -6727,6 +6823,16 @@ csstype@^2.2.0, csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== +csstype@^2.5.2: + version "2.6.13" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" + integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== + +csstype@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8" + integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag== + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -7258,6 +7364,14 @@ dom-helpers@^3.4.0: dependencies: "@babel/runtime" "^7.1.2" +dom-helpers@^5.0.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" + integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@0, dom-serializer@^0.1.0, dom-serializer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" @@ -9408,7 +9522,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -9611,6 +9725,11 @@ husky@^0.14.3: normalize-path "^1.0.0" strip-indent "^2.0.0" +hyphenate-style-name@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" + integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== + iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -10153,6 +10272,11 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz#e8a426a69b6d31470d3a33a47bb825cda02506ee" integrity sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA== +is-in-browser@^1.0.2, is-in-browser@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" + integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= + is-installed-globally@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" @@ -11609,6 +11733,76 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jss-plugin-camel-case@^10.0.3: + version "10.4.0" + resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.4.0.tgz#46c75ff7fd61c304984c21af5817823f0f501ceb" + integrity sha512-9oDjsQ/AgdBbMyRjc06Kl3P8lDCSEts2vYZiPZfGAxbGCegqE4RnMob3mDaBby5H9vL9gWmyyImhLRWqIkRUCw== + dependencies: + "@babel/runtime" "^7.3.1" + hyphenate-style-name "^1.0.3" + jss "10.4.0" + +jss-plugin-default-unit@^10.0.3: + version "10.4.0" + resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.4.0.tgz#2b10f01269eaea7f36f0f5fd1cfbfcc76ed42854" + integrity sha512-BYJ+Y3RUYiMEgmlcYMLqwbA49DcSWsGgHpVmEEllTC8MK5iJ7++pT9TnKkKBnNZZxTV75ycyFCR5xeLSOzVm4A== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.4.0" + +jss-plugin-global@^10.0.3: + version "10.4.0" + resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.4.0.tgz#19449425a94e4e74e113139b629fd44d3577f97d" + integrity sha512-b8IHMJUmv29cidt3nI4bUI1+Mo5RZE37kqthaFpmxf5K7r2aAegGliAw4hXvA70ca6ckAoXMUl4SN/zxiRcRag== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.4.0" + +jss-plugin-nested@^10.0.3: + version "10.4.0" + resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.4.0.tgz#017d0c02c0b6b454fd9d7d3fc33470a15eea9fd1" + integrity sha512-cKgpeHIxAP0ygeWh+drpLbrxFiak6zzJ2toVRi/NmHbpkNaLjTLgePmOz5+67ln3qzJiPdXXJB1tbOyYKAP4Pw== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.4.0" + tiny-warning "^1.0.2" + +jss-plugin-props-sort@^10.0.3: + version "10.4.0" + resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.4.0.tgz#7110bf0b6049cc2080b220b506532bf0b70c0e07" + integrity sha512-j/t0R40/2fp+Nzt6GgHeUFnHVY2kPGF5drUVlgkcwYoHCgtBDOhTTsOfdaQFW6sHWfoQYgnGV4CXdjlPiRrzwA== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.4.0" + +jss-plugin-rule-value-function@^10.0.3: + version "10.4.0" + resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.4.0.tgz#7cff4a91e84973536fa49b6ebbdbf7f339b01c82" + integrity sha512-w8504Cdfu66+0SJoLkr6GUQlEb8keHg8ymtJXdVHWh0YvFxDG2l/nS93SI5Gfx0fV29dO6yUugXnKzDFJxrdFQ== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.4.0" + tiny-warning "^1.0.2" + +jss-plugin-vendor-prefixer@^10.0.3: + version "10.4.0" + resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.4.0.tgz#2a78f3c5d57d1e024fe7ad7c41de34d04e72ecc0" + integrity sha512-DpF+/a+GU8hMh/948sBGnKSNfKkoHg2p9aRFUmyoyxgKjOeH9n74Ht3Yt8lOgdZsuWNJbPrvaa3U4PXKwxVpTQ== + dependencies: + "@babel/runtime" "^7.3.1" + css-vendor "^2.0.8" + jss "10.4.0" + +jss@10.4.0, jss@^10.0.3: + version "10.4.0" + resolved "https://registry.yarnpkg.com/jss/-/jss-10.4.0.tgz#473a6fbe42e85441020a07e9519dac1e8a2e79ca" + integrity sha512-l7EwdwhsDishXzqTc3lbsbyZ83tlUl5L/Hb16pHCvZliA9lRDdNBZmHzeJHP0sxqD0t1mrMmMR8XroR12JBYzw== + dependencies: + "@babel/runtime" "^7.3.1" + csstype "^3.0.2" + is-in-browser "^1.1.3" + tiny-warning "^1.0.2" + jstransformer@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3" @@ -11938,6 +12132,11 @@ lodash.escape@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg= +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" @@ -12033,7 +12232,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.0.0, lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.10, lodash@4.17.15: +lodash@4.17.15, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.10: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -13968,6 +14167,11 @@ polished@^3.3.1: dependencies: "@babel/runtime" "^7.4.5" +popper.js@1.16.1-lts: + version "1.16.1-lts" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05" + integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA== + popper.js@^1.14.4, popper.js@^1.14.7: version "1.16.1" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" @@ -14826,7 +15030,7 @@ react-inspector@^4.0.0: is-dom "^1.0.9" prop-types "^15.6.1" -react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: +react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -14919,6 +15123,16 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-transition-group@^4.4.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" + integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@16.12.0, react@^16.8.3: version "16.12.0" resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83" @@ -17147,6 +17361,11 @@ tiny-emitter@^2.0.0: resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + tinycolor2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" From ae32fbd5c337df93e1f91136f8e2676fb8fa524d Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Fri, 25 Sep 2020 22:21:50 -0700 Subject: [PATCH 04/49] chore(side-panel): Add notes --- modules/_labs/side-panel/react/lib/SidePanel.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index 67738dcfdf..92e655ad47 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -100,7 +100,6 @@ const SidePanel = ({ height = 400, onAnimationEnd, onAnimationStart, - // eslint-disable-next-line no-empty-function onCollapsedChange, origin = 'left', variant = 'standard', @@ -122,6 +121,8 @@ const SidePanel = ({ mounted.current = true; }, []); + // TOTHINK: Do we change up the state depending on the whether it's controlled or not? + // TOTHINK: Do we separate this into two callbacks? // Call our onCollapsedChange cb when either collapsed prop or internalState changes React.useEffect(() => { if (typeof onCollapsedChange !== 'undefined') { @@ -186,6 +187,7 @@ const SidePanel = ({ height: height, minHeight: height, maxHeight: height, + // mounted.current will be false on the first render, thus you won't get an unwanted animation here animation: mounted.current ? `${collapsed ? motion.expand : motion.collapse} 200ms ease-out` : undefined, From 6a19f2790de77caa768a4a68eec30602e749ef57 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Fri, 25 Sep 2020 22:52:11 -0700 Subject: [PATCH 05/49] feat(side-panel): Return isControlled boolean --- modules/_labs/side-panel/react/lib/SidePanel.tsx | 4 ++-- modules/_labs/side-panel/react/lib/hooks.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index 92e655ad47..cc786014bc 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -108,7 +108,7 @@ const SidePanel = ({ }: SidePanelProps) => { const sidePanelId = useUniqueId(id); const mounted = React.useRef(false); - const [collapsed, setCollapsed] = useControllableState( + const [collapsed, setCollapsed, isControlled] = useControllableState( collapsedProp, defaultCollapsed ); @@ -174,7 +174,7 @@ const SidePanel = ({ // TODO: if we're in controlled mode should we ship a hook that spreads aria-controls and aria-expanded return (
( value: T, defaultValue: T | (() => T) -): [T, (value: T) => void] => { +): [T, (value: T) => void, boolean] => { const [valueState, setValueState] = React.useState(defaultValue); const isControlled = value !== undefined; @@ -19,5 +19,5 @@ export const useControllableState = ( } }; - return [effectiveValue, updateValue]; + return [effectiveValue, updateValue, isControlled]; }; From 9690f432216cedb2cb2db7bc09b4a60b9cdfb1ab Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Fri, 25 Sep 2020 22:53:03 -0700 Subject: [PATCH 06/49] fix(side-panel): Change default height --- modules/_labs/side-panel/react/lib/SidePanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index cc786014bc..9df463056a 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -97,7 +97,7 @@ const SidePanel = ({ collapsed: collapsedProp, collapsedWidth = 64, defaultCollapsed = false, - height = 400, + height = '100%', onAnimationEnd, onAnimationStart, onCollapsedChange, From b133b328e096b3872e319360b172a34715857aa4 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Fri, 25 Sep 2020 22:53:48 -0700 Subject: [PATCH 07/49] test(side-panel): Clean up visual tests --- .../react/stories/stories_VisualTesting.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx b/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx index 4bcfb52799..df862dba3f 100644 --- a/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx +++ b/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx @@ -50,15 +50,12 @@ export const SidePanelStates = () => ( return true; } )} - // columnProps={[ - // { label: 'Controlled Collapsed', props: { collapsed: true } }, - // { label: 'Controlled Expanded', props: { collapsed: false } }, - // { label: 'Uncontrolled Collapsed', props: { defaultCollapsed: true } }, - // { label: 'Uncontrolled Expanded', props: { defaultCollapsed: false } }, - - // ]} > - {props => } + {props => ( +
+ +
+ )} ); From 2b1c98ac621492165fef41d5f68252d2485d5216 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Fri, 25 Sep 2020 23:03:03 -0700 Subject: [PATCH 08/49] perf(common): Optimize useUniqueId hook --- modules/common/react/lib/utils/useUniqueId.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/common/react/lib/utils/useUniqueId.ts b/modules/common/react/lib/utils/useUniqueId.ts index 3f577379de..4c09788fed 100644 --- a/modules/common/react/lib/utils/useUniqueId.ts +++ b/modules/common/react/lib/utils/useUniqueId.ts @@ -7,8 +7,14 @@ import uuid from 'uuid/v4'; */ export const useUniqueId = (id?: string) => { // https://codesandbox.io/s/react-functional-component-ids-p2ndq - const [generatedId] = React.useState(() => uuid().replace(/^[0-9]*/gi, '')); - return id || generatedId; + const [effectiveId] = React.useState(() => { + if (typeof id !== 'undefined') { + return id; + } + return uuid().replace(/^[0-9]*/gi, ''); + }); + + return effectiveId; }; /** From cf84d3d2ce25db39667c798f5c72daa1af7d940a Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Fri, 25 Sep 2020 23:11:00 -0700 Subject: [PATCH 09/49] chore(side-panel): Add some notes --- modules/_labs/side-panel/react/lib/SidePanel.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index 9df463056a..8bb642dde9 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -106,7 +106,6 @@ const SidePanel = ({ width = 320, ...elemProps }: SidePanelProps) => { - const sidePanelId = useUniqueId(id); const mounted = React.useRef(false); const [collapsed, setCollapsed, isControlled] = useControllableState( collapsedProp, @@ -115,6 +114,9 @@ const SidePanel = ({ const [internalState, setInternalState] = React.useState( collapsed ? 'collapsed' : 'expanded' ); + // Optimization would be to only call uuid in a hook if this is an uncontrolled component + const sidePanelId = useUniqueId(id); + // This is meant to prevent animations when the component renders for the first time. // mounted.current will only be false on the first pass React.useEffect(() => { From 3f4222b1469ea18a0e2d5404c64434b54ef713e8 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Sat, 26 Sep 2020 09:12:46 -0700 Subject: [PATCH 10/49] fix(side-panel): Make event handlers more specific --- modules/_labs/side-panel/react/lib/SidePanel.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index 8bb642dde9..a034c9dd01 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -143,7 +143,12 @@ const SidePanel = ({ const handleAnimationEnd = (event: React.AnimationEvent) => { if (event.currentTarget === event.target) { - setInternalState(collapsed ? 'collapsed' : 'expanded'); + if ( + event.animationName === motion.collapse.name || + event.animationName === motion.expand.name + ) { + setInternalState(collapsed ? 'collapsed' : 'expanded'); + } } if (typeof onAnimationEnd !== 'undefined') { @@ -153,7 +158,12 @@ const SidePanel = ({ const handleAnimationStart = (event: React.AnimationEvent) => { if (event.currentTarget === event.target) { - setInternalState(collapsed ? 'collapsing' : 'expanding'); + if ( + event.animationName === motion.collapse.name || + event.animationName === motion.expand.name + ) { + setInternalState(collapsed ? 'collapsing' : 'expanding'); + } } if (typeof onAnimationStart !== 'undefined') { From d5ca0f08455e775b257c5e2b315885d0549d6a7c Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Sat, 26 Sep 2020 11:43:10 -0700 Subject: [PATCH 11/49] docs(side-panel): Correct jsdoc default values --- modules/_labs/side-panel/react/lib/SidePanel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index a034c9dd01..f6e4a93c94 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -27,7 +27,7 @@ export interface SidePanelProps extends React.HTMLAttributes { /** * The height of the component (in `px` if it's a `number`). * - * @default 400 + * @default '100%' */ height?: number | string; /** @@ -57,7 +57,7 @@ export interface SidePanelProps extends React.HTMLAttributes { /** * Style variants of the side panel * - * @default 'grey' + * @default 'standard' */ variant?: SidePanelVariant; } From 9d3881aaca03747750721537ce30ba7cc1d442a0 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Sat, 26 Sep 2020 11:45:23 -0700 Subject: [PATCH 12/49] feat(side-panel): Separate state callbacks Better to call cb for internal state and collapsed state separately? --- .../_labs/side-panel/react/lib/SidePanel.tsx | 27 +++++++++++---- .../side-panel/react/stories/stories.tsx | 33 +++++++++---------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index f6e4a93c94..32ea908c97 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -14,6 +14,8 @@ export type SidePanelInternalStates = 'collapsed' | 'collapsing' | 'expanded' | export interface SidePanelProps extends React.HTMLAttributes { /** * Specifies the _controlled_ state the side panel is in. Leave undefined if you want to keep this an _uncontrolled_ component. + * Changing this prop will fire an animation that resolves to this state. For example, if collapsed is changed from `true` to `false`, + * an expand animation will fire and the side panel will eventually be expanded when the animation is finished. * * @default false */ @@ -49,11 +51,18 @@ export interface SidePanelProps extends React.HTMLAttributes { */ origin?: 'left' | 'right'; /** - * Fired when the side panel state changes + * Fired when the side panel's `collapsed` state changes. States like 'collapsing' and 'expanding' are tracked in another callback: `onStateChange` * * @param boolean */ - onCollapsedChange?: (collapsed?: boolean, animationState?: SidePanelInternalStates) => void; + onCollapsedChange?: (collapsed?: boolean) => void; + /** + * Fired when the side panel is transitioning between internal states. Use this to track when the side panel is animating between 'collapsed', 'collapsing', 'expanded', and 'expanding' states. + * This can be particularly helpful if child components need to react specifically to these states. + * + * @param SidePanelInternalStates + */ + onStateChange?: (state?: SidePanelInternalStates) => void; /** * Style variants of the side panel * @@ -101,6 +110,7 @@ const SidePanel = ({ onAnimationEnd, onAnimationStart, onCollapsedChange, + onStateChange, origin = 'left', variant = 'standard', width = 320, @@ -123,14 +133,17 @@ const SidePanel = ({ mounted.current = true; }, []); - // TOTHINK: Do we change up the state depending on the whether it's controlled or not? - // TOTHINK: Do we separate this into two callbacks? - // Call our onCollapsedChange cb when either collapsed prop or internalState changes React.useEffect(() => { if (typeof onCollapsedChange !== 'undefined') { - onCollapsedChange(collapsed, internalState); + onCollapsedChange(collapsed); + } + }, [collapsed, onCollapsedChange]); + + React.useEffect(() => { + if (typeof onStateChange !== 'undefined') { + onStateChange(internalState); } - }, [collapsed, internalState, onCollapsedChange]); + }, [internalState, onStateChange]); const motion = { collapse: createKeyframes(collapsedWidth, width), diff --git a/modules/_labs/side-panel/react/stories/stories.tsx b/modules/_labs/side-panel/react/stories/stories.tsx index 710e35bd7a..b99a39378e 100644 --- a/modules/_labs/side-panel/react/stories/stories.tsx +++ b/modules/_labs/side-panel/react/stories/stories.tsx @@ -21,12 +21,11 @@ export const Default = () => { { - console.log( - `${ - collapsed ? 'Collapsed state: ' : 'Expanded state: ' - } Side Panel is (internally) ${animation}` - ); + onCollapsedChange={collapsed => { + console.log(`collapsed state is: ${collapsed ? 'true' : 'false'}`); + }} + onStateChange={internalState => { + console.log(`Side Panel is ${internalState}`); }} > ); @@ -46,12 +45,11 @@ export const RightSidePanel = () => { defaultCollapsed origin="right" height={`calc(100vh - ${heightOffset * 2}px)`} - onCollapsedChange={(collapsed, animation) => { - console.log( - `${ - collapsed ? 'Collapsed state: ' : 'Expanded state: ' - } Side Panel is (internally) ${animation}` - ); + onCollapsedChange={collapsed => { + console.log(`collapsed state is: ${collapsed ? 'true' : 'false'}`); + }} + onStateChange={internalState => { + console.log(`Side Panel is ${internalState}`); }} >
@@ -68,12 +66,11 @@ export const ControlledSidePanel = () => { variant={'alternate'} collapsed={collapsed} height={`calc(100vh - ${heightOffset * 2}px)`} - onCollapsedChange={(collapsed, animation) => { - console.log( - `${ - collapsed ? 'Collapsed state: ' : 'Expanded state: ' - } Side Panel is (internally) ${animation}` - ); + onCollapsedChange={collapsed => { + console.log(`collapsed state is: ${collapsed ? 'true' : 'false'}`); + }} + onStateChange={internalState => { + console.log(`Side Panel is ${internalState}`); }} >
From ac4863f6eb1ada49c46f91b7657ed883117c1724 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Mon, 28 Sep 2020 08:10:57 -0700 Subject: [PATCH 13/49] test(side-panel): Init cypress tests --- cypress/integration/SidePanelLabs.spec.ts | 16 ++++++++++++++++ .../_labs/side-panel/react/stories/stories.tsx | 1 - 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 cypress/integration/SidePanelLabs.spec.ts diff --git a/cypress/integration/SidePanelLabs.spec.ts b/cypress/integration/SidePanelLabs.spec.ts new file mode 100644 index 0000000000..7c3966c8ea --- /dev/null +++ b/cypress/integration/SidePanelLabs.spec.ts @@ -0,0 +1,16 @@ +import * as h from '../helpers'; + +describe('Side Panel', () => { + before(() => { + h.stories.visit(); + }); + context(`given the 'Default' story is rendered`, () => { + beforeEach(() => { + h.stories.load('Labs|Side Panel/React', 'Default'); + }); + + it('should not have any axe errors', () => { + cy.checkA11y(); + }); + }); +}); diff --git a/modules/_labs/side-panel/react/stories/stories.tsx b/modules/_labs/side-panel/react/stories/stories.tsx index b99a39378e..d50ac8c0d3 100644 --- a/modules/_labs/side-panel/react/stories/stories.tsx +++ b/modules/_labs/side-panel/react/stories/stories.tsx @@ -19,7 +19,6 @@ export const Default = () => { return ( { console.log(`collapsed state is: ${collapsed ? 'true' : 'false'}`); From 13b746ea9d266e254038caf2b0eef5820292c5f9 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Mon, 28 Sep 2020 08:21:22 -0700 Subject: [PATCH 14/49] chore(side-panel): Add a note for potential bug This component assumes no "force re-rendering" happens. --- modules/_labs/side-panel/react/lib/SidePanel.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index 32ea908c97..ba39c69ce1 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -213,6 +213,7 @@ const SidePanel = ({ minHeight: height, maxHeight: height, // mounted.current will be false on the first render, thus you won't get an unwanted animation here + // Will animate again if you force a re-render (like in Storybook) animation: mounted.current ? `${collapsed ? motion.expand : motion.collapse} 200ms ease-out` : undefined, From 0ad05da57171a2bbeb7a7e153e55835116f830ca Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Mon, 28 Sep 2020 09:13:21 -0700 Subject: [PATCH 15/49] chore(side-panel): Add missing deps --- modules/_labs/side-panel/react/package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/_labs/side-panel/react/package.json b/modules/_labs/side-panel/react/package.json index 7dd3103f3e..86cde72472 100644 --- a/modules/_labs/side-panel/react/package.json +++ b/modules/_labs/side-panel/react/package.json @@ -46,6 +46,16 @@ "workday", "side-panel" ], + "dependencies": { + "@emotion/core": "^10.0.28", + "@workday/canvas-kit-react-button": "^4.2.0", + "@workday/canvas-kit-react-common": "^4.2.0", + "@workday/canvas-kit-react-core": "^4.2.0", + "@workday/canvas-system-icons-web": "^1.0.20" + }, + "devDependencies": { + "@workday/canvas-kit-labs-react-core": "^4.2.0" + }, "peerDependencies": { "react": ">= 16.8 < 17" } From 67efbeb4f4c118a26ac2b8fa014565f95c5dcf35 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Tue, 29 Sep 2020 12:20:07 -0700 Subject: [PATCH 16/49] chore: Revert inadvertant yarn.lock changes --- yarn.lock | 225 +----------------------------------------------------- 1 file changed, 3 insertions(+), 222 deletions(-) diff --git a/yarn.lock b/yarn.lock index 14579c2d0f..a3c1ff796c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1243,7 +1243,7 @@ "@emotion/utils" "0.11.3" babel-plugin-emotion "^10.0.27" -"@emotion/hash@0.8.0", "@emotion/hash@^0.8.0": +"@emotion/hash@0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== @@ -2508,70 +2508,6 @@ mkdirp "^0.5.1" rimraf "^2.5.2" -"@material-ui/core@^4.9.7": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.11.0.tgz#b69b26e4553c9e53f2bfaf1053e216a0af9be15a" - integrity sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g== - dependencies: - "@babel/runtime" "^7.4.4" - "@material-ui/styles" "^4.10.0" - "@material-ui/system" "^4.9.14" - "@material-ui/types" "^5.1.0" - "@material-ui/utils" "^4.10.2" - "@types/react-transition-group" "^4.2.0" - clsx "^1.0.4" - hoist-non-react-statics "^3.3.2" - popper.js "1.16.1-lts" - prop-types "^15.7.2" - react-is "^16.8.0" - react-transition-group "^4.4.0" - -"@material-ui/styles@^4.10.0": - version "4.10.0" - resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.10.0.tgz#2406dc23aa358217aa8cc772e6237bd7f0544071" - integrity sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q== - dependencies: - "@babel/runtime" "^7.4.4" - "@emotion/hash" "^0.8.0" - "@material-ui/types" "^5.1.0" - "@material-ui/utils" "^4.9.6" - clsx "^1.0.4" - csstype "^2.5.2" - hoist-non-react-statics "^3.3.2" - jss "^10.0.3" - jss-plugin-camel-case "^10.0.3" - jss-plugin-default-unit "^10.0.3" - jss-plugin-global "^10.0.3" - jss-plugin-nested "^10.0.3" - jss-plugin-props-sort "^10.0.3" - jss-plugin-rule-value-function "^10.0.3" - jss-plugin-vendor-prefixer "^10.0.3" - prop-types "^15.7.2" - -"@material-ui/system@^4.9.14": - version "4.9.14" - resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.9.14.tgz#4b00c48b569340cefb2036d0596b93ac6c587a5f" - integrity sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w== - dependencies: - "@babel/runtime" "^7.4.4" - "@material-ui/utils" "^4.9.6" - csstype "^2.5.2" - prop-types "^15.7.2" - -"@material-ui/types@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2" - integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A== - -"@material-ui/utils@^4.10.2", "@material-ui/utils@^4.9.6": - version "4.10.2" - resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.10.2.tgz#3fd5470ca61b7341f1e0468ac8f29a70bf6df321" - integrity sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw== - dependencies: - "@babel/runtime" "^7.4.4" - prop-types "^15.7.2" - react-is "^16.8.0" - "@mdx-js/loader@^1.5.1": version "1.5.5" resolved "https://registry.yarnpkg.com/@mdx-js/loader/-/loader-1.5.5.tgz#b658534153b3faab8f93ffc790c868dacc5b43d3" @@ -3635,18 +3571,6 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A== -"@types/lodash.flatten@^4.4.6": - version "4.4.6" - resolved "https://registry.yarnpkg.com/@types/lodash.flatten/-/lodash.flatten-4.4.6.tgz#b74c3267c87e44e603137d4621e8a9396b6551f5" - integrity sha512-omCBl4M8EJSmf2DZqh4/zwjgXQpzC7YO/PXTcG8rI9r7xun8CohrHeNx8HZRkqWc61uJfIaZop9MwJEXPVssHw== - dependencies: - "@types/lodash" "*" - -"@types/lodash@*": - version "4.14.161" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18" - integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA== - "@types/lodash@4.14.149", "@types/lodash@^4.14.136": version "4.14.149" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" @@ -3769,13 +3693,6 @@ dependencies: "@types/react" "*" -"@types/react-transition-group@^4.2.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d" - integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w== - dependencies: - "@types/react" "*" - "@types/react@*", "@types/react@^16.9.5": version "16.9.5" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.5.tgz#079dabd918b19b32118c25fd00a786bb6d0d5e51" @@ -6094,11 +6011,6 @@ clone@^2.1.2: resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= -clsx@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" - integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -6733,14 +6645,6 @@ css-url-regex@0.0.1: resolved "https://registry.yarnpkg.com/css-url-regex/-/css-url-regex-0.0.1.tgz#e05af8c6c290d451ef1632b455ea5c81b4b1395c" integrity sha1-4Fr4xsKQ1FHvFjK0VepcgbSxOVw= -css-vendor@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d" - integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ== - dependencies: - "@babel/runtime" "^7.8.3" - is-in-browser "^1.0.2" - css-what@2.1, css-what@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" @@ -6823,16 +6727,6 @@ csstype@^2.2.0, csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== -csstype@^2.5.2: - version "2.6.13" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" - integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== - -csstype@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8" - integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag== - currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -7364,14 +7258,6 @@ dom-helpers@^3.4.0: dependencies: "@babel/runtime" "^7.1.2" -dom-helpers@^5.0.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" - integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - dom-serializer@0, dom-serializer@^0.1.0, dom-serializer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" @@ -9522,7 +9408,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -9725,11 +9611,6 @@ husky@^0.14.3: normalize-path "^1.0.0" strip-indent "^2.0.0" -hyphenate-style-name@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" - integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== - iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -10272,11 +10153,6 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz#e8a426a69b6d31470d3a33a47bb825cda02506ee" integrity sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA== -is-in-browser@^1.0.2, is-in-browser@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" - integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= - is-installed-globally@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" @@ -11733,76 +11609,6 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jss-plugin-camel-case@^10.0.3: - version "10.4.0" - resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.4.0.tgz#46c75ff7fd61c304984c21af5817823f0f501ceb" - integrity sha512-9oDjsQ/AgdBbMyRjc06Kl3P8lDCSEts2vYZiPZfGAxbGCegqE4RnMob3mDaBby5H9vL9gWmyyImhLRWqIkRUCw== - dependencies: - "@babel/runtime" "^7.3.1" - hyphenate-style-name "^1.0.3" - jss "10.4.0" - -jss-plugin-default-unit@^10.0.3: - version "10.4.0" - resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.4.0.tgz#2b10f01269eaea7f36f0f5fd1cfbfcc76ed42854" - integrity sha512-BYJ+Y3RUYiMEgmlcYMLqwbA49DcSWsGgHpVmEEllTC8MK5iJ7++pT9TnKkKBnNZZxTV75ycyFCR5xeLSOzVm4A== - dependencies: - "@babel/runtime" "^7.3.1" - jss "10.4.0" - -jss-plugin-global@^10.0.3: - version "10.4.0" - resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.4.0.tgz#19449425a94e4e74e113139b629fd44d3577f97d" - integrity sha512-b8IHMJUmv29cidt3nI4bUI1+Mo5RZE37kqthaFpmxf5K7r2aAegGliAw4hXvA70ca6ckAoXMUl4SN/zxiRcRag== - dependencies: - "@babel/runtime" "^7.3.1" - jss "10.4.0" - -jss-plugin-nested@^10.0.3: - version "10.4.0" - resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.4.0.tgz#017d0c02c0b6b454fd9d7d3fc33470a15eea9fd1" - integrity sha512-cKgpeHIxAP0ygeWh+drpLbrxFiak6zzJ2toVRi/NmHbpkNaLjTLgePmOz5+67ln3qzJiPdXXJB1tbOyYKAP4Pw== - dependencies: - "@babel/runtime" "^7.3.1" - jss "10.4.0" - tiny-warning "^1.0.2" - -jss-plugin-props-sort@^10.0.3: - version "10.4.0" - resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.4.0.tgz#7110bf0b6049cc2080b220b506532bf0b70c0e07" - integrity sha512-j/t0R40/2fp+Nzt6GgHeUFnHVY2kPGF5drUVlgkcwYoHCgtBDOhTTsOfdaQFW6sHWfoQYgnGV4CXdjlPiRrzwA== - dependencies: - "@babel/runtime" "^7.3.1" - jss "10.4.0" - -jss-plugin-rule-value-function@^10.0.3: - version "10.4.0" - resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.4.0.tgz#7cff4a91e84973536fa49b6ebbdbf7f339b01c82" - integrity sha512-w8504Cdfu66+0SJoLkr6GUQlEb8keHg8ymtJXdVHWh0YvFxDG2l/nS93SI5Gfx0fV29dO6yUugXnKzDFJxrdFQ== - dependencies: - "@babel/runtime" "^7.3.1" - jss "10.4.0" - tiny-warning "^1.0.2" - -jss-plugin-vendor-prefixer@^10.0.3: - version "10.4.0" - resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.4.0.tgz#2a78f3c5d57d1e024fe7ad7c41de34d04e72ecc0" - integrity sha512-DpF+/a+GU8hMh/948sBGnKSNfKkoHg2p9aRFUmyoyxgKjOeH9n74Ht3Yt8lOgdZsuWNJbPrvaa3U4PXKwxVpTQ== - dependencies: - "@babel/runtime" "^7.3.1" - css-vendor "^2.0.8" - jss "10.4.0" - -jss@10.4.0, jss@^10.0.3: - version "10.4.0" - resolved "https://registry.yarnpkg.com/jss/-/jss-10.4.0.tgz#473a6fbe42e85441020a07e9519dac1e8a2e79ca" - integrity sha512-l7EwdwhsDishXzqTc3lbsbyZ83tlUl5L/Hb16pHCvZliA9lRDdNBZmHzeJHP0sxqD0t1mrMmMR8XroR12JBYzw== - dependencies: - "@babel/runtime" "^7.3.1" - csstype "^3.0.2" - is-in-browser "^1.1.3" - tiny-warning "^1.0.2" - jstransformer@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3" @@ -12132,11 +11938,6 @@ lodash.escape@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg= -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= - lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" @@ -14167,11 +13968,6 @@ polished@^3.3.1: dependencies: "@babel/runtime" "^7.4.5" -popper.js@1.16.1-lts: - version "1.16.1-lts" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05" - integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA== - popper.js@^1.14.4, popper.js@^1.14.7: version "1.16.1" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" @@ -15030,7 +14826,7 @@ react-inspector@^4.0.0: is-dom "^1.0.9" prop-types "^15.6.1" -react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: +react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -15123,16 +14919,6 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -react-transition-group@^4.4.0: - version "4.4.1" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" - integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== - dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" - react@16.12.0, react@^16.8.3: version "16.12.0" resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83" @@ -17361,11 +17147,6 @@ tiny-emitter@^2.0.0: resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== -tiny-warning@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== - tinycolor2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" From fc2d4b7fa3853f8dfc9abc9538351bcebeac2e89 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Wed, 30 Sep 2020 14:23:55 -0700 Subject: [PATCH 17/49] revert: Rollback useUniqueId changes --- modules/common/react/lib/utils/useUniqueId.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/modules/common/react/lib/utils/useUniqueId.ts b/modules/common/react/lib/utils/useUniqueId.ts index 4c09788fed..3f577379de 100644 --- a/modules/common/react/lib/utils/useUniqueId.ts +++ b/modules/common/react/lib/utils/useUniqueId.ts @@ -7,14 +7,8 @@ import uuid from 'uuid/v4'; */ export const useUniqueId = (id?: string) => { // https://codesandbox.io/s/react-functional-component-ids-p2ndq - const [effectiveId] = React.useState(() => { - if (typeof id !== 'undefined') { - return id; - } - return uuid().replace(/^[0-9]*/gi, ''); - }); - - return effectiveId; + const [generatedId] = React.useState(() => uuid().replace(/^[0-9]*/gi, '')); + return id || generatedId; }; /** From 5ad218a9750e61b19fb6d630b6cb0d0d657e711c Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Fri, 2 Oct 2020 16:07:40 -0700 Subject: [PATCH 18/49] feat(side-panel): Add aria-label attr --- modules/_labs/side-panel/react/lib/SidePanel.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index ba39c69ce1..3a9cb9dad6 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -12,6 +12,11 @@ export type SidePanelVariant = 'standard' | 'alternate'; export type SidePanelInternalStates = 'collapsed' | 'collapsing' | 'expanded' | 'expanding'; export interface SidePanelProps extends React.HTMLAttributes { + /** + * The `aria-label` attribute for the side panel and button. This is required for accessibility. + * + */ + 'aria-label': string; /** * Specifies the _controlled_ state the side panel is in. Leave undefined if you want to keep this an _uncontrolled_ component. * Changing this prop will fire an animation that resolves to this state. For example, if collapsed is changed from `true` to `false`, @@ -101,12 +106,13 @@ const containerVariantStyle: {[K in SidePanelVariant]: CSSObject} = { }; const SidePanel = ({ - id, + 'aria-label': ariaLabel = 'navigation pane', children, collapsed: collapsedProp, collapsedWidth = 64, defaultCollapsed = false, height = '100%', + id, onAnimationEnd, onAnimationStart, onCollapsedChange, @@ -196,9 +202,9 @@ const SidePanel = ({ : `scaleX(${origin === 'left' ? '-1' : '1'})`, }); - // TODO: if we're in controlled mode should we ship a hook that spreads aria-controls and aria-expanded return (
Date: Thu, 8 Oct 2020 07:37:05 -0700 Subject: [PATCH 19/49] feat(side-panel): Refactor using hooks Also changed prop name from collapsed to expanded to more closely align with aria-expanded. --- modules/_labs/side-panel/react/index.ts | 3 +- .../_labs/side-panel/react/lib/SidePanel.tsx | 182 +++++++++--------- modules/_labs/side-panel/react/lib/hooks.ts | 61 ++++-- .../side-panel/react/stories/stories.tsx | 117 ++++++++--- .../react/stories/stories_VisualTesting.tsx | 4 +- 5 files changed, 226 insertions(+), 141 deletions(-) diff --git a/modules/_labs/side-panel/react/index.ts b/modules/_labs/side-panel/react/index.ts index 3ccf10db9f..8a64323994 100644 --- a/modules/_labs/side-panel/react/index.ts +++ b/modules/_labs/side-panel/react/index.ts @@ -1,5 +1,6 @@ import SidePanel from './lib/SidePanel'; +import {useSidePanel} from './lib/hooks'; export default SidePanel; -export {SidePanel}; +export {SidePanel, useSidePanel}; export * from './lib/SidePanel'; diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index 3a9cb9dad6..4566e12d7f 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -1,75 +1,69 @@ /** @jsx jsx */ import * as React from 'react'; import {css, jsx, keyframes, CSSObject} from '@emotion/core'; -import {useUniqueId} from '@workday/canvas-kit-react-common'; -import {IconButton} from '@workday/canvas-kit-react-button'; +import {IconButton, IconButtonProps} from '@workday/canvas-kit-react-button'; import {spacing, colors, depth} from '@workday/canvas-kit-react-core'; +import {accessibleHide} from '@workday/canvas-kit-react-common'; import {transformationImportIcon} from '@workday/canvas-system-icons-web'; -import {useControllableState} from './hooks'; - export type SidePanelVariant = 'standard' | 'alternate'; -export type SidePanelInternalStates = 'collapsed' | 'collapsing' | 'expanded' | 'expanding'; +export type SidePanelTransitionStates = 'collapsed' | 'collapsing' | 'expanded' | 'expanding'; export interface SidePanelProps extends React.HTMLAttributes { /** * The `aria-label` attribute for the side panel and button. This is required for accessibility. * */ - 'aria-label': string; + 'aria-label'?: string; /** - * Specifies the _controlled_ state the side panel is in. Leave undefined if you want to keep this an _uncontrolled_ component. - * Changing this prop will fire an animation that resolves to this state. For example, if collapsed is changed from `true` to `false`, - * an expand animation will fire and the side panel will eventually be expanded when the animation is finished. - * - * @default false - */ - collapsed?: boolean; - /** - * Specifies default state (collapsed or expanded) if the component is uncontrolled. + * The width of the component (in `px` if it's a `number`) when it is collapsed. * - * @default false + * @default 64 */ - defaultCollapsed?: boolean; + collapsedWidth?: number | string; /** - * The height of the component (in `px` if it's a `number`). - * - * @default '100%' + * If true, sets the expanded state of the side panel + * @default true */ - height?: number | string; + expanded?: boolean; /** * The width of the component (in `px` if it's a `number`) when it is expanded. * * @default 320 */ - width?: number | string; + expandedWidth?: number | string; /** - * The width of the component (in `px` if it's a `number`) when it is collapsed. + * The height of the component (in `px` if it's a `number`). * - * @default 64 + * @default '100%' */ - collapsedWidth?: number | string; + height?: number | string; /** - * Specifies which side the side panel is meant to originate from. + * Which side the side panel is meant to originate from. * * @default 'left' */ origin?: 'left' | 'right'; /** - * Fired when the side panel's `collapsed` state changes. States like 'collapsing' and 'expanding' are tracked in another callback: `onStateChange` + * InternalState + */ + state?: SidePanelTransitionStates; + /** + * The function called when the side panel's `expanded` state changes. States like 'collapsing' and 'expanding' are tracked in another callback: `onStateChange` * * @param boolean */ - onCollapsedChange?: (collapsed?: boolean) => void; + onExpandedChange?: (expanded?: boolean) => void; /** - * Fired when the side panel is transitioning between internal states. Use this to track when the side panel is animating between 'collapsed', 'collapsing', 'expanded', and 'expanding' states. + * The function called when the side panel is transitioning between states. + * Use this to track when the side panel is animating between 'collapsed', 'collapsing', 'expanded', and 'expanding' states. * This can be particularly helpful if child components need to react specifically to these states. * - * @param SidePanelInternalStates + * @param SidePanelTransitionStates */ - onStateChange?: (state?: SidePanelInternalStates) => void; + onStateTransition?: (state?: SidePanelTransitionStates) => void; /** - * Style variants of the side panel + * The style variant of the side panel * * @default 'standard' */ @@ -105,33 +99,30 @@ const containerVariantStyle: {[K in SidePanelVariant]: CSSObject} = { }, }; +export const SidePanelContext = React.createContext({ + state: 'expanded', + origin: 'left', +}); + const SidePanel = ({ 'aria-label': ariaLabel = 'navigation pane', children, - collapsed: collapsedProp, collapsedWidth = 64, - defaultCollapsed = false, + expanded = true, + expandedWidth = 320, height = '100%', - id, onAnimationEnd, onAnimationStart, - onCollapsedChange, - onStateChange, + onExpandedChange, + onStateTransition, origin = 'left', variant = 'standard', - width = 320, ...elemProps }: SidePanelProps) => { const mounted = React.useRef(false); - const [collapsed, setCollapsed, isControlled] = useControllableState( - collapsedProp, - defaultCollapsed + const [state, setState] = React.useState( + expanded ? 'expanded' : 'collapsed' ); - const [internalState, setInternalState] = React.useState( - collapsed ? 'collapsed' : 'expanded' - ); - // Optimization would be to only call uuid in a hook if this is an uncontrolled component - const sidePanelId = useUniqueId(id); // This is meant to prevent animations when the component renders for the first time. // mounted.current will only be false on the first pass @@ -140,24 +131,20 @@ const SidePanel = ({ }, []); React.useEffect(() => { - if (typeof onCollapsedChange !== 'undefined') { - onCollapsedChange(collapsed); + if (typeof onExpandedChange !== 'undefined') { + onExpandedChange(expanded); } - }, [collapsed, onCollapsedChange]); + }, [expanded, onExpandedChange]); React.useEffect(() => { - if (typeof onStateChange !== 'undefined') { - onStateChange(internalState); + if (typeof onStateTransition !== 'undefined') { + onStateTransition(state); } - }, [internalState, onStateChange]); + }, [state, onStateTransition]); const motion = { - collapse: createKeyframes(collapsedWidth, width), - expand: createKeyframes(width, collapsedWidth), - }; - - const handleClick = () => { - setCollapsed(!collapsed); + collapse: createKeyframes(expandedWidth, collapsedWidth), + expand: createKeyframes(collapsedWidth, expandedWidth), }; const handleAnimationEnd = (event: React.AnimationEvent) => { @@ -166,7 +153,7 @@ const SidePanel = ({ event.animationName === motion.collapse.name || event.animationName === motion.expand.name ) { - setInternalState(collapsed ? 'collapsed' : 'expanded'); + setState(expanded ? 'expanded' : 'collapsed'); } } @@ -181,7 +168,7 @@ const SidePanel = ({ event.animationName === motion.collapse.name || event.animationName === motion.expand.name ) { - setInternalState(collapsed ? 'collapsing' : 'expanding'); + setState(expanded ? 'expanding' : 'collapsing'); } } @@ -190,38 +177,25 @@ const SidePanel = ({ } }; - // Note: Depending on the collapsed width, the button could "jump" to it's final position. - const buttonStyle = css({ - position: 'absolute', - top: spacing.l, - right: internalState === 'collapsed' ? 0 : origin === 'left' ? spacing.s : undefined, - left: internalState === 'collapsed' ? 0 : origin === 'right' ? spacing.s : undefined, - margin: internalState === 'collapsed' ? 'auto' : undefined, - transform: collapsed - ? `scaleX(${origin === 'left' ? '1' : '-1'})` - : `scaleX(${origin === 'left' ? '-1' : '1'})`, - }); - return (
- {typeof collapsedProp === 'undefined' ? ( - - ) : null} - {children} + + {children} +
); }; +/** + * A toggle button styled specifically for the side panel container. + */ +const ToggleButton = ({ + variant = IconButton.Variant.CircleFilled, + icon = transformationImportIcon, + ...rest +}: IconButtonProps) => { + const context = React.useContext(SidePanelContext); + + // Note: Depending on the collapsed width, the button could "jump" to it's final position. + const buttonStyle = css({ + position: 'absolute', + top: spacing.l, + right: context.state === 'collapsed' ? 0 : context.origin === 'left' ? spacing.s : undefined, + left: context.state === 'collapsed' ? 0 : context.origin === 'right' ? spacing.s : undefined, + margin: context.state === 'collapsed' ? 'auto' : undefined, + transform: + context.state === 'collapsed' || context.state === 'collapsing' + ? `scaleX(${context.origin === 'left' ? '1' : '-1'})` + : `scaleX(${context.origin === 'left' ? '-1' : '1'})`, + }); + + return ; +}; + +const HiddenLabel = ({children, ...elemProps}: React.HTMLAttributes) => ( + + {children} + +); + +SidePanel.HiddenLabel = HiddenLabel; +SidePanel.ToggleButton = ToggleButton; export default SidePanel; diff --git a/modules/_labs/side-panel/react/lib/hooks.ts b/modules/_labs/side-panel/react/lib/hooks.ts index bbab3eec7a..07a2168e55 100644 --- a/modules/_labs/side-panel/react/lib/hooks.ts +++ b/modules/_labs/side-panel/react/lib/hooks.ts @@ -1,23 +1,48 @@ import * as React from 'react'; -// TODO: From our friend NicholasBoll with slight modifications: https://codesandbox.io/s/controllable-tooltip-sfnus?file=/src/useControllableState.ts -export const useControllableState = ( - value: T, - defaultValue: T | (() => T) -): [T, (value: T) => void, boolean] => { - const [valueState, setValueState] = React.useState(defaultValue); - const isControlled = value !== undefined; - - // TODO: warn about switching between controlled and uncontrolled - // TODO: warn about changing the default value - - const effectiveValue = isControlled ? value : valueState; - - const updateValue = (newValue: T) => { - if (!isControlled) { - setValueState(newValue); - } +import {useUniqueId} from '@workday/canvas-kit-react-common'; +import {SidePanelProps} from './SidePanel'; + +export interface UseSidePanelProps extends Pick, 'id'> { + initialExpanded?: boolean; + sidePanelId?: string; + labelId?: string; +} + +export const useSidePanel = ({ + initialExpanded = true, + sidePanelId: sidePanelIdParam, + labelId: labelIdParam, +}: UseSidePanelProps) => { + const [expanded, setExpanded] = React.useState(initialExpanded); + const sidePanelId = useUniqueId(sidePanelIdParam); + const labelId = useUniqueId(labelIdParam); + + const handleClick = () => { + setExpanded(!expanded); + }; + + const panelProps: Partial = { + expanded: expanded, + id: sidePanelId, + 'aria-labelledby': typeof labelId === 'undefined' ? undefined : labelId, }; - return [effectiveValue, updateValue, isControlled]; + const labelProps = { + id: labelId, + }; + + const buttonProps = { + 'aria-controls': sidePanelId, + 'aria-expanded': expanded, + onClick: handleClick, + }; + + return { + expanded, + setExpanded, + panelProps, + labelProps, + buttonProps, + }; }; diff --git a/modules/_labs/side-panel/react/stories/stories.tsx b/modules/_labs/side-panel/react/stories/stories.tsx index d50ac8c0d3..59b08a0dc1 100644 --- a/modules/_labs/side-panel/react/stories/stories.tsx +++ b/modules/_labs/side-panel/react/stories/stories.tsx @@ -1,12 +1,19 @@ /// +/** @jsx jsx */ import * as React from 'react'; import withReadme from 'storybook-readme/with-readme'; - -import SidePanel from '../index'; +import {css, jsx} from '@emotion/core'; +import SidePanel, {useSidePanel} from '../index'; import {Button} from '@workday/canvas-kit-react-button'; import {colors, depth} from '@workday/canvas-kit-react-core'; +import {type} from '@workday/canvas-kit-labs-react-core'; +import {AccentIcon} from '@workday/canvas-kit-react-icon'; +import {rocketIcon} from '@workday/canvas-accent-icons-web'; +import {plusIcon} from '@workday/canvas-system-icons-web'; import README from '../README.md'; +import {accessibleHide} from '@workday/canvas-kit-react-common'; +import {SidePanelTransitionStates} from '../lib/SidePanel'; export default { title: 'Labs|Side Panel/React', @@ -15,23 +22,67 @@ export default { }; export const Default = () => { - const heightOffset = 40; + const height = `calc(100vh - 80px)`; + const {expanded, panelProps, labelProps, buttonProps} = useSidePanel({}); + const [panelState, setPanelState] = React.useState( + expanded ? 'expanded' : 'collapsed' + ); + + const headerStyles = state => + css(state === 'expanded' ? {} : accessibleHide, { + display: 'flex', + alignItems: 'center', + padding: `16px 12px`, + }); + /** + * This is a typical use case for a side panel. The Side Panel has a toggle button and + * a header. Together, these three elements make a expandable panel accessible and the + * useSidePanel hook helps connect the three elements together through prop spreading. + */ return ( - { - console.log(`collapsed state is: ${collapsed ? 'true' : 'false'}`); - }} - onStateChange={internalState => { - console.log(`Side Panel is ${internalState}`); - }} - > + +
+ +

+ Quick Tasks +

+
+ +
+ ); +}; + +export const NoHeaderPermanentlyOpen = () => { + const height = `calc(100vh - 80px)`; + const {panelProps, labelProps} = useSidePanel({}); + + /** + * We're not using buttonProps in this example because while there's a button, + * it's not controlling the Side Panel. It's meant for some other function. + * + * Because we don't have an explicit header, we're using a visually hidden element to + * label the side panel. This is done by spreading panelProps and labelProps to their + * respective elements (connects the two via aria-labelledby). + */ + return ( + + Quick Tasks +
+ +
+
); }; export const RightSidePanel = () => { const heightOffset = 40; + const {panelProps, buttonProps} = useSidePanel({ + initialExpanded: false, + id: 'sidepanelid1', + }); return (
{ }} > { - console.log(`collapsed state is: ${collapsed ? 'true' : 'false'}`); - }} - onStateChange={internalState => { - console.log(`Side Panel is ${internalState}`); - }} - > + > + +
); }; -export const ControlledSidePanel = () => { - const [collapsed, setCollapsed] = React.useState(false); +export const ExternalControl = () => { + const {panelProps, buttonProps} = useSidePanel({ + id: 'sidepanelid1', + initialExpanded: false, + }); const heightOffset = 40; return (
{ - console.log(`collapsed state is: ${collapsed ? 'true' : 'false'}`); + onExpandedChange={expanded => { + console.log(`expanded prop is: ${expanded ? 'true' : 'false'}`); }} - onStateChange={internalState => { - console.log(`Side Panel is ${internalState}`); + onStateTransition={state => { + console.log(`Side Panel is ${state}`); }} >
@@ -85,14 +137,15 @@ export const ControlledSidePanel = () => { backgroundColor: colors.frenchVanilla100, }} > -

Controlled Side Panel

+

Control from somewhere else

diff --git a/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx b/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx index df862dba3f..aae08a52e4 100644 --- a/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx +++ b/modules/_labs/side-panel/react/stories/stories_VisualTesting.tsx @@ -53,7 +53,9 @@ export const SidePanelStates = () => ( > {props => (
- + + +
)} From 988348de5f0926be77c018bcf0c2b96e14e7ddca Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Thu, 8 Oct 2020 09:01:03 -0700 Subject: [PATCH 20/49] fix(side-panel): Add type=button --- modules/_labs/side-panel/react/lib/SidePanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index 4566e12d7f..9a6395898a 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -239,7 +239,7 @@ const ToggleButton = ({ : `scaleX(${context.origin === 'left' ? '-1' : '1'})`, }); - return ; + return ; }; const HiddenLabel = ({children, ...elemProps}: React.HTMLAttributes) => ( From 7f6052e95e13abb12fbfe3bc9e4d4f177eb35f56 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Thu, 8 Oct 2020 10:23:45 -0700 Subject: [PATCH 21/49] chore(side-panel): Add deps for stories --- modules/_labs/side-panel/react/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/_labs/side-panel/react/package.json b/modules/_labs/side-panel/react/package.json index 86cde72472..f3990ae7ce 100644 --- a/modules/_labs/side-panel/react/package.json +++ b/modules/_labs/side-panel/react/package.json @@ -51,6 +51,8 @@ "@workday/canvas-kit-react-button": "^4.2.0", "@workday/canvas-kit-react-common": "^4.2.0", "@workday/canvas-kit-react-core": "^4.2.0", + "@workday/canvas-kit-react-icon": "^4.2.0", + "@workday/canvas-accent-icons-web": "^1.0.21", "@workday/canvas-system-icons-web": "^1.0.20" }, "devDependencies": { From 9525c734ccf63178b150e084932b8beb4c48b258 Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Mon, 12 Oct 2020 20:45:47 -0700 Subject: [PATCH 22/49] feat(side-panel): Add 'as' prop to specify the rendered element Also refactored hooks and the stories to match. --- .../_labs/side-panel/react/lib/SidePanel.tsx | 35 +++++---- modules/_labs/side-panel/react/lib/hooks.ts | 17 ++--- modules/_labs/side-panel/react/package.json | 1 + .../side-panel/react/stories/stories.tsx | 71 +++++++------------ 4 files changed, 58 insertions(+), 66 deletions(-) diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index 9a6395898a..182b00b30c 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -1,5 +1,6 @@ /** @jsx jsx */ import * as React from 'react'; +import styled from '@emotion/styled'; import {css, jsx, keyframes, CSSObject} from '@emotion/core'; import {IconButton, IconButtonProps} from '@workday/canvas-kit-react-button'; import {spacing, colors, depth} from '@workday/canvas-kit-react-core'; @@ -11,10 +12,11 @@ export type SidePanelTransitionStates = 'collapsed' | 'collapsing' | 'expanded' export interface SidePanelProps extends React.HTMLAttributes { /** - * The `aria-label` attribute for the side panel and button. This is required for accessibility. + * The element the side panel will render as (e.g. 'div'). * + * @default 'section' */ - 'aria-label'?: string; + as?: React.ElementType; /** * The width of the component (in `px` if it's a `number`) when it is collapsed. * @@ -44,10 +46,6 @@ export interface SidePanelProps extends React.HTMLAttributes { * @default 'left' */ origin?: 'left' | 'right'; - /** - * InternalState - */ - state?: SidePanelTransitionStates; /** * The function called when the side panel's `expanded` state changes. States like 'collapsing' and 'expanding' are tracked in another callback: `onStateChange` * @@ -99,13 +97,19 @@ const containerVariantStyle: {[K in SidePanelVariant]: CSSObject} = { }, }; +const Panel = styled('section')>({ + overflow: 'hidden', + position: 'relative', + boxSizing: 'border-box', +}); + export const SidePanelContext = React.createContext({ state: 'expanded', origin: 'left', }); const SidePanel = ({ - 'aria-label': ariaLabel = 'navigation pane', + as = 'section', children, collapsedWidth = 64, expanded = true, @@ -178,14 +182,11 @@ const SidePanel = ({ }; return ( -
{children} -
+ ); }; +// For SidePanel, we're leveraging a hidden span to label this toggle button via `aria-labelledby`. +// This type removes the requirement for `aria-label` in this component +export type ToggleButtonProps = Omit & + Partial>; + /** * A toggle button styled specifically for the side panel container. */ @@ -223,7 +229,7 @@ const ToggleButton = ({ variant = IconButton.Variant.CircleFilled, icon = transformationImportIcon, ...rest -}: IconButtonProps) => { +}: ToggleButtonProps) => { const context = React.useContext(SidePanelContext); // Note: Depending on the collapsed width, the button could "jump" to it's final position. @@ -239,6 +245,7 @@ const ToggleButton = ({ : `scaleX(${context.origin === 'left' ? '-1' : '1'})`, }); + // @ts-ignore We don't need this to have aria-label, let the user decide to use that or aria-labelledby on the ToggleButton component return ; }; diff --git a/modules/_labs/side-panel/react/lib/hooks.ts b/modules/_labs/side-panel/react/lib/hooks.ts index 07a2168e55..67c33c84cd 100644 --- a/modules/_labs/side-panel/react/lib/hooks.ts +++ b/modules/_labs/side-panel/react/lib/hooks.ts @@ -5,17 +5,17 @@ import {SidePanelProps} from './SidePanel'; export interface UseSidePanelProps extends Pick, 'id'> { initialExpanded?: boolean; - sidePanelId?: string; + panelId?: string; labelId?: string; } export const useSidePanel = ({ initialExpanded = true, - sidePanelId: sidePanelIdParam, + panelId: panelIdParam, labelId: labelIdParam, }: UseSidePanelProps) => { const [expanded, setExpanded] = React.useState(initialExpanded); - const sidePanelId = useUniqueId(sidePanelIdParam); + const panelId = useUniqueId(panelIdParam); const labelId = useUniqueId(labelIdParam); const handleClick = () => { @@ -24,17 +24,18 @@ export const useSidePanel = ({ const panelProps: Partial = { expanded: expanded, - id: sidePanelId, - 'aria-labelledby': typeof labelId === 'undefined' ? undefined : labelId, + id: panelId, + 'aria-labelledby': labelId, }; const labelProps = { id: labelId, }; - const buttonProps = { - 'aria-controls': sidePanelId, + const controlProps = { + 'aria-controls': panelId, 'aria-expanded': expanded, + 'aria-labelledby': labelId, onClick: handleClick, }; @@ -43,6 +44,6 @@ export const useSidePanel = ({ setExpanded, panelProps, labelProps, - buttonProps, + controlProps, }; }; diff --git a/modules/_labs/side-panel/react/package.json b/modules/_labs/side-panel/react/package.json index f3990ae7ce..eb021ffe5b 100644 --- a/modules/_labs/side-panel/react/package.json +++ b/modules/_labs/side-panel/react/package.json @@ -48,6 +48,7 @@ ], "dependencies": { "@emotion/core": "^10.0.28", + "@emotion/styled": "^10.0.27", "@workday/canvas-kit-react-button": "^4.2.0", "@workday/canvas-kit-react-common": "^4.2.0", "@workday/canvas-kit-react-core": "^4.2.0", diff --git a/modules/_labs/side-panel/react/stories/stories.tsx b/modules/_labs/side-panel/react/stories/stories.tsx index 59b08a0dc1..bb0d8b948e 100644 --- a/modules/_labs/side-panel/react/stories/stories.tsx +++ b/modules/_labs/side-panel/react/stories/stories.tsx @@ -23,7 +23,7 @@ export default { export const Default = () => { const height = `calc(100vh - 80px)`; - const {expanded, panelProps, labelProps, buttonProps} = useSidePanel({}); + const {expanded, panelProps, labelProps, controlProps} = useSidePanel({}); const [panelState, setPanelState] = React.useState( expanded ? 'expanded' : 'collapsed' ); @@ -35,20 +35,15 @@ export const Default = () => { padding: `16px 12px`, }); - /** - * This is a typical use case for a side panel. The Side Panel has a toggle button and - * a header. Together, these three elements make a expandable panel accessible and the - * useSidePanel hook helps connect the three elements together through prop spreading. - */ return ( +

- Quick Tasks + Tasks Panel

-
); }; @@ -57,17 +52,9 @@ export const NoHeaderPermanentlyOpen = () => { const height = `calc(100vh - 80px)`; const {panelProps, labelProps} = useSidePanel({}); - /** - * We're not using buttonProps in this example because while there's a button, - * it's not controlling the Side Panel. It's meant for some other function. - * - * Because we don't have an explicit header, we're using a visually hidden element to - * label the side panel. This is done by spreading panelProps and labelProps to their - * respective elements (connects the two via aria-labelledby). - */ return ( - Quick Tasks + Tasks Panel
); }; export const ExternalControl = () => { - const {panelProps, buttonProps} = useSidePanel({ + const {panelProps, labelProps, controlProps} = useSidePanel({ id: 'sidepanelid1', initialExpanded: false, }); const heightOffset = 40; return (
-
- { - console.log(`expanded prop is: ${expanded ? 'true' : 'false'}`); - }} - onStateTransition={state => { - console.log(`Side Panel is ${state}`); - }} - > -
-
+ { + console.log(`expanded prop is: ${expanded ? 'true' : 'false'}`); + }} + onStateTransition={state => { + console.log(`Side Panel is ${state}`); + }} + > + Controlled Panel + +
{ }} >

Control from somewhere else

-
-
+
); }; From e6c76d75d1bdb345b2c1b4e5107a61dde69da6ce Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Mon, 12 Oct 2020 21:43:09 -0700 Subject: [PATCH 23/49] docs(side-panel): Sync up docs --- modules/_labs/side-panel/react/README.md | 110 +++++++++++++++++- .../_labs/side-panel/react/lib/SidePanel.tsx | 8 +- 2 files changed, 109 insertions(+), 9 deletions(-) diff --git a/modules/_labs/side-panel/react/README.md b/modules/_labs/side-panel/react/README.md index 7db27cddbe..0ee193daad 100644 --- a/modules/_labs/side-panel/react/README.md +++ b/modules/_labs/side-panel/react/README.md @@ -15,15 +15,75 @@ yarn add @workday/canvas-kit-labs-react-side-panel ## Usage ```tsx -import * as React from 'react'; -import SidePanel from '@workday/canvas-kit-labs-react-side-panel'; +import SidePanel, {useSidePanel} from '@workday/canvas-kit-labs-react-side-panel'; + +/** + * A SidePanel is made up of three components + * - The panel itself + * - An accessible name (either an existing element or you can use ) + * - A control that expands/collapses the SidePanel, usually as a child of + * A convenience hook is available for setting the state of the SidePanel along with the proper aria- attributes + * + */ +const [panelProps, labelProps, controlProps] = useSidePanel({}); + + + +

Tasks Panel

+ {/* Your panel contents */} +
-; +``` +## Hooks +### `useSidePanel` +This hook manages the state and `aria-` attributes for the SidePanel. It takes in a `config` object: +* `intialExpanded: boolean`: Specifies the initial expanded/collapsed state of the side panel. +* `panelId: string`: specifies the id of the panel, if `undefined` then this hook will generate one +* `labelId: string` specifies the id of the label, if `undefined` then this hook will generate one + +and returns: +* `expanded: boolean`: The state for the `SidePanel`'s `expanded` prop. +* `setExpanded: (expanded?: boolean) => void`: A function that sets the `expanded` state. +* `panelProps: object`: Contains the `expanded`, `id`, and `aria-labelledby` props for the `SidePanel` (or equivalent) component. +* `labelProps: object`: Contains the `id` prop for the label. +* `controlProps: object`: Contains the `aria-controls`, `aria-expanded`, `aria-labelledby`, and click handler for the side panel's control element. + +#### Usage +```tsx +const [expanded, setExpanded, panelProps, labelProps, controlProps] = useSidePanel({ + initialExpanded: true, + panelId: 'side-panel-1', + labelId: 'side-panel-label-1' +}); + + + {/* Make sure the control is first focusable */} + + Example Panel + {/* Rest of the children here */} + + +or + + + {/* If you want to use your own button */} + + {/* If you already have an element that can be an accessible name for the panel */} +

Example Panel

+
``` ## Static Properties -> None +#### `HiddenLabel` +> Because a side panel is `role='region'` it must have an accessible name. You can use `` in conjunction with the `useSidePanel` hook or manually using `aria-labelledby`. This specifies the panel's accessible name if you don't have one as an element already (if you do, use that instead). + +> `HiddenLabel` hides the element visually and from screen readers. It is solely used to provide an accessible name. + +#### `ToggleButton` +> `` is a control that is meant to toggle between `expanded = true` and `expanded = false` states. It must be used within the `SidePanel` component as a child. Use in conjunction with `useSidePanel`'s `controlProps`, otherwise it does not come with explicit `onClick` handlers. + +> **Important**: For accessibility purposes, it must be the first focusable element. We recommend that you keep it as the first child of `SidePanel`. ## Component Props @@ -33,4 +93,44 @@ import SidePanel from '@workday/canvas-kit-labs-react-side-panel'; ### Optional -> None +#### `as: React.ElementType` +> The element the side panel will render as. + +Default: `'section'` + +#### `collapsedWidth: number | string` +> The width of the component (in `px` if it's a `number`) when it is collapsed. + +Default: `64` + +#### `expanded: boolean` +> If true, sets the expanded state of the side panel + +Default: `false` + +#### `expandedWidth: number | string` +> The width of the component (in `px` if it's a `number`) when it is expanded. + +Default: `320` + +#### `height: number | string` +> The height of the component (in `px` if it's a `number`). + +Default: `'100%'` + +#### `origin: 'left' | 'right'` +> Which side the side panel is meant to originate from. + +Default: `'left'` + +#### `onExpandedChange: (expanded?: boolean) => void` +> The function called when the side panel's `expanded` state changes. States like `'collapsing'` and `'expanding'` are tracked in another callback: `onStateChange` + +#### `onStateTransition: (state?: SidePanelTransitionStates) => void` +> The function called when the side panel is transitioning between states. Use this to track when the side panel is animating between 'collapsed', 'collapsing', 'expanded', and 'expanding' states. This can be particularly helpful if child components need to react specifically to these states. + +#### `variant: SidePanelVariant` +> The style variant of the side panel. 'standard' is with a `soap100` background, no depth. 'alternate' is a `frenchVanilla100` background with a level 3 depth. + +Default: `'standard'` + diff --git a/modules/_labs/side-panel/react/lib/SidePanel.tsx b/modules/_labs/side-panel/react/lib/SidePanel.tsx index 182b00b30c..f51c8339de 100644 --- a/modules/_labs/side-panel/react/lib/SidePanel.tsx +++ b/modules/_labs/side-panel/react/lib/SidePanel.tsx @@ -47,21 +47,21 @@ export interface SidePanelProps extends React.HTMLAttributes { */ origin?: 'left' | 'right'; /** - * The function called when the side panel's `expanded` state changes. States like 'collapsing' and 'expanding' are tracked in another callback: `onStateChange` + * The function called when the side panel's `expanded` state changes. States like `'collapsing'` and `'expanding'` are tracked in another callback: `onStateChange` * * @param boolean */ onExpandedChange?: (expanded?: boolean) => void; /** * The function called when the side panel is transitioning between states. - * Use this to track when the side panel is animating between 'collapsed', 'collapsing', 'expanded', and 'expanding' states. + * Use this to track when the side panel is animating between `'collapsed'`, `'collapsing'`, `'expanded'`, and `'expanding'` states. * This can be particularly helpful if child components need to react specifically to these states. * * @param SidePanelTransitionStates */ onStateTransition?: (state?: SidePanelTransitionStates) => void; /** - * The style variant of the side panel + * The style variant of the side panel. 'standard' is with a `soap100` background, no depth. 'alternate' is a `frenchVanilla100` background with a level 3 depth. * * @default 'standard' */ @@ -250,7 +250,7 @@ const ToggleButton = ({ }; const HiddenLabel = ({children, ...elemProps}: React.HTMLAttributes) => ( - + ); From ee30da88ea7e215c419ba9c1dbf66db4d15d327c Mon Sep 17 00:00:00 2001 From: lychyi <14299381+lychyi@users.noreply.github.com> Date: Tue, 13 Oct 2020 08:13:40 -0700 Subject: [PATCH 24/49] fix(side-panel): Remove hidden label static Replace with more standard way of doing it, adjust docs --- modules/_labs/side-panel/react/README.md | 9 ++--- .../_labs/side-panel/react/lib/SidePanel.tsx | 9 ----- modules/_labs/side-panel/react/package.json | 1 - .../side-panel/react/stories/stories.tsx | 36 +++++++++---------- 4 files changed, 20 insertions(+), 35 deletions(-) diff --git a/modules/_labs/side-panel/react/README.md b/modules/_labs/side-panel/react/README.md index 0ee193daad..83e5d13c07 100644 --- a/modules/_labs/side-panel/react/README.md +++ b/modules/_labs/side-panel/react/README.md @@ -20,7 +20,7 @@ import SidePanel, {useSidePanel} from '@workday/canvas-kit-labs-react-side-panel /** * A SidePanel is made up of three components * - The panel itself - * - An accessible name (either an existing element or you can use ) + * - An accessible name (either an existing element or you can use