@0xsquid/ui 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -21410,7 +21410,7 @@ const mainImageSizeMap = {
21410
21410
  large: "xxlarge",
21411
21411
  small: "xlarge",
21412
21412
  };
21413
- function TokenGroup({ isSelected = false, tokenItems, priceChange, balance, balanceUsd, extraSpacing = true, chainImageUrl, imageUrl, symbol, name, onClick, variant = "large", className, onChildClick, isAdjacentToPreviousExpanded = false, isAdjacentToNextExpanded = false, }) {
21413
+ function TokenGroup({ isSelected = false, tokenItems, priceChange, balance, balanceUsd, extraSpacing = true, chainImageUrl, imageUrl, fallbackImageUrl, symbol, name, onClick, variant = "large", className, onChildClick, isAdjacentToPreviousExpanded = false, isAdjacentToNextExpanded = false, }) {
21414
21414
  var _a;
21415
21415
  const [isCollapsed, setIsCollapsed] = React$1.useState(true);
21416
21416
  const isCollapsible = tokenItems && tokenItems.length > 0;
@@ -21445,7 +21445,7 @@ function TokenGroup({ isSelected = false, tokenItems, priceChange, balance, bala
21445
21445
  : "tw-h-squid-xl tw-gap-squid-xxs"
21446
21446
  : isSmallVariant
21447
21447
  ? "tw-h-[50px] tw-gap-squid-s tw-py-squid-xxs"
21448
- : "tw-h-squid-xl tw-gap-squid-xxs"), children: [jsxRuntime.jsxs("div", { className: "tw-relative tw-min-w-fit", children: [jsxRuntime.jsx(Image$1, { src: imageUrl, size: isLargeVariant && !isCollapsed
21448
+ : "tw-h-squid-xl tw-gap-squid-xxs"), children: [jsxRuntime.jsxs("div", { className: "tw-relative tw-min-w-fit", children: [jsxRuntime.jsx(Image$1, { src: imageUrl, placeholderImageUrl: fallbackImageUrl, size: isLargeVariant && !isCollapsed
21449
21449
  ? "medium"
21450
21450
  : (_a = mainImageSizeMap[variant]) !== null && _a !== void 0 ? _a : "medium", rounded: isCompactVariant ? "xxs" : "full", style: {
21451
21451
  transition: `${ANIMATION_DURATIONS.GENERIC}ms ${ANIMATION_TIMINGS.GENERIC}`,
@@ -21762,6 +21762,7 @@ const DEFAULT_TIMEFRAME = "1D";
21762
21762
  const DefaultChart = ({ timeframe, }) => (jsxRuntime.jsx("div", { className: "tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center", children: jsxRuntime.jsxs(BodyText, { size: "small", children: ["Chart (", timeframe, ")"] }) }));
21763
21763
  const TokenModalHeader = ({ token, chain, onLikeToken, onClose, isFavorite, }) => (jsxRuntime.jsxs("div", { className: "tw-flex tw-w-full tw-items-start tw-justify-between", children: [jsxRuntime.jsx("div", { className: "tw-flex tw-items-center tw-gap-squid-xs tw-pb-squid-xs", children: jsxRuntime.jsx(BadgeImage, { image: {
21764
21764
  src: token.image,
21765
+ placeholderImageUrl: token.fallbackImage,
21765
21766
  size: "xxlarge",
21766
21767
  rounded: "full",
21767
21768
  shadow: true,
@@ -21772,7 +21773,7 @@ const TokenModalHeader = ({ token, chain, onLikeToken, onClose, isFavorite, }) =
21772
21773
  border: "outline-lg",
21773
21774
  } }) }), jsxRuntime.jsxs("span", { className: "tw-absolute tw-right-squid-xs tw-top-squid-xs tw-flex tw-items-center tw-gap-squid-xxs", children: [jsxRuntime.jsx(IconButton, { className: "tw-group/token-detail-fav-icon tw-text-grey-300", onClick: onLikeToken, icon: isFavorite ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(FilledHeartIcon, { className: "tw-block group-hover/token-detail-fav-icon:tw-hidden" }), jsxRuntime.jsx(BrokenHeartIcon, { className: "tw-hidden group-hover/token-detail-fav-icon:tw-block" })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(EmptyHeartIcon, { className: "tw-block group-hover/token-detail-fav-icon:tw-hidden" }), jsxRuntime.jsx(FilledHeartIcon, { className: "tw-hidden group-hover/token-detail-fav-icon:tw-block" })] })) }), jsxRuntime.jsx(IconButton, { className: "tw-text-grey-300", onClick: onClose, icon: jsxRuntime.jsx(CircleXFilledIcon, {}) })] })] }));
21774
21775
  const TokenInfo = ({ token, tokenPrice, priceChange, loading, }) => (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx(BodyText, { size: "large", loading: { isLoading: loading, width: "100px" }, children: tokenPrice.toLocaleString() }), jsxRuntime.jsxs("div", { className: "tw-flex tw-flex-row tw-items-center tw-justify-between", children: [jsxRuntime.jsx(BodyText, { size: "small", children: token.name }), jsxRuntime.jsx(PriceChange, { value: priceChange, variant: "large" })] })] }));
21775
- const BalanceInfo = ({ balance, balanceUSD, token, loading, }) => (jsxRuntime.jsxs("div", { className: "tw-flex tw-flex-col", children: [jsxRuntime.jsx(CaptionText, { className: "tw-text-grey-500", children: "Balance" }), jsxRuntime.jsxs("div", { className: "tw-flex tw-flex-row tw-justify-between", children: [jsxRuntime.jsxs("span", { className: "tw-flex tw-flex-row tw-items-center tw-gap-squid-xxs", children: [jsxRuntime.jsx("img", { src: token.image, alt: token.name, className: "tw-h-squid-m tw-w-squid-m tw-rounded-full" }), jsxRuntime.jsxs(BodyText, { size: "small", loading: { isLoading: loading, width: "80px" }, children: [balance, " ", token.symbol] })] }), jsxRuntime.jsxs(CaptionText, { className: "tw-text-grey-500", loading: { isLoading: loading, width: "60px" }, children: [jsxRuntime.jsx("span", { className: "tw-opacity-66", children: "$" }), balanceUSD] })] })] }));
21776
+ const BalanceInfo = ({ balance, balanceUSD, token, loading, }) => (jsxRuntime.jsxs("div", { className: "tw-flex tw-flex-col", children: [jsxRuntime.jsx(CaptionText, { className: "tw-text-grey-500", children: "Balance" }), jsxRuntime.jsxs("div", { className: "tw-flex tw-flex-row tw-justify-between", children: [jsxRuntime.jsxs("span", { className: "tw-flex tw-flex-row tw-items-center tw-gap-squid-xxs", children: [jsxRuntime.jsx(Image$1, { src: token.image, placeholderImageUrl: token.fallbackImage, alt: token.name, size: "medium", rounded: "full" }), jsxRuntime.jsxs(BodyText, { size: "small", loading: { isLoading: loading, width: "80px" }, children: [balance, " ", token.symbol] })] }), jsxRuntime.jsxs(CaptionText, { className: "tw-text-grey-500", loading: { isLoading: loading, width: "60px" }, children: [jsxRuntime.jsx("span", { className: "tw-opacity-66", children: "$" }), balanceUSD] })] })] }));
21776
21777
  const MarketDataItem = ({ label, value, showDollar, loading, }) => (jsxRuntime.jsxs("div", { className: "tw-flex tw-flex-col", children: [jsxRuntime.jsx(CaptionText, { className: "tw-text-grey-500", children: label }), jsxRuntime.jsxs(CaptionText, { loading: { isLoading: loading, width: "60px" }, children: [showDollar && jsxRuntime.jsx("span", { className: "tw-text-grey-500", children: "$" }), value] })] }));
21777
21778
  const TokenMarketData = ({ volume24h, marketCap, totalSupply, loading, }) => (jsxRuntime.jsxs("div", { className: "tw-mb-squid-xs tw-flex tw-h-full tw-w-full tw-flex-row tw-items-center tw-justify-between", children: [jsxRuntime.jsx(MarketDataItem, { label: "24h volume", value: volume24h, showDollar: true, loading: loading }), jsxRuntime.jsx(MarketDataItem, { label: "Market cap", value: marketCap, showDollar: true, loading: loading }), jsxRuntime.jsx(MarketDataItem, { label: "Total supply", value: totalSupply, loading: loading })] }));
21778
21779
  const FooterButton = (_a) => {
@@ -28126,6 +28127,10 @@ const outlineWidthMap = {
28126
28127
  function AssetsButton({ chain, token, onClick, variant = "primary", isLoading = false, tooltip, emptyTokenLabel = "Select token", }) {
28127
28128
  var _a, _b, _c, _d;
28128
28129
  const [tokenImageLoadingState, setTokenImageLoadingState] = React$1.useState(ImageLoadingState.LOADING);
28130
+ const [fallbackImageLoadingState, setFallbackImageLoadingState] = React$1.useState(ImageLoadingState.LOADING);
28131
+ const isImgLoadError = tokenImageLoadingState === ImageLoadingState.ERROR;
28132
+ const isFallbackImgLoadError = fallbackImageLoadingState === ImageLoadingState.ERROR;
28133
+ const showTextFallback = (isImgLoadError && !(token === null || token === void 0 ? void 0 : token.fallbackIconUrl)) || isFallbackImgLoadError;
28129
28134
  const fallbackBgColor = useColorFromSeed((_a = token === null || token === void 0 ? void 0 : token.symbol) !== null && _a !== void 0 ? _a : "");
28130
28135
  const state = React$1.useMemo(() => {
28131
28136
  if (chain != null && !isLoading) {
@@ -28142,14 +28147,12 @@ function AssetsButton({ chain, token, onClick, variant = "primary", isLoading =
28142
28147
  const defaultBgColor = `var(${themeKeysToCssVariables.color[themeKeyVariantMap[variant]].cssVariable})`;
28143
28148
  const outlineWidth = outlineWidthMap[state];
28144
28149
  const chainBgColor = (_b = chain === null || chain === void 0 ? void 0 : chain.bgColor) !== null && _b !== void 0 ? _b : defaultBgColor;
28145
- const tokenTextColor = tokenImageLoadingState === ImageLoadingState.ERROR
28146
- ? "#fff"
28147
- : token === null || token === void 0 ? void 0 : token.textColor;
28150
+ const tokenTextColor = isImgLoadError ? "#fff" : token === null || token === void 0 ? void 0 : token.textColor;
28148
28151
  const tokenBgColor = React$1.useMemo(() => {
28149
28152
  if (state !== AssetsButtonState.FULL) {
28150
28153
  return defaultBgColor;
28151
28154
  }
28152
- if (tokenImageLoadingState === ImageLoadingState.ERROR) {
28155
+ if (showTextFallback) {
28153
28156
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
28154
28157
  return fallbackBgColor || defaultBgColor;
28155
28158
  }
@@ -28159,7 +28162,7 @@ function AssetsButton({ chain, token, onClick, variant = "primary", isLoading =
28159
28162
  state,
28160
28163
  token === null || token === void 0 ? void 0 : token.bgColor,
28161
28164
  defaultBgColor,
28162
- tokenImageLoadingState,
28165
+ showTextFallback,
28163
28166
  fallbackBgColor,
28164
28167
  ]);
28165
28168
  const bgGradient = React$1.useMemo(() => {
@@ -28177,13 +28180,22 @@ function AssetsButton({ chain, token, onClick, variant = "primary", isLoading =
28177
28180
  ? undefined
28178
28181
  : tooltip === null || tooltip === void 0 ? void 0 : tooltip.tooltipContent, children: jsxRuntime.jsxs(ButtonTag, { onClick: state === AssetsButtonState.LOADING ? undefined : onClick, disabled: state === AssetsButtonState.LOADING, "aria-disabled": state === AssetsButtonState.LOADING, className: cn("tw-relative tw-flex tw-h-squid-xl tw-w-fit tw-items-center focus:tw-outline-none", buttonTextClassNameMap[variant], state === AssetsButtonState.LOADING
28179
28182
  ? "tw-cursor-not-allowed"
28180
- : "tw-group/assets-button"), children: [jsxRuntime.jsx("svg", { className: "tw-absolute tw-left-0 tw-z-10 tw-h-[40px] tw-w-[72px] tw-text-material-light-thin", viewBox: "0 0 72 40", xmlns: "http://www.w3.org/2000/svg", fill: "currentColor", children: jsxRuntime.jsx("path", { d: "m57.86 5.86c-.53.53-1.05 1.06-1.56 1.59-3.29 3.38-6.37 6.55-10.3 6.55s-7.01-3.17-10.3-6.55c-.51-.53-1.03-1.06-1.56-1.59-3.9-3.91-9.02-5.86-14.14-5.86s-10.24 1.95-14.14 5.86c-3.91 3.9-5.86 9.02-5.86 14.14s1.95 10.24 5.86 14.14c3.91 3.91 9.02 5.86 14.14 5.86s10.24-1.95 14.14-5.86c.52-.53 1.04-1.06 1.55-1.58 3.29-3.39 6.37-6.56 10.31-6.56s7.02 3.17 10.31 6.56c.51.53 1.03 1.06 1.55 1.58 3.91 3.91 9.02 5.86 14.14 5.86v-40c-5.12 0-10.24 1.95-14.14 5.86zm14.14 33.14c-5.07 0-9.85-1.98-13.43-5.56-.52-.52-1.02-1.04-1.54-1.57-3.43-3.53-6.67-6.86-11.02-6.86s-7.6 3.33-11.04 6.88c-.5.52-1.01 1.04-1.53 1.56-3.59 3.59-8.36 5.57-13.44 5.57s-9.85-1.98-13.43-5.57c-3.59-3.59-5.56-8.36-5.56-13.43s1.98-9.85 5.56-13.44c3.58-3.6 8.35-5.58 13.43-5.58s9.85 1.98 13.44 5.57c.52.52 1.04 1.05 1.55 1.58 3.43 3.53 6.66 6.86 11.02 6.86s7.59-3.33 11.02-6.86c.51-.52 1.02-1.05 1.55-1.58 3.59-3.59 8.36-5.56 13.43-5.56v38z" }) }), jsxRuntime.jsxs("svg", { className: "tw-absolute -tw-left-[2px] -tw-top-[2px] tw-z-20 tw-hidden group-hover/assets-button:tw-block", xmlns: "http://www.w3.org/2000/svg", width: outlineWidth.inner, height: "44", viewBox: `0 0 ${outlineWidth.inner} 44`, fill: "none", children: [jsxRuntime.jsx("clipPath", { id: "halfClip1", children: jsxRuntime.jsx("rect", { x: "0", y: "0", width: outlineWidth.inner, height: "44" }) }), jsxRuntime.jsx("path", { d: "M59.1506 36.849L59.1508 36.8492C63.2511 40.9496 68.6272 43 74.0001 43H145C156.598 43 166 33.598 166 22C166 10.402 156.598 1 145 1H74.0001C68.6272 0.999977 63.2511 3.05042 59.1508 7.15076C58.6183 7.68325 58.0958 8.22097 57.5842 8.74755L57.5817 8.75009C55.9228 10.4574 54.3925 12.0283 52.8031 13.1834C51.2282 14.3279 49.6791 15 48 15C46.3209 15 44.7718 14.3279 43.1969 13.1834C41.6075 12.0283 40.0772 10.4574 38.4183 8.75009L38.4159 8.74759C37.9042 8.22101 37.3817 7.68327 36.8492 7.15076C32.7489 3.05042 27.3728 0.999977 21.9999 1C16.6271 1.00002 11.251 3.05047 7.15076 7.15076C3.05045 11.2511 1 16.6272 1 22C1 27.3728 3.05045 32.7489 7.15076 36.8492C11.251 40.9495 16.6271 43 21.9999 43C27.3728 43 32.7489 40.9496 36.8492 36.8492L36.8494 36.849C37.3794 36.3188 37.8994 35.7834 38.4088 35.2589L38.4108 35.2569C40.0704 33.5483 41.6019 31.9756 43.1928 30.8191C44.7691 29.6731 46.3197 29 48 29C49.6803 29 51.2309 29.6731 52.8072 30.8191C54.3981 31.9756 55.9296 33.5483 57.5892 35.2569L57.5912 35.2589C58.1006 35.7834 58.6206 36.3188 59.1506 36.849Z", stroke: "currentColor", className: "tw-text-grey-900", strokeWidth: "2", clipPath: "url(#halfClip1)" })] }), jsxRuntime.jsxs("svg", { className: "tw-absolute -tw-left-[4px] -tw-top-[4px] tw-z-10 tw-hidden group-hover/assets-button:tw-block", xmlns: "http://www.w3.org/2000/svg", width: outlineWidth.outer, height: "48", viewBox: `0 0 ${outlineWidth.outer} 48`, fill: "none", children: [jsxRuntime.jsx("clipPath", { id: "halfClip2", children: jsxRuntime.jsx("rect", { x: "0", y: "0", width: outlineWidth.outer, height: "48" }) }), jsxRuntime.jsx("path", { d: "M60.4432 39.5559L60.4437 39.5564C64.7391 43.8517 70.3729 46 76.0001 46H147C159.15 46 169 36.1503 169 24C169 11.8497 159.15 2 147 2H76.0001C70.3729 1.99998 64.7391 4.14825 60.4437 8.44365C59.9062 8.98109 59.3797 9.52298 58.8697 10.0479L58.8645 10.0532C57.1905 11.7761 55.7217 13.2796 54.2152 14.3745C52.7378 15.4481 51.3923 16 50 16C48.6077 16 47.2622 15.4481 45.7848 14.3745C44.2783 13.2796 42.8095 11.7761 41.1355 10.0532L41.1305 10.0481C40.6205 9.52314 40.0939 8.98117 39.5563 8.44365C35.2609 4.14825 29.6271 1.99998 23.9999 2C18.3727 2.00002 12.739 4.1483 8.44365 8.44365C4.14827 12.739 2 18.3728 2 24C2 29.6272 4.14827 35.261 8.44365 39.5564C12.739 43.8517 18.3727 46 23.9999 46C29.6271 46 35.2609 43.8517 39.5563 39.5564L39.5568 39.5559C40.0917 39.0207 40.6159 38.481 41.1237 37.9582L41.1281 37.9537C42.8028 36.2295 44.2728 34.7243 45.7808 33.6279C47.2597 32.5528 48.6066 32 50 32C51.3934 32 52.7403 32.5528 54.2192 33.6279C55.7272 34.7243 57.1972 36.2295 58.8719 37.9537L58.8763 37.9582C59.3841 38.481 59.9083 39.0207 60.4432 39.5559Z", stroke: "currentColor", className: "tw-text-material-light-average", strokeWidth: "4", clipPath: "url(#halfClip2)" })] }), jsxRuntime.jsx("div", { className: cn("tw-assets-button-mask tw-flex tw-h-squid-xl tw-w-[92px] tw-items-center tw-justify-between", state !== AssetsButtonState.FULL && "!tw-w-[72px]"), style: {
28183
+ : "tw-group/assets-button"), children: [jsxRuntime.jsx("svg", { className: "tw-absolute tw-left-0 tw-z-10 tw-h-[40px] tw-w-[72px] tw-text-material-light-thin", viewBox: "0 0 72 40", xmlns: "http://www.w3.org/2000/svg", fill: "currentColor", children: jsxRuntime.jsx("path", { d: "m57.86 5.86c-.53.53-1.05 1.06-1.56 1.59-3.29 3.38-6.37 6.55-10.3 6.55s-7.01-3.17-10.3-6.55c-.51-.53-1.03-1.06-1.56-1.59-3.9-3.91-9.02-5.86-14.14-5.86s-10.24 1.95-14.14 5.86c-3.91 3.9-5.86 9.02-5.86 14.14s1.95 10.24 5.86 14.14c3.91 3.91 9.02 5.86 14.14 5.86s10.24-1.95 14.14-5.86c.52-.53 1.04-1.06 1.55-1.58 3.29-3.39 6.37-6.56 10.31-6.56s7.02 3.17 10.31 6.56c.51.53 1.03 1.06 1.55 1.58 3.91 3.91 9.02 5.86 14.14 5.86v-40c-5.12 0-10.24 1.95-14.14 5.86zm14.14 33.14c-5.07 0-9.85-1.98-13.43-5.56-.52-.52-1.02-1.04-1.54-1.57-3.43-3.53-6.67-6.86-11.02-6.86s-7.6 3.33-11.04 6.88c-.5.52-1.01 1.04-1.53 1.56-3.59 3.59-8.36 5.57-13.44 5.57s-9.85-1.98-13.43-5.57c-3.59-3.59-5.56-8.36-5.56-13.43s1.98-9.85 5.56-13.44c3.58-3.6 8.35-5.58 13.43-5.58s9.85 1.98 13.44 5.57c.52.52 1.04 1.05 1.55 1.58 3.43 3.53 6.66 6.86 11.02 6.86s7.59-3.33 11.02-6.86c.51-.52 1.02-1.05 1.55-1.58 3.59-3.59 8.36-5.56 13.43-5.56v38z" }) }), jsxRuntime.jsxs("svg", { className: "tw-absolute -tw-left-[2px] -tw-top-[2px] tw-z-20 tw-hidden group-hover/assets-button:tw-block", xmlns: "http://www.w3.org/2000/svg", width: outlineWidth.inner, height: "44", viewBox: `0 0 ${outlineWidth.inner} 44`, fill: "none", children: [jsxRuntime.jsx("clipPath", { id: "halfClip1", children: jsxRuntime.jsx("rect", { x: "0", y: "0", width: outlineWidth.inner, height: "44" }) }), jsxRuntime.jsx("path", { d: "M59.1506 36.849L59.1508 36.8492C63.2511 40.9496 68.6272 43 74.0001 43H145C156.598 43 166 33.598 166 22C166 10.402 156.598 1 145 1H74.0001C68.6272 0.999977 63.2511 3.05042 59.1508 7.15076C58.6183 7.68325 58.0958 8.22097 57.5842 8.74755L57.5817 8.75009C55.9228 10.4574 54.3925 12.0283 52.8031 13.1834C51.2282 14.3279 49.6791 15 48 15C46.3209 15 44.7718 14.3279 43.1969 13.1834C41.6075 12.0283 40.0772 10.4574 38.4183 8.75009L38.4159 8.74759C37.9042 8.22101 37.3817 7.68327 36.8492 7.15076C32.7489 3.05042 27.3728 0.999977 21.9999 1C16.6271 1.00002 11.251 3.05047 7.15076 7.15076C3.05045 11.2511 1 16.6272 1 22C1 27.3728 3.05045 32.7489 7.15076 36.8492C11.251 40.9495 16.6271 43 21.9999 43C27.3728 43 32.7489 40.9496 36.8492 36.8492L36.8494 36.849C37.3794 36.3188 37.8994 35.7834 38.4088 35.2589L38.4108 35.2569C40.0704 33.5483 41.6019 31.9756 43.1928 30.8191C44.7691 29.6731 46.3197 29 48 29C49.6803 29 51.2309 29.6731 52.8072 30.8191C54.3981 31.9756 55.9296 33.5483 57.5892 35.2569L57.5912 35.2589C58.1006 35.7834 58.6206 36.3188 59.1506 36.849Z", stroke: "currentColor", className: "tw-text-grey-900", strokeWidth: "2", clipPath: "url(#halfClip1)" })] }), jsxRuntime.jsxs("svg", { className: "tw-absolute -tw-left-[4px] -tw-top-[4px] tw-z-10 tw-hidden group-hover/assets-button:tw-block", xmlns: "http://www.w3.org/2000/svg", width: outlineWidth.outer, height: "48", viewBox: `0 0 ${outlineWidth.outer} 48`, fill: "none", children: [jsxRuntime.jsx("clipPath", { id: "halfClip2", children: jsxRuntime.jsx("rect", { x: "0", y: "0", width: outlineWidth.outer, height: "48" }) }), jsxRuntime.jsx("path", { d: "M60.4432 39.5559L60.4437 39.5564C64.7391 43.8517 70.3729 46 76.0001 46H147C159.15 46 169 36.1503 169 24C169 11.8497 159.15 2 147 2H76.0001C70.3729 1.99998 64.7391 4.14825 60.4437 8.44365C59.9062 8.98109 59.3797 9.52298 58.8697 10.0479L58.8645 10.0532C57.1905 11.7761 55.7217 13.2796 54.2152 14.3745C52.7378 15.4481 51.3923 16 50 16C48.6077 16 47.2622 15.4481 45.7848 14.3745C44.2783 13.2796 42.8095 11.7761 41.1355 10.0532L41.1305 10.0481C40.6205 9.52314 40.0939 8.98117 39.5563 8.44365C35.2609 4.14825 29.6271 1.99998 23.9999 2C18.3727 2.00002 12.739 4.1483 8.44365 8.44365C4.14827 12.739 2 18.3728 2 24C2 29.6272 4.14827 35.261 8.44365 39.5564C12.739 43.8517 18.3727 46 23.9999 46C29.6271 46 35.2609 43.8517 39.5563 39.5564L39.5568 39.5559C40.0917 39.0207 40.6159 38.481 41.1237 37.9582L41.1281 37.9537C42.8028 36.2295 44.2728 34.7243 45.7808 33.6279C47.2597 32.5528 48.6066 32 50 32C51.3934 32 52.7403 32.5528 54.2192 33.6279C55.7272 34.7243 57.1972 36.2295 58.8719 37.9537L58.8763 37.9582C59.3841 38.481 59.9083 39.0207 60.4432 39.5559Z", stroke: "currentColor", className: "tw-text-material-light-average", strokeWidth: "4", clipPath: "url(#halfClip2)" })] }), jsxRuntime.jsx("div", { className: cn("tw-flex tw-h-squid-xl tw-w-[92px] tw-items-center tw-justify-between tw-assets-button-mask", state !== AssetsButtonState.FULL && "!tw-w-[72px]"), style: {
28181
28184
  backgroundImage: bgGradient,
28182
28185
  }, children: jsxRuntime.jsx("div", { className: clsx(imageWrapperClassName), children: state === AssetsButtonState.CHAIN_ONLY ||
28183
28186
  state === AssetsButtonState.FULL ? (jsxRuntime.jsx("img", { src: chain === null || chain === void 0 ? void 0 : chain.iconUrl, alt: "", className: "tw-h-squid-xl tw-w-squid-xl tw-rounded-full tw-object-cover" })) : (state !== AssetsButtonState.LOADING && (jsxRuntime.jsx("span", { className: emptyChainIconTextClassNameMap[variant], children: jsxRuntime.jsx(PlusIcon, {}) }))) }) }), state === AssetsButtonState.FULL ? (jsxRuntime.jsxs("div", { className: clsx(imageWrapperClassName, "tw-absolute tw-left-[54px] tw-z-[5] tw-h-squid-xl tw-overflow-hidden"), children: [jsxRuntime.jsx("div", { className: "tw-absolute tw-right-0 tw-h-full tw-w-[22px]", style: {
28184
28187
  backgroundColor: tokenBgColor,
28185
- } }), tokenImageLoadingState === ImageLoadingState.ERROR && (
28186
- // show fallback
28188
+ } }), isImgLoadError && (token === null || token === void 0 ? void 0 : token.fallbackIconUrl) && (
28189
+ // fallback image
28190
+ jsxRuntime.jsx("img", { src: token === null || token === void 0 ? void 0 : token.fallbackIconUrl, alt: token === null || token === void 0 ? void 0 : token.symbol, onLoad: () => {
28191
+ setFallbackImageLoadingState(ImageLoadingState.LOADED);
28192
+ }, onError: () => {
28193
+ setFallbackImageLoadingState(ImageLoadingState.ERROR);
28194
+ }, className: cn(tokenImageClassName,
28195
+ // when image load failed, hide it with opacity: 0 instead of display: none
28196
+ // so new images can load and update the loading state
28197
+ isFallbackImgLoadError && "tw-opacity-0") })), showTextFallback && (
28198
+ // fallback text
28187
28199
  jsxRuntime.jsx("div", { style: { color: tokenTextColor }, className: cn(tokenImageClassName, "tw-flex tw-items-center tw-justify-center tw-text-body-small"), children: (_d = (_c = token === null || token === void 0 ? void 0 : token.symbol) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.toUpperCase() })), jsxRuntime.jsx("img", { onLoad: () => {
28188
28200
  setTokenImageLoadingState(ImageLoadingState.LOADED);
28189
28201
  }, onError: () => {
@@ -28191,8 +28203,7 @@ function AssetsButton({ chain, token, onClick, variant = "primary", isLoading =
28191
28203
  }, src: token === null || token === void 0 ? void 0 : token.iconUrl, alt: token === null || token === void 0 ? void 0 : token.symbol, className: cn(tokenImageClassName,
28192
28204
  // when image load failed, hide it with opacity: 0 instead of display: none
28193
28205
  // so new images can load and update the loading state
28194
- tokenImageLoadingState === ImageLoadingState.ERROR &&
28195
- "tw-opacity-0") }), jsxRuntime.jsx("div", { className: "tw-absolute tw-right-0 tw-h-full tw-w-[22px] tw-border-b\n tw-border-t tw-border-material-light-thin" })] })) : null, jsxRuntime.jsxs("div", { className: cn("tw-relative tw-flex tw-h-squid-xl tw-w-fit tw-min-w-fit tw-items-center tw-gap-squid-xxs tw-rounded-br-full tw-rounded-tr-full tw-border tw-border-l-0 tw-border-material-light-thin tw-py-squid-xs tw-pr-squid-xs before:tw-absolute before:-tw-inset-[3px] before:tw-z-[1] before:tw-w-[calc(100%+3px)] before:tw-translate-x-[3px] before:tw-rounded-br-full before:tw-rounded-tr-full before:tw-border-2 before:tw-border-l-0 before:tw-border-transparent after:tw-absolute after:-tw-inset-[5px] after:tw-z-0 after:tw-w-[calc(100%+4px)] after:tw-translate-x-[6px] after:tw-rounded-br-full after:tw-rounded-tr-full after:tw-border-4 after:tw-border-l-0 after:tw-border-transparent group-hover/assets-button:before:tw-border-grey-900 group-hover/assets-button:after:tw-border-material-light-average", state === AssetsButtonState.FULL && "tw-pl-[7px]"), style: {
28206
+ isImgLoadError && "tw-opacity-0") }), jsxRuntime.jsx("div", { className: "tw-absolute tw-right-0 tw-h-full tw-w-[22px] tw-border-b\n tw-border-t tw-border-material-light-thin" })] })) : null, jsxRuntime.jsxs("div", { className: cn("tw-relative tw-flex tw-h-squid-xl tw-w-fit tw-min-w-fit tw-items-center tw-gap-squid-xxs tw-rounded-br-full tw-rounded-tr-full tw-border tw-border-l-0 tw-border-material-light-thin tw-py-squid-xs tw-pr-squid-xs before:tw-absolute before:-tw-inset-[3px] before:tw-z-[1] before:tw-w-[calc(100%+3px)] before:tw-translate-x-[3px] before:tw-rounded-br-full before:tw-rounded-tr-full before:tw-border-2 before:tw-border-l-0 before:tw-border-transparent after:tw-absolute after:-tw-inset-[5px] after:tw-z-0 after:tw-w-[calc(100%+4px)] after:tw-translate-x-[6px] after:tw-rounded-br-full after:tw-rounded-tr-full after:tw-border-4 after:tw-border-l-0 after:tw-border-transparent group-hover/assets-button:before:tw-border-grey-900 group-hover/assets-button:after:tw-border-material-light-average", state === AssetsButtonState.FULL && "tw-pl-[7px]"), style: {
28196
28207
  backgroundColor: tokenBgColor,
28197
28208
  color: tokenTextColor,
28198
28209
  }, children: [jsxRuntime.jsx(BodyText, { size: "small", className: cn("tw-leading-[13px]", state === AssetsButtonState.LOADING && "tw-min-w-[135px]"), children: state === AssetsButtonState.FULL
@@ -3,6 +3,7 @@ import { type TooltipProps } from "../controls";
3
3
  interface AssetsButtonProps {
4
4
  token?: {
5
5
  iconUrl: string;
6
+ fallbackIconUrl?: string;
6
7
  symbol: string;
7
8
  bgColor: string;
8
9
  textColor: string;
@@ -26,6 +26,7 @@ interface SwapConfigurationProps {
26
26
  };
27
27
  token?: {
28
28
  iconUrl: string;
29
+ fallbackIconUrl?: string;
29
30
  symbol: string;
30
31
  bgColor: string;
31
32
  textColor: string;
@@ -4,6 +4,7 @@ interface Token {
4
4
  name: string;
5
5
  symbol: string;
6
6
  image: string;
7
+ fallbackImage?: string;
7
8
  chainId: string;
8
9
  }
9
10
  interface Chain {
@@ -3,6 +3,7 @@ export type TokenGroupVariant = "compact" | "large" | "small";
3
3
  export interface TokenGroupProps<T> {
4
4
  isSelected?: boolean;
5
5
  imageUrl: string;
6
+ fallbackImageUrl?: string;
6
7
  symbol: string;
7
8
  name: string;
8
9
  chainImageUrl: string;
@@ -12,11 +13,10 @@ export interface TokenGroupProps<T> {
12
13
  tokenItems?: T[];
13
14
  extraSpacing?: boolean;
14
15
  onClick?: (event: React.MouseEvent<HTMLButtonElement | HTMLDivElement>) => void;
15
- maxPreviewItems?: number;
16
16
  variant?: TokenGroupVariant;
17
17
  className?: string;
18
18
  onChildClick?: (tokenProps: T) => void;
19
19
  isAdjacentToPreviousExpanded?: boolean;
20
20
  isAdjacentToNextExpanded?: boolean;
21
21
  }
22
- export declare function TokenGroup<T extends TokenGroupProps<T>>({ isSelected, tokenItems, priceChange, balance, balanceUsd, extraSpacing, chainImageUrl, imageUrl, symbol, name, onClick, variant, className, onChildClick, isAdjacentToPreviousExpanded, isAdjacentToNextExpanded, }: TokenGroupProps<T>): import("react/jsx-runtime").JSX.Element;
22
+ export declare function TokenGroup<T extends TokenGroupProps<T>>({ isSelected, tokenItems, priceChange, balance, balanceUsd, extraSpacing, chainImageUrl, imageUrl, fallbackImageUrl, symbol, name, onClick, variant, className, onChildClick, isAdjacentToPreviousExpanded, isAdjacentToNextExpanded, }: TokenGroupProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -14,3 +14,5 @@ export declare const ChainAndTokenDarkText: Story;
14
14
  export declare const LongTokenSymbol: Story;
15
15
  export declare const Tooltip: Story;
16
16
  export declare const BrokenTokenImage: Story;
17
+ export declare const BrokenTokenImageWithPlaceholder: Story;
18
+ export declare const BrokenTokenImageAndBrokenPlaceholder: Story;
@@ -12,3 +12,4 @@ export declare const Closed: Story;
12
12
  export declare const Loading: Story;
13
13
  export declare const MarkedFavorite: Story;
14
14
  export declare const WithFakeChart: Story;
15
+ export declare const FallbackImage: Story;
@@ -16,3 +16,5 @@ export declare const LargeVariantLongTokenName: Story;
16
16
  export declare const CompactVariantWithItems: Story;
17
17
  export declare const CompactVariantLongTokenName: Story;
18
18
  export declare const MultipleCollapsibleGroups: Story;
19
+ export declare const FallbackImage: Story;
20
+ export declare const ItemsWithFallbackImage: Story;
package/dist/esm/index.js CHANGED
@@ -21390,7 +21390,7 @@ const mainImageSizeMap = {
21390
21390
  large: "xxlarge",
21391
21391
  small: "xlarge",
21392
21392
  };
21393
- function TokenGroup({ isSelected = false, tokenItems, priceChange, balance, balanceUsd, extraSpacing = true, chainImageUrl, imageUrl, symbol, name, onClick, variant = "large", className, onChildClick, isAdjacentToPreviousExpanded = false, isAdjacentToNextExpanded = false, }) {
21393
+ function TokenGroup({ isSelected = false, tokenItems, priceChange, balance, balanceUsd, extraSpacing = true, chainImageUrl, imageUrl, fallbackImageUrl, symbol, name, onClick, variant = "large", className, onChildClick, isAdjacentToPreviousExpanded = false, isAdjacentToNextExpanded = false, }) {
21394
21394
  var _a;
21395
21395
  const [isCollapsed, setIsCollapsed] = useState(true);
21396
21396
  const isCollapsible = tokenItems && tokenItems.length > 0;
@@ -21425,7 +21425,7 @@ function TokenGroup({ isSelected = false, tokenItems, priceChange, balance, bala
21425
21425
  : "tw-h-squid-xl tw-gap-squid-xxs"
21426
21426
  : isSmallVariant
21427
21427
  ? "tw-h-[50px] tw-gap-squid-s tw-py-squid-xxs"
21428
- : "tw-h-squid-xl tw-gap-squid-xxs"), children: [jsxs("div", { className: "tw-relative tw-min-w-fit", children: [jsx(Image$1, { src: imageUrl, size: isLargeVariant && !isCollapsed
21428
+ : "tw-h-squid-xl tw-gap-squid-xxs"), children: [jsxs("div", { className: "tw-relative tw-min-w-fit", children: [jsx(Image$1, { src: imageUrl, placeholderImageUrl: fallbackImageUrl, size: isLargeVariant && !isCollapsed
21429
21429
  ? "medium"
21430
21430
  : (_a = mainImageSizeMap[variant]) !== null && _a !== void 0 ? _a : "medium", rounded: isCompactVariant ? "xxs" : "full", style: {
21431
21431
  transition: `${ANIMATION_DURATIONS.GENERIC}ms ${ANIMATION_TIMINGS.GENERIC}`,
@@ -21742,6 +21742,7 @@ const DEFAULT_TIMEFRAME = "1D";
21742
21742
  const DefaultChart = ({ timeframe, }) => (jsx("div", { className: "tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center", children: jsxs(BodyText, { size: "small", children: ["Chart (", timeframe, ")"] }) }));
21743
21743
  const TokenModalHeader = ({ token, chain, onLikeToken, onClose, isFavorite, }) => (jsxs("div", { className: "tw-flex tw-w-full tw-items-start tw-justify-between", children: [jsx("div", { className: "tw-flex tw-items-center tw-gap-squid-xs tw-pb-squid-xs", children: jsx(BadgeImage, { image: {
21744
21744
  src: token.image,
21745
+ placeholderImageUrl: token.fallbackImage,
21745
21746
  size: "xxlarge",
21746
21747
  rounded: "full",
21747
21748
  shadow: true,
@@ -21752,7 +21753,7 @@ const TokenModalHeader = ({ token, chain, onLikeToken, onClose, isFavorite, }) =
21752
21753
  border: "outline-lg",
21753
21754
  } }) }), jsxs("span", { className: "tw-absolute tw-right-squid-xs tw-top-squid-xs tw-flex tw-items-center tw-gap-squid-xxs", children: [jsx(IconButton, { className: "tw-group/token-detail-fav-icon tw-text-grey-300", onClick: onLikeToken, icon: isFavorite ? (jsxs(Fragment, { children: [jsx(FilledHeartIcon, { className: "tw-block group-hover/token-detail-fav-icon:tw-hidden" }), jsx(BrokenHeartIcon, { className: "tw-hidden group-hover/token-detail-fav-icon:tw-block" })] })) : (jsxs(Fragment, { children: [jsx(EmptyHeartIcon, { className: "tw-block group-hover/token-detail-fav-icon:tw-hidden" }), jsx(FilledHeartIcon, { className: "tw-hidden group-hover/token-detail-fav-icon:tw-block" })] })) }), jsx(IconButton, { className: "tw-text-grey-300", onClick: onClose, icon: jsx(CircleXFilledIcon, {}) })] })] }));
21754
21755
  const TokenInfo = ({ token, tokenPrice, priceChange, loading, }) => (jsxs("div", { children: [jsx(BodyText, { size: "large", loading: { isLoading: loading, width: "100px" }, children: tokenPrice.toLocaleString() }), jsxs("div", { className: "tw-flex tw-flex-row tw-items-center tw-justify-between", children: [jsx(BodyText, { size: "small", children: token.name }), jsx(PriceChange, { value: priceChange, variant: "large" })] })] }));
21755
- const BalanceInfo = ({ balance, balanceUSD, token, loading, }) => (jsxs("div", { className: "tw-flex tw-flex-col", children: [jsx(CaptionText, { className: "tw-text-grey-500", children: "Balance" }), jsxs("div", { className: "tw-flex tw-flex-row tw-justify-between", children: [jsxs("span", { className: "tw-flex tw-flex-row tw-items-center tw-gap-squid-xxs", children: [jsx("img", { src: token.image, alt: token.name, className: "tw-h-squid-m tw-w-squid-m tw-rounded-full" }), jsxs(BodyText, { size: "small", loading: { isLoading: loading, width: "80px" }, children: [balance, " ", token.symbol] })] }), jsxs(CaptionText, { className: "tw-text-grey-500", loading: { isLoading: loading, width: "60px" }, children: [jsx("span", { className: "tw-opacity-66", children: "$" }), balanceUSD] })] })] }));
21756
+ const BalanceInfo = ({ balance, balanceUSD, token, loading, }) => (jsxs("div", { className: "tw-flex tw-flex-col", children: [jsx(CaptionText, { className: "tw-text-grey-500", children: "Balance" }), jsxs("div", { className: "tw-flex tw-flex-row tw-justify-between", children: [jsxs("span", { className: "tw-flex tw-flex-row tw-items-center tw-gap-squid-xxs", children: [jsx(Image$1, { src: token.image, placeholderImageUrl: token.fallbackImage, alt: token.name, size: "medium", rounded: "full" }), jsxs(BodyText, { size: "small", loading: { isLoading: loading, width: "80px" }, children: [balance, " ", token.symbol] })] }), jsxs(CaptionText, { className: "tw-text-grey-500", loading: { isLoading: loading, width: "60px" }, children: [jsx("span", { className: "tw-opacity-66", children: "$" }), balanceUSD] })] })] }));
21756
21757
  const MarketDataItem = ({ label, value, showDollar, loading, }) => (jsxs("div", { className: "tw-flex tw-flex-col", children: [jsx(CaptionText, { className: "tw-text-grey-500", children: label }), jsxs(CaptionText, { loading: { isLoading: loading, width: "60px" }, children: [showDollar && jsx("span", { className: "tw-text-grey-500", children: "$" }), value] })] }));
21757
21758
  const TokenMarketData = ({ volume24h, marketCap, totalSupply, loading, }) => (jsxs("div", { className: "tw-mb-squid-xs tw-flex tw-h-full tw-w-full tw-flex-row tw-items-center tw-justify-between", children: [jsx(MarketDataItem, { label: "24h volume", value: volume24h, showDollar: true, loading: loading }), jsx(MarketDataItem, { label: "Market cap", value: marketCap, showDollar: true, loading: loading }), jsx(MarketDataItem, { label: "Total supply", value: totalSupply, loading: loading })] }));
21758
21759
  const FooterButton = (_a) => {
@@ -28106,6 +28107,10 @@ const outlineWidthMap = {
28106
28107
  function AssetsButton({ chain, token, onClick, variant = "primary", isLoading = false, tooltip, emptyTokenLabel = "Select token", }) {
28107
28108
  var _a, _b, _c, _d;
28108
28109
  const [tokenImageLoadingState, setTokenImageLoadingState] = useState(ImageLoadingState.LOADING);
28110
+ const [fallbackImageLoadingState, setFallbackImageLoadingState] = useState(ImageLoadingState.LOADING);
28111
+ const isImgLoadError = tokenImageLoadingState === ImageLoadingState.ERROR;
28112
+ const isFallbackImgLoadError = fallbackImageLoadingState === ImageLoadingState.ERROR;
28113
+ const showTextFallback = (isImgLoadError && !(token === null || token === void 0 ? void 0 : token.fallbackIconUrl)) || isFallbackImgLoadError;
28109
28114
  const fallbackBgColor = useColorFromSeed((_a = token === null || token === void 0 ? void 0 : token.symbol) !== null && _a !== void 0 ? _a : "");
28110
28115
  const state = useMemo(() => {
28111
28116
  if (chain != null && !isLoading) {
@@ -28122,14 +28127,12 @@ function AssetsButton({ chain, token, onClick, variant = "primary", isLoading =
28122
28127
  const defaultBgColor = `var(${themeKeysToCssVariables.color[themeKeyVariantMap[variant]].cssVariable})`;
28123
28128
  const outlineWidth = outlineWidthMap[state];
28124
28129
  const chainBgColor = (_b = chain === null || chain === void 0 ? void 0 : chain.bgColor) !== null && _b !== void 0 ? _b : defaultBgColor;
28125
- const tokenTextColor = tokenImageLoadingState === ImageLoadingState.ERROR
28126
- ? "#fff"
28127
- : token === null || token === void 0 ? void 0 : token.textColor;
28130
+ const tokenTextColor = isImgLoadError ? "#fff" : token === null || token === void 0 ? void 0 : token.textColor;
28128
28131
  const tokenBgColor = useMemo(() => {
28129
28132
  if (state !== AssetsButtonState.FULL) {
28130
28133
  return defaultBgColor;
28131
28134
  }
28132
- if (tokenImageLoadingState === ImageLoadingState.ERROR) {
28135
+ if (showTextFallback) {
28133
28136
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
28134
28137
  return fallbackBgColor || defaultBgColor;
28135
28138
  }
@@ -28139,7 +28142,7 @@ function AssetsButton({ chain, token, onClick, variant = "primary", isLoading =
28139
28142
  state,
28140
28143
  token === null || token === void 0 ? void 0 : token.bgColor,
28141
28144
  defaultBgColor,
28142
- tokenImageLoadingState,
28145
+ showTextFallback,
28143
28146
  fallbackBgColor,
28144
28147
  ]);
28145
28148
  const bgGradient = useMemo(() => {
@@ -28157,13 +28160,22 @@ function AssetsButton({ chain, token, onClick, variant = "primary", isLoading =
28157
28160
  ? undefined
28158
28161
  : tooltip === null || tooltip === void 0 ? void 0 : tooltip.tooltipContent, children: jsxs(ButtonTag, { onClick: state === AssetsButtonState.LOADING ? undefined : onClick, disabled: state === AssetsButtonState.LOADING, "aria-disabled": state === AssetsButtonState.LOADING, className: cn("tw-relative tw-flex tw-h-squid-xl tw-w-fit tw-items-center focus:tw-outline-none", buttonTextClassNameMap[variant], state === AssetsButtonState.LOADING
28159
28162
  ? "tw-cursor-not-allowed"
28160
- : "tw-group/assets-button"), children: [jsx("svg", { className: "tw-absolute tw-left-0 tw-z-10 tw-h-[40px] tw-w-[72px] tw-text-material-light-thin", viewBox: "0 0 72 40", xmlns: "http://www.w3.org/2000/svg", fill: "currentColor", children: jsx("path", { d: "m57.86 5.86c-.53.53-1.05 1.06-1.56 1.59-3.29 3.38-6.37 6.55-10.3 6.55s-7.01-3.17-10.3-6.55c-.51-.53-1.03-1.06-1.56-1.59-3.9-3.91-9.02-5.86-14.14-5.86s-10.24 1.95-14.14 5.86c-3.91 3.9-5.86 9.02-5.86 14.14s1.95 10.24 5.86 14.14c3.91 3.91 9.02 5.86 14.14 5.86s10.24-1.95 14.14-5.86c.52-.53 1.04-1.06 1.55-1.58 3.29-3.39 6.37-6.56 10.31-6.56s7.02 3.17 10.31 6.56c.51.53 1.03 1.06 1.55 1.58 3.91 3.91 9.02 5.86 14.14 5.86v-40c-5.12 0-10.24 1.95-14.14 5.86zm14.14 33.14c-5.07 0-9.85-1.98-13.43-5.56-.52-.52-1.02-1.04-1.54-1.57-3.43-3.53-6.67-6.86-11.02-6.86s-7.6 3.33-11.04 6.88c-.5.52-1.01 1.04-1.53 1.56-3.59 3.59-8.36 5.57-13.44 5.57s-9.85-1.98-13.43-5.57c-3.59-3.59-5.56-8.36-5.56-13.43s1.98-9.85 5.56-13.44c3.58-3.6 8.35-5.58 13.43-5.58s9.85 1.98 13.44 5.57c.52.52 1.04 1.05 1.55 1.58 3.43 3.53 6.66 6.86 11.02 6.86s7.59-3.33 11.02-6.86c.51-.52 1.02-1.05 1.55-1.58 3.59-3.59 8.36-5.56 13.43-5.56v38z" }) }), jsxs("svg", { className: "tw-absolute -tw-left-[2px] -tw-top-[2px] tw-z-20 tw-hidden group-hover/assets-button:tw-block", xmlns: "http://www.w3.org/2000/svg", width: outlineWidth.inner, height: "44", viewBox: `0 0 ${outlineWidth.inner} 44`, fill: "none", children: [jsx("clipPath", { id: "halfClip1", children: jsx("rect", { x: "0", y: "0", width: outlineWidth.inner, height: "44" }) }), jsx("path", { d: "M59.1506 36.849L59.1508 36.8492C63.2511 40.9496 68.6272 43 74.0001 43H145C156.598 43 166 33.598 166 22C166 10.402 156.598 1 145 1H74.0001C68.6272 0.999977 63.2511 3.05042 59.1508 7.15076C58.6183 7.68325 58.0958 8.22097 57.5842 8.74755L57.5817 8.75009C55.9228 10.4574 54.3925 12.0283 52.8031 13.1834C51.2282 14.3279 49.6791 15 48 15C46.3209 15 44.7718 14.3279 43.1969 13.1834C41.6075 12.0283 40.0772 10.4574 38.4183 8.75009L38.4159 8.74759C37.9042 8.22101 37.3817 7.68327 36.8492 7.15076C32.7489 3.05042 27.3728 0.999977 21.9999 1C16.6271 1.00002 11.251 3.05047 7.15076 7.15076C3.05045 11.2511 1 16.6272 1 22C1 27.3728 3.05045 32.7489 7.15076 36.8492C11.251 40.9495 16.6271 43 21.9999 43C27.3728 43 32.7489 40.9496 36.8492 36.8492L36.8494 36.849C37.3794 36.3188 37.8994 35.7834 38.4088 35.2589L38.4108 35.2569C40.0704 33.5483 41.6019 31.9756 43.1928 30.8191C44.7691 29.6731 46.3197 29 48 29C49.6803 29 51.2309 29.6731 52.8072 30.8191C54.3981 31.9756 55.9296 33.5483 57.5892 35.2569L57.5912 35.2589C58.1006 35.7834 58.6206 36.3188 59.1506 36.849Z", stroke: "currentColor", className: "tw-text-grey-900", strokeWidth: "2", clipPath: "url(#halfClip1)" })] }), jsxs("svg", { className: "tw-absolute -tw-left-[4px] -tw-top-[4px] tw-z-10 tw-hidden group-hover/assets-button:tw-block", xmlns: "http://www.w3.org/2000/svg", width: outlineWidth.outer, height: "48", viewBox: `0 0 ${outlineWidth.outer} 48`, fill: "none", children: [jsx("clipPath", { id: "halfClip2", children: jsx("rect", { x: "0", y: "0", width: outlineWidth.outer, height: "48" }) }), jsx("path", { d: "M60.4432 39.5559L60.4437 39.5564C64.7391 43.8517 70.3729 46 76.0001 46H147C159.15 46 169 36.1503 169 24C169 11.8497 159.15 2 147 2H76.0001C70.3729 1.99998 64.7391 4.14825 60.4437 8.44365C59.9062 8.98109 59.3797 9.52298 58.8697 10.0479L58.8645 10.0532C57.1905 11.7761 55.7217 13.2796 54.2152 14.3745C52.7378 15.4481 51.3923 16 50 16C48.6077 16 47.2622 15.4481 45.7848 14.3745C44.2783 13.2796 42.8095 11.7761 41.1355 10.0532L41.1305 10.0481C40.6205 9.52314 40.0939 8.98117 39.5563 8.44365C35.2609 4.14825 29.6271 1.99998 23.9999 2C18.3727 2.00002 12.739 4.1483 8.44365 8.44365C4.14827 12.739 2 18.3728 2 24C2 29.6272 4.14827 35.261 8.44365 39.5564C12.739 43.8517 18.3727 46 23.9999 46C29.6271 46 35.2609 43.8517 39.5563 39.5564L39.5568 39.5559C40.0917 39.0207 40.6159 38.481 41.1237 37.9582L41.1281 37.9537C42.8028 36.2295 44.2728 34.7243 45.7808 33.6279C47.2597 32.5528 48.6066 32 50 32C51.3934 32 52.7403 32.5528 54.2192 33.6279C55.7272 34.7243 57.1972 36.2295 58.8719 37.9537L58.8763 37.9582C59.3841 38.481 59.9083 39.0207 60.4432 39.5559Z", stroke: "currentColor", className: "tw-text-material-light-average", strokeWidth: "4", clipPath: "url(#halfClip2)" })] }), jsx("div", { className: cn("tw-assets-button-mask tw-flex tw-h-squid-xl tw-w-[92px] tw-items-center tw-justify-between", state !== AssetsButtonState.FULL && "!tw-w-[72px]"), style: {
28163
+ : "tw-group/assets-button"), children: [jsx("svg", { className: "tw-absolute tw-left-0 tw-z-10 tw-h-[40px] tw-w-[72px] tw-text-material-light-thin", viewBox: "0 0 72 40", xmlns: "http://www.w3.org/2000/svg", fill: "currentColor", children: jsx("path", { d: "m57.86 5.86c-.53.53-1.05 1.06-1.56 1.59-3.29 3.38-6.37 6.55-10.3 6.55s-7.01-3.17-10.3-6.55c-.51-.53-1.03-1.06-1.56-1.59-3.9-3.91-9.02-5.86-14.14-5.86s-10.24 1.95-14.14 5.86c-3.91 3.9-5.86 9.02-5.86 14.14s1.95 10.24 5.86 14.14c3.91 3.91 9.02 5.86 14.14 5.86s10.24-1.95 14.14-5.86c.52-.53 1.04-1.06 1.55-1.58 3.29-3.39 6.37-6.56 10.31-6.56s7.02 3.17 10.31 6.56c.51.53 1.03 1.06 1.55 1.58 3.91 3.91 9.02 5.86 14.14 5.86v-40c-5.12 0-10.24 1.95-14.14 5.86zm14.14 33.14c-5.07 0-9.85-1.98-13.43-5.56-.52-.52-1.02-1.04-1.54-1.57-3.43-3.53-6.67-6.86-11.02-6.86s-7.6 3.33-11.04 6.88c-.5.52-1.01 1.04-1.53 1.56-3.59 3.59-8.36 5.57-13.44 5.57s-9.85-1.98-13.43-5.57c-3.59-3.59-5.56-8.36-5.56-13.43s1.98-9.85 5.56-13.44c3.58-3.6 8.35-5.58 13.43-5.58s9.85 1.98 13.44 5.57c.52.52 1.04 1.05 1.55 1.58 3.43 3.53 6.66 6.86 11.02 6.86s7.59-3.33 11.02-6.86c.51-.52 1.02-1.05 1.55-1.58 3.59-3.59 8.36-5.56 13.43-5.56v38z" }) }), jsxs("svg", { className: "tw-absolute -tw-left-[2px] -tw-top-[2px] tw-z-20 tw-hidden group-hover/assets-button:tw-block", xmlns: "http://www.w3.org/2000/svg", width: outlineWidth.inner, height: "44", viewBox: `0 0 ${outlineWidth.inner} 44`, fill: "none", children: [jsx("clipPath", { id: "halfClip1", children: jsx("rect", { x: "0", y: "0", width: outlineWidth.inner, height: "44" }) }), jsx("path", { d: "M59.1506 36.849L59.1508 36.8492C63.2511 40.9496 68.6272 43 74.0001 43H145C156.598 43 166 33.598 166 22C166 10.402 156.598 1 145 1H74.0001C68.6272 0.999977 63.2511 3.05042 59.1508 7.15076C58.6183 7.68325 58.0958 8.22097 57.5842 8.74755L57.5817 8.75009C55.9228 10.4574 54.3925 12.0283 52.8031 13.1834C51.2282 14.3279 49.6791 15 48 15C46.3209 15 44.7718 14.3279 43.1969 13.1834C41.6075 12.0283 40.0772 10.4574 38.4183 8.75009L38.4159 8.74759C37.9042 8.22101 37.3817 7.68327 36.8492 7.15076C32.7489 3.05042 27.3728 0.999977 21.9999 1C16.6271 1.00002 11.251 3.05047 7.15076 7.15076C3.05045 11.2511 1 16.6272 1 22C1 27.3728 3.05045 32.7489 7.15076 36.8492C11.251 40.9495 16.6271 43 21.9999 43C27.3728 43 32.7489 40.9496 36.8492 36.8492L36.8494 36.849C37.3794 36.3188 37.8994 35.7834 38.4088 35.2589L38.4108 35.2569C40.0704 33.5483 41.6019 31.9756 43.1928 30.8191C44.7691 29.6731 46.3197 29 48 29C49.6803 29 51.2309 29.6731 52.8072 30.8191C54.3981 31.9756 55.9296 33.5483 57.5892 35.2569L57.5912 35.2589C58.1006 35.7834 58.6206 36.3188 59.1506 36.849Z", stroke: "currentColor", className: "tw-text-grey-900", strokeWidth: "2", clipPath: "url(#halfClip1)" })] }), jsxs("svg", { className: "tw-absolute -tw-left-[4px] -tw-top-[4px] tw-z-10 tw-hidden group-hover/assets-button:tw-block", xmlns: "http://www.w3.org/2000/svg", width: outlineWidth.outer, height: "48", viewBox: `0 0 ${outlineWidth.outer} 48`, fill: "none", children: [jsx("clipPath", { id: "halfClip2", children: jsx("rect", { x: "0", y: "0", width: outlineWidth.outer, height: "48" }) }), jsx("path", { d: "M60.4432 39.5559L60.4437 39.5564C64.7391 43.8517 70.3729 46 76.0001 46H147C159.15 46 169 36.1503 169 24C169 11.8497 159.15 2 147 2H76.0001C70.3729 1.99998 64.7391 4.14825 60.4437 8.44365C59.9062 8.98109 59.3797 9.52298 58.8697 10.0479L58.8645 10.0532C57.1905 11.7761 55.7217 13.2796 54.2152 14.3745C52.7378 15.4481 51.3923 16 50 16C48.6077 16 47.2622 15.4481 45.7848 14.3745C44.2783 13.2796 42.8095 11.7761 41.1355 10.0532L41.1305 10.0481C40.6205 9.52314 40.0939 8.98117 39.5563 8.44365C35.2609 4.14825 29.6271 1.99998 23.9999 2C18.3727 2.00002 12.739 4.1483 8.44365 8.44365C4.14827 12.739 2 18.3728 2 24C2 29.6272 4.14827 35.261 8.44365 39.5564C12.739 43.8517 18.3727 46 23.9999 46C29.6271 46 35.2609 43.8517 39.5563 39.5564L39.5568 39.5559C40.0917 39.0207 40.6159 38.481 41.1237 37.9582L41.1281 37.9537C42.8028 36.2295 44.2728 34.7243 45.7808 33.6279C47.2597 32.5528 48.6066 32 50 32C51.3934 32 52.7403 32.5528 54.2192 33.6279C55.7272 34.7243 57.1972 36.2295 58.8719 37.9537L58.8763 37.9582C59.3841 38.481 59.9083 39.0207 60.4432 39.5559Z", stroke: "currentColor", className: "tw-text-material-light-average", strokeWidth: "4", clipPath: "url(#halfClip2)" })] }), jsx("div", { className: cn("tw-flex tw-h-squid-xl tw-w-[92px] tw-items-center tw-justify-between tw-assets-button-mask", state !== AssetsButtonState.FULL && "!tw-w-[72px]"), style: {
28161
28164
  backgroundImage: bgGradient,
28162
28165
  }, children: jsx("div", { className: clsx(imageWrapperClassName), children: state === AssetsButtonState.CHAIN_ONLY ||
28163
28166
  state === AssetsButtonState.FULL ? (jsx("img", { src: chain === null || chain === void 0 ? void 0 : chain.iconUrl, alt: "", className: "tw-h-squid-xl tw-w-squid-xl tw-rounded-full tw-object-cover" })) : (state !== AssetsButtonState.LOADING && (jsx("span", { className: emptyChainIconTextClassNameMap[variant], children: jsx(PlusIcon, {}) }))) }) }), state === AssetsButtonState.FULL ? (jsxs("div", { className: clsx(imageWrapperClassName, "tw-absolute tw-left-[54px] tw-z-[5] tw-h-squid-xl tw-overflow-hidden"), children: [jsx("div", { className: "tw-absolute tw-right-0 tw-h-full tw-w-[22px]", style: {
28164
28167
  backgroundColor: tokenBgColor,
28165
- } }), tokenImageLoadingState === ImageLoadingState.ERROR && (
28166
- // show fallback
28168
+ } }), isImgLoadError && (token === null || token === void 0 ? void 0 : token.fallbackIconUrl) && (
28169
+ // fallback image
28170
+ jsx("img", { src: token === null || token === void 0 ? void 0 : token.fallbackIconUrl, alt: token === null || token === void 0 ? void 0 : token.symbol, onLoad: () => {
28171
+ setFallbackImageLoadingState(ImageLoadingState.LOADED);
28172
+ }, onError: () => {
28173
+ setFallbackImageLoadingState(ImageLoadingState.ERROR);
28174
+ }, className: cn(tokenImageClassName,
28175
+ // when image load failed, hide it with opacity: 0 instead of display: none
28176
+ // so new images can load and update the loading state
28177
+ isFallbackImgLoadError && "tw-opacity-0") })), showTextFallback && (
28178
+ // fallback text
28167
28179
  jsx("div", { style: { color: tokenTextColor }, className: cn(tokenImageClassName, "tw-flex tw-items-center tw-justify-center tw-text-body-small"), children: (_d = (_c = token === null || token === void 0 ? void 0 : token.symbol) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.toUpperCase() })), jsx("img", { onLoad: () => {
28168
28180
  setTokenImageLoadingState(ImageLoadingState.LOADED);
28169
28181
  }, onError: () => {
@@ -28171,8 +28183,7 @@ function AssetsButton({ chain, token, onClick, variant = "primary", isLoading =
28171
28183
  }, src: token === null || token === void 0 ? void 0 : token.iconUrl, alt: token === null || token === void 0 ? void 0 : token.symbol, className: cn(tokenImageClassName,
28172
28184
  // when image load failed, hide it with opacity: 0 instead of display: none
28173
28185
  // so new images can load and update the loading state
28174
- tokenImageLoadingState === ImageLoadingState.ERROR &&
28175
- "tw-opacity-0") }), jsx("div", { className: "tw-absolute tw-right-0 tw-h-full tw-w-[22px] tw-border-b\n tw-border-t tw-border-material-light-thin" })] })) : null, jsxs("div", { className: cn("tw-relative tw-flex tw-h-squid-xl tw-w-fit tw-min-w-fit tw-items-center tw-gap-squid-xxs tw-rounded-br-full tw-rounded-tr-full tw-border tw-border-l-0 tw-border-material-light-thin tw-py-squid-xs tw-pr-squid-xs before:tw-absolute before:-tw-inset-[3px] before:tw-z-[1] before:tw-w-[calc(100%+3px)] before:tw-translate-x-[3px] before:tw-rounded-br-full before:tw-rounded-tr-full before:tw-border-2 before:tw-border-l-0 before:tw-border-transparent after:tw-absolute after:-tw-inset-[5px] after:tw-z-0 after:tw-w-[calc(100%+4px)] after:tw-translate-x-[6px] after:tw-rounded-br-full after:tw-rounded-tr-full after:tw-border-4 after:tw-border-l-0 after:tw-border-transparent group-hover/assets-button:before:tw-border-grey-900 group-hover/assets-button:after:tw-border-material-light-average", state === AssetsButtonState.FULL && "tw-pl-[7px]"), style: {
28186
+ isImgLoadError && "tw-opacity-0") }), jsx("div", { className: "tw-absolute tw-right-0 tw-h-full tw-w-[22px] tw-border-b\n tw-border-t tw-border-material-light-thin" })] })) : null, jsxs("div", { className: cn("tw-relative tw-flex tw-h-squid-xl tw-w-fit tw-min-w-fit tw-items-center tw-gap-squid-xxs tw-rounded-br-full tw-rounded-tr-full tw-border tw-border-l-0 tw-border-material-light-thin tw-py-squid-xs tw-pr-squid-xs before:tw-absolute before:-tw-inset-[3px] before:tw-z-[1] before:tw-w-[calc(100%+3px)] before:tw-translate-x-[3px] before:tw-rounded-br-full before:tw-rounded-tr-full before:tw-border-2 before:tw-border-l-0 before:tw-border-transparent after:tw-absolute after:-tw-inset-[5px] after:tw-z-0 after:tw-w-[calc(100%+4px)] after:tw-translate-x-[6px] after:tw-rounded-br-full after:tw-rounded-tr-full after:tw-border-4 after:tw-border-l-0 after:tw-border-transparent group-hover/assets-button:before:tw-border-grey-900 group-hover/assets-button:after:tw-border-material-light-average", state === AssetsButtonState.FULL && "tw-pl-[7px]"), style: {
28176
28187
  backgroundColor: tokenBgColor,
28177
28188
  color: tokenTextColor,
28178
28189
  }, children: [jsx(BodyText, { size: "small", className: cn("tw-leading-[13px]", state === AssetsButtonState.LOADING && "tw-min-w-[135px]"), children: state === AssetsButtonState.FULL
@@ -3,6 +3,7 @@ import { type TooltipProps } from "../controls";
3
3
  interface AssetsButtonProps {
4
4
  token?: {
5
5
  iconUrl: string;
6
+ fallbackIconUrl?: string;
6
7
  symbol: string;
7
8
  bgColor: string;
8
9
  textColor: string;
@@ -26,6 +26,7 @@ interface SwapConfigurationProps {
26
26
  };
27
27
  token?: {
28
28
  iconUrl: string;
29
+ fallbackIconUrl?: string;
29
30
  symbol: string;
30
31
  bgColor: string;
31
32
  textColor: string;
@@ -4,6 +4,7 @@ interface Token {
4
4
  name: string;
5
5
  symbol: string;
6
6
  image: string;
7
+ fallbackImage?: string;
7
8
  chainId: string;
8
9
  }
9
10
  interface Chain {
@@ -3,6 +3,7 @@ export type TokenGroupVariant = "compact" | "large" | "small";
3
3
  export interface TokenGroupProps<T> {
4
4
  isSelected?: boolean;
5
5
  imageUrl: string;
6
+ fallbackImageUrl?: string;
6
7
  symbol: string;
7
8
  name: string;
8
9
  chainImageUrl: string;
@@ -12,11 +13,10 @@ export interface TokenGroupProps<T> {
12
13
  tokenItems?: T[];
13
14
  extraSpacing?: boolean;
14
15
  onClick?: (event: React.MouseEvent<HTMLButtonElement | HTMLDivElement>) => void;
15
- maxPreviewItems?: number;
16
16
  variant?: TokenGroupVariant;
17
17
  className?: string;
18
18
  onChildClick?: (tokenProps: T) => void;
19
19
  isAdjacentToPreviousExpanded?: boolean;
20
20
  isAdjacentToNextExpanded?: boolean;
21
21
  }
22
- export declare function TokenGroup<T extends TokenGroupProps<T>>({ isSelected, tokenItems, priceChange, balance, balanceUsd, extraSpacing, chainImageUrl, imageUrl, symbol, name, onClick, variant, className, onChildClick, isAdjacentToPreviousExpanded, isAdjacentToNextExpanded, }: TokenGroupProps<T>): import("react/jsx-runtime").JSX.Element;
22
+ export declare function TokenGroup<T extends TokenGroupProps<T>>({ isSelected, tokenItems, priceChange, balance, balanceUsd, extraSpacing, chainImageUrl, imageUrl, fallbackImageUrl, symbol, name, onClick, variant, className, onChildClick, isAdjacentToPreviousExpanded, isAdjacentToNextExpanded, }: TokenGroupProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -14,3 +14,5 @@ export declare const ChainAndTokenDarkText: Story;
14
14
  export declare const LongTokenSymbol: Story;
15
15
  export declare const Tooltip: Story;
16
16
  export declare const BrokenTokenImage: Story;
17
+ export declare const BrokenTokenImageWithPlaceholder: Story;
18
+ export declare const BrokenTokenImageAndBrokenPlaceholder: Story;
@@ -12,3 +12,4 @@ export declare const Closed: Story;
12
12
  export declare const Loading: Story;
13
13
  export declare const MarkedFavorite: Story;
14
14
  export declare const WithFakeChart: Story;
15
+ export declare const FallbackImage: Story;
@@ -16,3 +16,5 @@ export declare const LargeVariantLongTokenName: Story;
16
16
  export declare const CompactVariantWithItems: Story;
17
17
  export declare const CompactVariantLongTokenName: Story;
18
18
  export declare const MultipleCollapsibleGroups: Story;
19
+ export declare const FallbackImage: Story;
20
+ export declare const ItemsWithFallbackImage: Story;
package/dist/index.d.ts CHANGED
@@ -304,6 +304,7 @@ declare function Switch({ checked, onChange, size, disabled, inputProps, }: Swit
304
304
  interface AssetsButtonProps {
305
305
  token?: {
306
306
  iconUrl: string;
307
+ fallbackIconUrl?: string;
307
308
  symbol: string;
308
309
  bgColor: string;
309
310
  textColor: string;
@@ -1254,6 +1255,7 @@ interface SwapConfigurationProps {
1254
1255
  };
1255
1256
  token?: {
1256
1257
  iconUrl: string;
1258
+ fallbackIconUrl?: string;
1257
1259
  symbol: string;
1258
1260
  bgColor: string;
1259
1261
  textColor: string;
@@ -1327,6 +1329,7 @@ interface Token$2 {
1327
1329
  name: string;
1328
1330
  symbol: string;
1329
1331
  image: string;
1332
+ fallbackImage?: string;
1330
1333
  chainId: string;
1331
1334
  }
1332
1335
  interface Chain {
@@ -1891,6 +1894,7 @@ type TokenGroupVariant = "compact" | "large" | "small";
1891
1894
  interface TokenGroupProps<T> {
1892
1895
  isSelected?: boolean;
1893
1896
  imageUrl: string;
1897
+ fallbackImageUrl?: string;
1894
1898
  symbol: string;
1895
1899
  name: string;
1896
1900
  chainImageUrl: string;
@@ -1900,14 +1904,13 @@ interface TokenGroupProps<T> {
1900
1904
  tokenItems?: T[];
1901
1905
  extraSpacing?: boolean;
1902
1906
  onClick?: (event: React.MouseEvent<HTMLButtonElement | HTMLDivElement>) => void;
1903
- maxPreviewItems?: number;
1904
1907
  variant?: TokenGroupVariant;
1905
1908
  className?: string;
1906
1909
  onChildClick?: (tokenProps: T) => void;
1907
1910
  isAdjacentToPreviousExpanded?: boolean;
1908
1911
  isAdjacentToNextExpanded?: boolean;
1909
1912
  }
1910
- declare function TokenGroup<T extends TokenGroupProps<T>>({ isSelected, tokenItems, priceChange, balance, balanceUsd, extraSpacing, chainImageUrl, imageUrl, symbol, name, onClick, variant, className, onChildClick, isAdjacentToPreviousExpanded, isAdjacentToNextExpanded, }: TokenGroupProps<T>): react_jsx_runtime.JSX.Element;
1913
+ declare function TokenGroup<T extends TokenGroupProps<T>>({ isSelected, tokenItems, priceChange, balance, balanceUsd, extraSpacing, chainImageUrl, imageUrl, fallbackImageUrl, symbol, name, onClick, variant, className, onChildClick, isAdjacentToPreviousExpanded, isAdjacentToNextExpanded, }: TokenGroupProps<T>): react_jsx_runtime.JSX.Element;
1911
1914
 
1912
1915
  interface TokenGroupsProps<T extends TokenGroupProps<T>> {
1913
1916
  groups: T[];
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "git+https://github.com/0xsquid/squid-ui.git"
6
6
  },
7
7
  "description": "Squid's UI components",
8
- "version": "3.0.0",
8
+ "version": "3.0.2",
9
9
  "author": "",
10
10
  "license": "MIT",
11
11
  "resolutions": {