diff --git a/.changeset/long-carrots-sleep.md b/.changeset/long-carrots-sleep.md new file mode 100644 index 00000000000..03362d6c3da --- /dev/null +++ b/.changeset/long-carrots-sleep.md @@ -0,0 +1,15 @@ +--- +"@spectrum-css/search": major +--- + +## S2 Collapsed search field + +The search component allows for a minimized state where the search field is collapsed to a button. + +### Anatomy + +The collapsed state consists of a single action button that has a hover, keyboard focused, and disabled state. This state is triggered by the `is-collapsed` class on the search component. When the search field is in this state, the textfield is hidden and the search button is displayed. The button can be hovered and focused, and will expand the search field when clicked. + +### Usage + +The collapsed state is used to reduce the amount of space taken up by the search field. It is most commonly used next a filter button to allow users to quickly search and filter content. diff --git a/components/search/dist/metadata.json b/components/search/dist/metadata.json index f57d2c77c0f..0d4bd84abfe 100644 --- a/components/search/dist/metadata.json +++ b/components/search/dist/metadata.json @@ -22,7 +22,9 @@ ".spectrum-Search-textfield.is-focused:hover .spectrum-Search-icon", ".spectrum-Search-textfield.is-keyboardFocused .spectrum-Search-icon", ".spectrum-Search-textfield:hover .spectrum-Search-icon", + ".spectrum-Search.is-collapsed", ".spectrum-Search.is-disabled .spectrum-Search-clearButton", + ".spectrum-Search.is-expanded", ".spectrum-Search:lang(ja)", ".spectrum-Search:lang(ko)", ".spectrum-Search:lang(zh)" @@ -41,6 +43,7 @@ "--mod-search-border-width", "--mod-search-bottom-to-text", "--mod-search-button-inline-size", + "--mod-search-collapsed-animation-duration", "--mod-search-color-default", "--mod-search-color-disabled", "--mod-search-color-focus", @@ -77,6 +80,7 @@ "--spectrum-search-border-width", "--spectrum-search-bottom-to-text", "--spectrum-search-button-inline-size", + "--spectrum-search-collapsed-animation-duration", "--spectrum-search-color", "--spectrum-search-color-default", "--spectrum-search-color-disabled", @@ -102,6 +106,7 @@ "--spectrum-search-top-to-text" ], "global": [ + "--spectrum-animation-duration-800", "--spectrum-border-width-200", "--spectrum-cjk-line-height-100", "--spectrum-component-bottom-to-text-100", diff --git a/components/search/index.css b/components/search/index.css index 5ca303824a4..675279bb8f0 100644 --- a/components/search/index.css +++ b/components/search/index.css @@ -66,6 +66,9 @@ /* stylelint-disable-next-line spectrum-tools/no-unused-custom-properties -- used to assign Textfield mods */ --spectrum-search-border-color-disabled: var(--spectrum-disabled-border-color); + /* Collapsed Search */ + --spectrum-search-collapsed-animation-duration: var(--spectrum-animation-duration-800); + &:lang(ja), &:lang(zh), &:lang(ko) { @@ -149,6 +152,21 @@ .spectrum-HelpText { margin-block-start: var(--mod-search-to-help-text, var(--spectrum-search-to-help-text)); } + + /* Animation for collapsible search expansion */ + &.is-collapsed { + transition: inline-size var(--mod-search-collapsed-animation-duration, var(--spectrum-search-collapsed-animation-duration)) ease-in-out; + inline-size: var(--mod-search-button-inline-size, var(--spectrum-search-block-size)); + min-inline-size: var(--mod-search-button-inline-size, var(--spectrum-search-block-size)); + transform-origin: left center; + } + + &.is-expanded { + transition: inline-size var(--mod-search-collapsed-animation-duration, var(--spectrum-search-collapsed-animation-duration)) ease-in-out; + inline-size: var(--mod-search-inline-size, var(--spectrum-search-inline-size)); + min-inline-size: auto; + transform-origin: left center; + } } .spectrum-Search-clearButton { diff --git a/components/search/stories/search.stories.js b/components/search/stories/search.stories.js index ad429e00554..5eaecf655f1 100644 --- a/components/search/stories/search.stories.js +++ b/components/search/stories/search.stories.js @@ -28,6 +28,7 @@ export default { category: "Content", }, control: "boolean", + if: { arg: "isCollapsed", eq: false }, }, helpTextLabel: { name: "Help text (description)", @@ -50,6 +51,16 @@ export default { type: { summary: "string" }, category: "Content", }, + if: { arg: "isCollapsed", eq: false }, + }, + isCollapsed: { + name: "Collapsed", + type: { name: "boolean" }, + table: { + type: { summary: "boolean" }, + category: "Component", + }, + control: "boolean", }, }, args: { @@ -62,6 +73,7 @@ export default { showHelpText: false, helpTextLabel: "Help text with a suggestion of what to search for", inputValue: "", + isCollapsed: false, }, parameters: { actions: { @@ -69,6 +81,7 @@ export default { "change .spectrum-Search-input", "click .spectrum-Search-clearButton", "click .spectrum-Search-icon", + "click .spectrum-Search-actionButton", ], }, design: { @@ -141,6 +154,18 @@ WithValue.parameters = { }; WithValue.storyName = "With value and clear button"; +/** + * A search field can be collapsed to show only the search button. This is useful when there is limited space available. It is most commonly used next a filter button to allow users to quickly search and filter content. + */ +export const Collapsed = Template.bind({}); +Collapsed.args = { + isCollapsed: true, +}; +Collapsed.tags = ["!dev"]; +Collapsed.parameters = { + chromatic: { disableSnapshot: true }, +}; + /** * The medium size is the default and most frequently used option. Use the other sizes sparingly; they should be used to create a hierarchy of importance within the page. */ diff --git a/components/search/stories/search.test.js b/components/search/stories/search.test.js index 889dc7a6ee1..44b19ef2e6f 100644 --- a/components/search/stories/search.test.js +++ b/components/search/stories/search.test.js @@ -17,6 +17,10 @@ export const SearchGroup = Variants({ inputValue: "What should we search for?", withStates: false, }, + { + testHeading: "Collapsed", + isCollapsed: true, + } ], stateData: [ { @@ -30,11 +34,13 @@ export const SearchGroup = Variants({ { testHeading: "Focused", isFocused: true, + ignore: ["Collapsed"], }, { testHeading: "Focused + hovered", isFocused: true, isHovered: true, + ignore: ["Collapsed"], }, { testHeading: "Keyboard focused", diff --git a/components/search/stories/template.js b/components/search/stories/template.js index 9703bce7322..49f1c398b19 100644 --- a/components/search/stories/template.js +++ b/components/search/stories/template.js @@ -1,3 +1,4 @@ +import { Template as ActionButton } from "@spectrum-css/actionbutton/stories/template.js"; import { Template as ClearButton } from "@spectrum-css/clearbutton/stories/template.js"; import { Template as HelpText } from "@spectrum-css/helptext/stories/template.js"; import { Container } from "@spectrum-css/preview/decorators"; @@ -19,7 +20,9 @@ export const Template = ({ size = "m", showHelpText = false, helpTextLabel = "", + isCollapsed = false, } = {}, context = {}) => { + const { updateArgs } = context; return html`
({ ...a, [c]: true }), {}), })} aria-label="Search" > - ${TextField({ - isDisabled, - size, - customClasses: [ - `${rootClass}-textfield`, - isFocused && "is-focused", - isKeyboardFocused && "is-keyboardFocused", - isHovered && "is-hover" - ], - iconName: "Search", - setName: "workflow", - type: "search", - placeholder: "Search", - name: "search", - customInputClasses: [`${rootClass}-input`], - customIconClasses: [`${rootClass}-icon`], - autocomplete: false, - value: inputValue, - }, context)} - ${when(inputValue, () => + ${when(isCollapsed, () => + ActionButton({ + iconName: "Search", + isDisabled, + size, + isFocused: !isDisabled && (isFocused || isKeyboardFocused), + isQuiet: true, + customClasses: [ + `${rootClass}-actionButton`, + isHovered && "is-hover", + isDisabled && "is-disabled", + ], + onclick: () => { + updateArgs({ isCollapsed: !isCollapsed }); + }, + }, context) + )} + ${when(!isCollapsed, () => + TextField({ + isDisabled, + size, + customClasses: [ + `${rootClass}-textfield`, + isFocused && "is-focused", + isKeyboardFocused && "is-keyboardFocused", + isHovered && "is-hover" + ], + iconName: "Search", + setName: "workflow", + type: "search", + placeholder: "Search", + name: "search", + customInputClasses: [`${rootClass}-input`], + customIconClasses: [`${rootClass}-icon`], + autocomplete: false, + value: inputValue, + }, context) + )} + ${when(inputValue && !isCollapsed, () => ClearButton({ isDisabled, size, @@ -59,7 +83,7 @@ export const Template = ({ isFocusable: false, }, context) )} - ${when(showHelpText, () => + ${when(showHelpText && !isCollapsed, () => HelpText({ text: helpTextLabel, size,