import React from 'react';
import { Tooltip } from '@availity/mui-tooltip';
import { HeartIcon, HeartEmptyIcon } from '@availity/mui-icon';
import { CircularProgress } from '@availity/mui-progress';
import { styled } from '@mui/material/styles';
import { useFavorites } from './Favorites';
const icons = {
spinner: ,
unknownDisabledHeart: ,
favoritedDisabledHeart: ,
unfavoritedDisabledHeart: ,
favoritedHeart: ,
unfavoritedHeart: ,
};
const DEFAULT_SIZE = '2.5rem';
interface SizableHeartProps {
customSize?: string
}
const FavoriteHeartContainer = styled('div', {
name: 'AvFavoriteHeart',
slot: 'Root',
})(({ customSize }) => ({
height: customSize,
width: customSize,
}));
const FavoriteInput = styled('input', {
name: 'AvFavoriteHeart',
slot: 'input',
})(({ customSize }) => ({
height: customSize,
width: customSize,
minHeight: customSize,
minWidth: customSize,
}));
const fontSize = (size = DEFAULT_SIZE) => {
// Match the number and unit in the size string
const match = size.match(/^(\d*\.?\d+)(px|rem)$/);
if (!match) {
throw new Error("Invalid size format. Must be in 'px' or 'rem'.");
}
const [, number, unit] = match;
// Parse the number, halve it, and return the new size string
const halvedNumber = Number.parseFloat(number) / 2;
return `${halvedNumber}${unit}`;
}
const FavoriteIcon = styled('div', {
name: 'AvFavoriteHeart',
slot: 'icon',
})(({ customSize }) => ({
fontSize: fontSize(customSize),
}));
// validates the size input is the correct format and at least 24px or 1.5rem
const validateSize = (size: string) => {
// Match the number and unit in the size string
const match = size.match(/^(\d*\.?\d+)(px|rem)$/);
if (!match) {
return false; // Invalid format
}
const [, number, unit] = match;
const value = Number.parseFloat(number);
// Validate the size based on the unit
if (unit === 'rem' && value >= 1.5) {
return true;
} if (unit === 'px' && value >= 24) {
return true;
}
return false;
}
type FavoriteHeartProps = {
/** The configuration's id */
id: string;
/** The name of the configuration */
name: string;
/** What to do on Favorite Toggle */
onChange?: (
isFavorited: boolean,
event: React.ChangeEvent | React.KeyboardEvent
) => void;
/** What to do on click */
onMouseDown?: (event: React.MouseEvent) => void;
/** Whether or not the Favorite is disabled
* @default false
*/
disabled?: boolean;
/** The size of the icon in rem or px, minimum size is 1.5rem / 24px */
customSize?: string;
};
export const FavoriteHeart = ({
id,
name,
onChange,
onMouseDown,
disabled = false,
customSize = DEFAULT_SIZE,
}: FavoriteHeartProps): React.JSX.Element => {
const { isFavorited, isLastClickedFavorite, status, toggleFavorite } = useFavorites(id);
const handleChange = (event: React.ChangeEvent) => {
onChange?.(isFavorited, event);
toggleFavorite();
};
const handleKeyPress = (event: React.KeyboardEvent) => {
if (event.code === 'Enter' || event.key === 'Enter') {
onChange?.(isFavorited, event);
toggleFavorite();
}
};
const iconKey = (() => {
if (status === 'initLoading') return 'unknownDisabledHeart';
if (status === 'reloading') {
if (isLastClickedFavorite) return 'spinner';
return isFavorited ? 'favoritedDisabledHeart' : 'unfavoritedDisabledHeart';
}
if (disabled) {
return isFavorited ? 'favoritedDisabledHeart' : 'unfavoritedDisabledHeart';
}
if (isFavorited) return 'favoritedHeart';
return 'unfavoritedHeart';
})();
const cursor =
disabled || (!isLastClickedFavorite && (status === 'initLoading' || status === 'reloading'))
? 'not-allowed'
: undefined;
const tooltipContent = `${isFavorited ? 'Remove from' : 'Add to'} My Favorites`;
const favoriteInputProps = {
onKeyDown: handleKeyPress,
type: 'checkbox',
'aria-label': `Favorite ${name}`,
id: `av-favorite-heart-${id}`,
disabled,
checked: isFavorited,
onChange: handleChange,
onMouseDown,
style: { cursor },
};
const validSize = validateSize(customSize) ? customSize : DEFAULT_SIZE;
return (
{icons[iconKey]}
{isLastClickedFavorite && status === 'reloading'
? 'Loading...'
: isLastClickedFavorite && status === 'error'
? 'An error has occurred. Please try again.'
: ''}
{disabled ? (
) : (
)}
);
};