Skip to content

Commit

Permalink
feat(wallet): rebrand visual asset details (#2048)
Browse files Browse the repository at this point in the history
* feat: refine components

* feat: update NftImage component

* feat: rebrand asset details and improvements

* feat: improve classes

* feat: improvement

* feat: add truncate to links and use button instead of link

---------

Co-authored-by: JCNoguera <[email protected]>
Co-authored-by: Begoña Álvarez de la Cruz <[email protected]>
  • Loading branch information
3 people authored Aug 30, 2024
1 parent 1a709a4 commit c846fd6
Show file tree
Hide file tree
Showing 16 changed files with 269 additions and 371 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react';
import { VisualAssetType } from './visual-asset-card.enums';
import { ButtonUnstyled } from '../button';
import { MoreHoriz } from '@iota/ui-icons';
import cx from 'classnames';

export interface VisualAssetCardProps {
/**
Expand All @@ -30,11 +31,15 @@ export interface VisualAssetCardProps {
/**
* The icon to be displayed.
*/
icon: React.ReactNode;
icon?: React.ReactNode;
/**
* The title text to be displayed on hover.
*/
assetTitle?: string;
/**
* Whether the card is hoverable.
*/
isHoverable?: boolean;
}

export function VisualAssetCard({
Expand All @@ -45,31 +50,43 @@ export function VisualAssetCard({
onClick,
icon = <MoreHoriz />,
assetTitle,
isHoverable = true,
}: VisualAssetCardProps): React.JSX.Element {
const handleIconClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onIconClick?.(event);
event?.stopPropagation();
};

return (
<div
className="group relative aspect-square w-full cursor-pointer overflow-hidden rounded-xl"
className={cx('relative aspect-square w-full overflow-hidden rounded-xl', {
'group cursor-pointer': isHoverable,
})}
onClick={onClick}
>
{assetType === VisualAssetType.Video ? (
<video src={assetSrc} className="h-full w-full object-cover" autoPlay loop muted />
) : (
<img src={assetSrc} alt={altText} className="h-full w-full object-cover" />
)}
<div className="absolute left-0 top-0 h-full w-full bg-cover bg-center bg-no-repeat group-hover:bg-shader-neutral-light-48 group-hover:transition group-hover:duration-300 group-hover:ease-in-out group-hover:dark:bg-shader-primary-dark-48" />
<ButtonUnstyled
className="absolute right-2 top-2 h-9 w-9 cursor-pointer rounded-full p-xs opacity-0 transition-opacity duration-300 group-hover:bg-shader-neutral-light-72 group-hover:opacity-100 [&_svg]:h-5 [&_svg]:w-5 [&_svg]:text-primary-100"
onClick={handleIconClick}
>
{icon}
</ButtonUnstyled>
<div className="absolute bottom-0 flex items-center justify-center p-xs opacity-0 transition-opacity duration-300 group-hover:opacity-100">
{assetTitle && <span className="text-title-md text-neutral-100">{assetTitle}</span>}
</div>
{isHoverable && (
<div className="absolute left-0 top-0 h-full w-full bg-cover bg-center bg-no-repeat group-hover:bg-shader-neutral-light-48 group-hover:transition group-hover:duration-300 group-hover:ease-in-out group-hover:dark:bg-shader-primary-dark-48" />
)}
{isHoverable && (
<>
<ButtonUnstyled
className="absolute right-2 top-2 h-9 w-9 cursor-pointer rounded-full p-xs opacity-0 transition-opacity duration-300 group-hover:bg-shader-neutral-light-72 group-hover:opacity-100 [&_svg]:h-5 [&_svg]:w-5 [&_svg]:text-primary-100"
onClick={handleIconClick}
>
{icon}
</ButtonUnstyled>
<div className="absolute bottom-0 flex items-center justify-center p-xs opacity-0 transition-opacity duration-300 group-hover:opacity-100">
{assetTitle && (
<span className="text-title-md text-neutral-100">{assetTitle}</span>
)}
</div>
</>
)}
</div>
);
}
4 changes: 2 additions & 2 deletions apps/ui-kit/src/lib/components/molecules/title/Title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Tooltip, TooltipPosition } from '../../atoms';
import { Info } from '@iota/ui-icons';
import { TitleSize } from './title-size.enum';
import cx from 'classnames';
import { TITLE_PADDINGS } from './title-classes.constants';
import { TITLE_PADDINGS, TITLE_SIZE } from './title-classes.constants';

interface TitleProps {
/**
Expand Down Expand Up @@ -52,7 +52,7 @@ export function Title({
<div className="flex flex-row items-center gap-x-xxxs">
<div className="flex flex-col justify-start">
<div className="flex flex-row items-center gap-x-0.5 text-neutral-10 dark:text-neutral-92">
<h4 className="text-title-lg">{title}</h4>
<h4 className={cx(TITLE_SIZE[size])}>{title}</h4>
{tooltipText && (
<Tooltip text={tooltipText} position={tooltipPosition}>
<Info />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ export const TITLE_PADDINGS: Record<TitleSize, string> = {
[TitleSize.Small]: 'px-md py-sm--rs',
[TitleSize.Medium]: 'px-md--rs py-sm--rs',
};

export const TITLE_SIZE: Record<TitleSize, string> = {
[TitleSize.Small]: 'text-title-md',
[TitleSize.Medium]: 'text-title-lg',
};
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function AccordionContent({
}: PropsWithChildren<AccordionContentProps>) {
return (
<div
className={cx('px-lg pb-md pt-xs', {
className={cx({
hidden: !isExpanded,
})}
>
Expand Down
6 changes: 3 additions & 3 deletions apps/wallet/src/ui/app/components/nft-display/Kiosk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function Kiosk({ object, orientation, ...nftImageProps }: KioskProps) {
<div className="group relative h-36 w-36 transform-gpu overflow-visible rounded-xl hover:bg-transparent">
<div className="absolute z-0">
{itemsWithDisplay.length === 0 ? (
<NftImage animateHover src={null} name="Kiosk" {...nftImageProps} />
<NftImage isHoverable src={null} title="Kiosk" {...nftImageProps} />
) : (
items.map((item, idx) => {
const display = item.data?.display?.data;
Expand All @@ -74,8 +74,8 @@ export function Kiosk({ object, orientation, ...nftImageProps }: KioskProps) {
<NftImage
{...nftImageProps}
src={display?.image_url ?? null}
animateHover={items.length <= 1}
name="Kiosk"
isHoverable={items.length <= 1}
title="Kiosk"
/>
</div>
</div>
Expand Down
142 changes: 30 additions & 112 deletions apps/wallet/src/ui/app/components/nft-display/NftImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,127 +2,45 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { Image32, LockLocked16, MediaPlay16 } from '@iota/icons';
import { cva } from 'class-variance-authority';
import type { VariantProps } from 'class-variance-authority';
import cl from 'clsx';
import { useState } from 'react';
import { VisualAssetCard, VisualAssetType } from '@iota/apps-ui-kit';

const nftImageStyles = cva('overflow-hidden bg-gray-40 relative', {
variants: {
animateHover: {
true: [
'ease-ease-out-cubic duration-400',
'group-hover:shadow-blurXl group-hover:shadow-steel/50',
],
},
borderRadius: {
md: 'rounded-md',
xl: 'rounded-xl',
sm: 'rounded',
},
size: {
xs: 'w-10 h-10',
sm: 'w-12 h-12',
md: 'w-24 h-24',
lg: 'w-36 h-36',
xl: 'w-50 h-50',
},
},
compoundVariants: [
{
animateHover: true,
borderRadius: 'xl',
class: 'group-hover:rounded-md',
},
],
defaultVariants: {
borderRadius: 'md',
},
});

export interface NftImageProps extends VariantProps<typeof nftImageStyles> {
export interface NftImageProps {
src: string | null;
video?: string | null;
name: string | null;
title?: string;
showLabel?: boolean;
playable?: boolean;
className?: string;
isLocked?: boolean;
isHoverable?: boolean;
}

export function NftImage({
src,
name,
title,
showLabel,
animateHover,
borderRadius,
size,
video,
playable,
className,
isLocked,
}: NftImageProps) {
const [error, setError] = useState(false);
const imgCls = cl(
'w-full h-full object-cover',
animateHover && 'group-hover:scale-110 duration-500 ease-ease-out-cubic',
className,
);
export function NftImage({ src, title, isHoverable, video }: NftImageProps) {
const imgSrc = src ? src.replace(/^ipfs:\/\//, 'https://ipfs.io/ipfs/') : '';

if (video) {
return (
<VisualAssetCard
assetSrc={video}
assetTitle={title}
assetType={VisualAssetType.Video}
altText={title || 'NFT'}
isHoverable={isHoverable}
/>
);
}
if (!imgSrc) {
return (
<div className="flex aspect-square h-full w-full items-center justify-center">
<span className="text-captionSmall font-medium">No media</span>
</div>
);
}

return (
<div
className={nftImageStyles({
animateHover,
borderRadius,
size,
})}
>
{video ? (
playable ? (
<video
autoPlay
muted
controls
className="h-full w-full overflow-hidden rounded-md object-cover"
src={video}
/>
) : (
<div className="pointer-events-none absolute bottom-2 right-2 z-10 flex items-center justify-center rounded-full text-black opacity-80">
<MediaPlay16 className="h-8 w-8" />
</div>
)
) : error || !imgSrc ? (
<div
className={cl(
imgCls,
'flex flex-col flex-nowrap items-center justify-center',
'text-steel-dark select-none gap-2 bg-placeholderGradient01 uppercase',
)}
title={title}
>
<Image32 className="text-steel h-6 w-6 text-3xl" />
{showLabel ? (
<span className="text-captionSmall font-medium">No media</span>
) : null}
</div>
) : (
<img
className={imgCls}
src={imgSrc}
alt={name || 'NFT'}
title={title}
onError={() => setError(true)}
/>
)}
{isLocked ? (
<div className="absolute bottom-1.5 right-1.5 flex h-6 w-6 items-center justify-center rounded-md bg-gray-100 text-white">
<LockLocked16 className="h-3.5 w-3.5" />
</div>
) : null}
</div>
<VisualAssetCard
assetSrc={imgSrc}
assetTitle={title}
assetType={VisualAssetType.Image}
altText={title || 'NFT'}
isHoverable={isHoverable}
/>
);
}
33 changes: 7 additions & 26 deletions apps/wallet/src/ui/app/components/nft-display/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// SPDX-License-Identifier: Apache-2.0

import { Heading } from '_app/shared/heading';
import { Loading, NftImage, type NftImageProps } from '_components';
import { Loading, NftImage } from '_components';
import { useFileExtensionType } from '_hooks';
import { isKioskOwnerToken, useGetNFTMeta, useGetObject, useKioskClient } from '@iota/core';
import { formatAddress } from '@iota/iota-sdk/utils';
Expand All @@ -16,7 +16,7 @@ import { Kiosk } from './Kiosk';

const nftDisplayCardStyles = cva('flex flex-nowrap items-center h-full relative', {
variants: {
animateHover: {
isHoverable: {
true: 'group',
},
wideView: {
Expand All @@ -37,22 +37,15 @@ const nftDisplayCardStyles = cva('flex flex-nowrap items-center h-full relative'
export interface NFTDisplayCardProps extends VariantProps<typeof nftDisplayCardStyles> {
objectId: string;
hideLabel?: boolean;
size: NftImageProps['size'];
borderRadius?: NftImageProps['borderRadius'];
playable?: boolean;
isLocked?: boolean;
}

export function NFTDisplayCard({
objectId,
hideLabel,
size,
wideView,
animateHover,
borderRadius = 'md',
playable,
isHoverable,
orientation,
isLocked,
}: NFTDisplayCardProps) {
const { data: objectData } = useGetObject(objectId);
const { data: nftMeta, isPending } = useGetNFTMeta(objectId);
Expand All @@ -62,29 +55,17 @@ export function NFTDisplayCard({
const fileExtensionType = useFileExtensionType(nftImageUrl);
const kioskClient = useKioskClient();
const isOwnerToken = isKioskOwnerToken(kioskClient.network, objectData);
const shouldShowLabel = !wideView && orientation !== 'horizontal';

return (
<div className={nftDisplayCardStyles({ animateHover, wideView, orientation })}>
<div className={nftDisplayCardStyles({ isHoverable, wideView, orientation })}>
<Loading loading={isPending}>
{objectData?.data && isOwnerToken ? (
<Kiosk
object={objectData}
borderRadius={borderRadius}
size={size}
orientation={orientation}
playable={playable}
showLabel={shouldShowLabel}
/>
<Kiosk object={objectData} />
) : (
<NftImage
name={nftName}
title={nftName}
src={nftImageUrl}
animateHover={animateHover}
showLabel={shouldShowLabel}
borderRadius={borderRadius}
size={size}
isLocked={isLocked}
isHoverable={isHoverable ?? false}
video={video}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function TxnImage({ id, actionLabel }: TxnImageProps) {
</Text>
) : null}
<div className="flex w-full gap-2">
<NftImage borderRadius="sm" size="xs" name={nftMeta.name} src={nftMeta.imageUrl} />
<NftImage title={nftMeta.name ?? ''} src={nftMeta.imageUrl} />
<div className="flex w-56 flex-col justify-center gap-1 break-all">
{nftMeta.name && (
<Text color="gray-90" weight="semibold" variant="subtitleSmall" truncate>
Expand Down
1 change: 1 addition & 0 deletions apps/wallet/src/ui/app/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as notEmpty } from './notEmptyCheck';
// export { getEventsSummary } from './getEventsSummary';
export { getAmount } from './getAmount';
export { checkStakingTxn } from './checkStakingTxn';
export { truncateString } from './truncateString';
11 changes: 11 additions & 0 deletions apps/wallet/src/ui/app/helpers/truncateString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

export function truncateString(value: string, length: number, truncateLength: number): string {
if (value.length <= length) {
return value;
}
const startSegment = value.slice(0, truncateLength);
const endSegment = value.slice(-truncateLength);
return `${startSegment}...${endSegment}`;
}
Loading

0 comments on commit c846fd6

Please sign in to comment.