import React from "react"; import { ClassNames } from "@emotion/react"; import { Badge, Box, SimpleGrid, Tooltip, Wrap, WrapItem, useColorModeValue, useTheme, } from "@chakra-ui/react"; import { CheckIcon, EditIcon, NotAllowedIcon, StarIcon, } from "@chakra-ui/icons"; import { HiSparkles } from "react-icons/hi"; import SquareItemCard from "./SquareItemCard"; import { safeImageUrl, useCommonStyles } from "../util"; import usePreferArchive from "./usePreferArchive"; function ItemCard({ item, badges, variant = "list", ...props }) { const { brightBackground } = useCommonStyles(); switch (variant) { case "grid": return <SquareItemCard item={item} {...props} />; case "list": return ( <Box as="a" href={`/items/${item.id}`} display="block" p="2" boxShadow="lg" borderRadius="lg" background={brightBackground} transition="all 0.2s" className="item-card" width="100%" minWidth="0" {...props} > <ItemCardContent item={item} badges={badges} focusSelector=".item-card:hover &, .item-card:focus &" /> </Box> ); default: throw new Error(`Unexpected ItemCard variant: ${variant}`); } } export function ItemCardContent({ item, badges, isWorn, isDisabled, itemNameId, focusSelector, }) { return ( <Box display="flex"> <Box> <Box flex="0 0 auto" marginRight="3"> <ItemThumbnail item={item} isActive={isWorn} isDisabled={isDisabled} focusSelector={focusSelector} /> </Box> </Box> <Box flex="1 1 0" minWidth="0" marginTop="1px"> <ItemName id={itemNameId} isWorn={isWorn} isDisabled={isDisabled} focusSelector={focusSelector} > {item.name} </ItemName> {badges} </Box> </Box> ); } /** * ItemThumbnail shows a small preview image for the item, including some * hover/focus and worn/unworn states. */ export function ItemThumbnail({ item, size = "md", isActive, isDisabled, focusSelector, ...props }) { const [preferArchive] = usePreferArchive(); const theme = useTheme(); const borderColor = useColorModeValue( theme.colors.green["700"], "transparent", ); const focusBorderColor = useColorModeValue( theme.colors.green["600"], "transparent", ); return ( <ClassNames> {({ css }) => ( <Box width={size === "lg" ? "80px" : "50px"} height={size === "lg" ? "80px" : "50px"} transition="all 0.15s" transformOrigin="center" position="relative" className={css([ { transform: "scale(0.8)", }, !isDisabled && !isActive && { [focusSelector]: { opacity: "0.9", transform: "scale(0.9)", }, }, !isDisabled && isActive && { opacity: 1, transform: "none", }, ])} {...props} > <Box borderRadius="lg" boxShadow="md" border="1px" overflow="hidden" width="100%" height="100%" className={css([ { borderColor: `${borderColor} !important`, }, !isDisabled && !isActive && { [focusSelector]: { borderColor: `${focusBorderColor} !important`, }, }, ])} > {/* If the item is still loading, wait with an empty box. */} {item && ( <Box as="img" width="100%" height="100%" src={safeImageUrl(item.thumbnailUrl, { preferArchive })} alt={`Thumbnail art for ${item.name}`} /> )} </Box> </Box> )} </ClassNames> ); } /** * ItemName shows the item's name, including some hover/focus and worn/unworn * states. */ function ItemName({ children, isDisabled, focusSelector, ...props }) { const theme = useTheme(); return ( <ClassNames> {({ css }) => ( <Box fontSize="md" transition="all 0.15s" overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" className={ !isDisabled && css` ${focusSelector} { opacity: 0.9; font-weight: ${theme.fontWeights.medium}; } input:checked + .item-container & { opacity: 1; font-weight: ${theme.fontWeights.bold}; } ` } {...props} > {children} </Box> )} </ClassNames> ); } export function ItemCardList({ children }) { return ( <SimpleGrid columns={{ sm: 1, md: 2, lg: 3 }} spacing="6"> {children} </SimpleGrid> ); } export function ItemBadgeList({ children, ...props }) { return ( <Wrap spacing="2" opacity="0.7" {...props}> {React.Children.map( children, (badge) => badge && <WrapItem>{badge}</WrapItem>, )} </Wrap> ); } export function ItemBadgeTooltip({ label, children }) { return ( <Tooltip label={<Box textAlign="center">{label}</Box>} placement="top" openDelay={400} > {children} </Tooltip> ); } export const NcBadge = React.forwardRef(({ isEditButton, ...props }, ref) => { return ( <ItemBadgeTooltip label="Neocash"> <Badge ref={ref} as={isEditButton ? "button" : "span"} colorScheme="purple" display="flex" alignItems="center" _focus={{ outline: "none", boxShadow: "outline" }} {...props} > NC {isEditButton && <EditIcon fontSize="0.85em" marginLeft="1" />} </Badge> </ItemBadgeTooltip> ); }); export const NpBadge = React.forwardRef(({ isEditButton, ...props }, ref) => { return ( <ItemBadgeTooltip label="Neopoints"> <Badge ref={ref} as={isEditButton ? "button" : "span"} display="flex" alignItems="center" _focus={{ outline: "none", boxShadow: "outline" }} {...props} > NP {isEditButton && <EditIcon fontSize="0.85em" marginLeft="1" />} </Badge> </ItemBadgeTooltip> ); }); export const PbBadge = React.forwardRef(({ isEditButton, ...props }, ref) => { return ( <ItemBadgeTooltip label="This item is only obtainable via paintbrush"> <Badge ref={ref} as={isEditButton ? "button" : "span"} colorScheme="orange" display="flex" alignItems="center" _focus={{ outline: "none", boxShadow: "outline" }} {...props} > PB {isEditButton && <EditIcon fontSize="0.85em" marginLeft="1" />} </Badge> </ItemBadgeTooltip> ); }); export const ItemKindBadge = React.forwardRef( ({ isNc, isPb, isEditButton, ...props }, ref) => { if (isNc) { return <NcBadge ref={ref} isEditButton={isEditButton} {...props} />; } else if (isPb) { return <PbBadge ref={ref} isEditButton={isEditButton} {...props} />; } else { return <NpBadge ref={ref} isEditButton={isEditButton} {...props} />; } }, ); export function YouOwnThisBadge({ variant = "long" }) { let badge = ( <Badge colorScheme="green" display="flex" alignItems="center" minHeight="1.5em" > <CheckIcon aria-label="Check" /> {variant === "medium" && <Box marginLeft="1">Own</Box>} {variant === "long" && <Box marginLeft="1">You own this!</Box>} </Badge> ); if (variant === "short" || variant === "medium") { badge = ( <ItemBadgeTooltip label="You own this item">{badge}</ItemBadgeTooltip> ); } return badge; } export function YouWantThisBadge({ variant = "long" }) { let badge = ( <Badge colorScheme="blue" display="flex" alignItems="center" minHeight="1.5em" > <StarIcon aria-label="Star" /> {variant === "medium" && <Box marginLeft="1">Want</Box>} {variant === "long" && <Box marginLeft="1">You want this!</Box>} </Badge> ); if (variant === "short" || variant === "medium") { badge = ( <ItemBadgeTooltip label="You want this item">{badge}</ItemBadgeTooltip> ); } return badge; } function ZoneBadge({ variant, zoneLabel }) { // Shorten the label when necessary, to make the badges less bulky const shorthand = zoneLabel .replace("Background Item", "BG Item") .replace("Foreground Item", "FG Item") .replace("Lower-body", "Lower") .replace("Upper-body", "Upper") .replace("Transient", "Trans") .replace("Biology", "Bio"); if (variant === "restricts") { return ( <ItemBadgeTooltip label={`Restricted: This item can't be worn with ${zoneLabel} items`} > <Badge> <Box display="flex" alignItems="center"> {shorthand} <NotAllowedIcon marginLeft="1" /> </Box> </Badge> </ItemBadgeTooltip> ); } if (shorthand !== zoneLabel) { return ( <ItemBadgeTooltip label={zoneLabel}> <Badge>{shorthand}</Badge> </ItemBadgeTooltip> ); } return <Badge>{shorthand}</Badge>; } export function getZoneBadges(zones, propsForAllBadges) { // Get the sorted zone labels. Sometimes an item occupies multiple zones of // the same name, so it's important to de-duplicate them! let labels = zones.map((z) => z.label); labels = new Set(labels); labels = [...labels].sort(); return labels.map((label) => ( <ZoneBadge key={label} zoneLabel={label} {...propsForAllBadges} /> )); } export function MaybeAnimatedBadge() { return ( <ItemBadgeTooltip label="Maybe animated? (Support only)"> <Badge colorScheme="orange" display="flex" alignItems="center" minHeight="1.5em" > <Box as={HiSparkles} aria-label="Sparkles" /> </Badge> </ItemBadgeTooltip> ); } export default ItemCard;