mirror of
https://github.com/remvze/moodist.git
synced 2025-12-19 09:54:17 +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) => {
|
const playSavedMusic = async (music: SavedMusic) => {
|
||||||
// 清除当前所有声音选择
|
// 清除当前所有声音选择
|
||||||
|
|
@ -485,16 +486,7 @@ export function SelectedSoundsDisplay() {
|
||||||
>
|
>
|
||||||
{music.name}
|
{music.name}
|
||||||
</span>
|
</span>
|
||||||
<button
|
{/* 默认显示收录的声音名字 */}
|
||||||
onClick={() => toggleMusicExpansion(music.id)}
|
|
||||||
className={styles.expandButton}
|
|
||||||
title="展开/收起声音详情"
|
|
||||||
>
|
|
||||||
{expandedMusic.has(music.id) ? '收起 ▲' : '展开 ▼'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/* 展开时显示收录的声音名字 */}
|
|
||||||
{expandedMusic.has(music.id) && (
|
|
||||||
<div className={styles.soundNames}>
|
<div className={styles.soundNames}>
|
||||||
{music.sounds && music.sounds.length > 0 ? (
|
{music.sounds && music.sounds.length > 0 ? (
|
||||||
music.sounds.map((soundId: string, index: number) => {
|
music.sounds.map((soundId: string, index: number) => {
|
||||||
|
|
@ -513,7 +505,7 @@ export function SelectedSoundsDisplay() {
|
||||||
<span className={styles.noSounds}>暂无声音</span>
|
<span className={styles.noSounds}>暂无声音</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteMusic(music.id.toString())}
|
onClick={() => deleteMusic(music.id.toString())}
|
||||||
|
|
@ -522,6 +514,13 @@ export function SelectedSoundsDisplay() {
|
||||||
>
|
>
|
||||||
<FaTrash />
|
<FaTrash />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => toggleMusicExpansion(music.id)}
|
||||||
|
className={styles.expandButton}
|
||||||
|
title="展开/收起声音详情"
|
||||||
|
>
|
||||||
|
{expandedMusic.has(music.id) ? '收起 ▲' : '展开 ▼'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,14 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
background: var(--color-neutral-200);
|
background: var(--color-control-bg);
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sliderRange {
|
.sliderRange {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--color-neutral-800);
|
background: var(--color-control-progress);
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,18 +27,18 @@
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: var(--bg-tertiary);
|
background: var(--color-control-progress);
|
||||||
border: 1px solid var(--color-border);
|
border: 2px solid var(--color-control-progress);
|
||||||
border-radius: 50%;
|
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 {
|
.sliderThumb:hover {
|
||||||
background: var(--bg-secondary);
|
background: var(--color-control-bg-hover);
|
||||||
border-color: var(--color-foreground);
|
border-color: var(--color-control-bg-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sliderThumb:focus {
|
.sliderThumb:focus {
|
||||||
outline: none;
|
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;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 当进度条禁用时,容器内的图标也要相应调整 */
|
/* 当滑块禁用时,容器内的图标也要相应调整 */
|
||||||
.volumeContainer:has(.range:disabled) .volumeIcon,
|
.volumeContainer:has(.slider:disabled) .volumeIcon,
|
||||||
.speedContainer:has(.range:disabled) .speedIcon,
|
.speedContainer:has(.slider:disabled) .speedIcon,
|
||||||
.rateContainer:has(.range:disabled) .rateIcon {
|
.rateContainer:has(.slider:disabled) .rateIcon {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.range {
|
.slider {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1;
|
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 { FaVolumeUp, FaTachometerAlt, FaMusic } from 'react-icons/fa/index';
|
||||||
import { useSoundStore } from '@/stores/sound';
|
import { useSoundStore } from '@/stores/sound';
|
||||||
import { useTranslation } from '@/hooks/useTranslation';
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
import { Slider } from '@/components/slider';
|
||||||
|
|
||||||
import styles from './range.module.css';
|
import styles from './range.module.css';
|
||||||
|
|
||||||
|
|
@ -24,55 +25,38 @@ export function Range({ id, label }: RangeProps) {
|
||||||
<div className={styles.controlsContainer}>
|
<div className={styles.controlsContainer}>
|
||||||
<div className={styles.volumeContainer}>
|
<div className={styles.volumeContainer}>
|
||||||
<FaVolumeUp className={styles.volumeIcon} />
|
<FaVolumeUp className={styles.volumeIcon} />
|
||||||
<input
|
<Slider
|
||||||
aria-label={`${label} ${t('volume').toLowerCase()}`}
|
|
||||||
autoComplete="off"
|
|
||||||
className={styles.range}
|
|
||||||
disabled={!isSelected}
|
disabled={!isSelected}
|
||||||
max={100}
|
max={1}
|
||||||
min={0}
|
min={0}
|
||||||
type="range"
|
step={0.01}
|
||||||
value={volume * 100}
|
value={volume}
|
||||||
onClick={e => e.stopPropagation()}
|
onChange={value => !locked && isSelected && setVolume(id, value)}
|
||||||
onChange={e =>
|
className={styles.slider}
|
||||||
!locked && isSelected && setVolume(id, Number(e.target.value) / 100)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.speedContainer}>
|
<div className={styles.speedContainer}>
|
||||||
<FaTachometerAlt className={styles.speedIcon} />
|
<FaTachometerAlt className={styles.speedIcon} />
|
||||||
<input
|
<Slider
|
||||||
aria-label={`${label} speed`}
|
|
||||||
autoComplete="off"
|
|
||||||
className={styles.range}
|
|
||||||
disabled={!isSelected}
|
disabled={!isSelected}
|
||||||
max={200}
|
max={2}
|
||||||
min={50}
|
min={0.5}
|
||||||
step={10}
|
step={0.1}
|
||||||
type="range"
|
value={speed}
|
||||||
value={speed * 100}
|
onChange={value => !locked && isSelected && setSpeed(id, value)}
|
||||||
onClick={e => e.stopPropagation()}
|
className={styles.slider}
|
||||||
onChange={e =>
|
|
||||||
!locked && isSelected && setSpeed(id, Number(e.target.value) / 100)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rateContainer}>
|
<div className={styles.rateContainer}>
|
||||||
<FaMusic className={styles.rateIcon} />
|
<FaMusic className={styles.rateIcon} />
|
||||||
<input
|
<Slider
|
||||||
aria-label={`${label} rate`}
|
|
||||||
autoComplete="off"
|
|
||||||
className={styles.range}
|
|
||||||
disabled={!isSelected}
|
disabled={!isSelected}
|
||||||
max={200}
|
max={2}
|
||||||
min={50}
|
min={0.5}
|
||||||
step={10}
|
step={0.1}
|
||||||
type="range"
|
value={rate}
|
||||||
value={rate * 100}
|
onChange={value => !locked && isSelected && setRate(id, value)}
|
||||||
onClick={e => e.stopPropagation()}
|
className={styles.slider}
|
||||||
onChange={e =>
|
|
||||||
!locked && isSelected && setRate(id, Number(e.target.value) / 100)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
content: '';
|
content: '';
|
||||||
background-color: var(--bg-tertiary);
|
background-color: var(--color-control-bg);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,8 +113,8 @@
|
||||||
|
|
||||||
.musicContent {
|
.musicContent {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playButton {
|
.playButton {
|
||||||
|
|
@ -165,7 +165,22 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
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;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: 20px;
|
height: 24px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expandButton:hover {
|
.expandButton:hover {
|
||||||
|
|
@ -189,16 +205,16 @@
|
||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 声音名字显示 */
|
|
||||||
.soundNames {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--color-foreground-subtle);
|
|
||||||
line-height: 1.3;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soundName {
|
.soundName {
|
||||||
color: var(--color-foreground-subtle);
|
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 {
|
.noSounds {
|
||||||
|
|
@ -211,11 +227,13 @@
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 6px 8px;
|
padding: 4px 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
height: 24px;
|
||||||
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deleteButton:hover {
|
.deleteButton:hover {
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,40 @@ body {
|
||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
background-color: var(--color-neutral-300);
|
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: var(--color-neutral-950);
|
||||||
--color-foreground-subtle: var(--color-neutral-600);
|
--color-foreground-subtle: var(--color-neutral-600);
|
||||||
--color-foreground-subtler: var(--color-neutral-500);
|
--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