Add Settings Container Component

This commit is contained in:
copecone committed 2026-04-23 14:13:33 +09:00
1 parent 96f62b0e7c
commit 1885ed3926
9 files changed
+152 -94

No files matched your search

+3 -3
View File
@@ -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()"
+4 -4
View File
@@ -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) {
+8
View File
@@ -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
}
+17
View File
@@ -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>
}
+55
View File
@@ -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;
}
}
+26 -28
View File
@@ -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>
+1 -57
View File
@@ -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;
}
}
}
+37 -2
View File
@@ -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;
}
+1
View File
@@ -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);