Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(textarea): add start and end slots #28441

Merged
merged 22 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion core/src/components/textarea/test/a11y/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ <h1>Textarea - a11y</h1>
<ion-textarea label="Email" label-placement="stacked" value="hi@ionic.io"></ion-textarea>
<ion-textarea label="Email" label-placement="floating"></ion-textarea>
<ion-textarea label="Email" label-placement="floating" fill="outline" value="hi@ionic.io"></ion-textarea> <br />
<ion-textarea label="Email" label-placement="floating" fill="solid" value="hi@ionic.io"></ion-textarea>
<ion-textarea label="Email" label-placement="floating" fill="solid" value="hi@ionic.io"></ion-textarea><br />
<ion-textarea label="Email" label-placement="floating" fill="solid" value="hi@ionic.io">
<ion-button slot="start" aria-label="button">
<ion-icon slot="icon-only" name="lock-closed" aria-hidden="true"></ion-icon>
</ion-button>
<ion-button slot="end" aria-label="button">
<ion-icon slot="icon-only" name="lock-closed" aria-hidden="true"></ion-icon>
</ion-button>
</ion-textarea>
</main>
</body>
</html>
193 changes: 181 additions & 12 deletions core/src/components/textarea/test/slot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,51 +51,184 @@
<ion-content id="content" class="ion-padding">
<div class="grid">
<div class="grid-item">
<h2>No Fill / Start</h2>
<h2>No Fill / Start Label</h2>
<ion-textarea label-placement="start" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Solid / Start</h2>
<h2>Solid / Start Label</h2>
<ion-textarea label-placement="start" fill="solid" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Start</h2>
<h2>Outline / Start Label</h2>
<ion-textarea label-placement="start" fill="outline" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>

<div class="grid-item">
<h2>No Fill / Floating</h2>
<h2>No Fill / Floating Label</h2>
<ion-textarea label-placement="floating" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Solid / Floating</h2>
<h2>Solid / Floating Label</h2>
<ion-textarea label-placement="floating" fill="solid" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Floating</h2>
<h2>Outline / Floating Label</h2>
<ion-textarea label-placement="floating" fill="outline" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Floating / Async</h2>
<h2>No Fill / Start Label / Buttons</h2>
<ion-textarea label-placement="start" value="hi@ionic.io" label="Email">
<ion-button fill="clear" slot="start" aria-label="Lock">
<ion-icon slot="icon-only" name="lock-closed" aria-hidden="true"></ion-icon>
</ion-button>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Solid / Start Label / Buttons</h2>
<ion-textarea label-placement="start" fill="solid" value="hi@ionic.io" label="Email">
<ion-button fill="clear" slot="start" aria-label="Lock">
<ion-icon slot="icon-only" name="lock-closed" aria-hidden="true"></ion-icon>
</ion-button>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Start Label / Buttons</h2>
<ion-textarea label-placement="start" fill="outline" value="hi@ionic.io" label="Email">
<ion-button fill="clear" slot="start" aria-label="Lock">
<ion-icon slot="icon-only" name="lock-closed" aria-hidden="true"></ion-icon>
</ion-button>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-textarea>
</div>

<div class="grid-item">
<h2>No Fill / Floating Label / Buttons</h2>
<ion-textarea label-placement="floating" value="hi@ionic.io" label="Email">
<ion-button fill="clear" slot="start" aria-label="Lock">
<ion-icon slot="icon-only" name="lock-closed" aria-hidden="true"></ion-icon>
</ion-button>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Solid / Floating Label / Buttons</h2>
<ion-textarea label-placement="floating" fill="solid" value="hi@ionic.io" label="Email">
<ion-button fill="clear" slot="start" aria-label="Lock">
<ion-icon slot="icon-only" name="lock-closed" aria-hidden="true"></ion-icon>
</ion-button>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Floating Label / Buttons</h2>
<ion-textarea label-placement="floating" fill="outline" value="hi@ionic.io" label="Email">
<ion-button fill="clear" slot="start" aria-label="Lock">
<ion-icon slot="icon-only" name="lock-closed" aria-hidden="true"></ion-icon>
</ion-button>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-textarea>
</div>

<div class="grid-item">
<h2>No Fill / Start Label / Decorations</h2>
<ion-textarea label-placement="start" value="100" label="Weight">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
<ion-label slot="end">lbs</ion-label>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Solid / Start Label / Decorations</h2>
<ion-textarea label-placement="start" fill="solid" value="100" label="Weight">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
<ion-label slot="end">lbs</ion-label>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Start Label / Decorations</h2>
<ion-textarea label-placement="start" fill="outline" value="100" label="Weight">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
<ion-label slot="end">lbs</ion-label>
</ion-textarea>
</div>

<div class="grid-item">
<h2>No Fill / Floating Label / Decorations</h2>
<ion-textarea label-placement="floating" value="100" label="Weight">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
<ion-label slot="end">lbs</ion-label>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Solid / Floating Label / Decorations</h2>
<ion-textarea label-placement="floating" fill="solid" value="100" label="Weight">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
<ion-label slot="end">lbs</ion-label>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Floating Label / Decorations</h2>
<ion-textarea label-placement="floating" fill="outline" value="100" label="Weight">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
<ion-label slot="end">lbs</ion-label>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Async Label</h2>
<ion-textarea id="solid-async" label-placement="floating" fill="outline" value="hi@ionic.io"></ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Async Decorations</h2>
<ion-textarea id="async-decorations" label-placement="floating" fill="outline" label="Email"></ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Autogrow / Decorations</h2>
<ion-textarea label-placement="start" fill="outline" label="Email" auto-grow="true" value="hi@ionic.io">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
<ion-label slot="end">lbs</ion-label>
</ion-textarea>
</div>
</div>

<ion-button onclick="addSlot()">Add Slotted Content</ion-button>
Expand All @@ -106,29 +239,65 @@ <h2>Outline / Floating / Async</h2>

<script>
const solidAsync = document.querySelector('#solid-async');
const asyncDecos = document.querySelector('#async-decorations');

const getSlottedContent = () => {
const getSlottedLabel = () => {
return solidAsync.querySelector('[slot="label"]');
};

const getSlottedStartDeco = () => {
return asyncDecos.querySelector('[slot="start"]');
};

const getSlottedEndDeco = () => {
return asyncDecos.querySelector('[slot="end"]');
};

const addSlot = () => {
if (getSlottedContent() === null) {
if (getSlottedLabel() === null) {
const labelEl = document.createElement('div');
labelEl.slot = 'label';
labelEl.innerHTML = 'Comments <span class="required">*</span>';

solidAsync.appendChild(labelEl);
}

if (getSlottedStartDeco() === null) {
const startEl = document.createElement('div');
startEl.slot = 'start';
startEl.innerHTML = 'Start';

asyncDecos.insertAdjacentElement('afterbegin', startEl);
}

if (getSlottedEndDeco() === null) {
const endEl = document.createElement('div');
endEl.slot = 'end';
endEl.innerHTML = 'End';

asyncDecos.insertAdjacentElement('beforeend', endEl);
}
};

const removeSlot = () => {
if (getSlottedContent() !== null) {
solidAsync.querySelector('[slot="label"]').remove();
const slottedLabel = getSlottedLabel();
if (slottedLabel !== null) {
slottedLabel.remove();
}

const slottedStartDeco = getSlottedStartDeco();
if (slottedStartDeco !== null) {
slottedStartDeco.remove();
}

const slottedEndDeco = getSlottedEndDeco();
if (slottedEndDeco !== null) {
slottedEndDeco.remove();
}
};

const updateSlot = () => {
const slottedContent = getSlottedContent();
const slottedContent = getSlottedLabel();

if (slottedContent !== null) {
slottedContent.textContent = 'This is my really really really long text';
Expand Down
68 changes: 68 additions & 0 deletions core/src/components/textarea/test/slot/textarea.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';

configs().forEach(({ title, screenshot, config }) => {
test.describe(title('textarea: start and end slots (visual checks)'), () => {
test('should not have visual regressions with a start-positioned label', async ({ page }) => {
await page.setContent(
`
<ion-textarea label-placement="start" fill="solid" value="100" label="Weight">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
<ion-label slot="end">lbs</ion-label>
</ion-textarea>
`,
config
);

const textarea = page.locator('ion-textarea');
await expect(textarea).toHaveScreenshot(screenshot(`textarea-slots-label-start`));
});

test('should not have visual regressions with a floating label', async ({ page }) => {
await page.setContent(
`
<ion-textarea label-placement="floating" fill="solid" value="100" label="Weight">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
<ion-label slot="end">lbs</ion-label>
</ion-textarea>
`,
config
);

const textarea = page.locator('ion-textarea');
await expect(textarea).toHaveScreenshot(screenshot(`textarea-slots-label-floating`));
});
});
});

configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('textarea: start and end slots (functionality checks)'), () => {
test('should raise floating label when there is content in the start slot', async ({ page }) => {
await page.setContent(
`
<ion-textarea label-placement="floating" fill="solid" label="Weight">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
</ion-textarea>
`,
config
);

const textarea = page.locator('ion-textarea');
await expect(textarea).toHaveClass(/label-floating/);
});

test('should raise floating label when there is content in the end slot', async ({ page }) => {
await page.setContent(
`
<ion-textarea label-placement="floating" fill="solid" label="Weight">
<ion-icon slot="end" name="barbell" aria-hidden="true"></ion-icon>
</ion-textarea>
`,
config
);

const textarea = page.locator('ion-textarea');
await expect(textarea).toHaveClass(/label-floating/);
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 9 additions & 6 deletions core/src/components/textarea/textarea.md.outline.scss
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,7 @@
/**
* This makes the label sit above the textarea.
*/
:host(.has-focus.textarea-fill-outline.textarea-label-placement-floating) .label-text-wrapper,
:host(.has-value.textarea-fill-outline.textarea-label-placement-floating) .label-text-wrapper,
:host(.textarea-fill-outline.textarea-label-placement-stacked) .label-text-wrapper {
:host(.label-floating.textarea-fill-outline) .label-text-wrapper {
@include transform(translateY(-32%), scale(#{$form-control-label-stacked-scale}));
@include margin(0);

Expand All @@ -116,6 +114,13 @@
@include margin(12px, 0px, 0px, 0px);
}

:host(.textarea-fill-outline.textarea-label-placement-stacked) ::slotted([slot="start"]),
:host(.textarea-fill-outline.textarea-label-placement-stacked) ::slotted([slot="end"]),
:host(.textarea-fill-outline.textarea-label-placement-floating) ::slotted([slot="start"]),
:host(.textarea-fill-outline.textarea-label-placement-floating) ::slotted([slot="end"]) {
margin-top: 12px;
}

// Textarea Fill: Outline Outline Container
// ----------------------------------------------------------------

Expand Down Expand Up @@ -220,8 +225,6 @@
* the floating/stacked label. We simulate this "cut out"
* by removing the top border from the notch fragment.
*/
:host(.has-focus.textarea-fill-outline.textarea-label-placement-floating) .textarea-outline-notch,
:host(.has-value.textarea-fill-outline.textarea-label-placement-floating) .textarea-outline-notch,
:host(.textarea-fill-outline.textarea-label-placement-stacked) .textarea-outline-notch {
:host(.label-floating.textarea-fill-outline) .textarea-outline-notch {
border-top: none;
}
4 changes: 1 addition & 3 deletions core/src/components/textarea/textarea.md.solid.scss
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@
// Textarea Label
// ----------------------------------------------------------------

:host(.textarea-fill-solid.textarea-label-placement-stacked) .label-text-wrapper,
:host(.has-focus.textarea-fill-solid.textarea-label-placement-floating) .label-text-wrapper,
:host(.has-value.textarea-fill-solid.textarea-label-placement-floating) .label-text-wrapper {
:host(.label-floating.textarea-fill-solid) .label-text-wrapper {
/**
* Label text should not extend
* beyond the bounds of the textarea.
Expand Down
Loading
Loading