Skip to content

Commit

Permalink
fix(input): style actions
Browse files Browse the repository at this point in the history
  • Loading branch information
aesteves60 authored and dpellier committed Jul 29, 2024
1 parent 0dbf82e commit 4a1bc1d
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,98 +3,122 @@
@use '../../../../../style/input';

:host(.ods-input) {
display: inline-block;
position: relative;
width: 100%;
height: 100%;
}

$ods-input-button-right: 4px;
$ods-input-button-top: -1px;
$ods-input-actions-button-width: 24px;
$ods-input-actions-padding-right: 4px;
$ods-input-input-padding-right: calc($ods-input-actions-button-width + $ods-input-actions-padding-right);

.ods-input {
&__button {
$button: &;

&__actions {
display: inline-flex;
position: absolute;
top: $ods-input-button-top;
right: $ods-input-button-right;
border: none;
border-radius: 2px;
background: none;
cursor: pointer;
color: theme.$ods-color-primary-500;

&:focus-visible {
@include focus.ods-focus();
}

&:hover {
background-color: theme.$ods-color-primary-100;
}

&:active {
background-color: theme.$ods-color-primary-200;
top: 0;
right: 0;
bottom: 0;
align-items: center;
justify-content: center;
padding-right: 4px;

&__clearable, &__toggle-mask, &__spinner {
border: none;
border-radius: 2px;
background: none;
cursor: pointer;
}

&__clearable {
color: theme.$ods-color-neutral-600;
color: var(--ods-color-neutral-600);

&:hover {
background-color: var(--ods-color-neutral-100);
}

&:has(+ #{$button}) {
right: 30px;
&:active {
background-color: var(--ods-color-neutral-200);
}
}

&__toggle-mask {
color: var(--ods-color-primary-500);

&:hover {
background-color: theme.$ods-color-neutral-100;
background-color: var(--ods-color-primary-100);
}

&:active {
background-color: theme.$ods-color-neutral-200;
background-color: var(--ods-color-primary-200);
}
}

/* stylelint-disable-next-line no-descending-specificity */
&__clearable, &__toggle-mask {
width: $ods-input-actions-button-width;
height: 24px;

&:focus-visible {
@include focus.ods-focus();
}
}

&__spinner {
height: unset;

&::part(spinner) {
width: 1rem;
height: 1rem;
}
}

}

&__input {
$input: &;

@include input.ods-input();

&:not(#{$input}--error, #{$input}--disabled) {
&:not(#{$input}--error, :disabled) {
&:focus {
border-color: theme.$ods-form-element-border-color-focused;
border-color: var(--ods-form-element-border-color-focused);
}

&:hover {
border-color: theme.$ods-form-element-border-color-hover;
border-color: var(--ods-form-element-border-color-hover);
}
}

&--disabled {
background-color: theme.$ods-color-neutral-100;
cursor: not-allowed;
user-select: none;

~ .ods-input__button {
&:disabled {
~ .ods-input__actions {
cursor: not-allowed;

.ods-input__actions__clearable, .ods-input__actions__toggle-mask {
cursor: not-allowed;
}
}
}

&--clearable {
padding-right: $ods-input-input-padding-right;
}

&--error {
border-color: theme.$ods-form-element-border-color-critical;
border-color: var(--ods-form-element-border-color-critical);
}

&[type="number"] {
appearance: textfield;
&--toggle-mask {
padding-right: $ods-input-input-padding-right;
}
}

&__spinner {
position: absolute;
top: $ods-input-button-top;
right: $ods-input-button-right;
padding: 0 4px;
&.ods-input__input--clearable.ods-input__input--toggle-mask {
padding-right: calc($ods-input-input-padding-right * 2);
}

&::part(spinner) {
width: 1rem;
height: 1rem;
&[type="number"] {
appearance: textfield;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AttachInternals, Component, Element, Event, type EventEmitter, type Fun
import { ODS_ICON_NAME } from '../../../../icon/src';
import { ODS_INPUT_TYPE, type OdsInputType } from '../../constants/input-type';
import { handleKeySpace, isPassword, setFormValue } from '../../controller/ods-input';
import { type OdsInputEventValueChange } from '../../interfaces/events';
import { type OdsInputValueChangeEventDetail } from '../../interfaces/events';

@Component({
formAssociated: true,
Expand All @@ -18,7 +18,6 @@ export class OdsInput {
@AttachInternals() private internals!: ElementInternals;

@State() isPassword = false;
@State() internalError = false;

@Prop({ reflect: true }) public ariaLabel: HTMLElement['ariaLabel'] = null;
@Prop({ reflect: true }) public ariaLabelledby?: string;
Expand All @@ -39,20 +38,22 @@ export class OdsInput {
@Prop({ reflect: true }) public placeholder?: string;
@Prop({ reflect: true }) public step?: number;
@Prop({ reflect: true }) public type: OdsInputType = ODS_INPUT_TYPE.text;
@Prop({ mutable: true, reflect: true }) public value?: string | number = undefined;
@Prop({ mutable: true, reflect: true }) public value: string | number | null = null;

@Event() odsBlur!: EventEmitter<void>;
@Event() odsClear!: EventEmitter<void>;
@Event() odsFocus!: EventEmitter<void>;
@Event() odsToggleMask!: EventEmitter<void>;
@Event() odsReset!: EventEmitter<void>;
@Event() odsValueChange!: EventEmitter<OdsInputEventValueChange>;
@Event() odsValueChange!: EventEmitter<OdsInputValueChangeEventDetail>;

@Method()
async clear(): Promise<void> {
if (this.isDisabled) {
return;
}
this.value = undefined;
this.value = null;
this.inputEl?.focus();
this.odsClear.emit();
return;
}
Expand All @@ -77,7 +78,7 @@ export class OdsInput {
if (this.isDisabled) {
return;
}
this.value = this.defaultValue ?? undefined;
this.value = this.defaultValue ?? null;
this.odsReset.emit();
return;
}
Expand All @@ -87,15 +88,9 @@ export class OdsInput {
this.isPassword = isPassword(this.isMasked);
}

@Watch('error')
onErrorChange(): void {
this.internalError = !this.inputEl?.validity?.valid ?? this.hasError;
}

@Watch('value')
onValueChange(value: string | number, oldValue?: string | number): void {
setFormValue(this.internals, this.value);
this.onErrorChange();
this.odsValueChange.emit({
name: this.name,
oldValue: oldValue,
Expand All @@ -107,7 +102,7 @@ export class OdsInput {
componentWillLoad(): void {
this.onMaskedChange();
if (!this.value) {
this.value = this.defaultValue;
this.value = this.defaultValue ?? null;
}
setFormValue(this.internals, this.value);
}
Expand All @@ -120,33 +115,24 @@ export class OdsInput {
if (this.isDisabled) {
return;
}
this.value = this.inputEl?.value;
this.value = this.inputEl?.value ?? null;
return;
}

private renderButtonIcon(icon: ODS_ICON_NAME, callback: () => Promise<void>, customClass: string = ''): FunctionalComponent {
return (
<button
class= { `ods-input__button ${customClass}` }
disabled= { this.isDisabled }
onClick={ callback }
onKeyUp={ (event: KeyboardEvent): Promise<void> => handleKeySpace(event, this.isDisabled, callback) }>
<ods-icon name={ icon }>
</ods-icon>
</button>
);
}

render(): FunctionalComponent {
const hasClearableIcon = this.isClearable && !this.isLoading && !!this.value;
const hasToggleMaskIcon = this.isPassword && !this.isLoading;

return (
<Host class="ods-input">
<input
aria-label={ this.ariaLabel }
aria-labelledby={ this.ariaLabelledby }
class={{
'ods-input__input': true,
'ods-input__input--disabled': this.isDisabled,
'ods-input__input--error': this.internalError || this.hasError,
'ods-input__input--clearable': hasClearableIcon,
'ods-input__input--error': this.hasError,
'ods-input__input--toggle-mask': hasToggleMaskIcon,
}}
disabled={ this.isDisabled }
max={ this.max }
Expand All @@ -155,6 +141,7 @@ export class OdsInput {
minlength={ this.minlength }
name={ this.name }
onBlur={ (): CustomEvent<void> => this.odsBlur.emit() }
onFocus={ (): CustomEvent<void> => this.odsFocus.emit() }
onInput={ (): void => this.onInput() }
pattern={ this.pattern }
part="input"
Expand All @@ -166,17 +153,33 @@ export class OdsInput {
type={ this.isPassword && this.isMasked ? 'password' : this.type }
value={ this.value?.toString() || '' } />

{
this.isLoading && <ods-spinner class="ods-input__spinner"></ods-spinner>
}

{
this.isClearable && !this.isLoading && this.value && this.renderButtonIcon(ODS_ICON_NAME.cross, this.clear.bind(this), 'ods-input__button__clearable')
}

{
this.isPassword && !this.isLoading && this.renderButtonIcon(this.isMasked ? ODS_ICON_NAME.eyeClose : ODS_ICON_NAME.eyeOpen, this.toggleMask.bind(this))
}
<div class="ods-input__actions">
{
this.isLoading && <ods-spinner class="ods-input__actions__spinner"></ods-spinner>
}
{
hasClearableIcon &&
<button
class="ods-input__actions__clearable"
disabled={ this.isDisabled }
onClick={ this.clear.bind(this) }
onKeyUp={ (event: KeyboardEvent): Promise<void> => handleKeySpace(event, this.isDisabled, this.clear.bind(this)) }>
<ods-icon name={ ODS_ICON_NAME.cross }>
</ods-icon>
</button>
}
{
hasToggleMaskIcon &&
<button
class="ods-input__actions__toggle-mask"
disabled={ this.isDisabled }
onClick={ this.toggleMask.bind(this) }
onKeyUp={ (event: KeyboardEvent): Promise<void> => handleKeySpace(event, this.isDisabled, this.toggleMask.bind(this)) }>
<ods-icon name={ this.isMasked ? ODS_ICON_NAME.eyeClose : ODS_ICON_NAME.eyeOpen }>
</ods-icon>
</button>
}
</div>
</Host>
);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/ods/src/components/input/src/controller/ods-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ async function handleKeySpace(event: KeyboardEvent, isDisabled: boolean, callbac
}
}

function setFormValue(internals: ElementInternals, value?: number | string): void {
internals?.setFormValue?.(value?.toString() ?? '');
function setFormValue(internals: ElementInternals, value: number | string | null): void {
internals.setFormValue(value?.toString() ?? '');
}

function isPassword(isMasked?: boolean): boolean {
Expand Down
2 changes: 1 addition & 1 deletion packages/ods/src/components/input/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { OdsInput } from './components/ods-input/ods-input';
export { ODS_INPUT_TYPE, ODS_INPUT_TYPES, type OdsInputType } from './constants/input-type';
export { type OdsInputEventValueChange } from './interfaces/events';
export { type OdsInputValueChangeEvent, type OdsInputValueChangeEventDetail } from './interfaces/events';
7 changes: 5 additions & 2 deletions packages/ods/src/components/input/src/interfaces/events.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
interface OdsInputEventValueChange {
interface OdsInputValueChangeEventDetail {
name: string;
oldValue?: string | number;
validity?: ValidityState;
value: string | number;
}

type OdsInputValueChangeEvent = CustomEvent<OdsInputValueChangeEventDetail>;

export {
type OdsInputEventValueChange,
type OdsInputValueChangeEvent,
type OdsInputValueChangeEventDetail,
};
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('ods-input behaviour', () => {
await part.type('some text');
await page.waitForChanges();

expect(await el.getProperty('value')).toBe(undefined);
expect(await el.getProperty('value')).toBe(null);
expect(odsValueChangeSpy).not.toHaveReceivedEvent();
});
});
Expand Down
Loading

0 comments on commit 4a1bc1d

Please sign in to comment.