Skip to content

feat(search): collapsed search #4115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: spectrum-two
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/long-carrots-sleep.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions components/search/dist/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
14 changes: 14 additions & 0 deletions components/search/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,20 @@
.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 0.3s cubic-bezier(0.4, 0, 0.2, 1);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how would you feel about maybe using of any of the --spectrum-animation-duration-* tokens? Maybe we can use that here? Or was the 0.3 design directed? And just because I'm nosy, where did you find these timing curves?!

Should the cubic-beziers and even the transition-duration be set as custom props? Maybe it's not necessary, but we do use the same curve & durations twice 🤷‍♀️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just experimenting with animation curves here. I don't know what the intended animation should be

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh nice! I've been learning about each of the values of the cubic-bezier function, so I was just curious!

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 0.3s cubic-bezier(0.4, 0, 0.2, 1);
inline-size: var(--mod-search-inline-size, var(--spectrum-search-inline-size));
transform-origin: left center;
}
}

.spectrum-Search-clearButton {
Expand Down
25 changes: 25 additions & 0 deletions components/search/stories/search.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default {
category: "Content",
},
control: "boolean",
if: { arg: "isCollapsed", eq: false },
},
helpTextLabel: {
name: "Help text (description)",
Expand All @@ -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: {
Expand All @@ -62,13 +73,15 @@ export default {
showHelpText: false,
helpTextLabel: "Help text with a suggestion of what to search for",
inputValue: "",
isCollapsed: false,
},
parameters: {
actions: {
handles: [
"change .spectrum-Search-input",
"click .spectrum-Search-clearButton",
"click .spectrum-Search-icon",
"click .spectrum-Search-actionButton",
],
},
design: {
Expand Down Expand Up @@ -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.
*/
Expand Down
6 changes: 6 additions & 0 deletions components/search/stories/search.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const SearchGroup = Variants({
inputValue: "What should we search for?",
withStates: false,
},
{
testHeading: "Collapsed",
isCollapsed: true,
}
],
stateData: [
{
Expand All @@ -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",
Expand Down
64 changes: 43 additions & 21 deletions components/search/stories/template.js
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -19,7 +20,9 @@ export const Template = ({
size = "m",
showHelpText = false,
helpTextLabel = "",
isCollapsed = false,
} = {}, context = {}) => {
const { updateArgs } = context;
return html`
<form
class=${classMap({
Expand All @@ -28,38 +31,57 @@ export const Template = ({
typeof size !== "undefined" && size !== "m",
"is-disabled": isDisabled,
"is-keyboardFocused": isKeyboardFocused,
"is-collapsed": isCollapsed,
"is-expanded": !isCollapsed,
...customClasses.reduce((a, c) => ({ ...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",
size,
isFocused: isFocused || isKeyboardFocused,
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,
customClasses: [`${rootClass}-clearButton`],
isFocusable: false,
}, context)
)}
${when(showHelpText, () =>
${when(showHelpText && !isCollapsed, () =>
HelpText({
text: helpTextLabel,
size,
Expand Down
Loading