Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 Minimized 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 only has a hover 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
13 changes: 13 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
7 changes: 7 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,15 +34,18 @@ export const SearchGroup = Variants({
{
testHeading: "Focused",
isFocused: true,
ignore: ["Collapsed"],
},
{
testHeading: "Focused + hovered",
isFocused: true,
isHovered: true,
ignore: ["Collapsed"],
},
{
testHeading: "Keyboard focused",
isKeyboardFocused: true,
ignore: ["Collapsed"],
},
]
});
63 changes: 42 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,56 @@ 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,
customClasses: [
`${rootClass}-actionButton`,
isHovered && "is-hover",
isDisabled && "is-disabled",
Copy link
Collaborator

Choose a reason for hiding this comment

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

My only suggestion is to make sure we can't keyboard focus to this button when it's disabled!

Screenshot 2025-08-14 at 6 55 16 PM

I'm not sure if this is a property thing? It certainly looks like we're passing the disabled arg through correctly- I see it on the search AND on the action button, but maybe we need to revisit the disabled+keyboard focus styles.

],
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