Skip to content

Commit

Permalink
feat: 🎸 WIP checkbox and group
Browse files Browse the repository at this point in the history
  • Loading branch information
TomTomB committed Jan 17, 2023
1 parent 4e46011 commit 0078825
Show file tree
Hide file tree
Showing 43 changed files with 437 additions and 37 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ node_modules
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/ethdk.code-snippets

# misc
/.sass-cache
Expand Down
77 changes: 77 additions & 0 deletions .vscode/ethdk.code-snippets
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"Export public api": {
"scope": "typescript",
"prefix": "et-expub",
"body": ["export * from './public-api'"],
"description": "Export public api"
},
"Boolean input property": {
"scope": "typescript",
"prefix": "et-bool-prop",
"body": [
" @Input()",
" get ${1}(): boolean {",
" return this._${1};",
" }",
" set ${1}(value: BooleanInput) {",
" this._${1} = coerceBooleanProperty(value);",
" }",
" private _${1} = ${0:false};"
],
"description": "Add boolean input property"
},
"Number input property": {
"scope": "typescript",
"prefix": "et-number-prop",
"body": [
" @Input()",
" get ${1}(): number {",
" return this._${1};",
" }",
" set ${1}(value: NumberInput) {",
" this._${1} = coerceNumberProperty(value);",
" }",
" private _${1} = ${0:0};"
],
"description": "Add number input property"
},
"Destroy": {
"scope": "typescript",
"prefix": "et-destroy",
"body": "private readonly _destroy$ = inject(DestroyService).destroy$"
},
"Component": {
"scope": "typescript",
"prefix": "et-compoent",
"body": [
"import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';",
"",
"@Component({",
" selector: 'et-${1}',",
" templateUrl: './${1}.component.html',",
" styleUrls: ['./${1}.component.scss'],",
" standalone: true,",
" changeDetection: ChangeDetectionStrategy.OnPush,",
" encapsulation: ViewEncapsulation.None,",
" host: {",
" class: 'et-${1}',",
" },",
"})",
"export class ${0}Component {}"
],
"description": "Add component boilerplate"
},
"HostDirective": {
"scope": "typescript",
"prefix": "et-host-directive",
"body": [
"import { Directive } from '@angular/core';",
"",
"@Directive({",
" standalone: true,",
"})",
"export class ${0}Directive {}"
],
"description": "Add host directive boilerplate"
}
}
11 changes: 11 additions & 0 deletions apps/sandbox/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,14 @@
</button>

<button (click)="disabled = !disabled" et-button type="button">toggle disabled</button>

<et-checkbox></et-checkbox>
<et-checkbox checked></et-checkbox>
<et-checkbox indeterminate></et-checkbox>

<et-checkbox-group>
<et-checkbox etCheckboxGroupControl></et-checkbox>
<et-checkbox></et-checkbox>
<et-checkbox></et-checkbox>
<et-checkbox></et-checkbox>
</et-checkbox-group>
6 changes: 6 additions & 0 deletions apps/sandbox/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
BracketComponent,
BRACKET_MATCH_ID_TOKEN,
ButtonComponent,
CheckboxComponent,
CheckboxGroupComponent,
CheckboxGroupControlDirective,
createBracketConfig,
DialogService,
QueryButtonComponent,
Expand Down Expand Up @@ -88,6 +91,9 @@ export class TestCompComponent {
StructuredDataComponent,
ButtonComponent,
QueryButtonComponent,
CheckboxComponent,
CheckboxGroupComponent,
CheckboxGroupControlDirective,
],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AsyncPipe, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject, ViewEncapsulation } from '@angular/core';
import { LetDirective } from '@ethlete/core';
import { QueryDirective } from '@ethlete/query';
import { ButtonDirective } from '../../../../directives';
import { ButtonDirective } from '../../directives';

@Component({
selector: '[et-button]',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AsyncPipe, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject, ViewEncapsulation } from '@angular/core';
import { LetDirective } from '@ethlete/core';
import { QueryDirective } from '@ethlete/query';
import { ButtonDirective, QueryButtonDirective } from '../../../../directives';
import { ButtonDirective, QueryButtonDirective } from '../../directives';

@Component({
selector: '[et-query-button]',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type ButtonType = 'button' | 'submit' | 'reset' | 'menu';
@Directive({
standalone: true,
providers: [DestroyService],
exportAs: 'etButton',
})
export class ButtonDirective {
readonly isButton = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement.tagName === 'BUTTON';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import {
AnyQuery,
AnyQueryCreatorCollection,
AnyQueryOfCreatorCollection,
isQuery,
extractQuery,
isQueryStateFailure,
isQueryStateLoading,
isQueryStateSuccess,
} from '@ethlete/query';
import { BehaviorSubject, combineLatest, map, skip, takeUntil } from 'rxjs';
import { BehaviorSubject, combineLatest, map, of, skip, switchMap, takeUntil } from 'rxjs';
import { ButtonDirective } from '../button';

const CLASSES = {
Expand All @@ -21,6 +21,7 @@ const CLASSES = {
@Directive({
standalone: true,
providers: [DestroyService],
exportAs: 'etQueryButton',
})
export class QueryButtonDirective {
private readonly _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
Expand All @@ -34,16 +35,6 @@ export class QueryButtonDirective {
readonly didLoadOnce$ = new BehaviorSubject(false);
readonly isLoading$ = new BehaviorSubject(false);

private readonly _bindings = createReactiveBindings({
attribute: ['disabled', 'aria-disabled'],
observable: combineLatest([this._button.disabled$, this.showFailure$, this.showSuccess$, this.isLoading$]).pipe(
map(([disabled, showFailure, showSuccess, isLoading]) => ({
render: disabled || showFailure || showSuccess || isLoading,
value: true,
})),
),
});

@Input()
get query() {
return this._query$.value;
Expand All @@ -61,33 +52,14 @@ export class QueryButtonDirective {
classList.remove(CLASSES.failure);
classList.remove(CLASSES.loading);

this._bindings.remove('aria-live');
this._bindings.reset();

if (this._query$.value === null) {
const query = extractQuery(this._query$.value);

if (!query) {
return;
}

const query = isQuery(this._query$.value) ? this._query$.value : this._query$.value.query;

this._bindings.push({
attribute: ['aria-live'],
observable: combineLatest([query.state$, this.didLoadOnce$]).pipe(
map(([state, didLoadOnce]) => {
let value = 'off';

if (isQueryStateLoading(state) || isQueryStateSuccess(state) || isQueryStateFailure(state) || didLoadOnce) {
value = 'assertive';
}

return {
render: true,
value,
};
}),
),
});

query.state$.pipe(takeUntil(this._destroy$), takeUntil(this._query$.pipe(skip(1)))).subscribe((state) => {
if (isQueryStateLoading(state)) {
this.isLoading$.next(true);
Expand Down Expand Up @@ -126,6 +98,41 @@ export class QueryButtonDirective {
AnyQuery | AnyQueryOfCreatorCollection<AnyQueryCreatorCollection> | null
>(null);

private readonly _bindings = createReactiveBindings(
{
attribute: ['disabled', 'aria-disabled'],
observable: combineLatest([this._button.disabled$, this.showFailure$, this.showSuccess$, this.isLoading$]).pipe(
map(([disabled, showFailure, showSuccess, isLoading]) => ({
render: disabled || showFailure || showSuccess || isLoading,
value: true,
})),
),
},
{
attribute: ['aria-live'],
observable: combineLatest([
this.query$.pipe(
map((q) => extractQuery(q)),
switchMap((q) => q?.state$ ?? of(null)),
),
this.didLoadOnce$,
]).pipe(
map(([state, didLoadOnce]) => {
let value = 'off';

if (isQueryStateLoading(state) || isQueryStateSuccess(state) || isQueryStateFailure(state) || didLoadOnce) {
value = 'assertive';
}

return {
render: true,
value,
};
}),
),
},
);

constructor() {
this._button._removeDisabledBindings();
}
Expand Down
1 change: 1 addition & 0 deletions libs/components/src/lib/components/button/public-api.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './components/public-api';
export * from './directives/public-api';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<ng-content />
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
AfterContentInit,
ChangeDetectionStrategy,
Component,
ContentChild,
ContentChildren,
QueryList,
ViewEncapsulation,
} from '@angular/core';
import { CheckboxGroupControlDirective, CHECKBOX_GROUP_CONTROL_TOKEN, CHECKBOX_TOKEN } from '../../directives';
import { CheckboxComponent } from '../checkbox';

@Component({
selector: 'et-checkbox-group',
templateUrl: './checkbox-group.component.html',
styleUrls: ['./checkbox-group.component.scss'],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {
class: 'et-checkbox-group',
},
})
export class CheckboxGroupComponent implements AfterContentInit {
@ContentChildren(CHECKBOX_TOKEN)
checkboxes?: QueryList<CheckboxComponent>;

@ContentChild(CHECKBOX_GROUP_CONTROL_TOKEN)
groupControl?: CheckboxGroupControlDirective;

ngAfterContentInit(): void {
console.log(this.checkboxes);
console.log(this.groupControl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './public-api';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './checkbox-group.component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<input
[disabled]="checkbox.disabled"
[id]="checkbox.id"
[checked]="checkbox.checked"
[indeterminate]="!checkbox.checked && checkbox.indeterminate"
(change)="checkbox._onInputInteraction($event)"
class="et-checkbox-native-input"
type="checkbox"
/>

<div [ngClass]="{ 'et-checkbox-checked--active': checkbox.checked }" class="et-checkbox-checked"></div>
<div
[ngClass]="{ 'et-checkbox-indeterminate--active': !checkbox.checked && checkbox.indeterminate }"
class="et-checkbox-indeterminate"
></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
:where(.et-checkbox) {
--et-checkbox-size: 25px;
--et-checkbox-checked-size: 10px;
--et-checkbox-border-color: #e5e5e5;
--et-checkbox-state-color: #e5e5e5;
--et-checkbox-indeterminate-width: 10px;
--et-checkbox-indeterminate-height: 2px;

inline-size: var(--et-checkbox-size);
block-size: var(--et-checkbox-size);
border: 1px solid var(--et-checkbox-border-color);
}

:where(.et-checkbox-checked) {
inline-size: var(--et-checkbox-checked-size);
block-size: var(--et-checkbox-checked-size);

&.et-checkbox-checked--active {
background-color: var(--et-checkbox-state-color);
}
}

:where(.et-checkbox-indeterminate) {
inline-size: var(--et-checkbox-indeterminate-width);
block-size: var(--et-checkbox-indeterminate-height);

&.et-checkbox-indeterminate--active {
background-color: var(--et-checkbox-state-color);
}
}

.et-checkbox {
position: relative;
display: grid;
align-items: center;
justify-items: center;
}

.et-checkbox-native-input,
.et-checkbox-checked,
.et-checkbox-indeterminate {
grid-area: 1 / 1 / 2 / 2;
}

.et-checkbox-native-input {
z-index: 1;
inline-size: var(--et-checkbox-size);
block-size: var(--et-checkbox-size);
cursor: pointer;
opacity: 0;
margin: 0;
border: none;
}
Loading

0 comments on commit 0078825

Please sign in to comment.