From d48612b1709ebe7466659b744400ec1e398155a3 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 5 Feb 2021 16:59:29 +0000 Subject: [PATCH 01/74] Auto-generate react-menu --- packages/react-examples/package.json | 7 +- .../src/react-menu/react-menu.stories.scss | 1 + packages/react-menu/.eslintrc.json | 4 ++ packages/react-menu/.npmignore | 34 ++++++++++ packages/react-menu/LICENSE | 15 +++++ packages/react-menu/README.md | 11 ++++ packages/react-menu/Spec.md | 63 ++++++++++++++++++ packages/react-menu/config/api-extractor.json | 3 + packages/react-menu/config/tests.js | 7 ++ packages/react-menu/etc/react-menu.api.md | 0 packages/react-menu/jest.config.js | 9 +++ packages/react-menu/just.config.ts | 3 + packages/react-menu/package.json | 66 +++++++++++++++++++ packages/react-menu/src/index.ts | 1 + packages/react-menu/src/version.ts | 5 ++ packages/react-menu/tsconfig.json | 22 +++++++ 16 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 packages/react-examples/src/react-menu/react-menu.stories.scss create mode 100644 packages/react-menu/.eslintrc.json create mode 100644 packages/react-menu/.npmignore create mode 100644 packages/react-menu/LICENSE create mode 100644 packages/react-menu/README.md create mode 100644 packages/react-menu/Spec.md create mode 100644 packages/react-menu/config/api-extractor.json create mode 100644 packages/react-menu/config/tests.js create mode 100644 packages/react-menu/etc/react-menu.api.md create mode 100644 packages/react-menu/jest.config.js create mode 100644 packages/react-menu/just.config.ts create mode 100644 packages/react-menu/package.json create mode 100644 packages/react-menu/src/index.ts create mode 100644 packages/react-menu/src/version.ts create mode 100644 packages/react-menu/tsconfig.json diff --git a/packages/react-examples/package.json b/packages/react-examples/package.json index 9333e8946fc56e..f9803aec130c1d 100644 --- a/packages/react-examples/package.json +++ b/packages/react-examples/package.json @@ -54,12 +54,10 @@ "@fluentui/azure-themes": "^8.0.0-beta.48", "@fluentui/date-time-utilities": "^8.0.0-beta.2", "@fluentui/example-data": "^8.0.0-beta.3", - "@fluentui/react-file-type-icons": "^8.0.0-beta.15", "@fluentui/font-icons-mdl2": "^8.0.0-beta.13", "@fluentui/foundation-legacy": "^8.0.0-beta.13", "@fluentui/ie11-polyfills": "^0.1.4", "@fluentui/merge-styles": "^8.0.0-beta.4", - "@fluentui/react-provider": "^0.1.1", "@fluentui/react": "^8.0.0-beta.49", "@fluentui/react-avatar": "^0.9.0", "@fluentui/react-button": "^1.0.0-beta.27", @@ -69,6 +67,7 @@ "@fluentui/react-date-time": "^8.0.0-beta.39", "@fluentui/react-docsite-components": "^8.0.0-beta.49", "@fluentui/react-experiments": "^8.0.0-beta.53", + "@fluentui/react-file-type-icons": "^8.0.0-beta.15", "@fluentui/react-flex": "^0.2.3", "@fluentui/react-focus": "^8.0.0-beta.18", "@fluentui/react-hooks": "^8.0.0-beta.10", @@ -77,6 +76,8 @@ "@fluentui/react-internal": "^8.0.0-beta.43", "@fluentui/react-link": "^1.0.0-beta.38", "@fluentui/react-make-styles": "^0.2.2", + "@fluentui/react-menu": "*", + "@fluentui/react-provider": "^0.1.1", "@fluentui/react-shared-contexts": "^1.0.0-beta.5", "@fluentui/react-slider": "^1.0.0-beta.39", "@fluentui/react-tabs": "^1.0.0-beta.40", @@ -86,8 +87,8 @@ "@fluentui/react-toggle": "^1.0.0-beta.39", "@fluentui/scheme-utilities": "^8.0.0-beta.13", "@fluentui/style-utilities": "^8.0.0-beta.13", - "@fluentui/theme-samples": "^8.0.0-beta.48", "@fluentui/theme": "^2.0.0-beta.13", + "@fluentui/theme-samples": "^8.0.0-beta.48", "@fluentui/utilities": "^8.0.0-beta.10", "@microsoft/load-themed-styles": "^1.10.26", "@types/react": "16.9.42", diff --git a/packages/react-examples/src/react-menu/react-menu.stories.scss b/packages/react-examples/src/react-menu/react-menu.stories.scss new file mode 100644 index 00000000000000..15fbe14d671c70 --- /dev/null +++ b/packages/react-examples/src/react-menu/react-menu.stories.scss @@ -0,0 +1 @@ +/* Storybook SCSS file for package: react-menu */ diff --git a/packages/react-menu/.eslintrc.json b/packages/react-menu/.eslintrc.json new file mode 100644 index 00000000000000..ceea884c70dccc --- /dev/null +++ b/packages/react-menu/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "extends": ["plugin:@fluentui/eslint-plugin/react"], + "root": true +} diff --git a/packages/react-menu/.npmignore b/packages/react-menu/.npmignore new file mode 100644 index 00000000000000..24337b6c973e82 --- /dev/null +++ b/packages/react-menu/.npmignore @@ -0,0 +1,34 @@ +*.api.json +*.config.js +*.log +*.nuspec +*.test.* +*.yml +.editorconfig +.eslintrc* +.eslintcache +.gitattributes +.gitignore +.vscode +coverage +dist/storybook +dist/*.stats.html +dist/*.stats.json +dist/demo +fabric-test* +gulpfile.js +images +index.html +jsconfig.json +node_modules +results +src/**/* +!src/**/examples/*.tsx +!src/**/docs/**/*.md +!src/**/*.types.ts +temp +tsconfig.json +tsd.json +tslint.json +typings +visualtests diff --git a/packages/react-menu/LICENSE b/packages/react-menu/LICENSE new file mode 100644 index 00000000000000..c3c85572a6eeb3 --- /dev/null +++ b/packages/react-menu/LICENSE @@ -0,0 +1,15 @@ +@fluentui/react-menu + +Copyright (c) Microsoft Corporation + +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Note: Usage of the fonts and icons referenced in Fluent UI React is subject to the terms listed at https://aka.ms/fluentui-assets-license diff --git a/packages/react-menu/README.md b/packages/react-menu/README.md new file mode 100644 index 00000000000000..cb8606156b9000 --- /dev/null +++ b/packages/react-menu/README.md @@ -0,0 +1,11 @@ +# @fluentui/react-menu + +**React Menu components for [Fluent UI React](https://developer.microsoft.com/en-us/fluentui)** + +These are not production-ready components and **should never be used in product**. This space is useful for testing new components whose APIs might change before final release. + +To import React Menu components: + +```js +import { ComponentName } from '@fluentui/react-menu'; +``` diff --git a/packages/react-menu/Spec.md b/packages/react-menu/Spec.md new file mode 100644 index 00000000000000..7b51195888325d --- /dev/null +++ b/packages/react-menu/Spec.md @@ -0,0 +1,63 @@ +# @fluentui/react-menu Spec + +## Background + +_Description and use cases of this component_ + +## Prior Art + +_Include background research done for this component_ + +- _Link to Open UI research_ +- _Link to comparison of v7 and v0_ +- _Link to GitHub epic issue for the converged component_ + +## Sample Code + +_Provide some representative example code that uses the proposed API for the component_ + +## Variants + +_Describe visual or functional variants of this control, if applicable. For example, a slider could have a 2D variant._ + +## API + +_List the **Props** and **Slots** proposed for the component. Ideally this would just be a link to the component's `.types.ts` file_ + +## Structure + +- _**Public**_ +- _**Internal**_ +- _**DOM** - how the component will be rendered as HTML elements_ + +## Migration + +_Describe what will need to be done to upgrade from the existing implementations:_ + +- _Migration from v8_ +- _Migration from v0_ + +## Behaviors + +_Explain how the component will behave in use, including:_ + +- _Component States_ +- _Interaction_ + - _Keyboard_ + - _Cursor_ + - _Touch_ + - _Screen readers_ + +## Accessibility + +Base accessibility information is included in the design document. After the spec is filled and review, outcomes from it need to be communicated to design and incorporated in the design document. + +- Decide whether to use **native element** or folow **ARIA** and provide reasons +- Identify the **[ARIA](https://www.w3.org/TR/wai-aria-practices-1.2/) pattern** and, if the component is listed there, follow its specification as possible. +- Identify accessibility **variants**, the `role` ([ARIA roles](https://www.w3.org/TR/wai-aria-1.1/#role_definitions)) of the component, its `slots` and `aria-*` props. +- Describe the **keyboard navigation**: Tab Oder and Arrow Key Navigation. Describe any other keyboard **shortcuts** used +- Specify texts for **state change announcements** - [ARIA live regions + ](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) (number of available items in dropdown, error messages, confirmations, ...) +- Identify UI parts that appear on **hover or focus** and specify keyboard and screen reader interaction with them +- List cases when **focus** needs to be **trapped** in sections of the UI (for dialogs and popups or for hierarchical navigation) +- List cases when **focus** needs to be **moved programatically** (if parts of the UI are appearing/disappearing or other cases) diff --git a/packages/react-menu/config/api-extractor.json b/packages/react-menu/config/api-extractor.json new file mode 100644 index 00000000000000..c8406ab42ca3cc --- /dev/null +++ b/packages/react-menu/config/api-extractor.json @@ -0,0 +1,3 @@ +{ + "extends": "@fluentui/scripts/api-extractor/api-extractor.common.json" +} diff --git a/packages/react-menu/config/tests.js b/packages/react-menu/config/tests.js new file mode 100644 index 00000000000000..3882d3702ddc99 --- /dev/null +++ b/packages/react-menu/config/tests.js @@ -0,0 +1,7 @@ +/** Jest test setup file. */ + +const { configure } = require('enzyme'); +const Adapter = require('enzyme-adapter-react-16'); + +// Configure enzyme. +configure({ adapter: new Adapter() }); diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/packages/react-menu/jest.config.js b/packages/react-menu/jest.config.js new file mode 100644 index 00000000000000..2160afa436a444 --- /dev/null +++ b/packages/react-menu/jest.config.js @@ -0,0 +1,9 @@ +const { createConfig, resolveMergeStylesSerializer } = require('@fluentui/scripts/jest/jest-resources'); +const path = require('path'); + +const config = createConfig({ + setupFiles: [path.resolve(path.join(__dirname, 'config', 'tests.js'))], + snapshotSerializers: [resolveMergeStylesSerializer()], +}); + +module.exports = config; diff --git a/packages/react-menu/just.config.ts b/packages/react-menu/just.config.ts new file mode 100644 index 00000000000000..bcc7d9d264037c --- /dev/null +++ b/packages/react-menu/just.config.ts @@ -0,0 +1,3 @@ +import { preset } from '@fluentui/scripts'; + +preset(); diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json new file mode 100644 index 00000000000000..edd5337f617532 --- /dev/null +++ b/packages/react-menu/package.json @@ -0,0 +1,66 @@ +{ + "name": "@fluentui/react-menu", + "version": "0.1.0", + "description": "Fluent UI menu component", + "private": true, + "main": "lib-commonjs/index.js", + "module": "lib/index.js", + "typings": "lib/index.d.ts", + "sideEffects": [ + "lib/version.js" + ], + "repository": { + "type": "git", + "url": "https://github.com/microsoft/fluentui" + }, + "license": "MIT", + "scripts": { + "build": "just-scripts build", + "bundle": "just-scripts bundle", + "clean": "just-scripts clean", + "code-style": "just-scripts code-style", + "just": "just-scripts", + "lint": "just-scripts lint", + "start": "just-scripts dev:storybook", + "start-test": "just-scripts jest-watch", + "test": "just-scripts test", + "update-snapshots": "just-scripts jest -u" + }, + "devDependencies": { + "@fluentui/eslint-plugin": "^1.0.0-beta.1", + "@types/enzyme": "3.10.3", + "@types/enzyme-adapter-react-16": "1.0.3", + "@types/jest": "~24.9.0", + "@types/react": "16.9.42", + "@types/react-dom": "16.9.10", + "@types/react-test-renderer": "^16.0.0", + "@types/webpack-env": "1.16.0", + "@fluentui/scripts": "^1.0.0", + "enzyme": "~3.10.0", + "enzyme-adapter-react-16": "^1.15.0", + "react": "16.8.6", + "react-app-polyfill": "~1.0.1", + "react-dom": "16.8.6", + "react-test-renderer": "^16.3.0" + }, + "dependencies": { + "@fluentui/theme": "^2.0.0-beta.13", + "@fluentui/react-compose": "^1.0.0-beta.11", + "@fluentui/react-theme-provider": "^1.0.0-beta.19", + "@fluentui/set-version": "^8.0.0-beta.1", + "tslib": "^1.10.0" + }, + "peerDependencies": { + "@types/react": ">=16.8.0 <17.0.0", + "@types/react-dom": ">=16.8.0 <17.0.0", + "react": ">=16.8.0 <17.0.0", + "react-dom": ">=16.8.0 <17.0.0" + }, + "beachball": { + "tag": "latest", + "disallowedChangeTypes": [ + "major", + "prerelease" + ] + } +} diff --git a/packages/react-menu/src/index.ts b/packages/react-menu/src/index.ts new file mode 100644 index 00000000000000..107367257abe5c --- /dev/null +++ b/packages/react-menu/src/index.ts @@ -0,0 +1 @@ +import './version'; diff --git a/packages/react-menu/src/version.ts b/packages/react-menu/src/version.ts new file mode 100644 index 00000000000000..6e19ad1f002764 --- /dev/null +++ b/packages/react-menu/src/version.ts @@ -0,0 +1,5 @@ +// Do not modify this file; it is generated as part of publish. +// The checked in version is a placeholder only and will not be updated. +import { setVersion } from '@fluentui/set-version'; +setVersion('@fluentui/react-menu', '0.0.0'); + diff --git a/packages/react-menu/tsconfig.json b/packages/react-menu/tsconfig.json new file mode 100644 index 00000000000000..9ee9ee30f1eb04 --- /dev/null +++ b/packages/react-menu/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist", + "target": "es5", + "module": "commonjs", + "jsx": "react", + "declaration": true, + "sourceMap": true, + "experimentalDecorators": true, + "importHelpers": true, + "noUnusedLocals": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "moduleResolution": "node", + "preserveConstEnums": true, + "lib": ["es5", "dom"], + "typeRoots": ["../../node_modules/@types", "../../typings"], + "types": ["jest", "webpack-env", "custom-global"] + }, + "include": ["src"] +} From abd8efa7d31d1720a35854254458e881c4b40e52 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 5 Feb 2021 17:02:11 +0000 Subject: [PATCH 02/74] use correct react-menu version --- packages/react-examples/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-examples/package.json b/packages/react-examples/package.json index f9803aec130c1d..877fcf184efb17 100644 --- a/packages/react-examples/package.json +++ b/packages/react-examples/package.json @@ -76,7 +76,7 @@ "@fluentui/react-internal": "^8.0.0-beta.43", "@fluentui/react-link": "^1.0.0-beta.38", "@fluentui/react-make-styles": "^0.2.2", - "@fluentui/react-menu": "*", + "@fluentui/react-menu": "^0.1.0", "@fluentui/react-provider": "^0.1.1", "@fluentui/react-shared-contexts": "^1.0.0-beta.5", "@fluentui/react-slider": "^1.0.0-beta.39", From fc46c5b5b0dd1c2d72bc011e7d235a1c9efee51b Mon Sep 17 00:00:00 2001 From: ling1726 Date: Fri, 5 Feb 2021 17:06:00 +0000 Subject: [PATCH 03/74] Update packages/react-menu/package.json Co-authored-by: Oleksandr Fediashov --- packages/react-menu/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json index edd5337f617532..35cb5b3b1df69e 100644 --- a/packages/react-menu/package.json +++ b/packages/react-menu/package.json @@ -2,7 +2,6 @@ "name": "@fluentui/react-menu", "version": "0.1.0", "description": "Fluent UI menu component", - "private": true, "main": "lib-commonjs/index.js", "module": "lib/index.js", "typings": "lib/index.d.ts", From 101354c89b48112e38cdd426a52093f6e7c58b90 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 5 Feb 2021 17:27:48 +0000 Subject: [PATCH 04/74] update files --- .../react-examples/src/react-menu/index.ts | 1 + .../src/react-menu/react-menu.stories.scss | 1 - packages/react-menu/Spec.md | 1096 ++++++++++++++++- specs/Menu.md | 1085 ---------------- 4 files changed, 1060 insertions(+), 1123 deletions(-) create mode 100644 packages/react-examples/src/react-menu/index.ts delete mode 100644 packages/react-examples/src/react-menu/react-menu.stories.scss delete mode 100644 specs/Menu.md diff --git a/packages/react-examples/src/react-menu/index.ts b/packages/react-examples/src/react-menu/index.ts new file mode 100644 index 00000000000000..9ac88a11117486 --- /dev/null +++ b/packages/react-examples/src/react-menu/index.ts @@ -0,0 +1 @@ +// TODO placeholder diff --git a/packages/react-examples/src/react-menu/react-menu.stories.scss b/packages/react-examples/src/react-menu/react-menu.stories.scss deleted file mode 100644 index 15fbe14d671c70..00000000000000 --- a/packages/react-examples/src/react-menu/react-menu.stories.scss +++ /dev/null @@ -1 +0,0 @@ -/* Storybook SCSS file for package: react-menu */ diff --git a/packages/react-menu/Spec.md b/packages/react-menu/Spec.md index 7b51195888325d..58c40d4d0a894a 100644 --- a/packages/react-menu/Spec.md +++ b/packages/react-menu/Spec.md @@ -1,63 +1,1085 @@ -# @fluentui/react-menu Spec +# Menu ## Background -_Description and use cases of this component_ +### Definition -## Prior Art +This spec defines the default function of a `Menu` as an interactive component that displays a list of options that can be represented by a range possible states. Possible variants are defined in [the relevant section](#variants) -_Include background research done for this component_ +The `Menu` should be displayed on a temporary surface that interrupts the normal flow of content. The temporary surface should be triggered by an external user action such as (but not limited to) a click on a button or other UI control. -- _Link to Open UI research_ -- _Link to comparison of v7 and v0_ -- _Link to GitHub epic issue for the converged component_ +The interactions that result in the dismiss/removal of the `Menu` component should be configurable. -## Sample Code +## Prior art -_Provide some representative example code that uses the proposed API for the component_ +As a part of the spec definitions in Fluent UI, a research effort has been made through [Open UI](https://open-ui.org/). The current research proposal is available as an open source contribution undergoing review ([research proposal](https://github.com/WICG/open-ui/pull/249)) + +## Comparison of `@fluentui/react` and `@fluentui/react-northstar` + +- All mentions of v7 or v8 == `@fluentui/react` ([docsite](https://developer.microsoft.com/en-us/fluentui#/)) +- All mentions of v0 == `@fluentui/react-northstar` ([docsite](https://fluentsite.z22.web.core.windows.net/)) + +The most relevant comparison that can be achieved between the two libraries is between `ContextualMenu` in v7 and a combination of `Menu`, `Popup` and `ToolbarItem` in v0. + +v0 suffers from a consistency issue that the control used in `Menu` and the menu variant of `ToolbarItem` are not actually the same component and have different behavior. However, semantically for the purposes of this spec, they representthe same control that will be implemented. + +Note that the below code samples are not meant to be complete, but to highlight differences between the two libraries. Please refer to official docsites for actual API references. + +### Positioning + +`ContextualMenu` in v7 is a component that also exposes the API to control the positioning of the temporary popup surface that the Menu is rendered on. This aspect of the v7 component should be compared with the `Popup` component in v0 since the v0 `Menu` is created as a standalone component with no positioning properties. + +v0 uses the [OSS Popper.js library](https://popper.js.org/) while v7 uses a component based implementation `CalloutContent`. As a result, the API is very similar in intent and vocabulary as Popper. + +Below we provide the results of testing common positioning boundary/edge cases between the two. + +#### Configuration consistency + +The biggest difference bewteen the two libraries is that v0 provides a purely positioning based API out of the `Popup` react component. v0 Provides no direct prop values that will style the popup container and any adjustments to stlying properties such as (but not limited to) dimensions/margin/padding/layoud are expected to be implmented through the styling system used throughout the library + +The `Callout` component has some styling helpers that affect the styling of the contents: + +- calloutMaxHeight +- calloutMaxWidth +- calloutWidth +- backgroundColor + +The `ContextualMenu` component uses two styling properties not offered by `Callout` (useTargetWidth, useTargetMinWidth) and also duplicates some of `Callout's` own positioning properties while also allowing a shorthand slot for the `Callout` + +```typescript + +``` + +The result being that `ContextualMenu's` positiong and styling risks being abused by developers inexperienced in the library. There is also no documentation on the v7 docsite that states `calloutProps` is actually overriden by props declared directly on `ContextualMenu` + +### Position/Alignment hints + +Both libraries provide an API that achieves the same end result for positioning and alignment. Below is a table that maps the v7 `DirectionalHint` with the v0 props of `position` and `alignment` + +`DirectionalHint` can be passed to both `Callout` (which powers positioning) or directly to `ContextualMenu` (in two different ways). Whereas `position` and `alignment` are props of `Popup` in v0 and not used directly in `Menu` even for the positioning of its submenu. + +| DirectionalHint (v7) | position (v0) | align (v0) | +| -------------------- | ------------- | ---------- | +| topLeftEdge | above | start | +| topCenter | above | center | +| topRightEdge | above | bottom | +| topAutoEdge | above | | +| bottomLeftEdge | above | start | +| bottomCenter | below | center | +| bottomRightEdge | below | bottom | +| bottomAutoEdge | below | | +| leftTopEdge | before | top | +| leftCenter | before | center | +| leftBottomEdge | before | bottom | +| rightTopEdge | after | before | +| rightCenter | after | center | +| rightBottomEdge | after | bottom | + +First it's necessary to note the difference between the vocabulary used between the two. v7 will use `left` and `right` while v0 uses `before` and `after`. v0 vocabulary here is chosen to convey the appropriate meaning regardless of RTL by using the semantics of the conntent. It's also interesting to note that it's possible to supply an explicit RTL hint to v7 which is a flip by default. v0 will flip by default but requires the consumer to detect RTL scenarios and modify props in these situations + +In general the separation of both the position and alignment in v0 results in an API that is easier to use if a consumer only needs to modify one of the two props. However both try to achieve the same result in the end. + +It's important to note that if an incorrect pair of `position` and `align` are provided in v0, then `position` takes priority and `align` is set to `center` + +#### Offset + +```typescript + + + + +// offset can also be a function of raw Popper properties +const offsetFunction = ({ + popper: PopperJs.Rect; + reference: PopperJs.Rect; + placement: PopperJs.Placement; +}) => ([popper.width, -popper.height]) +``` + +v7 positioning can only apply a numerical value to the first part position attribute of `DirectionalHint`. v0 uses a much more flexible API that not only supports a function to defer calculation at runtime, but also supports the offset of the `Popup` in both axes. + +#### Bounds and overflow + +v0 `Popup` API is more consistent in this aspect and provides more control than the v7 `Callout`. + +```typescript + +``` + +`Popup` provider 3 different properties to handle bounds and overflow: + +- flipBoundary - the bounds to calculate when to flip positioning of the popup +- overflowBoundary - the bounds to shift the popup without overflowing +- mountNode - where the popup is actually rendered in the DOM, by default this is a portal to a div in body + +```typescript + ({/*Same object as above*/})} + target={htmlElement} + + // renders to a portal node on body + layerProps={/*ILayerProps*/} + + // every single one of the above can all be declared here too + calloutProps={{bounds, target}} +/> +``` + +`ContextualMenu` or `Callout` has no notion of separate boundaries for flip or overflow, and auto behaviour is used for flip and overflow 'pushing' +It should also be noted that `ContextualMenu` allows all of the same props to be passed to its submenus to custom tweak position for each submenu if necessary + +#### Submenu positioning + +The default positioning for both v0 and v7 submenus is: + +- rightTopEdge (v7) +- top-after (v0) + +Both will also flip appropriately when the overflow boundary is too small. + +The main difference between the two is that v0 submenu's position does not expose any way to customize or override the positioning of the submenu. However v7 allows every single customization as the root menu. It is very possible to do the below: + +```typescript +const menuItems: IContextualMenuItem[] = [ + { + key: 'newItem', + subMenuProps: { + // Any positioning props of `ContextualMenu` are usable + directionalHint: DirectionalHint.rightTopEdge, + // All `Callout` props as also usable + calloutProps: {...}, + items: [...], + }, + }, +``` + +#### Events + +v7 provides the following positioning event callbacks that might be used and should probably be supported for backwards compatibility: + +- onLayerMounted +- onPositioned +- onScroll + +### Trigger vs target + +The v7 `ContextualMenu` has a prop `target` which is intended to be a ref to the DOM element that the positioning logic anchors to. The usage of this prop requires the visibility state of the component to be controlled using React state by the consumer. The same prop exists on the v0 `Popup` component that is intended to perform the same function. + +```typescript +const buttonRef = React.useRef( + +
  • + +
  • + + + + +``` + +### Menu divider + +```html + + + + + +``` + +### Custom rendering and data + +v7 provides render callbacks that can be used to render either the entire menu list or specific slots of menut items. Each call back provides the props avaialble to that slot and a `defaultRender` which allows to easily extend the original render, if required. + +```typescript +// v7 custom rendering +const menuProps = { + onRenderMenuList: (props: IContextualMenuListProps, defaultRenderer) => {}, + onRenderSubMenu: (props: IContextualMenuProps, defaultRenderer) => {}, + items: [ + { + onRender: ( + item: any, + dismissMenu: (ev?: any, dismissAll?: boolean) => void + ) => React.ReactNode + } + {onRenderContent: (props: IContextualMenuItemProps, defaultRenderer) => {}}, + {onRenderIcon: (props: IContextualMenuItemProps, defaultRenderer) => {}}, + ] +} + + +``` + +Custom data can also be associated with menu items + +```typescript +const menuProps = { + items: [{ + ... + data: { foo: "bar" } + }] +} +``` + +v0 custom rendering through shorthand components is a consistent experience through all shorthand components, but provide a smaller API surface (whether this is simpler or less powerful can be subjective). Custom rendering in the case of the `Menu` component would be done through the use of `children` prop either through the standard React child component API or through shorthand as a callback function. + +```typescript +// v0 shorthand children render callback +const items = [ + { + key: 'editorials', + children: (El, props) => {props.key} + }, +] + + + +// v0 children API custom render + + + Editorials + + + CustomContent + + {/*Not recommended but definitely possible*/} +
    custom item
    +
    +``` ## Variants -_Describe visual or functional variants of this control, if applicable. For example, a slider could have a 2D variant._ +### Nested menus + +A `Menu` should be able to trigger an additional instance of itself as a part of one or more of its options. The nested `Menu` component should have the same functional capabilities as the root `Menu` component. + +The actions that trigger the the nested `Menu` should be be consistent with the actions that can trigger any root `Menu` from a similar UI control. + +We advise that no more than two nested `Menu` components be used, but this spec does not functionally apply that constrain to the implementation of the `Menu` component. + +### Selection state + +A `Menu` should be able to track and represent the selection state of all or some of its options if required. + +When an options is associated with a selection state. The `Menu`, either root or nested, should control its dismiss behavior accordingly based on configuration. + +### Sections + +A `Menu` can be partitioned into sections using visible dividers in its list of options. Each section can contain a heading title that announces or briefly describes the options in the particular section + +### Secondary label + +An option of a `Menu` component should be able to declare additional secondary label that can provide additional context describing the option or its usage. + +For example a secondary label can be a label that shows a keyboard shortcut that will perform an equivalent action of the option of the `Menu` component. + +### Split option with nesting + +An option of a `Menu` component can trigger a nested `Menu` component and also perform its default action by splitting the option into two interactable areas that handle each action separately. + +### Disabled option(s) + +All options in a `Menu` component can be disabled and should provide a visible indication for this purpose. User interaction should be defined for disabled options + +### Scrollable + +A `Menu` should display a vertical scrollbar when the number of options exceeds the height of the component + +### Standalone/No surface + +A `Menu` can be used without the temporary popup surface and its trigger. This will allow `Menu` components to be permanent page content or used in custom surfaces with a wider range of UI components. + +### Custom content + +``` +This variant is still a work in progress and needs additional thought +``` + +Any custom content can be used in the rendering of the Menu, all interactions and accessibility is left to the discretion of the consumer. ## API -_List the **Props** and **Slots** proposed for the component. Ideally this would just be a link to the component's `.types.ts` file_ +The `Menu` should implement a `children` based API as is the standard across all the surveyed alternatives as a part of Open UI research in [Prior Art](#prior-art). The component will leverage the use of `context` in the interaction and data flows of child components. -## Structure +Sample usages will be give in the following section of this document [Sample code](#sample-code) -- _**Public**_ -- _**Internal**_ -- _**DOM** - how the component will be rendered as HTML elements_ +### Menu -## Migration +The root level component serves as a simplified interface (sugar) for popup positioning and triggering. + +### MenuTrigger + +A non-visual component that wraps its child and configures them to be the trigger that will open a menu. This component should only accept one child + +### MenuList + +This component is used internally by `Menu` and manages the context and layout its items. + +`MenuList` can also be used separately as the standalone variant of the `Menu`, since it should not control popup positioning or triggers. It is the only component in the API that can be used standalone. Envisioned to be used with more complex popup or trigger scenarios where the `Menu` component does not provide enough control for these situations. + +### MenuGroup + +Creates a group inside a `MenuList`, setting up header layout and dividers between `MenuItems`. + +The MenuGroup is also a useful component to declare different selection groups (checkbox/radio) in a `MenuList`. + +| Prop name | Type | Details | +| --------- | ---- | --------------------------------------------------------------------------- | +| title | text | The title of of the section renders a [MenuGroupHeader](#menusectionheader) | + +### MenuGroupHeader + +Creates a section header element with appropriate styling. Will set correct `aria-labelledby` relationship if it is instantiated within a [MenuGroup](#menugroup) + +### MenuDivider + +Creates a divider element in the `MenuList` with correct HTML and aria semantics for divider. + +This divider is purely a visual cue. To ensure consistent narration experience across all screenreaders [MenuGroup](#menugroup) should be used + +### MenuItem + +As the name infers + +| Prop name | Type | Details | +| -------------- | --------- | ----------------------------------------- | +| icon | ReactNode | Icon that is rendered with the menu item | +| secondaryLabel | text | A secondary label i.e. keyboard shortcuts | + +### MenuItemCheckbox + +A variant of `MenuItem` that allows a multiple selection state based on the value that it represents + +| Prop name | Type | Details | +| --------- | ---- | -------------------------------------------------- | +| name | text | The name of the value that the checkbox represents | +| value | text | The value of the checkbox | + +### MenuItemRadio + +A variant of `MenuItem` that allows a single selection state based on the value that it represents + +| Prop name | Type | Details | +| --------- | ---- | -------------------------------------------------- | +| name | text | The name of the value that the checkbox represents | +| value | text | The value of the checkbox | + +## Sample code + +The below samples do not represent the definitive props of the final implemented component, but represent the ideal final implementations. Can be subject to change during the implementation phase. + +### Basic Menu + +```typescript +const menu = ( + + + + Option 1 + Option 2 + Option 3 + + +) +``` + +```html + + +
    +
    Option 1
    +
    Option 2
    +
    Option 3
    +
    +``` + +### Menu items with icons + +```typescript +const menu = ( + + + + }>Option 1 + }>Option 2 + }>Option 3 + + +) +``` + +```html + + +
    +
    + FileIcon + Option 1 +
    +
    + BellIcon + Option 2 +
    +
    + LinkIcon + Option 3 +
    +
    +``` + +### Sections + +```typescript +const menu = ( + + + + Option 1 + + + Section Option 1 + Section Option 2 + Section Option 3 + + + +) +``` + +```html + + + +
    +
    Option 1
    + +
    + +
    Section Option 1
    +
    Section Option 2
    +
    Section Option 3
    +
    +
    +
    +``` + +Custom section headings can also be used, but must be used within a [MenuGroup](#menugroup) to ensure correct narration experience + +```typescript + +const menu = ( + + + + Option 1 + + + {children} + Section Option 1 + Section Option 2 + Section Option 3 + + + +) +``` + +```html + + + +
    +
    Option 1
    + +
    + +
    Section Option 1
    +
    Section Option 2
    +
    Section Option 3
    +
    +
    +
    +``` + +### Submenus + +```typescript +const menu = ( + + + + Option 1 + + + Open submenu + + + Option 1 + Option 2 + Option 3 + + + + +) +``` + +```html + + +
    +
    Option 1
    + +
    + + +
    +
    Option 1
    +
    Option 2
    +
    Option 3
    +
    +``` + +### Standlone + +```typescript +const [open] = React.useState(false); + +const menu = ( + + + Option 1 + Option 2 + Option 3 + + +) +``` + +```html + +
    +
    Option 1
    +
    Option 2
    +
    Option 3
    +
    +``` + +### Selection + +```typescript +const trigger = +const [selectedItems, setSelectedItems] = React.useState([]); -_Describe what will need to be done to upgrade from the existing implementations:_ +// basic checkbox example +const menuCheckbox = ( + + + + Option 1 + Option 2 + Option 3 + + +) -- _Migration from v8_ -- _Migration from v0_ +// leverage MenuGroup for different selection groups +const menuSelectableSections = ( + + + + + Option 1 + Option 2 + Option 3 + + + Option 1 + Option 2 + Option 3 + + + +) +``` + +```html + + + +
    +
    Option 1
    +
    Option 2
    +
    Option 3
    +
    + + +
    +
    + +
    Option 1
    +
    Option 2
    +
    Option 3
    +
    +
    +
    + +
    Option 1
    +
    Option 2
    +
    Option 3
    +
    +
    +``` + +### Split button + +```typescript +const trigger = + +// basic checkbox example +const menuSplitbutton= ( + + + + Option 1 + + + + + Option 1 + Option 2 + Option 3 + + + +) +``` + +```html +
    +
    Option 1
    + +
    + + +
    +
    Option 1
    +
    +
    content slot
    + +
    +
    + + +
    +
    Option 1
    +
    Option 2
    +
    Option 3
    +
    +``` ## Behaviors -_Explain how the component will behave in use, including:_ +### Useful references + +The below references were used to decide n appropriate keyboard interactions from an a11y perspective. + +- https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-1/menubar-1.html# +- https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-links.html +- https://www.w3.org/WAI/tutorials/menus/application-menus/ + +### Menu open/dismiss + +A menu can be triggered by the following user interactions on the triggering/anchor element. Not all interactions should be supported at the same time, but the component must be able to support combinations of the below interactions. + +As a general rule, once the menu is closed the focus should return to the triggering element once the menu is closed unless the interaction would involve another focusable element. + +| Type | Action | Result | Details | Focus after | +| -------- | ---------- | ------- | ----------------------------------------------------------------- | --------------------------------------------- | +| Mouse | Click | Open | Click on the trigger element | First menuitem | +| Mouse | Hover | Open | Hover over the trigger element with delay | First menuitem | +| Mouse | LongPress | Open | MouseDown with delay, equivalent to right click for touch devices | First menuitem | +| Mouse | Click | Open | Right click for contextual menus | First menuitem | +| Keyboard | Enter | Open | Focus on trigger element and press Enter | First menuitem | +| Keyboard | Space | Open | Focus on trigger element and press Space | First menuitem | +| Keyboard | Shift+F10 | Open | Focus on trigger element to open context menu (i.e. right click) | First menuitem | +| Keyboard | ArrowDown | Open | Focus on trigger element. Used in menu buttons | First menuitem | +| Keyboard | ArrowUp | Open | Focus on trigger element. Used in menu buttons | Last menuitem | +| Mouse | Click | Dismiss | Click anywhere outside the component | menu trigger | +| Mouse | Click | Dismiss | Click on the trigger while the menu is open | menu trigger | +| Mouse | Click | Dismiss | Click on a menu item | User defined - default menu trigger | +| Mouse | MouseLeave | Dismiss | Mouse leaves the component after a delay | menu trigger | +| Keyboard | Enter | Dismiss | Invoked on a menu item | User defined - default menu trigger | +| Keyboard | Space | Dismiss | Invoked on a menu item | User defined - default menu trigger | +| Keyboard | Esc | Dismiss | Closes the menu | menu trigger | +| Keyboard | Tab | Dismiss | Closes the menu and all submenus | next tabbable element after menu trigger | +| Keyboard | Shift+Tab | Dismiss | Closes the menu and all submenus | previous tabbable element before menu trigger | + +### Submenu trigger/navigation + +A submenu can be triggered by the following user interactions on the triggering menu item. Not all interactions should be supported at the same time, but the component must be able to support combinations of the below interactions. + +As a general rule, once a submenu is dismissed without dismissing the menu, the focus should revert to the triggering menu item unless the interaction involves another focusable UI component. + +| Type | Action | Result | Details | Focus after | +| -------- | ---------- | ------- | ---------------------------------------------------------------- | -------------------------------------------------- | +| Mouse | Click | Open | Click the menu item that contains a submenu | First menuitem in submenu | +| Mouse | Hover | Open | Hover over the menu item that contains a submenu with delay | First menuitem in submenu | +| Keyboard | Enter | Open | Focus on triggering menu item | First menuitem in submenu | +| Keyboard | Space | Open | Focus on triggering menu item | Frist menuitem in submenu | +| Keyboard | ArrowRight | Open | Focus on triggering menu item | First menuitem in submenu | +| Mouse | Click | Dismiss | Click on an item in the submenu | | +| Keyboard | Space | Dismiss | Invoked on a submenu item | | +| Keyboard | Space | Dismiss | Invoked on a submenu item | | +| Mouse | Click | Dismiss | Click on a UI element that is not the submenu | Root menu trigger | +| Mouse | MouseLeave | Dismiss | Mouse leaves the submenu or its triggering menu item after delay | Root menu trigger | +| Keyboard | ArrowLeft | Dismiss | Closes the submenu | menu item that contained submenu | +| Keyboard | Esc | Dismiss | Closes the submenu | menu item that contained submenu | +| Keyboard | Tab | Dismiss | Closes the menu and all submenus | Next tabbable element after root menu trigger | +| Keyboard | Shift+Tab | Dismiss | Closes the menu and all submenus | Previous tabbable element before root menu trigger | + +### Split button MenuItem submenu + +All of the above Mouse events in the [previous section](#submenu-trigger/navigation) should apply to the part of the split button that is intended to open a submenu. + +``` +Keyboard interaction for the split button menu item WIP and requires input from a11y champs +``` + +Once the the submenu is open, the same behavior as in the [previous section](#submenu-trigger/navigation) apply + +### Menu keyboard navigation + +Keyboard interactions required to navigate the menu. The alphanumeric match interaction does not need to be supported in all cases, but should be supported as much as possible. + +| Type | Action | Result | Details | Focus after | +| -------- | --------- | ----------------- | --------------------------------------------------------------------- | ------------------------------------------ | +| Keyboard | ArrowDown | Next Item | Roving | Next item, if on last item then first | +| Keyboard | ArrowUp | Previous Item | Roving | Previous item, if on first item go to last | +| Keyboard | Home | First item | | First item | +| Keyboard | End | Last item | | Last item | +| Keyboard | A-Z, 0-9 | Matched item | Matches the first item that corresponds alphabetically or numerically | Matched item | +| Mouse | Hover | Reveals scrollbar | If required, reveals scrollbar after delay | Keeps focus on existing item | ### MenuItem selection | + +Below are the interactions that should be supported for all menu items that are required to handle a selection state. + +In the event that the selection method is a radio, the previous selected item must be unselected. + +| Type | Action | Result | Details | +| -------- | ------ | ------ | -------------------------------------------- | +| Keyboard | Space | Toggle | Toggle the selection status of the menu item | +| Keyboard | Enter | Toggle | Toggle the selection status of the menu item | +| Mouse | Click | Toggle | Toggle the selection status of the menu item | + +### Positioning + +### Placement + alignment + +A menu can be placed and aligned in any of the configurations allowed by current v0 and v7: + +- Before or after anchor element +- Above or below anchor element +- Aligned at top/bottom/left/right edge of anchor element +- Aligned centered to the anchor element + +The above should result in 12 possible position hints in total + +#### Flip + +A menu should be positioned so that it will flip its positioning on a given axis if the boundary (e.g. viewport) gets to small that it might overflow + +#### Nudging + +A menu should be positioned so that if its boundary (e.g. viewport) might overflow, the placement of the popup should be 'nudged' closer into the boundary + +#### Anchor placement offset + +A menu should be positioned so that the distance with respect to the anchor element should be configurable on both axes. + +#### Inline vs portal rendering + +A menu should be positioned so that it can be rendered either out of order on the DOM (e.g. portal to body) or inline in DOM order. + +#### Submenu positioning + +The default positioning for a submenu should be the standard seen in both v7 and v0. Submenu should be placed after the menu item trigger and aligned with the top edge. + +Although this should not be recommended, for the purposes of compatibility with v7, all positioning aspects should be configurable for submenus. + +## Accessibiltiy + +Accessibility behaviour is built into the spec as much as possible. This section addresses specific issues that don't fit well with the standard definition of the component. + +## Migration + +The immediate candidates for adoption for a converged `Menu` component which are hinted at the beginning of the spec are: + +- [ContextualMenu](https://developer.microsoft.com/en-us/fluentui#/controls/web/contextualmenu) for v7 +- [Menu](https://fluentsite.z22.web.core.windows.net/0.52.0/components/menu/definition) for v0 + +This component has characteristics that should probably be considered for the following components in terms of future migrations: + +- [Toolbar](https://fluentsite.z22.web.core.windows.net/0.52.0/components/toolbar/definition) in v0 which shares a menu component. The component itself also should use similar behaviour and interactions to `Menu` +- [Dropdown](https://fluentsite.z22.web.core.windows.net/0.52.0/components/dropdown/definition) in v0 contains a menu. Dropdown semantics are different to that of standard menus in accessibility, but certain behaviours such as keyboard navigation and selection can be reused for such a component +- [CommandBar](https://developer.microsoft.com/en-us/fluentui#/controls/web/commandbar) in v7 also contains a menu subcomponent as well as behaviour similar to `Menu` semantics +- [Nav](https://developer.microsoft.com/en-us/fluentui#/controls/web/nav) in v7 could reuse certain behaviours in `Menu` such as keyboard navigation, but will use different DOM and aria semantics, this could be achieved through state hook variants or composition +- [OverflowSet](https://developer.microsoft.com/en-us/fluentui#/controls/web/overflowset) in v7 contains a submenu component +- [PivotSet](https://developer.microsoft.com/en-us/fluentui#/controls/web/pivot) could be considered as a component variant of `Menu` +- [Breadcrumb](https://developer.microsoft.com/en-us/fluentui#/controls/web/breadcrumb) in v7 contains a submenu component and could also be considered as a component variant of `Menu + +### Creating sections or groups within a menu + +⚠️ When using [MenuDivider](#menudivider) without [MenuGroup](#menugroup) + +The [MenuDivider](#menudivider) is a purely visual component. The component is only intended to be used as visual 'sugar'. When meaningful partitions [MenuItems](#menuitem) exists, [MenuGroup](#menugroup) should be used to provide the correct experience for narration. + +⚠️ When using [MenuSectionHeader](#menudivider) -- _Component States_ -- _Interaction_ - - _Keyboard_ - - _Cursor_ - - _Touch_ - - _Screen readers_ +[MenuGroup](#menugroup) as a parent component ensures that correct `aria-labelledby` relationship is defined between the header and the group. -## Accessibility +### Focus management -Base accessibility information is included in the design document. After the spec is filled and review, outcomes from it need to be communicated to design and incorporated in the design document. +### Disabled menu items -- Decide whether to use **native element** or folow **ARIA** and provide reasons -- Identify the **[ARIA](https://www.w3.org/TR/wai-aria-practices-1.2/) pattern** and, if the component is listed there, follow its specification as possible. -- Identify accessibility **variants**, the `role` ([ARIA roles](https://www.w3.org/TR/wai-aria-1.1/#role_definitions)) of the component, its `slots` and `aria-*` props. -- Describe the **keyboard navigation**: Tab Oder and Arrow Key Navigation. Describe any other keyboard **shortcuts** used -- Specify texts for **state change announcements** - [ARIA live regions - ](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) (number of available items in dropdown, error messages, confirmations, ...) -- Identify UI parts that appear on **hover or focus** and specify keyboard and screen reader interaction with them -- List cases when **focus** needs to be **trapped** in sections of the UI (for dialogs and popups or for hierarchical navigation) -- List cases when **focus** needs to be **moved programatically** (if parts of the UI are appearing/disappearing or other cases) +Disabled menu items should be focusable diff --git a/specs/Menu.md b/specs/Menu.md deleted file mode 100644 index 58c40d4d0a894a..00000000000000 --- a/specs/Menu.md +++ /dev/null @@ -1,1085 +0,0 @@ -# Menu - -## Background - -### Definition - -This spec defines the default function of a `Menu` as an interactive component that displays a list of options that can be represented by a range possible states. Possible variants are defined in [the relevant section](#variants) - -The `Menu` should be displayed on a temporary surface that interrupts the normal flow of content. The temporary surface should be triggered by an external user action such as (but not limited to) a click on a button or other UI control. - -The interactions that result in the dismiss/removal of the `Menu` component should be configurable. - -## Prior art - -As a part of the spec definitions in Fluent UI, a research effort has been made through [Open UI](https://open-ui.org/). The current research proposal is available as an open source contribution undergoing review ([research proposal](https://github.com/WICG/open-ui/pull/249)) - -## Comparison of `@fluentui/react` and `@fluentui/react-northstar` - -- All mentions of v7 or v8 == `@fluentui/react` ([docsite](https://developer.microsoft.com/en-us/fluentui#/)) -- All mentions of v0 == `@fluentui/react-northstar` ([docsite](https://fluentsite.z22.web.core.windows.net/)) - -The most relevant comparison that can be achieved between the two libraries is between `ContextualMenu` in v7 and a combination of `Menu`, `Popup` and `ToolbarItem` in v0. - -v0 suffers from a consistency issue that the control used in `Menu` and the menu variant of `ToolbarItem` are not actually the same component and have different behavior. However, semantically for the purposes of this spec, they representthe same control that will be implemented. - -Note that the below code samples are not meant to be complete, but to highlight differences between the two libraries. Please refer to official docsites for actual API references. - -### Positioning - -`ContextualMenu` in v7 is a component that also exposes the API to control the positioning of the temporary popup surface that the Menu is rendered on. This aspect of the v7 component should be compared with the `Popup` component in v0 since the v0 `Menu` is created as a standalone component with no positioning properties. - -v0 uses the [OSS Popper.js library](https://popper.js.org/) while v7 uses a component based implementation `CalloutContent`. As a result, the API is very similar in intent and vocabulary as Popper. - -Below we provide the results of testing common positioning boundary/edge cases between the two. - -#### Configuration consistency - -The biggest difference bewteen the two libraries is that v0 provides a purely positioning based API out of the `Popup` react component. v0 Provides no direct prop values that will style the popup container and any adjustments to stlying properties such as (but not limited to) dimensions/margin/padding/layoud are expected to be implmented through the styling system used throughout the library - -The `Callout` component has some styling helpers that affect the styling of the contents: - -- calloutMaxHeight -- calloutMaxWidth -- calloutWidth -- backgroundColor - -The `ContextualMenu` component uses two styling properties not offered by `Callout` (useTargetWidth, useTargetMinWidth) and also duplicates some of `Callout's` own positioning properties while also allowing a shorthand slot for the `Callout` - -```typescript - -``` - -The result being that `ContextualMenu's` positiong and styling risks being abused by developers inexperienced in the library. There is also no documentation on the v7 docsite that states `calloutProps` is actually overriden by props declared directly on `ContextualMenu` - -### Position/Alignment hints - -Both libraries provide an API that achieves the same end result for positioning and alignment. Below is a table that maps the v7 `DirectionalHint` with the v0 props of `position` and `alignment` - -`DirectionalHint` can be passed to both `Callout` (which powers positioning) or directly to `ContextualMenu` (in two different ways). Whereas `position` and `alignment` are props of `Popup` in v0 and not used directly in `Menu` even for the positioning of its submenu. - -| DirectionalHint (v7) | position (v0) | align (v0) | -| -------------------- | ------------- | ---------- | -| topLeftEdge | above | start | -| topCenter | above | center | -| topRightEdge | above | bottom | -| topAutoEdge | above | | -| bottomLeftEdge | above | start | -| bottomCenter | below | center | -| bottomRightEdge | below | bottom | -| bottomAutoEdge | below | | -| leftTopEdge | before | top | -| leftCenter | before | center | -| leftBottomEdge | before | bottom | -| rightTopEdge | after | before | -| rightCenter | after | center | -| rightBottomEdge | after | bottom | - -First it's necessary to note the difference between the vocabulary used between the two. v7 will use `left` and `right` while v0 uses `before` and `after`. v0 vocabulary here is chosen to convey the appropriate meaning regardless of RTL by using the semantics of the conntent. It's also interesting to note that it's possible to supply an explicit RTL hint to v7 which is a flip by default. v0 will flip by default but requires the consumer to detect RTL scenarios and modify props in these situations - -In general the separation of both the position and alignment in v0 results in an API that is easier to use if a consumer only needs to modify one of the two props. However both try to achieve the same result in the end. - -It's important to note that if an incorrect pair of `position` and `align` are provided in v0, then `position` takes priority and `align` is set to `center` - -#### Offset - -```typescript - - - - -// offset can also be a function of raw Popper properties -const offsetFunction = ({ - popper: PopperJs.Rect; - reference: PopperJs.Rect; - placement: PopperJs.Placement; -}) => ([popper.width, -popper.height]) -``` - -v7 positioning can only apply a numerical value to the first part position attribute of `DirectionalHint`. v0 uses a much more flexible API that not only supports a function to defer calculation at runtime, but also supports the offset of the `Popup` in both axes. - -#### Bounds and overflow - -v0 `Popup` API is more consistent in this aspect and provides more control than the v7 `Callout`. - -```typescript - -``` - -`Popup` provider 3 different properties to handle bounds and overflow: - -- flipBoundary - the bounds to calculate when to flip positioning of the popup -- overflowBoundary - the bounds to shift the popup without overflowing -- mountNode - where the popup is actually rendered in the DOM, by default this is a portal to a div in body - -```typescript - ({/*Same object as above*/})} - target={htmlElement} - - // renders to a portal node on body - layerProps={/*ILayerProps*/} - - // every single one of the above can all be declared here too - calloutProps={{bounds, target}} -/> -``` - -`ContextualMenu` or `Callout` has no notion of separate boundaries for flip or overflow, and auto behaviour is used for flip and overflow 'pushing' -It should also be noted that `ContextualMenu` allows all of the same props to be passed to its submenus to custom tweak position for each submenu if necessary - -#### Submenu positioning - -The default positioning for both v0 and v7 submenus is: - -- rightTopEdge (v7) -- top-after (v0) - -Both will also flip appropriately when the overflow boundary is too small. - -The main difference between the two is that v0 submenu's position does not expose any way to customize or override the positioning of the submenu. However v7 allows every single customization as the root menu. It is very possible to do the below: - -```typescript -const menuItems: IContextualMenuItem[] = [ - { - key: 'newItem', - subMenuProps: { - // Any positioning props of `ContextualMenu` are usable - directionalHint: DirectionalHint.rightTopEdge, - // All `Callout` props as also usable - calloutProps: {...}, - items: [...], - }, - }, -``` - -#### Events - -v7 provides the following positioning event callbacks that might be used and should probably be supported for backwards compatibility: - -- onLayerMounted -- onPositioned -- onScroll - -### Trigger vs target - -The v7 `ContextualMenu` has a prop `target` which is intended to be a ref to the DOM element that the positioning logic anchors to. The usage of this prop requires the visibility state of the component to be controlled using React state by the consumer. The same prop exists on the v0 `Popup` component that is intended to perform the same function. - -```typescript -const buttonRef = React.useRef( - -
  • - -
  • - - - - -``` - -### Menu divider - -```html - - - - - -``` - -### Custom rendering and data - -v7 provides render callbacks that can be used to render either the entire menu list or specific slots of menut items. Each call back provides the props avaialble to that slot and a `defaultRender` which allows to easily extend the original render, if required. - -```typescript -// v7 custom rendering -const menuProps = { - onRenderMenuList: (props: IContextualMenuListProps, defaultRenderer) => {}, - onRenderSubMenu: (props: IContextualMenuProps, defaultRenderer) => {}, - items: [ - { - onRender: ( - item: any, - dismissMenu: (ev?: any, dismissAll?: boolean) => void - ) => React.ReactNode - } - {onRenderContent: (props: IContextualMenuItemProps, defaultRenderer) => {}}, - {onRenderIcon: (props: IContextualMenuItemProps, defaultRenderer) => {}}, - ] -} - - -``` - -Custom data can also be associated with menu items - -```typescript -const menuProps = { - items: [{ - ... - data: { foo: "bar" } - }] -} -``` - -v0 custom rendering through shorthand components is a consistent experience through all shorthand components, but provide a smaller API surface (whether this is simpler or less powerful can be subjective). Custom rendering in the case of the `Menu` component would be done through the use of `children` prop either through the standard React child component API or through shorthand as a callback function. - -```typescript -// v0 shorthand children render callback -const items = [ - { - key: 'editorials', - children: (El, props) => {props.key} - }, -] - - - -// v0 children API custom render - - - Editorials - - - CustomContent - - {/*Not recommended but definitely possible*/} -
    custom item
    -
    -``` - -## Variants - -### Nested menus - -A `Menu` should be able to trigger an additional instance of itself as a part of one or more of its options. The nested `Menu` component should have the same functional capabilities as the root `Menu` component. - -The actions that trigger the the nested `Menu` should be be consistent with the actions that can trigger any root `Menu` from a similar UI control. - -We advise that no more than two nested `Menu` components be used, but this spec does not functionally apply that constrain to the implementation of the `Menu` component. - -### Selection state - -A `Menu` should be able to track and represent the selection state of all or some of its options if required. - -When an options is associated with a selection state. The `Menu`, either root or nested, should control its dismiss behavior accordingly based on configuration. - -### Sections - -A `Menu` can be partitioned into sections using visible dividers in its list of options. Each section can contain a heading title that announces or briefly describes the options in the particular section - -### Secondary label - -An option of a `Menu` component should be able to declare additional secondary label that can provide additional context describing the option or its usage. - -For example a secondary label can be a label that shows a keyboard shortcut that will perform an equivalent action of the option of the `Menu` component. - -### Split option with nesting - -An option of a `Menu` component can trigger a nested `Menu` component and also perform its default action by splitting the option into two interactable areas that handle each action separately. - -### Disabled option(s) - -All options in a `Menu` component can be disabled and should provide a visible indication for this purpose. User interaction should be defined for disabled options - -### Scrollable - -A `Menu` should display a vertical scrollbar when the number of options exceeds the height of the component - -### Standalone/No surface - -A `Menu` can be used without the temporary popup surface and its trigger. This will allow `Menu` components to be permanent page content or used in custom surfaces with a wider range of UI components. - -### Custom content - -``` -This variant is still a work in progress and needs additional thought -``` - -Any custom content can be used in the rendering of the Menu, all interactions and accessibility is left to the discretion of the consumer. - -## API - -The `Menu` should implement a `children` based API as is the standard across all the surveyed alternatives as a part of Open UI research in [Prior Art](#prior-art). The component will leverage the use of `context` in the interaction and data flows of child components. - -Sample usages will be give in the following section of this document [Sample code](#sample-code) - -### Menu - -The root level component serves as a simplified interface (sugar) for popup positioning and triggering. - -### MenuTrigger - -A non-visual component that wraps its child and configures them to be the trigger that will open a menu. This component should only accept one child - -### MenuList - -This component is used internally by `Menu` and manages the context and layout its items. - -`MenuList` can also be used separately as the standalone variant of the `Menu`, since it should not control popup positioning or triggers. It is the only component in the API that can be used standalone. Envisioned to be used with more complex popup or trigger scenarios where the `Menu` component does not provide enough control for these situations. - -### MenuGroup - -Creates a group inside a `MenuList`, setting up header layout and dividers between `MenuItems`. - -The MenuGroup is also a useful component to declare different selection groups (checkbox/radio) in a `MenuList`. - -| Prop name | Type | Details | -| --------- | ---- | --------------------------------------------------------------------------- | -| title | text | The title of of the section renders a [MenuGroupHeader](#menusectionheader) | - -### MenuGroupHeader - -Creates a section header element with appropriate styling. Will set correct `aria-labelledby` relationship if it is instantiated within a [MenuGroup](#menugroup) - -### MenuDivider - -Creates a divider element in the `MenuList` with correct HTML and aria semantics for divider. - -This divider is purely a visual cue. To ensure consistent narration experience across all screenreaders [MenuGroup](#menugroup) should be used - -### MenuItem - -As the name infers - -| Prop name | Type | Details | -| -------------- | --------- | ----------------------------------------- | -| icon | ReactNode | Icon that is rendered with the menu item | -| secondaryLabel | text | A secondary label i.e. keyboard shortcuts | - -### MenuItemCheckbox - -A variant of `MenuItem` that allows a multiple selection state based on the value that it represents - -| Prop name | Type | Details | -| --------- | ---- | -------------------------------------------------- | -| name | text | The name of the value that the checkbox represents | -| value | text | The value of the checkbox | - -### MenuItemRadio - -A variant of `MenuItem` that allows a single selection state based on the value that it represents - -| Prop name | Type | Details | -| --------- | ---- | -------------------------------------------------- | -| name | text | The name of the value that the checkbox represents | -| value | text | The value of the checkbox | - -## Sample code - -The below samples do not represent the definitive props of the final implemented component, but represent the ideal final implementations. Can be subject to change during the implementation phase. - -### Basic Menu - -```typescript -const menu = ( - - - - Option 1 - Option 2 - Option 3 - - -) -``` - -```html - - -
    -
    Option 1
    -
    Option 2
    -
    Option 3
    -
    -``` - -### Menu items with icons - -```typescript -const menu = ( - - - - }>Option 1 - }>Option 2 - }>Option 3 - - -) -``` - -```html - - -
    -
    - FileIcon - Option 1 -
    -
    - BellIcon - Option 2 -
    -
    - LinkIcon - Option 3 -
    -
    -``` - -### Sections - -```typescript -const menu = ( - - - - Option 1 - - - Section Option 1 - Section Option 2 - Section Option 3 - - - -) -``` - -```html - - - -
    -
    Option 1
    - -
    - -
    Section Option 1
    -
    Section Option 2
    -
    Section Option 3
    -
    -
    -
    -``` - -Custom section headings can also be used, but must be used within a [MenuGroup](#menugroup) to ensure correct narration experience - -```typescript - -const menu = ( - - - - Option 1 - - - {children} - Section Option 1 - Section Option 2 - Section Option 3 - - - -) -``` - -```html - - - -
    -
    Option 1
    - -
    - -
    Section Option 1
    -
    Section Option 2
    -
    Section Option 3
    -
    -
    -
    -``` - -### Submenus - -```typescript -const menu = ( - - - - Option 1 - - - Open submenu - - - Option 1 - Option 2 - Option 3 - - - - -) -``` - -```html - - -
    -
    Option 1
    - -
    - - -
    -
    Option 1
    -
    Option 2
    -
    Option 3
    -
    -``` - -### Standlone - -```typescript -const [open] = React.useState(false); - -const menu = ( - - - Option 1 - Option 2 - Option 3 - - -) -``` - -```html - -
    -
    Option 1
    -
    Option 2
    -
    Option 3
    -
    -``` - -### Selection - -```typescript -const trigger = -const [selectedItems, setSelectedItems] = React.useState([]); - -// basic checkbox example -const menuCheckbox = ( - - - - Option 1 - Option 2 - Option 3 - - -) - -// leverage MenuGroup for different selection groups -const menuSelectableSections = ( - - - - - Option 1 - Option 2 - Option 3 - - - Option 1 - Option 2 - Option 3 - - - -) -``` - -```html - - - -
    -
    Option 1
    -
    Option 2
    -
    Option 3
    -
    - - -
    -
    - -
    Option 1
    -
    Option 2
    -
    Option 3
    -
    -
    -
    - -
    Option 1
    -
    Option 2
    -
    Option 3
    -
    -
    -``` - -### Split button - -```typescript -const trigger = - -// basic checkbox example -const menuSplitbutton= ( - - - - Option 1 - - - - - Option 1 - Option 2 - Option 3 - - - -) -``` - -```html -
    -
    Option 1
    - -
    - - -
    -
    Option 1
    -
    -
    content slot
    - -
    -
    - - -
    -
    Option 1
    -
    Option 2
    -
    Option 3
    -
    -``` - -## Behaviors - -### Useful references - -The below references were used to decide n appropriate keyboard interactions from an a11y perspective. - -- https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-1/menubar-1.html# -- https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-links.html -- https://www.w3.org/WAI/tutorials/menus/application-menus/ - -### Menu open/dismiss - -A menu can be triggered by the following user interactions on the triggering/anchor element. Not all interactions should be supported at the same time, but the component must be able to support combinations of the below interactions. - -As a general rule, once the menu is closed the focus should return to the triggering element once the menu is closed unless the interaction would involve another focusable element. - -| Type | Action | Result | Details | Focus after | -| -------- | ---------- | ------- | ----------------------------------------------------------------- | --------------------------------------------- | -| Mouse | Click | Open | Click on the trigger element | First menuitem | -| Mouse | Hover | Open | Hover over the trigger element with delay | First menuitem | -| Mouse | LongPress | Open | MouseDown with delay, equivalent to right click for touch devices | First menuitem | -| Mouse | Click | Open | Right click for contextual menus | First menuitem | -| Keyboard | Enter | Open | Focus on trigger element and press Enter | First menuitem | -| Keyboard | Space | Open | Focus on trigger element and press Space | First menuitem | -| Keyboard | Shift+F10 | Open | Focus on trigger element to open context menu (i.e. right click) | First menuitem | -| Keyboard | ArrowDown | Open | Focus on trigger element. Used in menu buttons | First menuitem | -| Keyboard | ArrowUp | Open | Focus on trigger element. Used in menu buttons | Last menuitem | -| Mouse | Click | Dismiss | Click anywhere outside the component | menu trigger | -| Mouse | Click | Dismiss | Click on the trigger while the menu is open | menu trigger | -| Mouse | Click | Dismiss | Click on a menu item | User defined - default menu trigger | -| Mouse | MouseLeave | Dismiss | Mouse leaves the component after a delay | menu trigger | -| Keyboard | Enter | Dismiss | Invoked on a menu item | User defined - default menu trigger | -| Keyboard | Space | Dismiss | Invoked on a menu item | User defined - default menu trigger | -| Keyboard | Esc | Dismiss | Closes the menu | menu trigger | -| Keyboard | Tab | Dismiss | Closes the menu and all submenus | next tabbable element after menu trigger | -| Keyboard | Shift+Tab | Dismiss | Closes the menu and all submenus | previous tabbable element before menu trigger | - -### Submenu trigger/navigation - -A submenu can be triggered by the following user interactions on the triggering menu item. Not all interactions should be supported at the same time, but the component must be able to support combinations of the below interactions. - -As a general rule, once a submenu is dismissed without dismissing the menu, the focus should revert to the triggering menu item unless the interaction involves another focusable UI component. - -| Type | Action | Result | Details | Focus after | -| -------- | ---------- | ------- | ---------------------------------------------------------------- | -------------------------------------------------- | -| Mouse | Click | Open | Click the menu item that contains a submenu | First menuitem in submenu | -| Mouse | Hover | Open | Hover over the menu item that contains a submenu with delay | First menuitem in submenu | -| Keyboard | Enter | Open | Focus on triggering menu item | First menuitem in submenu | -| Keyboard | Space | Open | Focus on triggering menu item | Frist menuitem in submenu | -| Keyboard | ArrowRight | Open | Focus on triggering menu item | First menuitem in submenu | -| Mouse | Click | Dismiss | Click on an item in the submenu | | -| Keyboard | Space | Dismiss | Invoked on a submenu item | | -| Keyboard | Space | Dismiss | Invoked on a submenu item | | -| Mouse | Click | Dismiss | Click on a UI element that is not the submenu | Root menu trigger | -| Mouse | MouseLeave | Dismiss | Mouse leaves the submenu or its triggering menu item after delay | Root menu trigger | -| Keyboard | ArrowLeft | Dismiss | Closes the submenu | menu item that contained submenu | -| Keyboard | Esc | Dismiss | Closes the submenu | menu item that contained submenu | -| Keyboard | Tab | Dismiss | Closes the menu and all submenus | Next tabbable element after root menu trigger | -| Keyboard | Shift+Tab | Dismiss | Closes the menu and all submenus | Previous tabbable element before root menu trigger | - -### Split button MenuItem submenu - -All of the above Mouse events in the [previous section](#submenu-trigger/navigation) should apply to the part of the split button that is intended to open a submenu. - -``` -Keyboard interaction for the split button menu item WIP and requires input from a11y champs -``` - -Once the the submenu is open, the same behavior as in the [previous section](#submenu-trigger/navigation) apply - -### Menu keyboard navigation - -Keyboard interactions required to navigate the menu. The alphanumeric match interaction does not need to be supported in all cases, but should be supported as much as possible. - -| Type | Action | Result | Details | Focus after | -| -------- | --------- | ----------------- | --------------------------------------------------------------------- | ------------------------------------------ | -| Keyboard | ArrowDown | Next Item | Roving | Next item, if on last item then first | -| Keyboard | ArrowUp | Previous Item | Roving | Previous item, if on first item go to last | -| Keyboard | Home | First item | | First item | -| Keyboard | End | Last item | | Last item | -| Keyboard | A-Z, 0-9 | Matched item | Matches the first item that corresponds alphabetically or numerically | Matched item | -| Mouse | Hover | Reveals scrollbar | If required, reveals scrollbar after delay | Keeps focus on existing item | ### MenuItem selection | - -Below are the interactions that should be supported for all menu items that are required to handle a selection state. - -In the event that the selection method is a radio, the previous selected item must be unselected. - -| Type | Action | Result | Details | -| -------- | ------ | ------ | -------------------------------------------- | -| Keyboard | Space | Toggle | Toggle the selection status of the menu item | -| Keyboard | Enter | Toggle | Toggle the selection status of the menu item | -| Mouse | Click | Toggle | Toggle the selection status of the menu item | - -### Positioning - -### Placement + alignment - -A menu can be placed and aligned in any of the configurations allowed by current v0 and v7: - -- Before or after anchor element -- Above or below anchor element -- Aligned at top/bottom/left/right edge of anchor element -- Aligned centered to the anchor element - -The above should result in 12 possible position hints in total - -#### Flip - -A menu should be positioned so that it will flip its positioning on a given axis if the boundary (e.g. viewport) gets to small that it might overflow - -#### Nudging - -A menu should be positioned so that if its boundary (e.g. viewport) might overflow, the placement of the popup should be 'nudged' closer into the boundary - -#### Anchor placement offset - -A menu should be positioned so that the distance with respect to the anchor element should be configurable on both axes. - -#### Inline vs portal rendering - -A menu should be positioned so that it can be rendered either out of order on the DOM (e.g. portal to body) or inline in DOM order. - -#### Submenu positioning - -The default positioning for a submenu should be the standard seen in both v7 and v0. Submenu should be placed after the menu item trigger and aligned with the top edge. - -Although this should not be recommended, for the purposes of compatibility with v7, all positioning aspects should be configurable for submenus. - -## Accessibiltiy - -Accessibility behaviour is built into the spec as much as possible. This section addresses specific issues that don't fit well with the standard definition of the component. - -## Migration - -The immediate candidates for adoption for a converged `Menu` component which are hinted at the beginning of the spec are: - -- [ContextualMenu](https://developer.microsoft.com/en-us/fluentui#/controls/web/contextualmenu) for v7 -- [Menu](https://fluentsite.z22.web.core.windows.net/0.52.0/components/menu/definition) for v0 - -This component has characteristics that should probably be considered for the following components in terms of future migrations: - -- [Toolbar](https://fluentsite.z22.web.core.windows.net/0.52.0/components/toolbar/definition) in v0 which shares a menu component. The component itself also should use similar behaviour and interactions to `Menu` -- [Dropdown](https://fluentsite.z22.web.core.windows.net/0.52.0/components/dropdown/definition) in v0 contains a menu. Dropdown semantics are different to that of standard menus in accessibility, but certain behaviours such as keyboard navigation and selection can be reused for such a component -- [CommandBar](https://developer.microsoft.com/en-us/fluentui#/controls/web/commandbar) in v7 also contains a menu subcomponent as well as behaviour similar to `Menu` semantics -- [Nav](https://developer.microsoft.com/en-us/fluentui#/controls/web/nav) in v7 could reuse certain behaviours in `Menu` such as keyboard navigation, but will use different DOM and aria semantics, this could be achieved through state hook variants or composition -- [OverflowSet](https://developer.microsoft.com/en-us/fluentui#/controls/web/overflowset) in v7 contains a submenu component -- [PivotSet](https://developer.microsoft.com/en-us/fluentui#/controls/web/pivot) could be considered as a component variant of `Menu` -- [Breadcrumb](https://developer.microsoft.com/en-us/fluentui#/controls/web/breadcrumb) in v7 contains a submenu component and could also be considered as a component variant of `Menu - -### Creating sections or groups within a menu - -⚠️ When using [MenuDivider](#menudivider) without [MenuGroup](#menugroup) - -The [MenuDivider](#menudivider) is a purely visual component. The component is only intended to be used as visual 'sugar'. When meaningful partitions [MenuItems](#menuitem) exists, [MenuGroup](#menugroup) should be used to provide the correct experience for narration. - -⚠️ When using [MenuSectionHeader](#menudivider) - -[MenuGroup](#menugroup) as a parent component ensures that correct `aria-labelledby` relationship is defined between the header and the group. - -### Focus management - -### Disabled menu items - -Disabled menu items should be focusable From 3670150ee130d5d939232f320380a7065f0508e0 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 5 Feb 2021 17:29:38 +0000 Subject: [PATCH 05/74] remove unnecessary --- packages/react-menu/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/react-menu/README.md b/packages/react-menu/README.md index cb8606156b9000..2f39caf6a7b178 100644 --- a/packages/react-menu/README.md +++ b/packages/react-menu/README.md @@ -5,7 +5,3 @@ These are not production-ready components and **should never be used in product**. This space is useful for testing new components whose APIs might change before final release. To import React Menu components: - -```js -import { ComponentName } from '@fluentui/react-menu'; -``` From 7d593977b50dceb654a21e04a184280bc9310a7f Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 5 Feb 2021 17:31:09 +0000 Subject: [PATCH 06/74] modified codeowners --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 527538f8d04016..9bb5f7e5403efa 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -85,6 +85,7 @@ common/_themeOverrides.scss @phkuo common/_common.scss @phkuo ## Component packages +packages/react-menu/ @ling1726 packages/react-button/ @dzearing @khmakoto packages/react-cards/ @khmakoto packages/react-checkbox/ @khmakoto @xugao From ce9bc5ebd76359d429e043ce1e30c1588a6089dd Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 5 Feb 2021 17:31:40 +0000 Subject: [PATCH 07/74] update codeowners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9bb5f7e5403efa..cfad0c1c7dd05d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -85,7 +85,7 @@ common/_themeOverrides.scss @phkuo common/_common.scss @phkuo ## Component packages -packages/react-menu/ @ling1726 +packages/react-menu/ @ling1726 @layershifter packages/react-button/ @dzearing @khmakoto packages/react-cards/ @khmakoto packages/react-checkbox/ @khmakoto @xugao From b2b8da75eca18fd46079482007c57c75c77bf4f1 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 5 Feb 2021 17:47:04 +0000 Subject: [PATCH 08/74] modify codeowners --- .github/CODEOWNERS | 2 +- packages/react-menu/etc/react-menu.api.md | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9bb5f7e5403efa..cfad0c1c7dd05d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -85,7 +85,7 @@ common/_themeOverrides.scss @phkuo common/_common.scss @phkuo ## Component packages -packages/react-menu/ @ling1726 +packages/react-menu/ @ling1726 @layershifter packages/react-button/ @dzearing @khmakoto packages/react-cards/ @khmakoto packages/react-checkbox/ @khmakoto @xugao diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index e69de29bb2d1d6..e17e7d7b8d515d 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -0,0 +1,10 @@ +## API Report File for "@fluentui/react-menu" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + + +// (No @packageDocumentation comment for this package) + +``` From 8cf1b35e54995a21dd1d3e2c7afe0d9e0047d74d Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 5 Feb 2021 17:47:30 +0000 Subject: [PATCH 09/74] Change files --- ...eact-examples-0c82dba8-1bb5-4aa9-af7d-c097009eb61f.json | 7 +++++++ ...ui-react-menu-f2959489-3302-4752-9147-01dd8d9daab5.json | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 change/@fluentui-react-examples-0c82dba8-1bb5-4aa9-af7d-c097009eb61f.json create mode 100644 change/@fluentui-react-menu-f2959489-3302-4752-9147-01dd8d9daab5.json diff --git a/change/@fluentui-react-examples-0c82dba8-1bb5-4aa9-af7d-c097009eb61f.json b/change/@fluentui-react-examples-0c82dba8-1bb5-4aa9-af7d-c097009eb61f.json new file mode 100644 index 00000000000000..f821925793d018 --- /dev/null +++ b/change/@fluentui-react-examples-0c82dba8-1bb5-4aa9-af7d-c097009eb61f.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Auto-generate react-menu", + "packageName": "@fluentui/react-examples", + "email": "lingfan.gao@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-menu-f2959489-3302-4752-9147-01dd8d9daab5.json b/change/@fluentui-react-menu-f2959489-3302-4752-9147-01dd8d9daab5.json new file mode 100644 index 00000000000000..ea7af3ae00f50b --- /dev/null +++ b/change/@fluentui-react-menu-f2959489-3302-4752-9147-01dd8d9daab5.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "Auto-generate react-menu", + "packageName": "@fluentui/react-menu", + "email": "lingfan.gao@microsoft.com", + "dependentChangeType": "none" +} From 5b80fc562694e56dae936d5a950d71b849e6e70e Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 09:23:17 +0000 Subject: [PATCH 10/74] add react-hooks dep --- .../react-menu/MenuList/MenuList.stories.tsx | 51 +++++++++++ packages/react-menu/.npmrc | 2 + packages/react-menu/README.md | 4 + packages/react-menu/etc/react-menu.api.md | 51 +++++++++++ packages/react-menu/package.json | 17 ++-- packages/react-menu/src/MenuItem/MenuItem.tsx | 21 +++++ .../react-menu/src/MenuItem/MenuItem.types.ts | 11 +++ packages/react-menu/src/MenuItem/index.ts | 5 ++ .../src/MenuItem/renderMenuItem.tsx | 19 ++++ .../react-menu/src/MenuItem/useMenuItem.ts | 32 +++++++ .../src/MenuItem/useMenuItemStyles.ts | 58 ++++++++++++ packages/react-menu/src/MenuList/MenuList.tsx | 16 ++++ .../react-menu/src/MenuList/MenuList.types.ts | 6 ++ packages/react-menu/src/MenuList/index.ts | 4 + .../src/MenuList/renderMenuList.tsx | 13 +++ .../react-menu/src/MenuList/useMenuList.ts | 23 +++++ packages/react-menu/src/index.ts | 3 + packages/react-menu/tsconfig.json | 1 + yarn.lock | 88 +++++++++++++++++++ 19 files changed, 416 insertions(+), 9 deletions(-) create mode 100644 packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx create mode 100644 packages/react-menu/.npmrc create mode 100644 packages/react-menu/src/MenuItem/MenuItem.tsx create mode 100644 packages/react-menu/src/MenuItem/MenuItem.types.ts create mode 100644 packages/react-menu/src/MenuItem/index.ts create mode 100644 packages/react-menu/src/MenuItem/renderMenuItem.tsx create mode 100644 packages/react-menu/src/MenuItem/useMenuItem.ts create mode 100644 packages/react-menu/src/MenuItem/useMenuItemStyles.ts create mode 100644 packages/react-menu/src/MenuList/MenuList.tsx create mode 100644 packages/react-menu/src/MenuList/MenuList.types.ts create mode 100644 packages/react-menu/src/MenuList/index.ts create mode 100644 packages/react-menu/src/MenuList/renderMenuList.tsx create mode 100644 packages/react-menu/src/MenuList/useMenuList.ts diff --git a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx new file mode 100644 index 00000000000000..dc6daf32da92d8 --- /dev/null +++ b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; + +import { MenuList, MenuItem } from '@fluentui/react-menu'; +import { teamsLightTheme } from '@fluentui/react-theme'; +import { FluentProvider } from '@fluentui/react-provider'; +import { CutIcon, PasteIcon, EditIcon } from '@fluentui/react-icons-mdl2'; +import { makeStyles } from '@fluentui/react-make-styles'; + +const useContainerStyles = makeStyles([ + // This should eventually be the popup container styles + [ + null, + theme => ({ + backgroundColor: theme.alias.color.neutral.neutralBackground1, + minWidth: '128px', + minHeight: '48px', + maxWidth: '128px', + boxShadow: `${theme.alias.shadow.shadow16}`, + paddingTop: '4px', + paddingBottom: '4px', + }), + ], +]); +const Container = (props: { children: React.ReactNode }) => { + const classNames = useContainerStyles({}); + return
    {props.children}
    ; +}; + +export const MenuListExample = () => ( + + + + Item + Item + Item + + + +); + +export const MenuListWithIconsExample = () => ( + + + + }>Item + }>Item + }>Item + + + +); diff --git a/packages/react-menu/.npmrc b/packages/react-menu/.npmrc new file mode 100644 index 00000000000000..825c83e09df4da --- /dev/null +++ b/packages/react-menu/.npmrc @@ -0,0 +1,2 @@ +registry=https://registry.npmjs.org/ + diff --git a/packages/react-menu/README.md b/packages/react-menu/README.md index 2f39caf6a7b178..cb8606156b9000 100644 --- a/packages/react-menu/README.md +++ b/packages/react-menu/README.md @@ -5,3 +5,7 @@ These are not production-ready components and **should never be used in product**. This space is useful for testing new components whose APIs might change before final release. To import React Menu components: + +```js +import { ComponentName } from '@fluentui/react-menu'; +``` diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index e17e7d7b8d515d..186486651c6122 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -4,6 +4,57 @@ ```ts +import { ComponentProps } from '@fluentui/react-utils'; +import * as React from 'react'; +import { ShorthandProps } from '@fluentui/react-utils'; + +// @public +export const MenuItem: React.ForwardRefExoticComponent & React.RefAttributes>; + +// @public (undocumented) +export interface MenuItemProps extends ComponentProps, React.HTMLAttributes { + icon?: ShorthandProps; +} + +// @public +export const menuItemShorthandProps: string[]; + +// @public (undocumented) +export interface MenuItemState extends MenuItemProps { +} + +// @public +export const MenuList: React.ForwardRefExoticComponent & React.RefAttributes>; + +// @public (undocumented) +export interface MenuListProps extends ComponentProps, React.HTMLAttributes { +} + +// @public (undocumented) +export interface MenuListState extends MenuListProps { +} + +// @public +export const renderMenuItem: (state: MenuItemState) => JSX.Element; + +// @public +export const renderMenuList: (state: MenuListState) => JSX.Element; + +// @public +export const useIconStyles: (selectors: unknown) => string; + +// @public +export const useMenuItem: (props: MenuItemProps, ref: React.Ref, defaultProps?: MenuItemProps | undefined) => MenuItemState; + +// @public +export const useMenuItemStyles: (state: MenuItemState) => void; + +// @public +export const useMenuList: (props: MenuListProps, ref: React.Ref, defaultProps?: MenuListProps | undefined) => MenuListState; + +// @public +export const useRootStyles: (selectors: unknown) => string; + // (No @packageDocumentation comment for this package) diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json index 35cb5b3b1df69e..6e18fd83eab3b8 100644 --- a/packages/react-menu/package.json +++ b/packages/react-menu/package.json @@ -2,6 +2,7 @@ "name": "@fluentui/react-menu", "version": "0.1.0", "description": "Fluent UI menu component", + "private": true, "main": "lib-commonjs/index.js", "module": "lib/index.js", "typings": "lib/index.d.ts", @@ -27,6 +28,7 @@ }, "devDependencies": { "@fluentui/eslint-plugin": "^1.0.0-beta.1", + "@fluentui/scripts": "^1.0.0", "@types/enzyme": "3.10.3", "@types/enzyme-adapter-react-16": "1.0.3", "@types/jest": "~24.9.0", @@ -34,7 +36,6 @@ "@types/react-dom": "16.9.10", "@types/react-test-renderer": "^16.0.0", "@types/webpack-env": "1.16.0", - "@fluentui/scripts": "^1.0.0", "enzyme": "~3.10.0", "enzyme-adapter-react-16": "^1.15.0", "react": "16.8.6", @@ -43,10 +44,15 @@ "react-test-renderer": "^16.3.0" }, "dependencies": { - "@fluentui/theme": "^2.0.0-beta.13", + "@fluentui/keyboard-key": "^0.2.13", "@fluentui/react-compose": "^1.0.0-beta.11", + "@fluentui/react-hooks": "^8.0.0-beta.10", + "@fluentui/react-make-styles": "^0.2.1", "@fluentui/react-theme-provider": "^1.0.0-beta.19", + "@fluentui/react-utils": "^0.2.0", "@fluentui/set-version": "^8.0.0-beta.1", + "@fluentui/theme": "^2.0.0-beta.13", + "@testing-library/react": "^11.2.5", "tslib": "^1.10.0" }, "peerDependencies": { @@ -54,12 +60,5 @@ "@types/react-dom": ">=16.8.0 <17.0.0", "react": ">=16.8.0 <17.0.0", "react-dom": ">=16.8.0 <17.0.0" - }, - "beachball": { - "tag": "latest", - "disallowedChangeTypes": [ - "major", - "prerelease" - ] } } diff --git a/packages/react-menu/src/MenuItem/MenuItem.tsx b/packages/react-menu/src/MenuItem/MenuItem.tsx new file mode 100644 index 00000000000000..bcc1912874121d --- /dev/null +++ b/packages/react-menu/src/MenuItem/MenuItem.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { useMenuItem } from './useMenuItem'; +import { MenuItemProps } from './MenuItem.types'; +import { renderMenuItem } from './renderMenuItem'; +import { useMenuItemStyles } from './useMenuItemStyles'; + +/** + * Define a styled MenuItem, using the `useMenuItem` and `useMenuItemStyles` hook. + * {@docCategory MenuItem} + */ +export const MenuItem = React.forwardRef((props, ref) => { + const state = useMenuItem(props, ref, { + role: 'menuitem', + tabIndex: 0, // TODO keyboard navigation + }); + + useMenuItemStyles(state); + return renderMenuItem(state); +}); + +MenuItem.displayName = 'MenuItem'; diff --git a/packages/react-menu/src/MenuItem/MenuItem.types.ts b/packages/react-menu/src/MenuItem/MenuItem.types.ts new file mode 100644 index 00000000000000..3a86a1f6f9cafb --- /dev/null +++ b/packages/react-menu/src/MenuItem/MenuItem.types.ts @@ -0,0 +1,11 @@ +import * as React from 'react'; +import { ComponentProps, ShorthandProps } from '@fluentui/react-utils'; + +export interface MenuItemProps extends ComponentProps, React.HTMLAttributes { + /** + * Icon slot rendered before children content + */ + icon?: ShorthandProps; +} + +export interface MenuItemState extends MenuItemProps {} diff --git a/packages/react-menu/src/MenuItem/index.ts b/packages/react-menu/src/MenuItem/index.ts new file mode 100644 index 00000000000000..0b29cffc22b177 --- /dev/null +++ b/packages/react-menu/src/MenuItem/index.ts @@ -0,0 +1,5 @@ +export * from './MenuItem'; +export * from './MenuItem.types'; +export * from './renderMenuItem'; +export * from './useMenuItem'; +export * from './useMenuItemStyles'; diff --git a/packages/react-menu/src/MenuItem/renderMenuItem.tsx b/packages/react-menu/src/MenuItem/renderMenuItem.tsx new file mode 100644 index 00000000000000..cdfdc0923bbc9b --- /dev/null +++ b/packages/react-menu/src/MenuItem/renderMenuItem.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utils'; +import { MenuItemState } from './MenuItem.types'; +import { menuItemShorthandProps } from './useMenuItem'; + +/** + * Function that renders the final JSX of the component + * @param state Component state + */ +export const renderMenuItem = (state: MenuItemState) => { + const { slots, slotProps } = getSlots(state, menuItemShorthandProps); + + return ( + + + {state.children} + + ); +}; diff --git a/packages/react-menu/src/MenuItem/useMenuItem.ts b/packages/react-menu/src/MenuItem/useMenuItem.ts new file mode 100644 index 00000000000000..c510e3213a9c74 --- /dev/null +++ b/packages/react-menu/src/MenuItem/useMenuItem.ts @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { makeMergeProps, resolveShorthandProps } from '@fluentui/react-utils'; +import { useMergedRefs } from '@fluentui/react-hooks'; +import { MenuItemProps, MenuItemState } from './MenuItem.types'; + +/** + * Consts listing which props are shorthand props. + */ +export const menuItemShorthandProps = ['icon']; + +const mergeProps = makeMergeProps({ deepMerge: menuItemShorthandProps }); + +/** + * Returns the props and state required to render the component + */ +export const useMenuItem = ( + props: MenuItemProps, + ref: React.Ref, + defaultProps?: MenuItemProps, +): MenuItemState => { + const state = mergeProps( + { + ref: useMergedRefs(ref, React.useRef(null)), + as: 'div', + icon: { as: 'span' }, + }, + defaultProps, + resolveShorthandProps(props, menuItemShorthandProps), + ); + + return state; +}; diff --git a/packages/react-menu/src/MenuItem/useMenuItemStyles.ts b/packages/react-menu/src/MenuItem/useMenuItemStyles.ts new file mode 100644 index 00000000000000..9614a35504e80c --- /dev/null +++ b/packages/react-menu/src/MenuItem/useMenuItemStyles.ts @@ -0,0 +1,58 @@ +import { makeStyles, ax } from '@fluentui/react-make-styles'; +import { MenuItemState } from './MenuItem.types'; + +/** + * Styles for the root slot + */ +export const useRootStyles = makeStyles([ + [ + null, + theme => ({ + color: theme.alias.color.neutral.neutralForeground1, + backgroundColor: theme.alias.color.neutral.neutralBackground1, + paddingRight: '12px', + paddingLeft: '12px', + height: '32px', + display: 'flex', + alignItems: 'center', + fontSize: theme.global.type.fontSizes.base[300], + + ':hover': { + backgroundColor: theme.alias.color.neutral.neutralBackground1Hover, + }, + + ':focus': { + backgroundColor: theme.alias.color.neutral.neutralBackground1Hover, + }, + }), + ], +]); + +/** + * Styles for the icon slot + */ +export const useIconStyles = makeStyles([ + [ + null, + () => ({ + width: '20px', + height: '20px', + marginRight: '9px', + }), + ], +]); + +/** Applies style classnames to slots */ +export const useMenuItemStyles = (state: MenuItemState) => { + const rootClassName = useRootStyles({}); + const iconClassName = useIconStyles({}); + + state.className = ax(rootClassName, state.className); + + if (state.icon) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // TODO figure out typings + state.icon.className = ax(iconClassName, state.icon.className); + } +}; diff --git a/packages/react-menu/src/MenuList/MenuList.tsx b/packages/react-menu/src/MenuList/MenuList.tsx new file mode 100644 index 00000000000000..87e63565a006a4 --- /dev/null +++ b/packages/react-menu/src/MenuList/MenuList.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { useMenuList } from './useMenuList'; +import { MenuListProps } from './MenuList.types'; +import { renderMenuList } from './renderMenuList'; + +/** + * Define a styled MenuList, using the `useMenuList` hook. + * {@docCategory MenuList} + */ +export const MenuList = React.forwardRef((props, ref) => { + const state = useMenuList(props, ref); + + return renderMenuList(state); +}); + +MenuList.displayName = 'MenuList'; diff --git a/packages/react-menu/src/MenuList/MenuList.types.ts b/packages/react-menu/src/MenuList/MenuList.types.ts new file mode 100644 index 00000000000000..020c0cf501f4de --- /dev/null +++ b/packages/react-menu/src/MenuList/MenuList.types.ts @@ -0,0 +1,6 @@ +import * as React from 'react'; +import { ComponentProps } from '@fluentui/react-utils'; + +export interface MenuListProps extends ComponentProps, React.HTMLAttributes {} + +export interface MenuListState extends MenuListProps {} diff --git a/packages/react-menu/src/MenuList/index.ts b/packages/react-menu/src/MenuList/index.ts new file mode 100644 index 00000000000000..e172a99830dd64 --- /dev/null +++ b/packages/react-menu/src/MenuList/index.ts @@ -0,0 +1,4 @@ +export * from './MenuList'; +export * from './MenuList.types'; +export * from './renderMenuList'; +export * from './useMenuList'; diff --git a/packages/react-menu/src/MenuList/renderMenuList.tsx b/packages/react-menu/src/MenuList/renderMenuList.tsx new file mode 100644 index 00000000000000..7145cae2e3d7b4 --- /dev/null +++ b/packages/react-menu/src/MenuList/renderMenuList.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utils'; +import { MenuListState } from './MenuList.types'; + +/** + * Function that renders the final JSX of the component + * @param state Component state + */ +export const renderMenuList = (state: MenuListState) => { + const { slots, slotProps } = getSlots(state); + + return {state.children}; +}; diff --git a/packages/react-menu/src/MenuList/useMenuList.ts b/packages/react-menu/src/MenuList/useMenuList.ts new file mode 100644 index 00000000000000..c5d99a0c5ea3ad --- /dev/null +++ b/packages/react-menu/src/MenuList/useMenuList.ts @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { makeMergeProps, resolveShorthandProps } from '@fluentui/react-utils'; +import { useMergedRefs } from '@fluentui/react-hooks'; +import { MenuListProps, MenuListState } from './MenuList.types'; + +const mergeProps = makeMergeProps(); + +/** + * Returns the props and state required to render the component + */ +export const useMenuList = (props: MenuListProps, ref: React.Ref, defaultProps?: MenuListProps) => { + const state = mergeProps( + { + ref: useMergedRefs(ref, React.useRef(null)), + as: 'div', + role: 'menu', + }, + defaultProps, + resolveShorthandProps(props, []), + ); + + return state as MenuListState; +}; diff --git a/packages/react-menu/src/index.ts b/packages/react-menu/src/index.ts index 107367257abe5c..755d1be450f301 100644 --- a/packages/react-menu/src/index.ts +++ b/packages/react-menu/src/index.ts @@ -1 +1,4 @@ import './version'; + +export * from './MenuList/index'; +export * from './MenuItem/index'; diff --git a/packages/react-menu/tsconfig.json b/packages/react-menu/tsconfig.json index 9ee9ee30f1eb04..dfb68aa05fcb9a 100644 --- a/packages/react-menu/tsconfig.json +++ b/packages/react-menu/tsconfig.json @@ -15,6 +15,7 @@ "moduleResolution": "node", "preserveConstEnums": true, "lib": ["es5", "dom"], + "skipLibCheck": true, "typeRoots": ["../../node_modules/@types", "../../typings"], "types": ["jest", "webpack-env", "custom-global"] }, diff --git a/yarn.lock b/yarn.lock index ae8ffdccd399fc..05dc1628b1f7a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1072,6 +1072,14 @@ pirates "^4.0.0" source-map-support "^0.5.16" +"@babel/runtime-corejs3@^7.10.2": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.13.tgz#53d09813b7c20d616caf258e9325550ff701c039" + integrity sha512-8fSpqYRETHATtNitsCXq8QQbKJP31/KnDl2Wz2Vtui9nKzjss2ysuZtyVsWjBtvkeEFo346gkwjYPab1hvrXkQ== + dependencies: + core-js-pure "^3.0.0" + regenerator-runtime "^0.13.4" + "@babel/runtime-corejs3@^7.7.4", "@babel/runtime-corejs3@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz#f29fc1990307c4c57b10dbd6ce667b27159d9e0d" @@ -1087,6 +1095,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" + integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/standalone@^7.10.4", "@babel/standalone@^7.4.5": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.10.4.tgz#63b9e211bee42e8ba8dfc1c0b68a856150e37bf2" @@ -1616,6 +1631,17 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + "@lerna/add@3.15.0": version "3.15.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.15.0.tgz#10be562f43cde59b60f299083d54ac39520ec60a" @@ -3639,6 +3665,20 @@ dom-accessibility-api "^0.4.2" pretty-format "^25.1.0" +"@testing-library/dom@^7.28.1": + version "7.29.4" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.29.4.tgz#1647c2b478789621ead7a50614ad81ab5ae5b86c" + integrity sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^4.2.2" + chalk "^4.1.0" + dom-accessibility-api "^0.5.4" + lz-string "^1.4.4" + pretty-format "^26.6.2" + "@testing-library/jest-dom@^5.1.1": version "5.1.1" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.1.1.tgz#e88a5c08f9b9f36b384f948a0532eae2abbc8204" @@ -3670,6 +3710,14 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react@^11.2.5": + version "11.2.5" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.5.tgz#ae1c36a66c7790ddb6662c416c27863d87818eb9" + integrity sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^7.28.1" + "@textlint/ast-node-types@^4.0.3": version "4.2.5" resolved "https://registry.yarnpkg.com/@textlint/ast-node-types/-/ast-node-types-4.2.5.tgz#ae13981bc8711c98313a6ac1c361194d6bf2d39b" @@ -3715,6 +3763,11 @@ resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.33.tgz#2728669427cdd74a99e53c9f457ca2866a37c52d" integrity sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ== +"@types/aria-query@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.1.tgz#78b5433344e2f92e8b306c06a5622c50c245bf6b" + integrity sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg== + "@types/babel__core@^7.1.0": version "7.1.2" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f" @@ -4105,6 +4158,13 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" + integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== + dependencies: + "@types/istanbul-lib-report" "*" + "@types/jest-axe@^3.2.1": version "3.2.1" resolved "https://registry.yarnpkg.com/@types/jest-axe/-/jest-axe-3.2.1.tgz#84dc4306c105b304f14a594765beaa6a7aef763e" @@ -5584,6 +5644,14 @@ aria-query@^4.0.2: "@babel/runtime" "^7.7.4" "@babel/runtime-corejs3" "^7.7.4" +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== + dependencies: + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" + arr-diff@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" @@ -9574,6 +9642,11 @@ dom-accessibility-api@^0.4.2: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.3.tgz#93ca9002eb222fd5a343b6e5e6b9cf5929411c4c" integrity sha512-JZ8iPuEHDQzq6q0k7PKMGbrIdsgBB7TRrtVOUm4nSMCExlg5qQG4KXWTH2k90yggjM4tTumRGwTKJSldMzKyLA== +dom-accessibility-api@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" + integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== + dom-converter@^0.2: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -19591,6 +19664,16 @@ pretty-format@^25.1.0: ansi-styles "^4.0.0" react-is "^16.12.0" +pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + pretty-hrtime@^1.0.0, pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -20298,6 +20381,11 @@ react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== +react-is@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" + integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== + react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" From 8d23df55ef8c6caba59ea492255f59089594153d Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 09:32:11 +0000 Subject: [PATCH 11/74] revert unnecessary files --- packages/react-menu/.npmrc | 2 - packages/react-menu/README.md | 2 +- packages/react-menu/package.json | 12 +++-- yarn.lock | 88 -------------------------------- 4 files changed, 8 insertions(+), 96 deletions(-) delete mode 100644 packages/react-menu/.npmrc diff --git a/packages/react-menu/.npmrc b/packages/react-menu/.npmrc deleted file mode 100644 index 825c83e09df4da..00000000000000 --- a/packages/react-menu/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -registry=https://registry.npmjs.org/ - diff --git a/packages/react-menu/README.md b/packages/react-menu/README.md index cb8606156b9000..1ef03970b035cc 100644 --- a/packages/react-menu/README.md +++ b/packages/react-menu/README.md @@ -7,5 +7,5 @@ These are not production-ready components and **should never be used in product* To import React Menu components: ```js -import { ComponentName } from '@fluentui/react-menu'; +import { MenuList, MenuItem } from '@fluentui/react-menu'; ``` diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json index 6e18fd83eab3b8..c74e8130087743 100644 --- a/packages/react-menu/package.json +++ b/packages/react-menu/package.json @@ -2,7 +2,6 @@ "name": "@fluentui/react-menu", "version": "0.1.0", "description": "Fluent UI menu component", - "private": true, "main": "lib-commonjs/index.js", "module": "lib/index.js", "typings": "lib/index.d.ts", @@ -44,15 +43,11 @@ "react-test-renderer": "^16.3.0" }, "dependencies": { - "@fluentui/keyboard-key": "^0.2.13", "@fluentui/react-compose": "^1.0.0-beta.11", "@fluentui/react-hooks": "^8.0.0-beta.10", - "@fluentui/react-make-styles": "^0.2.1", "@fluentui/react-theme-provider": "^1.0.0-beta.19", - "@fluentui/react-utils": "^0.2.0", "@fluentui/set-version": "^8.0.0-beta.1", "@fluentui/theme": "^2.0.0-beta.13", - "@testing-library/react": "^11.2.5", "tslib": "^1.10.0" }, "peerDependencies": { @@ -60,5 +55,12 @@ "@types/react-dom": ">=16.8.0 <17.0.0", "react": ">=16.8.0 <17.0.0", "react-dom": ">=16.8.0 <17.0.0" + }, + "beachball": { + "tag": "latest", + "disallowedChangeTypes": [ + "major", + "prerelease" + ] } } diff --git a/yarn.lock b/yarn.lock index 05dc1628b1f7a1..ae8ffdccd399fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1072,14 +1072,6 @@ pirates "^4.0.0" source-map-support "^0.5.16" -"@babel/runtime-corejs3@^7.10.2": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.13.tgz#53d09813b7c20d616caf258e9325550ff701c039" - integrity sha512-8fSpqYRETHATtNitsCXq8QQbKJP31/KnDl2Wz2Vtui9nKzjss2ysuZtyVsWjBtvkeEFo346gkwjYPab1hvrXkQ== - dependencies: - core-js-pure "^3.0.0" - regenerator-runtime "^0.13.4" - "@babel/runtime-corejs3@^7.7.4", "@babel/runtime-corejs3@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz#f29fc1990307c4c57b10dbd6ce667b27159d9e0d" @@ -1095,13 +1087,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" - integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/standalone@^7.10.4", "@babel/standalone@^7.4.5": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.10.4.tgz#63b9e211bee42e8ba8dfc1c0b68a856150e37bf2" @@ -1631,17 +1616,6 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jest/types@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" - integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - "@lerna/add@3.15.0": version "3.15.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.15.0.tgz#10be562f43cde59b60f299083d54ac39520ec60a" @@ -3665,20 +3639,6 @@ dom-accessibility-api "^0.4.2" pretty-format "^25.1.0" -"@testing-library/dom@^7.28.1": - version "7.29.4" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.29.4.tgz#1647c2b478789621ead7a50614ad81ab5ae5b86c" - integrity sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" - "@types/aria-query" "^4.2.0" - aria-query "^4.2.2" - chalk "^4.1.0" - dom-accessibility-api "^0.5.4" - lz-string "^1.4.4" - pretty-format "^26.6.2" - "@testing-library/jest-dom@^5.1.1": version "5.1.1" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.1.1.tgz#e88a5c08f9b9f36b384f948a0532eae2abbc8204" @@ -3710,14 +3670,6 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react@^11.2.5": - version "11.2.5" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.5.tgz#ae1c36a66c7790ddb6662c416c27863d87818eb9" - integrity sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ== - dependencies: - "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^7.28.1" - "@textlint/ast-node-types@^4.0.3": version "4.2.5" resolved "https://registry.yarnpkg.com/@textlint/ast-node-types/-/ast-node-types-4.2.5.tgz#ae13981bc8711c98313a6ac1c361194d6bf2d39b" @@ -3763,11 +3715,6 @@ resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.33.tgz#2728669427cdd74a99e53c9f457ca2866a37c52d" integrity sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ== -"@types/aria-query@^4.2.0": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.1.tgz#78b5433344e2f92e8b306c06a5622c50c245bf6b" - integrity sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg== - "@types/babel__core@^7.1.0": version "7.1.2" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f" @@ -4158,13 +4105,6 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" -"@types/istanbul-reports@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" - integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== - dependencies: - "@types/istanbul-lib-report" "*" - "@types/jest-axe@^3.2.1": version "3.2.1" resolved "https://registry.yarnpkg.com/@types/jest-axe/-/jest-axe-3.2.1.tgz#84dc4306c105b304f14a594765beaa6a7aef763e" @@ -5644,14 +5584,6 @@ aria-query@^4.0.2: "@babel/runtime" "^7.7.4" "@babel/runtime-corejs3" "^7.7.4" -aria-query@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" - integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== - dependencies: - "@babel/runtime" "^7.10.2" - "@babel/runtime-corejs3" "^7.10.2" - arr-diff@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" @@ -9642,11 +9574,6 @@ dom-accessibility-api@^0.4.2: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.3.tgz#93ca9002eb222fd5a343b6e5e6b9cf5929411c4c" integrity sha512-JZ8iPuEHDQzq6q0k7PKMGbrIdsgBB7TRrtVOUm4nSMCExlg5qQG4KXWTH2k90yggjM4tTumRGwTKJSldMzKyLA== -dom-accessibility-api@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" - integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== - dom-converter@^0.2: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -19664,16 +19591,6 @@ pretty-format@^25.1.0: ansi-styles "^4.0.0" react-is "^16.12.0" -pretty-format@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" - integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== - dependencies: - "@jest/types" "^26.6.2" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^17.0.1" - pretty-hrtime@^1.0.0, pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -20381,11 +20298,6 @@ react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== -react-is@^17.0.1: - version "17.0.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" - integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== - react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" From 586aa07ac592a77637aa255f907d5755641276f7 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 11:48:12 +0000 Subject: [PATCH 12/74] fix deps --- package.json | 3 +++ packages/react-menu/package.json | 4 ++-- packages/react-menu/src/MenuItem/useMenuItem.ts | 2 +- packages/react-menu/src/MenuList/useMenuList.ts | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index dcbcaa92c9e435..ec8ad791c55948 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,14 @@ "build": "lage build --verbose", "build:fluentui:docs": "gulp build:docs", "build:min": "yarn build --to @fluentui/react --to @fluentui/react-northstar --min", + "build:prod": "yarn build --production", "buildci": "yarn prelint && lage build test lint --verbose", + "buildci:prod": "yarn build:prod && yarn test && yarn lint && yarn bundle:prod", "builddemo": "yarn build --to public-docsite-resources", "buildto": "lage build --verbose --to", "buildto:lerna": "node ./scripts/monorepo/buildTo.js", "bundle": "lage bundle --verbose", + "bundle:prod": "yarn bundle --production", "bundlesize": "cd scripts && yarn bundlesize", "bundlesizecollect": "node ./scripts/bundle-size-collect", "checkchange": "beachball check --scope \"!packages/fluentui/*\" --changehint \"Run 'yarn change' to generate a change file\"", diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json index c74e8130087743..ff7f92b6f80776 100644 --- a/packages/react-menu/package.json +++ b/packages/react-menu/package.json @@ -43,11 +43,11 @@ "react-test-renderer": "^16.3.0" }, "dependencies": { - "@fluentui/react-compose": "^1.0.0-beta.11", "@fluentui/react-hooks": "^8.0.0-beta.10", + "@fluentui/react-theme": "^0.2.0", "@fluentui/react-theme-provider": "^1.0.0-beta.19", + "@fluentui/react-utils": "^0.2.0", "@fluentui/set-version": "^8.0.0-beta.1", - "@fluentui/theme": "^2.0.0-beta.13", "tslib": "^1.10.0" }, "peerDependencies": { diff --git a/packages/react-menu/src/MenuItem/useMenuItem.ts b/packages/react-menu/src/MenuItem/useMenuItem.ts index c510e3213a9c74..3e058c7e96cbd9 100644 --- a/packages/react-menu/src/MenuItem/useMenuItem.ts +++ b/packages/react-menu/src/MenuItem/useMenuItem.ts @@ -8,7 +8,7 @@ import { MenuItemProps, MenuItemState } from './MenuItem.types'; */ export const menuItemShorthandProps = ['icon']; -const mergeProps = makeMergeProps({ deepMerge: menuItemShorthandProps }); +const mergeProps = makeMergeProps({ deepMerge: menuItemShorthandProps }); /** * Returns the props and state required to render the component diff --git a/packages/react-menu/src/MenuList/useMenuList.ts b/packages/react-menu/src/MenuList/useMenuList.ts index c5d99a0c5ea3ad..f142a05bdd50d5 100644 --- a/packages/react-menu/src/MenuList/useMenuList.ts +++ b/packages/react-menu/src/MenuList/useMenuList.ts @@ -3,7 +3,7 @@ import { makeMergeProps, resolveShorthandProps } from '@fluentui/react-utils'; import { useMergedRefs } from '@fluentui/react-hooks'; import { MenuListProps, MenuListState } from './MenuList.types'; -const mergeProps = makeMergeProps(); +const mergeProps = makeMergeProps(); /** * Returns the props and state required to render the component From 816547c1f3f5f3e60559642090dde9a1693bf435 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 11:50:33 +0000 Subject: [PATCH 13/74] remove unnecessary comments --- packages/react-menu/src/MenuItem/renderMenuItem.tsx | 1 - packages/react-menu/src/MenuList/renderMenuList.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/react-menu/src/MenuItem/renderMenuItem.tsx b/packages/react-menu/src/MenuItem/renderMenuItem.tsx index cdfdc0923bbc9b..5a5fc917dc643b 100644 --- a/packages/react-menu/src/MenuItem/renderMenuItem.tsx +++ b/packages/react-menu/src/MenuItem/renderMenuItem.tsx @@ -5,7 +5,6 @@ import { menuItemShorthandProps } from './useMenuItem'; /** * Function that renders the final JSX of the component - * @param state Component state */ export const renderMenuItem = (state: MenuItemState) => { const { slots, slotProps } = getSlots(state, menuItemShorthandProps); diff --git a/packages/react-menu/src/MenuList/renderMenuList.tsx b/packages/react-menu/src/MenuList/renderMenuList.tsx index 7145cae2e3d7b4..aaff89c75a7a5b 100644 --- a/packages/react-menu/src/MenuList/renderMenuList.tsx +++ b/packages/react-menu/src/MenuList/renderMenuList.tsx @@ -4,7 +4,6 @@ import { MenuListState } from './MenuList.types'; /** * Function that renders the final JSX of the component - * @param state Component state */ export const renderMenuList = (state: MenuListState) => { const { slots, slotProps } = getSlots(state); From 8f3ffa664a96b6d753f662bfc3b37f024eecd32e Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 11:59:25 +0000 Subject: [PATCH 14/74] type ref --- packages/react-menu/package.json | 1 + packages/react-menu/src/MenuItem/MenuItem.types.ts | 8 +++++++- packages/react-menu/src/MenuItem/useMenuItemStyles.ts | 3 --- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json index ff7f92b6f80776..42e8d59aca42f5 100644 --- a/packages/react-menu/package.json +++ b/packages/react-menu/package.json @@ -44,6 +44,7 @@ }, "dependencies": { "@fluentui/react-hooks": "^8.0.0-beta.10", + "@fluentui/react-make-styles": "^0.2.2", "@fluentui/react-theme": "^0.2.0", "@fluentui/react-theme-provider": "^1.0.0-beta.19", "@fluentui/react-utils": "^0.2.0", diff --git a/packages/react-menu/src/MenuItem/MenuItem.types.ts b/packages/react-menu/src/MenuItem/MenuItem.types.ts index 3a86a1f6f9cafb..93835b2515904b 100644 --- a/packages/react-menu/src/MenuItem/MenuItem.types.ts +++ b/packages/react-menu/src/MenuItem/MenuItem.types.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import { ComponentProps, ShorthandProps } from '@fluentui/react-utils'; +import { ObjectSlotProp } from '../../../react-compose/lib/types'; export interface MenuItemProps extends ComponentProps, React.HTMLAttributes { /** @@ -8,4 +9,9 @@ export interface MenuItemProps extends ComponentProps, React.HTMLAttributes; // TODO use correct props when there is a converged icon pkg +} diff --git a/packages/react-menu/src/MenuItem/useMenuItemStyles.ts b/packages/react-menu/src/MenuItem/useMenuItemStyles.ts index 9614a35504e80c..b587b62c2b5926 100644 --- a/packages/react-menu/src/MenuItem/useMenuItemStyles.ts +++ b/packages/react-menu/src/MenuItem/useMenuItemStyles.ts @@ -50,9 +50,6 @@ export const useMenuItemStyles = (state: MenuItemState) => { state.className = ax(rootClassName, state.className); if (state.icon) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - // TODO figure out typings state.icon.className = ax(iconClassName, state.icon.className); } }; From b1ca465f80de5a5b6781daea7e5541911884a0af Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 12:08:51 +0000 Subject: [PATCH 15/74] use state for selectors --- packages/react-menu/src/MenuItem/useMenuItemStyles.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-menu/src/MenuItem/useMenuItemStyles.ts b/packages/react-menu/src/MenuItem/useMenuItemStyles.ts index b587b62c2b5926..510e8262614242 100644 --- a/packages/react-menu/src/MenuItem/useMenuItemStyles.ts +++ b/packages/react-menu/src/MenuItem/useMenuItemStyles.ts @@ -4,7 +4,7 @@ import { MenuItemState } from './MenuItem.types'; /** * Styles for the root slot */ -export const useRootStyles = makeStyles([ +export const useRootStyles = makeStyles([ [ null, theme => ({ @@ -31,7 +31,7 @@ export const useRootStyles = makeStyles([ /** * Styles for the icon slot */ -export const useIconStyles = makeStyles([ +export const useIconStyles = makeStyles([ [ null, () => ({ @@ -44,8 +44,8 @@ export const useIconStyles = makeStyles([ /** Applies style classnames to slots */ export const useMenuItemStyles = (state: MenuItemState) => { - const rootClassName = useRootStyles({}); - const iconClassName = useIconStyles({}); + const rootClassName = useRootStyles(state); + const iconClassName = useIconStyles(state); state.className = ax(rootClassName, state.className); From b094bed91a7b4dae785b2d0e238ec78bdad8d39c Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 12:09:19 +0000 Subject: [PATCH 16/74] add mising type --- packages/react-menu/src/MenuList/useMenuList.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react-menu/src/MenuList/useMenuList.ts b/packages/react-menu/src/MenuList/useMenuList.ts index f142a05bdd50d5..95cb2aa30f142e 100644 --- a/packages/react-menu/src/MenuList/useMenuList.ts +++ b/packages/react-menu/src/MenuList/useMenuList.ts @@ -8,7 +8,11 @@ const mergeProps = makeMergeProps(); /** * Returns the props and state required to render the component */ -export const useMenuList = (props: MenuListProps, ref: React.Ref, defaultProps?: MenuListProps) => { +export const useMenuList = ( + props: MenuListProps, + ref: React.Ref, + defaultProps?: MenuListProps, +): MenuListState => { const state = mergeProps( { ref: useMergedRefs(ref, React.useRef(null)), From 7a397c2179ebe74ea7d5fc8611aba93d45c07da3 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 12:10:38 +0000 Subject: [PATCH 17/74] remove cast --- packages/react-menu/src/MenuList/useMenuList.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-menu/src/MenuList/useMenuList.ts b/packages/react-menu/src/MenuList/useMenuList.ts index 95cb2aa30f142e..57b8e0ea0e33e1 100644 --- a/packages/react-menu/src/MenuList/useMenuList.ts +++ b/packages/react-menu/src/MenuList/useMenuList.ts @@ -23,5 +23,5 @@ export const useMenuList = ( resolveShorthandProps(props, []), ); - return state as MenuListState; + return state; }; From a8f65f2b73f9548aae3c0245b2a481bf52010be5 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 12:11:00 +0000 Subject: [PATCH 18/74] remove readme --- packages/react-menu/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/react-menu/README.md b/packages/react-menu/README.md index 1ef03970b035cc..cea24b6a6c284f 100644 --- a/packages/react-menu/README.md +++ b/packages/react-menu/README.md @@ -3,9 +3,3 @@ **React Menu components for [Fluent UI React](https://developer.microsoft.com/en-us/fluentui)** These are not production-ready components and **should never be used in product**. This space is useful for testing new components whose APIs might change before final release. - -To import React Menu components: - -```js -import { MenuList, MenuItem } from '@fluentui/react-menu'; -``` From e2fa3f14f23012b6272506b7ef63932caa93b0de Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 12:11:20 +0000 Subject: [PATCH 19/74] remove default div --- packages/react-menu/src/MenuItem/useMenuItem.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-menu/src/MenuItem/useMenuItem.ts b/packages/react-menu/src/MenuItem/useMenuItem.ts index 3e058c7e96cbd9..fe8616ee8e1e07 100644 --- a/packages/react-menu/src/MenuItem/useMenuItem.ts +++ b/packages/react-menu/src/MenuItem/useMenuItem.ts @@ -21,7 +21,6 @@ export const useMenuItem = ( const state = mergeProps( { ref: useMergedRefs(ref, React.useRef(null)), - as: 'div', icon: { as: 'span' }, }, defaultProps, From 52420633de2b7e5f2f58cdb26d27c99235365594 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 12:11:33 +0000 Subject: [PATCH 20/74] remove default div --- packages/react-menu/src/MenuList/useMenuList.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-menu/src/MenuList/useMenuList.ts b/packages/react-menu/src/MenuList/useMenuList.ts index 57b8e0ea0e33e1..a134b3159f8ee1 100644 --- a/packages/react-menu/src/MenuList/useMenuList.ts +++ b/packages/react-menu/src/MenuList/useMenuList.ts @@ -16,7 +16,6 @@ export const useMenuList = ( const state = mergeProps( { ref: useMergedRefs(ref, React.useRef(null)), - as: 'div', role: 'menu', }, defaultProps, From 7586a93fabeaf7d808cbd374ffc4b61f3e8e2e03 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 12:17:58 +0000 Subject: [PATCH 21/74] fix story type --- .../react-examples/src/react-menu/MenuList/MenuList.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx index dc6daf32da92d8..d1d7ea2fb82c3a 100644 --- a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx +++ b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx @@ -21,7 +21,7 @@ const useContainerStyles = makeStyles([ }), ], ]); -const Container = (props: { children: React.ReactNode }) => { +const Container: React.FC = props => { const classNames = useContainerStyles({}); return
    {props.children}
    ; }; From e3f3da0db20de2213c7cfce96906a1df93249ef1 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 12:55:44 +0000 Subject: [PATCH 22/74] follow package structure --- packages/react-menu/etc/react-menu.api.md | 6 ++-- packages/react-menu/package.json | 1 + .../react-menu/src/common/isConformant.ts | 10 +++++++ .../src/components/MenuItem/MenuItem.test.tsx | 30 +++++++++++++++++++ .../{ => components}/MenuItem/MenuItem.tsx | 0 .../MenuItem/MenuItem.types.ts | 3 +- .../__snapshots__/MenuItem.test.tsx.snap | 17 +++++++++++ .../src/{ => components}/MenuItem/index.ts | 0 .../MenuItem/renderMenuItem.tsx | 0 .../{ => components}/MenuItem/useMenuItem.ts | 0 .../MenuItem/useMenuItemStyles.ts | 0 .../src/components/MenuList/MenuList.test.tsx | 30 +++++++++++++++++++ .../{ => components}/MenuList/MenuList.tsx | 0 .../MenuList/MenuList.types.ts | 0 .../src/{ => components}/MenuList/index.ts | 0 .../MenuList/renderMenuList.tsx | 0 .../{ => components}/MenuList/useMenuList.ts | 0 packages/react-menu/src/components/index.ts | 1 + packages/react-menu/src/index.ts | 3 +- packages/react-utils/etc/react-utils.api.md | 8 +++++ packages/react-utils/src/compose/types.ts | 6 ++++ 21 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 packages/react-menu/src/common/isConformant.ts create mode 100644 packages/react-menu/src/components/MenuItem/MenuItem.test.tsx rename packages/react-menu/src/{ => components}/MenuItem/MenuItem.tsx (100%) rename packages/react-menu/src/{ => components}/MenuItem/MenuItem.types.ts (75%) create mode 100644 packages/react-menu/src/components/MenuItem/__snapshots__/MenuItem.test.tsx.snap rename packages/react-menu/src/{ => components}/MenuItem/index.ts (100%) rename packages/react-menu/src/{ => components}/MenuItem/renderMenuItem.tsx (100%) rename packages/react-menu/src/{ => components}/MenuItem/useMenuItem.ts (100%) rename packages/react-menu/src/{ => components}/MenuItem/useMenuItemStyles.ts (100%) create mode 100644 packages/react-menu/src/components/MenuList/MenuList.test.tsx rename packages/react-menu/src/{ => components}/MenuList/MenuList.tsx (100%) rename packages/react-menu/src/{ => components}/MenuList/MenuList.types.ts (100%) rename packages/react-menu/src/{ => components}/MenuList/index.ts (100%) rename packages/react-menu/src/{ => components}/MenuList/renderMenuList.tsx (100%) rename packages/react-menu/src/{ => components}/MenuList/useMenuList.ts (100%) create mode 100644 packages/react-menu/src/components/index.ts diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index 186486651c6122..ebf3ef7e348688 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -5,6 +5,7 @@ ```ts import { ComponentProps } from '@fluentui/react-utils'; +import { ObjectSlotProp } from '@fluentui/react-utils'; import * as React from 'react'; import { ShorthandProps } from '@fluentui/react-utils'; @@ -21,6 +22,7 @@ export const menuItemShorthandProps: string[]; // @public (undocumented) export interface MenuItemState extends MenuItemProps { + icon?: ObjectSlotProp; } // @public @@ -41,7 +43,7 @@ export const renderMenuItem: (state: MenuItemState) => JSX.Element; export const renderMenuList: (state: MenuListState) => JSX.Element; // @public -export const useIconStyles: (selectors: unknown) => string; +export const useIconStyles: (selectors: MenuItemState) => string; // @public export const useMenuItem: (props: MenuItemProps, ref: React.Ref, defaultProps?: MenuItemProps | undefined) => MenuItemState; @@ -53,7 +55,7 @@ export const useMenuItemStyles: (state: MenuItemState) => void; export const useMenuList: (props: MenuListProps, ref: React.Ref, defaultProps?: MenuListProps | undefined) => MenuListState; // @public -export const useRootStyles: (selectors: unknown) => string; +export const useRootStyles: (selectors: MenuItemState) => string; // (No @packageDocumentation comment for this package) diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json index 42e8d59aca42f5..8109b3ec60c60a 100644 --- a/packages/react-menu/package.json +++ b/packages/react-menu/package.json @@ -43,6 +43,7 @@ "react-test-renderer": "^16.3.0" }, "dependencies": { + "@fluentui/react-conformance": "^1.0.0", "@fluentui/react-hooks": "^8.0.0-beta.10", "@fluentui/react-make-styles": "^0.2.2", "@fluentui/react-theme": "^0.2.0", diff --git a/packages/react-menu/src/common/isConformant.ts b/packages/react-menu/src/common/isConformant.ts new file mode 100644 index 00000000000000..e3f58bea577b78 --- /dev/null +++ b/packages/react-menu/src/common/isConformant.ts @@ -0,0 +1,10 @@ +import { isConformant as baseIsConformant, IsConformantOptions } from '@fluentui/react-conformance'; + +export function isConformant(testInfo: Omit) { + const defaultOptions = { + disabledTests: ['has-docblock'], + componentPath: module!.parent!.filename.replace('.test', ''), + }; + + baseIsConformant(defaultOptions, testInfo); +} diff --git a/packages/react-menu/src/components/MenuItem/MenuItem.test.tsx b/packages/react-menu/src/components/MenuItem/MenuItem.test.tsx new file mode 100644 index 00000000000000..5035f37ac567d3 --- /dev/null +++ b/packages/react-menu/src/components/MenuItem/MenuItem.test.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { MenuItem } from './MenuItem'; +import * as renderer from 'react-test-renderer'; +import { ReactWrapper } from 'enzyme'; +import { isConformant } from '../../common/isConformant'; + +describe('MenuItem', () => { + isConformant({ + Component: MenuItem, + displayName: 'MenuItem', + }); + + let wrapper: ReactWrapper | undefined; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = undefined; + } + }); + + /** + * Note: see more visual regression tests for MenuItem in /apps/vr-tests. + */ + it('renders a default state', () => { + const component = renderer.create(Default MenuItem); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/packages/react-menu/src/MenuItem/MenuItem.tsx b/packages/react-menu/src/components/MenuItem/MenuItem.tsx similarity index 100% rename from packages/react-menu/src/MenuItem/MenuItem.tsx rename to packages/react-menu/src/components/MenuItem/MenuItem.tsx diff --git a/packages/react-menu/src/MenuItem/MenuItem.types.ts b/packages/react-menu/src/components/MenuItem/MenuItem.types.ts similarity index 75% rename from packages/react-menu/src/MenuItem/MenuItem.types.ts rename to packages/react-menu/src/components/MenuItem/MenuItem.types.ts index 93835b2515904b..ac87191a37448c 100644 --- a/packages/react-menu/src/MenuItem/MenuItem.types.ts +++ b/packages/react-menu/src/components/MenuItem/MenuItem.types.ts @@ -1,6 +1,5 @@ import * as React from 'react'; -import { ComponentProps, ShorthandProps } from '@fluentui/react-utils'; -import { ObjectSlotProp } from '../../../react-compose/lib/types'; +import { ComponentProps, ShorthandProps, ObjectSlotProp } from '@fluentui/react-utils'; export interface MenuItemProps extends ComponentProps, React.HTMLAttributes { /** diff --git a/packages/react-menu/src/components/MenuItem/__snapshots__/MenuItem.test.tsx.snap b/packages/react-menu/src/components/MenuItem/__snapshots__/MenuItem.test.tsx.snap new file mode 100644 index 00000000000000..7150cfb1aff9f9 --- /dev/null +++ b/packages/react-menu/src/components/MenuItem/__snapshots__/MenuItem.test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MenuItem renders a default state 1`] = ` +
    + Default MenuItem +
    +`; diff --git a/packages/react-menu/src/MenuItem/index.ts b/packages/react-menu/src/components/MenuItem/index.ts similarity index 100% rename from packages/react-menu/src/MenuItem/index.ts rename to packages/react-menu/src/components/MenuItem/index.ts diff --git a/packages/react-menu/src/MenuItem/renderMenuItem.tsx b/packages/react-menu/src/components/MenuItem/renderMenuItem.tsx similarity index 100% rename from packages/react-menu/src/MenuItem/renderMenuItem.tsx rename to packages/react-menu/src/components/MenuItem/renderMenuItem.tsx diff --git a/packages/react-menu/src/MenuItem/useMenuItem.ts b/packages/react-menu/src/components/MenuItem/useMenuItem.ts similarity index 100% rename from packages/react-menu/src/MenuItem/useMenuItem.ts rename to packages/react-menu/src/components/MenuItem/useMenuItem.ts diff --git a/packages/react-menu/src/MenuItem/useMenuItemStyles.ts b/packages/react-menu/src/components/MenuItem/useMenuItemStyles.ts similarity index 100% rename from packages/react-menu/src/MenuItem/useMenuItemStyles.ts rename to packages/react-menu/src/components/MenuItem/useMenuItemStyles.ts diff --git a/packages/react-menu/src/components/MenuList/MenuList.test.tsx b/packages/react-menu/src/components/MenuList/MenuList.test.tsx new file mode 100644 index 00000000000000..e627bd9ca7baf2 --- /dev/null +++ b/packages/react-menu/src/components/MenuList/MenuList.test.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { MenuList } from './MenuList'; +import * as renderer from 'react-test-renderer'; +import { ReactWrapper } from 'enzyme'; +import { isConformant } from '../../common/isConformant'; + +describe('MenuList', () => { + isConformant({ + Component: MenuList, + displayName: 'MenuList', + }); + + let wrapper: ReactWrapper | undefined; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = undefined; + } + }); + + /** + * Note: see more visual regression tests for MenuList in /apps/vr-tests. + */ + it('renders a default state', () => { + const component = renderer.create(Default MenuList); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/packages/react-menu/src/MenuList/MenuList.tsx b/packages/react-menu/src/components/MenuList/MenuList.tsx similarity index 100% rename from packages/react-menu/src/MenuList/MenuList.tsx rename to packages/react-menu/src/components/MenuList/MenuList.tsx diff --git a/packages/react-menu/src/MenuList/MenuList.types.ts b/packages/react-menu/src/components/MenuList/MenuList.types.ts similarity index 100% rename from packages/react-menu/src/MenuList/MenuList.types.ts rename to packages/react-menu/src/components/MenuList/MenuList.types.ts diff --git a/packages/react-menu/src/MenuList/index.ts b/packages/react-menu/src/components/MenuList/index.ts similarity index 100% rename from packages/react-menu/src/MenuList/index.ts rename to packages/react-menu/src/components/MenuList/index.ts diff --git a/packages/react-menu/src/MenuList/renderMenuList.tsx b/packages/react-menu/src/components/MenuList/renderMenuList.tsx similarity index 100% rename from packages/react-menu/src/MenuList/renderMenuList.tsx rename to packages/react-menu/src/components/MenuList/renderMenuList.tsx diff --git a/packages/react-menu/src/MenuList/useMenuList.ts b/packages/react-menu/src/components/MenuList/useMenuList.ts similarity index 100% rename from packages/react-menu/src/MenuList/useMenuList.ts rename to packages/react-menu/src/components/MenuList/useMenuList.ts diff --git a/packages/react-menu/src/components/index.ts b/packages/react-menu/src/components/index.ts new file mode 100644 index 00000000000000..2b5e263fae9a68 --- /dev/null +++ b/packages/react-menu/src/components/index.ts @@ -0,0 +1 @@ +export * from './MenuItem'; diff --git a/packages/react-menu/src/index.ts b/packages/react-menu/src/index.ts index 755d1be450f301..9b43e81f480bec 100644 --- a/packages/react-menu/src/index.ts +++ b/packages/react-menu/src/index.ts @@ -1,4 +1,3 @@ import './version'; -export * from './MenuList/index'; -export * from './MenuItem/index'; +export * from './components'; diff --git a/packages/react-utils/etc/react-utils.api.md b/packages/react-utils/etc/react-utils.api.md index 7b3d453e13d974..ca6382d1314593 100644 --- a/packages/react-utils/etc/react-utils.api.md +++ b/packages/react-utils/etc/react-utils.api.md @@ -45,6 +45,11 @@ export type MergePropsOptions = { deepMerge?: string[]; }; +// @public (undocumented) +export type ObjectSlotProp = TProps & { + children?: TProps['children'] | SlotPropRenderFunction; +}; + // @public export const resolveShorthandProps: (props: TProps, shorthandPropNames: (keyof TProps)[]) => TProps; @@ -56,6 +61,9 @@ export type ShorthandProps = React.ReactChil // @public (undocumented) export type ShorthandRenderFunction = (Component: React.ElementType, props: TProps) => React.ReactNode; +// @public (undocumented) +export type SlotPropRenderFunction = (Component: React.ElementType, props: TProps) => React.ReactNode; + // @public (undocumented) export type SlotProps> = { [key in keyof Omit]: key extends keyof TProps ? TProps[key] : any; diff --git a/packages/react-utils/src/compose/types.ts b/packages/react-utils/src/compose/types.ts index 77e746f45dbc29..428b511fe0acb1 100644 --- a/packages/react-utils/src/compose/types.ts +++ b/packages/react-utils/src/compose/types.ts @@ -39,6 +39,12 @@ export interface BaseSlots { root: React.ElementType; } +export type SlotPropRenderFunction = (Component: React.ElementType, props: TProps) => React.ReactNode; + +export type ObjectSlotProp = TProps & { + children?: TProps['children'] | SlotPropRenderFunction; +}; + export type SlotProps> = { // eslint-disable-next-line @typescript-eslint/no-explicit-any [key in keyof Omit]: key extends keyof TProps ? TProps[key] : any; From 4a330724fed1f3a4424648a2abbd5b7b09d5a4de Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 12:57:30 +0000 Subject: [PATCH 23/74] add export --- packages/react-menu/src/components/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-menu/src/components/index.ts b/packages/react-menu/src/components/index.ts index 2b5e263fae9a68..721e4ff57284b6 100644 --- a/packages/react-menu/src/components/index.ts +++ b/packages/react-menu/src/components/index.ts @@ -1 +1,2 @@ export * from './MenuItem'; +export * from './MenuList'; From 936ff5ddec7a8d3a0cff8f47a1db8ce598749a06 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 12:58:25 +0000 Subject: [PATCH 24/74] remove placeholder --- packages/react-examples/src/react-menu/index.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 packages/react-examples/src/react-menu/index.ts diff --git a/packages/react-examples/src/react-menu/index.ts b/packages/react-examples/src/react-menu/index.ts deleted file mode 100644 index 9ac88a11117486..00000000000000 --- a/packages/react-examples/src/react-menu/index.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO placeholder From a38745244712d7d654a15999ac66ecc2716ee80f Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 13:01:53 +0000 Subject: [PATCH 25/74] revert package.json --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index ec8ad791c55948..dcbcaa92c9e435 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,11 @@ "build": "lage build --verbose", "build:fluentui:docs": "gulp build:docs", "build:min": "yarn build --to @fluentui/react --to @fluentui/react-northstar --min", - "build:prod": "yarn build --production", "buildci": "yarn prelint && lage build test lint --verbose", - "buildci:prod": "yarn build:prod && yarn test && yarn lint && yarn bundle:prod", "builddemo": "yarn build --to public-docsite-resources", "buildto": "lage build --verbose --to", "buildto:lerna": "node ./scripts/monorepo/buildTo.js", "bundle": "lage bundle --verbose", - "bundle:prod": "yarn bundle --production", "bundlesize": "cd scripts && yarn bundlesize", "bundlesizecollect": "node ./scripts/bundle-size-collect", "checkchange": "beachball check --scope \"!packages/fluentui/*\" --changehint \"Run 'yarn change' to generate a change file\"", From 1b48ea2e3e82209badcf27816824d6c2e843a478 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 13:32:23 +0000 Subject: [PATCH 26/74] type refs --- .../react-menu/src/components/MenuItem/MenuItem.types.ts | 5 +++++ .../react-menu/src/components/MenuList/MenuList.types.ts | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/react-menu/src/components/MenuItem/MenuItem.types.ts b/packages/react-menu/src/components/MenuItem/MenuItem.types.ts index ac87191a37448c..42e0a20ae9e46a 100644 --- a/packages/react-menu/src/components/MenuItem/MenuItem.types.ts +++ b/packages/react-menu/src/components/MenuItem/MenuItem.types.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import { ComponentProps, ShorthandProps, ObjectSlotProp } from '@fluentui/react-utils'; +import { RefObjectFunction } from '@fluentui/react-hooks'; export interface MenuItemProps extends ComponentProps, React.HTMLAttributes { /** @@ -9,6 +10,10 @@ export interface MenuItemProps extends ComponentProps, React.HTMLAttributes; /** * Icon slot when processed by internal state */ diff --git a/packages/react-menu/src/components/MenuList/MenuList.types.ts b/packages/react-menu/src/components/MenuList/MenuList.types.ts index 020c0cf501f4de..26d792d7c152a6 100644 --- a/packages/react-menu/src/components/MenuList/MenuList.types.ts +++ b/packages/react-menu/src/components/MenuList/MenuList.types.ts @@ -1,6 +1,12 @@ import * as React from 'react'; import { ComponentProps } from '@fluentui/react-utils'; +import { RefObjectFunction } from '@fluentui/react-hooks'; export interface MenuListProps extends ComponentProps, React.HTMLAttributes {} -export interface MenuListState extends MenuListProps {} +export interface MenuListState extends MenuListProps { + /** + * Ref to the root slot + */ + ref: RefObjectFunction; +} From eb3fb95d552072b5c6549698c6a282f7f738dcf4 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 13:34:43 +0000 Subject: [PATCH 27/74] Change files --- ...eact-examples-9fac4130-8756-47e0-b70d-09925f28c31e.json | 7 +++++++ ...ui-react-menu-c4c9135d-c4fa-4b44-a114-eb5c8c432139.json | 7 +++++++ ...i-react-utils-b125c5c6-290a-4cde-bc2e-78094234bec7.json | 7 +++++++ 3 files changed, 21 insertions(+) create mode 100644 change/@fluentui-react-examples-9fac4130-8756-47e0-b70d-09925f28c31e.json create mode 100644 change/@fluentui-react-menu-c4c9135d-c4fa-4b44-a114-eb5c8c432139.json create mode 100644 change/@fluentui-react-utils-b125c5c6-290a-4cde-bc2e-78094234bec7.json diff --git a/change/@fluentui-react-examples-9fac4130-8756-47e0-b70d-09925f28c31e.json b/change/@fluentui-react-examples-9fac4130-8756-47e0-b70d-09925f28c31e.json new file mode 100644 index 00000000000000..2cf357354d1e36 --- /dev/null +++ b/change/@fluentui-react-examples-9fac4130-8756-47e0-b70d-09925f28c31e.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Implement basic MenuList example", + "packageName": "@fluentui/react-examples", + "email": "lingfan.gao@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-menu-c4c9135d-c4fa-4b44-a114-eb5c8c432139.json b/change/@fluentui-react-menu-c4c9135d-c4fa-4b44-a114-eb5c8c432139.json new file mode 100644 index 00000000000000..c158190e19c4df --- /dev/null +++ b/change/@fluentui-react-menu-c4c9135d-c4fa-4b44-a114-eb5c8c432139.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add MenuList and MenuItem components", + "packageName": "@fluentui/react-menu", + "email": "lingfan.gao@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-utils-b125c5c6-290a-4cde-bc2e-78094234bec7.json b/change/@fluentui-react-utils-b125c5c6-290a-4cde-bc2e-78094234bec7.json new file mode 100644 index 00000000000000..94e10d02a45aaf --- /dev/null +++ b/change/@fluentui-react-utils-b125c5c6-290a-4cde-bc2e-78094234bec7.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add ObjectSlot type", + "packageName": "@fluentui/react-utils", + "email": "lingfan.gao@microsoft.com", + "dependentChangeType": "patch" +} From 61415e6a1c9419e3007252da9366d2faa53b17d1 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 13:37:22 +0000 Subject: [PATCH 28/74] fix deps --- packages/react-menu/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json index 8109b3ec60c60a..7e6ea64cff8504 100644 --- a/packages/react-menu/package.json +++ b/packages/react-menu/package.json @@ -45,9 +45,9 @@ "dependencies": { "@fluentui/react-conformance": "^1.0.0", "@fluentui/react-hooks": "^8.0.0-beta.10", - "@fluentui/react-make-styles": "^0.2.2", - "@fluentui/react-theme": "^0.2.0", - "@fluentui/react-theme-provider": "^1.0.0-beta.19", + "@fluentui/react-make-styles": "^0.2.3", + "@fluentui/react-theme": "^0.3.0", + "@fluentui/react-theme-provider": "^1.0.0-beta.20", "@fluentui/react-utils": "^0.2.0", "@fluentui/set-version": "^8.0.0-beta.1", "tslib": "^1.10.0" From e7a0693ca7278742fd37924a236e730fc549e1cb Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 13:42:29 +0000 Subject: [PATCH 29/74] move conformance to devdep --- packages/react-menu/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json index 7e6ea64cff8504..bbaf01a1292821 100644 --- a/packages/react-menu/package.json +++ b/packages/react-menu/package.json @@ -26,6 +26,7 @@ "update-snapshots": "just-scripts jest -u" }, "devDependencies": { + "@fluentui/react-conformance": "^1.0.0", "@fluentui/eslint-plugin": "^1.0.0-beta.1", "@fluentui/scripts": "^1.0.0", "@types/enzyme": "3.10.3", @@ -43,7 +44,6 @@ "react-test-renderer": "^16.3.0" }, "dependencies": { - "@fluentui/react-conformance": "^1.0.0", "@fluentui/react-hooks": "^8.0.0-beta.10", "@fluentui/react-make-styles": "^0.2.3", "@fluentui/react-theme": "^0.3.0", From 82f9f6b6a834ae27d1af9d1341ef59ae67f482f4 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 13:43:13 +0000 Subject: [PATCH 30/74] update api --- packages/react-menu/etc/react-menu.api.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index ebf3ef7e348688..959f680cb0478b 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -7,6 +7,7 @@ import { ComponentProps } from '@fluentui/react-utils'; import { ObjectSlotProp } from '@fluentui/react-utils'; import * as React from 'react'; +import { RefObjectFunction } from '@fluentui/react-hooks'; import { ShorthandProps } from '@fluentui/react-utils'; // @public @@ -23,6 +24,7 @@ export const menuItemShorthandProps: string[]; // @public (undocumented) export interface MenuItemState extends MenuItemProps { icon?: ObjectSlotProp; + ref: RefObjectFunction; } // @public @@ -34,6 +36,7 @@ export interface MenuListProps extends ComponentProps, React.HTMLAttributes; } // @public From f28db021a6d578e608e25b728190d8505188fc65 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 14:50:15 +0000 Subject: [PATCH 31/74] fixes to types --- .../src/components/MenuItem/MenuItem.types.ts | 3 +-- .../src/components/MenuList/MenuList.types.ts | 3 +-- packages/react-utils/src/compose/types.ts | 10 ++++------ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/react-menu/src/components/MenuItem/MenuItem.types.ts b/packages/react-menu/src/components/MenuItem/MenuItem.types.ts index 42e0a20ae9e46a..d82f73fe2edfa0 100644 --- a/packages/react-menu/src/components/MenuItem/MenuItem.types.ts +++ b/packages/react-menu/src/components/MenuItem/MenuItem.types.ts @@ -1,6 +1,5 @@ import * as React from 'react'; import { ComponentProps, ShorthandProps, ObjectSlotProp } from '@fluentui/react-utils'; -import { RefObjectFunction } from '@fluentui/react-hooks'; export interface MenuItemProps extends ComponentProps, React.HTMLAttributes { /** @@ -13,7 +12,7 @@ export interface MenuItemState extends MenuItemProps { /** * Ref to the root slot */ - ref: RefObjectFunction; + ref: React.MutableRefObject; /** * Icon slot when processed by internal state */ diff --git a/packages/react-menu/src/components/MenuList/MenuList.types.ts b/packages/react-menu/src/components/MenuList/MenuList.types.ts index 26d792d7c152a6..97593059a04c95 100644 --- a/packages/react-menu/src/components/MenuList/MenuList.types.ts +++ b/packages/react-menu/src/components/MenuList/MenuList.types.ts @@ -1,6 +1,5 @@ import * as React from 'react'; import { ComponentProps } from '@fluentui/react-utils'; -import { RefObjectFunction } from '@fluentui/react-hooks'; export interface MenuListProps extends ComponentProps, React.HTMLAttributes {} @@ -8,5 +7,5 @@ export interface MenuListState extends MenuListProps { /** * Ref to the root slot */ - ref: RefObjectFunction; + ref: React.MutableRefObject; } diff --git a/packages/react-utils/src/compose/types.ts b/packages/react-utils/src/compose/types.ts index 428b511fe0acb1..0dca673591e010 100644 --- a/packages/react-utils/src/compose/types.ts +++ b/packages/react-utils/src/compose/types.ts @@ -35,16 +35,14 @@ export type ShorthandProps = children?: TProps['children'] | ShorthandRenderFunction; }); +export type ObjectShorthandProps = TProps & { + children?: TProps['children'] | ShorthandRenderFunction; +}; + export interface BaseSlots { root: React.ElementType; } -export type SlotPropRenderFunction = (Component: React.ElementType, props: TProps) => React.ReactNode; - -export type ObjectSlotProp = TProps & { - children?: TProps['children'] | SlotPropRenderFunction; -}; - export type SlotProps> = { // eslint-disable-next-line @typescript-eslint/no-explicit-any [key in keyof Omit]: key extends keyof TProps ? TProps[key] : any; From f26b13db46df93f072747212ec9dedd2f21811f6 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 14:51:39 +0000 Subject: [PATCH 32/74] remove coment --- packages/react-menu/src/components/MenuItem/MenuItem.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-menu/src/components/MenuItem/MenuItem.types.ts b/packages/react-menu/src/components/MenuItem/MenuItem.types.ts index d82f73fe2edfa0..83a1cf1a15da3b 100644 --- a/packages/react-menu/src/components/MenuItem/MenuItem.types.ts +++ b/packages/react-menu/src/components/MenuItem/MenuItem.types.ts @@ -16,5 +16,5 @@ export interface MenuItemState extends MenuItemProps { /** * Icon slot when processed by internal state */ - icon?: ObjectSlotProp; // TODO use correct props when there is a converged icon pkg + icon?: ObjectSlotProp; } From 8581e4709fefe341cf90feb42dd24c57cc6d7e72 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 14:57:10 +0000 Subject: [PATCH 33/74] fix imports --- packages/react-menu/src/components/index.ts | 4 ++-- packages/react-menu/src/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-menu/src/components/index.ts b/packages/react-menu/src/components/index.ts index 721e4ff57284b6..38c29d35e101cd 100644 --- a/packages/react-menu/src/components/index.ts +++ b/packages/react-menu/src/components/index.ts @@ -1,2 +1,2 @@ -export * from './MenuItem'; -export * from './MenuList'; +export * from './MenuItem/index'; +export * from './MenuList/index'; diff --git a/packages/react-menu/src/index.ts b/packages/react-menu/src/index.ts index 9b43e81f480bec..6da4b9a6af5277 100644 --- a/packages/react-menu/src/index.ts +++ b/packages/react-menu/src/index.ts @@ -1,3 +1,3 @@ import './version'; -export * from './components'; +export * from './components/index'; From 9f54bc558dbb40f0b5bfd17223d271375f349747 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 15:03:00 +0000 Subject: [PATCH 34/74] update api --- packages/react-menu/etc/react-menu.api.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index 959f680cb0478b..99241e4c07b87c 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -7,7 +7,6 @@ import { ComponentProps } from '@fluentui/react-utils'; import { ObjectSlotProp } from '@fluentui/react-utils'; import * as React from 'react'; -import { RefObjectFunction } from '@fluentui/react-hooks'; import { ShorthandProps } from '@fluentui/react-utils'; // @public @@ -24,7 +23,7 @@ export const menuItemShorthandProps: string[]; // @public (undocumented) export interface MenuItemState extends MenuItemProps { icon?: ObjectSlotProp; - ref: RefObjectFunction; + ref: React.MutableRefObject; } // @public @@ -36,7 +35,7 @@ export interface MenuListProps extends ComponentProps, React.HTMLAttributes; + ref: React.MutableRefObject; } // @public From a018d7da48aa81feb7e8e449efb6f38f42701281 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 15:14:05 +0000 Subject: [PATCH 35/74] update API --- packages/react-utils/etc/react-utils.api.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/react-utils/etc/react-utils.api.md b/packages/react-utils/etc/react-utils.api.md index ca6382d1314593..728a77694a0f26 100644 --- a/packages/react-utils/etc/react-utils.api.md +++ b/packages/react-utils/etc/react-utils.api.md @@ -46,8 +46,8 @@ export type MergePropsOptions = { }; // @public (undocumented) -export type ObjectSlotProp = TProps & { - children?: TProps['children'] | SlotPropRenderFunction; +export type ObjectShorthandProps = TProps & { + children?: TProps['children'] | ShorthandRenderFunction; }; // @public @@ -61,9 +61,6 @@ export type ShorthandProps = React.ReactChil // @public (undocumented) export type ShorthandRenderFunction = (Component: React.ElementType, props: TProps) => React.ReactNode; -// @public (undocumented) -export type SlotPropRenderFunction = (Component: React.ElementType, props: TProps) => React.ReactNode; - // @public (undocumented) export type SlotProps> = { [key in keyof Omit]: key extends keyof TProps ? TProps[key] : any; From 6aa381f836f2cae5d35b3af8fbd6670839f9dd6d Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 15:23:54 +0000 Subject: [PATCH 36/74] fix types --- packages/react-menu/etc/react-menu.api.md | 4 ++-- packages/react-menu/src/components/MenuItem/MenuItem.types.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index 99241e4c07b87c..5957c2a304682c 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -5,7 +5,7 @@ ```ts import { ComponentProps } from '@fluentui/react-utils'; -import { ObjectSlotProp } from '@fluentui/react-utils'; +import { ObjectShorthandProps } from '@fluentui/react-utils'; import * as React from 'react'; import { ShorthandProps } from '@fluentui/react-utils'; @@ -22,7 +22,7 @@ export const menuItemShorthandProps: string[]; // @public (undocumented) export interface MenuItemState extends MenuItemProps { - icon?: ObjectSlotProp; + icon?: ObjectShorthandProps; ref: React.MutableRefObject; } diff --git a/packages/react-menu/src/components/MenuItem/MenuItem.types.ts b/packages/react-menu/src/components/MenuItem/MenuItem.types.ts index 83a1cf1a15da3b..5db1be47e3ab57 100644 --- a/packages/react-menu/src/components/MenuItem/MenuItem.types.ts +++ b/packages/react-menu/src/components/MenuItem/MenuItem.types.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { ComponentProps, ShorthandProps, ObjectSlotProp } from '@fluentui/react-utils'; +import { ComponentProps, ShorthandProps, ObjectShorthandProps } from '@fluentui/react-utils'; export interface MenuItemProps extends ComponentProps, React.HTMLAttributes { /** @@ -16,5 +16,5 @@ export interface MenuItemState extends MenuItemProps { /** * Icon slot when processed by internal state */ - icon?: ObjectSlotProp; + icon?: ObjectShorthandProps; } From af33671ba0306b6cc8cae311c95fb0b9466e1b74 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 15:40:11 +0000 Subject: [PATCH 37/74] fix conformance tests --- packages/react-menu/src/MenuItem.ts | 1 + packages/react-menu/src/MenuList.ts | 1 + .../MenuList/__snapshots__/MenuList.test.tsx.snap | 9 +++++++++ packages/react-menu/src/index.ts | 3 ++- 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 packages/react-menu/src/MenuItem.ts create mode 100644 packages/react-menu/src/MenuList.ts create mode 100644 packages/react-menu/src/components/MenuList/__snapshots__/MenuList.test.tsx.snap diff --git a/packages/react-menu/src/MenuItem.ts b/packages/react-menu/src/MenuItem.ts new file mode 100644 index 00000000000000..9dccff40f33698 --- /dev/null +++ b/packages/react-menu/src/MenuItem.ts @@ -0,0 +1 @@ +export * from './components/MenuItem'; diff --git a/packages/react-menu/src/MenuList.ts b/packages/react-menu/src/MenuList.ts new file mode 100644 index 00000000000000..1d9d5da3c1ed18 --- /dev/null +++ b/packages/react-menu/src/MenuList.ts @@ -0,0 +1 @@ +export * from './components/MenuList'; diff --git a/packages/react-menu/src/components/MenuList/__snapshots__/MenuList.test.tsx.snap b/packages/react-menu/src/components/MenuList/__snapshots__/MenuList.test.tsx.snap new file mode 100644 index 00000000000000..d4f387c997350e --- /dev/null +++ b/packages/react-menu/src/components/MenuList/__snapshots__/MenuList.test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MenuList renders a default state 1`] = ` +
    + Default MenuList +
    +`; diff --git a/packages/react-menu/src/index.ts b/packages/react-menu/src/index.ts index 6da4b9a6af5277..7fdd4bfcbc7927 100644 --- a/packages/react-menu/src/index.ts +++ b/packages/react-menu/src/index.ts @@ -1,3 +1,4 @@ import './version'; -export * from './components/index'; +export * from './MenuItem'; +export * from './MenuList'; From c0fd3a53db677e0ac6138655ac59661452f92734 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Mon, 8 Feb 2021 15:52:57 +0000 Subject: [PATCH 38/74] fix exports --- packages/react-menu/src/MenuItem.ts | 2 +- packages/react-menu/src/MenuList.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-menu/src/MenuItem.ts b/packages/react-menu/src/MenuItem.ts index 9dccff40f33698..08c2c2845ff1cc 100644 --- a/packages/react-menu/src/MenuItem.ts +++ b/packages/react-menu/src/MenuItem.ts @@ -1 +1 @@ -export * from './components/MenuItem'; +export * from './components/MenuItem/index'; diff --git a/packages/react-menu/src/MenuList.ts b/packages/react-menu/src/MenuList.ts index 1d9d5da3c1ed18..b6a97c29e9d4aa 100644 --- a/packages/react-menu/src/MenuList.ts +++ b/packages/react-menu/src/MenuList.ts @@ -1 +1 @@ -export * from './components/MenuList'; +export * from './components/MenuList/index'; From 79b953e982924a0e919402b879ff4415416b28fe Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 11:09:09 +0000 Subject: [PATCH 39/74] add checkbox and tests --- packages/react-menu/package.json | 4 +- packages/react-menu/src/MenuItemCheckbox.ts | 1 + .../MenuItemCheckbox.test.tsx | 116 ++++++++++++++++++ .../MenuItemCheckbox/MenuItemCheckbox.tsx | 20 +++ .../MenuItemCheckbox.types.ts | 34 +++++ .../src/components/MenuItemCheckbox/index.ts | 4 + .../renderMenuItemCheckbox.tsx | 17 +++ .../MenuItemCheckbox/useMenuItemCheckbox.ts | 52 ++++++++ packages/react-menu/src/index.ts | 1 + packages/react-menu/src/menuListContext.tsx | 15 +++ packages/react-menu/src/selectable/index.ts | 3 + packages/react-menu/src/selectable/types.ts | 28 +++++ .../src/selectable/useCheckmarkStyles.ts | 22 ++++ .../selectable/useMenuItemSelectable.test.ts | 100 +++++++++++++++ .../src/selectable/useMenuItemSelectable.ts | 50 ++++++++ yarn.lock | 88 +++++++++++++ 16 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 packages/react-menu/src/MenuItemCheckbox.ts create mode 100644 packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx create mode 100644 packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx create mode 100644 packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts create mode 100644 packages/react-menu/src/components/MenuItemCheckbox/index.ts create mode 100644 packages/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx create mode 100644 packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts create mode 100644 packages/react-menu/src/menuListContext.tsx create mode 100644 packages/react-menu/src/selectable/index.ts create mode 100644 packages/react-menu/src/selectable/types.ts create mode 100644 packages/react-menu/src/selectable/useCheckmarkStyles.ts create mode 100644 packages/react-menu/src/selectable/useMenuItemSelectable.test.ts create mode 100644 packages/react-menu/src/selectable/useMenuItemSelectable.ts diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json index bbaf01a1292821..b9213d5cfa56ad 100644 --- a/packages/react-menu/package.json +++ b/packages/react-menu/package.json @@ -26,9 +26,10 @@ "update-snapshots": "just-scripts jest -u" }, "devDependencies": { - "@fluentui/react-conformance": "^1.0.0", "@fluentui/eslint-plugin": "^1.0.0-beta.1", + "@fluentui/react-conformance": "^1.0.0", "@fluentui/scripts": "^1.0.0", + "@testing-library/react": "^11.2.5", "@types/enzyme": "3.10.3", "@types/enzyme-adapter-react-16": "1.0.3", "@types/jest": "~24.9.0", @@ -44,6 +45,7 @@ "react-test-renderer": "^16.3.0" }, "dependencies": { + "@fluentui/keyboard-key": "^0.2.13", "@fluentui/react-hooks": "^8.0.0-beta.10", "@fluentui/react-make-styles": "^0.2.3", "@fluentui/react-theme": "^0.3.0", diff --git a/packages/react-menu/src/MenuItemCheckbox.ts b/packages/react-menu/src/MenuItemCheckbox.ts new file mode 100644 index 00000000000000..065d712fca8850 --- /dev/null +++ b/packages/react-menu/src/MenuItemCheckbox.ts @@ -0,0 +1 @@ +export * from './components/MenuItemCheckbox/index'; diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx new file mode 100644 index 00000000000000..06fc3bcf3ee53b --- /dev/null +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx @@ -0,0 +1,116 @@ +import * as React from 'react'; +import * as renderer from 'react-test-renderer'; +import { render, fireEvent } from '@testing-library/react'; +import { ReactWrapper } from 'enzyme'; +import { isConformant } from '../../common/isConformant'; +import { MenuItemCheckbox } from './MenuItemCheckbox'; +import { MenuListContext, MenuListProvider } from '../../menuListContext'; + +describe('MenuItemCheckbox conformance', () => { + isConformant({ + Component: MenuItemCheckbox, + displayName: 'MenuItemCheckbox', + }); + + let wrapper: ReactWrapper | undefined; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = undefined; + } + }); + + /** + * Note: see more visual regression tests for MenuItemCheckbox in /apps/vr-tests. + */ + it('renders a default state', () => { + const component = renderer.create(Default MenuItemCheckbox); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); +}); + +describe('MenuItemCheckbox', () => { + const TestMenuListContext = (props: { children: React.ReactNode; context?: Partial }) => { + const contextValue: MenuListContext = { + checkedValues: {}, + onCheckedValuesChange: jest.fn(), + ...(props.context && props.context), + }; + + return {props.children}; + }; + + it('should render checkmark slot if checked', () => { + // Arrange + const checkedValues = { test: ['1'] }; + const checkmark = 'xxx'; + const { getByText } = render( + + + Checkbox + + , + ); + + // Assert + expect(getByText(checkmark)).not.toBeNull(); + }); + + it('should render icon slot', () => { + // Arrange + const icon = 'xxx'; + const { getByText } = render( + + + Checkbox + + , + ); + + // Assert + expect(getByText(icon)).not.toBeNull(); + }); + + it('should set aria-checked value to true if value is checked', () => { + // Arrange + const checkedValues = { test: ['1'] }; + const checkmark = 'xxx'; + const { container } = render( + + + Checkbox + + , + ); + + // Assert + expect(container.querySelector('[role="menuitemcheckbox"')?.getAttribute('aria-checked')).toEqual('true'); + }); + + it.each([ + ['uncheck', ['1'], []], + ['check', [], ['1']], + ])('should %s checkbox on click', (_, checkedItems, expectedResult) => { + // Arrange + const checkboxName = 'name'; + const checkedValues = { [checkboxName]: checkedItems }; + const spy = jest.fn(); + const { container } = render( + + + Checkbox + + , + ); + + // Act + const menuitem = container.querySelector('[role="menuitemcheckbox"'); + menuitem && fireEvent.click(menuitem); + + // Assert + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(checkboxName, [...expectedResult]); + }); +}); diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx new file mode 100644 index 00000000000000..cbce2d52824a93 --- /dev/null +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { useMenuItemCheckbox } from './useMenuItemCheckbox'; +import { MenuItemCheckboxProps } from './MenuItemCheckbox.types'; +import { renderMenuItemCheckbox } from './renderMenuItemCheckbox'; +import { useCheckmarkStyles } from '../../selectable'; +import { useMenuItemStyles } from '../MenuItem/useMenuItemStyles'; + +/** + * Define a styled MenuItemCheckbox, using the `useMenuItemCheckbox` hook. + * {@docCategory MenuItemCheckbox} + */ +export const MenuItemCheckbox = React.forwardRef((props, ref) => { + const state = useMenuItemCheckbox(props, ref); + useMenuItemStyles(state); + useCheckmarkStyles(state); + + return renderMenuItemCheckbox(state); +}); + +MenuItemCheckbox.displayName = 'MenuItemCheckbox'; diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts new file mode 100644 index 00000000000000..86c7786528c677 --- /dev/null +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { ComponentProps, ObjectShorthandProps, ShorthandProps } from '@fluentui/react-utils'; +import { MenuItemSelectableProps, MenuItemSelectableState } from '../../selectable'; +import { MenuItemProps, MenuItemState } from '../MenuItem/MenuItem.types'; + +export interface MenuItemCheckboxProps + extends ComponentProps, + React.HTMLAttributes, + MenuItemProps, + MenuItemSelectableProps { + /** + * Icon slot rendered before children content + */ + icon?: ShorthandProps; + + /** + * Slot for the checkmark indicator + */ + checkmark?: ShorthandProps; +} + +export interface MenuItemCheckboxState extends MenuItemCheckboxProps, MenuItemState, MenuItemSelectableState { + ref: React.MutableRefObject; + + /** + * Icon slot rendered before children content + */ + icon?: ObjectShorthandProps; + + /** + * Slot for the checkmark indicator + */ + checkmark?: ObjectShorthandProps; +} diff --git a/packages/react-menu/src/components/MenuItemCheckbox/index.ts b/packages/react-menu/src/components/MenuItemCheckbox/index.ts new file mode 100644 index 00000000000000..fd28717014386b --- /dev/null +++ b/packages/react-menu/src/components/MenuItemCheckbox/index.ts @@ -0,0 +1,4 @@ +export * from './MenuItemCheckbox.types'; +export * from './MenuItemCheckbox'; +export * from './renderMenuItemCheckbox'; +export * from './useMenuItemCheckbox'; diff --git a/packages/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx b/packages/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx new file mode 100644 index 00000000000000..c871d9a7ee1776 --- /dev/null +++ b/packages/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utils'; +import { MenuItemCheckboxState } from './MenuItemCheckbox.types'; +import { menuItemCheckboxShorthandProps } from './useMenuItemCheckbox'; + +/** Function that renders the final JSX of the component */ +export const renderMenuItemCheckbox = (state: MenuItemCheckboxState) => { + const { slots, slotProps } = getSlots(state, menuItemCheckboxShorthandProps); + + return ( + + {state.checked && } + + {state.children} + + ); +}; diff --git a/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts new file mode 100644 index 00000000000000..529472a47b56ab --- /dev/null +++ b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts @@ -0,0 +1,52 @@ +import * as React from 'react'; +import { makeMergeProps, resolveShorthandProps } from '@fluentui/react-utils'; +import { MenuItemCheckboxProps, MenuItemCheckboxState } from './MenuItemCheckbox.types'; +import { useMenuListContext } from '../../menuListContext'; +import { useMenuItemSelectable } from '../../selectable'; +import { useMergedRefs } from '@fluentui/react-hooks'; + +/** + * Consts listing which props are shorthand props. + */ +export const menuItemCheckboxShorthandProps = ['icon', 'checkmark']; + +const mergeProps = makeMergeProps({ deepMerge: menuItemCheckboxShorthandProps }); + +/** Returns the props and state required to render the component */ +export const useMenuItemCheckbox = ( + props: MenuItemCheckboxProps, + ref: React.Ref, + defaultProps?: MenuItemCheckboxProps, +): MenuItemCheckboxState => { + const state = mergeProps( + { + ref: useMergedRefs(ref, React.useRef(null)), + as: 'div', + icon: { as: 'span' }, + checkmark: { as: 'span' }, + role: 'menuitemcheckbox', + tabIndex: 0, + }, + defaultProps, + resolveShorthandProps(props, menuItemCheckboxShorthandProps), + ); + + const { checkedValues: { [state.name]: checkedItems = [] } = {}, onCheckedValuesChange } = useMenuListContext(); + state.checkedItems = checkedItems; + state.onCheckedValuesChange = onCheckedValuesChange || (() => null); + state.checked = checkedItems.indexOf(state.value) !== -1; + + useMenuItemSelectable(state, () => { + const newCheckedItems = [...state.checkedItems]; + const index = state.checkedItems.indexOf(state.value); + if (index !== -1) { + newCheckedItems.splice(index, 1); + } else { + newCheckedItems.push(state.value); + } + + return newCheckedItems; + }); + + return state; +}; diff --git a/packages/react-menu/src/index.ts b/packages/react-menu/src/index.ts index 7fdd4bfcbc7927..053db49a75181d 100644 --- a/packages/react-menu/src/index.ts +++ b/packages/react-menu/src/index.ts @@ -2,3 +2,4 @@ import './version'; export * from './MenuItem'; export * from './MenuList'; +export * from './MenuItemCheckbox'; diff --git a/packages/react-menu/src/menuListContext.tsx b/packages/react-menu/src/menuListContext.tsx new file mode 100644 index 00000000000000..1b9154d8fe8398 --- /dev/null +++ b/packages/react-menu/src/menuListContext.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +const MenuListContext = React.createContext({ + checkedValues: {}, + onCheckedValuesChange: () => null, +}); + +export interface MenuListContext { + checkedValues?: Record; + onCheckedValuesChange?: (name: string, value: string[]) => void; +} + +export const MenuListProvider = MenuListContext.Provider; + +export const useMenuListContext = () => React.useContext(MenuListContext); diff --git a/packages/react-menu/src/selectable/index.ts b/packages/react-menu/src/selectable/index.ts new file mode 100644 index 00000000000000..2a83872c4bb959 --- /dev/null +++ b/packages/react-menu/src/selectable/index.ts @@ -0,0 +1,3 @@ +export * from './types'; +export * from './useMenuItemSelectable'; +export * from './useCheckmarkStyles'; diff --git a/packages/react-menu/src/selectable/types.ts b/packages/react-menu/src/selectable/types.ts new file mode 100644 index 00000000000000..ec7effc985a315 --- /dev/null +++ b/packages/react-menu/src/selectable/types.ts @@ -0,0 +1,28 @@ +import * as React from 'react'; + +/** Props for selecatble menu items */ +export interface MenuItemSelectableProps extends React.HTMLAttributes { + /** + * Follows input convention + * https://www.w3schools.com/jsref/prop_checkbox_name.asp + */ + name: string; + + /** + * Follows input convention + * https://www.w3schools.com/jsref/prop_checkbox_value.asp + */ + value: string; +} + +/** State for selectable menu items */ +export interface MenuItemSelectableState extends MenuItemSelectableProps { + /** Checked values for a give `name` */ + checkedItems: string[]; + + /** Callback when checked values changes for a given `name` */ + onCheckedValuesChange: (name: string, value: string[]) => void; + + /** Selectable is checked */ + checked: boolean; +} diff --git a/packages/react-menu/src/selectable/useCheckmarkStyles.ts b/packages/react-menu/src/selectable/useCheckmarkStyles.ts new file mode 100644 index 00000000000000..d792f87cd30705 --- /dev/null +++ b/packages/react-menu/src/selectable/useCheckmarkStyles.ts @@ -0,0 +1,22 @@ +import { makeStyles, ax } from '@fluentui/react-make-styles'; +import { MenuItemSelectableState } from './types'; + +/** Style hook for checkmark icons */ +const useStyles = makeStyles([ + [ + null, + () => ({ + width: '16px', + height: '16px', + marginRight: '9px', + }), + ], +]); + +/** Applies styles to a checkmark slot for selectable menu items */ +export const useCheckmarkStyles = (state: MenuItemSelectableState) => { + const checkmarkClassName = useStyles({}); + if (state.checkmark) { + state.checkmark.className = ax(checkmarkClassName, state.checkmark.className); + } +}; diff --git a/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts b/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts new file mode 100644 index 00000000000000..168cebdac54c7b --- /dev/null +++ b/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts @@ -0,0 +1,100 @@ +import { EnterKey, SpacebarKey } from '@fluentui/keyboard-key'; +import { MenuItemSelectableState } from './types'; +import { useMenuItemSelectable } from './useMenuItemSelectable'; + +describe('useMenuItemSelectable', () => { + const createTestState = (options: Partial = {}): MenuItemSelectableState => ({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + checkmark: {}, + name: 'name', + value: 'value', + checkedItems: ['1', '2', '3'], + onCheckedValuesChange: jest.fn(), + checked: false, + ...options, + }); + + it.each([ + [false, true], + [true, false], + [undefined, false], + [undefined, true], + ])('should set aria-checked to checked state', (ariaChecked, checked) => { + // Arrange + const state: MenuItemSelectableState = createTestState({ 'aria-checked': ariaChecked, checked }); + + // Act + useMenuItemSelectable(state, jest.fn()); + + // Assert + expect(state['aria-checked']).toBe(checked); + }); + + it('should not call onCheckedValuesChange if values did not change', () => { + // Arrange + const state: MenuItemSelectableState = createTestState(); + + // Act + useMenuItemSelectable(state, () => [...state.checkedItems]); + + // Assert + expect(state.onCheckedValuesChange).not.toHaveBeenCalled(); + }); + + it.each(['onClick', 'onKeyDown'])('should set %s handler', action => { + // Arrange + const state: MenuItemSelectableState = createTestState({ onKeyDown: undefined, onClick: undefined }); + + // Act + useMenuItemSelectable(state, jest.fn()); + + // Assert + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const callback = state[action]; + expect(callback).toBeDefined(); + expect(typeof callback).toBe('function'); + }); + + it('should call onCheckedValuesChange if values changed', () => { + // Arrange + const state: MenuItemSelectableState = createTestState(); + const newValues = [...state.checkedItems, 'x']; + + // Act + useMenuItemSelectable(state, () => newValues); + if (state.onClick) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + state.onClick(null); + } + + // Assert + expect(state.onCheckedValuesChange).toHaveBeenCalledTimes(1); + expect(state.onCheckedValuesChange).toHaveBeenCalledWith(state.name, newValues); + }); + + it.each([EnterKey, SpacebarKey])('should transform %s keydown to click', keyCode => { + // Arrange + const state: MenuItemSelectableState = createTestState(); + const event = { + defaultPrevented: false, + target: { click: jest.fn() }, + stopPropagation: jest.fn(), + preventDefault: jest.fn(), + keyCode, + }; + + // Act + useMenuItemSelectable(state, jest.fn()); + if (state.onKeyDown) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + state.onKeyDown(event); + } + + // Assert + expect(event.target.click).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/react-menu/src/selectable/useMenuItemSelectable.ts b/packages/react-menu/src/selectable/useMenuItemSelectable.ts new file mode 100644 index 00000000000000..4270f9f0518116 --- /dev/null +++ b/packages/react-menu/src/selectable/useMenuItemSelectable.ts @@ -0,0 +1,50 @@ +import { EnterKey, getCode, SpacebarKey } from '@fluentui/keyboard-key'; +import { MenuItemSelectableState } from './types'; + +/** + * Hook used to mutate state to handle selection logic for selectable menu items + * + * @param state Selectable menu item state + * @param getNewCheckedItems Callback that returns the new checked values for given menu item + */ +export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewCheckedItems: () => string[]) => { + const { onClick: onClickCallback, onKeyDown: onKeyDownCallback } = state; + + const onSelectionChange = () => { + const newCheckedItems = getNewCheckedItems(); + + if ( + newCheckedItems.length === state.checkedItems.length && + state.checkedItems.every((el, i) => el === newCheckedItems[i]) + ) { + return; + } + + state.onCheckedValuesChange(state.name, newCheckedItems); + }; + + state.onClick = e => { + if (onClickCallback) { + onClickCallback(e); + } + + onSelectionChange(); + }; + + state.onKeyDown = e => { + if (onKeyDownCallback) { + onKeyDownCallback(e); + } + + const keyCode = getCode(e); + if (!e.defaultPrevented && (keyCode === EnterKey || keyCode === SpacebarKey)) { + // Translate the keydown enter/space to a click. + e.preventDefault(); + e.stopPropagation(); + + (e.target as HTMLElement).click(); + } + }; + + state['aria-checked'] = state.checked; +}; diff --git a/yarn.lock b/yarn.lock index ae8ffdccd399fc..05dc1628b1f7a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1072,6 +1072,14 @@ pirates "^4.0.0" source-map-support "^0.5.16" +"@babel/runtime-corejs3@^7.10.2": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.13.tgz#53d09813b7c20d616caf258e9325550ff701c039" + integrity sha512-8fSpqYRETHATtNitsCXq8QQbKJP31/KnDl2Wz2Vtui9nKzjss2ysuZtyVsWjBtvkeEFo346gkwjYPab1hvrXkQ== + dependencies: + core-js-pure "^3.0.0" + regenerator-runtime "^0.13.4" + "@babel/runtime-corejs3@^7.7.4", "@babel/runtime-corejs3@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz#f29fc1990307c4c57b10dbd6ce667b27159d9e0d" @@ -1087,6 +1095,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" + integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/standalone@^7.10.4", "@babel/standalone@^7.4.5": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.10.4.tgz#63b9e211bee42e8ba8dfc1c0b68a856150e37bf2" @@ -1616,6 +1631,17 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + "@lerna/add@3.15.0": version "3.15.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.15.0.tgz#10be562f43cde59b60f299083d54ac39520ec60a" @@ -3639,6 +3665,20 @@ dom-accessibility-api "^0.4.2" pretty-format "^25.1.0" +"@testing-library/dom@^7.28.1": + version "7.29.4" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.29.4.tgz#1647c2b478789621ead7a50614ad81ab5ae5b86c" + integrity sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^4.2.2" + chalk "^4.1.0" + dom-accessibility-api "^0.5.4" + lz-string "^1.4.4" + pretty-format "^26.6.2" + "@testing-library/jest-dom@^5.1.1": version "5.1.1" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.1.1.tgz#e88a5c08f9b9f36b384f948a0532eae2abbc8204" @@ -3670,6 +3710,14 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react@^11.2.5": + version "11.2.5" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.5.tgz#ae1c36a66c7790ddb6662c416c27863d87818eb9" + integrity sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^7.28.1" + "@textlint/ast-node-types@^4.0.3": version "4.2.5" resolved "https://registry.yarnpkg.com/@textlint/ast-node-types/-/ast-node-types-4.2.5.tgz#ae13981bc8711c98313a6ac1c361194d6bf2d39b" @@ -3715,6 +3763,11 @@ resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.33.tgz#2728669427cdd74a99e53c9f457ca2866a37c52d" integrity sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ== +"@types/aria-query@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.1.tgz#78b5433344e2f92e8b306c06a5622c50c245bf6b" + integrity sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg== + "@types/babel__core@^7.1.0": version "7.1.2" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f" @@ -4105,6 +4158,13 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" + integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== + dependencies: + "@types/istanbul-lib-report" "*" + "@types/jest-axe@^3.2.1": version "3.2.1" resolved "https://registry.yarnpkg.com/@types/jest-axe/-/jest-axe-3.2.1.tgz#84dc4306c105b304f14a594765beaa6a7aef763e" @@ -5584,6 +5644,14 @@ aria-query@^4.0.2: "@babel/runtime" "^7.7.4" "@babel/runtime-corejs3" "^7.7.4" +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== + dependencies: + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" + arr-diff@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" @@ -9574,6 +9642,11 @@ dom-accessibility-api@^0.4.2: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.3.tgz#93ca9002eb222fd5a343b6e5e6b9cf5929411c4c" integrity sha512-JZ8iPuEHDQzq6q0k7PKMGbrIdsgBB7TRrtVOUm4nSMCExlg5qQG4KXWTH2k90yggjM4tTumRGwTKJSldMzKyLA== +dom-accessibility-api@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" + integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== + dom-converter@^0.2: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -19591,6 +19664,16 @@ pretty-format@^25.1.0: ansi-styles "^4.0.0" react-is "^16.12.0" +pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + pretty-hrtime@^1.0.0, pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -20298,6 +20381,11 @@ react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== +react-is@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" + integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== + react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" From 633c3458ac294effd59a4cbac44e4cddbf6e5c24 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 11:31:05 +0000 Subject: [PATCH 40/74] add snapshots --- .../__snapshots__/MenuItemCheckbox.test.tsx.snap | 14 ++++++++++++++ .../src/selectable/useCheckmarkStyles.ts | 4 ++++ 2 files changed, 18 insertions(+) create mode 100644 packages/react-menu/src/components/MenuItemCheckbox/__snapshots__/MenuItemCheckbox.test.tsx.snap diff --git a/packages/react-menu/src/components/MenuItemCheckbox/__snapshots__/MenuItemCheckbox.test.tsx.snap b/packages/react-menu/src/components/MenuItemCheckbox/__snapshots__/MenuItemCheckbox.test.tsx.snap new file mode 100644 index 00000000000000..6e45e8e5350dcc --- /dev/null +++ b/packages/react-menu/src/components/MenuItemCheckbox/__snapshots__/MenuItemCheckbox.test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MenuItemCheckbox conformance renders a default state 1`] = ` +
    + Default MenuItemCheckbox +
    +`; diff --git a/packages/react-menu/src/selectable/useCheckmarkStyles.ts b/packages/react-menu/src/selectable/useCheckmarkStyles.ts index d792f87cd30705..22c845a05d3967 100644 --- a/packages/react-menu/src/selectable/useCheckmarkStyles.ts +++ b/packages/react-menu/src/selectable/useCheckmarkStyles.ts @@ -16,7 +16,11 @@ const useStyles = makeStyles([ /** Applies styles to a checkmark slot for selectable menu items */ export const useCheckmarkStyles = (state: MenuItemSelectableState) => { const checkmarkClassName = useStyles({}); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore if (state.checkmark) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore state.checkmark.className = ax(checkmarkClassName, state.checkmark.className); } }; From 4be7c775bb5143915567a34bccc40f07b19a2a5b Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 11:47:56 +0000 Subject: [PATCH 41/74] use resolution for pretty-format --- package.json | 2 + packages/react-menu/etc/react-menu.api.md | 30 +++++++++ yarn.lock | 74 +---------------------- 3 files changed, 34 insertions(+), 72 deletions(-) diff --git a/package.json b/package.json index dcbcaa92c9e435..79cede72990c67 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,8 @@ "@types/react": "16.9.42", "@types/react-dom": "16.9.10", "eslint": "^7.1.0", + "//": "pretty-format contains typing only supported by TS 3.8+ remove when support in this repo is available", + "pretty-format": "^24.9.0", "copy-to-clipboard": "3.2.0" }, "syncpack": { diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index 5957c2a304682c..a238f60879914e 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -12,6 +12,30 @@ import { ShorthandProps } from '@fluentui/react-utils'; // @public export const MenuItem: React.ForwardRefExoticComponent & React.RefAttributes>; +// @public +export const MenuItemCheckbox: React.ForwardRefExoticComponent & React.RefAttributes>; + +// Warning: (ae-forgotten-export) The symbol "MenuItemSelectableProps" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export interface MenuItemCheckboxProps extends ComponentProps, React.HTMLAttributes, MenuItemProps, MenuItemSelectableProps { + checkmark?: ShorthandProps; + icon?: ShorthandProps; +} + +// @public +export const menuItemCheckboxShorthandProps: string[]; + +// Warning: (ae-forgotten-export) The symbol "MenuItemSelectableState" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export interface MenuItemCheckboxState extends MenuItemCheckboxProps, MenuItemState, MenuItemSelectableState { + checkmark?: ObjectShorthandProps; + icon?: ObjectShorthandProps; + // (undocumented) + ref: React.MutableRefObject; +} + // @public (undocumented) export interface MenuItemProps extends ComponentProps, React.HTMLAttributes { icon?: ShorthandProps; @@ -41,6 +65,9 @@ export interface MenuListState extends MenuListProps { // @public export const renderMenuItem: (state: MenuItemState) => JSX.Element; +// @public +export const renderMenuItemCheckbox: (state: MenuItemCheckboxState) => JSX.Element; + // @public export const renderMenuList: (state: MenuListState) => JSX.Element; @@ -50,6 +77,9 @@ export const useIconStyles: (selectors: MenuItemState) => string; // @public export const useMenuItem: (props: MenuItemProps, ref: React.Ref, defaultProps?: MenuItemProps | undefined) => MenuItemState; +// @public +export const useMenuItemCheckbox: (props: MenuItemCheckboxProps, ref: React.Ref, defaultProps?: MenuItemCheckboxProps | undefined) => MenuItemCheckboxState; + // @public export const useMenuItemStyles: (state: MenuItemState) => void; diff --git a/yarn.lock b/yarn.lock index 05dc1628b1f7a1..8521e245108e44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1612,7 +1612,7 @@ source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/types@^24.8.0", "@jest/types@^24.9.0": +"@jest/types@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== @@ -1621,27 +1621,6 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@jest/types@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.1.0.tgz#b26831916f0d7c381e11dbb5e103a72aed1b4395" - integrity sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - -"@jest/types@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" - integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - "@lerna/add@3.15.0": version "3.15.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.15.0.tgz#10be562f43cde59b60f299083d54ac39520ec60a" @@ -4158,13 +4137,6 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" -"@types/istanbul-reports@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" - integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== - dependencies: - "@types/istanbul-lib-report" "*" - "@types/jest-axe@^3.2.1": version "3.2.1" resolved "https://registry.yarnpkg.com/@types/jest-axe/-/jest-axe-3.2.1.tgz#84dc4306c105b304f14a594765beaa6a7aef763e" @@ -4705,13 +4677,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yargs@^15.0.0": - version "15.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.3.tgz#41453a0bc7ab393e995d1f5451455638edbd2baf" - integrity sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ== - dependencies: - "@types/yargs-parser" "*" - "@typescript-eslint/eslint-plugin@^2.23.0": version "2.34.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" @@ -19634,17 +19599,7 @@ pretty-error@^2.1.1: renderkid "^2.0.1" utila "~0.4" -pretty-format@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" - integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw== - dependencies: - "@jest/types" "^24.8.0" - ansi-regex "^4.0.0" - ansi-styles "^3.2.0" - react-is "^16.8.4" - -pretty-format@^24.9.0: +pretty-format@24.9.0, pretty-format@^24.8.0, pretty-format@^24.9.0, pretty-format@^25.1.0, pretty-format@^26.6.2: version "24.9.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== @@ -19654,26 +19609,6 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" -pretty-format@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.1.0.tgz#ed869bdaec1356fc5ae45de045e2c8ec7b07b0c8" - integrity sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ== - dependencies: - "@jest/types" "^25.1.0" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^16.12.0" - -pretty-format@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" - integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== - dependencies: - "@jest/types" "^26.6.2" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^17.0.1" - pretty-hrtime@^1.0.0, pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -20381,11 +20316,6 @@ react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== -react-is@^17.0.1: - version "17.0.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" - integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== - react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" From 57c4fff18ab99d23cce9c5676da2f00df1620acf Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 12:53:53 +0000 Subject: [PATCH 42/74] add checkbox story --- .../react-menu/MenuList/MenuList.stories.tsx | 30 +++++++++++++++++-- packages/react-menu/etc/react-menu.api.md | 2 ++ .../src/components/MenuList/MenuList.types.ts | 12 +++++++- .../components/MenuList/renderMenuList.tsx | 8 ++++- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx index d1d7ea2fb82c3a..8bd51681de97d3 100644 --- a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx +++ b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; -import { MenuList, MenuItem } from '@fluentui/react-menu'; +import { MenuList, MenuItem, MenuItemCheckbox } from '@fluentui/react-menu'; import { teamsLightTheme } from '@fluentui/react-theme'; import { FluentProvider } from '@fluentui/react-provider'; -import { CutIcon, PasteIcon, EditIcon } from '@fluentui/react-icons-mdl2'; +import { CutIcon, PasteIcon, EditIcon, AcceptIcon } from '@fluentui/react-icons-mdl2'; import { makeStyles } from '@fluentui/react-make-styles'; const useContainerStyles = makeStyles([ @@ -49,3 +49,29 @@ export const MenuListWithIconsExample = () => ( ); + +export const MenuListWithCheckboxes = () => { + const checkmark = ; + const [checkedValues, setCheckedValues] = React.useState>({}); + const onChange = (name: string, items: string[]) => { + setCheckedValues(s => ({ ...s, [name]: items })); + }; + + return ( + + + + + Item + + + Item + + + Item + + + + + ); +}; diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index a238f60879914e..00bb8efd64dcfb 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -55,6 +55,8 @@ export const MenuList: React.ForwardRefExoticComponent { + checkedValues?: Record; + onCheckedValuesChange?: (name: string, value: string[]) => void; } // @public (undocumented) diff --git a/packages/react-menu/src/components/MenuList/MenuList.types.ts b/packages/react-menu/src/components/MenuList/MenuList.types.ts index 97593059a04c95..ad13352e5ad8b6 100644 --- a/packages/react-menu/src/components/MenuList/MenuList.types.ts +++ b/packages/react-menu/src/components/MenuList/MenuList.types.ts @@ -1,7 +1,17 @@ import * as React from 'react'; import { ComponentProps } from '@fluentui/react-utils'; -export interface MenuListProps extends ComponentProps, React.HTMLAttributes {} +export interface MenuListProps extends ComponentProps, React.HTMLAttributes { + /** + * Callback when checked values change for a specific name + */ + onCheckedValuesChange?: (name: string, value: string[]) => void; + + /** + * Map of all checked values + */ + checkedValues?: Record; +} export interface MenuListState extends MenuListProps { /** diff --git a/packages/react-menu/src/components/MenuList/renderMenuList.tsx b/packages/react-menu/src/components/MenuList/renderMenuList.tsx index aaff89c75a7a5b..f9fea99c45ee88 100644 --- a/packages/react-menu/src/components/MenuList/renderMenuList.tsx +++ b/packages/react-menu/src/components/MenuList/renderMenuList.tsx @@ -1,12 +1,18 @@ import * as React from 'react'; import { getSlots } from '@fluentui/react-utils'; import { MenuListState } from './MenuList.types'; +import { MenuListProvider } from '../../menuListContext'; /** * Function that renders the final JSX of the component */ export const renderMenuList = (state: MenuListState) => { const { slots, slotProps } = getSlots(state); + const { onCheckedValuesChange, checkedValues } = state; - return {state.children}; + return ( + + {state.children} + + ); }; From 561cf82f6cdf7cbe81f58176efa5f183240dfc5b Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 12:56:52 +0000 Subject: [PATCH 43/74] fix types --- packages/react-menu/src/selectable/types.ts | 20 ++++++++++++++----- .../src/selectable/useCheckmarkStyles.ts | 8 ++++++-- yarn.lock | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/react-menu/src/selectable/types.ts b/packages/react-menu/src/selectable/types.ts index ec7effc985a315..9a7b0dc1b293bc 100644 --- a/packages/react-menu/src/selectable/types.ts +++ b/packages/react-menu/src/selectable/types.ts @@ -1,6 +1,8 @@ import * as React from 'react'; -/** Props for selecatble menu items */ +/** + * Props for selecatble menu items + */ export interface MenuItemSelectableProps extends React.HTMLAttributes { /** * Follows input convention @@ -15,14 +17,22 @@ export interface MenuItemSelectableProps extends React.HTMLAttributes void; - /** Selectable is checked */ + /** + * Selectable is checked + */ checked: boolean; } diff --git a/packages/react-menu/src/selectable/useCheckmarkStyles.ts b/packages/react-menu/src/selectable/useCheckmarkStyles.ts index 22c845a05d3967..ee35c7e978ee6f 100644 --- a/packages/react-menu/src/selectable/useCheckmarkStyles.ts +++ b/packages/react-menu/src/selectable/useCheckmarkStyles.ts @@ -1,7 +1,9 @@ import { makeStyles, ax } from '@fluentui/react-make-styles'; import { MenuItemSelectableState } from './types'; -/** Style hook for checkmark icons */ +/** + * Style hook for checkmark icons + */ const useStyles = makeStyles([ [ null, @@ -13,7 +15,9 @@ const useStyles = makeStyles([ ], ]); -/** Applies styles to a checkmark slot for selectable menu items */ +/** + * Applies styles to a checkmark slot for selectable menu items + */ export const useCheckmarkStyles = (state: MenuItemSelectableState) => { const checkmarkClassName = useStyles({}); // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/yarn.lock b/yarn.lock index 0f45988c2b0dbf..f0e43e306819a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19599,7 +19599,7 @@ pretty-error@^2.1.1: renderkid "^2.0.1" utila "~0.4" -pretty-format@24.9.0, pretty-format@^24.8.0, pretty-format@^24.9.0, pretty-format@^25.1.0, pretty-format@^26.6.2: +pretty-format@^24.8.0, pretty-format@^24.9.0, pretty-format@^25.1.0, pretty-format@^26.6.2: version "24.9.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== From 3be15724550d2c34586510461f7ab5a57025993f Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 12:57:50 +0000 Subject: [PATCH 44/74] add comment --- packages/react-menu/src/selectable/useCheckmarkStyles.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-menu/src/selectable/useCheckmarkStyles.ts b/packages/react-menu/src/selectable/useCheckmarkStyles.ts index ee35c7e978ee6f..8e90cb3770f25a 100644 --- a/packages/react-menu/src/selectable/useCheckmarkStyles.ts +++ b/packages/react-menu/src/selectable/useCheckmarkStyles.ts @@ -20,6 +20,8 @@ const useStyles = makeStyles([ */ export const useCheckmarkStyles = (state: MenuItemSelectableState) => { const checkmarkClassName = useStyles({}); + // Would love to use slots in the shared props/state but it doesn't extend properly when prop and state have the same + // key but different types // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (state.checkmark) { From 05038408401839676cca513be3d764e15bd2b48f Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 13:01:45 +0000 Subject: [PATCH 45/74] use storybook decorator --- packages/react-examples/.storybook/preview.js | 6 +- .../react-menu/MenuList/MenuList.stories.tsx | 60 +++++++++---------- .../src/selectable/useCheckmarkStyles.ts | 2 +- 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/packages/react-examples/.storybook/preview.js b/packages/react-examples/.storybook/preview.js index a8768cb4ca56d8..da72a46a81046e 100644 --- a/packages/react-examples/.storybook/preview.js +++ b/packages/react-examples/.storybook/preview.js @@ -6,7 +6,7 @@ import { withInfo } from '@storybook/addon-info'; import { withA11y } from '@storybook/addon-a11y'; import { withKnobs } from '@storybook/addon-knobs'; import { withPerformance } from 'storybook-addon-performance'; -import { withKeytipLayer, withStrictMode, withCompatThemeProvider } from '@fluentui/storybook'; +import { withKeytipLayer, withStrictMode, withCompatThemeProvider, withFluentProvider } from '@fluentui/storybook'; addDecorator(withPerformance); addDecorator(withInfo()); @@ -29,6 +29,10 @@ if ( addDecorator(withCompatThemeProvider); addDecorator(withStrictMode); } +if (['react-menu'].includes('PACKAGE_NAME')) { + addDecorator(withFluentProvider); + addDecorator(withStrictMode); +} addParameters({ a11y: { diff --git a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx index 8bd51681de97d3..5b015d9d6affef 100644 --- a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx +++ b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx @@ -27,27 +27,23 @@ const Container: React.FC = props => { }; export const MenuListExample = () => ( - - - - Item - Item - Item - - - + + + Item + Item + Item + + ); export const MenuListWithIconsExample = () => ( - - - - }>Item - }>Item - }>Item - - - + + + }>Item + }>Item + }>Item + + ); export const MenuListWithCheckboxes = () => { @@ -58,20 +54,18 @@ export const MenuListWithCheckboxes = () => { }; return ( - - - - - Item - - - Item - - - Item - - - - + + + + Item + + + Item + + + Item + + + ); }; diff --git a/packages/react-menu/src/selectable/useCheckmarkStyles.ts b/packages/react-menu/src/selectable/useCheckmarkStyles.ts index 8e90cb3770f25a..2a64ce43bac86b 100644 --- a/packages/react-menu/src/selectable/useCheckmarkStyles.ts +++ b/packages/react-menu/src/selectable/useCheckmarkStyles.ts @@ -20,7 +20,7 @@ const useStyles = makeStyles([ */ export const useCheckmarkStyles = (state: MenuItemSelectableState) => { const checkmarkClassName = useStyles({}); - // Would love to use slots in the shared props/state but it doesn't extend properly when prop and state have the same + // Would use slots in the shared props/state but it doesn't extend properly when prop and state have the same // key but different types // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore From 00e0301a9c12586f336f23c92084ccb22be4c664 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 13:08:46 +0000 Subject: [PATCH 46/74] remove imports, fix types --- .../src/react-menu/MenuList/MenuList.stories.tsx | 2 -- .../MenuItemCheckbox/MenuItemCheckbox.types.ts | 2 +- .../react-menu/src/selectable/useCheckmarkStyles.ts | 11 ++++------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx index 5b015d9d6affef..26e6bfecef199e 100644 --- a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx +++ b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx @@ -1,8 +1,6 @@ import * as React from 'react'; import { MenuList, MenuItem, MenuItemCheckbox } from '@fluentui/react-menu'; -import { teamsLightTheme } from '@fluentui/react-theme'; -import { FluentProvider } from '@fluentui/react-provider'; import { CutIcon, PasteIcon, EditIcon, AcceptIcon } from '@fluentui/react-icons-mdl2'; import { makeStyles } from '@fluentui/react-make-styles'; diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts index 86c7786528c677..9f9f142ae3b29c 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts @@ -30,5 +30,5 @@ export interface MenuItemCheckboxState extends MenuItemCheckboxProps, MenuItemSt /** * Slot for the checkmark indicator */ - checkmark?: ObjectShorthandProps; + checkmark?: ObjectShorthandProps; } diff --git a/packages/react-menu/src/selectable/useCheckmarkStyles.ts b/packages/react-menu/src/selectable/useCheckmarkStyles.ts index 2a64ce43bac86b..ac68d4017f8c75 100644 --- a/packages/react-menu/src/selectable/useCheckmarkStyles.ts +++ b/packages/react-menu/src/selectable/useCheckmarkStyles.ts @@ -1,4 +1,5 @@ import { makeStyles, ax } from '@fluentui/react-make-styles'; +import { ObjectShorthandProps } from '@fluentui/react-utils'; import { MenuItemSelectableState } from './types'; /** @@ -18,15 +19,11 @@ const useStyles = makeStyles([ /** * Applies styles to a checkmark slot for selectable menu items */ -export const useCheckmarkStyles = (state: MenuItemSelectableState) => { +export const useCheckmarkStyles = ( + state: MenuItemSelectableState & { checkmark: ObjectShorthandProps }, +) => { const checkmarkClassName = useStyles({}); - // Would use slots in the shared props/state but it doesn't extend properly when prop and state have the same - // key but different types - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore if (state.checkmark) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore state.checkmark.className = ax(checkmarkClassName, state.checkmark.className); } }; From a2d0408889573c6548035771f016eb725641bd25 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 13:11:01 +0000 Subject: [PATCH 47/74] make checkmark mandatory for checkbox --- .../src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts | 2 +- packages/react-menu/src/selectable/useCheckmarkStyles.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts index 9f9f142ae3b29c..dd56d68c9a5129 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts @@ -30,5 +30,5 @@ export interface MenuItemCheckboxState extends MenuItemCheckboxProps, MenuItemSt /** * Slot for the checkmark indicator */ - checkmark?: ObjectShorthandProps; + checkmark: ObjectShorthandProps; } diff --git a/packages/react-menu/src/selectable/useCheckmarkStyles.ts b/packages/react-menu/src/selectable/useCheckmarkStyles.ts index ac68d4017f8c75..0c5c5f439ae1d2 100644 --- a/packages/react-menu/src/selectable/useCheckmarkStyles.ts +++ b/packages/react-menu/src/selectable/useCheckmarkStyles.ts @@ -18,6 +18,8 @@ const useStyles = makeStyles([ /** * Applies styles to a checkmark slot for selectable menu items + * + * @param state should contain a `checkmark` slot */ export const useCheckmarkStyles = ( state: MenuItemSelectableState & { checkmark: ObjectShorthandProps }, From 83bc003527c06cc94f7d3d99cc97e28673a1fb64 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 13:21:29 +0000 Subject: [PATCH 48/74] make value/item naming more consistent --- packages/react-menu/etc/react-menu.api.md | 4 ++-- .../MenuItemCheckbox/MenuItemCheckbox.test.tsx | 4 ++-- .../MenuItemCheckbox/useMenuItemCheckbox.ts | 4 ++-- .../src/components/MenuList/MenuList.types.ts | 7 +++++-- .../src/components/MenuList/renderMenuList.tsx | 4 ++-- packages/react-menu/src/menuListContext.tsx | 4 ++-- packages/react-menu/src/selectable/types.ts | 6 +++--- .../src/selectable/useMenuItemSelectable.test.ts | 12 ++++++------ .../src/selectable/useMenuItemSelectable.ts | 2 +- 9 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index 00bb8efd64dcfb..cbb105348ab4e3 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -30,7 +30,7 @@ export const menuItemCheckboxShorthandProps: string[]; // // @public (undocumented) export interface MenuItemCheckboxState extends MenuItemCheckboxProps, MenuItemState, MenuItemSelectableState { - checkmark?: ObjectShorthandProps; + checkmark: ObjectShorthandProps; icon?: ObjectShorthandProps; // (undocumented) ref: React.MutableRefObject; @@ -56,7 +56,7 @@ export const MenuList: React.ForwardRefExoticComponent { checkedValues?: Record; - onCheckedValuesChange?: (name: string, value: string[]) => void; + onCheckedValueChange?: (name: string, checkedItems: string[]) => void; } // @public (undocumented) diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx index 06fc3bcf3ee53b..ae3684c7d68c85 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx @@ -35,7 +35,7 @@ describe('MenuItemCheckbox', () => { const TestMenuListContext = (props: { children: React.ReactNode; context?: Partial }) => { const contextValue: MenuListContext = { checkedValues: {}, - onCheckedValuesChange: jest.fn(), + onCheckedValueChange: jest.fn(), ...(props.context && props.context), }; @@ -98,7 +98,7 @@ describe('MenuItemCheckbox', () => { const checkedValues = { [checkboxName]: checkedItems }; const spy = jest.fn(); const { container } = render( - + Checkbox diff --git a/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts index 529472a47b56ab..fbf0d3ed7d311f 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts +++ b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts @@ -31,9 +31,9 @@ export const useMenuItemCheckbox = ( resolveShorthandProps(props, menuItemCheckboxShorthandProps), ); - const { checkedValues: { [state.name]: checkedItems = [] } = {}, onCheckedValuesChange } = useMenuListContext(); + const { checkedValues: { [state.name]: checkedItems = [] } = {}, onCheckedValueChange } = useMenuListContext(); state.checkedItems = checkedItems; - state.onCheckedValuesChange = onCheckedValuesChange || (() => null); + state.onCheckedValueChange = onCheckedValueChange || (() => null); state.checked = checkedItems.indexOf(state.value) !== -1; useMenuItemSelectable(state, () => { diff --git a/packages/react-menu/src/components/MenuList/MenuList.types.ts b/packages/react-menu/src/components/MenuList/MenuList.types.ts index ad13352e5ad8b6..123db6848a3160 100644 --- a/packages/react-menu/src/components/MenuList/MenuList.types.ts +++ b/packages/react-menu/src/components/MenuList/MenuList.types.ts @@ -3,9 +3,12 @@ import { ComponentProps } from '@fluentui/react-utils'; export interface MenuListProps extends ComponentProps, React.HTMLAttributes { /** - * Callback when checked values change for a specific name + * Callback when checked items change for value with a name + * + * @param name the name of the value + * @param checkedItems the items for this value that are checked */ - onCheckedValuesChange?: (name: string, value: string[]) => void; + onCheckedValueChange?: (name: string, checkedItems: string[]) => void; /** * Map of all checked values diff --git a/packages/react-menu/src/components/MenuList/renderMenuList.tsx b/packages/react-menu/src/components/MenuList/renderMenuList.tsx index f9fea99c45ee88..c4ddfed71ff09f 100644 --- a/packages/react-menu/src/components/MenuList/renderMenuList.tsx +++ b/packages/react-menu/src/components/MenuList/renderMenuList.tsx @@ -8,10 +8,10 @@ import { MenuListProvider } from '../../menuListContext'; */ export const renderMenuList = (state: MenuListState) => { const { slots, slotProps } = getSlots(state); - const { onCheckedValuesChange, checkedValues } = state; + const { onCheckedValueChange, checkedValues } = state; return ( - + {state.children} ); diff --git a/packages/react-menu/src/menuListContext.tsx b/packages/react-menu/src/menuListContext.tsx index 1b9154d8fe8398..c45e04ee737342 100644 --- a/packages/react-menu/src/menuListContext.tsx +++ b/packages/react-menu/src/menuListContext.tsx @@ -2,12 +2,12 @@ import * as React from 'react'; const MenuListContext = React.createContext({ checkedValues: {}, - onCheckedValuesChange: () => null, + onCheckedValueChange: () => null, }); export interface MenuListContext { checkedValues?: Record; - onCheckedValuesChange?: (name: string, value: string[]) => void; + onCheckedValueChange?: (name: string, items: string[]) => void; } export const MenuListProvider = MenuListContext.Provider; diff --git a/packages/react-menu/src/selectable/types.ts b/packages/react-menu/src/selectable/types.ts index 9a7b0dc1b293bc..a1ab3c0c4ea375 100644 --- a/packages/react-menu/src/selectable/types.ts +++ b/packages/react-menu/src/selectable/types.ts @@ -22,14 +22,14 @@ export interface MenuItemSelectableProps extends React.HTMLAttributes void; + onCheckedValueChange: (name: string, checkedItems: string[]) => void; /** * Selectable is checked diff --git a/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts b/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts index 168cebdac54c7b..ae520e0494b612 100644 --- a/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts +++ b/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts @@ -10,7 +10,7 @@ describe('useMenuItemSelectable', () => { name: 'name', value: 'value', checkedItems: ['1', '2', '3'], - onCheckedValuesChange: jest.fn(), + onCheckedValueChange: jest.fn(), checked: false, ...options, }); @@ -31,7 +31,7 @@ describe('useMenuItemSelectable', () => { expect(state['aria-checked']).toBe(checked); }); - it('should not call onCheckedValuesChange if values did not change', () => { + it('should not call onCheckedValueChange if values did not change', () => { // Arrange const state: MenuItemSelectableState = createTestState(); @@ -39,7 +39,7 @@ describe('useMenuItemSelectable', () => { useMenuItemSelectable(state, () => [...state.checkedItems]); // Assert - expect(state.onCheckedValuesChange).not.toHaveBeenCalled(); + expect(state.onCheckedValueChange).not.toHaveBeenCalled(); }); it.each(['onClick', 'onKeyDown'])('should set %s handler', action => { @@ -57,7 +57,7 @@ describe('useMenuItemSelectable', () => { expect(typeof callback).toBe('function'); }); - it('should call onCheckedValuesChange if values changed', () => { + it('should call onCheckedValueChange if values changed', () => { // Arrange const state: MenuItemSelectableState = createTestState(); const newValues = [...state.checkedItems, 'x']; @@ -71,8 +71,8 @@ describe('useMenuItemSelectable', () => { } // Assert - expect(state.onCheckedValuesChange).toHaveBeenCalledTimes(1); - expect(state.onCheckedValuesChange).toHaveBeenCalledWith(state.name, newValues); + expect(state.onCheckedValueChange).toHaveBeenCalledTimes(1); + expect(state.onCheckedValueChange).toHaveBeenCalledWith(state.name, newValues); }); it.each([EnterKey, SpacebarKey])('should transform %s keydown to click', keyCode => { diff --git a/packages/react-menu/src/selectable/useMenuItemSelectable.ts b/packages/react-menu/src/selectable/useMenuItemSelectable.ts index 4270f9f0518116..30e764083fef81 100644 --- a/packages/react-menu/src/selectable/useMenuItemSelectable.ts +++ b/packages/react-menu/src/selectable/useMenuItemSelectable.ts @@ -20,7 +20,7 @@ export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewChec return; } - state.onCheckedValuesChange(state.name, newCheckedItems); + state.onCheckedValueChange(state.name, newCheckedItems); }; state.onClick = e => { From 681e63b19b0da809873cc9b11786e4a7fe89b289 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 13:24:36 +0000 Subject: [PATCH 49/74] move checked to selectable state --- .../src/react-menu/MenuList/MenuList.stories.tsx | 2 +- .../src/components/MenuItemCheckbox/useMenuItemCheckbox.ts | 6 ------ packages/react-menu/src/selectable/useMenuItemSelectable.ts | 6 ++++++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx index 26e6bfecef199e..41ac785c4765c2 100644 --- a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx +++ b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx @@ -53,7 +53,7 @@ export const MenuListWithCheckboxes = () => { return ( - + Item diff --git a/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts index fbf0d3ed7d311f..698203b62434fa 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts +++ b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts @@ -1,7 +1,6 @@ import * as React from 'react'; import { makeMergeProps, resolveShorthandProps } from '@fluentui/react-utils'; import { MenuItemCheckboxProps, MenuItemCheckboxState } from './MenuItemCheckbox.types'; -import { useMenuListContext } from '../../menuListContext'; import { useMenuItemSelectable } from '../../selectable'; import { useMergedRefs } from '@fluentui/react-hooks'; @@ -31,11 +30,6 @@ export const useMenuItemCheckbox = ( resolveShorthandProps(props, menuItemCheckboxShorthandProps), ); - const { checkedValues: { [state.name]: checkedItems = [] } = {}, onCheckedValueChange } = useMenuListContext(); - state.checkedItems = checkedItems; - state.onCheckedValueChange = onCheckedValueChange || (() => null); - state.checked = checkedItems.indexOf(state.value) !== -1; - useMenuItemSelectable(state, () => { const newCheckedItems = [...state.checkedItems]; const index = state.checkedItems.indexOf(state.value); diff --git a/packages/react-menu/src/selectable/useMenuItemSelectable.ts b/packages/react-menu/src/selectable/useMenuItemSelectable.ts index 30e764083fef81..ff129568d95e1d 100644 --- a/packages/react-menu/src/selectable/useMenuItemSelectable.ts +++ b/packages/react-menu/src/selectable/useMenuItemSelectable.ts @@ -1,4 +1,5 @@ import { EnterKey, getCode, SpacebarKey } from '@fluentui/keyboard-key'; +import { useMenuListContext } from '../menuListContext'; import { MenuItemSelectableState } from './types'; /** @@ -9,6 +10,11 @@ import { MenuItemSelectableState } from './types'; */ export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewCheckedItems: () => string[]) => { const { onClick: onClickCallback, onKeyDown: onKeyDownCallback } = state; + const { checkedValues: { [state.name]: checkedItems = [] } = {}, onCheckedValueChange } = useMenuListContext(); + + state.checkedItems = checkedItems; + state.onCheckedValueChange = onCheckedValueChange || (() => null); + state.checked = checkedItems.indexOf(state.value) !== -1; const onSelectionChange = () => { const newCheckedItems = getNewCheckedItems(); From 1ab72c8e24aabdb6089989eda7f0a59d6eb116ae Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 13:46:39 +0000 Subject: [PATCH 50/74] Change files --- ...eact-examples-ee4c87f1-4801-40b7-8c39-fbd92ec8ca6d.json | 7 +++++++ ...ui-react-menu-04281faf-a371-42db-8ee1-6ec9abe65a84.json | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 change/@fluentui-react-examples-ee4c87f1-4801-40b7-8c39-fbd92ec8ca6d.json create mode 100644 change/@fluentui-react-menu-04281faf-a371-42db-8ee1-6ec9abe65a84.json diff --git a/change/@fluentui-react-examples-ee4c87f1-4801-40b7-8c39-fbd92ec8ca6d.json b/change/@fluentui-react-examples-ee4c87f1-4801-40b7-8c39-fbd92ec8ca6d.json new file mode 100644 index 00000000000000..8cf5d6fe707836 --- /dev/null +++ b/change/@fluentui-react-examples-ee4c87f1-4801-40b7-8c39-fbd92ec8ca6d.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add checkbox story for Menu", + "packageName": "@fluentui/react-examples", + "email": "lingfan.gao@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-menu-04281faf-a371-42db-8ee1-6ec9abe65a84.json b/change/@fluentui-react-menu-04281faf-a371-42db-8ee1-6ec9abe65a84.json new file mode 100644 index 00000000000000..854ca452f5545b --- /dev/null +++ b/change/@fluentui-react-menu-04281faf-a371-42db-8ee1-6ec9abe65a84.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add checkbox implementation for menu item", + "packageName": "@fluentui/react-menu", + "email": "lingfan.gao@microsoft.com", + "dependentChangeType": "patch" +} From 6c58230f0ae033bf81788a1d49433ef43f9b7875 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 14:43:18 +0000 Subject: [PATCH 51/74] update codeowner for react-menu --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a287946b20df48..e87bd2c9570cf3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -85,7 +85,7 @@ common/_themeOverrides.scss @phkuo common/_common.scss @phkuo ## Component packages -packages/react-menu/ @ling1726 @layershifter +packages/react-menu/ @ling1726 @layershifter @microsoft/fluent-ui packages/react-button/ @dzearing @khmakoto packages/react-cards/ @khmakoto packages/react-checkbox/ @khmakoto @xugao From d4077aaa0f0e75112f2069d700606e4cb1f6c818 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 16:13:53 +0000 Subject: [PATCH 52/74] hide checkmark instead of not rendering --- .../MenuItemCheckbox/renderMenuItemCheckbox.tsx | 2 +- .../react-menu/src/selectable/useCheckmarkStyles.ts | 11 +++++++++-- .../src/selectable/useMenuItemSelectable.ts | 3 +-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx b/packages/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx index c871d9a7ee1776..7f92d005aee9b1 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx +++ b/packages/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx @@ -9,7 +9,7 @@ export const renderMenuItemCheckbox = (state: MenuItemCheckboxState) => { return ( - {state.checked && } + {state.children} diff --git a/packages/react-menu/src/selectable/useCheckmarkStyles.ts b/packages/react-menu/src/selectable/useCheckmarkStyles.ts index 0c5c5f439ae1d2..d22c44551547c4 100644 --- a/packages/react-menu/src/selectable/useCheckmarkStyles.ts +++ b/packages/react-menu/src/selectable/useCheckmarkStyles.ts @@ -5,13 +5,20 @@ import { MenuItemSelectableState } from './types'; /** * Style hook for checkmark icons */ -const useStyles = makeStyles([ +const useStyles = makeStyles([ [ null, () => ({ width: '16px', height: '16px', marginRight: '9px', + visibility: 'hidden', + }), + ], + [ + state => state.checked, + () => ({ + visibility: 'visible', }), ], ]); @@ -24,7 +31,7 @@ const useStyles = makeStyles([ export const useCheckmarkStyles = ( state: MenuItemSelectableState & { checkmark: ObjectShorthandProps }, ) => { - const checkmarkClassName = useStyles({}); + const checkmarkClassName = useStyles(state); if (state.checkmark) { state.checkmark.className = ax(checkmarkClassName, state.checkmark.className); } diff --git a/packages/react-menu/src/selectable/useMenuItemSelectable.ts b/packages/react-menu/src/selectable/useMenuItemSelectable.ts index ff129568d95e1d..63727c297a642e 100644 --- a/packages/react-menu/src/selectable/useMenuItemSelectable.ts +++ b/packages/react-menu/src/selectable/useMenuItemSelectable.ts @@ -15,6 +15,7 @@ export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewChec state.checkedItems = checkedItems; state.onCheckedValueChange = onCheckedValueChange || (() => null); state.checked = checkedItems.indexOf(state.value) !== -1; + state['aria-checked'] = state.checked; const onSelectionChange = () => { const newCheckedItems = getNewCheckedItems(); @@ -51,6 +52,4 @@ export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewChec (e.target as HTMLElement).click(); } }; - - state['aria-checked'] = state.checked; }; From 1f0329a17842f0d1961ab6bd9d37bb2edd4965ba Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 17:14:22 +0000 Subject: [PATCH 53/74] add testing-library/hooks --- packages/react-menu/package.json | 1 + .../selectable/useMenuItemSelectable.test.ts | 80 ++++++++++++------- yarn.lock | 35 +++++++- 3 files changed, 84 insertions(+), 32 deletions(-) diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json index 955cf5147e226d..3459a4cc0b60f5 100644 --- a/packages/react-menu/package.json +++ b/packages/react-menu/package.json @@ -52,6 +52,7 @@ "@fluentui/react-theme-provider": "^1.0.0-beta.21", "@fluentui/react-utils": "^0.3.0", "@fluentui/set-version": "^8.0.0-beta.1", + "@testing-library/react-hooks": "^5.0.3", "tslib": "^1.10.0" }, "peerDependencies": { diff --git a/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts b/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts index ae520e0494b612..ded1f1f2c8d2dc 100644 --- a/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts +++ b/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts @@ -1,6 +1,10 @@ import { EnterKey, SpacebarKey } from '@fluentui/keyboard-key'; +import { renderHook } from '@testing-library/react-hooks'; import { MenuItemSelectableState } from './types'; import { useMenuItemSelectable } from './useMenuItemSelectable'; +import { useMenuListContext } from '../menuListContext'; + +jest.mock('../menuListContext'); describe('useMenuItemSelectable', () => { const createTestState = (options: Partial = {}): MenuItemSelectableState => ({ @@ -9,34 +13,68 @@ describe('useMenuItemSelectable', () => { checkmark: {}, name: 'name', value: 'value', - checkedItems: ['1', '2', '3'], onCheckedValueChange: jest.fn(), - checked: false, ...options, }); + const checkedItems = ['1', '2', '3']; + it.each([ - [false, true], - [true, false], + [['1'], true], + [['2'], false], [undefined, false], - [undefined, true], - ])('should set aria-checked to checked state', (ariaChecked, checked) => { + [[], false], + [['3', '1', '2'], true], + ])('should set checked and aria-checked', (values, expected) => { + // Arrange + const state: MenuItemSelectableState = createTestState({ value: '1', name: 'test' }); + (useMenuListContext as jest.Mock).mockReturnValue({ checkedValues: { test: values } }); + + // Act + renderHook(() => useMenuItemSelectable(state, jest.fn())); + + // Assert + expect(state.checked).toBe(expected); + expect(state['aria-checked']).toBe(expected); + }); + + it('should call onCheckedValueChange if values changed', () => { // Arrange - const state: MenuItemSelectableState = createTestState({ 'aria-checked': ariaChecked, checked }); + const state: MenuItemSelectableState = createTestState(); + (useMenuListContext as jest.Mock).mockReturnValue({ + onCheckedValueChange: jest.fn(), + checkedValues: { [state.name]: [...checkedItems] }, + }); + const newValues = [...checkedItems, 'x']; // Act - useMenuItemSelectable(state, jest.fn()); + renderHook(() => useMenuItemSelectable(state, () => newValues)); + if (state.onClick) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + state.onClick(null); + } // Assert - expect(state['aria-checked']).toBe(checked); + expect(state.onCheckedValueChange).toHaveBeenCalledTimes(1); + expect(state.onCheckedValueChange).toHaveBeenCalledWith(state.name, newValues); }); it('should not call onCheckedValueChange if values did not change', () => { // Arrange const state: MenuItemSelectableState = createTestState(); + (useMenuListContext as jest.Mock).mockReturnValue({ + onCheckedValueChange: jest.fn(), + checkedValues: { [state.name]: [...checkedItems] }, + }); // Act - useMenuItemSelectable(state, () => [...state.checkedItems]); + renderHook(() => useMenuItemSelectable(state, () => [...checkedItems])); + if (state.onClick) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + state.onClick(null); + } // Assert expect(state.onCheckedValueChange).not.toHaveBeenCalled(); @@ -47,7 +85,7 @@ describe('useMenuItemSelectable', () => { const state: MenuItemSelectableState = createTestState({ onKeyDown: undefined, onClick: undefined }); // Act - useMenuItemSelectable(state, jest.fn()); + renderHook(() => useMenuItemSelectable(state, jest.fn())); // Assert // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -57,24 +95,6 @@ describe('useMenuItemSelectable', () => { expect(typeof callback).toBe('function'); }); - it('should call onCheckedValueChange if values changed', () => { - // Arrange - const state: MenuItemSelectableState = createTestState(); - const newValues = [...state.checkedItems, 'x']; - - // Act - useMenuItemSelectable(state, () => newValues); - if (state.onClick) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - state.onClick(null); - } - - // Assert - expect(state.onCheckedValueChange).toHaveBeenCalledTimes(1); - expect(state.onCheckedValueChange).toHaveBeenCalledWith(state.name, newValues); - }); - it.each([EnterKey, SpacebarKey])('should transform %s keydown to click', keyCode => { // Arrange const state: MenuItemSelectableState = createTestState(); @@ -87,7 +107,7 @@ describe('useMenuItemSelectable', () => { }; // Act - useMenuItemSelectable(state, jest.fn()); + renderHook(() => useMenuItemSelectable(state, jest.fn())); if (state.onKeyDown) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/yarn.lock b/yarn.lock index f0e43e306819a8..8554b14d5704bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3689,6 +3689,18 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react-hooks@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-5.0.3.tgz#dd0d2048817b013b266d35ca45e3ea48a19fd87e" + integrity sha512-UrnnRc5II7LMH14xsYNm/WRch/67cBafmrSQcyFh0v+UUmSf1uzfB7zn5jQXSettGwOSxJwdQUN7PgkT0w22Lg== + dependencies: + "@babel/runtime" "^7.12.5" + "@types/react" ">=16.9.0" + "@types/react-dom" ">=16.9.0" + "@types/react-test-renderer" ">=16.9.0" + filter-console "^0.1.1" + react-error-boundary "^3.1.0" + "@testing-library/react@^11.2.5": version "11.2.5" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.5.tgz#ae1c36a66c7790ddb6662c416c27863d87818eb9" @@ -4319,7 +4331,7 @@ dependencies: "@types/react" "*" -"@types/react-dom@16.9.10": +"@types/react-dom@16.9.10", "@types/react-dom@>=16.9.0": version "16.9.10" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.10.tgz#4485b0bec3d41f856181b717f45fd7831101156f" integrity sha512-ItatOrnXDMAYpv6G8UCk2VhbYVTjZT9aorLtA/OzDN9XJ2GKcfam68jutoAcILdRjsRUO8qb7AmyObF77Q8QFw== @@ -4385,6 +4397,13 @@ dependencies: "@types/react" "*" +"@types/react-test-renderer@>=16.9.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.0.tgz#9be47b375eeb906fced37049e67284a438d56620" + integrity sha512-nvw+F81OmyzpyIE1S0xWpLonLUZCMewslPuA8BtjSKc5XEbn8zEQBXS7KuOLHTNnSOEM2Pum50gHOoZ62tqTRg== + dependencies: + "@types/react" "*" + "@types/react-test-renderer@^16.0.0": version "16.8.2" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.8.2.tgz#ad544b5571ebfc5f182c320376f1431a2b725c5e" @@ -4414,7 +4433,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@16.9.42", "@types/react@^16": +"@types/react@*", "@types/react@16.9.42", "@types/react@>=16.9.0", "@types/react@^16": version "16.9.42" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.42.tgz#9776508d59c1867bbf9bd7f036dab007fdaa1cb7" integrity sha512-iGy6HwfVfotqJ+PfRZ4eqPHPP5NdPZgQlr0lTs8EfkODRBV9cYy8QMKcC9qPCe1JrESC1Im6SrCFR6tQgg74ag== @@ -11349,6 +11368,11 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +filter-console@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/filter-console/-/filter-console-0.1.1.tgz#6242be28982bba7415bcc6db74a79f4a294fa67c" + integrity sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg== + finalhandler@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" @@ -20209,6 +20233,13 @@ react-element-to-jsx-string@^14.0.2: "@base2/pretty-print-object" "1.0.0" is-plain-object "3.0.0" +react-error-boundary@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.0.tgz#9487443df2f9ba1db90d8ab52351814907ea4af3" + integrity sha512-lmPrdi5SLRJR+AeJkqdkGlW/CRkAUvZnETahK58J4xb5wpbfDngasEGu+w0T1iXEhVrYBJZeW+c4V1hILCnMWQ== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-overlay@^6.0.3: version "6.0.4" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.4.tgz#0d165d6d27488e660bc08e57bdabaad741366f7a" From 6530e2476d9eddca906fda3e12089fa3a8a75d1c Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 17:18:43 +0000 Subject: [PATCH 54/74] revert codeowners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e87bd2c9570cf3..a287946b20df48 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -85,7 +85,7 @@ common/_themeOverrides.scss @phkuo common/_common.scss @phkuo ## Component packages -packages/react-menu/ @ling1726 @layershifter @microsoft/fluent-ui +packages/react-menu/ @ling1726 @layershifter packages/react-button/ @dzearing @khmakoto packages/react-cards/ @khmakoto packages/react-checkbox/ @khmakoto @xugao From 786b3613611c6039c3bf05590e60c30ee849d0e3 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 17:30:44 +0000 Subject: [PATCH 55/74] correct tsdoc --- packages/react-menu/src/components/MenuList/MenuList.types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-menu/src/components/MenuList/MenuList.types.ts b/packages/react-menu/src/components/MenuList/MenuList.types.ts index 123db6848a3160..f82d6487b942da 100644 --- a/packages/react-menu/src/components/MenuList/MenuList.types.ts +++ b/packages/react-menu/src/components/MenuList/MenuList.types.ts @@ -5,8 +5,8 @@ export interface MenuListProps extends ComponentProps, React.HTMLAttributes void; From 8d850dd368e3dfaff567e01695f465ef5fc86835 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 18:15:17 +0000 Subject: [PATCH 56/74] fix imports --- .../src/components/MenuItemCheckbox/MenuItemCheckbox.tsx | 2 +- .../src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts | 2 +- .../src/components/MenuItemCheckbox/useMenuItemCheckbox.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx index cbce2d52824a93..fff4eca3448c86 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { useMenuItemCheckbox } from './useMenuItemCheckbox'; import { MenuItemCheckboxProps } from './MenuItemCheckbox.types'; import { renderMenuItemCheckbox } from './renderMenuItemCheckbox'; -import { useCheckmarkStyles } from '../../selectable'; +import { useCheckmarkStyles } from '../../selectable/index'; import { useMenuItemStyles } from '../MenuItem/useMenuItemStyles'; /** diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts index dd56d68c9a5129..90c4fdb2f1e179 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { ComponentProps, ObjectShorthandProps, ShorthandProps } from '@fluentui/react-utils'; -import { MenuItemSelectableProps, MenuItemSelectableState } from '../../selectable'; +import { MenuItemSelectableProps, MenuItemSelectableState } from '../../selectable/index'; import { MenuItemProps, MenuItemState } from '../MenuItem/MenuItem.types'; export interface MenuItemCheckboxProps diff --git a/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts index 698203b62434fa..facdf8ce63c215 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts +++ b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { makeMergeProps, resolveShorthandProps } from '@fluentui/react-utils'; import { MenuItemCheckboxProps, MenuItemCheckboxState } from './MenuItemCheckbox.types'; -import { useMenuItemSelectable } from '../../selectable'; +import { useMenuItemSelectable } from '../../selectable/index'; import { useMergedRefs } from '@fluentui/react-hooks'; /** From 283c7d376e3a3034f84fc41e823724ebbfeddaa7 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 9 Feb 2021 19:07:47 +0000 Subject: [PATCH 57/74] add icons to checkboxes, fix hover/focus styles --- .../src/react-menu/MenuList/MenuList.stories.tsx | 8 ++++---- .../src/components/MenuItem/useMenuItemStyles.ts | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx index 41ac785c4765c2..456008c8b88c32 100644 --- a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx +++ b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx @@ -46,7 +46,7 @@ export const MenuListWithIconsExample = () => ( export const MenuListWithCheckboxes = () => { const checkmark = ; - const [checkedValues, setCheckedValues] = React.useState>({}); + const [checkedValues, setCheckedValues] = React.useState>({ checkbox: ['2'] }); const onChange = (name: string, items: string[]) => { setCheckedValues(s => ({ ...s, [name]: items })); }; @@ -54,13 +54,13 @@ export const MenuListWithCheckboxes = () => { return ( - + } name="checkbox" value="1" checkmark={checkmark}> Item - + } name="checkbox" value="2" checkmark={checkmark}> Item - + } name="checkbox" value="3" checkmark={checkmark}> Item diff --git a/packages/react-menu/src/components/MenuItem/useMenuItemStyles.ts b/packages/react-menu/src/components/MenuItem/useMenuItemStyles.ts index 510e8262614242..bfb4f026f92893 100644 --- a/packages/react-menu/src/components/MenuItem/useMenuItemStyles.ts +++ b/packages/react-menu/src/components/MenuItem/useMenuItemStyles.ts @@ -19,10 +19,12 @@ export const useRootStyles = makeStyles([ ':hover': { backgroundColor: theme.alias.color.neutral.neutralBackground1Hover, + color: theme.alias.color.neutral.neutralForeground2Hover, }, ':focus': { backgroundColor: theme.alias.color.neutral.neutralBackground1Hover, + color: theme.alias.color.neutral.neutralForeground2Hover, }, }), ], From 4e127087026f97d58c0d60722719cd525687b95f Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 10 Feb 2021 14:10:56 +0000 Subject: [PATCH 58/74] downgrade testing library and resolove testing-library/dom instead --- package.json | 2 +- packages/react-menu/package.json | 2 +- yarn.lock | 93 +++++++++++++++----------------- 3 files changed, 45 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index 79cede72990c67..3d9e00fb5e63c6 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@types/react-dom": "16.9.10", "eslint": "^7.1.0", "//": "pretty-format contains typing only supported by TS 3.8+ remove when support in this repo is available", - "pretty-format": "^24.9.0", + "@testing-library/dom": "7.22.3", "copy-to-clipboard": "3.2.0" }, "syncpack": { diff --git a/packages/react-menu/package.json b/packages/react-menu/package.json index 3459a4cc0b60f5..0a35702cfac1a7 100644 --- a/packages/react-menu/package.json +++ b/packages/react-menu/package.json @@ -29,7 +29,7 @@ "@fluentui/eslint-plugin": "^1.0.0-beta.1", "@fluentui/react-conformance": "^1.0.0", "@fluentui/scripts": "^1.0.0", - "@testing-library/react": "^11.2.5", + "@testing-library/react": "^10.4.9", "@types/enzyme": "3.10.3", "@types/enzyme-adapter-react-16": "1.0.3", "@types/jest": "~24.9.0", diff --git a/yarn.lock b/yarn.lock index 8554b14d5704bd..3fbe61c9e226bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1095,7 +1095,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.5": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw== @@ -1621,6 +1621,16 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" +"@jest/types@^25.5.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" + integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + "@lerna/add@3.15.0": version "3.15.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.15.0.tgz#10be562f43cde59b60f299083d54ac39520ec60a" @@ -3633,30 +3643,16 @@ dependencies: defer-to-connect "^1.0.1" -"@testing-library/dom@^7.2.1": - version "7.2.1" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.2.1.tgz#bb3b31d669bbe0c4939dadd95d69caa3c1d0b372" - integrity sha512-xIGoHlQ2ZiEL1dJIFKNmLDypzYF+4OJTTASRctl/aoIDaS5y/pRVHRigoqvPUV11mdJoR71IIgi/6UviMgyz4g== - dependencies: - "@babel/runtime" "^7.9.2" - "@types/testing-library__dom" "^7.0.0" - aria-query "^4.0.2" - dom-accessibility-api "^0.4.2" - pretty-format "^25.1.0" - -"@testing-library/dom@^7.28.1": - version "7.29.4" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.29.4.tgz#1647c2b478789621ead7a50614ad81ab5ae5b86c" - integrity sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA== +"@testing-library/dom@7.22.3", "@testing-library/dom@^7.2.1", "@testing-library/dom@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.22.3.tgz#12c0b1b97115e7731da6a86b4574eae401cb9ac5" + integrity sha512-IK6/eL1Xza/0goDKrwnBvlM06L+5eL9b1o+hUhX7HslfUvMETh0TYgXEr2LVpsVkHiOhRmUbUyml95KV/VlRNw== dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" + "@babel/runtime" "^7.10.3" "@types/aria-query" "^4.2.0" aria-query "^4.2.2" - chalk "^4.1.0" - dom-accessibility-api "^0.5.4" - lz-string "^1.4.4" - pretty-format "^26.6.2" + dom-accessibility-api "^0.5.1" + pretty-format "^25.5.0" "@testing-library/jest-dom@^5.1.1": version "5.1.1" @@ -3701,13 +3697,13 @@ filter-console "^0.1.1" react-error-boundary "^3.1.0" -"@testing-library/react@^11.2.5": - version "11.2.5" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.5.tgz#ae1c36a66c7790ddb6662c416c27863d87818eb9" - integrity sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ== +"@testing-library/react@^10.4.9": + version "10.4.9" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.4.9.tgz#9faa29c6a1a217bf8bbb96a28bd29d7a847ca150" + integrity sha512-pHZKkqUy0tmiD81afs8xfiuseXfU/N7rAX3iKjeZYje86t9VaB0LrxYVa+OOsvkrveX5jCK3IjajVn2MbePvqA== dependencies: - "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^7.28.1" + "@babel/runtime" "^7.10.3" + "@testing-library/dom" "^7.22.3" "@textlint/ast-node-types@^4.0.3": version "4.2.5" @@ -4513,13 +4509,6 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA== -"@types/testing-library__dom@^7.0.0": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-7.0.1.tgz#426bef0aa306a603fe071859d4b485941b28aca6" - integrity sha512-WokGRksRJb3Dla6h02/0/NNHTkjsj4S8aJZiwMj/5/UL8VZ1iCe3H8SHzfpmBeH8Vp4SPRT8iC2o9kYULFhDIw== - dependencies: - pretty-format "^25.1.0" - "@types/testing-library__jest-dom@^5.0.0": version "5.0.1" resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.0.1.tgz#cc7f384535a3d9597e27f58d38a795f5c137cc53" @@ -4696,6 +4685,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^15.0.0": + version "15.0.13" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.13.tgz#34f7fec8b389d7f3c1fd08026a5763e072d3c6dc" + integrity sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@^2.23.0": version "2.34.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" @@ -5620,14 +5616,6 @@ aria-query@^3.0.0: ast-types-flow "0.0.7" commander "^2.11.0" -aria-query@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.0.2.tgz#250687b4ccde1ab86d127da0432ae3552fc7b145" - integrity sha512-S1G1V790fTaigUSM/Gd0NngzEfiMy9uTUfMyHhKhVyy4cH5O/eTuR01ydhGL0z4Za1PXFTRGH3qL8VhUQuEO5w== - dependencies: - "@babel/runtime" "^7.7.4" - "@babel/runtime-corejs3" "^7.7.4" - aria-query@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" @@ -9621,12 +9609,7 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.3.tgz#93ca9002eb222fd5a343b6e5e6b9cf5929411c4c" - integrity sha512-JZ8iPuEHDQzq6q0k7PKMGbrIdsgBB7TRrtVOUm4nSMCExlg5qQG4KXWTH2k90yggjM4tTumRGwTKJSldMzKyLA== - -dom-accessibility-api@^0.5.4: +dom-accessibility-api@^0.5.1: version "0.5.4" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== @@ -19623,7 +19606,7 @@ pretty-error@^2.1.1: renderkid "^2.0.1" utila "~0.4" -pretty-format@^24.8.0, pretty-format@^24.9.0, pretty-format@^25.1.0, pretty-format@^26.6.2: +pretty-format@^24.8.0, pretty-format@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== @@ -19633,6 +19616,16 @@ pretty-format@^24.8.0, pretty-format@^24.9.0, pretty-format@^25.1.0, pretty-form ansi-styles "^3.2.0" react-is "^16.8.4" +pretty-format@^25.1.0, pretty-format@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" + integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ== + dependencies: + "@jest/types" "^25.5.0" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^16.12.0" + pretty-hrtime@^1.0.0, pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" From b93431528a6af638f19954ef73f863d617651d4e Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 09:13:45 +0000 Subject: [PATCH 59/74] add checkbox implem --- .../react-menu/MenuList/MenuList.stories.tsx | 26 +++- packages/react-menu/etc/react-menu.api.md | 28 ++++ packages/react-menu/src/MenuItemRadio.ts | 1 + .../MenuItemRadio/MenuItemRadio.test.tsx | 136 ++++++++++++++++++ .../MenuItemRadio/MenuItemRadio.tsx | 20 +++ .../MenuItemRadio/MenuItemRadio.types.ts | 29 ++++ .../__snapshots__/MenuItemRadio.test.tsx.snap | 14 ++ .../src/components/MenuItemRadio/index.ts | 4 + .../MenuItemRadio/renderMenuItemRadio.tsx | 20 +++ .../MenuItemRadio/useMenuItemRadio.ts | 40 ++++++ packages/react-menu/src/index.ts | 1 + 11 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 packages/react-menu/src/MenuItemRadio.ts create mode 100644 packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx create mode 100644 packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.tsx create mode 100644 packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts create mode 100644 packages/react-menu/src/components/MenuItemRadio/__snapshots__/MenuItemRadio.test.tsx.snap create mode 100644 packages/react-menu/src/components/MenuItemRadio/index.ts create mode 100644 packages/react-menu/src/components/MenuItemRadio/renderMenuItemRadio.tsx create mode 100644 packages/react-menu/src/components/MenuItemRadio/useMenuItemRadio.ts diff --git a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx index 456008c8b88c32..5d22c85dfc2ac1 100644 --- a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx +++ b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { MenuList, MenuItem, MenuItemCheckbox } from '@fluentui/react-menu'; +import { MenuList, MenuItem, MenuItemCheckbox, MenuItemRadio } from '@fluentui/react-menu'; import { CutIcon, PasteIcon, EditIcon, AcceptIcon } from '@fluentui/react-icons-mdl2'; import { makeStyles } from '@fluentui/react-make-styles'; @@ -67,3 +67,27 @@ export const MenuListWithCheckboxes = () => { ); }; + +export const MenuListWithRadios = () => { + const checkmark = ; + const [checkedValues, setCheckedValues] = React.useState>({ checkbox: ['2'] }); + const onChange = (name: string, items: string[]) => { + setCheckedValues(s => ({ ...s, [name]: items })); + }; + + return ( + + + } name="checkbox" value="1" checkmark={checkmark}> + Item + + } name="checkbox" value="2" checkmark={checkmark}> + Item + + } name="checkbox" value="3" checkmark={checkmark}> + Item + + + + ); +}; diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index cbb105348ab4e3..98886ac3181547 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -41,6 +41,28 @@ export interface MenuItemProps extends ComponentProps, React.HTMLAttributes & React.RefAttributes>; + +// @public +export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes, MenuItemSelectableProps { + // (undocumented) + checkmark?: ShorthandProps; + // (undocumented) + icon?: ShorthandProps; +} + +// @public +export const menuItemRadioShorthandProps: string[]; + +// @public +export interface MenuItemRadioState extends MenuItemRadioProps, MenuItemSelectableState { + checkmark: ObjectShorthandProps; + icon?: ObjectShorthandProps; + // (undocumented) + ref: React.MutableRefObject; +} + // @public export const menuItemShorthandProps: string[]; @@ -70,6 +92,9 @@ export const renderMenuItem: (state: MenuItemState) => JSX.Element; // @public export const renderMenuItemCheckbox: (state: MenuItemCheckboxState) => JSX.Element; +// @public +export const renderMenuItemRadio: (state: MenuItemRadioState) => JSX.Element; + // @public export const renderMenuList: (state: MenuListState) => JSX.Element; @@ -82,6 +107,9 @@ export const useMenuItem: (props: MenuItemProps, ref: React.Ref, de // @public export const useMenuItemCheckbox: (props: MenuItemCheckboxProps, ref: React.Ref, defaultProps?: MenuItemCheckboxProps | undefined) => MenuItemCheckboxState; +// @public +export const useMenuItemRadio: (props: MenuItemRadioProps, ref: React.Ref, defaultProps?: MenuItemRadioProps | undefined) => MenuItemRadioState; + // @public export const useMenuItemStyles: (state: MenuItemState) => void; diff --git a/packages/react-menu/src/MenuItemRadio.ts b/packages/react-menu/src/MenuItemRadio.ts new file mode 100644 index 00000000000000..99e28d486ca724 --- /dev/null +++ b/packages/react-menu/src/MenuItemRadio.ts @@ -0,0 +1 @@ +export * from './components/MenuItemRadio/index'; diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx new file mode 100644 index 00000000000000..9d065617b96006 --- /dev/null +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx @@ -0,0 +1,136 @@ +import * as React from 'react'; +import * as renderer from 'react-test-renderer'; +import { render, fireEvent } from '@testing-library/react'; +import { MenuItemRadio } from './MenuItemRadio'; +import { ReactWrapper } from 'enzyme'; +import { isConformant } from '../../common/isConformant'; +import { MenuListContext, MenuListProvider } from '../../menuListContext'; + +describe('MenuItemRadio', () => { + isConformant({ + Component: MenuItemRadio, + displayName: 'MenuItemRadio', + }); + + let wrapper: ReactWrapper | undefined; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = undefined; + } + }); + + /** + * Note: see more visual regression tests for MenuItemRadio in /apps/vr-tests. + */ + it('renders a default state', () => { + const component = renderer.create(Default MenuItemRadio); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); +}); + +describe('MenuItemRadio', () => { + const TestMenuListContext = (props: { children: React.ReactNode; context?: Partial }) => { + const contextValue: MenuListContext = { + checkedValues: {}, + onCheckedValueChange: jest.fn(), + ...(props.context && props.context), + }; + + return {props.children}; + }; + + it('should render checkmark slot if checked', () => { + // Arrange + const checkedValues = { test: ['1'] }; + const checkmark = 'xxx'; + const { getByText } = render( + + + Radio + + , + ); + + // Assert + expect(getByText(checkmark)).not.toBeNull(); + }); + + it('should render icon slot', () => { + // Arrange + const icon = 'xxx'; + const { getByText } = render( + + + Radio + + , + ); + + // Assert + expect(getByText(icon)).not.toBeNull(); + }); + + it('should set aria-checked value to true if value is checked', () => { + // Arrange + const checkedValues = { test: ['1'] }; + const { container } = render( + + + Radio + + , + ); + + // Assert + expect(container.querySelector('[role="menuitemradio"')?.getAttribute('aria-checked')).toEqual('true'); + }); + + it('should check radio on click', () => { + // Arrange + const radioName = 'name'; + const radioValue = '1'; + const checkedValues = { [radioName]: [] }; + const spy = jest.fn(); + const { container } = render( + + + Radio + + , + ); + + // Act + const menuitem = container.querySelector('[role="menuitemradio"'); + menuitem && fireEvent.click(menuitem); + + // Assert + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(radioName, [radioValue]); + }); + + it('should uncheck other radio on click', () => { + // Arrange + const radioName = 'name'; + const radioValue = '1'; + const checkedValues = { [radioName]: ['2'] }; + const spy = jest.fn(); + const { container } = render( + + + Radio + + , + ); + + // Act + const menuitem = container.querySelector('[role="menuitemradio"'); + menuitem && fireEvent.click(menuitem); + + // Assert + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(radioName, [radioValue]); + }); +}); diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.tsx b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.tsx new file mode 100644 index 00000000000000..63dce0fb2a485e --- /dev/null +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { useMenuItemRadio } from './useMenuItemRadio'; +import { MenuItemRadioProps } from './MenuItemRadio.types'; +import { renderMenuItemRadio } from './renderMenuItemRadio'; +import { useMenuItemStyles } from '../MenuItem/useMenuItemStyles'; +import { useCheckmarkStyles } from '../../selectable/index'; + +/** + * Define a styled MenuItemRadio, using the `useMenuItemRadio` hook. + * {@docCategory MenuItemRadio} + */ +export const MenuItemRadio = React.forwardRef((props, ref) => { + const state = useMenuItemRadio(props, ref); + useMenuItemStyles(state); + useCheckmarkStyles(state); + + return renderMenuItemRadio(state); +}); + +MenuItemRadio.displayName = 'MenuItemRadio'; diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts new file mode 100644 index 00000000000000..e4037904c0aba7 --- /dev/null +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { ComponentProps, ObjectShorthandProps, ShorthandProps } from '@fluentui/react-utils'; +import { MenuItemSelectableProps, MenuItemSelectableState } from '../../selectable/index'; + +/** + * {@docCategory MenuItemRadio\} + */ +export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes, MenuItemSelectableProps { + icon?: ShorthandProps; + + checkmark?: ShorthandProps; +} + +/** + * {@docCategory MenuItemRadio\} + */ +export interface MenuItemRadioState extends MenuItemRadioProps, MenuItemSelectableState { + ref: React.MutableRefObject; + + /** + * Icon slot rendered before children content + */ + icon?: ObjectShorthandProps; + + /** + * Slot for the checkmark indicator + */ + checkmark: ObjectShorthandProps; +} diff --git a/packages/react-menu/src/components/MenuItemRadio/__snapshots__/MenuItemRadio.test.tsx.snap b/packages/react-menu/src/components/MenuItemRadio/__snapshots__/MenuItemRadio.test.tsx.snap new file mode 100644 index 00000000000000..71707d30c430f6 --- /dev/null +++ b/packages/react-menu/src/components/MenuItemRadio/__snapshots__/MenuItemRadio.test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MenuItemRadio renders a default state 1`] = ` +
    + Default MenuItemRadio +
    +`; diff --git a/packages/react-menu/src/components/MenuItemRadio/index.ts b/packages/react-menu/src/components/MenuItemRadio/index.ts new file mode 100644 index 00000000000000..36a208dd52a567 --- /dev/null +++ b/packages/react-menu/src/components/MenuItemRadio/index.ts @@ -0,0 +1,4 @@ +export * from './MenuItemRadio.types'; +export * from './MenuItemRadio'; +export * from './renderMenuItemRadio'; +export * from './useMenuItemRadio'; diff --git a/packages/react-menu/src/components/MenuItemRadio/renderMenuItemRadio.tsx b/packages/react-menu/src/components/MenuItemRadio/renderMenuItemRadio.tsx new file mode 100644 index 00000000000000..d7d51de7e7244b --- /dev/null +++ b/packages/react-menu/src/components/MenuItemRadio/renderMenuItemRadio.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utils'; +import { MenuItemRadioState } from './MenuItemRadio.types'; +import { menuItemRadioShorthandProps } from './useMenuItemRadio'; + +/** + * Redefine the render function to add slots. Reuse the menuitemradio structure but add + * slots to children. + */ +export const renderMenuItemRadio = (state: MenuItemRadioState) => { + const { slots, slotProps } = getSlots(state, menuItemRadioShorthandProps); + + return ( + + + + {state.children} + + ); +}; diff --git a/packages/react-menu/src/components/MenuItemRadio/useMenuItemRadio.ts b/packages/react-menu/src/components/MenuItemRadio/useMenuItemRadio.ts new file mode 100644 index 00000000000000..90b9753a04a954 --- /dev/null +++ b/packages/react-menu/src/components/MenuItemRadio/useMenuItemRadio.ts @@ -0,0 +1,40 @@ +import * as React from 'react'; +import { makeMergeProps, resolveShorthandProps } from '@fluentui/react-utils'; +import { MenuItemRadioProps, MenuItemRadioState } from './MenuItemRadio.types'; +import { useMenuItemSelectable } from '../../selectable/index'; + +/** + * Consts listing which props are shorthand props. + */ +export const menuItemRadioShorthandProps = ['icon', 'checkmark']; + +const mergeProps = makeMergeProps({ deepMerge: menuItemRadioShorthandProps }); + +/** + * Given user props, returns state and render function for a MenuItemRadio. + */ +export const useMenuItemRadio = ( + props: MenuItemRadioProps, + ref: React.Ref, + defaultProps?: MenuItemRadioProps, +): MenuItemRadioState => { + // Ensure that the `ref` prop can be used by other things (like useFocusRects) to refer to the root. + // NOTE: We are assuming refs should not mutate to undefined. Either they are passed or not. + // eslint-disable-next-line react-hooks/rules-of-hooks + const resolvedRef = ref || React.useRef(); + const state = mergeProps( + { + ref: resolvedRef, + as: 'div', + icon: { as: 'span' }, + checkmark: { as: 'span' }, + role: 'menuitemradio', + tabIndex: 0, + }, + defaultProps, + resolveShorthandProps(props, menuItemRadioShorthandProps), + ); + + useMenuItemSelectable(state, () => [state.value]); + return state; +}; diff --git a/packages/react-menu/src/index.ts b/packages/react-menu/src/index.ts index 053db49a75181d..435ff83f407e7c 100644 --- a/packages/react-menu/src/index.ts +++ b/packages/react-menu/src/index.ts @@ -3,3 +3,4 @@ import './version'; export * from './MenuItem'; export * from './MenuList'; export * from './MenuItemCheckbox'; +export * from './MenuItemRadio'; From 3f93461d614b39b3a5a4a3431950450b7e1294c5 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 09:41:41 +0000 Subject: [PATCH 60/74] update md --- packages/react-menu/etc/react-menu.api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index 98886ac3181547..b54d71766e69e6 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -44,7 +44,7 @@ export interface MenuItemProps extends ComponentProps, React.HTMLAttributes & React.RefAttributes>; -// @public +// @public (undocumented) export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes, MenuItemSelectableProps { // (undocumented) checkmark?: ShorthandProps; @@ -55,7 +55,7 @@ export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes // @public export const menuItemRadioShorthandProps: string[]; -// @public +// @public (undocumented) export interface MenuItemRadioState extends MenuItemRadioProps, MenuItemSelectableState { checkmark: ObjectShorthandProps; icon?: ObjectShorthandProps; From 654b5e039f9b3806bb18557ba9fe879352b7287e Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 13:54:10 +0000 Subject: [PATCH 61/74] use styling hooks --- packages/react-menu/etc/react-menu.api.md | 4 ++-- .../components/MenuItemCheckbox/MenuItemCheckbox.test.tsx | 1 + .../src/components/MenuItemCheckbox/MenuItemCheckbox.tsx | 6 ++---- .../components/MenuItemCheckbox/MenuItemCheckbox.types.ts | 6 ++++++ .../components/MenuItemCheckbox/useMenuItemCheckbox.ts | 1 - .../MenuItemCheckbox/useMenuItemCheckboxStyles.ts | 8 ++++++++ .../src/components/MenuItemRadio/MenuItemRadio.test.tsx | 1 + .../src/components/MenuItemRadio/MenuItemRadio.tsx | 6 ++---- .../src/components/MenuItemRadio/MenuItemRadio.types.ts | 4 ++-- .../src/components/MenuItemRadio/useMenuItemRadio.ts | 8 ++------ .../components/MenuItemRadio/useMenuItemRadioStyles.ts | 8 ++++++++ 11 files changed, 34 insertions(+), 19 deletions(-) create mode 100644 packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckboxStyles.ts create mode 100644 packages/react-menu/src/components/MenuItemRadio/useMenuItemRadioStyles.ts diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index b54d71766e69e6..98886ac3181547 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -44,7 +44,7 @@ export interface MenuItemProps extends ComponentProps, React.HTMLAttributes & React.RefAttributes>; -// @public (undocumented) +// @public export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes, MenuItemSelectableProps { // (undocumented) checkmark?: ShorthandProps; @@ -55,7 +55,7 @@ export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes // @public export const menuItemRadioShorthandProps: string[]; -// @public (undocumented) +// @public export interface MenuItemRadioState extends MenuItemRadioProps, MenuItemSelectableState { checkmark: ObjectShorthandProps; icon?: ObjectShorthandProps; diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx index ae3684c7d68c85..e5e13b4d45c511 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx @@ -8,6 +8,7 @@ import { MenuListContext, MenuListProvider } from '../../menuListContext'; describe('MenuItemCheckbox conformance', () => { isConformant({ + asPropHandlesRef: true, Component: MenuItemCheckbox, displayName: 'MenuItemCheckbox', }); diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx index fff4eca3448c86..f0cf5e331424db 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.tsx @@ -2,8 +2,7 @@ import * as React from 'react'; import { useMenuItemCheckbox } from './useMenuItemCheckbox'; import { MenuItemCheckboxProps } from './MenuItemCheckbox.types'; import { renderMenuItemCheckbox } from './renderMenuItemCheckbox'; -import { useCheckmarkStyles } from '../../selectable/index'; -import { useMenuItemStyles } from '../MenuItem/useMenuItemStyles'; +import { useMenuItemCheckBoxStyles } from './useMenuItemCheckboxStyles'; /** * Define a styled MenuItemCheckbox, using the `useMenuItemCheckbox` hook. @@ -11,8 +10,7 @@ import { useMenuItemStyles } from '../MenuItem/useMenuItemStyles'; */ export const MenuItemCheckbox = React.forwardRef((props, ref) => { const state = useMenuItemCheckbox(props, ref); - useMenuItemStyles(state); - useCheckmarkStyles(state); + useMenuItemCheckBoxStyles(state); return renderMenuItemCheckbox(state); }); diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts index 90c4fdb2f1e179..3fcb76f112c29e 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts @@ -3,6 +3,9 @@ import { ComponentProps, ObjectShorthandProps, ShorthandProps } from '@fluentui/ import { MenuItemSelectableProps, MenuItemSelectableState } from '../../selectable/index'; import { MenuItemProps, MenuItemState } from '../MenuItem/MenuItem.types'; +/** + * {@docCategory MenuItemCheckbox} + */ export interface MenuItemCheckboxProps extends ComponentProps, React.HTMLAttributes, @@ -19,6 +22,9 @@ export interface MenuItemCheckboxProps checkmark?: ShorthandProps; } +/** + * {@docCategory MenuItemCheckbox} + */ export interface MenuItemCheckboxState extends MenuItemCheckboxProps, MenuItemState, MenuItemSelectableState { ref: React.MutableRefObject; diff --git a/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts index facdf8ce63c215..c727db650e96ae 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts +++ b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.ts @@ -20,7 +20,6 @@ export const useMenuItemCheckbox = ( const state = mergeProps( { ref: useMergedRefs(ref, React.useRef(null)), - as: 'div', icon: { as: 'span' }, checkmark: { as: 'span' }, role: 'menuitemcheckbox', diff --git a/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckboxStyles.ts b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckboxStyles.ts new file mode 100644 index 00000000000000..3e6fab28b06cd6 --- /dev/null +++ b/packages/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckboxStyles.ts @@ -0,0 +1,8 @@ +import { useCheckmarkStyles } from '../../selectable/index'; +import { useMenuItemStyles } from '../MenuItem/useMenuItemStyles'; +import { MenuItemCheckboxState } from './MenuItemCheckbox.types'; + +export const useMenuItemCheckBoxStyles = (state: MenuItemCheckboxState) => { + useMenuItemStyles(state); + useCheckmarkStyles(state); +}; diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx index 9d065617b96006..b800b3a1ccaeaa 100644 --- a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx @@ -8,6 +8,7 @@ import { MenuListContext, MenuListProvider } from '../../menuListContext'; describe('MenuItemRadio', () => { isConformant({ + asPropHandlesRef: true, Component: MenuItemRadio, displayName: 'MenuItemRadio', }); diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.tsx b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.tsx index 63dce0fb2a485e..b3eed52b5186b6 100644 --- a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.tsx +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.tsx @@ -2,8 +2,7 @@ import * as React from 'react'; import { useMenuItemRadio } from './useMenuItemRadio'; import { MenuItemRadioProps } from './MenuItemRadio.types'; import { renderMenuItemRadio } from './renderMenuItemRadio'; -import { useMenuItemStyles } from '../MenuItem/useMenuItemStyles'; -import { useCheckmarkStyles } from '../../selectable/index'; +import { useMenuItemRadioStyles } from './useMenuItemRadioStyles'; /** * Define a styled MenuItemRadio, using the `useMenuItemRadio` hook. @@ -11,8 +10,7 @@ import { useCheckmarkStyles } from '../../selectable/index'; */ export const MenuItemRadio = React.forwardRef((props, ref) => { const state = useMenuItemRadio(props, ref); - useMenuItemStyles(state); - useCheckmarkStyles(state); + useMenuItemRadioStyles(state); return renderMenuItemRadio(state); }); diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts index e4037904c0aba7..ced279e0c11d9b 100644 --- a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts @@ -3,7 +3,7 @@ import { ComponentProps, ObjectShorthandProps, ShorthandProps } from '@fluentui/ import { MenuItemSelectableProps, MenuItemSelectableState } from '../../selectable/index'; /** - * {@docCategory MenuItemRadio\} + * {@docCategory MenuItemRadio} */ export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes, MenuItemSelectableProps { icon?: ShorthandProps; @@ -12,7 +12,7 @@ export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes } /** - * {@docCategory MenuItemRadio\} + * {@docCategory MenuItemRadio} */ export interface MenuItemRadioState extends MenuItemRadioProps, MenuItemSelectableState { ref: React.MutableRefObject; diff --git a/packages/react-menu/src/components/MenuItemRadio/useMenuItemRadio.ts b/packages/react-menu/src/components/MenuItemRadio/useMenuItemRadio.ts index 90b9753a04a954..cb37bda39dbbd1 100644 --- a/packages/react-menu/src/components/MenuItemRadio/useMenuItemRadio.ts +++ b/packages/react-menu/src/components/MenuItemRadio/useMenuItemRadio.ts @@ -2,6 +2,7 @@ import * as React from 'react'; import { makeMergeProps, resolveShorthandProps } from '@fluentui/react-utils'; import { MenuItemRadioProps, MenuItemRadioState } from './MenuItemRadio.types'; import { useMenuItemSelectable } from '../../selectable/index'; +import { useMergedRefs } from '@fluentui/react-hooks'; /** * Consts listing which props are shorthand props. @@ -18,14 +19,9 @@ export const useMenuItemRadio = ( ref: React.Ref, defaultProps?: MenuItemRadioProps, ): MenuItemRadioState => { - // Ensure that the `ref` prop can be used by other things (like useFocusRects) to refer to the root. - // NOTE: We are assuming refs should not mutate to undefined. Either they are passed or not. - // eslint-disable-next-line react-hooks/rules-of-hooks - const resolvedRef = ref || React.useRef(); const state = mergeProps( { - ref: resolvedRef, - as: 'div', + ref: useMergedRefs(ref, React.useRef(null)), icon: { as: 'span' }, checkmark: { as: 'span' }, role: 'menuitemradio', diff --git a/packages/react-menu/src/components/MenuItemRadio/useMenuItemRadioStyles.ts b/packages/react-menu/src/components/MenuItemRadio/useMenuItemRadioStyles.ts new file mode 100644 index 00000000000000..d6a4d64c0fbacf --- /dev/null +++ b/packages/react-menu/src/components/MenuItemRadio/useMenuItemRadioStyles.ts @@ -0,0 +1,8 @@ +import { useCheckmarkStyles } from '../../selectable/index'; +import { useMenuItemStyles } from '../MenuItem/useMenuItemStyles'; +import { MenuItemRadioState } from './MenuItemRadio.types'; + +export const useMenuItemRadioStyles = (state: MenuItemRadioState) => { + useMenuItemStyles(state); + useCheckmarkStyles(state); +}; From 4c586d7f2e30a2c880541fb6905db01f2017c482 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 13:59:04 +0000 Subject: [PATCH 62/74] add comment --- packages/react-menu/src/menuListContext.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-menu/src/menuListContext.tsx b/packages/react-menu/src/menuListContext.tsx index c45e04ee737342..7d5cacf95071d5 100644 --- a/packages/react-menu/src/menuListContext.tsx +++ b/packages/react-menu/src/menuListContext.tsx @@ -5,6 +5,7 @@ const MenuListContext = React.createContext({ onCheckedValueChange: () => null, }); +// TODO add context selector to reduce the number of rerenders export interface MenuListContext { checkedValues?: Record; onCheckedValueChange?: (name: string, items: string[]) => void; From c766a1fd00c0901b21c7deeaf2cc8720b454e878 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 15:16:02 +0000 Subject: [PATCH 63/74] add events --- .../src/react-menu/MenuList/MenuList.stories.tsx | 4 ++-- packages/react-menu/etc/react-menu.api.md | 6 +++--- .../react-menu/src/components/MenuList/MenuList.types.ts | 2 +- packages/react-menu/src/menuListContext.tsx | 2 +- packages/react-menu/src/selectable/types.ts | 2 +- .../react-menu/src/selectable/useMenuItemSelectable.ts | 7 ++++--- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx index 5d22c85dfc2ac1..5337520034297d 100644 --- a/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx +++ b/packages/react-examples/src/react-menu/MenuList/MenuList.stories.tsx @@ -47,7 +47,7 @@ export const MenuListWithIconsExample = () => ( export const MenuListWithCheckboxes = () => { const checkmark = ; const [checkedValues, setCheckedValues] = React.useState>({ checkbox: ['2'] }); - const onChange = (name: string, items: string[]) => { + const onChange = (e: React.SyntheticEvent, name: string, items: string[]) => { setCheckedValues(s => ({ ...s, [name]: items })); }; @@ -71,7 +71,7 @@ export const MenuListWithCheckboxes = () => { export const MenuListWithRadios = () => { const checkmark = ; const [checkedValues, setCheckedValues] = React.useState>({ checkbox: ['2'] }); - const onChange = (name: string, items: string[]) => { + const onChange = (e: React.SyntheticEvent, name: string, items: string[]) => { setCheckedValues(s => ({ ...s, [name]: items })); }; diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index 98886ac3181547..4f03e1986a7f96 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -44,7 +44,7 @@ export interface MenuItemProps extends ComponentProps, React.HTMLAttributes & React.RefAttributes>; -// @public +// @public (undocumented) export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes, MenuItemSelectableProps { // (undocumented) checkmark?: ShorthandProps; @@ -55,7 +55,7 @@ export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes // @public export const menuItemRadioShorthandProps: string[]; -// @public +// @public (undocumented) export interface MenuItemRadioState extends MenuItemRadioProps, MenuItemSelectableState { checkmark: ObjectShorthandProps; icon?: ObjectShorthandProps; @@ -78,7 +78,7 @@ export const MenuList: React.ForwardRefExoticComponent { checkedValues?: Record; - onCheckedValueChange?: (name: string, checkedItems: string[]) => void; + onCheckedValueChange?: (e: React.MouseEvent | React.KeyboardEvent, name: string, checkedItems: string[]) => void; } // @public (undocumented) diff --git a/packages/react-menu/src/components/MenuList/MenuList.types.ts b/packages/react-menu/src/components/MenuList/MenuList.types.ts index f82d6487b942da..e7ca45f8f9868b 100644 --- a/packages/react-menu/src/components/MenuList/MenuList.types.ts +++ b/packages/react-menu/src/components/MenuList/MenuList.types.ts @@ -8,7 +8,7 @@ export interface MenuListProps extends ComponentProps, React.HTMLAttributes void; + onCheckedValueChange?: (e: React.MouseEvent | React.KeyboardEvent, name: string, checkedItems: string[]) => void; /** * Map of all checked values diff --git a/packages/react-menu/src/menuListContext.tsx b/packages/react-menu/src/menuListContext.tsx index 7d5cacf95071d5..c71269a5c5d27b 100644 --- a/packages/react-menu/src/menuListContext.tsx +++ b/packages/react-menu/src/menuListContext.tsx @@ -8,7 +8,7 @@ const MenuListContext = React.createContext({ // TODO add context selector to reduce the number of rerenders export interface MenuListContext { checkedValues?: Record; - onCheckedValueChange?: (name: string, items: string[]) => void; + onCheckedValueChange?: (e: React.MouseEvent | React.KeyboardEvent, name: string, items: string[]) => void; } export const MenuListProvider = MenuListContext.Provider; diff --git a/packages/react-menu/src/selectable/types.ts b/packages/react-menu/src/selectable/types.ts index a1ab3c0c4ea375..96c227121c8d9a 100644 --- a/packages/react-menu/src/selectable/types.ts +++ b/packages/react-menu/src/selectable/types.ts @@ -29,7 +29,7 @@ export interface MenuItemSelectableState extends MenuItemSelectableProps { /** * Callback when checked items changes for a given value with `name` */ - onCheckedValueChange: (name: string, checkedItems: string[]) => void; + onCheckedValueChange: (e: React.MouseEvent | React.KeyboardEvent, name: string, checkedItems: string[]) => void; /** * Selectable is checked diff --git a/packages/react-menu/src/selectable/useMenuItemSelectable.ts b/packages/react-menu/src/selectable/useMenuItemSelectable.ts index 63727c297a642e..0c125af21be72a 100644 --- a/packages/react-menu/src/selectable/useMenuItemSelectable.ts +++ b/packages/react-menu/src/selectable/useMenuItemSelectable.ts @@ -1,3 +1,4 @@ +import * as React from 'react'; import { EnterKey, getCode, SpacebarKey } from '@fluentui/keyboard-key'; import { useMenuListContext } from '../menuListContext'; import { MenuItemSelectableState } from './types'; @@ -17,7 +18,7 @@ export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewChec state.checked = checkedItems.indexOf(state.value) !== -1; state['aria-checked'] = state.checked; - const onSelectionChange = () => { + const onSelectionChange = (e: React.MouseEvent | React.KeyboardEvent) => { const newCheckedItems = getNewCheckedItems(); if ( @@ -27,7 +28,7 @@ export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewChec return; } - state.onCheckedValueChange(state.name, newCheckedItems); + state.onCheckedValueChange(e, state.name, newCheckedItems); }; state.onClick = e => { @@ -35,7 +36,7 @@ export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewChec onClickCallback(e); } - onSelectionChange(); + onSelectionChange(e); }; state.onKeyDown = e => { From d2fc8f591535967bcb758f3c39d79a0472641452 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 15:46:55 +0000 Subject: [PATCH 64/74] use separate events for keyboard/click --- .../selectable/useMenuItemSelectable.test.ts | 21 +++++++++++-------- .../src/selectable/useMenuItemSelectable.ts | 9 ++++---- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts b/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts index ded1f1f2c8d2dc..6f60882f7d9274 100644 --- a/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts +++ b/packages/react-menu/src/selectable/useMenuItemSelectable.test.ts @@ -52,12 +52,12 @@ describe('useMenuItemSelectable', () => { if (state.onClick) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - state.onClick(null); + state.onClick({ persist: jest.fn() }); } // Assert expect(state.onCheckedValueChange).toHaveBeenCalledTimes(1); - expect(state.onCheckedValueChange).toHaveBeenCalledWith(state.name, newValues); + expect(state.onCheckedValueChange).toHaveBeenCalledWith(expect.anything(), state.name, newValues); }); it('should not call onCheckedValueChange if values did not change', () => { @@ -73,7 +73,7 @@ describe('useMenuItemSelectable', () => { if (state.onClick) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - state.onClick(null); + state.onClick({ persist: jest.fn() }); } // Assert @@ -95,19 +95,21 @@ describe('useMenuItemSelectable', () => { expect(typeof callback).toBe('function'); }); - it.each([EnterKey, SpacebarKey])('should transform %s keydown to click', keyCode => { + it.each([EnterKey, SpacebarKey])('should toggle selection on %s keydown', keyCode => { // Arrange const state: MenuItemSelectableState = createTestState(); + (useMenuListContext as jest.Mock).mockReturnValue({ + onCheckedValueChange: jest.fn(), + checkedValues: { [state.name]: [...checkedItems] }, + }); const event = { defaultPrevented: false, - target: { click: jest.fn() }, - stopPropagation: jest.fn(), - preventDefault: jest.fn(), keyCode, + persist: jest.fn(), }; // Act - renderHook(() => useMenuItemSelectable(state, jest.fn())); + renderHook(() => useMenuItemSelectable(state, () => [])); if (state.onKeyDown) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -115,6 +117,7 @@ describe('useMenuItemSelectable', () => { } // Assert - expect(event.target.click).toHaveBeenCalledTimes(1); + expect(state.onCheckedValueChange).toHaveBeenCalledTimes(1); + expect(state.onCheckedValueChange).toHaveBeenCalledWith(expect.anything(), state.name, []); }); }); diff --git a/packages/react-menu/src/selectable/useMenuItemSelectable.ts b/packages/react-menu/src/selectable/useMenuItemSelectable.ts index 0c125af21be72a..209718daebad66 100644 --- a/packages/react-menu/src/selectable/useMenuItemSelectable.ts +++ b/packages/react-menu/src/selectable/useMenuItemSelectable.ts @@ -32,6 +32,7 @@ export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewChec }; state.onClick = e => { + e.persist(); if (onClickCallback) { onClickCallback(e); } @@ -41,16 +42,14 @@ export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewChec state.onKeyDown = e => { if (onKeyDownCallback) { + e.persist(); onKeyDownCallback(e); } const keyCode = getCode(e); if (!e.defaultPrevented && (keyCode === EnterKey || keyCode === SpacebarKey)) { - // Translate the keydown enter/space to a click. - e.preventDefault(); - e.stopPropagation(); - - (e.target as HTMLElement).click(); + e.persist(); + onSelectionChange(e); } }; }; From 33a3916177856df649b311ef4393184ea38ab7df Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 16:36:07 +0000 Subject: [PATCH 65/74] remove e.persist --- .../src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx | 2 +- .../src/components/MenuItemRadio/MenuItemRadio.test.tsx | 4 ++-- packages/react-menu/src/selectable/useMenuItemSelectable.ts | 3 --- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx index e5e13b4d45c511..9eff93e73e46be 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx @@ -112,6 +112,6 @@ describe('MenuItemCheckbox', () => { // Assert expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith(checkboxName, [...expectedResult]); + expect(spy).toHaveBeenCalledWith(expect.anything(), checkboxName, [...expectedResult]); }); }); diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx index b800b3a1ccaeaa..d7301570f19ed4 100644 --- a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx @@ -109,7 +109,7 @@ describe('MenuItemRadio', () => { // Assert expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith(radioName, [radioValue]); + expect(spy).toHaveBeenCalledWith(expect.anything(), radioName, [radioValue]); }); it('should uncheck other radio on click', () => { @@ -132,6 +132,6 @@ describe('MenuItemRadio', () => { // Assert expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith(radioName, [radioValue]); + expect(spy).toHaveBeenCalledWith(expect.anything(), radioName, [radioValue]); }); }); diff --git a/packages/react-menu/src/selectable/useMenuItemSelectable.ts b/packages/react-menu/src/selectable/useMenuItemSelectable.ts index 209718daebad66..a7d8686c261275 100644 --- a/packages/react-menu/src/selectable/useMenuItemSelectable.ts +++ b/packages/react-menu/src/selectable/useMenuItemSelectable.ts @@ -32,7 +32,6 @@ export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewChec }; state.onClick = e => { - e.persist(); if (onClickCallback) { onClickCallback(e); } @@ -42,13 +41,11 @@ export const useMenuItemSelectable = (state: MenuItemSelectableState, getNewChec state.onKeyDown = e => { if (onKeyDownCallback) { - e.persist(); onKeyDownCallback(e); } const keyCode = getCode(e); if (!e.defaultPrevented && (keyCode === EnterKey || keyCode === SpacebarKey)) { - e.persist(); onSelectionChange(e); } }; From 33bf9199ceb6462e245b3018bd548ab7baaab94a Mon Sep 17 00:00:00 2001 From: ling1726 Date: Thu, 11 Feb 2021 19:20:30 +0000 Subject: [PATCH 66/74] Update packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx Co-authored-by: Oleksandr Fediashov --- .../src/components/MenuItemRadio/MenuItemRadio.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx index d7301570f19ed4..925575826e7329 100644 --- a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx @@ -127,7 +127,7 @@ describe('MenuItemRadio', () => { ); // Act - const menuitem = container.querySelector('[role="menuitemradio"'); + const menuitem = container.querySelector('[role="menuitemradio"]'); menuitem && fireEvent.click(menuitem); // Assert From 36085974c69fcf65f1bf3004d549837585a21cdb Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 19:22:19 +0000 Subject: [PATCH 67/74] fix tests --- .../src/components/MenuItemRadio/MenuItemRadio.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx index 925575826e7329..b368283b341f05 100644 --- a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx @@ -86,7 +86,7 @@ describe('MenuItemRadio', () => { ); // Assert - expect(container.querySelector('[role="menuitemradio"')?.getAttribute('aria-checked')).toEqual('true'); + expect(container.querySelector('[role="menuitemradio"]')?.getAttribute('aria-checked')).toEqual('true'); }); it('should check radio on click', () => { @@ -104,7 +104,7 @@ describe('MenuItemRadio', () => { ); // Act - const menuitem = container.querySelector('[role="menuitemradio"'); + const menuitem = container.querySelector('[role="menuitemradio"]'); menuitem && fireEvent.click(menuitem); // Assert From 59127e9196d41bdfc35f0a43738857356212b538 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 19:24:17 +0000 Subject: [PATCH 68/74] fix tests --- .../src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx index 9eff93e73e46be..c01b01d714fdf4 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx @@ -87,7 +87,7 @@ describe('MenuItemCheckbox', () => { ); // Assert - expect(container.querySelector('[role="menuitemcheckbox"')?.getAttribute('aria-checked')).toEqual('true'); + expect(container.querySelector('[role="menuitemcheckbox"]')?.getAttribute('aria-checked')).toEqual('true'); }); it.each([ @@ -107,7 +107,7 @@ describe('MenuItemCheckbox', () => { ); // Act - const menuitem = container.querySelector('[role="menuitemcheckbox"'); + const menuitem = container.querySelector('[role="menuitemcheckbox"]'); menuitem && fireEvent.click(menuitem); // Assert From 96e38553842a2e3896670f3cc2f860db045993f9 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 21:19:53 +0000 Subject: [PATCH 69/74] fix test --- .../components/MenuItemCheckbox/MenuItemCheckbox.test.tsx | 6 +++++- .../src/components/MenuItemRadio/MenuItemRadio.test.tsx | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx index c01b01d714fdf4..1e4a12af86d864 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx @@ -26,7 +26,11 @@ describe('MenuItemCheckbox conformance', () => { * Note: see more visual regression tests for MenuItemCheckbox in /apps/vr-tests. */ it('renders a default state', () => { - const component = renderer.create(Default MenuItemCheckbox); + const component = renderer.create( + + Default MenuItemCheckbox + , + ); const tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx index b368283b341f05..32a8028373a41f 100644 --- a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx @@ -26,7 +26,11 @@ describe('MenuItemRadio', () => { * Note: see more visual regression tests for MenuItemRadio in /apps/vr-tests. */ it('renders a default state', () => { - const component = renderer.create(Default MenuItemRadio); + const component = renderer.create( + + Default MenuItemRadio + , + ); const tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); From f5cdcec0ed84420a9b376b547484f9afdefe4755 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 21:55:19 +0000 Subject: [PATCH 70/74] add required props for conformance test --- .../src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx | 4 ++++ .../src/components/MenuItemRadio/MenuItemRadio.test.tsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx index 1e4a12af86d864..0adbb23c639367 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx @@ -10,6 +10,10 @@ describe('MenuItemCheckbox conformance', () => { isConformant({ asPropHandlesRef: true, Component: MenuItemCheckbox, + requiredProps: { + name: 'checkbox', + value: '1', + }, displayName: 'MenuItemCheckbox', }); diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx index 32a8028373a41f..3f83f64d2fd1a5 100644 --- a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx @@ -10,6 +10,10 @@ describe('MenuItemRadio', () => { isConformant({ asPropHandlesRef: true, Component: MenuItemRadio, + requiredProps: { + name: 'radio', + value: '1', + }, displayName: 'MenuItemRadio', }); From 78651647f86cdfe7ab646fa553b3a0536818427a Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 11 Feb 2021 22:16:06 +0000 Subject: [PATCH 71/74] ignore confirmance types for now --- packages/react-menu/etc/react-menu.api.md | 12 ++++++------ .../MenuItemCheckbox/MenuItemCheckbox.test.tsx | 3 +++ .../MenuItemCheckbox/MenuItemCheckbox.types.ts | 4 ++-- .../components/MenuItemRadio/MenuItemRadio.test.tsx | 3 +++ .../components/MenuItemRadio/MenuItemRadio.types.ts | 4 ++-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/react-menu/etc/react-menu.api.md b/packages/react-menu/etc/react-menu.api.md index f06c14553b7be5..7a9fc42894849a 100644 --- a/packages/react-menu/etc/react-menu.api.md +++ b/packages/react-menu/etc/react-menu.api.md @@ -13,14 +13,14 @@ import { ShorthandProps } from '@fluentui/react-utils'; export const MenuItem: React.ForwardRefExoticComponent>; // @public -export const MenuItemCheckbox: React.ForwardRefExoticComponent & React.RefAttributes>; +export const MenuItemCheckbox: React.ForwardRefExoticComponent>; // Warning: (ae-forgotten-export) The symbol "MenuItemSelectableProps" needs to be exported by the entry point index.d.ts // // @public (undocumented) export interface MenuItemCheckboxProps extends ComponentProps, React.HTMLAttributes, MenuItemProps, MenuItemSelectableProps { - checkmark?: ShorthandProps; - icon?: ShorthandProps; + checkmark?: ShorthandProps; + icon?: ShorthandProps; } // @public @@ -42,14 +42,14 @@ export interface MenuItemProps extends ComponentProps, React.HTMLAttributes & React.RefAttributes>; +export const MenuItemRadio: React.ForwardRefExoticComponent>; // @public (undocumented) export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes, MenuItemSelectableProps { // (undocumented) - checkmark?: ShorthandProps; + checkmark?: ShorthandProps; // (undocumented) - icon?: ShorthandProps; + icon?: ShorthandProps; } // @public diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx index 0adbb23c639367..e4480a96ba0f4f 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.test.tsx @@ -9,6 +9,9 @@ import { MenuListContext, MenuListProvider } from '../../menuListContext'; describe('MenuItemCheckbox conformance', () => { isConformant({ asPropHandlesRef: true, + // TODO fix generics in conformance + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Component: MenuItemCheckbox, requiredProps: { name: 'checkbox', diff --git a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts index 3fcb76f112c29e..962b9cd9222570 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts +++ b/packages/react-menu/src/components/MenuItemCheckbox/MenuItemCheckbox.types.ts @@ -14,12 +14,12 @@ export interface MenuItemCheckboxProps /** * Icon slot rendered before children content */ - icon?: ShorthandProps; + icon?: ShorthandProps; /** * Slot for the checkmark indicator */ - checkmark?: ShorthandProps; + checkmark?: ShorthandProps; } /** diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx index 3f83f64d2fd1a5..354916b4d0734a 100644 --- a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.test.tsx @@ -9,6 +9,9 @@ import { MenuListContext, MenuListProvider } from '../../menuListContext'; describe('MenuItemRadio', () => { isConformant({ asPropHandlesRef: true, + // TODO fix generics in conformance + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Component: MenuItemRadio, requiredProps: { name: 'radio', diff --git a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts index ced279e0c11d9b..29350e6067aea0 100644 --- a/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts +++ b/packages/react-menu/src/components/MenuItemRadio/MenuItemRadio.types.ts @@ -6,9 +6,9 @@ import { MenuItemSelectableProps, MenuItemSelectableState } from '../../selectab * {@docCategory MenuItemRadio} */ export interface MenuItemRadioProps extends ComponentProps, React.HTMLAttributes, MenuItemSelectableProps { - icon?: ShorthandProps; + icon?: ShorthandProps; - checkmark?: ShorthandProps; + checkmark?: ShorthandProps; } /** From 68b4a63636907af2782c34aaaa6c8958b5a64ce4 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 12 Feb 2021 08:28:08 +0000 Subject: [PATCH 72/74] update snapshots --- .../__snapshots__/MenuItemCheckbox.test.tsx.snap | 1 + .../MenuItemRadio/__snapshots__/MenuItemRadio.test.tsx.snap | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/react-menu/src/components/MenuItemCheckbox/__snapshots__/MenuItemCheckbox.test.tsx.snap b/packages/react-menu/src/components/MenuItemCheckbox/__snapshots__/MenuItemCheckbox.test.tsx.snap index 6e45e8e5350dcc..057e6021816346 100644 --- a/packages/react-menu/src/components/MenuItemCheckbox/__snapshots__/MenuItemCheckbox.test.tsx.snap +++ b/packages/react-menu/src/components/MenuItemCheckbox/__snapshots__/MenuItemCheckbox.test.tsx.snap @@ -4,6 +4,7 @@ exports[`MenuItemCheckbox conformance renders a default state 1`] = `
    Date: Fri, 12 Feb 2021 09:55:42 +0000 Subject: [PATCH 73/74] fix change file --- ...tui-react-examples-ee4c87f1-4801-40b7-8c39-fbd92ec8ca6d.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/change/@fluentui-react-examples-ee4c87f1-4801-40b7-8c39-fbd92ec8ca6d.json b/change/@fluentui-react-examples-ee4c87f1-4801-40b7-8c39-fbd92ec8ca6d.json index 8cf5d6fe707836..8cb7fd57475d23 100644 --- a/change/@fluentui-react-examples-ee4c87f1-4801-40b7-8c39-fbd92ec8ca6d.json +++ b/change/@fluentui-react-examples-ee4c87f1-4801-40b7-8c39-fbd92ec8ca6d.json @@ -1,5 +1,5 @@ { - "type": "minor", + "type": "prerelease", "comment": "Add checkbox story for Menu", "packageName": "@fluentui/react-examples", "email": "lingfan.gao@microsoft.com", From 675bc60f2ca1403d40c80f93426058a690175477 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Fri, 12 Feb 2021 12:40:07 +0100 Subject: [PATCH 74/74] deduplicate @babel/* entires --- yarn.lock | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2a40f14201c503..65a0cc1999eb55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1072,7 +1072,7 @@ pirates "^4.0.0" source-map-support "^0.5.16" -"@babel/runtime-corejs3@^7.10.2": +"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.7.4", "@babel/runtime-corejs3@^7.8.3": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.13.tgz#53d09813b7c20d616caf258e9325550ff701c039" integrity sha512-8fSpqYRETHATtNitsCXq8QQbKJP31/KnDl2Wz2Vtui9nKzjss2ysuZtyVsWjBtvkeEFo346gkwjYPab1hvrXkQ== @@ -1080,22 +1080,7 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime-corejs3@^7.7.4", "@babel/runtime-corejs3@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz#f29fc1990307c4c57b10dbd6ce667b27159d9e0d" - integrity sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw== - dependencies: - core-js-pure "^3.0.0" - regenerator-runtime "^0.13.4" - -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99" - integrity sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw== - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.5": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.10.4", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==