forked from OpenNeo/impress
This has been bothering me for a long time, but I couldn't really figure out what to do about it. But tweaking the site bg color a smidge has helped us really add texture to the cards I want to have pop out, like the outfit polaroids! I kinda went all-in in a burst, but tbh I think it looks great :3 I haven't really touched the wardrobe page with it yet though, that'll probably need some tweaking... for now I'm overriding it to keep the old background!
381 lines
8.5 KiB
JavaScript
381 lines
8.5 KiB
JavaScript
import React from "react";
|
|
import { ClassNames } from "@emotion/react";
|
|
import {
|
|
Badge,
|
|
Box,
|
|
SimpleGrid,
|
|
Tooltip,
|
|
Wrap,
|
|
WrapItem,
|
|
useColorModeValue,
|
|
useTheme,
|
|
} from "@chakra-ui/react";
|
|
import { CheckIcon, NotAllowedIcon, StarIcon } from "@chakra-ui/icons";
|
|
import { HiSparkles } from "react-icons/hi";
|
|
import { Link } from "react-router-dom";
|
|
|
|
import { safeImageUrl, useCommonStyles } from "../util";
|
|
|
|
function ItemCard({ item, badges, ...props }) {
|
|
const { brightBackground } = useCommonStyles();
|
|
|
|
return (
|
|
<Box
|
|
as={Link}
|
|
to={`/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>
|
|
);
|
|
}
|
|
|
|
export function ItemCardContent({
|
|
item,
|
|
badges,
|
|
isWorn,
|
|
isDisabled,
|
|
itemNameId,
|
|
focusSelector,
|
|
}) {
|
|
return (
|
|
<Box display="flex">
|
|
<Box flex="0 0 auto" marginRight="3">
|
|
<ItemThumbnail
|
|
item={item}
|
|
isActive={isWorn}
|
|
isDisabled={isDisabled}
|
|
focusSelector={focusSelector}
|
|
/>
|
|
</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 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`,
|
|
},
|
|
},
|
|
])}
|
|
>
|
|
<Box
|
|
as="img"
|
|
width="100%"
|
|
height="100%"
|
|
src={safeImageUrl(item.thumbnailUrl)}
|
|
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 function NcBadge() {
|
|
return (
|
|
<ItemBadgeTooltip label="Neocash">
|
|
<Badge colorScheme="purple" display="block">
|
|
NC
|
|
</Badge>
|
|
</ItemBadgeTooltip>
|
|
);
|
|
}
|
|
|
|
export function NpBadge() {
|
|
return (
|
|
<ItemBadgeTooltip label="Neopoints">
|
|
<Badge display="block">NP</Badge>
|
|
</ItemBadgeTooltip>
|
|
);
|
|
}
|
|
|
|
export function PbBadge() {
|
|
return (
|
|
<ItemBadgeTooltip label="This item is only obtainable via paintbrush">
|
|
<Badge colorScheme="orange" display="block">
|
|
PB
|
|
</Badge>
|
|
</ItemBadgeTooltip>
|
|
);
|
|
}
|
|
|
|
export function ItemKindBadge({ isNc, isPb }) {
|
|
if (isNc) {
|
|
return <NcBadge />;
|
|
} else if (isPb) {
|
|
return <PbBadge />;
|
|
} else {
|
|
return <NpBadge />;
|
|
}
|
|
}
|
|
|
|
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;
|