Fix Animation Consistency
This commit is contained in:
1 parent
898df6a318
commit
8d55b478eb
6 files changed
+36
-15
No files matched your search
@@ -27,6 +27,11 @@ $dropdown_animation_duration: 0.2s;
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
--dropdown-duration: #{$dropdown_animation_duration};
|
||||||
|
|
||||||
|
&.reduce-motion {
|
||||||
|
--dropdown-duration: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
box-shadow: 0 0 0 1px $dropdown_border_color inset, 0 0 0 2px white, 0 0 0 4px $dropdown_accent;
|
box-shadow: 0 0 0 1px $dropdown_border_color inset, 0 0 0 2px white, 0 0 0 4px $dropdown_accent;
|
||||||
@@ -60,7 +65,7 @@ $dropdown_animation_duration: 0.2s;
|
|||||||
.chevron {
|
.chevron {
|
||||||
width: 24px; height: 24px;
|
width: 24px; height: 24px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
transition: transform $dropdown_animation_duration $dropdown_animation_timing;
|
transition: transform var(--dropdown-duration) $dropdown_animation_timing;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&.open {
|
&.open {
|
||||||
@@ -83,7 +88,7 @@ $dropdown_animation_duration: 0.2s;
|
|||||||
max-height: 240px;
|
max-height: 240px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
animation: slideIn $dropdown_animation_duration $dropdown_animation_timing;
|
animation: slideIn var(--dropdown-duration) $dropdown_animation_timing;
|
||||||
|
|
||||||
// 자식 요소의 배경색이 테두리를 침범하지 못하도록 하는 사용자님의 핵심 설계 복구
|
// 자식 요소의 배경색이 테두리를 침범하지 못하도록 하는 사용자님의 핵심 설계 복구
|
||||||
&::after {
|
&::after {
|
||||||
|
|||||||
@@ -14,21 +14,23 @@ export interface DropdownOption {
|
|||||||
templateUrl: './dropdown.html',
|
templateUrl: './dropdown.html',
|
||||||
styleUrl: './dropdown.scss',
|
styleUrl: './dropdown.scss',
|
||||||
host: {
|
host: {
|
||||||
'role': 'combobox',
|
role: 'combobox',
|
||||||
'aria-haspopup': 'listbox',
|
'aria-haspopup': 'listbox',
|
||||||
'[attr.aria-expanded]': 'isOpen()',
|
'[attr.aria-expanded]': 'isOpen()',
|
||||||
'[attr.aria-controls]': "isOpen() ? dropdownId + '-listbox' : null",
|
'[attr.aria-controls]': "isOpen() ? dropdownId + '-listbox' : null",
|
||||||
'[attr.aria-activedescendant]': "isOpen() && focusedIndex() >= 0 ? dropdownId + '-option-' + focusedIndex() : null",
|
'[attr.aria-activedescendant]':
|
||||||
|
"isOpen() && focusedIndex() >= 0 ? dropdownId + '-option-' + focusedIndex() : null",
|
||||||
'[attr.aria-label]': 'ariaLabel()',
|
'[attr.aria-label]': 'ariaLabel()',
|
||||||
'[class.open]': 'isOpen()',
|
'[class.open]': 'isOpen()',
|
||||||
'tabindex': '0',
|
'[class.reduce-motion]': 'reduceMotion()',
|
||||||
|
tabindex: '0',
|
||||||
'(click)': 'toggle()',
|
'(click)': 'toggle()',
|
||||||
'(keydown.enter)': 'onEnter($event)',
|
'(keydown.enter)': 'onEnter($event)',
|
||||||
'(keydown.space)': 'onSpaceDown($event)',
|
'(keydown.space)': 'onSpaceDown($event)',
|
||||||
'(keydown.escape)': 'close()',
|
'(keydown.escape)': 'close()',
|
||||||
'(keydown.arrowdown)': 'onArrowDown($event)',
|
'(keydown.arrowdown)': 'onArrowDown($event)',
|
||||||
'(keydown.arrowup)': 'onArrowUp($event)'
|
'(keydown.arrowup)': 'onArrowUp($event)',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
export class Dropdown implements OnInit {
|
export class Dropdown implements OnInit {
|
||||||
options = input<Array<DropdownOption | string>>([]);
|
options = input<Array<DropdownOption | string>>([]);
|
||||||
@@ -45,6 +47,12 @@ export class Dropdown implements OnInit {
|
|||||||
private static nextId = 0;
|
private static nextId = 0;
|
||||||
dropdownId = `dropdown-${Dropdown.nextId++}`;
|
dropdownId = `dropdown-${Dropdown.nextId++}`;
|
||||||
|
|
||||||
|
reduceMotion = input<boolean>(
|
||||||
|
typeof window !== 'undefined'
|
||||||
|
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
: false,
|
||||||
|
);
|
||||||
|
|
||||||
constructor(private el: ElementRef) {}
|
constructor(private el: ElementRef) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -58,7 +66,7 @@ export class Dropdown implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
normalizedOptions = computed(() => {
|
normalizedOptions = computed(() => {
|
||||||
return this.options().map(opt => {
|
return this.options().map((opt) => {
|
||||||
if (typeof opt === 'string') {
|
if (typeof opt === 'string') {
|
||||||
return { label: opt, value: opt };
|
return { label: opt, value: opt };
|
||||||
}
|
}
|
||||||
@@ -67,9 +75,9 @@ export class Dropdown implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
this.isOpen.update(v => !v);
|
this.isOpen.update((v) => !v);
|
||||||
if (this.isOpen()) {
|
if (this.isOpen()) {
|
||||||
this.focusedIndex.set(this.normalizedOptions().findIndex(o => o.value === this.value()));
|
this.focusedIndex.set(this.normalizedOptions().findIndex((o) => o.value === this.value()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +122,7 @@ export class Dropdown implements OnInit {
|
|||||||
this.toggle();
|
this.toggle();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.focusedIndex.update(i => {
|
this.focusedIndex.update((i) => {
|
||||||
if (i === -1) return 0;
|
if (i === -1) return 0;
|
||||||
return (i + 1) % this.normalizedOptions().length;
|
return (i + 1) % this.normalizedOptions().length;
|
||||||
});
|
});
|
||||||
@@ -126,7 +134,7 @@ export class Dropdown implements OnInit {
|
|||||||
this.toggle();
|
this.toggle();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.focusedIndex.update(i => {
|
this.focusedIndex.update((i) => {
|
||||||
if (i <= 0) return this.normalizedOptions().length - 1;
|
if (i <= 0) return this.normalizedOptions().length - 1;
|
||||||
return i - 1;
|
return i - 1;
|
||||||
});
|
});
|
||||||
@@ -140,7 +148,7 @@ export class Dropdown implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectedLabel = computed(() => {
|
selectedLabel = computed(() => {
|
||||||
const selected = this.normalizedOptions().find(o => o.value === this.value());
|
const selected = this.normalizedOptions().find((o) => o.value === this.value());
|
||||||
return selected ? selected.label : this.placeholder();
|
return selected ? selected.label : this.placeholder();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ $switch_border_radius: 15px;
|
|||||||
$switch_border_color: oklch(0 0 0 / 20%);
|
$switch_border_color: oklch(0 0 0 / 20%);
|
||||||
|
|
||||||
$switch_animation_timing: cubic-bezier(0.4, 0, 0.2, 1);
|
$switch_animation_timing: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
$switch_animation_duration: 0.3s;
|
$switch_animation_duration: 0.2s;
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -18,12 +18,13 @@ import { Component, input, model, output } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class Switch {
|
export class Switch {
|
||||||
isActive = model<boolean>(false);
|
isActive = model<boolean>(false);
|
||||||
|
toggle = output<boolean>();
|
||||||
|
|
||||||
reduceMotion = input<boolean>(
|
reduceMotion = input<boolean>(
|
||||||
typeof window !== 'undefined'
|
typeof window !== 'undefined'
|
||||||
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
: false,
|
: false,
|
||||||
);
|
);
|
||||||
toggle = output<boolean>();
|
|
||||||
|
|
||||||
onClick(): void {
|
onClick(): void {
|
||||||
this.isActive.update((value) => !value);
|
this.isActive.update((value) => !value);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<div class="settings-container">
|
<div class="settings-container">
|
||||||
<dropdown [options]="languages"
|
<dropdown [options]="languages"
|
||||||
placeholder="Choose Language"
|
placeholder="Choose Language"
|
||||||
|
[defaultIndex]="0"
|
||||||
icon="language"></dropdown>
|
icon="language"></dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,10 @@
|
|||||||
<p>Dropdown Component를 테스트하기 위한 UI에서, 상위 컨테이너의 크기가 충분하지 않은 경우 드롭다운이 상위 컨테이너 바깥의 공간을 침범하는 문제가 발생했습니다.</p>
|
<p>Dropdown Component를 테스트하기 위한 UI에서, 상위 컨테이너의 크기가 충분하지 않은 경우 드롭다운이 상위 컨테이너 바깥의 공간을 침범하는 문제가 발생했습니다.</p>
|
||||||
<div class="status-tag resolved">Resolved</div>
|
<div class="status-tag resolved">Resolved</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="issue-item">
|
||||||
|
<h2>Issue #2: Dropdown Animation Control</h2>
|
||||||
|
<p>Dropdown Component의 애니메이션 효과가 운영체제의 애니메이션 설정에 영향을 받지 않는 문제가 발생했습니다.</p>
|
||||||
|
<div class="status-tag resolved">Resolved</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
Reference in new issue
Block a user