diff --git a/apps/ui-kit/src/lib/components/atoms/visual-asset-card/VisualAssetCard.tsx b/apps/ui-kit/src/lib/components/atoms/visual-asset-card/VisualAssetCard.tsx index bf36f18243e..b69a0be12f2 100644 --- a/apps/ui-kit/src/lib/components/atoms/visual-asset-card/VisualAssetCard.tsx +++ b/apps/ui-kit/src/lib/components/atoms/visual-asset-card/VisualAssetCard.tsx @@ -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 { /** @@ -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({ @@ -45,14 +50,18 @@ export function VisualAssetCard({ onClick, icon = , assetTitle, + isHoverable = true, }: VisualAssetCardProps): React.JSX.Element { const handleIconClick = (event: React.MouseEvent) => { onIconClick?.(event); event?.stopPropagation(); }; + return (
{assetType === VisualAssetType.Video ? ( @@ -60,16 +69,24 @@ export function VisualAssetCard({ ) : ( {altText} )} -
- - {icon} - -
- {assetTitle && {assetTitle}} -
+ {isHoverable && ( +
+ )} + {isHoverable && ( + <> + + {icon} + +
+ {assetTitle && ( + {assetTitle} + )} +
+ + )}
); } diff --git a/apps/ui-kit/src/lib/components/molecules/title/Title.tsx b/apps/ui-kit/src/lib/components/molecules/title/Title.tsx index d6f9d911aa2..bfaaaf8e37b 100644 --- a/apps/ui-kit/src/lib/components/molecules/title/Title.tsx +++ b/apps/ui-kit/src/lib/components/molecules/title/Title.tsx @@ -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 { /** @@ -52,7 +52,7 @@ export function Title({
-

{title}

+

{title}

{tooltipText && ( diff --git a/apps/ui-kit/src/lib/components/molecules/title/title-classes.constants.ts b/apps/ui-kit/src/lib/components/molecules/title/title-classes.constants.ts index 5ac9912a43d..15640fbb854 100644 --- a/apps/ui-kit/src/lib/components/molecules/title/title-classes.constants.ts +++ b/apps/ui-kit/src/lib/components/molecules/title/title-classes.constants.ts @@ -7,3 +7,8 @@ export const TITLE_PADDINGS: Record = { [TitleSize.Small]: 'px-md py-sm--rs', [TitleSize.Medium]: 'px-md--rs py-sm--rs', }; + +export const TITLE_SIZE: Record = { + [TitleSize.Small]: 'text-title-md', + [TitleSize.Medium]: 'text-title-lg', +}; diff --git a/apps/ui-kit/src/lib/components/organisms/accordion/Accordion.tsx b/apps/ui-kit/src/lib/components/organisms/accordion/Accordion.tsx index 5007017d305..85fea391043 100644 --- a/apps/ui-kit/src/lib/components/organisms/accordion/Accordion.tsx +++ b/apps/ui-kit/src/lib/components/organisms/accordion/Accordion.tsx @@ -62,7 +62,7 @@ export function AccordionContent({ }: PropsWithChildren) { return (
diff --git a/apps/wallet/src/ui/app/components/nft-display/Kiosk.tsx b/apps/wallet/src/ui/app/components/nft-display/Kiosk.tsx index 176764b8fae..2fa7a969919 100644 --- a/apps/wallet/src/ui/app/components/nft-display/Kiosk.tsx +++ b/apps/wallet/src/ui/app/components/nft-display/Kiosk.tsx @@ -53,7 +53,7 @@ export function Kiosk({ object, orientation, ...nftImageProps }: KioskProps) {
{itemsWithDisplay.length === 0 ? ( - + ) : ( items.map((item, idx) => { const display = item.data?.display?.data; @@ -74,8 +74,8 @@ export function Kiosk({ object, orientation, ...nftImageProps }: KioskProps) {
diff --git a/apps/wallet/src/ui/app/components/nft-display/NftImage.tsx b/apps/wallet/src/ui/app/components/nft-display/NftImage.tsx index 99e9563c15a..2cb19652003 100644 --- a/apps/wallet/src/ui/app/components/nft-display/NftImage.tsx +++ b/apps/wallet/src/ui/app/components/nft-display/NftImage.tsx @@ -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 { +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 ( + + ); + } + if (!imgSrc) { + return ( +
+ No media +
+ ); + } + return ( -
- {video ? ( - playable ? ( -
+ ); } diff --git a/apps/wallet/src/ui/app/components/nft-display/index.tsx b/apps/wallet/src/ui/app/components/nft-display/index.tsx index a5456dc73dc..bd10b36c7d8 100644 --- a/apps/wallet/src/ui/app/components/nft-display/index.tsx +++ b/apps/wallet/src/ui/app/components/nft-display/index.tsx @@ -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'; @@ -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: { @@ -37,22 +37,15 @@ const nftDisplayCardStyles = cva('flex flex-nowrap items-center h-full relative' export interface NFTDisplayCardProps extends VariantProps { 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); @@ -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 ( -
+
{objectData?.data && isOwnerToken ? ( - + ) : ( )} diff --git a/apps/wallet/src/ui/app/components/transactions-card/TxnImage.tsx b/apps/wallet/src/ui/app/components/transactions-card/TxnImage.tsx index ee95f755ce8..eb33c3daa5a 100644 --- a/apps/wallet/src/ui/app/components/transactions-card/TxnImage.tsx +++ b/apps/wallet/src/ui/app/components/transactions-card/TxnImage.tsx @@ -24,7 +24,7 @@ export function TxnImage({ id, actionLabel }: TxnImageProps) { ) : null}
- +
{nftMeta.name && ( diff --git a/apps/wallet/src/ui/app/helpers/index.ts b/apps/wallet/src/ui/app/helpers/index.ts index 307696d6ec4..12a7aa20a37 100644 --- a/apps/wallet/src/ui/app/helpers/index.ts +++ b/apps/wallet/src/ui/app/helpers/index.ts @@ -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'; diff --git a/apps/wallet/src/ui/app/helpers/truncateString.ts b/apps/wallet/src/ui/app/helpers/truncateString.ts new file mode 100644 index 00000000000..26e8377c0be --- /dev/null +++ b/apps/wallet/src/ui/app/helpers/truncateString.ts @@ -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}`; +} diff --git a/apps/wallet/src/ui/app/pages/home/kiosk-details/index.tsx b/apps/wallet/src/ui/app/pages/home/kiosk-details/index.tsx index b3c5c98b8c2..85b046da563 100644 --- a/apps/wallet/src/ui/app/pages/home/kiosk-details/index.tsx +++ b/apps/wallet/src/ui/app/pages/home/kiosk-details/index.tsx @@ -54,10 +54,7 @@ function KioskDetailsPage() { diff --git a/apps/wallet/src/ui/app/pages/home/nft-details/index.tsx b/apps/wallet/src/ui/app/pages/home/nft-details/index.tsx index cb9fc057def..f7fcb8e404a 100644 --- a/apps/wallet/src/ui/app/pages/home/nft-details/index.tsx +++ b/apps/wallet/src/ui/app/pages/home/nft-details/index.tsx @@ -3,31 +3,24 @@ // SPDX-License-Identifier: Apache-2.0 import { useActiveAddress } from '_app/hooks/useActiveAddress'; -import { Button } from '_app/shared/ButtonUI'; import { Collapsible } from '_app/shared/collapse'; -import { Link } from '_app/shared/Link'; -import { - ExplorerLinkType, - LabelValueItem, - LabelValuesContainer, - Loading, - NFTDisplayCard, -} from '_components'; +import { ExplorerLinkType, Loading, NFTDisplayCard, PageTemplate } from '_components'; import { useNFTBasicData, useOwnedNFT } from '_hooks'; import { useExplorerLink } from '_src/ui/app/hooks/useExplorerLink'; import { useUnlockedGuard } from '_src/ui/app/hooks/useUnlockedGuard'; -import PageTitle from '_src/ui/app/shared/PageTitle'; import { useGetKioskContents, useGetNFTMeta } from '@iota/core'; -import { ArrowRight16, ArrowUpRight12 } from '@iota/icons'; import { formatAddress } from '@iota/iota-sdk/utils'; import cl from 'clsx'; -import { Navigate, useSearchParams } from 'react-router-dom'; +import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; +import { Button, ButtonType, KeyValueInfo } from '@iota/apps-ui-kit'; +import { truncateString } from '_src/ui/app/helpers'; type NftFields = { metadata?: { fields?: { attributes?: { fields?: { keys: string[]; values: string[] } } } }; }; function NFTDetailsPage() { + const navigate = useNavigate(); const [searchParams] = useSearchParams(); const nftId = searchParams.get('objectId'); const accountAddress = useActiveAddress(); @@ -76,158 +69,172 @@ function NFTDetailsPage() { const isGuardLoading = useUnlockedGuard(); const isPending = isNftLoading || isPendingDisplay || isGuardLoading; + function handleMoreAboutKiosk() { + window.open('https://wiki.iota.org/', '_blank'); + } + + function handleMarketplace() { + window.open('https://wiki.iota.org/', '_blank'); + } + + function handleSend() { + navigate(`/nft-transfer/${nftId}`); + } + + function handleViewOnExplorer() { + window.open(objectExplorerLink || '', '_blank'); + } + + function formatMetaValue(value: string) { + if (value.includes('http')) { + return { + valueText: value.startsWith('http') + ? truncateString(value, 20, 8) + : formatAddress(value), + valueLink: value, + }; + } + return { + valueText: value, + valueLink: undefined, + }; + } + return ( -
- - {objectData ? ( - <> - -
-
- - {nftId ? ( - } - /> - ) : null} -
- - {ownerExplorerLink ? ( - navigate(-1)}> +
+ + {objectData ? ( + <> +
+
+
+ + {nftId ? ( +
+
+ {isContainedInKiosk && kioskItem?.isLocked ? ( +
+
- ) : ( -
-
+ ) : ( +
+
+ )}
- )} -
- - ) : ( - - )} - -
+
+ + ) : ( + + )} + +
+ ); } diff --git a/apps/wallet/src/ui/app/pages/home/nft-transfer/index.tsx b/apps/wallet/src/ui/app/pages/home/nft-transfer/index.tsx index 23c9bcfe109..295a6f6679d 100644 --- a/apps/wallet/src/ui/app/pages/home/nft-transfer/index.tsx +++ b/apps/wallet/src/ui/app/pages/home/nft-transfer/index.tsx @@ -26,7 +26,7 @@ function NftTransferPage() { {nftId && !!ownedNFT && isAssetTransferable(ownedNFT) ? ( <>
- +
diff --git a/apps/wallet/src/ui/app/pages/home/nfts/VisualAssets.tsx b/apps/wallet/src/ui/app/pages/home/nfts/VisualAssets.tsx index f1e0a612efb..0da0ef12fab 100644 --- a/apps/wallet/src/ui/app/pages/home/nfts/VisualAssets.tsx +++ b/apps/wallet/src/ui/app/pages/home/nfts/VisualAssets.tsx @@ -64,12 +64,7 @@ export default function VisualAssets({ items }: VisualAssetsProps) { ) : null}
- +
diff --git a/apps/wallet/src/ui/app/shared/collapse/index.tsx b/apps/wallet/src/ui/app/shared/collapse/index.tsx index 50c36bffeaf..0d37f496d7e 100644 --- a/apps/wallet/src/ui/app/shared/collapse/index.tsx +++ b/apps/wallet/src/ui/app/shared/collapse/index.tsx @@ -2,9 +2,7 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { ChevronDown12, ChevronRight12 } from '@iota/icons'; -import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; -import cn from 'clsx'; +import { Accordion, AccordionContent, AccordionHeader, Title, TitleSize } from '@iota/apps-ui-kit'; import { useState, type ReactNode } from 'react'; interface CollapsibleProps { @@ -14,6 +12,7 @@ interface CollapsibleProps { shade?: 'lighter' | 'darker'; isOpen?: boolean; onOpenChange?: (isOpen: boolean) => void; + titleSize?: TitleSize; } export function Collapsible({ @@ -23,6 +22,7 @@ export function Collapsible({ isOpen, onOpenChange, shade = 'lighter', + titleSize = TitleSize.Small, }: CollapsibleProps) { const [open, setOpen] = useState(isOpen ?? defaultOpen ?? false); @@ -32,40 +32,11 @@ export function Collapsible({ }; return ( - - -
- {title} -
-
-
- {open ? : } -
- - - {children} - + + handleOpenChange(!open)}> + + </AccordionHeader> + <AccordionContent isExpanded={isOpen ?? open}>{children}</AccordionContent> + </Accordion> ); } diff --git a/apps/wallet/src/ui/app/shared/transaction-summary/cards/objectSummary/ObjectChangeDisplay.tsx b/apps/wallet/src/ui/app/shared/transaction-summary/cards/objectSummary/ObjectChangeDisplay.tsx index 29215fb4a7c..e07060e6ada 100644 --- a/apps/wallet/src/ui/app/shared/transaction-summary/cards/objectSummary/ObjectChangeDisplay.tsx +++ b/apps/wallet/src/ui/app/shared/transaction-summary/cards/objectSummary/ObjectChangeDisplay.tsx @@ -15,12 +15,7 @@ export function ObjectChangeDisplay({ change }: { change: IotaObjectChangeWithDi if (!display) return null; return ( <div className="group relative w-32 min-w-min cursor-pointer whitespace-nowrap"> - <NftImage - size="md" - name={display.name ?? ''} - borderRadius="xl" - src={display.image_url ?? ''} - /> + <NftImage title={display.name ?? ''} src={display.image_url ?? ''} /> {objectId && ( <div className="full absolute bottom-2 left-1/2 -translate-x-1/2 justify-center rounded-lg bg-white/90 px-2 py-1 opacity-0 transition-opacity group-hover:opacity-100"> <ExplorerLink