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

Create improved book toggling component #2680

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
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 @@ -32,18 +32,13 @@
}
<div class="flex-row">
@for (book of bookOptions; track book) {
<mat-chip-listbox
hideSingleSelectionIndicator
[selectable]="!readonly"
class="book-multi-select"
<app-toggle-book
[selected]="book.selected"
(selectedChanged)="onChipListChange(book)"
[disabled]="readonly"
(change)="onChipListChange(book)"
[progress]="book.progressPercentage / 100"
>{{ "canon.book_names." + book.bookId | transloco }}</app-toggle-book
>
<mat-chip-option [value]="book" [selected]="book.selected">
{{ "canon.book_names." + book.bookId | transloco }}
<div class="border-fill" [style.width]="book.progressPercentage + '%'"></div>
</mat-chip-option>
</mat-chip-listbox>
}
</div>
</ng-container>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Canon } from '@sillsdev/scripture';
import { filter, firstValueFrom, map } from 'rxjs';
import { SubscriptionDisposable } from 'xforge-common/subscription-disposable';
import { UICommonModule } from 'xforge-common/ui-common.module';
import { ToggleBookComponent } from '../../translate/draft-generation/toggle-book/toggle-book.component';
import { ProgressService } from '../progress-service/progress-service';

export interface BookOption {
Expand All @@ -19,7 +20,7 @@ export interface BookOption {
selector: 'app-book-multi-select',
templateUrl: './book-multi-select.component.html',
standalone: true,
imports: [UICommonModule, MatChipsModule, TranslocoModule],
imports: [UICommonModule, MatChipsModule, TranslocoModule, ToggleBookComponent],
styleUrls: ['./book-multi-select.component.scss']
})
export class BookMultiSelectComponent extends SubscriptionDisposable implements OnInit, OnChanges {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<span
class="book"
[style.--progress]="progressCssValue"
[style.--progress-color]="progressColorCssValue"
[style.--progress-hover-color]="progressColorHoverCssValue"
[style.--progress-bg-color]="progressBgColorCssValue"
[style.--progress-hover-bg-color]="progressHoverBgColorCssValue"
[style.--progress-direction-degrees]="progressDirectionCssValue"
(click)="toggleSelected()"
(keypress)="onKeyPress($event)"
[matTooltip]="progressDescription"
matRipple
[matRippleDisabled]="disabled"
[tabindex]="disabled ? -1 : 0"
>
<ng-content></ng-content>
</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
:host.selected .book {
color: white;
}

.book {
display: inline-block;
user-select: none;
padding: 0 16px;
line-height: 32px;
border-radius: 1000px;
min-width: 5em;
text-align: center;

background-image: linear-gradient(
var(--progress-direction-degrees),
var(--progress-color) var(--progress),
var(--progress-bg-color) var(--progress)
);
}

:host:not(.disabled) .book:hover {
background-image: linear-gradient(
var(--progress-direction-degrees),
var(--progress-hover-color) var(--progress),
var(--progress-hover-bg-color) var(--progress)
);
}

:host.disabled:not(.selected) {
opacity: 0.7;
}

:host:not(.disabled) {
cursor: pointer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';
import { MatRippleModule } from '@angular/material/core';
import { MatTooltipModule } from '@angular/material/tooltip';
import { I18nService } from '../../../../xforge-common/i18n.service';

interface ButtonColorSpec {
hue: number;
saturation: number;
light: number;
dark: number;
hoverLight: number;
hoverDark: number;
}

const availableColors: ButtonColorSpec[] = [
// some are commented out because they are ugly, or too similar to others
{ hue: 0 },
// { hue: 30, dark: 35 },
// { hue: 60, dark: 35 },
{ hue: 90, dark: 35 },
// { hue: 120, dark: 35 },
// { hue: 150, dark: 35 },
{ hue: 180, dark: 35 },
{ hue: 210 },
{ hue: 240 },
{ hue: 270 },
{ hue: 300 },
{ hue: 330 }
].map(spec => {
const dark = spec['dark'] ?? 55;
const light = spec['light'] ?? Math.round(Math.min(dark * 1.18, 100));
return {
...spec,
dark,
light,
hoverLight: spec['hoverLight'] ?? Math.round(Math.min(light * 1.07, 100)),
hoverDark: spec['hoverDark'] ?? Math.round(Math.min(dark * 1.07, 100)),
saturation: spec['saturation'] ?? 80
};
});

@Component({
selector: 'app-toggle-book',
standalone: true,
imports: [MatTooltipModule, MatRippleModule],
templateUrl: './toggle-book.component.html',
styleUrl: './toggle-book.component.scss'
})
export class ToggleBookComponent {
@Output() selectedChanged = new EventEmitter<boolean>();

Check warning on line 50 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L50

Added line #L50 was not covered by tests

@HostBinding('class.selected')
@Input()
selected = false;

Check warning on line 54 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L54

Added line #L54 was not covered by tests

@HostBinding('class.disabled')
@Input()
disabled = false;

Check warning on line 58 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L58

Added line #L58 was not covered by tests

@Input() borderWidth = 2;

Check warning on line 60 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L60

Added line #L60 was not covered by tests
@Input() progress?: number;

selectedColorSpec = availableColors[3];
unselectedColorSpec: ButtonColorSpec = { hue: 0, saturation: 0, dark: 80, light: 90, hoverLight: 80, hoverDark: 70 };

Check warning on line 64 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L63-L64

Added lines #L63 - L64 were not covered by tests

get colorSpec(): ButtonColorSpec {
return this.selected ? this.selectedColorSpec : this.unselectedColorSpec;
}

constructor(readonly i18n: I18nService) {
console.log(i18n.direction);

Check warning on line 71 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L70-L71

Added lines #L70 - L71 were not covered by tests
}

toggleSelected(): void {
if (!this.disabled) {
this.selected = !this.selected;
this.selectedChanged.emit(this.selected);

Check warning on line 77 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L76-L77

Added lines #L76 - L77 were not covered by tests
}
}

onKeyPress(event: KeyboardEvent): void {
if (event.key === 'Enter' || event.key === ' ') {
this.toggleSelected();
event.preventDefault();

Check warning on line 84 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L83-L84

Added lines #L83 - L84 were not covered by tests
}
}

get progressCssValue(): string {
return `${(this.progress ?? 0) * 100}%`;
}

get progressColorCssValue(): string {
return this.hsl(this.colorSpec.hue, this.colorSpec.saturation, this.colorSpec.dark);

Check warning on line 93 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L93

Added line #L93 was not covered by tests
}
get progressColorHoverCssValue(): string {
return this.hsl(this.colorSpec.hue, this.colorSpec.saturation, this.colorSpec.hoverDark);

Check warning on line 96 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L96

Added line #L96 was not covered by tests
}
get progressBgColorCssValue(): string {
return this.hsl(this.colorSpec.hue, this.colorSpec.saturation, this.colorSpec.light);

Check warning on line 99 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L99

Added line #L99 was not covered by tests
}
get progressHoverBgColorCssValue(): string {
return this.hsl(this.colorSpec.hue, this.colorSpec.saturation, this.colorSpec.hoverLight);

Check warning on line 102 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L102

Added line #L102 was not covered by tests
}

get progressDirectionCssValue(): string {
return this.i18n.direction === 'rtl' ? '270deg' : '90deg';
}

hsl(hue: number, saturation: number, light: number): string {
return `hsl(${hue}, ${saturation}%, ${light}%)`;

Check warning on line 110 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts

View check run for this annotation

Codecov / codecov/patch

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts#L110

Added line #L110 was not covered by tests
}

get progressDescription(): string {
if (this.progress == null) return '';

// avoid showing 100% when it's not quite there
let percent = this.progress > 0.99 && this.progress < 1 ? 99 : Math.round(this.progress * 100);
return this.progress != null ? `${Math.round(percent)}% translated` : '';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { CommonModule } from '@angular/common';
import { TranslocoModule } from '@ngneat/transloco';
import { Meta, moduleMetadata, StoryObj } from '@storybook/angular';
import { TranslocoMarkupModule } from 'ngx-transloco-markup';
import { I18nStoryModule } from '../../../../xforge-common/i18n-story.module';
import { UICommonModule } from '../../../../xforge-common/ui-common.module';
import { ToggleBookComponent } from './toggle-book.component';

const meta: Meta = {
title: 'Translate/ToggleBook',
component: ToggleBookComponent,
decorators: [
moduleMetadata({
imports: [UICommonModule, I18nStoryModule, CommonModule, TranslocoModule, TranslocoMarkupModule]
})
],
argTypes: {
progress: { control: { type: 'range', min: 0, max: 1, step: 0.01 } },
selected: { control: 'boolean' },
text: { control: 'text' }
},
render: args => ({
props: args,
template: `<app-toggle-book [selected]="selected" [disabled]="disabled" [progress]="progress">${args.text}</app-toggle-book>`
})
};

export default meta;

type Story = StoryObj<ToggleBookComponent & { text: string }>;

export const Default: Story = {
args: {
text: 'Genesis',
selected: false,
progress: 0.37
}
};

export const Selected: Story = {
args: {
...Default.args,
selected: true
}
};

export const Disabled: Story = {
args: {
...Default.args,
disabled: true
}
};

export const RTL: Story = {
args: {
...Default.args,
text: 'تكوين'
},
parameters: {
locale: 'ar'
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { MatStepperModule } from '@angular/material/stepper';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MAT_TOOLTIP_DEFAULT_OPTIONS, MatTooltipModule } from '@angular/material/tooltip';
import { TranslocoService } from '@ngneat/transloco';
import { NgCircleProgressModule } from 'ng-circle-progress';
import { AutofocusDirective } from './autofocus.directive';
Expand Down Expand Up @@ -153,6 +153,10 @@ const appFlexLayoutBreakPoints = [
{
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
useValue: { appearance: 'outline', hideRequiredMarker: true }
},
{
provide: MAT_TOOLTIP_DEFAULT_OPTIONS,
useValue: { disableTooltipInteractivity: true }
}
]
})
Expand Down
Loading