Skip to content

Commit a3defeb

Browse files
committed
Create improved book toggling component
1 parent 36b51a3 commit a3defeb

File tree

6 files changed

+215
-11
lines changed

6 files changed

+215
-11
lines changed

src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.html

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,12 @@
3232
}
3333
<div class="flex-row">
3434
@for (book of bookOptions; track book) {
35-
<mat-chip-listbox
36-
hideSingleSelectionIndicator
37-
[selectable]="!readonly"
38-
class="book-multi-select"
35+
<app-toggle-book
36+
[book]="book.bookNum"
37+
[selected]="book.selected"
38+
(selected)="onChipListChange(book)"
3939
[disabled]="readonly"
40-
(change)="onChipListChange(book)"
41-
>
42-
<mat-chip-option [value]="book" [selected]="book.selected">
43-
{{ "canon.book_names." + book.bookId | transloco }}
44-
</mat-chip-option>
45-
</mat-chip-listbox>
40+
></app-toggle-book>
4641
}
4742
</div>
4843
</ng-container>

src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { MatChipsModule } from '@angular/material/chips';
33
import { TranslocoModule } from '@ngneat/transloco';
44
import { Canon } from '@sillsdev/scripture';
55
import { UICommonModule } from 'xforge-common/ui-common.module';
6+
import { ToggleBookComponent } from '../../translate/draft-generation/toggle-book/toggle-book.component';
67

78
export interface BookOption {
89
bookNum: number;
@@ -14,7 +15,7 @@ export interface BookOption {
1415
selector: 'app-book-multi-select',
1516
templateUrl: './book-multi-select.component.html',
1617
standalone: true,
17-
imports: [UICommonModule, MatChipsModule, TranslocoModule],
18+
imports: [UICommonModule, MatChipsModule, TranslocoModule, ToggleBookComponent],
1819
styleUrls: ['./book-multi-select.component.scss']
1920
})
2021
export class BookMultiSelectComponent implements OnChanges {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<ng-container transloco>
2+
<span class="wrapper" [style.--background-image]="backgroundCssGradientStripes">
3+
<span
4+
class="book"
5+
[style.--progress]="progressCssValue"
6+
[style.--border-width]="borderWidthCssValue"
7+
[class.selected]="selected"
8+
[class.disabled]="disabled"
9+
(click)="toggleSelected()"
10+
(keypress)="onKeyPress($event)"
11+
[matTooltip]="progressDescription"
12+
matRipple
13+
[matRippleDisabled]="disabled"
14+
[tabindex]="disabled ? -1 : 0"
15+
>
16+
{{ bookName(book) }}
17+
</span>
18+
</span>
19+
</ng-container>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
:host {
2+
--progress-color: hsl(0, 0%, 80%);
3+
--progress-hover-color: hsl(0, 0%, 70%);
4+
--progress-bg-color: hsl(0, 0%, 90%);
5+
--progress-hover-bg-color: hsl(0, 0%, 80%);
6+
7+
user-select: none;
8+
}
9+
10+
.wrapper {
11+
display: inline-block;
12+
border-radius: 1000px;
13+
}
14+
15+
.wrapper:has(.selected) {
16+
background-image: var(--background-image);
17+
}
18+
19+
.book {
20+
display: block;
21+
background-color: var(--progress-bg-color);
22+
padding: 0 16px;
23+
line-height: 32px;
24+
border-radius: 1000px;
25+
margin: var(--border-width);
26+
position: relative;
27+
28+
background-image: linear-gradient(
29+
90deg,
30+
var(--progress-color) var(--progress),
31+
var(--progress-bg-color) var(--progress)
32+
);
33+
34+
&.disabled:not(.selected) {
35+
opacity: 0.7;
36+
}
37+
38+
&:not(.disabled) {
39+
cursor: pointer;
40+
}
41+
42+
&:hover:not(.disabled),
43+
&:focus:not(.disabled) {
44+
outline: none;
45+
background-image: linear-gradient(
46+
90deg,
47+
var(--progress-hover-color) var(--progress),
48+
var(--progress-hover-bg-color) var(--progress)
49+
);
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Component, EventEmitter, Input, Output } from '@angular/core';
2+
import { MatRippleModule } from '@angular/material/core';
3+
import { MatTooltipModule } from '@angular/material/tooltip';
4+
import { TranslocoModule } from '@ngneat/transloco';
5+
import { I18nService } from '../../../../xforge-common/i18n.service';
6+
7+
@Component({
8+
selector: 'app-toggle-book',
9+
standalone: true,
10+
imports: [TranslocoModule, MatTooltipModule, MatRippleModule],
11+
templateUrl: './toggle-book.component.html',
12+
styleUrl: './toggle-book.component.scss'
13+
})
14+
export class ToggleBookComponent {
15+
@Output() selectedChanged = new EventEmitter<number>();
16+
@Input() selected = false;
17+
@Input() disabled = false;
18+
@Input() borderWidth = 2;
19+
@Input() book!: number;
20+
@Input() progress?: number;
21+
@Input() hues: number[] = [230];
22+
23+
constructor(private readonly i18n: I18nService) {}
24+
25+
bookName(book: number): string {
26+
return this.i18n.localizeBook(book);
27+
}
28+
29+
toggleSelected(): void {
30+
if (!this.disabled) {
31+
this.selected = !this.selected;
32+
this.selectedChanged.emit(this.book);
33+
}
34+
}
35+
36+
onKeyPress(event: KeyboardEvent): void {
37+
if (event.key === 'Enter' || event.key === ' ') {
38+
this.toggleSelected();
39+
event.preventDefault();
40+
}
41+
}
42+
43+
get backgroundCssGradientStripes(): string {
44+
const percentPerStripe = 12.5;
45+
const colors = this.hues.map(hue => `hsl(${hue}, 80%, 60%)`);
46+
let gradient = [];
47+
for (const [index, color] of colors.entries()) {
48+
const from = index * percentPerStripe;
49+
const to = (index + 1) * percentPerStripe;
50+
gradient.push(`${color} ${from}%, ${color} ${to}%`);
51+
}
52+
return `repeating-linear-gradient(135deg, ${gradient.join(', ')})`;
53+
}
54+
55+
get progressCssValue(): string {
56+
return `${(this.progress ?? 0) * 100}%`;
57+
}
58+
59+
get borderWidthCssValue(): string {
60+
return `${this.borderWidth}px`;
61+
}
62+
63+
get progressDescription(): string {
64+
if (this.progress == null) return '';
65+
66+
// avoid showing 100% when it's not quite there
67+
let percent = this.progress > 0.99 && this.progress < 1 ? 99 : Math.round(this.progress * 100);
68+
return this.progress != null ? `${Math.round(percent)}% translated` : '';
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { CommonModule } from '@angular/common';
2+
import { TranslocoModule } from '@ngneat/transloco';
3+
import { Meta, moduleMetadata, StoryObj } from '@storybook/angular';
4+
import { TranslocoMarkupModule } from 'ngx-transloco-markup';
5+
import { I18nStoryModule } from '../../../../xforge-common/i18n-story.module';
6+
import { UICommonModule } from '../../../../xforge-common/ui-common.module';
7+
import { ToggleBookComponent } from './toggle-book.component';
8+
9+
const meta: Meta = {
10+
title: 'Translate/ToggleBook',
11+
component: ToggleBookComponent,
12+
decorators: [
13+
moduleMetadata({
14+
imports: [UICommonModule, I18nStoryModule, CommonModule, TranslocoModule, TranslocoMarkupModule]
15+
})
16+
],
17+
argTypes: {
18+
progress: { control: { type: 'range', min: 0, max: 1, step: 0.01 } }
19+
}
20+
};
21+
22+
export default meta;
23+
24+
interface StoryState {
25+
book: number;
26+
progress?: number;
27+
hues: number[];
28+
selected: boolean;
29+
disabled: boolean;
30+
}
31+
32+
type Story = StoryObj<StoryState>;
33+
34+
export const Default: Story = {
35+
args: {
36+
book: 1,
37+
progress: 0.37,
38+
hues: [0]
39+
}
40+
};
41+
42+
export const Selected: Story = {
43+
args: {
44+
...Default.args,
45+
selected: true
46+
}
47+
};
48+
49+
export const TwoColor: Story = {
50+
args: {
51+
...Selected.args,
52+
hues: [0, 240]
53+
}
54+
};
55+
56+
export const ThreeColor: Story = {
57+
args: {
58+
...Selected.args,
59+
hues: [0, 120, 240]
60+
}
61+
};
62+
63+
export const Disabled: Story = {
64+
args: {
65+
book: 8,
66+
disabled: true
67+
}
68+
};

0 commit comments

Comments
 (0)