feat(searchbar): add sizes for ionic theme (#30211)

This commit is contained in:
Maria Hutt
2025-03-04 11:23:38 -08:00
committed by GitHub
parent 715cc06c5f
commit ab81f3de7e
45 changed files with 254 additions and 38 deletions

View File

@ -1890,6 +1890,7 @@ ion-searchbar,prop,searchIcon,string | undefined,undefined,false,false
ion-searchbar,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false
ion-searchbar,prop,showCancelButton,"always" | "focus" | "never",'never',false,false
ion-searchbar,prop,showClearButton,"always" | "focus" | "never",'always',false,false
ion-searchbar,prop,size,"large" | "medium" | "small" | undefined,undefined,false,false
ion-searchbar,prop,spellcheck,boolean,false,false,false
ion-searchbar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-searchbar,prop,type,"email" | "number" | "password" | "search" | "tel" | "text" | "url",'search',false,false

View File

@ -3124,6 +3124,10 @@ export namespace Components {
* Sets the behavior for the clear button. Defaults to `"focus"`. Setting to `"focus"` shows the clear button on focus if the input is not empty. Setting to `"never"` hides the clear button. Setting to `"always"` shows the clear button regardless of focus state, but only if the input is not empty.
*/
"showClearButton": 'never' | 'focus' | 'always';
/**
* Set to `"large"` for a searchbar with an increase in height, while "small" and "medium" provide progressively smaller heights. Defaults to `"medium"` for the ionic theme, and `undefined` for all other themes.
*/
"size"?: 'small' | 'medium' | 'large';
/**
* If `true`, enable spellcheck on the input.
*/
@ -8539,6 +8543,10 @@ declare namespace LocalJSX {
* Sets the behavior for the clear button. Defaults to `"focus"`. Setting to `"focus"` shows the clear button on focus if the input is not empty. Setting to `"never"` hides the clear button. Setting to `"always"` shows the clear button regardless of focus state, but only if the input is not empty.
*/
"showClearButton"?: 'never' | 'focus' | 'always';
/**
* Set to `"large"` for a searchbar with an increase in height, while "small" and "medium" provide progressively smaller heights. Defaults to `"medium"` for the ionic theme, and `undefined` for all other themes.
*/
"size"?: 'small' | 'medium' | 'large';
/**
* If `true`, enable spellcheck on the input.
*/

View File

@ -28,9 +28,6 @@
// -----------------------------------------
.searchbar-search-icon {
// Position is based on the size of the search icon.
@include globals.position(globals.$ion-scale-400, null, null, globals.$ion-scale-400);
width: globals.$ion-scale-400;
height: globals.$ion-scale-400;
}
@ -39,25 +36,7 @@
// -----------------------------------------
.searchbar-input {
/**
* Padding start is based on
* desired padding from design,
* the size of the search icon,
* and the gap between the icon and the input.
*
* Padding end is based on
* desired padding from design,
* the size of the clear icon,
* and the gap between the icon and the input.
*/
@include globals.padding(
globals.$ion-space-300,
calc(globals.$ion-space-400 + globals.$ion-scale-400 + globals.$ion-space-200),
globals.$ion-space-300,
calc(globals.$ion-space-400 + globals.$ion-scale-400 + globals.$ion-space-200)
);
min-height: globals.$ion-scale-1200;
@include globals.padding(globals.$ion-space-300, null);
contain: strict;
}
@ -66,9 +45,6 @@
// -----------------------------------------
.searchbar-clear-button {
// Position is based on the size of the clear icon.
@include globals.position(globals.$ion-scale-400, globals.$ion-scale-400, null, null);
width: globals.$ion-scale-400;
height: globals.$ion-scale-400;
@ -83,13 +59,8 @@
// -----------------------------------------
.searchbar-cancel-button {
/**
* The left edge of the cancel button
* should align with the left edge
* of the back button if the searchbar
* is used in a toolbar.
*/
@include globals.position(0, null, null, 9px);
width: globals.$ion-scale-400;
height: globals.$ion-scale-400;
background-color: transparent;
@ -102,7 +73,11 @@
.searchbar-search-icon,
.searchbar-clear-button,
.searchbar-cancel-button {
@include globals.position(50%, null);
position: absolute;
transform: translateY(-50%);
}
// Clear Icon & Cancel Icon
@ -120,8 +95,25 @@
// Searchbar in Toolbar
// -----------------------------------------
:host-context(ion-toolbar) {
min-height: globals.$ion-scale-1200;
:host-context(ion-toolbar).searchbar-should-show-cancel .searchbar-cancel-button {
/**
* The left edge of the cancel button
* should align with the left edge
* of the back button if the searchbar
* is used in a toolbar regardless of
* the searchbar size.
*/
@include globals.position-horizontal(9px, null);
}
:host-context(ion-toolbar).searchbar-should-show-cancel .searchbar-input {
/**
* Padding start is based on
* the alignment of the back button,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*/
padding-inline-start: calc(9px + globals.$ion-scale-400 + globals.$ion-space-200);
}
// Searchbar States
@ -172,3 +164,118 @@
:host(.searchbar-shape-rectangular) {
--border-radius: #{globals.$ion-border-radius-0};
}
// Searchbar Sizes
// --------------------------------------------------
/* Small */
:host(.searchbar-size-small) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*
* Padding end is based on
* desired padding from design,
* the size of the trailing icon (clear),
* and the gap between the icon and the input.
*/
@include globals.padding-horizontal(calc(globals.$ion-space-300 + globals.$ion-scale-400 + globals.$ion-space-200));
height: globals.$ion-scale-1000;
}
:host(.searchbar-size-small) .searchbar-search-icon,
:host(.searchbar-size-small) .searchbar-cancel-button {
// Start is based on the desired padding start from design.
@include globals.position-horizontal(globals.$ion-space-300, null);
}
:host(.searchbar-size-small) .searchbar-clear-button {
// End is based on the desired padding end from design.
@include globals.position-horizontal(null, globals.$ion-space-300);
}
/* Medium */
:host(.searchbar-size-medium) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*
* Padding end is based on
* desired padding from design,
* the size of the trailing icon (clear),
* and the gap between the icon and the input.
*/
@include globals.padding-horizontal(calc(globals.$ion-space-400 + globals.$ion-scale-400 + globals.$ion-space-200));
height: globals.$ion-scale-1200;
}
:host(.searchbar-size-medium) .searchbar-search-icon,
:host(.searchbar-size-medium) .searchbar-cancel-button {
// Start is based on the desired padding start from design.
@include globals.position-horizontal(globals.$ion-space-400, null);
}
:host(.searchbar-size-medium) .searchbar-clear-button {
// End is based on the desired padding end from design.
@include globals.position-horizontal(null, globals.$ion-space-400);
}
/* Large */
:host(.searchbar-size-large) .searchbar-search-icon,
:host(.searchbar-size-large) .searchbar-cancel-button {
width: globals.$ion-scale-500;
height: globals.$ion-scale-500;
}
:host(.searchbar-size-large) .searchbar-cancel-button {
font-size: globals.$ion-font-size-500;
}
:host-context(ion-toolbar).searchbar-size-large.searchbar-should-show-cancel .searchbar-input {
/**
* Padding start is based on
* the alignment of the back button,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*/
padding-inline-start: calc(9px + globals.$ion-scale-500 + globals.$ion-space-200);
}
:host(.searchbar-size-large) .searchbar-input {
@include globals.padding(globals.$ion-space-400, null);
/**
* Padding start is based on
* desired padding from design,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*
* Padding end is based on
* desired padding from design,
* the size of the trailing icon (clear),
* and the gap between the icon and the input.
*/
@include globals.padding-horizontal(
calc(globals.$ion-space-500 + globals.$ion-scale-500 + globals.$ion-space-200),
calc(globals.$ion-space-500 + globals.$ion-scale-400 + globals.$ion-space-200)
);
height: globals.$ion-scale-1400;
}
:host(.searchbar-size-large) .searchbar-search-icon,
:host(.searchbar-size-large) .searchbar-cancel-button {
// Start is based on the desired padding start from design.
@include globals.position-horizontal(globals.$ion-space-500, null);
}
:host(.searchbar-size-large) .searchbar-clear-button {
// End is based on the desired padding end from design.
@include globals.position-horizontal(null, globals.$ion-space-500);
}

View File

@ -220,10 +220,19 @@ export class Searchbar implements ComponentInterface {
* Set to `"soft"` for a searchbar with slightly rounded corners,
* `"round"` for a searchbar with fully rounded corners,
* or `"rectangular"` for a searchbar without rounded corners.
*
* Defaults to `"round"` for the ionic theme, and `undefined` for all other themes.
*/
@Prop() shape?: 'soft' | 'round' | 'rectangular';
/**
* Set to `"large"` for a searchbar with an increase in height,
* while "small" and "medium" provide progressively smaller heights.
*
* Defaults to `"medium"` for the ionic theme, and `undefined` for all other themes.
*/
@Prop() size?: 'small' | 'medium' | 'large';
/**
* Emitted when the `value` of the `ion-searchbar` element has changed.
*/
@ -636,6 +645,22 @@ export class Searchbar implements ComponentInterface {
return shape;
}
private getSize(): string | undefined {
const theme = getIonTheme(this);
const { size } = this;
// TODO(ROU-11678): Remove theme check when sizes are defined for all themes.
if (theme !== 'ionic') {
return undefined;
}
if (size === undefined) {
return 'medium';
}
return size;
}
/**
* Get the icon to use for the clear icon.
* If an icon is set on the component, use that.
@ -723,6 +748,7 @@ export class Searchbar implements ComponentInterface {
const theme = getIonTheme(this);
const shouldShowCancelButton = this.shouldShowCancelButton();
const shape = this.getShape();
const size = this.getSize();
const cancelButton = this.showCancelButton !== 'never' && (
<button
@ -760,6 +786,7 @@ export class Searchbar implements ComponentInterface {
'searchbar-should-show-clear': this.shouldShowClearButton(),
'searchbar-should-show-cancel': this.shouldShowCancelButton(),
[`searchbar-shape-${shape}`]: shape !== undefined,
[`searchbar-size-${size}`]: size !== undefined,
})}
>
<div class="searchbar-input-container">

View File

@ -38,6 +38,19 @@
<h5 class="ion-padding-start ion-padding-top">Search - Show Cancel Button on Focus</h5>
<ion-searchbar id="focusCancel" value="after view" animated="true" show-cancel-button="focus"> </ion-searchbar>
<h5 class="ion-padding-start ion-padding-top">Search - Show Cancel Button within Toolbar</h5>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button default-href="#"></ion-back-button>
</ion-buttons>
<ion-title>Test</ion-title>
</ion-toolbar>
<ion-toolbar>
<ion-searchbar show-cancel-button="always"></ion-searchbar>
</ion-toolbar>
</ion-header>
<h5 class="ion-padding-start ion-padding-top">Search - No Clear Button</h5>
<ion-searchbar id="noClear" value="after view" type="text" show-clear-button="never"> </ion-searchbar>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Searchbar - Size</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-content>
<h5 class="ion-padding-start ion-padding-top">Search - Default</h5>
<ion-searchbar show-clear-button="always" value="Filled text"></ion-searchbar>
<h5 class="ion-padding-start ion-padding-top">Search - Small</h5>
<ion-searchbar size="small" show-clear-button="always" value="Filled text"></ion-searchbar>
<h5 class="ion-padding-start ion-padding-top">Search - Medium</h5>
<ion-searchbar size="medium" show-clear-button="always" value="Filled text"></ion-searchbar>
<h5 class="ion-padding-start ion-padding-top">Search - Large</h5>
<ion-searchbar size="large" show-clear-button="always" value="Filled text"></ion-searchbar>
</ion-content>
</ion-app>
</body>
</html>

View File

@ -0,0 +1,25 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior only applies to the `ionic` theme.
* This behavior does not vary across directions.
*/
configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('searchbar: size'), () => {
['small', 'medium', 'large'].forEach((size) => {
test(`${size} - should not have visual regressions`, async ({ page }) => {
await page.setContent(
`
<ion-searchbar size="${size}" show-clear-button="always" value="Filled text"></ion-searchbar>
`,
config
);
const searchbar = page.locator('ion-searchbar');
await expect(searchbar).toHaveScreenshot(screenshot(`searchbar-size-${size}`));
});
});
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1900,7 +1900,7 @@ export declare interface IonRow extends Components.IonRow {}
@ProxyCmp({
inputs: ['animated', 'autocapitalize', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'searchIcon', 'shape', 'showCancelButton', 'showClearButton', 'spellcheck', 'theme', 'type', 'value'],
inputs: ['animated', 'autocapitalize', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'searchIcon', 'shape', 'showCancelButton', 'showClearButton', 'size', 'spellcheck', 'theme', 'type', 'value'],
methods: ['setFocus', 'getInputElement']
})
@Component({
@ -1908,7 +1908,7 @@ export declare interface IonRow extends Components.IonRow {}
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['animated', 'autocapitalize', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'searchIcon', 'shape', 'showCancelButton', 'showClearButton', 'spellcheck', 'theme', 'type', 'value'],
inputs: ['animated', 'autocapitalize', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'searchIcon', 'shape', 'showCancelButton', 'showClearButton', 'size', 'spellcheck', 'theme', 'type', 'value'],
})
export class IonSearchbar {
protected el: HTMLElement;

View File

@ -739,6 +739,7 @@ export const IonSearchbar = /*@__PURE__*/ defineContainer<JSX.IonSearchbar, JSX.
'type',
'value',
'shape',
'size',
'ionInput',
'ionChange',
'ionCancel',