ReceiveToSelect.tsx raw

   1  "use client";
   2  
   3  import { WalletIcon } from "lucide-react";
   4  import React from "react";
   5  import AppAvatar from "src/components/AppAvatar";
   6  import {
   7    Combobox,
   8    ComboboxContent,
   9    ComboboxEmpty,
  10    ComboboxInput,
  11    ComboboxItem,
  12    ComboboxList,
  13    useComboboxAnchor,
  14  } from "src/components/ui/combobox";
  15  import { InputGroupAddon } from "src/components/ui/input-group";
  16  import { Label } from "src/components/ui/label";
  17  import { APP_SELECT_APPS_LIMIT } from "src/constants";
  18  import { useApps } from "src/hooks/useApps";
  19  import { getAppDisplayName } from "src/lib/utils";
  20  import { App } from "src/types";
  21  
  22  const LIGHTNING_BALANCE = "lightning-balance";
  23  const LIGHTNING_BALANCE_LABEL = "Lightning Balance";
  24  
  25  type ReceiveToOption = {
  26    value: string;
  27    label: string;
  28    app?: App;
  29  };
  30  
  31  type Props = {
  32    appId?: number;
  33    onChange(appId: number | undefined): void;
  34  };
  35  
  36  function LightningOption() {
  37    return (
  38      <div className="flex items-center gap-3">
  39        <div className="flex size-6 items-center justify-center rounded-lg bg-muted">
  40          <WalletIcon className="size-3 text-muted-foreground" />
  41        </div>
  42        <div>{LIGHTNING_BALANCE_LABEL}</div>
  43      </div>
  44    );
  45  }
  46  
  47  function AppOption({ app }: { app: App }) {
  48    return (
  49      <div className="flex items-center gap-3">
  50        <AppAvatar app={app} className="size-6 rounded-lg" />
  51        <div className="min-w-0">
  52          <div>{getAppDisplayName(app.name)}</div>
  53        </div>
  54      </div>
  55    );
  56  }
  57  
  58  export default function ReceiveToSelect({ appId, onChange }: Props) {
  59    const anchorRef = useComboboxAnchor();
  60    const [search, setSearch] = React.useState("");
  61    const { data: appsData } = useApps(APP_SELECT_APPS_LIMIT, undefined, {
  62      name: search,
  63    });
  64  
  65    const apps = React.useMemo(
  66      () =>
  67        [...(appsData?.apps || [])].sort((a, b) =>
  68          getAppDisplayName(a.name).localeCompare(getAppDisplayName(b.name))
  69        ),
  70      [appsData?.apps]
  71    );
  72  
  73    const options = React.useMemo<ReceiveToOption[]>(
  74      () => [
  75        { value: LIGHTNING_BALANCE, label: LIGHTNING_BALANCE_LABEL },
  76        ...apps.map((app) => ({
  77          value: app.id.toString(),
  78          label: getAppDisplayName(app.name),
  79          app,
  80        })),
  81      ],
  82      [apps]
  83    );
  84  
  85    const selectedOption = options.find((opt) =>
  86      appId ? opt.value === appId.toString() : undefined
  87    );
  88  
  89    return (
  90      <div className="grid w-full gap-1.5">
  91        <Label>Receive to</Label>
  92        <Combobox
  93          items={options}
  94          value={selectedOption}
  95          itemToStringValue={(option) => option.value}
  96          onInputValueChange={setSearch}
  97          onValueChange={(option) =>
  98            onChange(
  99              !option || option.value === LIGHTNING_BALANCE
 100                ? undefined
 101                : Number(option.value)
 102            )
 103          }
 104        >
 105          <div ref={anchorRef} className="w-full">
 106            <ComboboxInput placeholder={LIGHTNING_BALANCE_LABEL}>
 107              <InputGroupAddon>
 108                {selectedOption?.app ? (
 109                  <AppAvatar
 110                    app={selectedOption.app}
 111                    className="size-6 rounded-full"
 112                  />
 113                ) : (
 114                  <div className="flex size-6 items-center justify-center rounded-sm bg-muted">
 115                    <WalletIcon className="size-3 text-muted-foreground" />
 116                  </div>
 117                )}
 118              </InputGroupAddon>
 119            </ComboboxInput>
 120          </div>
 121          <ComboboxContent anchor={anchorRef}>
 122            <ComboboxEmpty>No connections found.</ComboboxEmpty>
 123            <ComboboxList>
 124              {(option: ReceiveToOption) => (
 125                <ComboboxItem key={option.value} value={option}>
 126                  {option.app ? (
 127                    <AppOption app={option.app} />
 128                  ) : (
 129                    <LightningOption />
 130                  )}
 131                </ComboboxItem>
 132              )}
 133            </ComboboxList>
 134          </ComboboxContent>
 135        </Combobox>
 136      </div>
 137    );
 138  }
 139