feat(textarea): add ionic theme design with medium size (#29776)
- Adds the typography styles for the default size (medium) - Adds the styles for the outline fill - Adds the styles for the label & helper text - Adds the spacing for the label, textarea, counter and helper text
@ -2280,6 +2280,7 @@ 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,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
|
||||
ion-textarea,prop,value,null | string | undefined,'',false,false
|
||||
|
8
core/src/components.d.ts
vendored
@ -3620,6 +3620,10 @@ export namespace Components {
|
||||
* The shape of the textarea. If "round" it will have an increased border radius.
|
||||
*/
|
||||
"shape"?: 'round';
|
||||
/**
|
||||
* The size of the textarea. If "large", it will have an increased height. By default the size is "medium". This property only applies to the `"ionic"` theme.
|
||||
*/
|
||||
"size"?: 'small' | 'medium' | 'large';
|
||||
/**
|
||||
* If `true`, the element will have its spelling and grammar checked.
|
||||
*/
|
||||
@ -8976,6 +8980,10 @@ declare namespace LocalJSX {
|
||||
* The shape of the textarea. If "round" it will have an increased border radius.
|
||||
*/
|
||||
"shape"?: 'round';
|
||||
/**
|
||||
* The size of the textarea. If "large", it will have an increased height. By default the size is "medium". This property only applies to the `"ionic"` theme.
|
||||
*/
|
||||
"size"?: 'small' | 'medium' | 'large';
|
||||
/**
|
||||
* If `true`, the element will have its spelling and grammar checked.
|
||||
*/
|
||||
|
@ -22,8 +22,18 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content id="content">
|
||||
<ion-textarea label="Textarea"></ion-textarea>
|
||||
<ion-content id="content" class="ion-padding">
|
||||
<ion-textarea
|
||||
fill="outline"
|
||||
label="Label"
|
||||
label-placement="stacked"
|
||||
placeholder="Placeholder"
|
||||
helper-text="Helper message"
|
||||
counter="true"
|
||||
maxlength="999"
|
||||
>
|
||||
<ion-icon slot="end" name="square-outline"></ion-icon>
|
||||
</ion-textarea>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
75
core/src/components/textarea/test/size/index.html
Normal file
@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Textarea - Size</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;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
@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 - Size</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content id="content" class="ion-padding">
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>No Fill: No Size</h2>
|
||||
<ion-textarea label="Label" label-placement="stacked" placeholder="Placeholder"></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Outline: No Size</h2>
|
||||
<ion-textarea fill="outline" label="Label" label-placement="stacked"></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>No Fill: No Size, Round Shape</h2>
|
||||
<ion-textarea shape="round" label="Label" label-placement="stacked"></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"></ion-textarea>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
57
core/src/components/textarea/test/size/textarea.e2e.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* Size is only available in the Ionic theme
|
||||
*/
|
||||
configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('textarea: size'), () => {
|
||||
test.describe('textarea: size medium', () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-textarea
|
||||
label="Email"
|
||||
value="hi@ionic.io"
|
||||
></ion-textarea>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const textarea = page.locator('ion-textarea');
|
||||
await expect(textarea).toHaveScreenshot(screenshot(`textarea-size-medium`));
|
||||
});
|
||||
test('should render correctly with stacked label', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-textarea
|
||||
label="Email"
|
||||
label-placement="stacked"
|
||||
value="hi@ionic.io"
|
||||
></ion-textarea>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const textarea = page.locator('ion-textarea');
|
||||
await expect(textarea).toHaveScreenshot(screenshot(`textarea-size-medium-label-stacked`));
|
||||
});
|
||||
test('should not have visual regressions with fill outline', 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-size-medium-outline`));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 3.1 KiB |
@ -59,35 +59,11 @@
|
||||
|
||||
width: 100%;
|
||||
|
||||
min-height: 44px;
|
||||
|
||||
color: var(--color);
|
||||
|
||||
font-family: $font-family-base;
|
||||
|
||||
z-index: $z-index-item-input;
|
||||
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// Textarea Wrapper
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Since the label sits on top of the element,
|
||||
* the component needs to be taller otherwise the
|
||||
* label will appear too close to the textarea text.
|
||||
* Also, floating and stacked labels should not
|
||||
* push the label down since it it
|
||||
* sits on top of the textarea.
|
||||
*/
|
||||
:host(.textarea-label-placement-floating),
|
||||
:host(.textarea-label-placement-stacked) {
|
||||
--padding-top: 0px;
|
||||
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the cols property is set we should
|
||||
* respect that width instead of defaulting
|
||||
@ -279,17 +255,11 @@
|
||||
caret-color: var(--highlight-color);
|
||||
}
|
||||
|
||||
.native-wrapper textarea {
|
||||
@include padding(var(--padding-top), 0px, var(--padding-bottom), 0px);
|
||||
}
|
||||
|
||||
.native-wrapper {
|
||||
display: grid;
|
||||
|
||||
min-width: inherit;
|
||||
max-width: inherit;
|
||||
min-height: inherit;
|
||||
max-height: inherit;
|
||||
|
||||
/**
|
||||
* This avoids a WebKit bug where
|
||||
@ -375,20 +345,11 @@
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
.textarea-bottom {
|
||||
/**
|
||||
* The bottom content should take on the start and end
|
||||
* padding so it is always aligned with either the label
|
||||
* or the start of the textarea.
|
||||
*/
|
||||
@include padding(5px, var(--padding-end), 0, var(--padding-start));
|
||||
|
||||
display: flex;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
border-top: var(--border-width) var(--border-style) var(--border-color);
|
||||
|
||||
font-size: dynamic-font(12px);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -417,7 +378,7 @@
|
||||
.textarea-bottom .helper-text {
|
||||
display: block;
|
||||
|
||||
color: #{$text-color-step-450};
|
||||
color: $text-color-step-450;
|
||||
}
|
||||
|
||||
:host(.ion-touched.ion-invalid) .textarea-bottom .error-text {
|
||||
@ -439,11 +400,7 @@
|
||||
*/
|
||||
@include margin-horizontal(auto, null);
|
||||
|
||||
color: #{$text-color-step-450};
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
padding-inline-start: 16px;
|
||||
}
|
||||
|
||||
// Textarea Label
|
||||
@ -524,15 +481,6 @@
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
:host(.textarea-label-placement-start) .label-text-wrapper {
|
||||
/**
|
||||
* The margin between the label and
|
||||
* the textarea should be on the end
|
||||
* when the label sits at the start.
|
||||
*/
|
||||
@include margin(0, $form-control-label-margin, 0, 0);
|
||||
}
|
||||
|
||||
// Textarea Label Placement - End
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
@ -544,27 +492,9 @@
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
/**
|
||||
* The margin between the label and
|
||||
* the textarea should be on the start
|
||||
* when the label sits at the end.
|
||||
*/
|
||||
:host(.textarea-label-placement-end) .label-text-wrapper {
|
||||
@include margin(0, 0, 0, $form-control-label-margin);
|
||||
}
|
||||
|
||||
// Textarea Label Placement - Fixed
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
:host(.textarea-label-placement-fixed) .label-text-wrapper {
|
||||
/**
|
||||
* The margin between the label and
|
||||
* the textarea should be on the end
|
||||
* when the label sits at the start.
|
||||
*/
|
||||
@include margin(0, $form-control-label-margin, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Label is on the left of the textarea in LTR and
|
||||
* on the right in RTL. Label also has a fixed width.
|
||||
@ -624,13 +554,6 @@
|
||||
@include margin(8px, 0px, 0px, 0px);
|
||||
}
|
||||
|
||||
:host(.textarea-label-placement-stacked) ::slotted([slot="start"]),
|
||||
:host(.textarea-label-placement-stacked) ::slotted([slot="end"]),
|
||||
:host(.textarea-label-placement-floating) ::slotted([slot="start"]),
|
||||
:host(.textarea-label-placement-floating) ::slotted([slot="end"]) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/**
|
||||
* This makes the label sit over the textarea
|
||||
* when the textarea is blurred and has no value.
|
||||
@ -671,26 +594,9 @@
|
||||
|
||||
.start-slot-wrapper,
|
||||
.end-slot-wrapper {
|
||||
@include padding(var(--padding-top), 0, var(--padding-bottom), 0);
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
::slotted([slot="start"]),
|
||||
::slotted([slot="end"]) {
|
||||
margin-top: 0; // ensure slot content is vertically aligned with label
|
||||
}
|
||||
|
||||
::slotted([slot="start"]:last-of-type) {
|
||||
margin-inline-end: $form-control-label-margin;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
::slotted([slot="end"]:first-of-type) {
|
||||
margin-inline-start: $form-control-label-margin;
|
||||
margin-inline-end: 0;
|
||||
}
|
55
core/src/components/textarea/textarea.ionic.outline.scss
Normal file
@ -0,0 +1,55 @@
|
||||
@use "../../themes/ionic/ionic.globals.scss" as globals;
|
||||
|
||||
// Textarea Fill: Outline
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
:host(.textarea-fill-outline) {
|
||||
--border-width: #{globals.$ionic-border-size-025};
|
||||
--border-color: #{globals.$ionic-color-neutral-500};
|
||||
}
|
||||
|
||||
// Textarea Fill: Outline, Textarea Wrapper
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
:host(.textarea-fill-outline) .textarea-wrapper-inner {
|
||||
/**
|
||||
* The border should be relative to the inner wrapper
|
||||
* so that it does not include the label.
|
||||
*/
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Textarea Fill: Outline, Outline Container
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
:host(.textarea-fill-outline) .textarea-outline {
|
||||
@include globals.position(0, 0, 0, 0);
|
||||
@include globals.border-radius(var(--border-radius));
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
border: var(--border-width) var(--border-style) var(--border-color);
|
||||
}
|
||||
|
||||
// Textarea Fill: Outline, Bottom Content
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The bottom content should never have
|
||||
* a border with the outline style.
|
||||
*/
|
||||
:host(.textarea-fill-outline) .textarea-bottom {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
// Textarea Fill: Outline, Native Textarea
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
:host(.textarea-fill-outline) textarea {
|
||||
margin-top: globals.$ionic-space-100;
|
||||
}
|
101
core/src/components/textarea/textarea.ionic.scss
Normal file
@ -0,0 +1,101 @@
|
||||
@use "../../themes/ionic/ionic.globals.scss" as globals;
|
||||
@use "./textarea.common";
|
||||
@use "./textarea.ionic.outline.scss" as outline;
|
||||
|
||||
// Ionic Textarea
|
||||
// --------------------------------------------------
|
||||
|
||||
: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};
|
||||
--placeholder-color: #{globals.$ionic-color-neutral-800};
|
||||
--placeholder-opacity: 1;
|
||||
--background: #{globals.$ionic-color-base-white};
|
||||
--padding-top: #{globals.$ionic-space-300};
|
||||
--padding-end: #{globals.$ionic-space-400};
|
||||
--padding-bottom: #{globals.$ionic-space-300};
|
||||
--padding-start: #{globals.$ionic-space-400};
|
||||
|
||||
@include globals.typography(globals.$ionic-body-md-regular);
|
||||
}
|
||||
|
||||
// Ionic Textarea Sizes
|
||||
// --------------------------------------------------
|
||||
|
||||
// Setting height to 0 allows it to collapse in height
|
||||
// instead of growing above the min height by default.
|
||||
.textarea-wrapper-inner {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
:host(.textarea-size-medium) .textarea-wrapper-inner {
|
||||
min-height: globals.$ionic-scale-3400;
|
||||
}
|
||||
|
||||
// Textarea Wrapper
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
.textarea-wrapper {
|
||||
gap: globals.$ionic-space-100;
|
||||
}
|
||||
|
||||
.textarea-wrapper-inner {
|
||||
@include globals.padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
|
||||
}
|
||||
|
||||
// Textarea Auto Grow
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
// The height should be auto only when auto-grow is enabled.
|
||||
:host([auto-grow]) .textarea-wrapper-inner {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
// The min and max height should be inherited if auto-grow is enabled.
|
||||
// This allows the textarea to grow and shrink as needed.
|
||||
:host([auto-grow]) .native-wrapper {
|
||||
min-height: inherit;
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
// Textarea Label
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
.label-text-wrapper {
|
||||
@include globals.typography(globals.$ionic-body-sm-medium);
|
||||
|
||||
color: globals.$ionic-color-neutral-1000;
|
||||
}
|
||||
|
||||
:host(.label-floating) .label-text-wrapper {
|
||||
@include globals.transform(none);
|
||||
}
|
||||
|
||||
// Textarea Slotted Content
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
ion-icon {
|
||||
color: globals.$ionic-color-neutral-800;
|
||||
|
||||
font-size: globals.$ionic-scale-400;
|
||||
}
|
||||
|
||||
.start-slot-wrapper,
|
||||
.end-slot-wrapper {
|
||||
margin-top: globals.$ionic-space-050;
|
||||
}
|
||||
|
||||
// Textarea Bottom Content
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
.textarea-bottom {
|
||||
@include globals.padding(globals.$ionic-space-100, var(--padding-end), null, var(--padding-start));
|
||||
@include globals.typography(globals.$ionic-body-sm-medium);
|
||||
}
|
||||
|
||||
.textarea-bottom .helper-text,
|
||||
.textarea-bottom .counter {
|
||||
color: globals.$ionic-color-neutral-800;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
@import "./textarea";
|
||||
@import "./textarea.native";
|
||||
@import "./textarea.vars";
|
||||
@import "./textarea.ios.vars";
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import "./textarea";
|
||||
@import "./textarea.native";
|
||||
@import "./textarea.vars";
|
||||
@import "./textarea.md.vars";
|
||||
@import "./textarea.md.solid";
|
||||
|
133
core/src/components/textarea/textarea.native.scss
Normal file
@ -0,0 +1,133 @@
|
||||
@use "../../themes/native/native.globals" as globals;
|
||||
@use "./textarea.common";
|
||||
|
||||
// Textarea - iOS and Material Design
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
min-height: 44px;
|
||||
|
||||
font-family: globals.$font-family-base;
|
||||
|
||||
z-index: globals.$z-index-item-input;
|
||||
}
|
||||
|
||||
// Textarea Wrapper
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Since the label sits on top of the element,
|
||||
* the component needs to be taller otherwise the
|
||||
* label will appear too close to the textarea text.
|
||||
* Also, floating and stacked labels should not
|
||||
* push the label down since it it
|
||||
* sits on top of the textarea.
|
||||
*/
|
||||
:host(.textarea-label-placement-floating),
|
||||
:host(.textarea-label-placement-stacked) {
|
||||
--padding-top: 0px;
|
||||
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
// Textarea Native
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
// This is required for auto-grow to work.
|
||||
.native-wrapper {
|
||||
min-height: inherit;
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
.native-wrapper textarea {
|
||||
@include globals.padding(var(--padding-top), 0px, var(--padding-bottom), 0px);
|
||||
}
|
||||
|
||||
// Textarea Bottom Content
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
.textarea-bottom {
|
||||
/**
|
||||
* The bottom content should take on the start and end
|
||||
* padding so it is always aligned with either the label
|
||||
* or the start of the textarea.
|
||||
*/
|
||||
@include globals.padding(5px, var(--padding-end), 0, var(--padding-start));
|
||||
|
||||
font-size: globals.dynamic-font(12px);
|
||||
}
|
||||
|
||||
// Textarea Max Length Counter
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
.textarea-bottom .counter {
|
||||
color: #{globals.$text-color-step-450};
|
||||
|
||||
padding-inline-start: 16px;
|
||||
}
|
||||
|
||||
// Textarea Label Placement - Start
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
:host(.textarea-label-placement-start) .label-text-wrapper {
|
||||
/**
|
||||
* The margin between the label and
|
||||
* the textarea should be on the end
|
||||
* when the label sits at the start.
|
||||
*/
|
||||
@include globals.margin(0, globals.$form-control-label-margin, 0, 0);
|
||||
}
|
||||
|
||||
// Textarea Label Placement - End
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The margin between the label and
|
||||
* the textarea should be on the start
|
||||
* when the label sits at the end.
|
||||
*/
|
||||
:host(.textarea-label-placement-end) .label-text-wrapper {
|
||||
@include globals.margin(0, 0, 0, globals.$form-control-label-margin);
|
||||
}
|
||||
|
||||
// Textarea Label Placement - Fixed
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
:host(.textarea-label-placement-fixed) .label-text-wrapper {
|
||||
/**
|
||||
* The margin between the label and
|
||||
* the textarea should be on the end
|
||||
* when the label sits at the start.
|
||||
*/
|
||||
@include globals.margin(0, globals.$form-control-label-margin, 0, 0);
|
||||
}
|
||||
|
||||
// Start / End Slots
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
.start-slot-wrapper,
|
||||
.end-slot-wrapper {
|
||||
@include globals.padding(var(--padding-top), 0, var(--padding-bottom), 0);
|
||||
}
|
||||
|
||||
:host(.textarea-label-placement-stacked) ::slotted([slot="start"]),
|
||||
:host(.textarea-label-placement-stacked) ::slotted([slot="end"]),
|
||||
:host(.textarea-label-placement-floating) ::slotted([slot="start"]),
|
||||
:host(.textarea-label-placement-floating) ::slotted([slot="end"]) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
::slotted([slot="start"]),
|
||||
::slotted([slot="end"]) {
|
||||
margin-top: 0; // ensure slot content is vertically aligned with label
|
||||
}
|
||||
|
||||
::slotted([slot="start"]:last-of-type) {
|
||||
margin-inline-end: globals.$form-control-label-margin;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
::slotted([slot="end"]:first-of-type) {
|
||||
margin-inline-start: globals.$form-control-label-margin;
|
||||
margin-inline-end: 0;
|
||||
}
|
@ -40,7 +40,7 @@ import type { TextareaChangeEventDetail, TextareaInputEventDetail } from './text
|
||||
styleUrls: {
|
||||
ios: 'textarea.ios.scss',
|
||||
md: 'textarea.md.scss',
|
||||
ionic: 'textarea.md.scss',
|
||||
ionic: 'textarea.ionic.scss',
|
||||
},
|
||||
scoped: true,
|
||||
})
|
||||
@ -248,6 +248,12 @@ export class Textarea implements ComponentInterface {
|
||||
*/
|
||||
@Prop() shape?: 'round';
|
||||
|
||||
/**
|
||||
* The size of the textarea. If "large", it will have an increased height. By default the
|
||||
* size is "medium". This property only applies to the `"ionic"` theme.
|
||||
*/
|
||||
@Prop() size?: 'small' | 'medium' | 'large' = 'medium';
|
||||
|
||||
/**
|
||||
* Update the native input element when the value changes
|
||||
*/
|
||||
@ -619,7 +625,7 @@ export class Textarea implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { inputId, disabled, fill, shape, labelPlacement, el, hasFocus } = this;
|
||||
const { inputId, disabled, fill, shape, size, labelPlacement, el, hasFocus } = this;
|
||||
const theme = getIonTheme(this);
|
||||
const value = this.getValue();
|
||||
const inItem = hostContext('ion-item', this.el);
|
||||
@ -657,6 +663,7 @@ export class Textarea implements ComponentInterface {
|
||||
'label-floating': labelShouldFloat,
|
||||
[`textarea-fill-${fill}`]: fill !== undefined,
|
||||
[`textarea-shape-${shape}`]: shape !== undefined,
|
||||
[`textarea-size-${size}`]: true,
|
||||
[`textarea-label-placement-${labelPlacement}`]: true,
|
||||
'textarea-disabled': disabled,
|
||||
})}
|
||||
@ -670,6 +677,18 @@ export class Textarea implements ComponentInterface {
|
||||
<label class="textarea-wrapper" htmlFor={inputId}>
|
||||
{this.renderLabelContainer()}
|
||||
<div class="textarea-wrapper-inner">
|
||||
{
|
||||
/**
|
||||
* For the ionic theme, we render the outline container here
|
||||
* instead of higher up, so it can be positioned relative to
|
||||
* the native wrapper instead of the <label> element or the
|
||||
* entire component. This allows the label text to be positioned
|
||||
* above the outline, while staying within the bounds of the
|
||||
* <label> element, ensuring that clicking the label text
|
||||
* focuses the textarea.
|
||||
*/
|
||||
theme === 'ionic' && fill === 'outline' && <div class="textarea-outline"></div>
|
||||
}
|
||||
{/**
|
||||
* Some elements have their own padding styles which may
|
||||
* interfere with slot content alignment (such as icon-
|
||||
|
@ -2221,7 +2221,7 @@ export declare interface IonText extends Components.IonText {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'shape', 'spellcheck', 'theme', 'value', 'wrap'],
|
||||
inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'shape', 'size', 'spellcheck', 'theme', 'value', 'wrap'],
|
||||
methods: ['setFocus', 'getInputElement']
|
||||
})
|
||||
@Component({
|
||||
@ -2229,7 +2229,7 @@ export declare interface IonText extends Components.IonText {}
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'shape', 'spellcheck', 'theme', 'value', 'wrap'],
|
||||
inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'shape', 'size', 'spellcheck', 'theme', 'value', 'wrap'],
|
||||
})
|
||||
export class IonTextarea {
|
||||
protected el: HTMLElement;
|
||||
|
@ -858,6 +858,7 @@ export const IonTextarea = /*@__PURE__*/ defineContainer<JSX.IonTextarea, JSX.Io
|
||||
'label',
|
||||
'labelPlacement',
|
||||
'shape',
|
||||
'size',
|
||||
'ionChange',
|
||||
'ionInput',
|
||||
'ionBlur',
|
||||
|