Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/inputs-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/inputs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 69 additions & 0 deletions demo/src/app/index/pages/inputs/inputs.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ <h2>inputs</h2>
<ion-input label="Floating label" labelPlacement="floating" placeholder="Enter text"></ion-input>
</ion-item>

<ion-item>
<ion-input label="Helper text" placeholder="Enter text" helperText="This is helper text"></ion-input>
</ion-item>

<ion-item>
<ion-input class="ion-invalid ion-touched" label="Error text" placeholder="Enter text" errorText="This is error text"></ion-input>
</ion-item>

<ion-item>
<ion-input type="password" label="Password" value="NeverGonnaGiveYouUp">
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
Expand All @@ -62,6 +70,67 @@ <h2>inputs</h2>
<ion-item>
<ion-textarea label="Floating label" labelPlacement="floating" placeholder="Enter textarea"></ion-textarea>
</ion-item>
<ion-item>
<ion-textarea label="Helper text" placeholder="Enter textarea" helperText="This is helper text"></ion-textarea>
</ion-item>
<ion-item>
<ion-textarea
class="ion-invalid ion-touched"
label="Error text"
placeholder="Enter textarea"
errorText="This is error text"
></ion-textarea>
</ion-item>
</ion-item-group>
</ion-list>

<ion-list [inset]="true">
<ion-item-group>
<ion-item>
<ion-input fill="outline" label="Outline input" placeholder="Enter text"></ion-input>
</ion-item>
</ion-item-group>
<ion-item-group>
<ion-item>
<ion-textarea fill="outline" label="Outline input" placeholder="Enter text"></ion-textarea>
</ion-item>
</ion-item-group>
<ion-item-group>
<ion-item>
<ion-input fill="outline" label="Outline input" placeholder="Enter text"></ion-input>
</ion-item>
<ion-item>
<ion-input fill="outline" label="Outline helper text" placeholder="Enter text" helperText="This is helper text"></ion-input>
</ion-item>
<ion-item>
<ion-input
class="ion-invalid ion-touched"
fill="outline"
label="Outline error text"
placeholder="Enter text"
errorText="This is error text"
></ion-input>
</ion-item>
<ion-item>
<ion-textarea fill="outline" label="Outline textarea" placeholder="Enter textarea"></ion-textarea>
</ion-item>
<ion-item>
<ion-textarea
fill="outline"
label="Outline helper text"
placeholder="Enter textarea"
helperText="This is helper text"
></ion-textarea>
</ion-item>
<ion-item>
<ion-textarea
class="ion-invalid ion-touched"
fill="outline"
label="Outline error text"
placeholder="Enter textarea"
errorText="This is error text"
></ion-textarea>
</ion-item>
</ion-item-group>
</ion-list>
</ion-content>
34 changes: 34 additions & 0 deletions demo/src/app/index/pages/inputs/inputs.page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,38 @@ describe('InputsPage', () => {
it('should create', () => {
expect(component).toBeTruthy();
});

it('should render helper and error input patterns', () => {
const element = fixture.nativeElement as HTMLElement;
const helperInput = element.querySelector('ion-input[helpertext]');
const errorInput = element.querySelector('ion-input[errortext]');
const helperTextarea = element.querySelector('ion-textarea[helpertext]');
const errorTextarea = element.querySelector('ion-textarea[errortext]');

expect(helperInput?.getAttribute('helpertext')).toBe('This is helper text');
expect(errorInput?.getAttribute('errortext')).toBe('This is error text');
expect(errorInput?.classList.contains('ion-invalid')).toBe(true);
expect(errorInput?.classList.contains('ion-touched')).toBe(true);
expect(helperTextarea?.getAttribute('helpertext')).toBe('This is helper text');
expect(errorTextarea?.getAttribute('errortext')).toBe('This is error text');
expect(errorTextarea?.classList.contains('ion-invalid')).toBe(true);
expect(errorTextarea?.classList.contains('ion-touched')).toBe(true);
});

it('should render outlined helper and error patterns', () => {
const element = fixture.nativeElement as HTMLElement;
const outlinedInputs = element.querySelectorAll('ion-input[fill="outline"]');
const outlinedTextareas = element.querySelectorAll('ion-textarea[fill="outline"]');

expect(outlinedInputs).toHaveLength(4);
expect(outlinedInputs[2].getAttribute('helpertext')).toBe('This is helper text');
expect(outlinedInputs[3].getAttribute('errortext')).toBe('This is error text');
expect(outlinedInputs[3].classList.contains('ion-invalid')).toBe(true);
expect(outlinedInputs[3].classList.contains('ion-touched')).toBe(true);
expect(outlinedTextareas).toHaveLength(4);
expect(outlinedTextareas[2].getAttribute('helpertext')).toBe('This is helper text');
expect(outlinedTextareas[3].getAttribute('errortext')).toBe('This is error text');
expect(outlinedTextareas[3].classList.contains('ion-invalid')).toBe(true);
expect(outlinedTextareas[3].classList.contains('ion-touched')).toBe(true);
});
});
6 changes: 6 additions & 0 deletions demo/src/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ html.ionic-v8 {
&:is(.textarea-label-placement-stacked, .textarea-label-placement-floating) {
block-size: 68px;
}

&.textarea-fill-outline .textarea-wrapper {
block-size: 124px;
box-sizing: border-box;
padding-top: 12px;
}
}
}

Expand Down
82 changes: 82 additions & 0 deletions src/styles/components/ion-inputs.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
@mixin outline-field($control) {
--placeholder-opacity: 1;
--padding-top: 12px;
--padding-bottom: 0;
margin-bottom: 12px;

.#{$control}-control,
.#{$control}-wrapper {
flex-direction: column;
}

.label-text-wrapper {
max-width: 100%;
margin: 0 2px 4px;
padding: 0;
font-size: 0.875rem;
line-height: 1.25rem;
transform: none;
}

.native-wrapper {
min-height: 48px;
border: 1px solid
var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-250, var(--ion-background-color-step-250, #c8c7cc))));
border-radius: 10px;
background: var(--ion-background-color, #fff);
transition: border-color 150ms ease;
}

.#{$control}-bottom {
inset-inline: 0;
padding: 4px 2px 0;
border: 0;
font-size: 0.75rem;
line-height: 1rem;
}

&.has-focus {
.label-text-wrapper {
color: var(--highlight-color-focused);
}

.native-wrapper {
border-color: var(--highlight-color-focused);
}
}

&.ion-touched.ion-invalid .native-wrapper {
border-color: var(--highlight-color-invalid);
}

&.has-focus.ion-touched.ion-invalid .label-text-wrapper {
color: var(--highlight-color-invalid);
}
}

ion-input.ios:not(.ios26-disabled) {
&.input-fill-outline {
@include outline-field('input');

.native-input {
min-height: 46px;
padding-inline: 12px;
}
}
}

ion-textarea.ios:not(.ios26-disabled) {
&.textarea-fill-outline {
@include outline-field('textarea');

.native-wrapper {
min-height: 88px;
}

.native-textarea {
min-height: 86px;
padding-block: 12px;
padding-inline: 12px;
}
}
}
1 change: 1 addition & 0 deletions src/styles/ionic-theme-ios26.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@use 'components/ion-content';
@use 'components/ion-datetime';
@use 'components/ion-fab';
@use 'components/ion-inputs';
@use 'components/ion-list';
@use 'components/ion-loading';
@use 'components/ion-menu';
Expand Down
12 changes: 12 additions & 0 deletions src/styles/utils/structured-list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@
}

ion-item {
&:has(ion-input.input-fill-outline),
&:has(ion-textarea.textarea-fill-outline),
&:has(ion-input .input-bottom > :is(.helper-text:not(:empty), .error-text:not(:empty))),
&:has(ion-textarea .textarea-bottom > :is(.helper-text:not(:empty), .error-text:not(:empty))) {
--inner-border-width: 0;
--inner-padding-bottom: 4px;
}
ion-input .input-bottom:not(:has(> :is(.helper-text:not(:empty), .error-text:not(:empty)))),
ion-textarea .textarea-bottom:not(:has(> :is(.helper-text:not(:empty), .error-text:not(:empty)))) {
display: none;
}

&:has(> ion-label:not([slot])):has(> ion-note:not([slot])) {
&::part(container) {
flex-direction: column;
Expand Down
Loading