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(core): Appearance allow multiple modes #9042

Merged
merged 3 commits into from
Sep 16, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
TuiAppearance,
tuiAppearance,
tuiAppearanceFocus,
tuiAppearanceMode,
tuiAppearanceState,
} from '@taiga-ui/core/directives/appearance';
import {
Expand Down Expand Up @@ -104,7 +105,6 @@ export interface TuiCard {
],
host: {
'data-size': 'l',
'[attr.data-mode]': 'mode()',
'(mousedown)': 'onMouseDown($event)',
},
})
Expand Down Expand Up @@ -144,6 +144,7 @@ export class TuiInputCardGroup
protected readonly texts = toSignal(inject(TUI_INPUT_CARD_GROUP_TEXTS));
protected readonly open = tuiDropdownOpen();

protected readonly m = tuiAppearanceMode(this.mode);
protected readonly appearance = tuiAppearance(
inject(TUI_TEXTFIELD_OPTIONS).appearance,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@
transform: translateY(-0.7em);
}

:host([data-mode='invalid']) & {
:host([data-mode~='invalid']) & {
color: var(--tui-text-negative);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const ATTR_WITH_VALUES_TO_REPLACE: ReplacementAttributeValue[] = [
},
{
attrNames: ['[pseudoInvalid]'],
newAttrName: '[attr.data-mode]',
newAttrName: '[tuiAppearanceMode]',
withTagNames: hasPseudoInvalid,
valueReplacer: (condition) => `${condition} ? 'invalid' : null`,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ export function migrateButtonAppearance({
startOffset + templateOffset,
` ${appearanceInputName}="whiteblock"`,
);
recorder.insertLeft(startOffset + templateOffset, ' data-mode="checked"');
recorder.insertLeft(
startOffset + templateOffset,
' tuiAppearanceMode="checked"',
);
}
});

Expand All @@ -87,6 +90,6 @@ export function migrateButtonAppearance({
function addTodo(recorder: UpdateRecorder, templateOffset: number): void {
recorder.insertRight(
templateOffset,
'<!-- Taiga migration TODO: tuiButton "whiteblock-active" appearance is no longer available. Use \'appearance="whiteblock" data-mode="checked"\' -->\n',
'<!-- Taiga migration TODO: tuiButton "whiteblock-active" appearance is no longer available. Use \'appearance="whiteblock" tuiAppearanceMode="checked"\' -->\n',
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const TEMPLATE_BEFORE = `
`.trim();

const TEMPLATE_AFTER = `
<label tuiBlock><input tuiCheckbox type="checkbox" [(ngModel)]="value" [attr.data-mode]="invalid ? 'invalid' : null" [tuiAppearanceFocus]="pseudoFocus">Content</label>
<label tuiBlock><input tuiCheckbox type="checkbox" [(ngModel)]="value" [tuiAppearanceMode]="invalid ? 'invalid' : null" [tuiAppearanceFocus]="pseudoFocus">Content</label>

<form [formGroup]="testForm">
<label tuiBlock> <input tuiRadio type="radio"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,18 @@ const TEMPLATE_WITH_CONDITION_BEFORE = `
const TEMPLATE_AFTER = `
<button tuiButton></button>

<button tuiButton appearance="whiteblock" data-mode="checked"></button>
<button tuiButton appearance="whiteblock" tuiAppearanceMode="checked"></button>

<button tuiButton appearance="whiteblock" data-mode="checked"></button>
<button tuiButton appearance="whiteblock" tuiAppearanceMode="checked"></button>

<a tuiIconButton appearance="whiteblock" data-mode="checked"></a>
<a tuiIconButton appearance="whiteblock" tuiAppearanceMode="checked"></a>

<a tuiButton [appearance]="
'flat'
"></a>
`;

const TEMPLATE_WITH_CONDITION_AFTER = `<!-- Taiga migration TODO: tuiButton "whiteblock-active" appearance is no longer available. Use 'appearance="whiteblock" data-mode="checked"' -->
const TEMPLATE_WITH_CONDITION_AFTER = `<!-- Taiga migration TODO: tuiButton "whiteblock-active" appearance is no longer available. Use 'appearance="whiteblock" tuiAppearanceMode="checked"' -->

<a tuiButton [appearance]="
true ? appearance : 'flat'
Expand Down
5 changes: 2 additions & 3 deletions projects/core/components/textfield/select.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {TuiTextfieldBase, TuiTextfieldDirective} from './textfield.directive';
hostDirectives: [TuiNativeValidator, TuiAppearance],
host: {
'[id]': 'textfield.id',
'[attr.data-mode]': 'mode',
'[class._empty]': 'value === ""',
'(input)': '0',
'(focusin)': '0',
Expand All @@ -33,14 +32,14 @@ import {TuiTextfieldBase, TuiTextfieldDirective} from './textfield.directive';
'(keydown.meta.c)': 'onCopy()',
},
})
export class TuiSelect extends TuiTextfieldBase {
export class TuiSelect<T> extends TuiTextfieldBase<T> {
private readonly nav = inject(WA_NAVIGATOR);
private readonly control = inject(NgControl);

@Input()
public placeholder = '';

public override setValue(value: string): void {
public override setValue(value: T): void {
this.control.control?.setValue(value);
this.el.dispatchEvent(new Event('input', {bubbles: true}));
}
Expand Down
4 changes: 2 additions & 2 deletions projects/core/components/textfield/textfield.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class TuiTextfieldComponent<T> implements TuiDataListHost<T> {
private readonly focusedIn = tuiFocusedIn(tuiInjectElement());

@ContentChild(forwardRef(() => TuiTextfieldDirective))
protected readonly directive?: TuiTextfieldDirective;
protected readonly directive?: TuiTextfieldDirective<T>;

@ContentChild(forwardRef(() => TuiLabel), {read: ElementRef})
protected readonly label?: ElementRef<HTMLElement>;
Expand Down Expand Up @@ -114,7 +114,7 @@ export class TuiTextfieldComponent<T> implements TuiDataListHost<T> {
}

public handleOption(option: T): void {
this.directive?.setValue(this.stringify(option));
this.directive?.setValue(option);
this.open.set(false);
}

Expand Down
21 changes: 14 additions & 7 deletions projects/core/components/textfield/textfield.directive.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {computed, Directive, inject, Input, signal} from '@angular/core';
import {computed, Directive, inject, Input, type OnChanges, signal} from '@angular/core';
import {TuiNativeValidator} from '@taiga-ui/cdk/directives/native-validator';
import {tuiInjectElement} from '@taiga-ui/cdk/utils/dom';
import {
TuiAppearance,
tuiAppearance,
tuiAppearanceFocus,
tuiAppearanceMode,
tuiAppearanceState,
} from '@taiga-ui/core/directives/appearance';
import type {TuiInteractiveState} from '@taiga-ui/core/types';
Expand All @@ -13,18 +14,20 @@ import {TuiTextfieldComponent} from './textfield.component';
import {TUI_TEXTFIELD_OPTIONS} from './textfield.options';

@Directive()
export class TuiTextfieldBase {
export class TuiTextfieldBase<T> implements OnChanges {
// TODO: refactor to signal inputs after Angular update
private readonly focused = signal<boolean | null>(null);

protected readonly a = tuiAppearance(inject(TUI_TEXTFIELD_OPTIONS).appearance);
protected readonly s = tuiAppearanceState(null);
protected readonly m = tuiAppearanceMode(this.mode);
protected readonly f = tuiAppearanceFocus(
computed(() => this.focused() || this.textfield.focused()),
);

protected readonly textfield = inject(TuiTextfieldComponent);
protected readonly el = tuiInjectElement<HTMLInputElement>();
protected readonly textfield: TuiTextfieldComponent<T> =
inject(TuiTextfieldComponent);

@Input()
public readOnly = false;
Expand Down Expand Up @@ -58,8 +61,13 @@ export class TuiTextfieldBase {
return null;
}

public setValue(value: string | null): void {
this.el.value = value || '';
// TODO: refactor to signal inputs after Angular update
public ngOnChanges(): void {
this.m.set(this.mode);
}

public setValue(value: T | null): void {
this.el.value = value == null ? '' : this.textfield.stringify(value);
this.el.dispatchEvent(new Event('input', {bubbles: true}));
}
}
Expand All @@ -72,10 +80,9 @@ export class TuiTextfieldBase {
'[id]': 'textfield.id',
'[readOnly]': 'readOnly',
'[class._empty]': 'el.value === ""',
'[attr.data-mode]': 'mode',
'(input)': '0',
'(focusin)': '0',
'(focusout)': '0',
},
})
export class TuiTextfieldDirective extends TuiTextfieldBase {}
export class TuiTextfieldDirective<T> extends TuiTextfieldBase<T> {}
8 changes: 8 additions & 0 deletions projects/core/directives/appearance/appearance.bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type S = TuiInteractiveState | null;

type F = boolean | null;

type M = string | readonly string[] | null;

export function tuiAppearance(value: A | WritableSignal<A>): WritableSignal<A>;
export function tuiAppearance(value: Signal<A>): Signal<A>;
export function tuiAppearance(value: A | Signal<A>): Signal<A> {
Expand All @@ -27,3 +29,9 @@ export function tuiAppearanceFocus(value: Signal<F>): Signal<F>;
export function tuiAppearanceFocus(value: F | Signal<F>): Signal<F> {
return tuiDirectiveBinding(TuiAppearance, 'focus', value);
}

export function tuiAppearanceMode(value: M | WritableSignal<M>): WritableSignal<M>;
export function tuiAppearanceMode(value: Signal<M>): Signal<M>;
export function tuiAppearanceMode(value: M | Signal<M>): Signal<M> {
return tuiDirectiveBinding(TuiAppearance, 'mode', value);
}
13 changes: 12 additions & 1 deletion projects/core/directives/appearance/appearance.directive.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {
ChangeDetectionStrategy,
Component,
computed,
Directive,
inject,
Input,
signal,
ViewEncapsulation,
} from '@angular/core';
import {tuiWithStyles} from '@taiga-ui/cdk/utils/miscellaneous';
import {tuiIsString, tuiWithStyles} from '@taiga-ui/cdk/utils/miscellaneous';
import type {TuiInteractiveState} from '@taiga-ui/core/types';

import {TUI_APPEARANCE_OPTIONS, type TuiAppearanceOptions} from './appearance.options';
Expand All @@ -32,15 +33,20 @@ class TuiAppearanceStyles {}
'[attr.data-appearance]': 'appearance()',
'[attr.data-state]': 'state()',
'[attr.data-focus]': 'focus()',
'[attr.data-mode]': 'modes()',
},
})
export class TuiAppearance {
protected readonly nothing = tuiWithStyles(TuiAppearanceStyles);
protected readonly modes = computed((mode = this.mode()) =>
!mode || tuiIsString(mode) ? mode : mode.join(' '),
);

// TODO: refactor to signal inputs after Angular update
public readonly appearance = signal(inject(TUI_APPEARANCE_OPTIONS).appearance);
public readonly state = signal<TuiInteractiveState | null>(null);
public readonly focus = signal<boolean | null>(null);
public readonly mode = signal<string | readonly string[] | null>(null);

@Input()
public set tuiAppearance(appearance: TuiAppearanceOptions['appearance']) {
Expand All @@ -56,4 +62,9 @@ export class TuiAppearance {
public set tuiAppearanceFocus(focus: boolean | null) {
this.focus.set(focus);
}

@Input()
public set tuiAppearanceMode(mode: string | readonly string[] | null) {
this.mode.set(mode);
}
}
1 change: 1 addition & 0 deletions projects/core/directives/appearance/with-appearance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {TuiAppearance} from './appearance.directive';
'tuiAppearance: appearance',
'tuiAppearanceState',
'tuiAppearanceFocus',
'tuiAppearanceMode',
],
},
],
Expand Down
2 changes: 1 addition & 1 deletion projects/core/styles/components/appearance.less
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* data-appearance — current appearance
* data-state — manual interactive state override ('active' | 'disabled' | 'hover')
* data-focus — manual :focus-visible state override
* data-mode — arbitrary manual mode like 'checked', 'invalid' or 'readonly'
* data-mode — arbitrary manual mode like 'checked', 'invalid' or 'checked invalid'
*
* @example
* <button tuiAppearance data-appearance='primary'></button>
Expand Down
11 changes: 6 additions & 5 deletions projects/core/styles/components/group.less
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@
z-index: 0;
}

&:invalid,
&[data-mode='invalid'] {
&:invalid:not([data-mode]),
&[data-mode~='invalid'] {
z-index: 2;

--t-group-clip: none;
}

&:has(:invalid),
&:has([data-mode='invalid']) {
&:has(:invalid:not([data-mode])),
&:has([data-mode~='invalid']) {
z-index: 2;

--t-group-clip: none;
Expand All @@ -64,7 +64,8 @@
--t-group-clip: none;
}

&[data-mode='checked'] {
&:checked:not([data-mode]),
&[data-mode~='checked'] {
z-index: 4;

--t-group-clip: none;
Expand Down
10 changes: 5 additions & 5 deletions projects/core/styles/components/textfield.less
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ tui-textfield {
color: var(--tui-text-secondary);

&:has(input:read-only),
&:has(select[data-mode='readonly']) {
&:has(select[data-mode~='readonly']) {
color: var(--tui-text-tertiary);
}
}
Expand Down Expand Up @@ -219,17 +219,17 @@ tui-textfield {
transform: translateY(-0.7em);
}

&:not(:disabled)[data-mode='invalid'] ~ label,
&:not(:disabled)[data-mode~='invalid'] ~ label,
&:invalid:not(:disabled):not([data-mode]) ~ label {
color: var(--tui-text-negative);
}

&:not(:disabled):not([data-mode='readonly']) ~ .t-content .t-clear {
&:not(:disabled):not([data-mode~='readonly']) ~ .t-content .t-clear {
display: flex;
}
}

&:not([data-mode='readonly']) {
&:not([data-mode~='readonly']) {
.appearance-focus({
&::placeholder,
&._empty {
Expand Down Expand Up @@ -270,7 +270,7 @@ tui-textfield {
color: var(--tui-text-secondary);
}

select:not([data-mode='readonly']) {
select:not([data-mode~='readonly']) {
cursor: pointer;
}

Expand Down
11 changes: 6 additions & 5 deletions projects/core/styles/theme/appearance/outline.less
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
color: var(--tui-text-action);
box-shadow: inset 0 0 0 1px var(--t-bs);

&:checked,
&[data-mode='checked'] {
&:checked:not([data-mode]),
&[data-mode~='checked'] {
--t-bs: var(--tui-background-accent-1);

box-shadow: inset 0 0 0 2px var(--t-bs);
Expand All @@ -22,12 +22,13 @@
});
}

&:invalid {
&:invalid:not([data-mode]),
&[data-mode~='invalid'] {
box-shadow: inset 0 0 0 1px var(--tui-status-negative-pale-hover);
}

&:checked:invalid,
&[data-mode='checked']:invalid {
&:checked:invalid:not([data-mode]),
&[data-mode~='checked'][data-mode~='invalid'] {
box-shadow: inset 0 0 0 2px var(--tui-status-negative);
}

Expand Down
3 changes: 2 additions & 1 deletion projects/core/styles/theme/appearance/primary.less
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
background: var(--t-bg);
color: var(--tui-text-primary-on-accent-1);

&:invalid {
&:invalid:not([data-mode]),
&[data-mode~='invalid'] {
background: var(--tui-status-negative);
}

Expand Down
Loading
Loading