Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>

This commit is contained in:
mbrucedogs 2025-07-20 18:26:51 -05:00
parent 8626251f33
commit dfb3da4e10
13 changed files with 302 additions and 139 deletions

View File

@ -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

View File

@ -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>

View File

@ -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>
);

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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}
/>
);
}

View File

@ -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];

View File

@ -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>

View File

@ -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>

View File

@ -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>
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>

View File

@ -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>

View File

@ -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 {