feat(textarea): add support for the shape property with soft, round and rectangular options (#29788)

- Adds support for `"soft"`, `"round"` and `"rectangular"` shapes in the Ionic theme, with `"round"` as the default
- Adds a `shape` folder and test with examples
- Adds an e2e test with screenshots for all shapes
This commit is contained in:
Brandy Carney
2024-08-22 18:29:49 -04:00
committed by GitHub
parent 0d67c83c21
commit ca4c0a5a8d
25 changed files with 238 additions and 46 deletions

View File

@@ -2279,7 +2279,7 @@ ion-textarea,prop,placeholder,string | undefined,undefined,false,false
ion-textarea,prop,readonly,boolean,false,false,false
ion-textarea,prop,required,boolean,false,false,false
ion-textarea,prop,rows,number | undefined,undefined,false,false
ion-textarea,prop,shape,"round" | undefined,undefined,false,false
ion-textarea,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false
ion-textarea,prop,size,"large" | "medium" | "small" | undefined,'medium',false,false
ion-textarea,prop,spellcheck,boolean,false,false,false
ion-textarea,prop,theme,"ios" | "md" | "ionic",undefined,false,false

View File

@@ -3617,9 +3617,9 @@ export namespace Components {
*/
"setFocus": () => Promise<void>;
/**
* The shape of the textarea. If "round" it will have an increased border radius.
* Set to `"soft"` for a textarea with slightly rounded corners, `"round"` for a textarea with fully rounded corners, or `"rectangular"` for a textarea without rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
*/
"shape"?: 'round';
"shape"?: 'soft' | 'round' | 'rectangular';
/**
* The size of the textarea. If "large" it will increase the height of the textarea, while "small" and "medium" provide progressively smaller heights. The default size is "medium". This property only applies to the `"ionic"` theme.
*/
@@ -8977,9 +8977,9 @@ declare namespace LocalJSX {
*/
"rows"?: number;
/**
* The shape of the textarea. If "round" it will have an increased border radius.
* Set to `"soft"` for a textarea with slightly rounded corners, `"round"` for a textarea with fully rounded corners, or `"rectangular"` for a textarea without rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
*/
"shape"?: 'round';
"shape"?: 'soft' | 'round' | 'rectangular';
/**
* The size of the textarea. If "large" it will increase the height of the textarea, while "small" and "medium" provide progressively smaller heights. The default size is "medium". This property only applies to the `"ionic"` theme.
*/

View File

@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Textarea - Shape</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<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>
<style>
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(250px, 1fr));
grid-row-gap: 20px;
grid-column-gap: 20px;
}
h2 {
font-size: 12px;
font-weight: normal;
color: #6f7378;
margin-top: 10px;
}
@media screen and (max-width: 800px) {
.grid {
grid-template-columns: 1fr;
padding: 0;
}
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Textarea - Shape</ion-title>
</ion-toolbar>
</ion-header>
<ion-content id="content" class="ion-padding">
<div class="grid">
<div class="grid-item">
<h2>Default</h2>
<ion-textarea
fill="outline"
label="Label"
label-placement="stacked"
placeholder="Placeholder"
></ion-textarea>
</div>
<div class="grid-item">
<h2>Round</h2>
<ion-textarea
shape="round"
fill="outline"
label="Label"
label-placement="stacked"
placeholder="Placeholder"
></ion-textarea>
</div>
<div class="grid-item">
<h2>Soft</h2>
<ion-textarea
shape="soft"
fill="outline"
label="Label"
label-placement="stacked"
placeholder="Placeholder"
></ion-textarea>
</div>
<div class="grid-item">
<h2>Rectangular</h2>
<ion-textarea
shape="rectangular"
fill="outline"
label="Label"
label-placement="stacked"
placeholder="Placeholder"
></ion-textarea>
</div>
</div>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across directions
* The round shape is only available in the Ionic and Material Design themes
*/
configs({ modes: ['md', 'ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('textarea: shape'), () => {
test.describe('default', () => {
test('should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<ion-textarea
fill="outline"
label="Email"
label-placement="stacked"
value="hi@ionic.io"
></ion-textarea>
`,
config
);
const textarea = page.locator('ion-textarea');
await expect(textarea).toHaveScreenshot(screenshot(`textarea-default`));
});
});
test.describe('round', () => {
test('should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<ion-textarea
shape="round"
fill="outline"
label="Email"
label-placement="stacked"
value="hi@ionic.io"
></ion-textarea>
`,
config
);
const textarea = page.locator('ion-textarea');
await expect(textarea).toHaveScreenshot(screenshot(`textarea-round`));
});
});
});
});
/**
* The soft and rectangular shapes are only available in the Ionic theme
*/
configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('textarea: shape'), () => {
test.describe('soft', () => {
test('should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<ion-textarea
shape="soft"
fill="outline"
label="Email"
label-placement="stacked"
value="hi@ionic.io"
></ion-textarea>
`,
config
);
const textarea = page.locator('ion-textarea');
await expect(textarea).toHaveScreenshot(screenshot(`textarea-soft`));
});
});
test.describe('rectangular', () => {
test('should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<ion-textarea
shape="rectangular"
fill="outline"
label="Email"
label-placement="stacked"
value="hi@ionic.io"
></ion-textarea>
`,
config
);
const textarea = page.locator('ion-textarea');
await expect(textarea).toHaveScreenshot(screenshot(`textarea-rectangular`));
});
});
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -15,7 +15,7 @@
<style>
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(250px, 1fr));
grid-template-columns: repeat(3, minmax(250px, 1fr));
grid-row-gap: 20px;
grid-column-gap: 20px;
}
@@ -27,7 +27,6 @@
color: #6f7378;
margin-top: 10px;
padding-left: 16px;
}
@media screen and (max-width: 800px) {
@@ -60,18 +59,6 @@
></ion-textarea>
</div>
<div class="grid-item">
<h2>Outline: Small Size, Round Shape</h2>
<ion-textarea
size="small"
fill="outline"
shape="round"
label="Label"
label-placement="stacked"
placeholder="Placeholder"
></ion-textarea>
</div>
<div class="grid-item">
<h2>Outline: No Size</h2>
<ion-textarea
@@ -82,17 +69,6 @@
></ion-textarea>
</div>
<div class="grid-item">
<h2>Outline: No Size, Round Shape</h2>
<ion-textarea
fill="outline"
shape="round"
label="Label"
label-placement="stacked"
placeholder="Placeholder"
></ion-textarea>
</div>
<div class="grid-item">
<h2>Outline: Large Size</h2>
<ion-textarea
@@ -103,18 +79,6 @@
placeholder="Placeholder"
></ion-textarea>
</div>
<div class="grid-item">
<h2>Outline: Large Size, Round Shape</h2>
<ion-textarea
size="large"
fill="outline"
shape="round"
label="Label"
label-placement="stacked"
placeholder="Placeholder"
></ion-textarea>
</div>
</div>
</ion-content>
</ion-app>

View File

@@ -6,7 +6,6 @@
// --------------------------------------------------
:host {
--border-radius: #{globals.$ionic-border-radius-400};
--color: #{globals.$ionic-color-neutral-1200};
--highlight-color-valid: #{globals.$ionic-color-success-base};
--highlight-color-invalid: #{globals.$ionic-color-danger-base};
@@ -53,6 +52,21 @@
min-height: globals.$ionic-scale-3600;
}
// Ionic Textarea Shapes
// --------------------------------------------------
:host(.textarea-shape-soft) {
--border-radius: #{globals.$ionic-border-radius-200};
}
:host(.textarea-shape-round) {
--border-radius: #{globals.$ionic-border-radius-400};
}
:host(.textarea-shape-rectangular) {
--border-radius: #{globals.$ionic-border-radius-0};
}
// Textarea Wrapper
// ----------------------------------------------------------------

View File

@@ -244,9 +244,13 @@ export class Textarea implements ComponentInterface {
@Prop() labelPlacement: 'start' | 'end' | 'floating' | 'stacked' | 'fixed' = 'start';
/**
* The shape of the textarea. If "round" it will have an increased border radius.
* Set to `"soft"` for a textarea with slightly rounded corners,
* `"round"` for a textarea with fully rounded corners, or `"rectangular"`
* for a textarea without rounded corners.
*
* Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
*/
@Prop() shape?: 'round';
@Prop() shape?: 'soft' | 'round' | 'rectangular';
/**
* The size of the textarea. If "large" it will increase the height of the textarea, while
@@ -541,6 +545,18 @@ export class Textarea implements ComponentInterface {
return this.label !== undefined || this.labelSlot !== null;
}
private getShape(): string | undefined {
const theme = getIonTheme(this);
const { shape } = this;
// TODO(ROU-11050): Update theme check when shapes are defined for all themes.
if (theme === 'ionic' && shape === undefined) {
return 'round';
}
return shape;
}
/**
* Renders the border container when fill="outline".
*/
@@ -626,8 +642,9 @@ export class Textarea implements ComponentInterface {
}
render() {
const { inputId, disabled, fill, shape, size, labelPlacement, el, hasFocus } = this;
const { inputId, disabled, fill, size, labelPlacement, el, hasFocus } = this;
const theme = getIonTheme(this);
const shape = this.getShape();
const value = this.getValue();
const inItem = hostContext('ion-item', this.el);
const shouldRenderHighlight = theme === 'md' && fill !== 'outline' && !inItem;