mirror of
https://github.com/remvze/moodist.git
synced 2025-12-18 09:24:14 +00:00
feat: 统一拖动条样式并优化UI配色方案
重构所有拖动条使用统一的Slider组件,建立一致的视觉体验: - 统一所有音量、速度、倍速拖动条使用相同的Slider组件 - 创建统一的控件背景色CSS变量,与声音图标保持一致 - 优化拖动条配色:已选中部分和拖动点使用相同的温和色调 - 添加明亮模式下的滚动条样式,提升视觉体验 - 调整音乐列表布局,优化声音名称显示和展开按钮 - 精简CSS代码,减少重复样式定义 技术改进: - 移除重复的range input样式代码(从122行减至46行) - 使用CSS变量统一管理控件配色方案 - 保持组件间的一致性和可维护性
This commit is contained in:
parent
a464745a9f
commit
71ab17a39e
8 changed files with 122 additions and 145 deletions
|
|
@ -198,6 +198,7 @@ export function SelectedSoundsDisplay() {
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
// 播放保存的音乐
|
||||
const playSavedMusic = async (music: SavedMusic) => {
|
||||
// 清除当前所有声音选择
|
||||
|
|
@ -485,16 +486,7 @@ export function SelectedSoundsDisplay() {
|
|||
>
|
||||
{music.name}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => toggleMusicExpansion(music.id)}
|
||||
className={styles.expandButton}
|
||||
title="展开/收起声音详情"
|
||||
>
|
||||
{expandedMusic.has(music.id) ? '收起 ▲' : '展开 ▼'}
|
||||
</button>
|
||||
</div>
|
||||
{/* 展开时显示收录的声音名字 */}
|
||||
{expandedMusic.has(music.id) && (
|
||||
{/* 默认显示收录的声音名字 */}
|
||||
<div className={styles.soundNames}>
|
||||
{music.sounds && music.sounds.length > 0 ? (
|
||||
music.sounds.map((soundId: string, index: number) => {
|
||||
|
|
@ -513,7 +505,7 @@ export function SelectedSoundsDisplay() {
|
|||
<span className={styles.noSounds}>暂无声音</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => deleteMusic(music.id.toString())}
|
||||
|
|
@ -522,6 +514,13 @@ export function SelectedSoundsDisplay() {
|
|||
>
|
||||
<FaTrash />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => toggleMusicExpansion(music.id)}
|
||||
className={styles.expandButton}
|
||||
title="展开/收起声音详情"
|
||||
>
|
||||
{expandedMusic.has(music.id) ? '收起 ▲' : '展开 ▼'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@
|
|||
position: relative;
|
||||
flex-grow: 1;
|
||||
height: 4px;
|
||||
background: var(--color-neutral-200);
|
||||
background: var(--color-control-bg);
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.sliderRange {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
background: var(--color-neutral-800);
|
||||
background: var(--color-control-progress);
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
|
|
@ -27,18 +27,18 @@
|
|||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-control-progress);
|
||||
border: 2px solid var(--color-control-progress);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 3px var(--color-neutral-50);
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.sliderThumb:hover {
|
||||
background: var(--bg-secondary);
|
||||
border-color: var(--color-foreground);
|
||||
background: var(--color-control-bg-hover);
|
||||
border-color: var(--color-control-bg-active);
|
||||
}
|
||||
|
||||
.sliderThumb:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--color-neutral-400);
|
||||
box-shadow: 0 0 0 3px var(--color-foreground-subtler);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,83 +32,14 @@
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 当进度条禁用时,容器内的图标也要相应调整 */
|
||||
.volumeContainer:has(.range:disabled) .volumeIcon,
|
||||
.speedContainer:has(.range:disabled) .speedIcon,
|
||||
.rateContainer:has(.range:disabled) .rateIcon {
|
||||
/* 当滑块禁用时,容器内的图标也要相应调整 */
|
||||
.volumeContainer:has(.slider:disabled) .volumeIcon,
|
||||
.speedContainer:has(.slider:disabled) .speedIcon,
|
||||
.rateContainer:has(.slider:disabled) .rateIcon {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.range {
|
||||
.slider {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
|
||||
/********** Range Input Styles **********/
|
||||
|
||||
/* Range Reset */
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
|
||||
/* Removes default focus */
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/***** Chrome, Safari, Opera and Edge Chromium styles *****/
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
height: 0.5rem;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-top: -3px;
|
||||
appearance: none;
|
||||
background-color: var(--color-neutral-700);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 2px var(--color-neutral-400);
|
||||
}
|
||||
|
||||
&:not(:disabled):focus::-webkit-slider-thumb {
|
||||
border: 1px solid var(--color-foreground);
|
||||
outline: 3px solid var(--color-foreground);
|
||||
outline-offset: 0.125rem;
|
||||
}
|
||||
|
||||
/******** Firefox styles ********/
|
||||
|
||||
&::-moz-range-track {
|
||||
height: 0.5rem;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-top: -3px;
|
||||
background-color: var(--color-neutral-700);
|
||||
border: none;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 2px var(--color-neutral-400);
|
||||
}
|
||||
|
||||
&:not(:disabled):focus::-moz-range-thumb {
|
||||
border: 1px solid var(--color-foreground);
|
||||
outline: 3px solid var(--color-foreground);
|
||||
outline-offset: 0.125rem;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { FaVolumeUp, FaTachometerAlt, FaMusic } from 'react-icons/fa/index';
|
||||
import { useSoundStore } from '@/stores/sound';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { Slider } from '@/components/slider';
|
||||
|
||||
import styles from './range.module.css';
|
||||
|
||||
|
|
@ -24,55 +25,38 @@ export function Range({ id, label }: RangeProps) {
|
|||
<div className={styles.controlsContainer}>
|
||||
<div className={styles.volumeContainer}>
|
||||
<FaVolumeUp className={styles.volumeIcon} />
|
||||
<input
|
||||
aria-label={`${label} ${t('volume').toLowerCase()}`}
|
||||
autoComplete="off"
|
||||
className={styles.range}
|
||||
<Slider
|
||||
disabled={!isSelected}
|
||||
max={100}
|
||||
max={1}
|
||||
min={0}
|
||||
type="range"
|
||||
value={volume * 100}
|
||||
onClick={e => e.stopPropagation()}
|
||||
onChange={e =>
|
||||
!locked && isSelected && setVolume(id, Number(e.target.value) / 100)
|
||||
}
|
||||
step={0.01}
|
||||
value={volume}
|
||||
onChange={value => !locked && isSelected && setVolume(id, value)}
|
||||
className={styles.slider}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.speedContainer}>
|
||||
<FaTachometerAlt className={styles.speedIcon} />
|
||||
<input
|
||||
aria-label={`${label} speed`}
|
||||
autoComplete="off"
|
||||
className={styles.range}
|
||||
<Slider
|
||||
disabled={!isSelected}
|
||||
max={200}
|
||||
min={50}
|
||||
step={10}
|
||||
type="range"
|
||||
value={speed * 100}
|
||||
onClick={e => e.stopPropagation()}
|
||||
onChange={e =>
|
||||
!locked && isSelected && setSpeed(id, Number(e.target.value) / 100)
|
||||
}
|
||||
max={2}
|
||||
min={0.5}
|
||||
step={0.1}
|
||||
value={speed}
|
||||
onChange={value => !locked && isSelected && setSpeed(id, value)}
|
||||
className={styles.slider}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.rateContainer}>
|
||||
<FaMusic className={styles.rateIcon} />
|
||||
<input
|
||||
aria-label={`${label} rate`}
|
||||
autoComplete="off"
|
||||
className={styles.range}
|
||||
<Slider
|
||||
disabled={!isSelected}
|
||||
max={200}
|
||||
min={50}
|
||||
step={10}
|
||||
type="range"
|
||||
value={rate * 100}
|
||||
onClick={e => e.stopPropagation()}
|
||||
onChange={e =>
|
||||
!locked && isSelected && setRate(id, Number(e.target.value) / 100)
|
||||
}
|
||||
max={2}
|
||||
min={0.5}
|
||||
step={0.1}
|
||||
value={rate}
|
||||
onChange={value => !locked && isSelected && setRate(id, value)}
|
||||
className={styles.slider}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
background-color: var(--bg-tertiary);
|
||||
background-color: var(--color-control-bg);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@
|
|||
|
||||
.musicContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.playButton {
|
||||
|
|
@ -165,7 +165,22 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* 声音名字显示 */
|
||||
.soundNames {
|
||||
font-size: 12px;
|
||||
color: var(--color-foreground-subtle);
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
padding: 1px 4px;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 展开按钮 */
|
||||
|
|
@ -179,8 +194,9 @@
|
|||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
height: 20px;
|
||||
height: 24px;
|
||||
line-height: 1;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.expandButton:hover {
|
||||
|
|
@ -189,16 +205,16 @@
|
|||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
/* 声音名字显示 */
|
||||
.soundNames {
|
||||
font-size: 12px;
|
||||
color: var(--color-foreground-subtle);
|
||||
line-height: 1.3;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.soundName {
|
||||
color: var(--color-foreground-subtle);
|
||||
background: rgba(var(--color-muted-rgb), 0.3);
|
||||
padding: 1px 4px;
|
||||
border-radius: 2px;
|
||||
margin-right: 2px;
|
||||
display: inline-block;
|
||||
margin-bottom: 2px;
|
||||
font-size: 11px;
|
||||
border: 1px solid rgba(var(--color-border-rgb), 0.3);
|
||||
}
|
||||
|
||||
.noSounds {
|
||||
|
|
@ -211,11 +227,13 @@
|
|||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 6px 8px;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
height: 24px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.deleteButton:hover {
|
||||
|
|
|
|||
|
|
@ -26,3 +26,40 @@ body {
|
|||
color: var(--color-foreground);
|
||||
background-color: var(--color-neutral-300);
|
||||
}
|
||||
|
||||
/* 滚动条样式 - 明亮模式下使用更浅的颜色 */
|
||||
[data-theme="light"] ::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
[data-theme="light"] ::-webkit-scrollbar-track {
|
||||
background: #f3f4f6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
[data-theme="light"] ::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
[data-theme="light"] ::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
[data-theme="light"] ::-webkit-scrollbar-thumb:active {
|
||||
background: #6b7280;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Firefox 滚动条样式 - 明亮模式 */
|
||||
[data-theme="light"] * {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #d1d5db #f3f4f6;
|
||||
}
|
||||
|
||||
[data-theme="light"] *::-webkit-scrollbar-thumb {
|
||||
background-color: #d1d5db;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,4 +15,12 @@
|
|||
--color-foreground: var(--color-neutral-950);
|
||||
--color-foreground-subtle: var(--color-neutral-600);
|
||||
--color-foreground-subtler: var(--color-neutral-500);
|
||||
|
||||
/* 统一控件背景色 - 使用与声音图标一致的配色方案 */
|
||||
--color-control-bg: var(--bg-tertiary);
|
||||
--color-control-bg-hover: var(--bg-quaternary);
|
||||
--color-control-bg-active: var(--bg-secondary);
|
||||
|
||||
/* 拖动条已选中部分颜色 - 只比背景色深一点点 */
|
||||
--color-control-progress: var(--color-neutral-800);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue