Add Settings Container Component
This commit is contained in:
1 parent
96f62b0e7c
commit
1885ed3926
9 files changed
+152
-94
No files matched your search
@@ -7,7 +7,7 @@
|
||||
</ng-template>
|
||||
|
||||
<div class="dropdown-box">
|
||||
@if (icon()) {
|
||||
@if (icon()?.trim()?.length ?? 0 > 0) {
|
||||
<span class="material-symbols-outlined icon-24 prefix-icon" aria-hidden="true">{{ icon() }}</span>
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
|
||||
@if (isOpen()) {
|
||||
<div class="dropdown-container" tabindex="-1">
|
||||
<ul class="dropdown-list" role="listbox" [id]="dropdownId + '-listbox'" tabindex="-1">
|
||||
<ul class="dropdown-list" role="listbox" [id]="dropdownID + '-listbox'" tabindex="-1">
|
||||
@for (option of filteredOptions(); track option.value; let i = $index) {
|
||||
<li
|
||||
#optionItem
|
||||
class="dropdown-item"
|
||||
role="option"
|
||||
[id]="dropdownId + '-option-' + i"
|
||||
[id]="dropdownID + '-option-' + i"
|
||||
[class.selected]="option.value === value()"
|
||||
[class.focused]="i === focusedIndex()"
|
||||
[attr.aria-selected]="option.value === value()"
|
||||
|
||||
@@ -33,7 +33,7 @@ export interface DropdownOption {
|
||||
role: 'combobox',
|
||||
'aria-haspopup': 'listbox',
|
||||
'[attr.aria-expanded]': 'isOpen()',
|
||||
'[attr.aria-controls]': "isOpen() ? dropdownId + '-listbox' : null",
|
||||
'[attr.aria-controls]': "isOpen() ? dropdownID + '-listbox' : null",
|
||||
'[attr.aria-activedescendant]': 'ariaActiveDescendant()',
|
||||
'[attr.aria-label]': 'ariaLabel()',
|
||||
'[class.open]': 'isOpen()',
|
||||
@@ -71,13 +71,13 @@ export class Dropdown implements OnInit {
|
||||
|
||||
ariaActiveDescendant = computed(() => {
|
||||
return this.isOpen() && this.focusedIndex() >= 0
|
||||
? this.dropdownId + '-option-' + this.focusedIndex()
|
||||
? this.dropdownID + '-option-' + this.focusedIndex()
|
||||
: null;
|
||||
});
|
||||
|
||||
private static nextId = 0;
|
||||
private static nextID = 0;
|
||||
private isLatestMouseDownInsideInput = false;
|
||||
dropdownId = `dropdown-${Dropdown.nextId++}`;
|
||||
dropdownID = `dropdown-${Dropdown.nextID++}`;
|
||||
private isLatestMouseDownInsideContainer = false;
|
||||
|
||||
constructor(private element: ElementRef) {
|
||||
|
||||
@@ -7,3 +7,11 @@
|
||||
margin: 0; padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
height: 15px;
|
||||
// color: oklch(0.6830 0 0); // #999999
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
@if (settingsType() == SettingsType.Switch) {
|
||||
<div class="settings-name">
|
||||
@if (labelIcon()?.trim()?.length ?? 0 > 0) {
|
||||
<span class="material-symbols-outlined icon-24 settings-icon" aria-hidden="true">{{ labelIcon() }}</span>
|
||||
}
|
||||
<label [id]="labelID">{{ labelText() }}</label>
|
||||
</div>
|
||||
<switch [isActive]="value()" (toggle)="onChange.emit($event)" [aria-labelledby]="labelID"></switch>
|
||||
} @else if (settingsType() == SettingsType.Dropdown) {
|
||||
<label [id]="labelID" class="hidden-label">{{ labelText() }}</label>
|
||||
<dropdown [options]="options()"
|
||||
[value]="value()"
|
||||
(selectionChange)="onChange.emit($event)"
|
||||
[aria-labelledby]="labelID"
|
||||
[placeholder]="placeholder()?.trim() ?? ''"
|
||||
[icon]="labelIcon()"></dropdown>
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
:host {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
&:has(> switch) {
|
||||
height: 32px;
|
||||
|
||||
&:nth-child(1 of settings-container) {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(> dropdown) {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.settings-name {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
gap: 8px;
|
||||
|
||||
span, label {
|
||||
display: inline-block;
|
||||
font-size: 18px;
|
||||
|
||||
&.settings-icon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
font-size: 24px;
|
||||
|
||||
/*
|
||||
line-height: 24px;
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dropdown {
|
||||
width: 324px;
|
||||
}
|
||||
|
||||
.hidden-label {
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,41 @@
|
||||
<app-popup (closeRequest)="close()" [position]="position()">
|
||||
<div header-left class="profile-section">
|
||||
<img ngSrc="/guest_profile.png" alt="profile" class="avatar" width="24" height="24">
|
||||
<span>{{ settingsService.userName() }}</span>
|
||||
<span>{{ settingsService.isGuest() ? translation.settings.guest : settingsService.userName() }}</span>
|
||||
</div>
|
||||
|
||||
<div class="settings-content">
|
||||
<settings-category [headerLabel]="translation.settings.language">
|
||||
<div class="settings-container">
|
||||
<dropdown [options]="languages"
|
||||
[value]="settingsService.language()"
|
||||
(selectionChange)="onLanguageChange($event)"
|
||||
icon="language"></dropdown>
|
||||
</div>
|
||||
<settings-container
|
||||
[settingsType]="SettingsType.Dropdown"
|
||||
[options]="languages"
|
||||
[value]="settingsService.language()"
|
||||
[labelText]="translation.settings.language" labelIcon="language"
|
||||
(onChange)="onLanguageChange($event)">
|
||||
</settings-container>
|
||||
|
||||
<div class="settings-container">
|
||||
<div class="settings-name">
|
||||
<span class="material-symbols-outlined icon-24 settings-icon" aria-hidden="true">match_case</span>
|
||||
<span>{{ translation.settings.romanisation }}</span>
|
||||
</div>
|
||||
<switch [isActive]="settingsService.romanisation()" (toggle)="onRomanisationChange($event)"></switch>
|
||||
</div>
|
||||
<settings-container
|
||||
[settingsType]="SettingsType.Switch"
|
||||
[value]="settingsService.romanisation()"
|
||||
[labelText]="translation.settings.romanisation" labelIcon="match_case"
|
||||
(onChange)="onRomanisationChange($event)">
|
||||
</settings-container>
|
||||
</settings-category>
|
||||
|
||||
<settings-category [headerLabel]="translation.settings.appearance">
|
||||
<div class="settings-container">
|
||||
<div class="settings-name">
|
||||
<span class="material-symbols-outlined icon-24 settings-icon" aria-hidden="true">dark_mode</span>
|
||||
<span>{{ translation.settings.darkMode }}</span>
|
||||
</div>
|
||||
<switch [isActive]="settingsService.darkMode()" (toggle)="onDarkModeChange($event)"></switch>
|
||||
</div>
|
||||
<settings-container
|
||||
[settingsType]="SettingsType.Switch"
|
||||
[value]="settingsService.darkMode()"
|
||||
[labelText]="translation.settings.darkMode" labelIcon="dark_mode"
|
||||
(onChange)="onDarkModeChange($event)">
|
||||
</settings-container>
|
||||
|
||||
<div class="settings-container">
|
||||
<div class="settings-name">
|
||||
<span class="material-symbols-outlined icon-24 settings-icon" aria-hidden="true">motion_photos_paused</span>
|
||||
<span>{{ translation.settings.reduceAnimation }}</span>
|
||||
</div>
|
||||
<switch [isActive]="settingsService.reduceAnimation()" (toggle)="onReduceAnimationChange($event)"></switch>
|
||||
</div>
|
||||
<settings-container
|
||||
[settingsType]="SettingsType.Switch"
|
||||
[value]="settingsService.reduceAnimation()"
|
||||
[labelText]="translation.settings.reduceAnimation" labelIcon="motion_photos_paused"
|
||||
(onChange)="onReduceAnimationChange($event)">
|
||||
</settings-container>
|
||||
</settings-category>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,61 +24,5 @@
|
||||
gap: 16px;
|
||||
|
||||
padding-bottom: 12px;
|
||||
|
||||
settings-category {
|
||||
label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
height: 15px;
|
||||
// color: oklch(0.6830 0 0); // #999999
|
||||
}
|
||||
|
||||
& > .settings-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
&:has(> switch) {
|
||||
height: 32px;
|
||||
|
||||
&:nth-child(1 of .settings-container) {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(> dropdown) {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.settings-name {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
font-size: 18px;
|
||||
|
||||
&.settings-icon {
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dropdown {
|
||||
width: 324px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, inject, input } from '@angular/core';
|
||||
import { Component, inject, input, output } from '@angular/core';
|
||||
import { PopupComponent, PopupPosition } from '../../components/popup/popup';
|
||||
import { Dropdown, DropdownOption } from '../../components/dropdown/dropdown';
|
||||
import { Switch } from '../../components/switch/switch';
|
||||
@@ -7,6 +7,10 @@ import { PopupService } from '../../services/popup.service';
|
||||
import { SettingsService, Language } from '../../services/settings.service';
|
||||
import { TranslationService } from '../../services/translation.service';
|
||||
|
||||
export enum SettingsType {
|
||||
Switch = 'switch', Dropdown = 'dropdown',
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'settings-category',
|
||||
standalone: true,
|
||||
@@ -17,10 +21,39 @@ export class SettingsCategory {
|
||||
headerLabel = input<string>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'settings-container',
|
||||
standalone: true,
|
||||
templateUrl: './container.html',
|
||||
styleUrl: './container.scss',
|
||||
imports: [Switch, Dropdown],
|
||||
})
|
||||
export class SettingsContainer {
|
||||
settingsType = input.required<SettingsType>();
|
||||
protected readonly SettingsType = SettingsType;
|
||||
|
||||
labelIcon = input<string>();
|
||||
labelText = input.required<string>();
|
||||
|
||||
value = input.required<any>();
|
||||
onChange = output<any>();
|
||||
|
||||
placeholder = input<string>();
|
||||
options = input<any>();
|
||||
|
||||
private static nextID = 0;
|
||||
labelID = `settings-${SettingsContainer.nextID++}-label`;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'settings-popup',
|
||||
standalone: true,
|
||||
imports: [PopupComponent, Dropdown, Switch, NgOptimizedImage, SettingsCategory],
|
||||
imports: [
|
||||
PopupComponent,
|
||||
NgOptimizedImage,
|
||||
SettingsCategory,
|
||||
SettingsContainer,
|
||||
],
|
||||
templateUrl: './settings.html',
|
||||
styleUrl: './settings.scss',
|
||||
})
|
||||
@@ -62,4 +95,6 @@ export class SettingsPopup {
|
||||
close() {
|
||||
this.popupService.close(SettingsPopup);
|
||||
}
|
||||
|
||||
protected readonly SettingsType = SettingsType;
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export class SettingsService {
|
||||
romanisation = computed(() => this._romanisation());
|
||||
userName = computed(() => this._userName());
|
||||
reduceAnimation = computed(() => this._reduceAnimation());
|
||||
isGuest = computed(() => true);
|
||||
|
||||
constructor() {
|
||||
this.request = inject(REQUEST);
|
||||
|
||||
Reference in new issue
Block a user