diff --git a/.storybook/decorators/index.js b/.storybook/decorators/index.js index 91f45ced79..ba1900993c 100644 --- a/.storybook/decorators/index.js +++ b/.storybook/decorators/index.js @@ -2,6 +2,7 @@ import { makeDecorator, useEffect } from "@storybook/preview-api"; import { html } from "lit"; export { withContextWrapper } from "./contextsWrapper.js"; +export { withDownStateDimensionCapture } from "./withDownStateDimensionCapture.js"; export { withTestingPreviewWrapper } from "./withTestingPreviewWrapper.js"; /** diff --git a/.storybook/decorators/withDownStateDimensionCapture.js b/.storybook/decorators/withDownStateDimensionCapture.js new file mode 100644 index 0000000000..616852d34c --- /dev/null +++ b/.storybook/decorators/withDownStateDimensionCapture.js @@ -0,0 +1,19 @@ +export const withDownStateDimensionCapture = (selector) => (Story, context) => { + const captureDownStateDimensions = () => { + const components = document.querySelectorAll(selector); + components.forEach((component) => { + const { width, height } = component.getBoundingClientRect(); + component.style.setProperty('--spectrum-downstate-width', `${width}px`); + component.style.setProperty('--spectrum-downstate-height', `${height}px`); + }); + }; + + document.addEventListener("DOMContentLoaded", () => { + // Wait to make sure the story is fully rendered (otherwise width/height can be wrong) + setTimeout(() => { + captureDownStateDimensions(); + }, 100); + }); + + return Story(context); +}; diff --git a/.storybook/foundations/down-state/button-down-state.stories.js b/.storybook/foundations/down-state/button-down-state.stories.js new file mode 100644 index 0000000000..e938e302aa --- /dev/null +++ b/.storybook/foundations/down-state/button-down-state.stories.js @@ -0,0 +1,32 @@ +import { Template } from "../../../components/button/stories/template"; + +export default { + title: "Foundations/Down state", + description: + "Buttons allow users to perform an action or to navigate to another page. They have multiple styles for various needs, and are ideal for calling attention to where a user needs to do something in order to move forward in a flow.", + component: "Button", + args: { + rootClass: "spectrum-Button", + }, + parameters: { + actions: { + handles: ['click .spectrum-Button'], + }, + status: { + type: process.env.MIGRATED_PACKAGES.includes("button") + ? "migrated" + : undefined, + }, + }, + tags: ['foundation'], +}; + +export const ButtonDownState = Template.bind({}); +ButtonDownState.args = { + label: "Edit", + variant: "accent", + customStyles: { + "--spectrum-downstate-width": "72px", + "--spectrum-downstate-height": "32px" + } +}; diff --git a/.storybook/foundations/down-state/checkbox-down-state.stories.js b/.storybook/foundations/down-state/checkbox-down-state.stories.js new file mode 100644 index 0000000000..889c4fe4ce --- /dev/null +++ b/.storybook/foundations/down-state/checkbox-down-state.stories.js @@ -0,0 +1,27 @@ +import { Template } from "../../../components/checkbox/stories/template"; + +export default { + title: "Foundations/Down state", + description: + "Checkboxes allow users to select multiple items from a list of individual items, or mark one individual item as selected.", + component: "Checkbox", + args: { + rootClass: "spectrum-Checkbox", + }, + parameters: { + actions: { + handles: ['click input[type="checkbox"]'], + }, + status: { + type: process.env.MIGRATED_PACKAGES.includes("checkbox") + ? "migrated" + : undefined, + }, + }, + tags: ['foundation'], +}; + +export const CheckboxDownState = Template.bind({}); +CheckboxDownState.args = { + label: "Checkbox", +}; diff --git a/.storybook/foundations/down-state/down-state.mdx b/.storybook/foundations/down-state/down-state.mdx new file mode 100644 index 0000000000..f07e65d2f8 --- /dev/null +++ b/.storybook/foundations/down-state/down-state.mdx @@ -0,0 +1,44 @@ +import { Meta, Story } from '@storybook/blocks'; +import * as Checkbox from './checkbox-down-state.stories.js'; +import * as Button from './button-down-state.stories.js'; + + + +# Down state + +Down state is a Spectrum 2 feature that creates the illusion of components being pressed-in when active. This functionality is already included in Spectrum 2 components that require down state in this project. It is implemented with a CSS transform. The implementation depends on the size of the interactable element, as shown in the examples below. + +## Examples + +### Minimum perspective + +For elements that have a width of 24px or less, the minimum perspective token is used to apply the down state. One example of a component that uses this token is the checkbox: + + + +In this case, we use the minimum perspective token: + +``` +transform: + perspective(var(--spectrum-component-size-minimum-perspective-down)) + translateZ(var(--spectrum-component-size-difference-down)); +``` + +### Calculated perspective + +For elements that have a width of greater than 24px, we need to use the component's width and height to apply the down state. One example of a component that uses this logic is the button: + + + +In this case, we use a max formula to calculate the perspective based on component width and height (this helps us account for components that may be very wide): + +``` +transform: + perspective(max( + var(--spectrum-downstate-height), + var(--spectrum-downstate-width) * var(--spectrum-component-size-width-ratio-down) + )) + translateZ(var(--spectrum-component-size-difference-down)); +``` + +*Note that in this case, users are required to develop an implementation to determine the width and height of the component. Assign these values to the `--spectrum-downstate-width` and `--spectrum-downstate-height` custom properties in a `style` attribute on the HTML element to expose them for use in the CSS.* diff --git a/.storybook/main.js b/.storybook/main.js index 48f390a027..966a6005fa 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -11,6 +11,8 @@ module.exports = { stories: [ "../components/*/stories/*.stories.js", "./guides/*.mdx", + "./foundations/*/*.mdx", + "./foundations/*/*.stories.js", "./deprecated/*/*.stories.js", ], rootDir: "../", diff --git a/.storybook/manager.js b/.storybook/manager.js index 654fba3bd9..1618a74f84 100644 --- a/.storybook/manager.js +++ b/.storybook/manager.js @@ -7,8 +7,8 @@ import logo from "./assets/logo.svg"; import pkg from "./package.json"; // Load global styles -import "@spectrum-css/vars/css/globals/index.css"; import "@spectrum-css/vars/css/components/index.css"; +import "@spectrum-css/vars/css/globals/index.css"; import "@spectrum-css/vars/css/scales/spectrum-medium.css"; import "@spectrum-css/vars/css/themes/spectrum-light.css"; @@ -53,5 +53,10 @@ addons.setConfig({ }), sidebar: { showRoots: false, + filters: { + patterns: (item) => { + return !item.tags.includes('foundation'); + } + } }, }); diff --git a/.storybook/preview.js b/.storybook/preview.js index bfa19b5420..ad4f5fc741 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -205,7 +205,7 @@ export const parameters = { options: { storySort: { method: "alphabetical", - order: ['Guides', ['Contributing', '*', 'Adobe Code of Conduct', 'Changelog'], 'Components', '*'], + order: ['Guides', ['Contributing', '*', 'Adobe Code of Conduct', 'Changelog'], 'Foundations', 'Components', '*'], includeNames: true, }, }, diff --git a/components/button/index.css b/components/button/index.css index 8ca9498d74..cb59d7b40a 100644 --- a/components/button/index.css +++ b/components/button/index.css @@ -24,6 +24,9 @@ governing permissions and limitations under the License. --spectrum-button-focus-ring-thickness: var(--spectrum-focus-indicator-thickness); --spectrum-button-focus-indicator-color: var(--spectrum-focus-indicator-color); --spectrum-button-intended-icon-size: var(--spectrum-workflow-icon-size-50); + + /* stylelint-disable-next-line spectrum-tools/no-unknown-custom-properties */ + --spectrum-downstate-perspective: max(var(--spectrum-downstate-height), var(--spectrum-downstate-width) * var(--spectrum-component-size-width-ratio-down)); } .spectrum-Button--sizeS { @@ -42,6 +45,10 @@ governing permissions and limitations under the License. --spectrum-button-bottom-to-text: var(--spectrum-button-bottom-to-text-small); --spectrum-button-top-to-icon: var(--spectrum-component-top-to-workflow-icon-75); --spectrum-button-intended-icon-size: var(--spectrum-workflow-icon-size-75); + + &.spectrum-Button--iconOnly { + --spectrum-downstate-perspective: var(--spectrum-component-size-minimum-perspective-down); + } } .spectrum-Button--sizeM { @@ -147,17 +154,21 @@ governing permissions and limitations under the License. box-shadow: none; } + &:active { + transform: perspective(var(--spectrum-downstate-perspective)) translateZ(var(--spectrum-component-size-difference-down)); + } + .spectrum-Icon { /* Any block-size difference between the intended workflow icon size and actual icon used. Helps support any existing use of smaller UI icons instead of intended Workflow icons. */ --_icon-size-difference: max(0px, - var(--spectrum-button-intended-icon-size) - + var(--spectrum-button-intended-icon-size) - var(--spectrum-icon-block-size, var(--spectrum-button-intended-icon-size)) ); margin-block-start: var(--mod-button-icon-margin-block-start, max(0px, - var(--mod-button-top-to-icon, var(--spectrum-button-top-to-icon)) - + var(--mod-button-top-to-icon, var(--spectrum-button-top-to-icon)) - var(--mod-button-border-width, var(--spectrum-button-border-width)) + (var(--_icon-size-difference, 0px) / 2) ) diff --git a/components/button/package.json b/components/button/package.json index cd71429f97..b94d454358 100644 --- a/components/button/package.json +++ b/components/button/package.json @@ -1,6 +1,6 @@ { "name": "@spectrum-css/button", - "version": "12.0.3-next.0", + "version": "14.0.0-next.2", "description": "The Spectrum CSS button component", "license": "Apache-2.0", "author": "Adobe", diff --git a/components/button/stories/button.stories.js b/components/button/stories/button.stories.js index a5d5dcd4c4..4c6b990f9a 100644 --- a/components/button/stories/button.stories.js +++ b/components/button/stories/button.stories.js @@ -2,6 +2,7 @@ import { html } from "lit"; import { ifDefined } from "lit/directives/if-defined.js"; import { styleMap } from "lit/directives/style-map.js"; import { when } from "lit/directives/when.js"; +import { withDownStateDimensionCapture } from "../../../.storybook/decorators"; import { default as IconStories } from "@spectrum-css/icon/stories/icon.stories.js"; import { Template as Typography } from "@spectrum-css/typography/stories/template.js"; @@ -12,6 +13,7 @@ export default { description: "Buttons allow users to perform an action or to navigate to another page. They have multiple styles for various needs, and are ideal for calling attention to where a user needs to do something in order to move forward in a flow.", component: "Button", + decorators: [withDownStateDimensionCapture('.spectrum-Button:not(:disabled)')], argTypes: { size: { name: "Size", @@ -100,7 +102,7 @@ export default { name: "Layout", description: "How the buttons align in the preview (Storybook only).", type: { name: "string" }, - table: { + table: { type: { summary: "string" }, category: "Advanced" }, @@ -349,4 +351,4 @@ Wrapping.args = { showIconOnlyButton: false, variant: "accent", label: "An example of text overflow behavior within the button component. When the button text is too long for the horizontal space available, it wraps to form another line.", -}; \ No newline at end of file +}; diff --git a/components/checkbox/index.css b/components/checkbox/index.css index 31f68c6380..a61fd135bc 100644 --- a/components/checkbox/index.css +++ b/components/checkbox/index.css @@ -150,6 +150,12 @@ governing permissions and limitations under the License. } } + &:not(.is-readOnly):active { + .spectrum-Checkbox-input:not(:disabled) + .spectrum-Checkbox-box { + transform: perspective(var(--spectrum-component-size-minimum-perspective-down)) translateZ(var(--spectrum-component-size-difference-down)); + } + } + /* Selected Invalid */ &.is-invalid { .spectrum-Checkbox-input:checked + .spectrum-Checkbox-box, @@ -372,6 +378,7 @@ governing permissions and limitations under the License. } } } + /* stylelint-enable max-nesting-depth */ .spectrum-Checkbox-label { @@ -571,7 +578,7 @@ governing permissions and limitations under the License. outline-style: auto; outline-offset: var(--highcontrast-checkbox-focus-indicator-gap, var(--mod-checkbox-focus-indicator-gap, var(--spectrum-checkbox-focus-indicator-gap))); /* stylelint-disable-next-line declaration-block-no-redundant-longhand-properties */ - outline-width: var(--mod-focus-indicator-thickness, var(--spectrum-focus-indicator-thickness)); + outline-width: var(--mod-focus-indicator-thickness, var(--spectrum-focus-indicator-thickness)); &::after { box-shadow: diff --git a/components/checkbox/package.json b/components/checkbox/package.json index 8a9eae6eea..350b9220ca 100644 --- a/components/checkbox/package.json +++ b/components/checkbox/package.json @@ -1,6 +1,6 @@ { "name": "@spectrum-css/checkbox", - "version": "8.1.6-next.0", + "version": "14.0.0-next.2", "description": "The Spectrum CSS checkbox component", "license": "Apache-2.0", "author": "Adobe",