mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
refactor(settings): SettingsSubCard primitives across all of Settings (#1182)
* feat(settings): add SettingsSubCard primitives for sub-section controls Reusable wrappers (SettingsSubCard / SettingsField / SettingsRow / SettingsValue / SettingsCallout) that encapsulate the settings-norm-* sub-card styles. Per-mode and detail controls inside a settings section should use these instead of hand-rolling the box with inline padding or one-off classes — the lack of a shared primitive is what let recent additions drift off the pattern. * refactor(settings): move the Audio tab onto the SettingsSubCard primitives Normalization, Track transitions and Hi-Res now build their sub-cards from the shared primitives instead of raw settings-norm-* markup. Track Previews, which rendered its sub-options bare with inline styles and dividers, now uses the same sub-card so the whole Audio tab is consistent. Behaviour and styling unchanged; Track Previews' start/duration become labelled slider rows like Normalization. * refactor(settings): move the remaining tabs onto SettingsSubCard Apply the shared sub-card primitives across the rest of Settings so every tab matches the Audio-tab reference look: - Appearance: grid columns, window buttons, UI scale, font, seekbar - Library: random-mix blacklist, rating-filter detail - Input: in-app + global keybind lists - Integrations: Discord templates, external-artwork BYOK - System: language, Linux Wayland text render, clock format, logging - Storage: media dir, hot-cache detail, downloads folder - Backup: mode picker + per-mode export/import Detail/per-mode controls now sit in the bordered sub-card; toggle groups stay on SettingsToggle. Behaviour unchanged. * refactor(settings): box the data-heavy and shared sections in SettingsSubCard Bring the previously-skipped 'borderline' sections onto the sub-card too: - Playback speed: wrap the settings-mode controls in a sub-card; the compact player-popup path is left untouched (shares the component via the compact flag) - Theme scheduler: mode picker + theme/time selects move into the sub-card - Cover-cache + analytics strategy: the per-server tables sit in a sub-card Behaviour unchanged; the table layouts and the playback-rate-* internals stay as they are, only the surrounding sub-card is added for visual consistency.
This commit is contained in:
@@ -5,6 +5,7 @@ import { useThemeStore } from '../../store/themeStore';
|
||||
import SettingsSubSection from '../SettingsSubSection';
|
||||
import { SettingsGroup } from './SettingsGroup';
|
||||
import { SettingsToggle } from './SettingsToggle';
|
||||
import { SettingsSubCard, SettingsField } from './SettingsSubCard';
|
||||
import { MusicNetworkSection } from './musicNetwork/MusicNetworkSection';
|
||||
import { purgeExternalArtworkAllServers } from '../../api/coverCache';
|
||||
|
||||
@@ -77,46 +78,44 @@ export function IntegrationsTab() {
|
||||
</SettingsGroup>
|
||||
|
||||
<SettingsGroup title={t('settings.discordTemplates')} desc={t('settings.discordTemplatesDesc')}>
|
||||
<div className="form-group" style={{ marginBottom: '0.75rem' }}>
|
||||
<label style={{ fontSize: 12 }}>{t('settings.discordTemplateName')}</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
value={auth.discordTemplateName}
|
||||
onChange={e => auth.setDiscordTemplateName(e.target.value)}
|
||||
placeholder="{title}"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group" style={{ marginBottom: '0.75rem' }}>
|
||||
<label style={{ fontSize: 12 }}>{t('settings.discordTemplateDetails')}</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
value={auth.discordTemplateDetails}
|
||||
onChange={e => auth.setDiscordTemplateDetails(e.target.value)}
|
||||
placeholder="{artist}"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group" style={{ marginBottom: '0.75rem' }}>
|
||||
<label style={{ fontSize: 12 }}>{t('settings.discordTemplateState')}</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
value={auth.discordTemplateState}
|
||||
onChange={e => auth.setDiscordTemplateState(e.target.value)}
|
||||
placeholder="{title}"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label style={{ fontSize: 12 }}>{t('settings.discordTemplateLargeText')}</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
value={auth.discordTemplateLargeText}
|
||||
onChange={e => auth.setDiscordTemplateLargeText(e.target.value)}
|
||||
placeholder="{album}"
|
||||
/>
|
||||
</div>
|
||||
<SettingsSubCard>
|
||||
<SettingsField label={t('settings.discordTemplateName')}>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
value={auth.discordTemplateName}
|
||||
onChange={e => auth.setDiscordTemplateName(e.target.value)}
|
||||
placeholder="{title}"
|
||||
/>
|
||||
</SettingsField>
|
||||
<SettingsField label={t('settings.discordTemplateDetails')}>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
value={auth.discordTemplateDetails}
|
||||
onChange={e => auth.setDiscordTemplateDetails(e.target.value)}
|
||||
placeholder="{artist}"
|
||||
/>
|
||||
</SettingsField>
|
||||
<SettingsField label={t('settings.discordTemplateState')}>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
value={auth.discordTemplateState}
|
||||
onChange={e => auth.setDiscordTemplateState(e.target.value)}
|
||||
placeholder="{title}"
|
||||
/>
|
||||
</SettingsField>
|
||||
<SettingsField label={t('settings.discordTemplateLargeText')}>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
value={auth.discordTemplateLargeText}
|
||||
onChange={e => auth.setDiscordTemplateLargeText(e.target.value)}
|
||||
placeholder="{album}"
|
||||
/>
|
||||
</SettingsField>
|
||||
</SettingsSubCard>
|
||||
</SettingsGroup>
|
||||
</>
|
||||
)}
|
||||
@@ -166,31 +165,33 @@ export function IntegrationsTab() {
|
||||
title={t('settings.externalArtworkByokTitle')}
|
||||
desc={t('settings.externalArtworkByokDesc')}
|
||||
>
|
||||
<div className="form-group">
|
||||
<input
|
||||
className="input"
|
||||
type="password"
|
||||
value={theme.externalArtworkByok}
|
||||
onChange={e => theme.setExternalArtworkByok(e.target.value)}
|
||||
placeholder="fanart.tv personal API key"
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{theme.externalArtworkByok.trim() && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: 'var(--text-muted)',
|
||||
marginTop: 6,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<Check size={13} /> {t('settings.externalArtworkByokSaved')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SettingsSubCard>
|
||||
<SettingsField>
|
||||
<input
|
||||
className="input"
|
||||
type="password"
|
||||
value={theme.externalArtworkByok}
|
||||
onChange={e => theme.setExternalArtworkByok(e.target.value)}
|
||||
placeholder="fanart.tv personal API key"
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{theme.externalArtworkByok.trim() && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: 'var(--text-muted)',
|
||||
marginTop: 6,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<Check size={13} /> {t('settings.externalArtworkByokSaved')}
|
||||
</div>
|
||||
)}
|
||||
</SettingsField>
|
||||
</SettingsSubCard>
|
||||
</SettingsGroup>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user