Skip to content

Commit 06f8c47

Browse files
authored
feat: fluid search web component (#20799)
* feat: fluid search web component * feat: added doc * fix: disabled state * fix: disabled * fix: width
1 parent ffdeb72 commit 06f8c47

File tree

9 files changed

+451
-2
lines changed

9 files changed

+451
-2
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/**
2+
* Copyright IBM Corp. 2025
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import '@carbon/web-components/es/components/fluid-search/index.js';
9+
import { expect, fixture, html } from '@open-wc/testing';
10+
11+
describe('cds-fluid-search', () => {
12+
describe('renders as expected - Component API', () => {
13+
it('should respect the autocomplete attribute', async () => {
14+
const el = await fixture(html`
15+
<cds-fluid-search
16+
label-text="test-fluid-search"
17+
autocomplete="test"></cds-fluid-search>
18+
`);
19+
20+
const input = el.shadowRoot?.querySelector('input');
21+
expect(input).to.have.attribute('autocomplete', 'test');
22+
});
23+
24+
it('should support a custom class on the outermost element', async () => {
25+
const el = await fixture(html`
26+
<cds-fluid-search
27+
label-text="test-fluid-search"
28+
class="custom-class"></cds-fluid-search>
29+
`);
30+
31+
expect(el).to.have.class('custom-class');
32+
});
33+
34+
it('should respect the close-button-label-text attribute', async () => {
35+
const el = await fixture(html`
36+
<cds-fluid-search
37+
label-text="test-fluid-search"
38+
close-button-label-text="clear"></cds-fluid-search>
39+
`);
40+
41+
const closeButton =
42+
el.shadowRoot?.querySelector('button[data-action="clear"]') ||
43+
el.shadowRoot?.querySelector('button[aria-label="clear"]') ||
44+
el.shadowRoot?.querySelector('button[title="clear"]');
45+
expect(closeButton).to.exist;
46+
});
47+
48+
it('should respect the disabled attribute', async () => {
49+
const el = await fixture(html`
50+
<cds-fluid-search
51+
label-text="test-fluid-search"
52+
disabled></cds-fluid-search>
53+
`);
54+
55+
const input = el.shadowRoot?.querySelector('input');
56+
expect(input).to.have.attribute('disabled');
57+
});
58+
59+
it('should respect the label-text attribute', async () => {
60+
const el = await fixture(html`
61+
<cds-fluid-search label-text="test-fluid-search"></cds-fluid-search>
62+
`);
63+
64+
const label = el.shadowRoot?.querySelector('label');
65+
expect(label?.textContent?.trim()).to.equal('test-fluid-search');
66+
});
67+
68+
it('should call focus expand button on Escape when expanded', async () => {
69+
const el = await fixture(html`
70+
<cds-fluid-search label-text="test-fluid-search" is-expanded>
71+
</cds-fluid-search>
72+
`);
73+
74+
const input = el.shadowRoot?.querySelector('input');
75+
const expandButton =
76+
el.shadowRoot?.querySelector('button[data-action="expand"]') ||
77+
el.shadowRoot?.querySelector('button.cds--search-magnifier');
78+
79+
if (input && expandButton) {
80+
input.focus();
81+
const escapeEvent = new KeyboardEvent('keydown', {
82+
key: 'Escape',
83+
bubbles: true,
84+
});
85+
input.dispatchEvent(escapeEvent);
86+
await el.updateComplete;
87+
88+
expect(document.activeElement).to.equal(expandButton);
89+
}
90+
});
91+
92+
it('should have tabbable button and untabbable input if expandable and not expanded', async () => {
93+
const el = await fixture(html`
94+
<cds-fluid-search label-text="test-fluid-search" expandable>
95+
</cds-fluid-search>
96+
`);
97+
98+
expect(el).to.have.attribute('expandable');
99+
expect(el).to.not.have.attribute('expanded');
100+
});
101+
102+
it('should have tabbable input and untabbable button if not expandable', async () => {
103+
const el = await fixture(html`
104+
<cds-fluid-search label-text="test-fluid-search"></cds-fluid-search>
105+
`);
106+
107+
expect(el).to.not.have.attribute('expandable');
108+
});
109+
110+
it('should respect the placeholder attribute', async () => {
111+
const el = await fixture(html`
112+
<cds-fluid-search
113+
label-text="test-fluid-search"
114+
placeholder="test-placeholder"></cds-fluid-search>
115+
`);
116+
117+
const input = el.shadowRoot?.querySelector('input');
118+
expect(input).to.have.attribute('placeholder', 'test-placeholder');
119+
});
120+
121+
it('should set hasCustomIcon when a custom icon is provided', async () => {
122+
const el = await fixture(html`
123+
<cds-fluid-search label-text="test-fluid-search">
124+
<svg slot="icon" data-testid="test-icon"></svg>
125+
</cds-fluid-search>
126+
`);
127+
128+
const customIcon = el.querySelector('svg[data-testid="test-icon"]');
129+
expect(customIcon).to.exist;
130+
});
131+
132+
it('should respect the role attribute', async () => {
133+
const el = await fixture(html`
134+
<cds-fluid-search
135+
label-text="test-fluid-search"
136+
role="combobox"></cds-fluid-search>
137+
`);
138+
139+
const input = el.shadowRoot?.querySelector('input');
140+
expect(input).to.have.attribute('role', 'combobox');
141+
});
142+
143+
it('should respect the type attribute', async () => {
144+
const el = await fixture(html`
145+
<cds-fluid-search
146+
label-text="test-fluid-search"
147+
type="search"></cds-fluid-search>
148+
`);
149+
150+
const input = el.shadowRoot?.querySelector('input');
151+
expect(input).to.have.attribute('type', 'search');
152+
});
153+
154+
it('should respect the value attribute', async () => {
155+
const el = await fixture(html`
156+
<cds-fluid-search
157+
label-text="test-fluid-search"
158+
value="test-value"></cds-fluid-search>
159+
`);
160+
const input = el.shadowRoot?.querySelector('input');
161+
expect(input).to.have.property('value', 'test-value');
162+
});
163+
});
164+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright IBM Corp.2025
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import { prefix } from '../../globals/settings';
9+
import { html } from 'lit';
10+
import { carbonElement as customElement } from '../../globals/decorators/carbon-element';
11+
import styles from './fluid-search.scss?lit';
12+
import CDSSearchSkeleton from '../search/search-skeleton';
13+
14+
/**
15+
* Fluid Search.
16+
*
17+
* @element cds-fluid-search-skeleton
18+
*/
19+
@customElement(`${prefix}-fluid-search-skeleton`)
20+
class CDSFluidSearchSkeleton extends CDSSearchSkeleton {
21+
render() {
22+
return html`${super.render()}`;
23+
}
24+
25+
static styles = [CDSSearchSkeleton.styles, styles];
26+
}
27+
28+
export default CDSFluidSearchSkeleton;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { ArgTypes, Canvas, Markdown, Meta } from '@storybook/addon-docs/blocks';
2+
import { cdnJs } from '../../globals/internal/storybook-cdn';
3+
import * as FluidSearchStories from './fluid-search.stories';
4+
5+
<Meta of={FluidSearchStories} />
6+
7+
# Search
8+
9+
[Source code](https://github.com/carbon-design-system/carbon/tree/main/packages/web-components/src/components/fluid-search)
10+
11+
## Table of Contents
12+
13+
- [Overview](#overview)
14+
- [Skeleton](#skeleton)
15+
- [Component API](#component-api)
16+
- [CDN](#cdn)
17+
- [Feedback](#feedback)
18+
19+
## Overview
20+
21+
<Canvas of={FluidSearchStories.Default} />
22+
23+
## Skeleton
24+
25+
<Canvas of={FluidSearchStories.Skeleton} />
26+
27+
## Component API
28+
29+
## `cds-fluid-search`
30+
31+
<ArgTypes of="cds-search" />
32+
33+
<Markdown>{`${cdnJs({ components: ['fluid-search'] })}`}</Markdown>
34+
35+
## Feedback
36+
37+
Help us improve this component by providing feedback, asking questions on Slack,
38+
or updating this file on
39+
[GitHub](https://github.com/carbon-design-system/carbon/edit/main/packages/web-components/src/components/fluid-search/fluid-search.mdx).
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Copyright IBM Corp.2025
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
$css--plex: true !default;
9+
@use '@carbon/styles/scss/config' as *;
10+
@use '@carbon/styles/scss/components/fluid-search/index';
11+
@use '@carbon/styles/scss/layout' as *;
12+
@use '@carbon/styles/scss/spacing' as *;
13+
@use '@carbon/styles/scss/theme' as *;
14+
@use '@carbon/styles/scss/type' as *;
15+
@use '@carbon/styles/scss/utilities' as *;
16+
@use '@carbon/styles/scss/components/fluid-text-input' as *;
17+
@use '@carbon/styles/scss/components/search' as *;
18+
19+
:host(#{$prefix}-fluid-search) {
20+
@extend .#{$prefix}--search;
21+
@extend .#{$prefix}--search--fluid;
22+
@include emit-layout-tokens();
23+
24+
outline: none;
25+
}
26+
27+
:host(#{$prefix}-fluid-search-skeleton) {
28+
@extend .#{$prefix}--text-input--fluid__skeleton;
29+
30+
display: block;
31+
}
32+
33+
:host(#{$prefix}-fluid-search[disabled]) {
34+
svg {
35+
fill: $icon-on-color-disabled;
36+
}
37+
38+
.#{$prefix}--search-close {
39+
outline: none;
40+
pointer-events: none;
41+
42+
&::before {
43+
background: none;
44+
}
45+
}
46+
47+
.#{$prefix}--label {
48+
color: $text-disabled;
49+
cursor: not-allowed;
50+
}
51+
52+
.#{$prefix}--search-input[disabled] {
53+
border-block-end: 1px solid $border-subtle;
54+
}
55+
}

0 commit comments

Comments
 (0)