diff --git a/web-components/packages/carbon-web-components/src/components/data-table/table-toolbar-search.ts b/web-components/packages/carbon-web-components/src/components/data-table/table-toolbar-search.ts
index 7b0090c8bd02..02a9b6dc5b67 100644
--- a/web-components/packages/carbon-web-components/src/components/data-table/table-toolbar-search.ts
+++ b/web-components/packages/carbon-web-components/src/components/data-table/table-toolbar-search.ts
@@ -14,7 +14,7 @@ import { prefix } from '../../globals/settings';
import HostListenerMixin from '../../globals/mixins/host-listener';
import HostListener from '../../globals/decorators/host-listener';
import { INPUT_SIZE } from '../text-input/text-input';
-import BXSearch from '../search/search';
+import CDSSearch from '../search/search';
import styles from './data-table.scss';
/**
@@ -24,7 +24,7 @@ import styles from './data-table.scss';
* @fires cds-search-input - The custom event fired after the search content is changed upon a user gesture.
*/
@customElement(`${prefix}-table-toolbar-search`)
-class BXTableToolbarSearch extends HostListenerMixin(BXSearch) {
+class BXTableToolbarSearch extends HostListenerMixin(CDSSearch) {
@query('input')
private _inputNode!: HTMLInputElement;
diff --git a/web-components/packages/carbon-web-components/src/components/search/defs.ts b/web-components/packages/carbon-web-components/src/components/search/defs.ts
deleted file mode 100644
index e154c620dc68..000000000000
--- a/web-components/packages/carbon-web-components/src/components/search/defs.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * @license
- *
- * Copyright IBM Corp. 2020
- *
- * This source code is licensed under the Apache-2.0 license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-export { FORM_ELEMENT_COLOR_SCHEME as SEARCH_COLOR_SCHEME } from '../../globals/shared-enums';
diff --git a/web-components/packages/carbon-web-components/src/components/search/search-skeleton.ts b/web-components/packages/carbon-web-components/src/components/search/search-skeleton.ts
index 1367317f994c..206195bbb2c9 100644
--- a/web-components/packages/carbon-web-components/src/components/search/search-skeleton.ts
+++ b/web-components/packages/carbon-web-components/src/components/search/search-skeleton.ts
@@ -17,7 +17,7 @@ import styles from './search.scss';
* Skeleton of search.
*/
@customElement(`${prefix}-search-skeleton`)
-class BXSearchSkeleton extends LitElement {
+class CDSSearchSkeleton extends LitElement {
/**
* The search box size. Corresponds to the attribute with the same name.
*/
@@ -34,4 +34,4 @@ class BXSearchSkeleton extends LitElement {
static styles = styles;
}
-export default BXSearchSkeleton;
+export default CDSSearchSkeleton;
diff --git a/web-components/packages/carbon-web-components/src/components/search/search-story.ts b/web-components/packages/carbon-web-components/src/components/search/search-story.ts
index cc1a548b340d..b8c82f351fcf 100644
--- a/web-components/packages/carbon-web-components/src/components/search/search-story.ts
+++ b/web-components/packages/carbon-web-components/src/components/search/search-story.ts
@@ -9,85 +9,154 @@
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
-import { action } from '@storybook/addon-actions';
-import { boolean, select } from '@storybook/addon-knobs';
+import { boolean, number, select } from '@storybook/addon-knobs';
import textNullable from '../../../.storybook/knob-text-nullable';
import { INPUT_SIZE } from '../text-input/text-input';
-import { SEARCH_COLOR_SCHEME } from './search';
import './search-skeleton';
import storyDocs from './search-story.mdx';
import { prefix } from '../../globals/settings';
-const colorSchemes = {
- [`Regular`]: null,
- [`Light (${SEARCH_COLOR_SCHEME.LIGHT})`]: SEARCH_COLOR_SCHEME.LIGHT,
-};
-
const sizes = {
- 'Regular size': null,
[`Small size (${INPUT_SIZE.SMALL})`]: INPUT_SIZE.SMALL,
+ [`Medium size (${INPUT_SIZE.MEDIUM})`]: INPUT_SIZE.MEDIUM,
[`Large size (${INPUT_SIZE.LARGE})`]: INPUT_SIZE.LARGE,
};
-export const Default = (args) => {
+const widthSliderOptions = {
+ range: true,
+ min: 300,
+ max: 800,
+ step: 50,
+};
+
+export const Default = () => {
+ return html`
+
+ `;
+};
+
+export const Disabled = () => {
+ return html`
+
+ `;
+};
+
+export const Expandable = () => {
+ return html`
+
+ `;
+};
+
+export const ExpandableWithLayer = () => {
+ return html`
+
+
+
+
+
+
+
+
+
+ `;
+};
+
+export const WithLayer = () => {
+ return html`
+
+
+
+
+
+
+
+
+
+ `;
+};
+
+export const Playground = (args) => {
const {
- closeButtonAssistiveText,
+ autoComplete,
+ closeButtonLabelText,
colorScheme,
disabled,
labelText,
- name,
placeholder,
+ playgroundWidth,
size,
+ role,
type,
value,
onInput,
} = args?.[`${prefix}-search`] ?? {};
+
+ const mainDiv = document.querySelector('#main-content');
+
+ if (mainDiv) {
+ (mainDiv as HTMLElement).style.width = `${playgroundWidth}px`;
+ }
+
return html`
+ @cds-search-input="${onInput}">
+
`;
};
-Default.storyName = 'Default';
-
-Default.parameters = {
+Playground.parameters = {
knobs: {
[`${prefix}-search`]: () => ({
- closeButtonAssistiveText: textNullable(
- 'The label text for the close button (close-button-assistive-text)',
+ autoComplete: textNullable('Autocomplete (autocomplete)', 'off'),
+ closeButtonLabelText: textNullable(
+ 'The label text for the close button (close-button-label-text)',
'Clear search input'
),
- colorScheme: select('Color scheme (color-scheme)', colorSchemes, null),
disabled: boolean('Disabled (disabled)', false),
labelText: textNullable('Label text (label-text)', 'Search'),
- name: textNullable('Name (name)', ''),
- placeholder: textNullable('Placeholder text (placeholder)', ''),
+ placeholder: textNullable(
+ 'Placeholder text (placeholder)',
+ 'Placeholder text'
+ ),
+ playgroundWidth: number('Playground width', 300, widthSliderOptions),
+ role: textNullable('The role of the (role)', 'searchbox'),
size: select('Search size (size)', sizes, null),
- type: textNullable('The type of the (type)', ''),
+ type: textNullable('The type of the (type)', 'text'),
value: textNullable('Value (value)', ''),
- onInput: action(`${prefix}-search-input`),
}),
},
};
-export const skeleton = () =>
- html` `;
-
-skeleton.parameters = {
- percy: {
- skip: true,
- },
-};
-
export default {
title: 'Components/Search',
parameters: {
diff --git a/web-components/packages/carbon-web-components/src/components/search/search.scss b/web-components/packages/carbon-web-components/src/components/search/search.scss
index 54327cb9a8e1..cba8494273df 100644
--- a/web-components/packages/carbon-web-components/src/components/search/search.scss
+++ b/web-components/packages/carbon-web-components/src/components/search/search.scss
@@ -8,6 +8,8 @@
$css--plex: true !default;
@use '@carbon/styles/scss/config' as *;
+@use '@carbon/styles/scss/spacing' as *;
+@use '@carbon/styles/scss/theme' as *;
@use '@carbon/styles/scss/utilities' as *;
@use '@carbon/styles/scss/components/form';
@use '@carbon/styles/scss/components/search' as *;
@@ -15,35 +17,51 @@ $css--plex: true !default;
// https://github.com/carbon-design-system/carbon/issues/11408
@include search;
-:host(#{$prefix}-search),
-:host(#{$prefix}-search-skeleton) {
- @extend .#{$prefix}--search--lg;
-}
-
:host(#{$prefix}-search) {
@extend .#{$prefix}--search;
outline: none;
-}
-:host(#{$prefix}-search[size='sm']),
-:host(#{$prefix}-search-skeleton[size='sm']) {
- @extend .#{$prefix}--search--sm;
-}
+ &[expandable] {
+ @extend .#{$prefix}--search--expandable;
-:host(#{$prefix}-search[size='lg']),
-:host(#{$prefix}-search-skeleton[size='lg']) {
- @extend .#{$prefix}--search--lg;
-}
+ &[expanded] {
+ @extend .#{$prefix}--search--expanded;
-// TODO: deprecate
-// :host(#{$prefix}-search[size='xl']),
-// :host(#{$prefix}-search-skeleton[size='xl']) {
-// @extend .#{$prefix}--search--xl;
-// }
+ &[size='sm'] {
+ .#{$prefix}--search-input {
+ padding: 0 $spacing-07;
+ }
+ }
+
+ &[size='md'] {
+ .#{$prefix}--search-input {
+ padding: 0 $spacing-08;
+ }
+ }
+
+ &[size='lg'] {
+ .#{$prefix}--search-input {
+ padding: 0 $spacing-09;
+ }
+ }
+ }
+ }
+
+ &[disabled] {
+ svg {
+ fill: $icon-on-color-disabled;
+ }
-:host(#{$prefix}-search[color-scheme='light']) {
- @extend .#{$prefix}--search--light;
+ .#{$prefix}--search-close {
+ pointer-events: none;
+ outline: none;
+
+ &::before {
+ background: none;
+ }
+ }
+ }
}
:host(#{$prefix}-search-skeleton) {
@@ -59,3 +77,18 @@ $css--plex: true !default;
}
}
}
+
+:host(#{$prefix}-search),
+:host(#{$prefix}-search-skeleton) {
+ &[size='sm'] {
+ @extend .#{$prefix}--search--sm;
+ }
+
+ &[size='md'] {
+ @extend .#{$prefix}--search--md;
+ }
+
+ &[size='lg'] {
+ @extend .#{$prefix}--search--lg;
+ }
+}
diff --git a/web-components/packages/carbon-web-components/src/components/search/search.ts b/web-components/packages/carbon-web-components/src/components/search/search.ts
index 226d2422d78c..7415cedd25f0 100644
--- a/web-components/packages/carbon-web-components/src/components/search/search.ts
+++ b/web-components/packages/carbon-web-components/src/components/search/search.ts
@@ -11,18 +11,16 @@ import { classMap } from 'lit/directives/class-map.js';
import { LitElement, html } from 'lit';
import { property, customElement } from 'lit/decorators.js';
import Close16 from '@carbon/icons/lib/close/16';
-import Close20 from '@carbon/icons/lib/close/20';
import Search16 from '@carbon/icons/lib/search/16';
import { prefix } from '../../globals/settings';
import ifNonEmpty from '../../globals/directives/if-non-empty';
import FocusMixin from '../../globals/mixins/focus';
import FormMixin from '../../globals/mixins/form';
import { INPUT_SIZE } from '../text-input/text-input';
-import { SEARCH_COLOR_SCHEME } from './defs';
+import HostListener from '../../globals/decorators/host-listener';
+import HostListenerMixin from '../../globals/mixins/host-listener';
import styles from './search.scss';
-export { SEARCH_COLOR_SCHEME };
-
/**
* Search box.
*
@@ -35,7 +33,7 @@ export { SEARCH_COLOR_SCHEME };
* @fires cds-search-input - The custom event fired after the search content is changed upon a user gesture.
*/
@customElement(`${prefix}-search`)
-class BXSearch extends FocusMixin(FormMixin(LitElement)) {
+class CDSSearch extends HostListenerMixin(FocusMixin(FormMixin(LitElement))) {
/**
* Handles `input` event on the `` in the shadow DOM.
*/
@@ -43,7 +41,7 @@ class BXSearch extends FocusMixin(FormMixin(LitElement)) {
const { target } = event;
const { value } = target as HTMLInputElement;
this.dispatchEvent(
- new CustomEvent((this.constructor as typeof BXSearch).eventInput, {
+ new CustomEvent((this.constructor as typeof CDSSearch).eventInput, {
bubbles: true,
composed: true,
cancelable: false,
@@ -61,7 +59,7 @@ class BXSearch extends FocusMixin(FormMixin(LitElement)) {
private _handleClearInputButtonClick() {
if (this.value) {
this.dispatchEvent(
- new CustomEvent((this.constructor as typeof BXSearch).eventInput, {
+ new CustomEvent((this.constructor as typeof CDSSearch).eventInput, {
bubbles: true,
composed: true,
cancelable: false,
@@ -78,6 +76,42 @@ class BXSearch extends FocusMixin(FormMixin(LitElement)) {
}
}
+ /**
+ * Handles `focus` event on the button when the button can be expanded
+ */
+ @HostListener('focus')
+ // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to
+ private _handleExpand() {
+ if (this.expandable && !this.expanded) {
+ this.setAttribute('expanded', '');
+ }
+ }
+
+ /**
+ * Handles `focusout` event on the component to be closed after being expanded
+ * Will not close if there is a value typed within.
+ */
+ @HostListener('focusout')
+ // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to
+ private _handleClose() {
+ if (this.expandable && this.expanded && !this.value) {
+ this.removeAttribute('expanded');
+ }
+ }
+
+ /**
+ * Handler for @slotchange, will only be ran if user sets an element under the "icon" slot.
+ *
+ * @private
+ */
+ private _handleSlotChange() {
+ const icon = this.querySelector('svg');
+ icon?.setAttribute('part', 'search-icon');
+ icon?.setAttribute('class', `${prefix}--search-magnifier-icon`);
+ icon?.setAttribute('role', `img`);
+ this.hasCustomIcon = true;
+ }
+
_handleFormdata(event: Event) {
const { formData } = event as any; // TODO: Wait for `FormDataEvent` being available in `lib.dom.d.ts`
const { disabled, name, value } = this;
@@ -87,16 +121,17 @@ class BXSearch extends FocusMixin(FormMixin(LitElement)) {
}
/**
- * The assistive text for the close button.
+ * Specify an optional value for the autocomplete property on the underlying ,
+ * defaults to "off"
*/
- @property({ attribute: 'close-button-assistive-text' })
- closeButtonAssistiveText = '';
+ @property({ attribute: 'autocomplete' })
+ autoComplete = 'off';
/**
- * The color scheme.
+ * Specify a label to be read by screen readers on the "close" button
*/
- @property({ attribute: 'color-scheme', reflect: true })
- colorScheme = SEARCH_COLOR_SCHEME.REGULAR;
+ @property({ attribute: 'close-button-label-text' })
+ closeButtonLabelText = '';
/**
* `true` if the search box should be disabled.
@@ -104,6 +139,21 @@ class BXSearch extends FocusMixin(FormMixin(LitElement)) {
@property({ type: Boolean, reflect: true })
disabled = false;
+ /**
+ * `true` if the search bar can be expandable
+ */
+ @property({ type: Boolean, reflect: true })
+ expandable = false;
+
+ /**
+ * `true` if the expandable search has been expanded
+ */
+ @property({ type: Boolean, reflect: true })
+ expanded = false;
+
+ @property({ type: Boolean })
+ hasCustomIcon = false;
+
/**
* The label text.
*/
@@ -116,11 +166,17 @@ class BXSearch extends FocusMixin(FormMixin(LitElement)) {
@property()
name = '';
+ /**
+ * Specify the role for the underlying , defaults to searchbox
+ */
+ @property()
+ role = '';
+
/**
* The placeholder text.
*/
@property()
- placeholder = '';
+ placeholder = 'Search';
/**
* The search box size.
@@ -142,31 +198,41 @@ class BXSearch extends FocusMixin(FormMixin(LitElement)) {
render() {
const {
- closeButtonAssistiveText,
+ autoComplete,
+ closeButtonLabelText,
disabled,
+ hasCustomIcon,
labelText,
name,
placeholder,
- size,
+ role,
type,
value = '',
_handleInput: handleInput,
_handleClearInputButtonClick: handleClearInputButtonClick,
+ _handleSlotChange: handleSlotChange,
} = this;
const clearClasses = classMap({
[`${prefix}--search-close`]: true,
[`${prefix}--search-close--hidden`]: !this.value,
});
return html`
- ${Search16({
- part: 'search-icon',
- class: `${prefix}--search-magnifier-icon`,
- role: 'img',
- })}
+
+
+ ${hasCustomIcon
+ ? html``
+ : html`${Search16({
+ part: 'search-icon',
+ class: `${prefix}--search-magnifier-icon`,
+ role: 'img',
+ })}`}
+
+
@@ -206,4 +272,4 @@ class BXSearch extends FocusMixin(FormMixin(LitElement)) {
static styles = styles; // `styles` here is a `CSSResult` generated by custom WebPack loader
}
-export default BXSearch;
+export default CDSSearch;
diff --git a/web-components/packages/carbon-web-components/tests/spec/search_spec.ts b/web-components/packages/carbon-web-components/tests/spec/search_spec.ts
index 987faeec22c8..5686b44c7a51 100644
--- a/web-components/packages/carbon-web-components/tests/spec/search_spec.ts
+++ b/web-components/packages/carbon-web-components/tests/spec/search_spec.ts
@@ -10,13 +10,11 @@
import { render } from 'lit';
import EventManager from '../utils/event-manager';
import { INPUT_SIZE } from '../../src/components/text-input/text-input';
-import BXSearch, {
- SEARCH_COLOR_SCHEME,
-} from '../../src/components/search/search';
-import { Default } from '../../src/components/search/search-story';
+import CDSSearch from '../../src/components/search/search';
+import { Playground } from '../../src/components/search/search-story';
const template = (props?) =>
- Default({
+ Playground({
'cds-search': props,
});
@@ -36,7 +34,6 @@ describe('cds-search', function () {
render(
template({
closeButtonAssistiveText: 'close-button-assistive-text-foo',
- colorScheme: SEARCH_COLOR_SCHEME.LIGHT,
disabled: true,
labelText: 'label-text-foo',
name: 'name-foo',
@@ -62,7 +59,7 @@ describe('cds-search', function () {
const inputNode = search!.shadowRoot!.querySelector('input');
inputNode!.value = 'value-bar';
inputNode!.dispatchEvent(new CustomEvent('input', { bubbles: true }));
- expect((search as BXSearch).value).toBe('value-bar');
+ expect((search as CDSSearch).value).toBe('value-bar');
});
it('Should fire cds-search-input event upon typing', async function () {
@@ -86,7 +83,7 @@ describe('cds-search', function () {
await Promise.resolve();
const search = document.body.querySelector('cds-search');
search!.shadowRoot!.querySelector('button')!.click();
- expect((search as BXSearch).value).toBe('');
+ expect((search as CDSSearch).value).toBe('');
});
it('Should fire cds-search-input event upon clearing', async function () {