Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>
This commit is contained in:
parent
8626251f33
commit
dfb3da4e10
@ -1,11 +1,11 @@
|
||||
vite.svg,499162500000,699a02e0e68a579f687d364bbbe7633161244f35af068220aee37b1b33dfb3c7
|
||||
index.html,1753036974325,d4ec7269769afa4794c9b3bc4382b8bb4ca2a9cc11d47522053a62b9c1dbd99b
|
||||
assets/swipe-back-0ASdaLec.js,1753036974325,dfdb4ccfc65b5ddd1d04306a2cbc650f7f2be8d659bb029d32c75fd5041b6f5e
|
||||
assets/status-tap-DKvxsyfd.js,1753036974325,1bbe8dd5487685ce491d40b73bc1bbbc1a3fdeac4e485438c55484248ca69fff
|
||||
assets/md.transition-CWuowIQj.js,1753036974325,b5d793bffc7672549acba90697be0b704a7b39d8f4ff21faaf05ddba2951dcb0
|
||||
assets/ios.transition-BPJsV1yP.js,1753036974325,e401e6df1d7c900f8f54e717308a57abfcf913ee17b2eb56846b4ba3049d94cf
|
||||
assets/input-shims-BtXJpb0t.js,1753036974325,1f111da7ba4ae84995311f07a6d41c62ca179213cd8123623048af9ccee533f9
|
||||
assets/index7-_QrtZLKR.js,1753036974325,fac87389bb8d2e7850fef07b4fd01132a49206e2b5ddd40feff932d041e1874e
|
||||
assets/focus-visible-supuXXMI.js,1753036974325,df9266429356671847fa2c8123e1564bae645f75df3094f0055c365fa2beae28
|
||||
assets/index-CzY-nILz.css,1753036974324,c68362f490f176fbe950e6cc313da199ed6e08c67058d82b833a2026a0be75c9
|
||||
assets/index-DxnqBJhb.js,1753036974326,143303c7b9c04d8a450f75b956258ba32195e518861db9a8c07e4cbbab3d6c0d
|
||||
index.html,1753039071822,289c705921beda96f6c4b135228e50d878a006af729bda9ea798c308ba8e28d2
|
||||
assets/status-tap-CEY3QyNR.js,1753039071809,6c19db5130690d358a896ae78fec5e08f22b1cfd20385bb51eb8b346ee1b9e04
|
||||
assets/swipe-back-d2lW66aK.js,1753039071809,e420e33827964afc4fbe53b029bbfa7f23b0c1260900677c7ca4eab979e16c02
|
||||
assets/md.transition-MdrV1caB.js,1753039071823,59c5b55c8425c0d65b4a6116578e6b7dd98e70bffbbc3781413f312f17635c60
|
||||
assets/index7-C6G0api_.js,1753039071809,57b89d09c14fbac6c16a22825f7bdb81c92de6f5df1bae8fe148908f5d2812ef
|
||||
assets/ios.transition-DXsH9Qpm.js,1753039071823,758b15c7838ba462b05f15fb7b4ac68e1dd489bda3d25a5e2a12bf1c3a1f5b97
|
||||
assets/focus-visible-supuXXMI.js,1753039071811,df9266429356671847fa2c8123e1564bae645f75df3094f0055c365fa2beae28
|
||||
assets/input-shims-DA6_iXWm.js,1753039071811,3394836233f6eb8c423038c9a190ae5e1b0ef552dc27e0c73287275573e79088
|
||||
assets/index-CzY-nILz.css,1753039071808,c68362f490f176fbe950e6cc313da199ed6e08c67058d82b833a2026a0be75c9
|
||||
assets/index-BlzLgABC.js,1753039071823,cd8075e0b94a4d73bcdfc453f3276398223974fa2cb193599229a82aa2ccba6c
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { IonApp, IonHeader, IonToolbar, IonTitle, IonContent, IonMenuButton, IonIcon } from '@ionic/react';
|
||||
import { logOut } from 'ionicons/icons';
|
||||
import { IonApp, IonHeader, IonToolbar, IonTitle, IonContent, IonMenuButton } from '@ionic/react';
|
||||
import { logout } from '../../redux/authSlice';
|
||||
import { ActionButton } from '../common';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
import Navigation from '../Navigation/Navigation';
|
||||
import { getPageTitle } from '../../utils/routeUtils';
|
||||
import type { LayoutProps } from '../../types';
|
||||
@ -65,11 +66,11 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
<div slot="end">
|
||||
<ActionButton
|
||||
onClick={handleLogout}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={logOut} />
|
||||
</ActionButton>
|
||||
variant={ActionButtonVariant.SECONDARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.LOGOUT}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
</div>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
@ -1,22 +1,27 @@
|
||||
import React from 'react';
|
||||
import { IonButton } from '@ionic/react';
|
||||
import { IonButton, IonIcon } from '@ionic/react';
|
||||
import type { ActionButtonProps } from '../../types';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot, ActionButtonIconSize } from '../../types';
|
||||
|
||||
const ActionButton: React.FC<ActionButtonProps> = ({
|
||||
onClick,
|
||||
children,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
variant = ActionButtonVariant.PRIMARY,
|
||||
size = ActionButtonSize.MEDIUM,
|
||||
disabled = false,
|
||||
className = ''
|
||||
className = '',
|
||||
icon,
|
||||
iconSlot = ActionButtonIconSlot.START,
|
||||
iconSize = ActionButtonIconSize.LARGE,
|
||||
fill = 'solid'
|
||||
}) => {
|
||||
const getVariant = () => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
case ActionButtonVariant.PRIMARY:
|
||||
return 'primary';
|
||||
case 'secondary':
|
||||
case ActionButtonVariant.SECONDARY:
|
||||
return 'medium';
|
||||
case 'danger':
|
||||
case ActionButtonVariant.DANGER:
|
||||
return 'danger';
|
||||
default:
|
||||
return 'primary';
|
||||
@ -25,11 +30,11 @@ const ActionButton: React.FC<ActionButtonProps> = ({
|
||||
|
||||
const getSize = () => {
|
||||
switch (size) {
|
||||
case 'sm':
|
||||
case ActionButtonSize.SMALL:
|
||||
return 'small';
|
||||
case 'md':
|
||||
case ActionButtonSize.MEDIUM:
|
||||
return 'default';
|
||||
case 'lg':
|
||||
case ActionButtonSize.LARGE:
|
||||
return 'large';
|
||||
default:
|
||||
return 'default';
|
||||
@ -40,11 +45,24 @@ const ActionButton: React.FC<ActionButtonProps> = ({
|
||||
<IonButton
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
fill="solid"
|
||||
fill={fill}
|
||||
color={getVariant()}
|
||||
size={getSize()}
|
||||
className={className}
|
||||
style={{
|
||||
minWidth: '40px',
|
||||
minHeight: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
{icon && <IonIcon
|
||||
icon={icon}
|
||||
slot={iconSlot}
|
||||
size={typeof iconSize === 'number' ? undefined : iconSize}
|
||||
style={typeof iconSize === 'number' ? { fontSize: `${iconSize}px` } : undefined}
|
||||
/>}
|
||||
{children}
|
||||
</IonButton>
|
||||
);
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
import { IonCard, IonCardContent, IonChip, IonIcon } from '@ionic/react';
|
||||
import { play, pause, stop, pauseOutline, playOutline, stopOutline } from 'ionicons/icons';
|
||||
import { pauseOutline, playOutline, stopOutline } from 'ionicons/icons';
|
||||
import ActionButton from './ActionButton';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectPlayerState, selectIsAdmin, selectQueueLength, selectControllerName } from '../../redux';
|
||||
import { playerService } from '../../firebase/services';
|
||||
@ -190,30 +192,30 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({ className = '', variant
|
||||
{currentState === PlayerState.playing ? (
|
||||
<ActionButton
|
||||
onClick={handlePause}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={pause} slot="icon-only" />
|
||||
</ActionButton>
|
||||
variant={ActionButtonVariant.PRIMARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.PAUSE}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
) : (
|
||||
<ActionButton
|
||||
onClick={handlePlay}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
variant={ActionButtonVariant.PRIMARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.PLAY}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
disabled={!hasSongsInQueue}
|
||||
>
|
||||
<IonIcon icon={play} slot="icon-only" />
|
||||
</ActionButton>
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentState !== PlayerState.stopped && (
|
||||
<ActionButton
|
||||
onClick={handleStop}
|
||||
variant="danger"
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={stop} slot="icon-only" />
|
||||
</ActionButton>
|
||||
variant={ActionButtonVariant.DANGER}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.STOP}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@ -7,15 +7,15 @@ import {
|
||||
IonContent,
|
||||
IonList,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
IonIcon
|
||||
IonLabel
|
||||
} from '@ionic/react';
|
||||
import { close } from 'ionicons/icons';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectSingersArray, selectControllerName, selectQueueObject } from '../../redux';
|
||||
import { queueService } from '../../firebase/services';
|
||||
import { useToast } from '../../hooks/useToast';
|
||||
import { ActionButton } from './index';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
import type { Song, Singer, QueueItem } from '../../types';
|
||||
|
||||
interface SelectSingerProps {
|
||||
@ -76,9 +76,13 @@ const SelectSinger: React.FC<SelectSingerProps> = ({ isOpen, onClose, song }) =>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Select Singer</IonTitle>
|
||||
<IonButton slot="end" fill="clear" onClick={onClose}>
|
||||
<IonIcon icon={close} />
|
||||
</IonButton>
|
||||
<ActionButton
|
||||
onClick={onClose}
|
||||
variant={ActionButtonVariant.SECONDARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.CLOSE}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
IonButton, IonIcon, IonList, IonItem, IonLabel
|
||||
} from '@ionic/react';
|
||||
import {
|
||||
add, heart, heartOutline, ban, checkmark, close, people
|
||||
add, heart, heartOutline, ban, checkmark, people
|
||||
} from 'ionicons/icons';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectIsAdmin, selectFavorites, selectSongs, selectQueue } from '../../redux';
|
||||
@ -13,6 +13,9 @@ import { useDisabledSongs } from '../../hooks/useDisabledSongs';
|
||||
import { useSelectSinger } from '../../hooks/useSelectSinger';
|
||||
import { useToast } from '../../hooks/useToast';
|
||||
import SelectSinger from './SelectSinger';
|
||||
import { ActionButton } from './index';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
import { SongInfoDisplay } from './SongItem';
|
||||
import type { Song, QueueItem } from '../../types';
|
||||
|
||||
@ -90,9 +93,13 @@ const SongInfo: React.FC<SongInfoProps> = ({ isOpen, onClose, song }) => {
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Song Info</IonTitle>
|
||||
<IonButton slot="end" fill="clear" onClick={onClose}>
|
||||
<IonIcon icon={close} />
|
||||
</IonButton>
|
||||
<ActionButton
|
||||
onClick={onClose}
|
||||
variant={ActionButtonVariant.SECONDARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.CLOSE}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
@ -186,9 +193,13 @@ const SongInfo: React.FC<SongInfoProps> = ({ isOpen, onClose, song }) => {
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Songs by {song.artist}</IonTitle>
|
||||
<IonButton slot="end" fill="clear" onClick={() => setShowArtistSongs(false)}>
|
||||
<IonIcon icon={close} />
|
||||
</IonButton>
|
||||
<ActionButton
|
||||
onClick={() => setShowArtistSongs(false)}
|
||||
variant={ActionButtonVariant.SECONDARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.CLOSE}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import React from 'react';
|
||||
import { IonItem, IonLabel, IonIcon } from '@ionic/react';
|
||||
import { add, heart, heartOutline, trash, informationCircle } from 'ionicons/icons';
|
||||
import { IonItem, IonLabel } from '@ionic/react';
|
||||
import ActionButton from './ActionButton';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectQueue, selectFavorites } from '../../redux';
|
||||
import { debugLog } from '../../utils/logger';
|
||||
import type { SongItemProps, QueueItem, Song } from '../../types';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
|
||||
// Utility function to extract filename from path
|
||||
const extractFilename = (path: string): string => {
|
||||
@ -119,11 +120,11 @@ export const SongActionButtons: React.FC<{
|
||||
<ActionButton
|
||||
key="info"
|
||||
onClick={onSelectSinger}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={informationCircle} />
|
||||
</ActionButton>
|
||||
variant={ActionButtonVariant.SECONDARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.INFORMATION_CIRCLE}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -133,11 +134,11 @@ export const SongActionButtons: React.FC<{
|
||||
<ActionButton
|
||||
key="add"
|
||||
onClick={onAddToQueue || (() => {})}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={add} />
|
||||
</ActionButton>
|
||||
variant={ActionButtonVariant.PRIMARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.ADD}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -147,11 +148,11 @@ export const SongActionButtons: React.FC<{
|
||||
<ActionButton
|
||||
key="remove"
|
||||
onClick={onRemoveFromQueue}
|
||||
variant="danger"
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={trash} />
|
||||
</ActionButton>
|
||||
variant={ActionButtonVariant.DANGER}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.TRASH}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -161,11 +162,11 @@ export const SongActionButtons: React.FC<{
|
||||
<ActionButton
|
||||
key="delete"
|
||||
onClick={onDeleteItem}
|
||||
variant="danger"
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={trash} />
|
||||
</ActionButton>
|
||||
variant={ActionButtonVariant.DANGER}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.TRASH}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -175,11 +176,11 @@ export const SongActionButtons: React.FC<{
|
||||
<ActionButton
|
||||
key="favorite"
|
||||
onClick={onToggleFavorite || (() => {})}
|
||||
variant={isInFavorites ? 'danger' : 'secondary'}
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={isInFavorites ? heart : heartOutline} />
|
||||
</ActionButton>
|
||||
variant={isInFavorites ? ActionButtonVariant.DANGER : ActionButtonVariant.SECONDARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={isInFavorites ? Icons.HEART : Icons.HEART_OUTLINE}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -81,3 +81,77 @@ export const FEATURES = {
|
||||
ENABLE_TOP_PLAYED: true,
|
||||
ENABLE_ADMIN_CONTROLS: true,
|
||||
} as const;
|
||||
|
||||
import {
|
||||
add,
|
||||
trash,
|
||||
close,
|
||||
ban,
|
||||
checkmark,
|
||||
searchOutline,
|
||||
musicalNotesOutline,
|
||||
peopleCircleOutline,
|
||||
peopleOutline,
|
||||
starOutline,
|
||||
heart,
|
||||
heartOutline,
|
||||
timeOutline,
|
||||
listOutline,
|
||||
settingsOutline,
|
||||
play,
|
||||
playOutline,
|
||||
pause,
|
||||
pauseOutline,
|
||||
stop,
|
||||
stopOutline,
|
||||
reorderThreeOutline,
|
||||
reorderTwoOutline,
|
||||
informationCircle,
|
||||
logOut,
|
||||
micOutline,
|
||||
time,
|
||||
list
|
||||
} from 'ionicons/icons';
|
||||
|
||||
// Icon constants for better type safety and autocomplete
|
||||
export const Icons = {
|
||||
// Action icons
|
||||
ADD: add,
|
||||
TRASH: trash,
|
||||
CLOSE: close,
|
||||
BAN: ban,
|
||||
CHECKMARK: checkmark,
|
||||
|
||||
// Navigation icons
|
||||
SEARCH: searchOutline,
|
||||
MUSIC_NOTES: musicalNotesOutline,
|
||||
PEOPLE: peopleCircleOutline,
|
||||
PEOPLE_OUTLINE: peopleOutline,
|
||||
STAR: starOutline,
|
||||
HEART: heart,
|
||||
HEART_OUTLINE: heartOutline,
|
||||
TIME: timeOutline,
|
||||
LIST: listOutline,
|
||||
SETTINGS: settingsOutline,
|
||||
|
||||
// Player controls
|
||||
PLAY: play,
|
||||
PLAY_OUTLINE: playOutline,
|
||||
PAUSE: pause,
|
||||
PAUSE_OUTLINE: pauseOutline,
|
||||
STOP: stop,
|
||||
STOP_OUTLINE: stopOutline,
|
||||
|
||||
// UI icons
|
||||
REORDER_THREE: reorderThreeOutline,
|
||||
REORDER_TWO: reorderTwoOutline,
|
||||
INFORMATION_CIRCLE: informationCircle,
|
||||
LOGOUT: logOut,
|
||||
MIC: micOutline,
|
||||
|
||||
// Status icons
|
||||
TIME_SIMPLE: time,
|
||||
LIST_SIMPLE: list
|
||||
} as const;
|
||||
|
||||
export type IconType = typeof Icons[keyof typeof Icons];
|
||||
@ -1,10 +1,12 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { IonItem, IonLabel, IonItemSliding, IonItemOptions, IonItemOption, IonButton, IonIcon, IonReorderGroup, IonReorder } from '@ionic/react';
|
||||
import { trash, reorderThreeOutline, reorderTwoOutline, list } from 'ionicons/icons';
|
||||
import { IonItem, IonLabel, IonItemSliding, IonItemOptions, IonItemOption, IonIcon, IonReorderGroup, IonReorder } from '@ionic/react';
|
||||
import { reorderThreeOutline, reorderTwoOutline, list } from 'ionicons/icons';
|
||||
import { useQueue } from '../../hooks';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectQueueLength, selectPlayerStateMemoized, selectIsAdmin, selectControllerName } from '../../redux';
|
||||
import { ActionButton, NumberDisplay, EmptyState } from '../../components/common';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
import { SongInfoDisplay } from '../../components/common/SongItem';
|
||||
import { queueService } from '../../firebase/services';
|
||||
import { debugLog } from '../../utils/logger';
|
||||
@ -130,11 +132,11 @@ const Queue: React.FC = () => {
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<ActionButton
|
||||
onClick={() => handleRemoveFromQueue(queueItem)}
|
||||
variant="danger"
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={trash} />
|
||||
</ActionButton>
|
||||
variant={ActionButtonVariant.DANGER}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.TRASH}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{canReorder && queueMode === 'reorder' && (
|
||||
@ -152,7 +154,7 @@ const Queue: React.FC = () => {
|
||||
color="danger"
|
||||
onClick={() => handleRemoveFromQueue(queueItem)}
|
||||
>
|
||||
<IonIcon icon={trash} slot="icon-only" />
|
||||
<IonIcon icon={Icons.TRASH} slot="icon-only" />
|
||||
</IonItemOption>
|
||||
</IonItemOptions>
|
||||
)}
|
||||
@ -194,11 +196,11 @@ const Queue: React.FC = () => {
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<ActionButton
|
||||
onClick={() => handleRemoveFromQueue(firstItem)}
|
||||
variant="danger"
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={trash} />
|
||||
</ActionButton>
|
||||
variant={ActionButtonVariant.DANGER}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.TRASH}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -211,7 +213,7 @@ const Queue: React.FC = () => {
|
||||
color="danger"
|
||||
onClick={() => handleRemoveFromQueue(firstItem)}
|
||||
>
|
||||
<IonIcon icon={trash} slot="icon-only"/>
|
||||
<IonIcon icon={Icons.TRASH} slot="icon-only"/>
|
||||
</IonItemOption>
|
||||
</IonItemOptions>
|
||||
)}
|
||||
@ -234,13 +236,14 @@ const Queue: React.FC = () => {
|
||||
<>
|
||||
<div className="ion-padding ion-text-end">
|
||||
{isAdmin && (
|
||||
<IonButton
|
||||
<ActionButton
|
||||
onClick={toggleQueueMode}
|
||||
variant={ActionButtonVariant.SECONDARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={queueMode === 'delete' ? reorderThreeOutline : Icons.TRASH}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
fill="outline"
|
||||
size="small"
|
||||
>
|
||||
<IonIcon icon={queueMode === 'delete' ? reorderThreeOutline : trash} />
|
||||
</IonButton>
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonContent, IonHeader, IonTitle, IonToolbar, IonList, IonItem, IonLabel, IonToggle, IonButton, IonIcon, IonModal, IonSearchbar } from '@ionic/react';
|
||||
import { ban, trash } from 'ionicons/icons';
|
||||
import { ban } from 'ionicons/icons';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectIsAdmin, selectSettings } from '../../redux';
|
||||
import { useDisabledSongs } from '../../hooks';
|
||||
import { InfiniteScrollList, ActionButton } from '../../components/common';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
import { filterSongs } from '../../utils/dataProcessing';
|
||||
import { setDebugEnabled, isDebugEnabled, debugLog } from '../../utils/logger';
|
||||
import type { Song, DisabledSong } from '../../types';
|
||||
@ -181,11 +183,11 @@ const Settings: React.FC = () => {
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<ActionButton
|
||||
onClick={() => handleRemoveDisabledSong(song)}
|
||||
variant="danger"
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={trash} />
|
||||
</ActionButton>
|
||||
variant={ActionButtonVariant.DANGER}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.TRASH}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</IonItem>
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonItem, IonLabel, IonIcon, IonModal, IonHeader, IonToolbar, IonTitle, IonButton, IonContent, IonInput, IonLabel as IonInputLabel } from '@ionic/react';
|
||||
import { trash, add, close } from 'ionicons/icons';
|
||||
import { IonItem, IonLabel, IonModal, IonHeader, IonToolbar, IonTitle, IonButton, IonContent, IonInput, IonLabel as IonInputLabel } from '@ionic/react';
|
||||
import { InfiniteScrollList, ActionButton, NumberDisplay } from '../../components/common';
|
||||
import { useSingers } from '../../hooks';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectSingers } from '../../redux';
|
||||
import { debugLog } from '../../utils/logger';
|
||||
import type { Singer } from '../../types';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
|
||||
const Singers: React.FC = () => {
|
||||
const {
|
||||
@ -59,15 +60,13 @@ const Singers: React.FC = () => {
|
||||
{/* Delete Button (Admin Only) */}
|
||||
{isAdmin && (
|
||||
<div slot="end" style={{ marginRight: '-16px' }}>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<ActionButton
|
||||
onClick={() => handleRemoveSinger(singer)}
|
||||
variant="danger"
|
||||
size="sm"
|
||||
>
|
||||
<IonIcon icon={trash} />
|
||||
</ActionButton>
|
||||
</div>
|
||||
<ActionButton
|
||||
onClick={() => handleRemoveSinger(singer)}
|
||||
variant={ActionButtonVariant.DANGER}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.TRASH}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</IonItem>
|
||||
@ -77,12 +76,14 @@ const Singers: React.FC = () => {
|
||||
<>
|
||||
<div className="ion-padding ion-text-end">
|
||||
{isAdmin && (
|
||||
<IonButton
|
||||
fill="clear"
|
||||
<ActionButton
|
||||
onClick={handleOpenAddModal}
|
||||
>
|
||||
<IonIcon icon={add} slot="icon-only" size="large" />
|
||||
</IonButton>
|
||||
variant={ActionButtonVariant.PRIMARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.ADD}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
fill="clear"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -111,9 +112,13 @@ const Singers: React.FC = () => {
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Add New Singer</IonTitle>
|
||||
<IonButton slot="end" fill="clear" onClick={handleCloseAddModal}>
|
||||
<IonIcon icon={close} />
|
||||
</IonButton>
|
||||
<ActionButton
|
||||
onClick={handleCloseAddModal}
|
||||
variant={ActionButtonVariant.SECONDARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.CLOSE}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { IonChip, IonModal, IonHeader, IonToolbar, IonTitle, IonButton, IonIcon, IonContent, IonList } from '@ionic/react';
|
||||
import { close, list } from 'ionicons/icons';
|
||||
import { IonChip, IonModal, IonHeader, IonToolbar, IonTitle, IonIcon, IonContent, IonList } from '@ionic/react';
|
||||
import { list } from 'ionicons/icons';
|
||||
import { useTopPlayed } from '../../hooks';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectTopPlayed, selectSongsArray } from '../../redux';
|
||||
import { InfiniteScrollList, SongItem, ListItem, SongInfo } from '../../components/common';
|
||||
import { InfiniteScrollList, SongItem, ListItem, SongInfo, ActionButton } from '../../components/common';
|
||||
import { filterSongs } from '../../utils/dataProcessing';
|
||||
import { debugLog } from '../../utils/logger';
|
||||
import { useSongOperations } from '../../hooks';
|
||||
import { useToast } from '../../hooks';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
import type { TopPlayed, Song } from '../../types';
|
||||
|
||||
const Top100: React.FC = () => {
|
||||
@ -139,9 +141,13 @@ const Top100: React.FC = () => {
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>{selectedTopPlayed?.artist}</IonTitle>
|
||||
<IonButton slot="end" fill="clear" onClick={handleCloseModal}>
|
||||
<IonIcon icon={close} />
|
||||
</IonButton>
|
||||
<ActionButton
|
||||
onClick={handleCloseModal}
|
||||
variant={ActionButtonVariant.SECONDARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.CLOSE}
|
||||
iconSlot={ActionButtonIconSlot.ICON_ONLY}
|
||||
/>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
|
||||
@ -117,13 +117,49 @@ export interface ToastProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
// ActionButton constants
|
||||
export const ActionButtonVariant = {
|
||||
PRIMARY: 'primary',
|
||||
SECONDARY: 'secondary',
|
||||
DANGER: 'danger'
|
||||
} as const;
|
||||
|
||||
export const ActionButtonSize = {
|
||||
SMALL: 'sm',
|
||||
MEDIUM: 'md',
|
||||
LARGE: 'lg'
|
||||
} as const;
|
||||
|
||||
export const ActionButtonIconSlot = {
|
||||
START: 'start',
|
||||
END: 'end',
|
||||
ICON_ONLY: 'icon-only'
|
||||
} as const;
|
||||
|
||||
export const ActionButtonIconSize = {
|
||||
SMALL: 18,
|
||||
MEDIUM: 20,
|
||||
LARGE: 22
|
||||
} as const;
|
||||
|
||||
export type ActionButtonVariantType = typeof ActionButtonVariant[keyof typeof ActionButtonVariant];
|
||||
export type ActionButtonSizeType = typeof ActionButtonSize[keyof typeof ActionButtonSize];
|
||||
export type ActionButtonIconSlotType = typeof ActionButtonIconSlot[keyof typeof ActionButtonIconSlot];
|
||||
export type ActionButtonIconSizeType = typeof ActionButtonIconSize[keyof typeof ActionButtonIconSize];
|
||||
|
||||
import type { IconType } from '../constants';
|
||||
|
||||
export interface ActionButtonProps {
|
||||
onClick: () => void;
|
||||
children: React.ReactNode;
|
||||
variant?: 'primary' | 'secondary' | 'danger';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
children?: React.ReactNode;
|
||||
variant?: ActionButtonVariantType;
|
||||
size?: ActionButtonSizeType;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
icon?: IconType;
|
||||
iconSlot?: ActionButtonIconSlotType;
|
||||
iconSize?: ActionButtonIconSizeType;
|
||||
fill?: 'solid' | 'outline' | 'clear';
|
||||
}
|
||||
|
||||
export interface SongItemProps {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user