mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
feat(library): "favorites only" filter on Albums, Artists, AdvancedSearch (#466)
* feat(ui): StarFilterButton component + common i18n keys Reusable toggle button for "favorites only" filtering. Three size variants for different toolbar contexts: - default: icon + label (Albums-style) - compact: icon-only with 0.5rem padding (Artists view-mode buttons) - small: icon + label at 12px / 4×14 padding (AdvancedSearch tabs) Adds common.favorites + favoritesTooltipOff/On in all 8 locales. * feat(library): "favorites only" filter on Albums, Artists, AdvancedSearch Client-side filter using the existing useMemo pipelines on each page. Reads starred state from item.starred + playerStore.starredOverrides (O(1) Map lookup, picks up live star toggles without refetch). - Albums: toolbar button (default size) next to compilation filter. - Artists: toolbar button (compact / icon-only) before the Images toggle. - AdvancedSearch: toolbar button (small) next to the result-type tabs; filters all three result categories (artists / albums / songs) and updates the count badges accordingly. Filter state is ephemeral per-page (not persisted) so users don't get surprised by hidden items after a restart. Zero extra server calls. * docs(contributors): credit + changelog entry for #466
This commit is contained in:
committed by
GitHub
parent
0fab2849e5
commit
d33abf565c
@@ -0,0 +1,68 @@
|
||||
import { Star } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Props {
|
||||
active: boolean;
|
||||
onChange: (next: boolean) => void;
|
||||
/** 'default' = icon + label, regular padding (Albums toolbar).
|
||||
* 'compact' = icon-only, 0.5rem padding (Artists view-mode buttons).
|
||||
* 'small' = icon + label, 4px/14px padding + 12px text (AdvancedSearch tabs). */
|
||||
size?: 'default' | 'compact' | 'small';
|
||||
}
|
||||
|
||||
export default function StarFilterButton({ active, onChange, size = 'default' }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const tooltip = active ? t('common.favoritesTooltipOn') : t('common.favoritesTooltipOff');
|
||||
const activeStyle = active ? { background: 'var(--accent)', color: 'var(--ctp-crust)' } : {};
|
||||
|
||||
if (size === 'compact') {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-surface${active ? ' btn-sort-active' : ''}`}
|
||||
onClick={() => onChange(!active)}
|
||||
aria-pressed={active}
|
||||
aria-label={tooltip}
|
||||
data-tooltip={tooltip}
|
||||
data-tooltip-pos="bottom"
|
||||
style={{ padding: '0.5rem', ...activeStyle }}
|
||||
>
|
||||
<Star size={20} fill={active ? 'currentColor' : 'none'} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
if (size === 'small') {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`btn ${active ? 'btn-primary' : 'btn-surface'}`}
|
||||
onClick={() => onChange(!active)}
|
||||
aria-pressed={active}
|
||||
data-tooltip={tooltip}
|
||||
data-tooltip-pos="bottom"
|
||||
style={{ fontSize: 12, padding: '4px 14px', display: 'inline-flex', alignItems: 'center', gap: '0.35rem' }}
|
||||
>
|
||||
<Star size={12} fill={active ? 'currentColor' : 'none'} />
|
||||
{t('common.favorites')}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-surface${active ? ' btn-sort-active' : ''}`}
|
||||
onClick={() => onChange(!active)}
|
||||
aria-pressed={active}
|
||||
data-tooltip={tooltip}
|
||||
data-tooltip-pos="bottom"
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: '0.4rem', ...activeStyle,
|
||||
}}
|
||||
>
|
||||
<Star size={14} fill={active ? 'currentColor' : 'none'} />
|
||||
{t('common.favorites')}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user