@0xsquid/ui 0.21.1-beta.0 → 0.22.0

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
@@ -2637,24 +2637,41 @@ const mainImageSizeClassMap = {
2637
2637
  md: 'tw-w-10 tw-h-10',
2638
2638
  };
2639
2639
  const loadingSkeletonClassName = 'tw-bg-grey-500';
2640
- function BadgeImage({ imageUrl, badgeUrl, size = 'sm', extraMarginForBadge, rounded = false, }) {
2640
+ var ImageState;
2641
+ (function (ImageState) {
2642
+ ImageState[ImageState["LOADING"] = 0] = "LOADING";
2643
+ ImageState[ImageState["LOADED"] = 1] = "LOADED";
2644
+ ImageState[ImageState["ERROR"] = 2] = "ERROR";
2645
+ })(ImageState || (ImageState = {}));
2646
+ function BadgeImage({ imageUrl: _imageUrl, badgeUrl, size = 'sm', extraMarginForBadge, rounded = false, placeholderImageUrl, }) {
2647
+ const imageUrl = (_imageUrl === null || _imageUrl === void 0 ? void 0 : _imageUrl.trim()) || placeholderImageUrl;
2641
2648
  const [imagesLoadState, setImageLoadState] = React.useState({
2642
- badgeLoaded: false,
2643
- mainImageLoaded: false,
2649
+ badge: ImageState.LOADING,
2650
+ mainImage: ImageState.LOADING,
2644
2651
  });
2645
2652
  const badgeImageClassName = cn('tw-absolute -tw-right-1/3 tw-bottom-0 tw-z-10 tw-m-0 -tw-translate-x-1/3 tw-rounded-md tw-border-[1px] tw-border-grey-800', badgeSizeClassMap[size]);
2646
2653
  const mainImageClassName = cn('tw-h-full tw-w-full tw-absolute', rounded ? ' tw-rounded-full' : 'tw-rounded-squid-xs');
2647
- return imageUrl ? (jsxRuntime.jsxs("div", { className: cn('tw-relative', extraMarginForBadge && badgeUrl ? 'tw-mr-1.5' : null, mainImageSizeClassMap[size]), children: [!imagesLoadState.mainImageLoaded && (jsxRuntime.jsx("div", { className: cn(mainImageClassName, loadingSkeletonClassName) })), jsxRuntime.jsx("img", { src: imageUrl, alt: "", onLoad: () => {
2654
+ return imageUrl ? (jsxRuntime.jsxs("div", { className: cn('tw-relative', extraMarginForBadge && badgeUrl ? 'tw-mr-1.5' : null, mainImageSizeClassMap[size]), children: [imagesLoadState.mainImage !== ImageState.LOADED &&
2655
+ (placeholderImageUrl ? (jsxRuntime.jsx("img", { src: placeholderImageUrl, alt: "", className: mainImageClassName })) : (jsxRuntime.jsx("div", { className: cn(mainImageClassName, loadingSkeletonClassName) }))), jsxRuntime.jsx("img", { src: imageUrl, alt: "", onError: () => {
2656
+ setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { mainImage: ImageState.ERROR })));
2657
+ }, onLoad: () => {
2648
2658
  // update state when image is fully loaded
2649
- setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { mainImageLoaded: true })));
2659
+ setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { mainImage: ImageState.LOADED })));
2650
2660
  }, className: cn(mainImageClassName,
2651
2661
  // hide main image while it is loading, and display it when it is loaded
2652
- imagesLoadState.mainImageLoaded ? 'tw-block' : 'tw-hidden') }), badgeUrl ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [!imagesLoadState.badgeLoaded && (jsxRuntime.jsx("div", { className: cn(badgeImageClassName, loadingSkeletonClassName) })), jsxRuntime.jsx("img", { src: badgeUrl, alt: "", onLoad: () => {
2662
+ imagesLoadState.mainImage === ImageState.LOADED
2663
+ ? 'tw-block'
2664
+ : 'tw-hidden') }), badgeUrl ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [imagesLoadState.badge !== ImageState.LOADED &&
2665
+ (placeholderImageUrl ? (jsxRuntime.jsx("img", { src: placeholderImageUrl, alt: "", className: badgeImageClassName })) : (jsxRuntime.jsx("div", { className: cn(badgeImageClassName, loadingSkeletonClassName) }))), jsxRuntime.jsx("img", { src: badgeUrl, alt: "", onError: () => {
2666
+ setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { badge: ImageState.ERROR })));
2667
+ }, onLoad: () => {
2653
2668
  // update state when badge image is fully loaded
2654
- setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { badgeLoaded: true })));
2669
+ setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { badge: ImageState.LOADED })));
2655
2670
  }, className: cn(badgeImageClassName,
2656
2671
  // hide badge image while it is loading, and display it when it is loaded
2657
- imagesLoadState.badgeLoaded ? 'tw-block' : 'tw-hidden') })] })) : null] })) : null;
2672
+ imagesLoadState.badge === ImageState.LOADED
2673
+ ? 'tw-block'
2674
+ : 'tw-hidden') })] })) : null] })) : null;
2658
2675
  }
2659
2676
 
2660
2677
  /******************************************************************************
@@ -17082,7 +17099,7 @@ const collapsedListItemClassMap = {
17082
17099
  large: 'tw-w-[80px]',
17083
17100
  };
17084
17101
  function ListItem(_a) {
17085
- var { itemTitle, mainImageUrl, subtitle, subtitleOnHover, detail, icon, secondaryImageUrl, size = 'large', mainIcon, className, isSelected, onDetailClick, showDetailOnHoverOnly, rounded = false, detailButtonClassName, loading, containerProps, compactOnMobile } = _a, props = __rest$1(_a, ["itemTitle", "mainImageUrl", "subtitle", "subtitleOnHover", "detail", "icon", "secondaryImageUrl", "size", "mainIcon", "className", "isSelected", "onDetailClick", "showDetailOnHoverOnly", "rounded", "detailButtonClassName", "loading", "containerProps", "compactOnMobile"]);
17102
+ var { itemTitle, mainImageUrl, subtitle, subtitleOnHover, detail, icon, secondaryImageUrl, placeholderImageUrl, size = 'large', mainIcon, className, isSelected, onDetailClick, showDetailOnHoverOnly, rounded = false, detailButtonClassName, loading, containerProps, compactOnMobile } = _a, props = __rest$1(_a, ["itemTitle", "mainImageUrl", "subtitle", "subtitleOnHover", "detail", "icon", "secondaryImageUrl", "placeholderImageUrl", "size", "mainIcon", "className", "isSelected", "onDetailClick", "showDetailOnHoverOnly", "rounded", "detailButtonClassName", "loading", "containerProps", "compactOnMobile"]);
17086
17103
  const subtitleClassName = cn('tw-h-[14px] tw-max-w-full tw-truncate !tw-leading-[16px] tw-text-grey-500', compactOnMobile ? 'tw-hidden mobile-lg:tw-block' : 'tw-block');
17087
17104
  // 'small' variant does not have detail
17088
17105
  const showDetail = size === 'large' && (!!detail || !!icon || showDetailOnHoverOnly);
@@ -17115,7 +17132,7 @@ function ListItem(_a) {
17115
17132
  const itemProps = isInteractive ? props : {};
17116
17133
  return (jsxRuntime.jsx("li", Object.assign({}, containerProps, { className: cn('tw-flex tw-max-w-full tw-bg-grey-900 tw-text-grey-300', listItemSizeMap[size], compactOnMobile
17117
17134
  ? `${collapsedListItemClassMap[size]} mobile-lg:tw-w-full`
17118
- : 'tw-w-full', className), children: jsxRuntime.jsxs(ItemTag, Object.assign({}, itemProps, { className: cn('tw-group/list-item tw-flex tw-w-full tw-max-w-full tw-items-center tw-justify-start tw-gap-squid-xs tw-rounded-squid-s tw-px-squid-xs tw-py-squid-xxs', isSelected && 'tw-bg-material-light-thin', isInteractive && 'hover:tw-bg-material-light-thin'), children: [size === 'large' ? (jsxRuntime.jsx("div", { className: "tw-h-10 tw-w-10", children: mainIcon ? (mainIcon) : (jsxRuntime.jsx(BadgeImage, { extraMarginForBadge: false, imageUrl: mainImageUrl, badgeUrl: secondaryImageUrl, size: "md", rounded: rounded })) })) : (jsxRuntime.jsx("div", { className: "tw-flex tw-min-h-[30px] tw-min-w-[30px] tw-items-center tw-justify-center", children: mainIcon ? (mainIcon) : (jsxRuntime.jsx("img", { src: mainImageUrl, className: "tw-h-[30px] tw-w-[30px] tw-rounded-squid-xs" })) })), jsxRuntime.jsxs("div", { className: cn('tw-flex tw-h-[40px] tw-flex-1 tw-flex-col tw-items-start tw-justify-center tw-gap-squid-xxs',
17135
+ : 'tw-w-full', className), children: jsxRuntime.jsxs(ItemTag, Object.assign({}, itemProps, { className: cn('tw-group/list-item tw-flex tw-w-full tw-max-w-full tw-items-center tw-justify-start tw-gap-squid-xs tw-rounded-squid-s tw-px-squid-xs tw-py-squid-xxs', isSelected && 'tw-bg-material-light-thin', isInteractive && 'hover:tw-bg-material-light-thin'), children: [size === 'large' ? (jsxRuntime.jsx("div", { className: "tw-h-10 tw-w-10", children: mainIcon ? (mainIcon) : (jsxRuntime.jsx(BadgeImage, { extraMarginForBadge: false, imageUrl: mainImageUrl, badgeUrl: secondaryImageUrl, placeholderImageUrl: placeholderImageUrl, size: "md", rounded: rounded })) })) : (jsxRuntime.jsx("div", { className: "tw-flex tw-min-h-[30px] tw-min-w-[30px] tw-items-center tw-justify-center", children: mainIcon ? (mainIcon) : (jsxRuntime.jsx("img", { src: mainImageUrl, className: "tw-h-[30px] tw-w-[30px] tw-rounded-squid-xs" })) })), jsxRuntime.jsxs("div", { className: cn('tw-flex tw-h-[40px] tw-flex-1 tw-flex-col tw-items-start tw-justify-center tw-gap-squid-xxs',
17119
17136
  // 'large' variant has extra padding
17120
17137
  size === 'large' ? 'tw-w-[56%] tw-pl-squid-xxs' : 'tw-w-[67%]'), children: [typeof itemTitle === 'string' ? (jsxRuntime.jsx(BodyText, { size: "small", className: cn('tw-max-w-full tw-truncate', subtitle && 'tw-h-[17px] !tw-leading-[17px]', compactOnMobile ? 'tw-hidden mobile-lg:tw-block' : 'tw-block'), children: itemTitle })) : (itemTitle), size === 'large' &&
17121
17138
  ((loading === null || loading === void 0 ? void 0 : loading.subtitle) ? (loadingComponent()) : subtitle ? (jsxRuntime.jsxs(CaptionText, { className: subtitleClassName, children: [subtitleOnHover && (jsxRuntime.jsx(CaptionText, { className: cn(subtitleClassName, 'tw-hidden group-hover/list-item:tw-block'), children: subtitleOnHover })), subtitle] })) : null)] }), showDetail && (jsxRuntime.jsxs(DetailTag, Object.assign({}, detailProps, { className: cn('tw-flex tw-w-fit tw-items-center tw-justify-center tw-rounded-squid-xs', size === 'large' ? 'tw-h-squid-xl' : 'tw-h-squid-l', showDetailOnHoverOnly
@@ -24435,6 +24452,7 @@ function NumericInput({ priceImpactPercentage, balance = '0', error, criticalPri
24435
24452
  }
24436
24453
  };
24437
24454
  const handleSwitchInputMode = () => {
24455
+ var _a;
24438
24456
  if (inputValue !== '') {
24439
24457
  const convertedAmount = inputMode === InputMode.TOKEN
24440
24458
  ? convertTokenAmountToUSD(inputValue, token.price, maxUsdDecimals)
@@ -24442,6 +24460,7 @@ function NumericInput({ priceImpactPercentage, balance = '0', error, criticalPri
24442
24460
  setInputValue(convertedAmount);
24443
24461
  }
24444
24462
  setInputMode((prevMode) => prevMode === InputMode.TOKEN ? InputMode.USD : InputMode.TOKEN);
24463
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
24445
24464
  };
24446
24465
  const getRawAmounts = (amount) => {
24447
24466
  if (amount === '')
@@ -24489,7 +24508,7 @@ function NumericInput({ priceImpactPercentage, balance = '0', error, criticalPri
24489
24508
  const { isTokenAmountVerySmall, isUsdAmountVerySmall } = getRawAmounts(inputValue);
24490
24509
  const amountFormatted = React.useMemo(() => {
24491
24510
  var _a;
24492
- if (inputValue === '')
24511
+ if (isNaN(Number(inputValue)) || inputValue === '')
24493
24512
  return '0';
24494
24513
  if (inputMode === InputMode.TOKEN) {
24495
24514
  if (direction === 'from') {
@@ -24517,9 +24536,10 @@ function NumericInput({ priceImpactPercentage, balance = '0', error, criticalPri
24517
24536
  const balanceFormatted = React.useMemo(() => {
24518
24537
  return formatAmount(balance !== null && balance !== void 0 ? balance : '0');
24519
24538
  }, [balance]);
24539
+ const inputRef = React.useRef(null);
24520
24540
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [isInteractive && !isLoading ? (jsxRuntime.jsxs("form", { className: "tw-relative tw-h-[80.6px] tw-px-squid-xs tw-pb-[15px] tw-pt-[5px] tw-text-heading-small tw-font-heading-regular mobile-lg:tw-h-[75px] mobile-lg:tw-px-squid-m", onSubmit: (e) => {
24521
24541
  e.preventDefault();
24522
- }, children: [inputMode === InputMode.USD && (jsxRuntime.jsx("span", { className: "tw-absolute tw-left-5 tw-top-[11px] tw-leading-[43px] tw-text-grey-600 mobile-lg:tw-left-[30px]", children: "$" })), jsxRuntime.jsx("input", { type: "text", value: inputValue, onChange: handleInputChange, placeholder: "0", className: cn('tw-h-[55px] tw-w-full tw-rounded-squid-s tw-bg-transparent tw-px-squid-xs tw-py-squid-s tw-text-grey-300 placeholder:tw-text-grey-600 hover:tw-bg-material-light-thin focus:tw-bg-material-light-thin focus:tw-text-royal-400 focus:tw-outline-none', inputMode === InputMode.USD && 'tw-pl-[33px]') })] })) : (jsxRuntime.jsx("div", { className: cn('tw-w-full tw-px-squid-xs tw-pb-[15px] tw-pt-[5px] mobile-lg:tw-px-squid-m', isLoading && loadingClassName), children: jsxRuntime.jsx("div", { className: "tw-flex tw-h-[55px] tw-w-full tw-items-center tw-rounded-squid-s tw-bg-transparent tw-px-squid-xs tw-py-squid-s tw-text-heading-small tw-font-heading-regular tw-text-grey-300", children: jsxRuntime.jsx("span", { children: inputValue || 0 }) }) })), !showDetails ? null : (jsxRuntime.jsxs("footer", { className: cn('tw-flex tw-h-squid-m tw-max-h-squid-m tw-items-center tw-justify-between tw-gap-2 tw-px-squid-xs tw-text-grey-500 mobile-lg:tw-px-squid-m', isLoading && loadingClassName), children: [error ? (jsxRuntime.jsx("div", { className: "tw-px-squid-xs", children: jsxRuntime.jsx(ErrorMessage, { message: error.message }) })) : (jsxRuntime.jsx(Tooltip, Object.assign({}, (isLoading
24542
+ }, children: [inputMode === InputMode.USD && (jsxRuntime.jsx("span", { className: "tw-absolute tw-left-5 tw-top-[11px] tw-leading-[43px] tw-text-grey-600 mobile-lg:tw-left-[30px]", children: "$" })), jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: inputValue, onChange: handleInputChange, placeholder: "0", className: cn('tw-h-[55px] tw-w-full tw-rounded-squid-s tw-bg-transparent tw-px-squid-xs tw-py-squid-s tw-text-grey-300 placeholder:tw-text-grey-600 hover:tw-bg-material-light-thin focus:tw-bg-material-light-thin focus:tw-text-royal-400 focus:tw-outline-none', inputMode === InputMode.USD && 'tw-pl-[33px]') })] })) : (jsxRuntime.jsx("div", { className: cn('tw-w-full tw-px-squid-xs tw-pb-[15px] tw-pt-[5px] mobile-lg:tw-px-squid-m', isLoading && loadingClassName), children: jsxRuntime.jsx("div", { className: "tw-flex tw-h-[55px] tw-w-full tw-items-center tw-rounded-squid-s tw-bg-transparent tw-px-squid-xs tw-py-squid-s tw-text-heading-small tw-font-heading-regular tw-text-grey-300", children: jsxRuntime.jsx("span", { children: inputValue || 0 }) }) })), !showDetails ? null : (jsxRuntime.jsxs("footer", { className: cn('tw-flex tw-h-squid-m tw-max-h-squid-m tw-items-center tw-justify-between tw-gap-2 tw-px-squid-xs tw-text-grey-500 mobile-lg:tw-px-squid-m', isLoading && loadingClassName), children: [error ? (jsxRuntime.jsx("div", { className: "tw-px-squid-xs", children: jsxRuntime.jsx(ErrorMessage, { message: error.message }) })) : (jsxRuntime.jsx(Tooltip, Object.assign({}, (isLoading
24523
24543
  ? undefined
24524
24544
  : inputMode === InputMode.TOKEN
24525
24545
  ? inputModeButton === null || inputModeButton === void 0 ? void 0 : inputModeButton.tokenModeTooltip
@@ -4,7 +4,12 @@ interface BadgeImageProps {
4
4
  size?: BadgeSize;
5
5
  extraMarginForBadge?: boolean;
6
6
  rounded?: boolean;
7
+ /**
8
+ * The URL of the placeholder image. Will be displayed while the images are loading
9
+ * or if there's an error loading them.
10
+ */
11
+ placeholderImageUrl?: string;
7
12
  }
8
13
  type BadgeSize = 'sm' | 'md';
9
- export declare function BadgeImage({ imageUrl, badgeUrl, size, extraMarginForBadge, rounded, }: BadgeImageProps): import("react/jsx-runtime").JSX.Element | null;
14
+ export declare function BadgeImage({ imageUrl: _imageUrl, badgeUrl, size, extraMarginForBadge, rounded, placeholderImageUrl, }: BadgeImageProps): import("react/jsx-runtime").JSX.Element | null;
10
15
  export {};
@@ -3,6 +3,7 @@ interface ListItemProps extends React.HTMLAttributes<HTMLButtonElement> {
3
3
  itemTitle: string | React.ReactNode;
4
4
  mainImageUrl?: string;
5
5
  secondaryImageUrl?: string;
6
+ placeholderImageUrl?: string;
6
7
  subtitle?: string;
7
8
  subtitleOnHover?: React.ReactNode;
8
9
  detail?: string;
@@ -22,5 +23,5 @@ interface ListItemProps extends React.HTMLAttributes<HTMLButtonElement> {
22
23
  compactOnMobile?: boolean;
23
24
  }
24
25
  type ListItemSize = 'small' | 'large';
25
- export declare function ListItem({ itemTitle, mainImageUrl, subtitle, subtitleOnHover, detail, icon, secondaryImageUrl, size, mainIcon, className, isSelected, onDetailClick, showDetailOnHoverOnly, rounded, detailButtonClassName, loading, containerProps, compactOnMobile, ...props }: ListItemProps): import("react/jsx-runtime").JSX.Element;
26
+ export declare function ListItem({ itemTitle, mainImageUrl, subtitle, subtitleOnHover, detail, icon, secondaryImageUrl, placeholderImageUrl, size, mainIcon, className, isSelected, onDetailClick, showDetailOnHoverOnly, rounded, detailButtonClassName, loading, containerProps, compactOnMobile, ...props }: ListItemProps): import("react/jsx-runtime").JSX.Element;
26
27
  export {};
@@ -9,3 +9,7 @@ export declare const ExtraMarginForBadge: Story;
9
9
  export declare const Rounded: Story;
10
10
  export declare const SizeMedium: Story;
11
11
  export declare const SizeSmall: Story;
12
+ export declare const CustomPlaceholderForBrokenImage: Story;
13
+ export declare const CustomPlaceholderForLoadingImage: Story;
14
+ export declare const CustomPlaceholderForBrokenBadgeImage: Story;
15
+ export declare const CustomPlaceholderForLoadingBadgeImage: Story;
@@ -4,4 +4,4 @@ declare const meta: Meta<typeof RangeInput>;
4
4
  export default meta;
5
5
  type Story = StoryObj<typeof meta>;
6
6
  export declare const Default: Story;
7
- export declare const Warning10: Story;
7
+ export declare const Warning: Story;
package/dist/esm/index.js CHANGED
@@ -2617,24 +2617,41 @@ const mainImageSizeClassMap = {
2617
2617
  md: 'tw-w-10 tw-h-10',
2618
2618
  };
2619
2619
  const loadingSkeletonClassName = 'tw-bg-grey-500';
2620
- function BadgeImage({ imageUrl, badgeUrl, size = 'sm', extraMarginForBadge, rounded = false, }) {
2620
+ var ImageState;
2621
+ (function (ImageState) {
2622
+ ImageState[ImageState["LOADING"] = 0] = "LOADING";
2623
+ ImageState[ImageState["LOADED"] = 1] = "LOADED";
2624
+ ImageState[ImageState["ERROR"] = 2] = "ERROR";
2625
+ })(ImageState || (ImageState = {}));
2626
+ function BadgeImage({ imageUrl: _imageUrl, badgeUrl, size = 'sm', extraMarginForBadge, rounded = false, placeholderImageUrl, }) {
2627
+ const imageUrl = (_imageUrl === null || _imageUrl === void 0 ? void 0 : _imageUrl.trim()) || placeholderImageUrl;
2621
2628
  const [imagesLoadState, setImageLoadState] = useState({
2622
- badgeLoaded: false,
2623
- mainImageLoaded: false,
2629
+ badge: ImageState.LOADING,
2630
+ mainImage: ImageState.LOADING,
2624
2631
  });
2625
2632
  const badgeImageClassName = cn('tw-absolute -tw-right-1/3 tw-bottom-0 tw-z-10 tw-m-0 -tw-translate-x-1/3 tw-rounded-md tw-border-[1px] tw-border-grey-800', badgeSizeClassMap[size]);
2626
2633
  const mainImageClassName = cn('tw-h-full tw-w-full tw-absolute', rounded ? ' tw-rounded-full' : 'tw-rounded-squid-xs');
2627
- return imageUrl ? (jsxs("div", { className: cn('tw-relative', extraMarginForBadge && badgeUrl ? 'tw-mr-1.5' : null, mainImageSizeClassMap[size]), children: [!imagesLoadState.mainImageLoaded && (jsx("div", { className: cn(mainImageClassName, loadingSkeletonClassName) })), jsx("img", { src: imageUrl, alt: "", onLoad: () => {
2634
+ return imageUrl ? (jsxs("div", { className: cn('tw-relative', extraMarginForBadge && badgeUrl ? 'tw-mr-1.5' : null, mainImageSizeClassMap[size]), children: [imagesLoadState.mainImage !== ImageState.LOADED &&
2635
+ (placeholderImageUrl ? (jsx("img", { src: placeholderImageUrl, alt: "", className: mainImageClassName })) : (jsx("div", { className: cn(mainImageClassName, loadingSkeletonClassName) }))), jsx("img", { src: imageUrl, alt: "", onError: () => {
2636
+ setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { mainImage: ImageState.ERROR })));
2637
+ }, onLoad: () => {
2628
2638
  // update state when image is fully loaded
2629
- setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { mainImageLoaded: true })));
2639
+ setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { mainImage: ImageState.LOADED })));
2630
2640
  }, className: cn(mainImageClassName,
2631
2641
  // hide main image while it is loading, and display it when it is loaded
2632
- imagesLoadState.mainImageLoaded ? 'tw-block' : 'tw-hidden') }), badgeUrl ? (jsxs(Fragment, { children: [!imagesLoadState.badgeLoaded && (jsx("div", { className: cn(badgeImageClassName, loadingSkeletonClassName) })), jsx("img", { src: badgeUrl, alt: "", onLoad: () => {
2642
+ imagesLoadState.mainImage === ImageState.LOADED
2643
+ ? 'tw-block'
2644
+ : 'tw-hidden') }), badgeUrl ? (jsxs(Fragment, { children: [imagesLoadState.badge !== ImageState.LOADED &&
2645
+ (placeholderImageUrl ? (jsx("img", { src: placeholderImageUrl, alt: "", className: badgeImageClassName })) : (jsx("div", { className: cn(badgeImageClassName, loadingSkeletonClassName) }))), jsx("img", { src: badgeUrl, alt: "", onError: () => {
2646
+ setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { badge: ImageState.ERROR })));
2647
+ }, onLoad: () => {
2633
2648
  // update state when badge image is fully loaded
2634
- setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { badgeLoaded: true })));
2649
+ setImageLoadState((prevState) => (Object.assign(Object.assign({}, prevState), { badge: ImageState.LOADED })));
2635
2650
  }, className: cn(badgeImageClassName,
2636
2651
  // hide badge image while it is loading, and display it when it is loaded
2637
- imagesLoadState.badgeLoaded ? 'tw-block' : 'tw-hidden') })] })) : null] })) : null;
2652
+ imagesLoadState.badge === ImageState.LOADED
2653
+ ? 'tw-block'
2654
+ : 'tw-hidden') })] })) : null] })) : null;
2638
2655
  }
2639
2656
 
2640
2657
  /******************************************************************************
@@ -17062,7 +17079,7 @@ const collapsedListItemClassMap = {
17062
17079
  large: 'tw-w-[80px]',
17063
17080
  };
17064
17081
  function ListItem(_a) {
17065
- var { itemTitle, mainImageUrl, subtitle, subtitleOnHover, detail, icon, secondaryImageUrl, size = 'large', mainIcon, className, isSelected, onDetailClick, showDetailOnHoverOnly, rounded = false, detailButtonClassName, loading, containerProps, compactOnMobile } = _a, props = __rest$1(_a, ["itemTitle", "mainImageUrl", "subtitle", "subtitleOnHover", "detail", "icon", "secondaryImageUrl", "size", "mainIcon", "className", "isSelected", "onDetailClick", "showDetailOnHoverOnly", "rounded", "detailButtonClassName", "loading", "containerProps", "compactOnMobile"]);
17082
+ var { itemTitle, mainImageUrl, subtitle, subtitleOnHover, detail, icon, secondaryImageUrl, placeholderImageUrl, size = 'large', mainIcon, className, isSelected, onDetailClick, showDetailOnHoverOnly, rounded = false, detailButtonClassName, loading, containerProps, compactOnMobile } = _a, props = __rest$1(_a, ["itemTitle", "mainImageUrl", "subtitle", "subtitleOnHover", "detail", "icon", "secondaryImageUrl", "placeholderImageUrl", "size", "mainIcon", "className", "isSelected", "onDetailClick", "showDetailOnHoverOnly", "rounded", "detailButtonClassName", "loading", "containerProps", "compactOnMobile"]);
17066
17083
  const subtitleClassName = cn('tw-h-[14px] tw-max-w-full tw-truncate !tw-leading-[16px] tw-text-grey-500', compactOnMobile ? 'tw-hidden mobile-lg:tw-block' : 'tw-block');
17067
17084
  // 'small' variant does not have detail
17068
17085
  const showDetail = size === 'large' && (!!detail || !!icon || showDetailOnHoverOnly);
@@ -17095,7 +17112,7 @@ function ListItem(_a) {
17095
17112
  const itemProps = isInteractive ? props : {};
17096
17113
  return (jsx("li", Object.assign({}, containerProps, { className: cn('tw-flex tw-max-w-full tw-bg-grey-900 tw-text-grey-300', listItemSizeMap[size], compactOnMobile
17097
17114
  ? `${collapsedListItemClassMap[size]} mobile-lg:tw-w-full`
17098
- : 'tw-w-full', className), children: jsxs(ItemTag, Object.assign({}, itemProps, { className: cn('tw-group/list-item tw-flex tw-w-full tw-max-w-full tw-items-center tw-justify-start tw-gap-squid-xs tw-rounded-squid-s tw-px-squid-xs tw-py-squid-xxs', isSelected && 'tw-bg-material-light-thin', isInteractive && 'hover:tw-bg-material-light-thin'), children: [size === 'large' ? (jsx("div", { className: "tw-h-10 tw-w-10", children: mainIcon ? (mainIcon) : (jsx(BadgeImage, { extraMarginForBadge: false, imageUrl: mainImageUrl, badgeUrl: secondaryImageUrl, size: "md", rounded: rounded })) })) : (jsx("div", { className: "tw-flex tw-min-h-[30px] tw-min-w-[30px] tw-items-center tw-justify-center", children: mainIcon ? (mainIcon) : (jsx("img", { src: mainImageUrl, className: "tw-h-[30px] tw-w-[30px] tw-rounded-squid-xs" })) })), jsxs("div", { className: cn('tw-flex tw-h-[40px] tw-flex-1 tw-flex-col tw-items-start tw-justify-center tw-gap-squid-xxs',
17115
+ : 'tw-w-full', className), children: jsxs(ItemTag, Object.assign({}, itemProps, { className: cn('tw-group/list-item tw-flex tw-w-full tw-max-w-full tw-items-center tw-justify-start tw-gap-squid-xs tw-rounded-squid-s tw-px-squid-xs tw-py-squid-xxs', isSelected && 'tw-bg-material-light-thin', isInteractive && 'hover:tw-bg-material-light-thin'), children: [size === 'large' ? (jsx("div", { className: "tw-h-10 tw-w-10", children: mainIcon ? (mainIcon) : (jsx(BadgeImage, { extraMarginForBadge: false, imageUrl: mainImageUrl, badgeUrl: secondaryImageUrl, placeholderImageUrl: placeholderImageUrl, size: "md", rounded: rounded })) })) : (jsx("div", { className: "tw-flex tw-min-h-[30px] tw-min-w-[30px] tw-items-center tw-justify-center", children: mainIcon ? (mainIcon) : (jsx("img", { src: mainImageUrl, className: "tw-h-[30px] tw-w-[30px] tw-rounded-squid-xs" })) })), jsxs("div", { className: cn('tw-flex tw-h-[40px] tw-flex-1 tw-flex-col tw-items-start tw-justify-center tw-gap-squid-xxs',
17099
17116
  // 'large' variant has extra padding
17100
17117
  size === 'large' ? 'tw-w-[56%] tw-pl-squid-xxs' : 'tw-w-[67%]'), children: [typeof itemTitle === 'string' ? (jsx(BodyText, { size: "small", className: cn('tw-max-w-full tw-truncate', subtitle && 'tw-h-[17px] !tw-leading-[17px]', compactOnMobile ? 'tw-hidden mobile-lg:tw-block' : 'tw-block'), children: itemTitle })) : (itemTitle), size === 'large' &&
17101
17118
  ((loading === null || loading === void 0 ? void 0 : loading.subtitle) ? (loadingComponent()) : subtitle ? (jsxs(CaptionText, { className: subtitleClassName, children: [subtitleOnHover && (jsx(CaptionText, { className: cn(subtitleClassName, 'tw-hidden group-hover/list-item:tw-block'), children: subtitleOnHover })), subtitle] })) : null)] }), showDetail && (jsxs(DetailTag, Object.assign({}, detailProps, { className: cn('tw-flex tw-w-fit tw-items-center tw-justify-center tw-rounded-squid-xs', size === 'large' ? 'tw-h-squid-xl' : 'tw-h-squid-l', showDetailOnHoverOnly
@@ -24415,6 +24432,7 @@ function NumericInput({ priceImpactPercentage, balance = '0', error, criticalPri
24415
24432
  }
24416
24433
  };
24417
24434
  const handleSwitchInputMode = () => {
24435
+ var _a;
24418
24436
  if (inputValue !== '') {
24419
24437
  const convertedAmount = inputMode === InputMode.TOKEN
24420
24438
  ? convertTokenAmountToUSD(inputValue, token.price, maxUsdDecimals)
@@ -24422,6 +24440,7 @@ function NumericInput({ priceImpactPercentage, balance = '0', error, criticalPri
24422
24440
  setInputValue(convertedAmount);
24423
24441
  }
24424
24442
  setInputMode((prevMode) => prevMode === InputMode.TOKEN ? InputMode.USD : InputMode.TOKEN);
24443
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
24425
24444
  };
24426
24445
  const getRawAmounts = (amount) => {
24427
24446
  if (amount === '')
@@ -24469,7 +24488,7 @@ function NumericInput({ priceImpactPercentage, balance = '0', error, criticalPri
24469
24488
  const { isTokenAmountVerySmall, isUsdAmountVerySmall } = getRawAmounts(inputValue);
24470
24489
  const amountFormatted = useMemo(() => {
24471
24490
  var _a;
24472
- if (inputValue === '')
24491
+ if (isNaN(Number(inputValue)) || inputValue === '')
24473
24492
  return '0';
24474
24493
  if (inputMode === InputMode.TOKEN) {
24475
24494
  if (direction === 'from') {
@@ -24497,9 +24516,10 @@ function NumericInput({ priceImpactPercentage, balance = '0', error, criticalPri
24497
24516
  const balanceFormatted = useMemo(() => {
24498
24517
  return formatAmount(balance !== null && balance !== void 0 ? balance : '0');
24499
24518
  }, [balance]);
24519
+ const inputRef = useRef(null);
24500
24520
  return (jsxs(Fragment, { children: [isInteractive && !isLoading ? (jsxs("form", { className: "tw-relative tw-h-[80.6px] tw-px-squid-xs tw-pb-[15px] tw-pt-[5px] tw-text-heading-small tw-font-heading-regular mobile-lg:tw-h-[75px] mobile-lg:tw-px-squid-m", onSubmit: (e) => {
24501
24521
  e.preventDefault();
24502
- }, children: [inputMode === InputMode.USD && (jsx("span", { className: "tw-absolute tw-left-5 tw-top-[11px] tw-leading-[43px] tw-text-grey-600 mobile-lg:tw-left-[30px]", children: "$" })), jsx("input", { type: "text", value: inputValue, onChange: handleInputChange, placeholder: "0", className: cn('tw-h-[55px] tw-w-full tw-rounded-squid-s tw-bg-transparent tw-px-squid-xs tw-py-squid-s tw-text-grey-300 placeholder:tw-text-grey-600 hover:tw-bg-material-light-thin focus:tw-bg-material-light-thin focus:tw-text-royal-400 focus:tw-outline-none', inputMode === InputMode.USD && 'tw-pl-[33px]') })] })) : (jsx("div", { className: cn('tw-w-full tw-px-squid-xs tw-pb-[15px] tw-pt-[5px] mobile-lg:tw-px-squid-m', isLoading && loadingClassName), children: jsx("div", { className: "tw-flex tw-h-[55px] tw-w-full tw-items-center tw-rounded-squid-s tw-bg-transparent tw-px-squid-xs tw-py-squid-s tw-text-heading-small tw-font-heading-regular tw-text-grey-300", children: jsx("span", { children: inputValue || 0 }) }) })), !showDetails ? null : (jsxs("footer", { className: cn('tw-flex tw-h-squid-m tw-max-h-squid-m tw-items-center tw-justify-between tw-gap-2 tw-px-squid-xs tw-text-grey-500 mobile-lg:tw-px-squid-m', isLoading && loadingClassName), children: [error ? (jsx("div", { className: "tw-px-squid-xs", children: jsx(ErrorMessage, { message: error.message }) })) : (jsx(Tooltip, Object.assign({}, (isLoading
24522
+ }, children: [inputMode === InputMode.USD && (jsx("span", { className: "tw-absolute tw-left-5 tw-top-[11px] tw-leading-[43px] tw-text-grey-600 mobile-lg:tw-left-[30px]", children: "$" })), jsx("input", { ref: inputRef, type: "text", value: inputValue, onChange: handleInputChange, placeholder: "0", className: cn('tw-h-[55px] tw-w-full tw-rounded-squid-s tw-bg-transparent tw-px-squid-xs tw-py-squid-s tw-text-grey-300 placeholder:tw-text-grey-600 hover:tw-bg-material-light-thin focus:tw-bg-material-light-thin focus:tw-text-royal-400 focus:tw-outline-none', inputMode === InputMode.USD && 'tw-pl-[33px]') })] })) : (jsx("div", { className: cn('tw-w-full tw-px-squid-xs tw-pb-[15px] tw-pt-[5px] mobile-lg:tw-px-squid-m', isLoading && loadingClassName), children: jsx("div", { className: "tw-flex tw-h-[55px] tw-w-full tw-items-center tw-rounded-squid-s tw-bg-transparent tw-px-squid-xs tw-py-squid-s tw-text-heading-small tw-font-heading-regular tw-text-grey-300", children: jsx("span", { children: inputValue || 0 }) }) })), !showDetails ? null : (jsxs("footer", { className: cn('tw-flex tw-h-squid-m tw-max-h-squid-m tw-items-center tw-justify-between tw-gap-2 tw-px-squid-xs tw-text-grey-500 mobile-lg:tw-px-squid-m', isLoading && loadingClassName), children: [error ? (jsx("div", { className: "tw-px-squid-xs", children: jsx(ErrorMessage, { message: error.message }) })) : (jsx(Tooltip, Object.assign({}, (isLoading
24503
24523
  ? undefined
24504
24524
  : inputMode === InputMode.TOKEN
24505
24525
  ? inputModeButton === null || inputModeButton === void 0 ? void 0 : inputModeButton.tokenModeTooltip
@@ -4,7 +4,12 @@ interface BadgeImageProps {
4
4
  size?: BadgeSize;
5
5
  extraMarginForBadge?: boolean;
6
6
  rounded?: boolean;
7
+ /**
8
+ * The URL of the placeholder image. Will be displayed while the images are loading
9
+ * or if there's an error loading them.
10
+ */
11
+ placeholderImageUrl?: string;
7
12
  }
8
13
  type BadgeSize = 'sm' | 'md';
9
- export declare function BadgeImage({ imageUrl, badgeUrl, size, extraMarginForBadge, rounded, }: BadgeImageProps): import("react/jsx-runtime").JSX.Element | null;
14
+ export declare function BadgeImage({ imageUrl: _imageUrl, badgeUrl, size, extraMarginForBadge, rounded, placeholderImageUrl, }: BadgeImageProps): import("react/jsx-runtime").JSX.Element | null;
10
15
  export {};
@@ -3,6 +3,7 @@ interface ListItemProps extends React.HTMLAttributes<HTMLButtonElement> {
3
3
  itemTitle: string | React.ReactNode;
4
4
  mainImageUrl?: string;
5
5
  secondaryImageUrl?: string;
6
+ placeholderImageUrl?: string;
6
7
  subtitle?: string;
7
8
  subtitleOnHover?: React.ReactNode;
8
9
  detail?: string;
@@ -22,5 +23,5 @@ interface ListItemProps extends React.HTMLAttributes<HTMLButtonElement> {
22
23
  compactOnMobile?: boolean;
23
24
  }
24
25
  type ListItemSize = 'small' | 'large';
25
- export declare function ListItem({ itemTitle, mainImageUrl, subtitle, subtitleOnHover, detail, icon, secondaryImageUrl, size, mainIcon, className, isSelected, onDetailClick, showDetailOnHoverOnly, rounded, detailButtonClassName, loading, containerProps, compactOnMobile, ...props }: ListItemProps): import("react/jsx-runtime").JSX.Element;
26
+ export declare function ListItem({ itemTitle, mainImageUrl, subtitle, subtitleOnHover, detail, icon, secondaryImageUrl, placeholderImageUrl, size, mainIcon, className, isSelected, onDetailClick, showDetailOnHoverOnly, rounded, detailButtonClassName, loading, containerProps, compactOnMobile, ...props }: ListItemProps): import("react/jsx-runtime").JSX.Element;
26
27
  export {};
@@ -9,3 +9,7 @@ export declare const ExtraMarginForBadge: Story;
9
9
  export declare const Rounded: Story;
10
10
  export declare const SizeMedium: Story;
11
11
  export declare const SizeSmall: Story;
12
+ export declare const CustomPlaceholderForBrokenImage: Story;
13
+ export declare const CustomPlaceholderForLoadingImage: Story;
14
+ export declare const CustomPlaceholderForBrokenBadgeImage: Story;
15
+ export declare const CustomPlaceholderForLoadingBadgeImage: Story;
@@ -4,4 +4,4 @@ declare const meta: Meta<typeof RangeInput>;
4
4
  export default meta;
5
5
  type Story = StoryObj<typeof meta>;
6
6
  export declare const Default: Story;
7
- export declare const Warning10: Story;
7
+ export declare const Warning: Story;
package/dist/index.d.ts CHANGED
@@ -11,9 +11,14 @@ interface BadgeImageProps {
11
11
  size?: BadgeSize;
12
12
  extraMarginForBadge?: boolean;
13
13
  rounded?: boolean;
14
+ /**
15
+ * The URL of the placeholder image. Will be displayed while the images are loading
16
+ * or if there's an error loading them.
17
+ */
18
+ placeholderImageUrl?: string;
14
19
  }
15
20
  type BadgeSize = 'sm' | 'md';
16
- declare function BadgeImage({ imageUrl, badgeUrl, size, extraMarginForBadge, rounded, }: BadgeImageProps): react_jsx_runtime.JSX.Element | null;
21
+ declare function BadgeImage({ imageUrl: _imageUrl, badgeUrl, size, extraMarginForBadge, rounded, placeholderImageUrl, }: BadgeImageProps): react_jsx_runtime.JSX.Element | null;
17
22
 
18
23
  type TextSize = 'small' | 'medium' | 'large';
19
24
  type SwitchSize = 'small' | 'large';
@@ -1224,6 +1229,7 @@ interface ListItemProps extends React.HTMLAttributes<HTMLButtonElement> {
1224
1229
  itemTitle: string | React.ReactNode;
1225
1230
  mainImageUrl?: string;
1226
1231
  secondaryImageUrl?: string;
1232
+ placeholderImageUrl?: string;
1227
1233
  subtitle?: string;
1228
1234
  subtitleOnHover?: React.ReactNode;
1229
1235
  detail?: string;
@@ -1243,7 +1249,7 @@ interface ListItemProps extends React.HTMLAttributes<HTMLButtonElement> {
1243
1249
  compactOnMobile?: boolean;
1244
1250
  }
1245
1251
  type ListItemSize = 'small' | 'large';
1246
- declare function ListItem({ itemTitle, mainImageUrl, subtitle, subtitleOnHover, detail, icon, secondaryImageUrl, size, mainIcon, className, isSelected, onDetailClick, showDetailOnHoverOnly, rounded, detailButtonClassName, loading, containerProps, compactOnMobile, ...props }: ListItemProps): react_jsx_runtime.JSX.Element;
1252
+ declare function ListItem({ itemTitle, mainImageUrl, subtitle, subtitleOnHover, detail, icon, secondaryImageUrl, placeholderImageUrl, size, mainIcon, className, isSelected, onDetailClick, showDetailOnHoverOnly, rounded, detailButtonClassName, loading, containerProps, compactOnMobile, ...props }: ListItemProps): react_jsx_runtime.JSX.Element;
1247
1253
 
1248
1254
  interface MenuItemProps {
1249
1255
  label: string;
package/package.json CHANGED
@@ -5,14 +5,14 @@
5
5
  "url": "git+https://github.com/0xsquid/squid-ui.git"
6
6
  },
7
7
  "description": "Squid's UI components",
8
- "version": "0.21.1-beta.0",
8
+ "version": "0.22.0",
9
9
  "author": "",
10
10
  "license": "MIT",
11
11
  "resolutions": {
12
12
  "string-width": "4.2.3"
13
13
  },
14
14
  "scripts": {
15
- "storybook": "concurrently 'yarn:watch:*'",
15
+ "dev": "concurrently 'yarn:watch:*'",
16
16
  "watch:storybook": "storybook dev -p 6006",
17
17
  "build-storybook": "storybook build",
18
18
  "watch:build-tailwind": "yarn build-tailwind --watch",