From 3c3118e215bb37c1d8696e4f3a493a759eb81c22 Mon Sep 17 00:00:00 2001
From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Date: Wed, 22 Jan 2025 13:13:19 -0500
Subject: [PATCH 01/14] feat(textarea): add css vars for helper and error text
---
core/src/components/textarea/textarea.md.solid.scss | 4 ++++
core/src/components/textarea/textarea.scss | 11 +++++++++--
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/core/src/components/textarea/textarea.md.solid.scss b/core/src/components/textarea/textarea.md.solid.scss
index 2dabbeb4e73..9d831daf0e0 100644
--- a/core/src/components/textarea/textarea.md.solid.scss
+++ b/core/src/components/textarea/textarea.md.solid.scss
@@ -32,6 +32,10 @@
--border-color: var(--highlight-color);
}
+/**
+ * The bottom content should never have
+ * a border with the solid style.
+ */
:host(.textarea-fill-solid) .textarea-bottom {
border-top: none;
}
diff --git a/core/src/components/textarea/textarea.scss b/core/src/components/textarea/textarea.scss
index 3a54391cf42..277307f4285 100644
--- a/core/src/components/textarea/textarea.scss
+++ b/core/src/components/textarea/textarea.scss
@@ -29,6 +29,9 @@
* @prop --padding-end: Right padding if direction is left-to-right, and left padding if direction is right-to-left of the textarea
* @prop --padding-bottom: Bottom padding of the textarea
* @prop --padding-start: Left padding if direction is left-to-right, and right padding if direction is right-to-left of the textarea
+ *
+ * @prop --helper-text-color: The color of the supporting text displayed beneath the textarea when the textarea is valid.
+ * @prop --error-text-color: The color of the supporting text displayed beneath the textarea when the textarea is invalid and touched.
*/
--background: initial;
--color: initial;
@@ -45,6 +48,8 @@
--highlight-color-focused: #{ion-color(primary, base)};
--highlight-color-valid: #{ion-color(success, base)};
--highlight-color-invalid: #{ion-color(danger, base)};
+ --helper-text-color: #{$text-color-step-300};
+ --error-text-color: var(--highlight-color-invalid);
/**
* This is a private API that is used to switch
@@ -389,6 +394,8 @@
border-top: var(--border-width) var(--border-style) var(--border-color);
font-size: dynamic-font(12px);
+
+ white-space: normal;
}
/**
@@ -411,13 +418,13 @@
.textarea-bottom .error-text {
display: none;
- color: var(--highlight-color-invalid);
+ color: var(--error-text-color);
}
.textarea-bottom .helper-text {
display: block;
- color: #{$text-color-step-450};
+ color: var(--helper-text-color);
}
:host(.ion-touched.ion-invalid) .textarea-bottom .error-text {
From be503747fd5ded4f1f27d84cc07dc8474fe7a0db Mon Sep 17 00:00:00 2001
From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Date: Wed, 22 Jan 2025 13:13:31 -0500
Subject: [PATCH 02/14] test(textarea): update e2e test for new vars
---
.../textarea/test/bottom-content/index.html | 101 +++--
.../test/bottom-content/textarea.e2e.ts | 356 +++++++++++-------
2 files changed, 283 insertions(+), 174 deletions(-)
diff --git a/core/src/components/textarea/test/bottom-content/index.html b/core/src/components/textarea/test/bottom-content/index.html
index 04de279bd2b..7e02ec505fd 100644
--- a/core/src/components/textarea/test/bottom-content/index.html
+++ b/core/src/components/textarea/test/bottom-content/index.html
@@ -15,7 +15,7 @@
-
- `,
- config
- );
+ test('textarea should have an aria-describedby attribute when error text is present', async ({ page }) => {
+ await page.setContent(
+ ``,
+ config
+ );
- const errorText = page.locator('ion-textarea .error-text');
- await expect(errorText).toHaveScreenshot(screenshot(`textarea-error-custom-color`));
- });
- test('textarea should have an aria-describedby attribute when error text is present', async ({ page }) => {
- await page.setContent(
- ``,
- config
- );
+ const input = page.locator('ion-textarea textarea');
+ const errorText = page.locator('ion-textarea .error-text');
+ const errorTextId = await errorText.getAttribute('id');
+ const ariaDescribedBy = await input.getAttribute('aria-describedby');
- const textarea = page.locator('ion-textarea textarea');
- const errorText = page.locator('ion-textarea .error-text');
- const errorTextId = await errorText.getAttribute('id');
- const ariaDescribedBy = await textarea.getAttribute('aria-describedby');
+ expect(ariaDescribedBy).toBe(errorTextId);
+ });
+ test('textarea should have aria-invalid attribute when input is invalid', async ({ page }) => {
+ await page.setContent(
+ ``,
+ config
+ );
- expect(ariaDescribedBy).toBe(errorTextId);
- });
- test('textarea should have aria-invalid attribute when input is invalid', async ({ page }) => {
- await page.setContent(
- ``,
- config
- );
+ const input = page.locator('ion-textarea textarea');
- const textarea = page.locator('ion-textarea textarea');
+ await expect(input).toHaveAttribute('aria-invalid');
+ });
+ test('textarea should not have aria-invalid attribute when input is valid', async ({ page }) => {
+ await page.setContent(
+ ``,
+ config
+ );
- await expect(textarea).toHaveAttribute('aria-invalid');
- });
- test('textarea should not have aria-invalid attribute when input is valid', async ({ page }) => {
- await page.setContent(
- ``,
- config
- );
+ const input = page.locator('ion-textarea textarea');
- const textarea = page.locator('ion-textarea textarea');
+ await expect(input).not.toHaveAttribute('aria-invalid');
+ });
+ test('textarea should not have aria-describedby attribute when no hint or error text is present', async ({
+ page,
+ }) => {
+ await page.setContent(``, config);
- await expect(textarea).not.toHaveAttribute('aria-invalid');
- });
- test('textarea should not have aria-describedby attribute when no hint or error text is present', async ({
- page,
- }) => {
- await page.setContent(``, config);
+ const input = page.locator('ion-textarea textarea');
- const textarea = page.locator('ion-textarea textarea');
+ await expect(input).not.toHaveAttribute('aria-describedby');
+ });
+ });
+});
- await expect(textarea).not.toHaveAttribute('aria-describedby');
- });
+/**
+ * Rendering is different across modes
+ */
+configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
+ test.describe(title('textarea: helper text rendering'), () => {
+ test('should not have visual regressions when rendering helper text', async ({ page }) => {
+ await page.setContent(``, config);
+
+ const bottomEl = page.locator('ion-textarea');
+ await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-helper-text`));
});
- test.describe('textarea: hint text rendering', () => {
- test.describe('regular textareas', () => {
- test('should not have visual regressions when rendering helper text', async ({ page }) => {
- await page.setContent(``, config);
-
- const bottomEl = page.locator('ion-textarea .textarea-bottom');
- await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-helper`));
- });
- test('should not have visual regressions when rendering error text', async ({ page }) => {
- await page.setContent(
- ``,
- config
- );
-
- const bottomEl = page.locator('ion-textarea .textarea-bottom');
- await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-error`));
- });
- });
+ test('should not have visual regressions when rendering helper text with wrapping text', async ({ page }) => {
+ await page.setContent(
+ ``,
+ config
+ );
+
+ const bottomEl = page.locator('ion-textarea');
+ await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-helper-text-wrapping`));
+ });
+ test('should not have visual regressions when rendering helper text with a stacked label', async ({ page }) => {
+ await page.setContent(
+ ``,
+ config
+ );
+
+ const bottomEl = page.locator('ion-textarea');
+ await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-helper-text-stacked-label`));
+ });
+ });
+
+ test.describe(title('textarea: error text rendering'), () => {
+ test('should not have visual regressions when rendering error text', async ({ page }) => {
+ await page.setContent(
+ ``,
+ config
+ );
+
+ const bottomEl = page.locator('ion-textarea');
+ await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-error-text`));
+ });
+ test('should not have visual regressions when rendering error text with a stacked label', async ({ page }) => {
+ await page.setContent(
+ ``,
+ config
+ );
+
+ const bottomEl = page.locator('ion-textarea');
+ await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-error-text-stacked-label`));
+ });
+ });
+});
+
+/**
+ * Customizing supporting text is the same across modes and directions
+ */
+configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
+ test.describe(title('textarea: supporting text customization'), () => {
+ test('should not have visual regressions when rendering helper text with a custom color via css var', async ({
+ page,
+ }) => {
+ await page.setContent(
+ `
+
+
+ `,
+ config
+ );
+
+ const helperText = page.locator('ion-textarea');
+ await expect(helperText).toHaveScreenshot(screenshot(`textarea-helper-text-custom-color-var`));
+ });
+ test('should not have visual regressions when rendering error text with a custom color via css var', async ({
+ page,
+ }) => {
+ await page.setContent(
+ `
+
+
+ `,
+ config
+ );
+
+ const errorText = page.locator('ion-textarea');
+ await expect(errorText).toHaveScreenshot(screenshot(`textarea-error-text-custom-color-var`));
+ });
+ test('should not have visual regressions when rendering error text with a custom color via css highlight var', async ({
+ page,
+ }) => {
+ await page.setContent(
+ `
+
+
+ `,
+ config
+ );
+
+ const errorText = page.locator('ion-textarea');
+ await expect(errorText).toHaveScreenshot(screenshot(`textarea-error-text-custom-highlight-var`));
});
});
});
@@ -139,7 +216,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
test('should not activate if maxlength is not specified even if bottom content is visible', async ({ page }) => {
await page.setContent(
`
-
+
`,
config
);
@@ -149,7 +226,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
test('default formatter should be used', async ({ page }) => {
await page.setContent(
`
-
+
`,
config
);
@@ -159,7 +236,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
test('custom formatter should be used when provided', async ({ page }) => {
await page.setContent(
`
-
+