diff --git a/packages/react/stories/nft/NftMint.stories.tsx b/packages/react/stories/nft/NftMint.stories.tsx index 10597ad8..95fed576 100644 --- a/packages/react/stories/nft/NftMint.stories.tsx +++ b/packages/react/stories/nft/NftMint.stories.tsx @@ -1,3 +1,4 @@ +import * as React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import NftMint from "../../src/ui/nft-mint"; @@ -28,12 +29,29 @@ export const Primary: Story = { limited: 50, imgSrc: "https://res.cloudinary.com/stargaze/image/upload/f_auto,w_700/ighabxq1rjqr5xblblue", - starsPrice: 0.01063943, - onMint: () => { - console.log("onMint"); - }, - onChange: (value) => { + pricePerToken: 0.01063943, + tokenName: "STARS", + }, + render: (props) => { + const [amount, setAmount] = React.useState(0); + const onChange = (value) => { console.log("onChange", value); - }, + setAmount(value); + }; + + const onMint = () => { + setAmount(0); + console.log("minted and reset amount to 0"); + }; + + return ( + + ); }, }; diff --git a/packages/react/stories/nft/NftProfile.stories.tsx b/packages/react/stories/nft/NftProfile.stories.tsx index bc6fbfc7..5335d3e5 100644 --- a/packages/react/stories/nft/NftProfile.stories.tsx +++ b/packages/react/stories/nft/NftProfile.stories.tsx @@ -2,6 +2,7 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import NftProfile from "../../src/ui/nft-profile"; +import type { NftProfileCardProps } from "../../src/ui/nft-profile-card/nft-profile-card.types"; const meta: Meta = { component: NftProfile, @@ -14,10 +15,197 @@ export default meta; type Story = StoryObj; +const list1: NftProfileCardProps[] = [ + { + imgSrc: + "https://res.cloudinary.com/stargaze/image/upload/w_700/m4sxagqpu98z76lcggyj.jpg", + name: "Bad Kid #9501", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, + { + imgSrc: + "https://res.cloudinary.com/stargaze/image/upload/w_700/wnfkydgxvp2vf0fqq277.jpg", + name: "Bad Kid #9502", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, + { + imgSrc: + "https://res.cloudinary.com/stargaze/image/upload/w_700/zkmiu7m7jelttu6ewrvu.jpg", + name: "Bad Kid #9505", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, + { + imgSrc: + "https://res.cloudinary.com/stargaze/image/upload/w_700/yisqceq5imrkgrzml02b.jpg", + name: "Bad Kid #9506", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, + { + imgSrc: + "https://res.cloudinary.com/stargaze/image/upload/w_700/benc6jvttlv9nbemyxch.jpg", + name: "Bad Kid #9508", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, + { + imgSrc: + "https://res.cloudinary.com/stargaze/image/upload/w_700/ndkbjpwtgys09w1xxwny.jpg", + name: "Bad Kid #9509", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, +]; + +const list2: NftProfileCardProps[] = [ + { + imgSrc: + "https://ipfs-gw.stargaze-apis.com/ipfs/bafybeibl5rrhfola4m2gxyzfji63fxyboeic36pjxjmrnmgablqhy3mnvy/3054.png", + name: "Bad Kid #9501", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, + { + imgSrc: + "https://ipfs-gw.stargaze-apis.com/ipfs/bafybeibl5rrhfola4m2gxyzfji63fxyboeic36pjxjmrnmgablqhy3mnvy/6.png", + name: "Bad Kid #9502", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, + { + imgSrc: + "https://ipfs-gw.stargaze-apis.com/ipfs/bafybeicf6q3az6bk4fyilhgk7ytgkctxjb3jsm4542cxtb6r4xsb35zoni/images/608.png", + name: "Bad Kid #9505", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, + { + imgSrc: + "https://ipfs-gw.stargaze-apis.com/ipfs/bafybeicf6q3az6bk4fyilhgk7ytgkctxjb3jsm4542cxtb6r4xsb35zoni/images/444.png", + name: "Bad Kid #9506", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, + { + imgSrc: + "https://ipfs-gw.stargaze-apis.com/ipfs/bafybeicf6q3az6bk4fyilhgk7ytgkctxjb3jsm4542cxtb6r4xsb35zoni/images/668.png", + name: "Bad Kid #9508", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, + { + imgSrc: + "https://res.cloudinary.com/stargaze/image/upload/w_700/ndkbjpwtgys09w1xxwny.jpg", + name: "Bad Kid #9509", + priceItems: [ + { + label: "Highest offer", + value: "450", + }, + { + label: "Listing price", + value: "373", + }, + ], + }, +]; + /* This is primary NftProfile */ export const Primary: Story = { args: { title: "Profile", + thumbnailBehavior: "contain", headerButtonLabel: "View on Stargaze", meta: [ { @@ -38,49 +226,6 @@ export const Primary: Story = { onView() { console.log("onView"); }, - list: [ - { - imgSrc: - "https://res.cloudinary.com/stargaze/image/upload/w_700/m4sxagqpu98z76lcggyj.jpg", - name: "Bad Kid #9501", - highestOffer: "450", - listPrice: "373", - }, - { - imgSrc: - "https://res.cloudinary.com/stargaze/image/upload/w_700/wnfkydgxvp2vf0fqq277.jpg", - name: "Bad Kid #9502", - highestOffer: "450", - listPrice: "373", - }, - { - imgSrc: - "https://res.cloudinary.com/stargaze/image/upload/w_700/zkmiu7m7jelttu6ewrvu.jpg", - name: "Bad Kid #9505", - highestOffer: "450", - listPrice: "373", - }, - { - imgSrc: - "https://res.cloudinary.com/stargaze/image/upload/w_700/yisqceq5imrkgrzml02b.jpg", - name: "Bad Kid #9506", - highestOffer: "450", - listPrice: "373", - }, - { - imgSrc: - "https://res.cloudinary.com/stargaze/image/upload/w_700/benc6jvttlv9nbemyxch.jpg", - name: "Bad Kid #9508", - highestOffer: "450", - listPrice: "373", - }, - { - imgSrc: - "https://res.cloudinary.com/stargaze/image/upload/w_700/ndkbjpwtgys09w1xxwny.jpg", - name: "Bad Kid #9509", - highestOffer: "450", - listPrice: "373", - }, - ], + list: list2, }, }; diff --git a/src/styles/rainbow-sprinkles.css.ts b/src/styles/rainbow-sprinkles.css.ts index 07c035e5..5cf5be54 100644 --- a/src/styles/rainbow-sprinkles.css.ts +++ b/src/styles/rainbow-sprinkles.css.ts @@ -16,12 +16,19 @@ const allSpace = { ...themeVars.space, ...extendedSpace }; const margins = themeVars.space; const responsiveProperties = defineProperties({ - conditions: transformBreakpoints<{ mobile: {}; tablet: {}; desktop: {}, mdMobile: {} }>( - breakpoints - ), + conditions: transformBreakpoints<{ + mobile: {}; + tablet: {}; + desktop: {}; + mdMobile: {}; + }>(breakpoints), defaultCondition: "mobile", dynamicProperties: { display: true, + backgroundImage: true, + backgroundSize: true, + backgroundPosition: true, + backgroundRepeat: true, objectFit: true, flex: true, flexBasis: true, diff --git a/src/ui/icon/icon.lite.tsx b/src/ui/icon/icon.lite.tsx index 036a79eb..274d3824 100644 --- a/src/ui/icon/icon.lite.tsx +++ b/src/ui/icon/icon.lite.tsx @@ -709,8 +709,8 @@ export default function Icon(props: IconProps) { y="0px" viewBox="0 0 432 432" style={{ - "enable-background": "new 0 0 432 432", - "background-color": "black", + enableBackground: "new 0 0 432 432", + backgroundColor: "black", }} > diff --git a/src/ui/nft-detail/nft-detail.lite.tsx b/src/ui/nft-detail/nft-detail.lite.tsx index 0d65d37f..1e712e97 100644 --- a/src/ui/nft-detail/nft-detail.lite.tsx +++ b/src/ui/nft-detail/nft-detail.lite.tsx @@ -174,10 +174,10 @@ export default function NftDetail(props: NftDetailProps) { {/* ==== CTA */} + + {props.children} diff --git a/src/ui/nft-detail/nft-detail.types.tsx b/src/ui/nft-detail/nft-detail.types.tsx index 6f9d65bf..33fb4a17 100644 --- a/src/ui/nft-detail/nft-detail.types.tsx +++ b/src/ui/nft-detail/nft-detail.types.tsx @@ -4,7 +4,7 @@ import type { NftDetailInfoProps } from "../nft-detail-info/nft-detail-info.type import type { NftDetailTopOfferProps } from "../nft-detail-top-offers/nft-detail-top-offers.types"; import type { NftDetailActivityListProps } from "../nft-detail-activity-list/nft-detail-activity-list.types"; -export type DetailType = "listForSale" | "makeOffer" | "buyNow"; +export type DetailType = "listForSale" | "makeOffer" | "buyNow" | "custom"; interface BaseNftDetailProps extends BaseComponentProps { collectionName: string; @@ -23,6 +23,7 @@ interface BaseNftDetailProps extends BaseComponentProps { onDownload: (event?: any) => void; onShare: (event?: any) => void; attributes?: any; + children?: BaseComponentProps["children"]; } export type ListForSale = { @@ -43,6 +44,10 @@ export type BuyNow = { onMakeOffer: (event?: any) => void; }; -export type NftDetailVariant = ListForSale | MakeOffer | BuyNow; +export type Custom = { + type: "custom"; +}; + +export type NftDetailVariant = ListForSale | MakeOffer | BuyNow | Custom; export type NftDetailProps = NftDetailVariant & BaseNftDetailProps; diff --git a/src/ui/nft-mint/nft-mint.lite.tsx b/src/ui/nft-mint/nft-mint.lite.tsx index 6a1f5f04..711337bf 100644 --- a/src/ui/nft-mint/nft-mint.lite.tsx +++ b/src/ui/nft-mint/nft-mint.lite.tsx @@ -1,4 +1,9 @@ -import { useStore, Show, useMetadata } from "@builder.io/mitosis"; +import { + useStore, + Show, + useDefaultProps, + useMetadata, +} from "@builder.io/mitosis"; import BigNumber from "bignumber.js"; import Stack from "../stack"; import Text from "../text"; @@ -19,39 +24,49 @@ useMetadata({ }, }); +useDefaultProps>({ + mintButtonLabel: "Mint", + mintButtonDisabledLabel: "Insufficient balance", + tokenName: "STAR", +}); + export default function NftMint(props: NftMintProps) { const state = useStore<{ amount: number; starsAmount: string; starsAmountPrice: string; - isAffordable: boolean; - isMintLoading: boolean; handleAmountChange: (value: number) => void; + isControlled: () => boolean; }>({ amount: 0, starsAmount: "", starsAmountPrice: "", - isAffordable: true, - isMintLoading: false, + isControlled() { + return typeof props.amount !== "undefined"; + }, handleAmountChange(value: number) { + props?.onChange?.(value); + + // Update internal amount if uncontrolled + if (!state.isControlled()) { + state.amount = value; + } + let starsCount: BigNumber = new BigNumber(value).multipliedBy( props.priceDisplayAmount ); - props?.onChange?.(value); - state.amount = value; + if (new BigNumber(value || 0).eq(0)) { state.starsAmount = ""; state.starsAmountPrice = ""; - state.isAffordable = true; } else { state.starsAmount = starsCount.decimalPlaces(2).toString(); state.starsAmountPrice = store.getState().formatNumber({ value: starsCount - .multipliedBy(props.starsPrice) + .multipliedBy(props.pricePerToken) .decimalPlaces(2) .toString(), }); - state.isAffordable = starsCount.lt(props.available); } }, }); @@ -349,7 +364,7 @@ export default function NftMint(props: NftMintProps) { }} fontWeight="$semibold" > - {`${props?.available} STARS`} + {`${props?.available} ${props.tokenName}`} @@ -366,11 +381,18 @@ export default function NftMint(props: NftMintProps) { {/* @ts-expect-error */} state.handleAmountChange(value)} inputClassName={styles.baseInput} + formatOptions={{ + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }} /> {`${state.starsAmount} STARS`} + >{`${state.starsAmount} ${props.tokenName}`} {`≈ $${state.starsAmountPrice}`} @@ -415,11 +437,18 @@ export default function NftMint(props: NftMintProps) { {/* @ts-expect-error */} state.handleAmountChange(value)} inputClassName={styles.baseInput} + formatOptions={{ + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }} /> {`${state.starsAmount} STARS`} + >{`${state.starsAmount} ${props.tokenName}`} {`≈ $${state.starsAmountPrice}`} @@ -498,7 +527,7 @@ export default function NftMint(props: NftMintProps) { > {`${store.getState()?.formatNumber?.({ value: props?.priceDisplayAmount, - })} STARS`} + })} ${props.tokenName}`} props?.onMint?.()} - isLoading={state.isMintLoading} + isLoading={props.isMintLoading} > - {`${state.isAffordable ? "Mint" : "Insufficient Balance"}`} + {`${ + props.isMintButtonDisabled + ? props.mintButtonDisabledLabel + : props.mintButtonLabel + }`} @@ -548,11 +581,15 @@ export default function NftMint(props: NftMintProps) { size="lg" intent="tertiary" fluidWidth - disabled={new BigNumber(state.amount).lte(0) || !state.isAffordable} + disabled={props.isMintButtonDisabled} onClick={() => props?.onMint?.()} - isLoading={state.isMintLoading} + isLoading={props.isMintLoading} > - {`${state.isAffordable ? "Mint" : "Insufficient Balance"}`} + {`${ + props.isMintButtonDisabled + ? props.mintButtonDisabledLabel + : props.mintButtonLabel + }`} diff --git a/src/ui/nft-mint/nft-mint.types.tsx b/src/ui/nft-mint/nft-mint.types.tsx index dc2c29ef..3230e686 100644 --- a/src/ui/nft-mint/nft-mint.types.tsx +++ b/src/ui/nft-mint/nft-mint.types.tsx @@ -12,7 +12,17 @@ export interface NftMintProps extends BaseComponentProps { priceDisplayAmount: number | string; limited: number | string; imgSrc: string; - starsPrice: number | string; + // ==== Token props + tokenName?: string; + pricePerToken: number | string; + // ==== Amount to bid for NFT + amount?: number; + defaultAmount?: number; onChange?: (value: number) => void; + // ==== Mint button props + isMintButtonDisabled?: boolean; + isMintLoading?: boolean; + mintButtonLabel?: string; + mintButtonDisabledLabel?: string; onMint: (event?: any) => void; } diff --git a/src/ui/nft-profile-card-list/nft-profile-card-list-types.tsx b/src/ui/nft-profile-card-list/nft-profile-card-list-types.tsx index 54c2edc0..7b667892 100644 --- a/src/ui/nft-profile-card-list/nft-profile-card-list-types.tsx +++ b/src/ui/nft-profile-card-list/nft-profile-card-list-types.tsx @@ -2,6 +2,7 @@ import type { BaseComponentProps } from "../../models/components.model"; import type { NftProfileCardProps } from "../nft-profile-card/nft-profile-card.types"; export interface NftProfileCardListProps extends BaseComponentProps { + thumbnailBehavior?: NftProfileCardProps["thumbnailBehavior"]; list: NftProfileCardProps[]; attributes?: any; } diff --git a/src/ui/nft-profile-card-list/nft-profile-card-list.lite.tsx b/src/ui/nft-profile-card-list/nft-profile-card-list.lite.tsx index 90076c5a..852a1524 100644 --- a/src/ui/nft-profile-card-list/nft-profile-card-list.lite.tsx +++ b/src/ui/nft-profile-card-list/nft-profile-card-list.lite.tsx @@ -27,8 +27,8 @@ export default function NftProfileCardList(props: NftProfileCardListProps) { key={item.imgSrc} name={item?.name} imgSrc={item?.imgSrc} - highestOffer={item?.highestOffer} - listPrice={item?.listPrice} + priceItems={item.priceItems} + thumbnailBehavior={props.thumbnailBehavior} /> )} diff --git a/src/ui/nft-profile-card/nft-profile-card.lite.tsx b/src/ui/nft-profile-card/nft-profile-card.lite.tsx index 35015ff3..4167795d 100644 --- a/src/ui/nft-profile-card/nft-profile-card.lite.tsx +++ b/src/ui/nft-profile-card/nft-profile-card.lite.tsx @@ -1,4 +1,4 @@ -import { useMetadata } from "@builder.io/mitosis"; +import { Show, For, useMetadata, useDefaultProps } from "@builder.io/mitosis"; import clsx from "clsx"; import Stack from "../stack"; import Box from "../box"; @@ -13,6 +13,10 @@ useMetadata({ }, }); +useDefaultProps>({ + thumbnailBehavior: "contain", +}); + export default function NftProfileCard(props: NftProfileCardProps) { return ( props?.onClick?.() }} > - - - + + {/* Show full image dimensions within the image component */} + + {/* Border background in case the image dimension doesn't fit */} + + + + - {props.name} + {/* Show crop-to-fit image dimensions, forced aspect ratio */} + + + + + - + {props.name} - + + {(priceItem, index) => ( + + )} + ); diff --git a/src/ui/nft-profile-card/nft-profile-card.types.tsx b/src/ui/nft-profile-card/nft-profile-card.types.tsx index 5a4ff194..d34285d2 100644 --- a/src/ui/nft-profile-card/nft-profile-card.types.tsx +++ b/src/ui/nft-profile-card/nft-profile-card.types.tsx @@ -1,12 +1,23 @@ import type { BaseComponentProps } from "../../models/components.model"; import type { Sprinkles } from "../../styles/rainbow-sprinkles.css"; +export type ProfileCardPriceItem = { + label: string; + value: string | number; + iconSrc?: string; + tokenName?: string; + onClick?: (event?: any) => void; +}; + export interface NftProfileCardProps extends BaseComponentProps { + // ==== Controls the thumbnail display behavior + // "full" means the img will have its original aspect ratio fit within the square + // "contain" means the img will have a fixed aspect ratio to fit the square + thumbnailBehavior?: "full" | "contain"; width?: Sprinkles["width"]; imgSrc: string; name: string; - highestOffer: string; - listPrice: string; + priceItems: [ProfileCardPriceItem, ProfileCardPriceItem]; onClick?: (event?: any) => void; attributes?: any; } diff --git a/src/ui/nft-profile/nft-profile.lite.tsx b/src/ui/nft-profile/nft-profile.lite.tsx index bd37e841..3a01c71e 100644 --- a/src/ui/nft-profile/nft-profile.lite.tsx +++ b/src/ui/nft-profile/nft-profile.lite.tsx @@ -126,7 +126,14 @@ export default function NftProfile(props: NftProfileProps) { - + + + + + {props.children} ); } diff --git a/src/ui/nft-profile/nft-profile.types.tsx b/src/ui/nft-profile/nft-profile.types.tsx index ac166517..c812aba3 100644 --- a/src/ui/nft-profile/nft-profile.types.tsx +++ b/src/ui/nft-profile/nft-profile.types.tsx @@ -13,6 +13,7 @@ export interface NftProfileProps extends BaseComponentProps { name: string; isVerified: boolean; list: NftProfileCardProps[]; + thumbnailBehavior?: NftProfileCardProps["thumbnailBehavior"]; onView: (event?: any) => void; attributes?: any; } diff --git a/src/ui/star-text/star-text.lite.tsx b/src/ui/star-text/star-text.lite.tsx index 332ab274..54bd9e9d 100644 --- a/src/ui/star-text/star-text.lite.tsx +++ b/src/ui/star-text/star-text.lite.tsx @@ -1,4 +1,5 @@ import { Show, useMetadata } from "@builder.io/mitosis"; +import Box from "../box"; import Stack from "../stack"; import Icon from "../icon"; import Text from "../text"; @@ -13,26 +14,43 @@ useMetadata({ export default function StarText(props: StarTextProps) { return ( - - - - {props.label} - - +
+ + + + {props.label} + + - {`${props?.value} STARS`} + {`${ + props.value + } ${props.tokenName ?? "STARS"}`} - - + + + + + + + + +
); } diff --git a/src/ui/star-text/star-text.types.tsx b/src/ui/star-text/star-text.types.tsx index 01121c0c..890ad945 100644 --- a/src/ui/star-text/star-text.types.tsx +++ b/src/ui/star-text/star-text.types.tsx @@ -1,4 +1,9 @@ -export interface StarTextProps { +import type { BaseComponentProps } from "../../models/components.model"; + +export interface StarTextProps extends BaseComponentProps { label?: string; value: string | number; + tokenName?: string; + iconSrc?: string; + onClick?: (event?: any) => void; }