From e47c341261c6e0f55bc70206a4c4aae009167077 Mon Sep 17 00:00:00 2001 From: Puscasu Razvan Date: Thu, 10 Feb 2022 19:39:10 +0200 Subject: [PATCH 1/3] add steplist component --- .../components/steplist/index.css | 390 +++++++++--------- .../components/steplist/skin.css | 58 ++- .../selection/src/useSelectableCollection.ts | 4 +- packages/@react-aria/steplist/README.md | 3 + .../@react-aria/steplist/docs/useStepList.mdx | 39 ++ packages/@react-aria/steplist/index.ts | 13 + packages/@react-aria/steplist/package.json | 38 ++ .../steplist/src/StepListKeyboardDelegate.ts | 102 +++++ packages/@react-aria/steplist/src/index.ts | 14 + .../@react-aria/steplist/src/useStepList.ts | 70 ++++ .../steplist/src/useStepListItem.ts | 38 ++ .../steplist/test/useStepList.test.js | 22 + packages/@react-spectrum/steplist/README.md | 3 + .../steplist/chromatic/StepList.chromatic.tsx | 28 ++ .../steplist/docs/StepList.mdx | 17 + packages/@react-spectrum/steplist/index.ts | 13 + .../@react-spectrum/steplist/intl/en-US.json | 6 + .../@react-spectrum/steplist/intl/index.js | 5 + .../@react-spectrum/steplist/package.json | 67 +++ .../@react-spectrum/steplist/src/StepList.tsx | 60 +++ .../steplist/src/StepListItem.tsx | 91 ++++ .../@react-spectrum/steplist/src/index.ts | 15 + .../steplist/stories/StepList.stories.tsx | 239 +++++++++++ .../steplist/test/StepList.test.js | 29 ++ packages/@react-stately/steplist/README.md | 3 + .../steplist/docs/useStepListState.mdx | 0 packages/@react-stately/steplist/index.ts | 13 + packages/@react-stately/steplist/package.json | 33 ++ packages/@react-stately/steplist/src/index.ts | 13 + .../steplist/src/useStepListState.ts | 88 ++++ packages/@react-types/steplist/README.md | 3 + packages/@react-types/steplist/package.json | 19 + packages/@react-types/steplist/src/index.d.ts | 88 ++++ 33 files changed, 1427 insertions(+), 197 deletions(-) create mode 100644 packages/@react-aria/steplist/README.md create mode 100644 packages/@react-aria/steplist/docs/useStepList.mdx create mode 100644 packages/@react-aria/steplist/index.ts create mode 100644 packages/@react-aria/steplist/package.json create mode 100644 packages/@react-aria/steplist/src/StepListKeyboardDelegate.ts create mode 100644 packages/@react-aria/steplist/src/index.ts create mode 100644 packages/@react-aria/steplist/src/useStepList.ts create mode 100644 packages/@react-aria/steplist/src/useStepListItem.ts create mode 100644 packages/@react-aria/steplist/test/useStepList.test.js create mode 100644 packages/@react-spectrum/steplist/README.md create mode 100644 packages/@react-spectrum/steplist/chromatic/StepList.chromatic.tsx create mode 100644 packages/@react-spectrum/steplist/docs/StepList.mdx create mode 100644 packages/@react-spectrum/steplist/index.ts create mode 100644 packages/@react-spectrum/steplist/intl/en-US.json create mode 100644 packages/@react-spectrum/steplist/intl/index.js create mode 100644 packages/@react-spectrum/steplist/package.json create mode 100644 packages/@react-spectrum/steplist/src/StepList.tsx create mode 100644 packages/@react-spectrum/steplist/src/StepListItem.tsx create mode 100644 packages/@react-spectrum/steplist/src/index.ts create mode 100644 packages/@react-spectrum/steplist/stories/StepList.stories.tsx create mode 100644 packages/@react-spectrum/steplist/test/StepList.test.js create mode 100644 packages/@react-stately/steplist/README.md create mode 100644 packages/@react-stately/steplist/docs/useStepListState.mdx create mode 100644 packages/@react-stately/steplist/index.ts create mode 100644 packages/@react-stately/steplist/package.json create mode 100644 packages/@react-stately/steplist/src/index.ts create mode 100644 packages/@react-stately/steplist/src/useStepListState.ts create mode 100644 packages/@react-types/steplist/README.md create mode 100644 packages/@react-types/steplist/package.json create mode 100644 packages/@react-types/steplist/src/index.d.ts diff --git a/packages/@adobe/spectrum-css-temp/components/steplist/index.css b/packages/@adobe/spectrum-css-temp/components/steplist/index.css index 205365d2d38..5c29e551604 100644 --- a/packages/@adobe/spectrum-css-temp/components/steplist/index.css +++ b/packages/@adobe/spectrum-css-temp/components/steplist/index.css @@ -13,255 +13,277 @@ governing permissions and limitations under the License. @import '../commons/index.css'; :root { - /* The width of a step */ - --spectrum-steplist-step-width: 80px; + --spectrum-steplist-incomplete-marker-color: var(--spectrum-global-color-gray-400); + /* The color of the complete marker */ + --spectrum-steplist-complete-marker-color: var(--spectrum-global-color-gray-700); + /* The color of the selected marker */ + --spectrum-steplist-current-marker-color: var(--spectrum-global-color-gray-900); /* The diameter of the marker */ - --spectrum-steplist-marker-diameter: 8px; + --spectrum-steplist-marker-diameter-small: 8px; + --spectrum-steplist-marker-diameter-medium: 16px; + --spectrum-steplist-marker-diameter-large: 24px; + --spectrum-steplist-marker-diameter-xlarge: 32px; - /* The width of the hit area */ - --spectrum-steplist-marker-hitArea: 20px; + /* The width of the border */ + --spectrum-steplist-marker-border-width: 2px; + --spectrum-steplist-marker-total-border-width: calc(var(--spectrum-steplist-marker-border-width) * 4); /* The height of the line */ --spectrum-steplist-segment-height: 2px; - /* This gives enough space for one line of text */ - --spectrum-steplist-topPadding: 22px; - --spectrum-steplist-small-topPadding: 11px; + /* The width of the marker including borders */ + --spectrum-steplist-marker-box-width-small: calc(var(--spectrum-steplist-marker-diameter-small) + var(--spectrum-steplist-marker-total-border-width)); + --spectrum-steplist-marker-box-width-medium: calc(var(--spectrum-steplist-marker-diameter-medium) + var(--spectrum-steplist-marker-total-border-width)); + --spectrum-steplist-marker-box-width-large: calc(var(--spectrum-steplist-marker-diameter-large) + var(--spectrum-steplist-marker-total-border-width)); + --spectrum-steplist-marker-box-width-xlarge: calc(var(--spectrum-steplist-marker-diameter-xlarge) + var(--spectrum-steplist-marker-total-border-width)); - /* This gives enough space for the longest possible label */ - --spectrum-steplist-sidePadding: 60px; + /* Distance between chevron and previous/next link */ + --spectrum-steplist-chevron-link-gap-small: 6px; + --spectrum-steplist-chevron-link-gap-medium: 12px; + --spectrum-steplist-chevron-link-gap-large: 16px; + --spectrum-steplist-chevron-link-gap-xlarge: 24px; - /* Offset from the BOTTOM of the steplist */ - --spectrum-steplist-label-labelOffset: 10px; + /* Distance between number bullet and label */ + --spectrum-steplist-number-label-gap-small: 6px; + --spectrum-steplist-number-label-gap-medium: 8px; + --spectrum-steplist-number-label-gap-large: 12px; + --spectrum-steplist-number-label-gap-xlarge: 16px; + + /* The distance between steps in vertical */ + --spectrum-steplist-vertical-separation: 28px; - /* Font size of the label */ - --spectrum-steplist-label-text-size: 12px; } .spectrum-Steplist { - /* Contain child elements with positive z-index */ - z-index: 0; - position: relative; - - display: block; - vertical-align: top; + padding: 0; margin: 0; - padding-top: var(--spectrum-steplist-topPadding); - padding-left: var(--spectrum-steplist-sidePadding); - padding-right: var(--spectrum-steplist-sidePadding); - - text-align: center; - white-space: nowrap; - font-size: 0; /* To remove html whitespace between inline elements */ - line-height: 16px; /* in case the container changes it */ + display: flex; + flex-wrap: nowrap; + overflow: hidden; } -.spectrum-Steplist--interactive { - .spectrum-Steplist-label, - .spectrum-Steplist-markerContainer { - /* Show cursor on hit zone when clickable */ - cursor: pointer; - } +.spectrum-Steplist-marker-focus { + padding: var(--spectrum-steplist-marker-border-width); + border-width: var(--spectrum-steplist-marker-border-width); + border-color: transparent; + border-style: solid; + border-radius: 100%; } -/* Small (w/o label or tooltip) */ -.spectrum-Steplist--small { - padding: var(--spectrum-steplist-small-topPadding) 0; - - .spectrum-Steplist-item { - padding: 0 40px 0 0; - width: 80px; - - /* Disable Label */ - .spectrum-Steplist-label { - display: none; - } - - &:first-child, &:last-child { - width: 20px; - } - - /* Disable first line */ - &:first-child { - .spectrum-Steplist-markerContainer { - margin-left: -10px; - } - } +.spectrum-Steplist-item { + display: grid; + justify-items: center; + align-items: start; + grid-template-rows: auto; + grid-template-areas: + "chevron link"; + .spectrum-Steplist-segment { + grid-area: chevron; + height: 100%; + display: flex; + justify-content: center; + flex-direction: column; + } - &:last-child { - .spectrum-Steplist-markerContainer { - margin-right: -10px; - } + .spectrum-Steplist-chevron { + &.is-reversed { + transform: scale(-1); } } -} - -.spectrum-Steplist-item { - outline: none; - position: relative; - display: inline-block; - margin: 0; - - /* Don't let tooltips mess up alignment */ - vertical-align: bottom; - box-sizing: content-box; - width: var(--spectrum-steplist-step-width); - padding: 0 calc(var(--spectrum-steplist-step-width) / 2) 0 0; + /* Focus */ + &:focus, + &.is-focused, + *:focus { + outline: none; + } /* Completed State */ - &.is-complete { + &.is-completed { .spectrum-Steplist-marker { border: none; } - - /* The next step's line should be solid */ - & + .spectrum-Steplist-item .spectrum-Steplist-segment { - border-bottom-width: var(--spectrum-steplist-segment-height); - border-bottom-style: solid; - } } /* Selected state */ &.is-selected { - /* Focus */ - &:focus, - &.is-focused, - *:focus { - outline: none; + .spectrum-Steplist-marker { + border: none; } - .spectrum-Steplist-marker { + } + &.is-navigable .spectrum-Steplist-link { + cursor: pointer; + } + &.is-hovered { + &.is-navigable .spectrum-Steplist-marker { border: none; } } } -.spectrum-Steplist-label { - position: absolute; - left: 50%; - bottom: var(--spectrum-steplist-label-labelOffset); +.spectrum-Steplist-link { + grid-area: link; + display: grid; + justify-items: center; + align-items: center; + grid-template-areas: "number label"; + + .spectrum-Steplist-marker { + grid-area: number; + outline: none; + box-sizing: border-box; /* So we can stroke nicely */ + border-style: solid; + border-width: var(--spectrum-steplist-marker-border-width); + display: flex; + flex-direction: column; + align-items: center; + font-weight: bold; + } + .spectrum-Steplist-label { + grid-area: label; + } +} - display: block; - width: calc(var(--spectrum-steplist-step-width) * 1.5); +/* Small size */ +.spectrum-Steplist--small { + line-height: calc(var(--spectrum-steplist-marker-diameter-small) + (var(--spectrum-steplist-marker-border-width) * 4)); + column-gap: calc(var(--spectrum-steplist-chevron-link-gap-small) + (var(--spectrum-steplist-marker-border-width) * 2)); + font-size: var(--spectrum-global-dimension-font-size-50); - font-size: var(--spectrum-steplist-label-text-size); - white-space: normal; + .spectrum-Steplist-item { + grid-template-columns: var(--spectrum-steplist-marker-box-width-small) auto; + grid-column-gap: var(--spectrum-steplist-chevron-link-gap-small); + } + .spectrum-Steplist-link { + grid-column-gap: var(--spectrum-steplist-number-label-gap-small); - transform: translateX(-50%); + .spectrum-Steplist-marker { + line-height: var(--spectrum-steplist-marker-diameter-small); + width: var(--spectrum-steplist-marker-diameter-small); + height: var(--spectrum-steplist-marker-diameter-small); + border-radius: var(--spectrum-steplist-marker-diameter-small); + font-size: 0; + } + } + .spectrum-Steplist-chevron { + transform: scale(0.9); + } } -.spectrum-Steplist-markerContainer { - display: block; - - z-index: 2; - position: absolute; - bottom: -10px; - left: 50%; +/* Medium size */ +.spectrum-Steplist--medium { + line-height: calc(var(--spectrum-steplist-marker-diameter-medium) + (var(--spectrum-steplist-marker-border-width) * 4)); + column-gap: calc(var(--spectrum-steplist-chevron-link-gap-medium) + (var(--spectrum-steplist-marker-border-width) * 2)); + font-size: var(--spectrum-global-dimension-font-size-75); - width: var(--spectrum-steplist-marker-hitArea); - height: var(--spectrum-steplist-marker-hitArea); + .spectrum-Steplist-item { + grid-template-columns: var(--spectrum-steplist-marker-box-width-medium) auto; + grid-column-gap: var(--spectrum-steplist-chevron-link-gap-medium); + } + .spectrum-Steplist-link { + grid-column-gap: var(--spectrum-steplist-number-label-gap-medium); - margin-left: calc(calc(var(--spectrum-steplist-marker-hitArea) / 2) * -1); + .spectrum-Steplist-marker { + line-height: var(--spectrum-steplist-marker-diameter-medium); + width: var(--spectrum-steplist-marker-diameter-medium); + height: var(--spectrum-steplist-marker-diameter-medium); + border-radius: var(--spectrum-steplist-marker-diameter-medium); + font-size: var(--spectrum-global-dimension-font-size-50); + } + } } -/* Circle step marker */ -.spectrum-Steplist-marker { - box-sizing: border-box; /* So we can stroke nicely */ - display: block; - - position: absolute; - top: 50%; - left: 50%; - margin-top: calc(calc(var(--spectrum-steplist-marker-diameter) / 2) * -1); - margin-left: calc(calc(var(--spectrum-steplist-marker-diameter) / 2) * -1); +/* large size */ +.spectrum-Steplist--large { + line-height: calc(var(--spectrum-steplist-marker-diameter-large) + (var(--spectrum-steplist-marker-border-width) * 4)); + column-gap: calc(var(--spectrum-steplist-chevron-link-gap-large) + (var(--spectrum-steplist-marker-border-width) * 2)); + font-size: var(--spectrum-global-dimension-font-size-150); - width: var(--spectrum-steplist-marker-diameter); - height: var(--spectrum-steplist-marker-diameter); - - border-radius: var(--spectrum-steplist-marker-diameter); + .spectrum-Steplist-item { + grid-template-columns: var(--spectrum-steplist-marker-box-width-large) auto; + grid-column-gap: var(--spectrum-steplist-chevron-link-gap-large); + } + .spectrum-Steplist-link { + grid-column-gap: var(--spectrum-steplist-number-label-gap-large); - border: 2px solid; - border-color: transparent; - background: transparent; + .spectrum-Steplist-marker { + line-height: var(--spectrum-steplist-marker-diameter-large); + width: var(--spectrum-steplist-marker-diameter-large); + height: var(--spectrum-steplist-marker-diameter-large); + border-radius: var(--spectrum-steplist-marker-diameter-large); + font-size: var(--spectrum-global-dimension-font-size-100); + } + } + .spectrum-Steplist-chevron { + transform: scale(1.25); + } } -/* Step line */ -.spectrum-Steplist-segment { - display: block; - z-index: 1; - position: absolute; - right: calc(var(--spectrum-steplist-step-width) - calc(var(--spectrum-steplist-marker-diameter) * 1.5)); +/* xlarge size */ +.spectrum-Steplist--xlarge { + line-height: calc(var(--spectrum-steplist-marker-diameter-xlarge) + (var(--spectrum-steplist-marker-border-width) * 4)); + column-gap: calc(var(--spectrum-steplist-chevron-link-gap-xlarge) + (var(--spectrum-steplist-marker-border-width) * 2)); + font-size: var(--spectrum-global-dimension-font-size-200); - box-sizing: content-box; - width: calc(calc(var(--spectrum-steplist-step-width) * 1.5) - calc(var(--spectrum-steplist-marker-diameter) * 2)); - bottom: calc(calc(var(--spectrum-steplist-segment-height) / 2) * -1); + .spectrum-Steplist-item { + grid-template-columns: var(--spectrum-steplist-marker-box-width-xlarge) auto; + grid-column-gap: var(--spectrum-steplist-chevron-link-gap-xlarge); + } + .spectrum-Steplist-link { + grid-column-gap: var(--spectrum-steplist-number-label-gap-xlarge); - /* Default is dashed */ - border-bottom-width: var(--spectrum-steplist-segment-height); - border-bottom-style: dashed; + .spectrum-Steplist-marker { + line-height: var(--spectrum-steplist-marker-diameter-xlarge); + width: var(--spectrum-steplist-marker-diameter-xlarge); + height: var(--spectrum-steplist-marker-diameter-xlarge); + border-radius: var(--spectrum-steplist-marker-diameter-xlarge); + font-size: var(--spectrum-global-dimension-font-size-150); + } + } + .spectrum-Steplist-chevron { + transform: scale(1.5); + } } + /* First step overrides */ .spectrum-Steplist-item:first-child { - .spectrum-Steplist-markerContainer { - left: 0; - } - - .spectrum-Steplist-label { - left: 0; - } - + grid-template-areas: "link"; + grid-template-columns: auto; .spectrum-Steplist-segment { /* Not visible for first step */ display: none; } } -/* Last step overrides */ -.spectrum-Steplist-item:last-child { - .spectrum-Steplist-label { - left: auto; - right: 0; - transform: translateX(50%); +/* vertical version */ +.spectrum-Steplist--vertical { + flex-direction: column; + gap: 0; + .spectrum-Steplist-item { + grid-template-areas: "chevron ." + "link link"; + justify-items: left; + &.is-navigable { + .spectrum-Steplist-segment { + border-style: solid; + } + } } - - .spectrum-Steplist-markerContainer { - left: auto; - right: 0; - - margin-left: 0; - margin-right: calc(calc(var(--spectrum-steplist-marker-hitArea) / 2) * -1); + .spectrum-Steplist-chevron { + display: none; } - .spectrum-Steplist-segment { - right: var(--spectrum-steplist-marker-diameter); - left: auto; - } -} - -.spectrum-Steplist-item:first-child, -.spectrum-Steplist-item:last-child { - width: calc(calc(var(--spectrum-steplist-step-width) / 2) - calc(var(--spectrum-steplist-marker-diameter) * 2.5)); -} - -/* Single step */ -.spectrum-Steplist-item:only-child { - .spectrum-Steplist-label { - /* Be centered */ - left: 50%; - - /* Override last-child bits */ - transform: translate(-50%); - } - - .spectrum-Steplist-markerContainer { - /* Be centered */ - left: 50%; - - /* Override last-child bits */ - margin-right: 0; - margin-left: calc(calc(var(--spectrum-steplist-marker-hitArea) / 2) * -1); + grid-area: chevron; + justify-self: center; + display: block; + box-sizing: content-box; + height: var(--spectrum-steplist-vertical-separation); + width: 0; + border-width: 0; + border-left-width: 2px; + border-left-style: dashed; } } diff --git a/packages/@adobe/spectrum-css-temp/components/steplist/skin.css b/packages/@adobe/spectrum-css-temp/components/steplist/skin.css index e33fcc6236e..d5d56b05020 100644 --- a/packages/@adobe/spectrum-css-temp/components/steplist/skin.css +++ b/packages/@adobe/spectrum-css-temp/components/steplist/skin.css @@ -18,17 +18,21 @@ governing permissions and limitations under the License. /* Default Marker Style is 'incomplete' */ .spectrum-Steplist-marker { - border-color: var(--spectrum-steplist-incomplete-marker-color) + border-color: var(--spectrum-steplist-incomplete-marker-color); + color: transparent; + } /* Default Line Style is 'incomplete' */ .spectrum-Steplist-segment { - border-bottom-color: var(--spectrum-steplist-incomplete-segment-color) + .spectrum-Steplist-chevron { + color: var(--spectrum-steplist-incomplete-marker-color); + } } .spectrum-Steplist-item { /* Step Completed */ - &.is-complete { + &.is-completed { .spectrum-Steplist-label { color: var(--spectrum-steplist-complete-label-text-color) } @@ -47,10 +51,26 @@ governing permissions and limitations under the License. } } - &.is-complete, + &.is-completed, &.is-selected { - .spectrum-Steplist-segment { - border-bottom-color: var(--spectrum-steplist-complete-segment-color) + .spectrum-Steplist-marker { + color: var(--spectrum-global-color-gray-50); + } + } + &.is-hovered { + &.is-navigable { + .spectrum-Steplist-marker { + color: var(--spectrum-global-color-gray-50); + } + } + } + /* Steplist emphasized*/ + &.is-emphasized { + .spectrum-Steplist-label { + color: var(--spectrum-global-color-blue-500) + } + .spectrum-Steplist-marker { + background-color: var(--spectrum-global-color-blue-500) } } } @@ -59,11 +79,27 @@ governing permissions and limitations under the License. &.spectrum-Steplist--interactive { .spectrum-Steplist-item { /* Step Focused */ - &:focus, - &.is-focused, - *:focus { - .spectrum-Steplist-marker { - background-color: var(--spectrum-steplist-current-marker-color-key-focus) + &.is-keyboard-focused { + .spectrum-Steplist-marker-focus { + border-color: var(--spectrum-steplist-current-marker-color-key-focus) + } + } + &.is-hovered { + &.is-navigable { + .spectrum-Steplist-marker { + background-color: var(--spectrum-steplist-current-marker-color) + } + } + } + /* Steplist emphasized*/ + &.is-emphasized { + &.is-navigable { + .spectrum-Steplist-label { + color: var(--spectrum-global-color-blue-500) + } + .spectrum-Steplist-marker { + background-color: var(--spectrum-global-color-blue-500) + } } } } diff --git a/packages/@react-aria/selection/src/useSelectableCollection.ts b/packages/@react-aria/selection/src/useSelectableCollection.ts index c78b28b5be6..de7c2c62d5e 100644 --- a/packages/@react-aria/selection/src/useSelectableCollection.ts +++ b/packages/@react-aria/selection/src/useSelectableCollection.ts @@ -31,7 +31,7 @@ interface SelectableCollectionOptions { /** * The ref attached to the element representing the collection. */ - ref: RefObject, + ref: RefObject, /** * Whether the collection or one of its items should be automatically focused upon render. * @default false @@ -78,7 +78,7 @@ interface SelectableCollectionOptions { * The ref attached to the scrollable body. Used to provide automatic scrolling on item focus for non-virtualized collections. * If not provided, defaults to the collection ref. */ - scrollRef?: RefObject + scrollRef?: RefObject } interface SelectableCollectionAria { diff --git a/packages/@react-aria/steplist/README.md b/packages/@react-aria/steplist/README.md new file mode 100644 index 00000000000..062f5d0c1fb --- /dev/null +++ b/packages/@react-aria/steplist/README.md @@ -0,0 +1,3 @@ +# @react-aria/steplist + +This package is part of [react-spectrum](https://github.com/adobe/react-spectrum). See the repo for more details. diff --git a/packages/@react-aria/steplist/docs/useStepList.mdx b/packages/@react-aria/steplist/docs/useStepList.mdx new file mode 100644 index 00000000000..e6280c5f822 --- /dev/null +++ b/packages/@react-aria/steplist/docs/useStepList.mdx @@ -0,0 +1,39 @@ +import {Layout} from '@react-spectrum/docs'; +export default Layout; + +import docs from 'docs:@react-aria/steplist'; +import {HeaderInfo, FunctionAPI, TypeContext, InterfaceType} from '@react-spectrum/docs'; +import packageData from '@react-aria/steplist/package.json'; + +```jsx import +import {useStepList} from '@react-aria/steplist'; +``` + +--- +category: Category Name +keywords: [] +--- + +# useStepList + +

{docs.exports.useStepList.description}

+ + + +## API + + + +## Features + +*Describe what the aria hook helps with/provides.* + +## Anatomy/Usage + +*For hooks that are meant to be used with specific elements/components, include an Anatomy section detailing the props the hook returns.* +*For hooks that are meant for more general use, include a Usage section detailing the props/params the hook accepts and returns.* diff --git a/packages/@react-aria/steplist/index.ts b/packages/@react-aria/steplist/index.ts new file mode 100644 index 00000000000..1210ae1e402 --- /dev/null +++ b/packages/@react-aria/steplist/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export * from './src'; diff --git a/packages/@react-aria/steplist/package.json b/packages/@react-aria/steplist/package.json new file mode 100644 index 00000000000..0b08ca5e6f4 --- /dev/null +++ b/packages/@react-aria/steplist/package.json @@ -0,0 +1,38 @@ +{ + "name": "@react-aria/steplist", + "version": "3.0.0-alpha.1", + "private": true, + "description": "Spectrum UI components in React", + "license": "Apache-2.0", + "main": "dist/main.js", + "module": "dist/module.js", + "types": "dist/types.d.ts", + "source": "src/index.ts", + "files": [ + "dist", + "src" + ], + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/adobe/react-spectrum" + }, + "dependencies": { + "@babel/runtime": "^7.6.2", + "@react-aria/i18n": "3.2.0", + "@react-aria/interactions": "3.3.2", + "@react-aria/link": "^3.2.0", + "@react-aria/selection": "^3.7.1", + "@react-aria/tabs": "^3.1.0", + "@react-aria/utils": "3.4.1", + "@react-stately/list": "3.2.2", + "@react-types/shared": "3.3.0", + "@react-types/steplist": "^3.0.0-alpha.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/@react-aria/steplist/src/StepListKeyboardDelegate.ts b/packages/@react-aria/steplist/src/StepListKeyboardDelegate.ts new file mode 100644 index 00000000000..6e2b78c7ab8 --- /dev/null +++ b/packages/@react-aria/steplist/src/StepListKeyboardDelegate.ts @@ -0,0 +1,102 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {Collection, Direction, KeyboardDelegate, Orientation} from '@react-types/shared'; +import {Key} from 'react'; + +export class StepListKeyboardDelegate implements KeyboardDelegate { + private collection: Collection; + private flipDirection: boolean; + private disabledKeys: Set; + private orientation: Orientation; + + constructor(collection: Collection, direction: Direction, orientation: Orientation, disabledKeys: Set = new Set()) { + this.collection = collection; + this.flipDirection = direction === 'rtl' && orientation === 'horizontal'; + this.orientation = orientation; + this.disabledKeys = disabledKeys; + } + + getKeyLeftOf(key: Key) { + console.log('this.flipDirection', this.flipDirection); + if (this.flipDirection) { + return this.getNextKey(key); + } else { + if (this.orientation === 'horizontal') { + return this.getPreviousKey(key); + } + return null; + } + } + + getKeyRightOf(key: Key) { + console.log('this.flipDirection RIGHT', this.flipDirection); + if (this.flipDirection) { + return this.getPreviousKey(key); + } else { + if (this.orientation === 'horizontal') { + return this.getNextKey(key); + } + return null; + } + } + + getKeyAbove(key: Key) { + if (this.orientation === 'vertical') { + return this.getPreviousKey(key); + } + return null; + } + + getKeyBelow(key: Key) { + if (this.orientation === 'vertical') { + return this.getNextKey(key); + } + return null; + } + + getFirstKey() { + let key = this.collection.getFirstKey(); + if (this.disabledKeys.has(key)) { + key = this.getNextKey(key); + } + return key; + } + + getLastKey() { + let key = this.collection.getLastKey(); + if (this.disabledKeys.has(key)) { + key = this.getPreviousKey(key); + } + return key; + } + + getNextKey(key) { + do { + key = this.collection.getKeyAfter(key); + if (key == null) { + key = this.collection.getFirstKey(); + } + } while (this.disabledKeys.has(key)); + return key; + } + + getPreviousKey(key) { + do { + key = this.collection.getKeyBefore(key); + if (key == null) { + key = this.collection.getLastKey(); + } + } while (this.disabledKeys.has(key)); + return key; + } +} diff --git a/packages/@react-aria/steplist/src/index.ts b/packages/@react-aria/steplist/src/index.ts new file mode 100644 index 00000000000..c54fe749fdc --- /dev/null +++ b/packages/@react-aria/steplist/src/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export * from './useStepList'; +export * from './useStepListItem'; diff --git a/packages/@react-aria/steplist/src/useStepList.ts b/packages/@react-aria/steplist/src/useStepList.ts new file mode 100644 index 00000000000..5e56156769a --- /dev/null +++ b/packages/@react-aria/steplist/src/useStepList.ts @@ -0,0 +1,70 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + + +import {filterDOMProps, mergeProps} from '@react-aria/utils'; +import {HTMLAttributes, RefObject, useMemo} from 'react'; +// @ts-ignore +import intlMessages from '../intl/*.json'; +import {SpectrumStepListProps, StepListAria, StepListState} from '@react-types/steplist'; +import {StepListKeyboardDelegate} from './StepListKeyboardDelegate'; +import {useLocale, useMessageFormatter} from '@react-aria/i18n'; +import {useSelectableCollection} from '@react-aria/selection'; + +export function useStepList(props: SpectrumStepListProps, state: StepListState, ref: RefObject): StepListAria { + let { + isDisabled, + 'aria-label': ariaLabel, + orientation = 'horizontal', + keyboardActivation = 'automatic' + } = props; + let allKeys = [...state.collection.getKeys()]; + + if (!allKeys.some(key => !state.disabledKeys.has(key))) { + isDisabled = true; + } + + let { + collection, + selectionManager: manager, + disabledKeys + } = state; + let {direction} = useLocale(); + let delegate = useMemo(() => new StepListKeyboardDelegate( + collection, + direction, + orientation, + disabledKeys), [collection, disabledKeys, orientation, direction]); + + let {collectionProps} = useSelectableCollection({ + ref, + selectionManager: manager, + keyboardDelegate: delegate, + selectOnFocus: keyboardActivation === 'automatic', + disallowEmptySelection: true, + scrollRef: ref + }); + + const formatMessage = useMessageFormatter(intlMessages); + const listProps: HTMLAttributes = { + ...mergeProps(collectionProps, filterDOMProps(props)), + 'aria-orientation': orientation, + 'aria-label': ariaLabel || formatMessage('steplist'), + 'aria-disabled': isDisabled + }; + + return { + listProps + }; +} + + diff --git a/packages/@react-aria/steplist/src/useStepListItem.ts b/packages/@react-aria/steplist/src/useStepListItem.ts new file mode 100644 index 00000000000..da07e25bd93 --- /dev/null +++ b/packages/@react-aria/steplist/src/useStepListItem.ts @@ -0,0 +1,38 @@ +import {RefObject} from 'react'; +// @ts-ignore +import {StepListItemAria, StepListItemProps, StepListState} from '@react-types/steplist'; +import {useSelectableItem} from '@react-aria/selection'; + +export function useStepListItem(props: StepListItemProps, state: StepListState, ref: RefObject): StepListItemAria { + const {isDisabled: propsDisabled, item} = props; + const {key} = item; + + let {selectionManager: manager, selectedKey} = state; + + let isDisabled = propsDisabled || state.disabledKeys.has(key); + + let {itemProps} = useSelectableItem({ + selectionManager: manager, + key, + ref, + isDisabled + }); + + const isSelected = selectedKey === key; + const isFocused = key === manager.focusedKey; + + let {tabIndex} = itemProps; + + return { + stepProps: { + ...itemProps, + 'aria-selected': isSelected, + 'aria-current': isSelected ? 'step' : undefined, + 'aria-disabled': isDisabled || undefined, + 'aria-live': isFocused ? 'assertive' : undefined, + 'aria-atomic': isFocused || undefined, + 'aria-relevant': isFocused ? 'text' : undefined, + tabIndex: isDisabled ? undefined : tabIndex + } + }; +} diff --git a/packages/@react-aria/steplist/test/useStepList.test.js b/packages/@react-aria/steplist/test/useStepList.test.js new file mode 100644 index 00000000000..db0a42a1871 --- /dev/null +++ b/packages/@react-aria/steplist/test/useStepList.test.js @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {render} from '@testing-library/react'; +import {renderHook} from '@testing-library/react-hooks'; +import React, {useRef} from 'react'; +import {useStepList} from '../'; + +describe('useStepList', function () { + it('fill me in', function () { + expect(true).toBeTruthy(); + }); +}); diff --git a/packages/@react-spectrum/steplist/README.md b/packages/@react-spectrum/steplist/README.md new file mode 100644 index 00000000000..603d1a53e15 --- /dev/null +++ b/packages/@react-spectrum/steplist/README.md @@ -0,0 +1,3 @@ +# @react-spectrum/steplist + +This package is part of [react-spectrum](https://github.com/adobe/react-spectrum). See the repo for more details. diff --git a/packages/@react-spectrum/steplist/chromatic/StepList.chromatic.tsx b/packages/@react-spectrum/steplist/chromatic/StepList.chromatic.tsx new file mode 100644 index 00000000000..469cfef39f6 --- /dev/null +++ b/packages/@react-spectrum/steplist/chromatic/StepList.chromatic.tsx @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {StepList, StepListProps} from '../'; +import React from 'react'; +import {storiesOf} from '@storybook/react'; + +storiesOf('StepList', module) + .add( + 'name me', + () => render({}) + ); + +function render(props:StepListProps = {}) { + return ( + + + ); +} diff --git a/packages/@react-spectrum/steplist/docs/StepList.mdx b/packages/@react-spectrum/steplist/docs/StepList.mdx new file mode 100644 index 00000000000..614221c88db --- /dev/null +++ b/packages/@react-spectrum/steplist/docs/StepList.mdx @@ -0,0 +1,17 @@ +import {Layout} from '@react-spectrum/docs'; +export default Layout; + +import docs from 'docs:@react-spectrum/steplist'; +import {HeaderInfo, PropTable} from '@react-spectrum/docs'; +import packageData from '@react-spectrum/steplist/package.json'; + +```jsx import +import {StepList} from '@react-spectrum/steplist'; +``` + +--- +category: Category Name +keywords: [] +--- + +# StepList diff --git a/packages/@react-spectrum/steplist/index.ts b/packages/@react-spectrum/steplist/index.ts new file mode 100644 index 00000000000..1210ae1e402 --- /dev/null +++ b/packages/@react-spectrum/steplist/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export * from './src'; diff --git a/packages/@react-spectrum/steplist/intl/en-US.json b/packages/@react-spectrum/steplist/intl/en-US.json new file mode 100644 index 00000000000..52edecea785 --- /dev/null +++ b/packages/@react-spectrum/steplist/intl/en-US.json @@ -0,0 +1,6 @@ +{ + "completed": "Completed: ", + "notCompleted": "Not completed: ", + "current": "Current: ", + "steplist": "Step List" +} diff --git a/packages/@react-spectrum/steplist/intl/index.js b/packages/@react-spectrum/steplist/intl/index.js new file mode 100644 index 00000000000..eb8677cfc49 --- /dev/null +++ b/packages/@react-spectrum/steplist/intl/index.js @@ -0,0 +1,5 @@ +import enUS from './en-US.json'; + +export default { + 'en-US': enUS +}; diff --git a/packages/@react-spectrum/steplist/package.json b/packages/@react-spectrum/steplist/package.json new file mode 100644 index 00000000000..509abd60abd --- /dev/null +++ b/packages/@react-spectrum/steplist/package.json @@ -0,0 +1,67 @@ +{ + "name": "@react-spectrum/steplist", + "version": "3.0.0-alpha.1", + "private": true, + "description": "Spectrum UI components in React", + "license": "Apache-2.0", + "main": "dist/main.js", + "module": "dist/module.js", + "types": "dist/types.d.ts", + "source": "src/index.ts", + "files": [ + "dist", + "src" + ], + "sideEffects": [ + "*.css" + ], + "targets": { + "main": { + "includeNodeModules": [ + "@adobe/spectrum-css-temp" + ] + }, + "module": { + "includeNodeModules": [ + "@adobe/spectrum-css-temp" + ] + } + }, + "repository": { + "type": "git", + "url": "https://github.com/adobe/react-spectrum" + }, + "dependencies": { + "@babel/runtime": "^7.6.2", + "@react-aria/breadcrumbs": "3.1.1", + "@react-aria/focus": "3.2.3", + "@react-aria/i18n": "3.2.0", + "@react-aria/interactions": "3.3.2", + "@react-aria/sidenav": "3.0.0-alpha.1", + "@react-aria/steplist": "^3.0.0-alpha.1", + "@react-aria/tabs": "^3.1.0", + "@react-aria/utils": "^3.0.0-alpha.1", + "@react-aria/virtualizer": "3.3.1", + "@react-aria/visually-hidden": "3.2.1", + "@react-spectrum/utils": "^3.0.0-alpha.1", + "@react-stately/layout": "3.2.0", + "@react-stately/steplist": "^3.0.0-alpha.1", + "@react-stately/tree": "3.1.2", + "@react-stately/virtualizer": "3.1.2", + "@react-types/breadcrumbs": "3.1.1", + "@react-types/shared": "3.3.0", + "@react-types/sidenav": "3.0.0-alpha.1", + "@react-types/steplist": "^3.0.0-alpha.1", + "@spectrum-icons/ui": "3.2.0" + }, + "devDependencies": { + "@adobe/spectrum-css-temp": "^3.0.0-alpha.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1", + "@react-spectrum/provider": "^3.0.0-rc.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/@react-spectrum/steplist/src/StepList.tsx b/packages/@react-spectrum/steplist/src/StepList.tsx new file mode 100644 index 00000000000..b829d7254b0 --- /dev/null +++ b/packages/@react-spectrum/steplist/src/StepList.tsx @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {classNames, useDOMRef, useStyleProps} from '@react-spectrum/utils'; +import {DOMRef} from '@react-types/shared'; +import React, {RefObject} from 'react'; +import {SpectrumStepListProps} from '@react-types/steplist'; +import {StepListItem} from './StepListItem'; +import styles from '@adobe/spectrum-css-temp/components/steplist/vars.css'; +import {useProviderProps} from '@react-spectrum/provider'; +import {useStepList} from '@react-aria/steplist'; +import {useStepListState} from '@react-stately/steplist'; + +function StepList(props: SpectrumStepListProps, ref: DOMRef) { + const {size = 'M', orientation} = props; + props = useProviderProps(props); + let {styleProps} = useStyleProps(props); + let domRef = useDOMRef(ref) as RefObject; + + let state = useStepListState(props); + let {listProps} = useStepList(props, state, domRef); + + const {isDisabled, isEmphasized, isReadOnly} = props; + const stepListItems = [...state.collection].map((item) => () + ); + return ( +
    + {stepListItems} +
+ ); +} + +const _StepList = React.forwardRef(StepList); +export {_StepList as StepList}; diff --git a/packages/@react-spectrum/steplist/src/StepListItem.tsx b/packages/@react-spectrum/steplist/src/StepListItem.tsx new file mode 100644 index 00000000000..8ba4d4b3078 --- /dev/null +++ b/packages/@react-spectrum/steplist/src/StepListItem.tsx @@ -0,0 +1,91 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import ChevronRightMedium from '@spectrum-icons/ui/ChevronRightMedium'; +import {classNames} from '@react-spectrum/utils'; +import {FocusRing} from '@react-aria/focus'; +import intlMessages from '../intl'; +import {mergeProps} from '@react-aria/utils'; +import React, {useRef} from 'react'; +import {StepListItemProps} from '@react-types/steplist'; +import styles from '@adobe/spectrum-css-temp/components/steplist/vars.css'; +import {useHover} from '@react-aria/interactions'; +import {useLocale, useMessageFormatter} from '@react-aria/i18n'; +import {useStepListItem} from '@react-aria/steplist'; +import {VisuallyHidden} from '@react-aria/visually-hidden'; + +export function StepListItem(props: StepListItemProps) { + let { + isDisabled, + isEmphasized, + item, + state + } = props; + + let ref = useRef(); + let {direction} = useLocale(); + let {stepProps, stepStateProps} = useStepListItem({...props}, state, ref); + + let {hoverProps, isHovered} = useHover(props); + const itemKey = item.key; + const isCompleted = state.isCompleted(itemKey); + const isSelected = state.selectedKey === itemKey; + const isNavigable = state.isNavigable(itemKey); + const isItemDisabled = state.disabledKeys.has(itemKey); + + let stepStateText = ''; + const formatMessage = useMessageFormatter(intlMessages); + + if (isSelected) { + stepStateText = formatMessage('current'); + } else if (isCompleted) { + stepStateText = formatMessage('completed'); + } else { + stepStateText = formatMessage('notCompleted'); + } + + return ( + +
  • + {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} + + {stepStateText} + + {item.rendered} + + + {(item.index || 0) + 1} + + + + + +
  • +
    + ); +} diff --git a/packages/@react-spectrum/steplist/src/index.ts b/packages/@react-spectrum/steplist/src/index.ts new file mode 100644 index 00000000000..8a7ac8a9953 --- /dev/null +++ b/packages/@react-spectrum/steplist/src/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/// + +export * from './StepList'; diff --git a/packages/@react-spectrum/steplist/stories/StepList.stories.tsx b/packages/@react-spectrum/steplist/stories/StepList.stories.tsx new file mode 100644 index 00000000000..e1e181339db --- /dev/null +++ b/packages/@react-spectrum/steplist/stories/StepList.stories.tsx @@ -0,0 +1,239 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +// import {action} from '@storybook/addon-actions'; +import {Item} from '@react-stately/collections'; +import React, {Key, useState} from 'react'; +import {StepList} from '../'; +import {storiesOf} from '@storybook/react'; +import {useAsyncList} from '@react-stately/data'; + +const options = [{ + key: 'cat', value: 'Cat' +}, { + key: 'dog', value: 'Dog' +}, { + key: 'monkey', value: 'Monkey' +}, { + key: 'skunk', value: 'Skunk' +}]; + +storiesOf('StepList', module) + .add('Controlled selection', + () => ControlledSelection() + ) + .add('Controlled selection vertical', + () => ControlledSelectionVertical() + ) + .add('Uncontrolled selection', + () => UncontrolledSelection() + ) + .add('ReadOnly', + () => ReadOnly() + ) + .add('Disabled', + () => Disabled() + ) + .add('DisabledKeys', + () => AllDisabledKeys() + ) + .add( + 'middle disabled', + () => MiddleDisabled() + ) + .add('AsyncItems', + () => AsyncItems() + ) + .add('Medium Size', Medium) + .add('Small Size', Small) + .add('Large Size', Large) + .add('Extra Large Size', XLarge) + .add('Medium Size Vertical', MediumVert) + .add('Small Size Vertical', SmallVert) + .add('Large Size Vertical', LargeVert) + .add('Extra Large Size Vertical', XLargeVert); + +function ControlledSelection() { + const [selectedIdx, setSelected] = useState(0); + function handleSelectionChange(key: Key) { + const selectedIdx = options.findIndex(o => o.key === key); + setSelected(selectedIdx); + } + const currKey = options[selectedIdx].key; + return (
    + + {options.map((o) => {o.value})} + +
    + + +
    +
    ); +} + +function ControlledSelectionVertical() { + const [selectedIdx, setSelected] = useState(0); + function handleSelectionChange(key: Key) { + const selectedIdx = options.findIndex(o => o.key === key); + setSelected(selectedIdx); + } + + const currKey = options[selectedIdx].key; + return (
    + + {options.map((o) => {o.value})} + +
    + + +
    +
    ); +} + +function UncontrolledSelection() { + return ( + + {options.map((o) => {o.value})} + + ); +} + +function ReadOnly() { + return ( + + {options.map((o) => {o.value})} + + ); +} + +function Disabled() { + return ( + + {options.map((o) => {o.value})} + + ); +} + + +function AllDisabledKeys() { + return ( + o.key)} defaultSelectedKey={options[1].key} defaultLastCompletedStep={options[1].key}> + {options.map((o) => {o.value})} + + ); +} + +function MiddleDisabled() { + return ( + + {options.map((o) => {o.value})} + + ); +} + +function Medium() { + return ( + + Offer + Fallback + Summary + + ); +} + +function Small() { + return ( + + Offer + Fallback + Summary + + ); +} + +function Large() { + return ( + + Offer + Fallback + Summary + + ); +} + +function XLarge() { + return ( + + Offer + Fallback + Summary + + ); +} + + +function MediumVert() { + return ( + + Offer + Fallback + Summary + + ); +} + +function SmallVert() { + return ( + + Offer + Fallback + Summary + + ); +} + +function LargeVert() { + return ( + + Offer + Fallback + Summary + + ); +} + +function XLargeVert() { + return ( + + Offer + Fallback + Summary + + ); +} + +function AsyncItems() { + type PokeMon = { name: string }; + let list = useAsyncList({ + async load({signal}) { + let res = await fetch('https://pokeapi.co/api/v2/pokemon', {signal}); + let json = await res.json(); + return {items: json.results.slice(0, 8)}; + } + }); + const [selectedKey, setSelectedKey] = useState('venusaur'); + return (
    + + {(item: PokeMon) => {item.name}} + +
    ); +} diff --git a/packages/@react-spectrum/steplist/test/StepList.test.js b/packages/@react-spectrum/steplist/test/StepList.test.js new file mode 100644 index 00000000000..e63a74737f2 --- /dev/null +++ b/packages/@react-spectrum/steplist/test/StepList.test.js @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import StepList from '../'; +import {render} from '@testing-library/react'; +import React from 'react'; +import V2StepList from '@react/react-spectrum/StepList'; + + +describe('StepList', function () { + it.each` + Name | Component | props + ${'StepList'} | ${StepList}| ${{}} + ${'V2StepList'} | ${V2StepList} | ${{}} + `('$Name handles defaults', function ({Component, props}) { + let {getByRole, getByText} = render(); + + expect(true).toBeTruthy(); + }); +}); diff --git a/packages/@react-stately/steplist/README.md b/packages/@react-stately/steplist/README.md new file mode 100644 index 00000000000..5a8b2faad76 --- /dev/null +++ b/packages/@react-stately/steplist/README.md @@ -0,0 +1,3 @@ +# @react-stately/steplist + +This package is part of [react-spectrum](https://github.com/adobe/react-spectrum). See the repo for more details. diff --git a/packages/@react-stately/steplist/docs/useStepListState.mdx b/packages/@react-stately/steplist/docs/useStepListState.mdx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/@react-stately/steplist/index.ts b/packages/@react-stately/steplist/index.ts new file mode 100644 index 00000000000..1210ae1e402 --- /dev/null +++ b/packages/@react-stately/steplist/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export * from './src'; diff --git a/packages/@react-stately/steplist/package.json b/packages/@react-stately/steplist/package.json new file mode 100644 index 00000000000..71925de4c06 --- /dev/null +++ b/packages/@react-stately/steplist/package.json @@ -0,0 +1,33 @@ +{ + "name": "@react-stately/steplist", + "version": "3.0.0-alpha.1", + "description": "Spectrum UI components in React", + "license": "Apache-2.0", + "private": true, + "main": "dist/main.js", + "module": "dist/module.js", + "types": "dist/types.d.ts", + "source": "src/index.ts", + "files": [ + "dist", + "src" + ], + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/adobe/react-spectrum" + }, + "dependencies": { + "@babel/runtime": "^7.6.2", + "@react-stately/list": "3.2.2", + "@react-stately/utils": "^3.0.0-alpha.1", + "@react-types/shared": "3.3.0", + "@react-types/steplist": "^3.0.0-alpha.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/@react-stately/steplist/src/index.ts b/packages/@react-stately/steplist/src/index.ts new file mode 100644 index 00000000000..f32e5dcaa76 --- /dev/null +++ b/packages/@react-stately/steplist/src/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export * from './useStepListState'; diff --git a/packages/@react-stately/steplist/src/useStepListState.ts b/packages/@react-stately/steplist/src/useStepListState.ts new file mode 100644 index 00000000000..439a7086f43 --- /dev/null +++ b/packages/@react-stately/steplist/src/useStepListState.ts @@ -0,0 +1,88 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {AriaStepListProps, StepListState} from '@react-types/steplist'; +import {Collection, Node} from '@react-types/shared'; +import {Key, useEffect, useMemo} from 'react'; +import {useControlledState} from '@react-stately/utils'; +import {useSingleSelectListState} from '@react-stately/list'; + +export function useStepListState(props: AriaStepListProps): StepListState { + let state = useSingleSelectListState(props); + + let [lastCompletedStep, setLastCompletedStep] = useControlledState(props.lastCompletedStep, props.defaultLastCompletedStep, props.onLastCompletedStepChange); + const {setSelectedKey: realSetSelectedKey, selectedKey, collection} = state; + const {prevKeyMap, indexMap} = useMemo(() => buildKeysMaps(collection), [collection]); + const disabledKeys = useMemo(() => new Set(props.disabledKeys), [props.disabledKeys]); + const selectedIdx = indexMap.get(selectedKey); + if (selectedIdx > 0 && selectedIdx > (indexMap.get(lastCompletedStep) ?? -1) + 1) { + setLastCompletedStep(prevKeyMap.get(selectedKey)); + } + + useEffect(() => { + // Ensure a step is always selected (in case no selected key was specified or if selected item was deleted from collection) + let selectedKey = state.selectedKey; + if (state.selectionManager.isEmpty || !state.collection.getItem(selectedKey)) { + selectedKey = state.collection.getFirstKey(); + state.selectionManager.replaceSelection(selectedKey); + } + + if (state.selectionManager.focusedKey == null) { + state.selectionManager.setFocusedKey(selectedKey); + } + }, [state.selectionManager, state.selectedKey, state.collection]); + + function isCompleted(step: Key) { + if (step === undefined) { + return false; + } + return indexMap.get(step) <= indexMap.get(lastCompletedStep); + } + + function isNavigable(step: Key) { + return !props.isDisabled && !disabledKeys.has(step) && !props.isReadOnly && + (isCompleted(step) || isCompleted(prevKeyMap.get(step))); + } + + function setSelectedKey(key: Key) { + const prevKey = prevKeyMap.get(key); + if (prevKey && !isCompleted(prevKey)) { + setLastCompletedStep(prevKey); + } + realSetSelectedKey(key); + } + + return { + ...state, + setSelectedKey, + setLastCompletedStep, + isCompleted, + isNavigable + }; +} + +function buildKeysMaps(coll: Collection>): { indexMap: Map, prevKeyMap: Map } { + const indexMap = new Map(); + const prevKeyMap = new Map(); + let i = 0; + let prev: Node = undefined; + for (const item of coll) { + indexMap.set(item.key, i); + prevKeyMap.set(item.key, prev?.key); + prev = item; + i++; + } + return { + indexMap, + prevKeyMap + }; +} diff --git a/packages/@react-types/steplist/README.md b/packages/@react-types/steplist/README.md new file mode 100644 index 00000000000..b988ccdffd6 --- /dev/null +++ b/packages/@react-types/steplist/README.md @@ -0,0 +1,3 @@ +# @react-types/steplist + +This package is part of [react-spectrum](https://github.com/adobe/react-spectrum). See the repo for more details. diff --git a/packages/@react-types/steplist/package.json b/packages/@react-types/steplist/package.json new file mode 100644 index 00000000000..0c88cd8256f --- /dev/null +++ b/packages/@react-types/steplist/package.json @@ -0,0 +1,19 @@ +{ + "name": "@react-types/steplist", + "version": "3.0.0-alpha.1", + "description": "Spectrum UI components in React", + "license": "Apache-2.0", + "private": true, + "types": "src/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/adobe/react-spectrum" + }, + "dependencies": { + "@react-stately/list": "3.2.2", + "@react-types/shared": "^3.1.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1" + } +} diff --git a/packages/@react-types/steplist/src/index.d.ts b/packages/@react-types/steplist/src/index.d.ts new file mode 100644 index 00000000000..88a99724540 --- /dev/null +++ b/packages/@react-types/steplist/src/index.d.ts @@ -0,0 +1,88 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {AriaLabelingProps, CollectionBase, Orientation, SingleSelection} from '@react-types/shared'; +import {DOMProps, Node, StyleProps} from '@react-types/shared'; +import {HTMLAttributes, Key} from 'react'; +import {SingleSelectListState} from '@react-stately/list'; + + +interface AriaStepListBase { + /** + * Whether tabs are activated automatically on focus or manually. + * @default 'automatic' + */ + keyboardActivation?: 'automatic' | 'manual', + /** + * The orientation of the tabs. + * @default 'horizontal' + */ + orientation?: Orientation, + /** + * Whether the Tabs are disabled. + * Shows that a selection exists, but is not available in that circumstance. + */ + isDisabled?: boolean +} + +export interface SingleSelectListProps extends CollectionBase, SingleSelection { + /** Filter function to generate a filtered list of nodes. */ + filter?: (nodes: Iterable>) => Iterable> +} + +interface StepListProps extends CollectionBase, SingleSelection, SingleSelectListProps { + /** The key of the last completed step (controlled). */ + lastCompletedStep?: Key, + /** The key of the initially last completed step (uncontrolled). */ + defaultLastCompletedStep?: Key, + /** Callback for when the last completed step changes. */ + onLastCompletedStepChange?: (key: Key) => void, + /** Whether the step list is disabled. Steps will not be focusable or interactive. */ + isDisabled?: boolean, + /** Whether the step list is read only. Steps will be focusable but non-interactive. */ + isReadOnly?: boolean +} + +interface AriaStepListProps extends StepListProps, AriaStepListBase, AriaLabelingProps, DOMProps {} + +interface SpectrumStepListProps extends AriaStepListProps, StyleProps { + isEmphasized?: boolean, + size: 'S' | 'M' | 'L' | 'XL' +} + +interface StepListState extends SingleSelectListState { + readonly lastCompletedStep?: Key, + setLastCompletedStep(key: Key): void, + isCompleted(key: Key): boolean, + isNavigable(key: Key): boolean +} + +interface StepListAria { + listProps: HTMLAttributes +} + +interface StepListItemProps { + isDisabled: boolean, + isEmphasized: boolean, + isReadOnly: boolean, + state: StepListState, + item: Node +} + +interface StepListItemAria { + /** Props for the step link element. */ + stepProps: HTMLAttributes, + /** Props for the visually hidden element indicating the step state. */ + stepStateProps?: HTMLAttributes, + /** Text content for the visually hidden message indicating the status of the step state. */ + stepStateText?: String +} From 25a7cf0c41af45e7508260946f07bc36acc79813 Mon Sep 17 00:00:00 2001 From: Puscasu Razvan Date: Thu, 10 Feb 2022 20:03:28 +0200 Subject: [PATCH 2/3] add steplist aria translations --- packages/@react-aria/steplist/intl/en-US.json | 3 +++ packages/@react-aria/steplist/src/useStepList.ts | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 packages/@react-aria/steplist/intl/en-US.json diff --git a/packages/@react-aria/steplist/intl/en-US.json b/packages/@react-aria/steplist/intl/en-US.json new file mode 100644 index 00000000000..ded046ec988 --- /dev/null +++ b/packages/@react-aria/steplist/intl/en-US.json @@ -0,0 +1,3 @@ +{ + "steplist": "Step List" +} diff --git a/packages/@react-aria/steplist/src/useStepList.ts b/packages/@react-aria/steplist/src/useStepList.ts index 5e56156769a..a14251ca190 100644 --- a/packages/@react-aria/steplist/src/useStepList.ts +++ b/packages/@react-aria/steplist/src/useStepList.ts @@ -54,6 +54,8 @@ export function useStepList(props: SpectrumStepListProps, state: StepListS scrollRef: ref }); + console.log('collectionProps', collectionProps); + const formatMessage = useMessageFormatter(intlMessages); const listProps: HTMLAttributes = { ...mergeProps(collectionProps, filterDOMProps(props)), From aeb64fc2ad085d4e5f7c57d26c7f07f83aec839d Mon Sep 17 00:00:00 2001 From: Puscasu Razvan Date: Thu, 10 Feb 2022 20:30:28 +0200 Subject: [PATCH 3/3] cleanup --- packages/@react-aria/steplist/src/StepListKeyboardDelegate.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/@react-aria/steplist/src/StepListKeyboardDelegate.ts b/packages/@react-aria/steplist/src/StepListKeyboardDelegate.ts index 6e2b78c7ab8..668b1727135 100644 --- a/packages/@react-aria/steplist/src/StepListKeyboardDelegate.ts +++ b/packages/@react-aria/steplist/src/StepListKeyboardDelegate.ts @@ -27,7 +27,6 @@ export class StepListKeyboardDelegate implements KeyboardDelegate { } getKeyLeftOf(key: Key) { - console.log('this.flipDirection', this.flipDirection); if (this.flipDirection) { return this.getNextKey(key); } else { @@ -39,7 +38,6 @@ export class StepListKeyboardDelegate implements KeyboardDelegate { } getKeyRightOf(key: Key) { - console.log('this.flipDirection RIGHT', this.flipDirection); if (this.flipDirection) { return this.getPreviousKey(key); } else {