mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2026-01-10 09:44:21 +01:00
qr redesign
This commit is contained in:
@@ -17,6 +17,7 @@ export function Amount(props: {
|
||||
loading?: boolean;
|
||||
centered?: boolean;
|
||||
icon?: "lightning" | "chain";
|
||||
whiteBg?: boolean;
|
||||
}) {
|
||||
const [state, _] = useMegaStore();
|
||||
|
||||
@@ -26,7 +27,9 @@ export function Amount(props: {
|
||||
return (
|
||||
<div
|
||||
class="flex flex-col gap-1"
|
||||
classList={{ "items-center": props.centered }}
|
||||
classList={{
|
||||
"items-center": props.centered
|
||||
}}
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<Show when={props.icon === "lightning"}>
|
||||
@@ -35,7 +38,12 @@ export function Amount(props: {
|
||||
<Show when={props.icon === "chain"}>
|
||||
<img src={chain} alt="chain" class="h-[18px]" />
|
||||
</Show>
|
||||
<h1 class="text-2xl font-light">
|
||||
<h1
|
||||
class="text-2xl font-light"
|
||||
classList={{
|
||||
"text-black": props.whiteBg
|
||||
}}
|
||||
>
|
||||
{props.loading
|
||||
? "..."
|
||||
: prettyPrintAmount(props.amountSats)}
|
||||
@@ -44,7 +52,13 @@ export function Amount(props: {
|
||||
</h1>
|
||||
</div>
|
||||
<Show when={props.showFiat}>
|
||||
<h2 class="text-sm font-light text-white/70">
|
||||
<h2
|
||||
class="text-sm font-light"
|
||||
classList={{
|
||||
"text-black": props.whiteBg,
|
||||
"text-white/70": !props.whiteBg
|
||||
}}
|
||||
>
|
||||
≈ {props.loading ? "..." : amountInUsd()}
|
||||
<span class="text-sm">USD</span>
|
||||
</h2>
|
||||
|
||||
111
src/components/IntegratedQR.tsx
Normal file
111
src/components/IntegratedQR.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import { Match, Show, Switch } from "solid-js";
|
||||
import { QRCodeSVG } from "solid-qr-code";
|
||||
import { ReceiveFlavor } from "~/routes/Receive";
|
||||
import { useCopy } from "~/utils/useCopy";
|
||||
import { Amount } from "./Amount";
|
||||
import { TruncateMiddle } from "./ShareCard";
|
||||
import copyBlack from "~/assets/icons/copy-black.svg";
|
||||
import shareBlack from "~/assets/icons/share-black.svg";
|
||||
import chainBlack from "~/assets/icons/chain-black.svg";
|
||||
import boltBlack from "~/assets/icons/bolt-black.svg";
|
||||
|
||||
function KindIndicator(props: { kind: ReceiveFlavor }) {
|
||||
return (
|
||||
<div class="text-black flex flex-col items-end">
|
||||
<Switch>
|
||||
<Match when={props.kind === "onchain"}>
|
||||
<h3 class="font-semibold">On-chain</h3>
|
||||
<img src={chainBlack} alt="chain" />
|
||||
</Match>
|
||||
|
||||
<Match when={props.kind === "lightning"}>
|
||||
<h3 class="font-semibold">Lightning</h3>
|
||||
<img src={boltBlack} alt="bolt" />
|
||||
</Match>
|
||||
|
||||
<Match when={props.kind === "unified"}>
|
||||
<h3 class="font-semibold">Unified</h3>
|
||||
<div class="flex gap-1">
|
||||
<img src={chainBlack} alt="chain" />
|
||||
<img src={boltBlack} alt="bolt" />
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function share(receiveString: string) {
|
||||
// If the browser doesn't support share we can just copy the address
|
||||
if (!navigator.share) {
|
||||
console.error("Share not supported");
|
||||
}
|
||||
const shareData: ShareData = {
|
||||
title: "Mutiny Wallet",
|
||||
text: receiveString
|
||||
};
|
||||
try {
|
||||
await navigator.share(shareData);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function IntegratedQr(props: {
|
||||
value: string;
|
||||
amountSats: string;
|
||||
kind: ReceiveFlavor;
|
||||
}) {
|
||||
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
|
||||
return (
|
||||
<div
|
||||
id="qr"
|
||||
class="w-full bg-white rounded-xl relative flex flex-col items-center px-4"
|
||||
onClick={() => copy(props.value)}
|
||||
>
|
||||
<Show when={copied()}>
|
||||
<div class="absolute w-full h-full bg-neutral-900/60 z-50 rounded-xl flex flex-col items-center justify-center transition-all">
|
||||
<p class="text-xl font-bold">Copied</p>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="w-full flex justify-between items-center py-4 max-w-[256px]">
|
||||
<Amount
|
||||
amountSats={Number(props.amountSats)}
|
||||
showFiat
|
||||
whiteBg
|
||||
/>
|
||||
<KindIndicator kind={props.kind} />
|
||||
</div>
|
||||
|
||||
<QRCodeSVG
|
||||
value={props.value}
|
||||
class="w-full h-full max-h-[256px]"
|
||||
/>
|
||||
<div
|
||||
class="w-full grid gap-1 py-4 max-w-[256px] "
|
||||
classList={{
|
||||
"grid-cols-[2rem_minmax(0,1fr)_2rem]": !!navigator.share,
|
||||
"grid-cols-[minmax(0,1fr)_2rem]": !navigator.share
|
||||
}}
|
||||
>
|
||||
<Show when={!!navigator.share}>
|
||||
<button
|
||||
class="justify-self-start"
|
||||
onClick={(_) => share(props.value)}
|
||||
>
|
||||
<img src={shareBlack} alt="share" />
|
||||
</button>
|
||||
</Show>
|
||||
<div class="">
|
||||
<TruncateMiddle text={props.value} whiteBg />
|
||||
</div>
|
||||
<button
|
||||
class=" justify-self-end"
|
||||
onClick={() => copy(props.value)}
|
||||
>
|
||||
<img src={copyBlack} alt="copy" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Card, VStack } from "~/components/layout";
|
||||
import { useCopy } from "~/utils/useCopy";
|
||||
import copyIcon from "~/assets/icons/copy.svg";
|
||||
import copyBlack from "~/assets/icons/copy-black.svg";
|
||||
import shareIcon from "~/assets/icons/share.svg";
|
||||
import shareBlack from "~/assets/icons/share-black.svg";
|
||||
import eyeIcon from "~/assets/icons/eye.svg";
|
||||
import { Show, createSignal } from "solid-js";
|
||||
import { JsonModal } from "./JsonModal";
|
||||
@@ -9,7 +11,10 @@ import { JsonModal } from "./JsonModal";
|
||||
const STYLE =
|
||||
"px-4 py-2 rounded-xl border-2 border-white flex gap-2 items-center font-semibold hover:text-m-blue transition-colors";
|
||||
|
||||
export function ShareButton(props: { receiveString: string }) {
|
||||
export function ShareButton(props: {
|
||||
receiveString: string;
|
||||
whiteBg?: boolean;
|
||||
}) {
|
||||
async function share(receiveString: string) {
|
||||
// If the browser doesn't support share we can just copy the address
|
||||
if (!navigator.share) {
|
||||
@@ -29,14 +34,20 @@ export function ShareButton(props: { receiveString: string }) {
|
||||
return (
|
||||
<button class={STYLE} onClick={(_) => share(props.receiveString)}>
|
||||
<span>Share</span>
|
||||
<img src={shareIcon} alt="share" />
|
||||
<img src={props.whiteBg ? shareBlack : shareIcon} alt="share" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function TruncateMiddle(props: { text: string }) {
|
||||
export function TruncateMiddle(props: { text: string; whiteBg?: boolean }) {
|
||||
return (
|
||||
<div class="flex text-neutral-300 font-mono">
|
||||
<div
|
||||
class="flex font-mono"
|
||||
classList={{
|
||||
"text-black": props.whiteBg,
|
||||
"text-neutral-300": !props.whiteBg
|
||||
}}
|
||||
>
|
||||
<span class="truncate">{props.text}</span>
|
||||
<span class="pr-2">
|
||||
{props.text.length > 8 ? props.text.slice(-8) : ""}
|
||||
@@ -65,7 +76,11 @@ export function StringShower(props: { text: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function CopyButton(props: { text?: string; title?: string }) {
|
||||
export function CopyButton(props: {
|
||||
text?: string;
|
||||
title?: string;
|
||||
whiteBg?: boolean;
|
||||
}) {
|
||||
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
|
||||
|
||||
function handleCopy() {
|
||||
@@ -75,7 +90,7 @@ export function CopyButton(props: { text?: string; title?: string }) {
|
||||
return (
|
||||
<button class={STYLE} onClick={handleCopy}>
|
||||
{copied() ? "Copied" : props.title ?? "Copy"}
|
||||
<img src={copyIcon} alt="copy" />
|
||||
<img src={props.whiteBg ? copyBlack : copyIcon} alt="copy" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,16 +15,20 @@ export function StyledRadioGroup(props: {
|
||||
onValueChange: (value: string) => void;
|
||||
small?: boolean;
|
||||
accent?: "red" | "white";
|
||||
vertical?: boolean;
|
||||
}) {
|
||||
return (
|
||||
// TODO: rewrite this with CVA, props are bad for tailwind
|
||||
<RadioGroup.Root
|
||||
value={props.value}
|
||||
onChange={props.onValueChange}
|
||||
class={"grid w-full gap-4"}
|
||||
class={"w-full gap-4"}
|
||||
classList={{
|
||||
"grid-cols-2": props.choices.length === 2,
|
||||
"grid-cols-3": props.choices.length === 3,
|
||||
"flex flex-col": props.vertical,
|
||||
"grid grid-cols-2":
|
||||
props.choices.length === 2 && !props.vertical,
|
||||
"grid grid-cols-3":
|
||||
props.choices.length === 3 && !props.vertical,
|
||||
"gap-2": props.small
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "solid-js";
|
||||
import Linkify from "./Linkify";
|
||||
import { Button, ButtonLink } from "./Button";
|
||||
import { Checkbox as KCheckbox, Separator } from "@kobalte/core";
|
||||
import { Dialog, Checkbox as KCheckbox, Separator } from "@kobalte/core";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import check from "~/assets/icons/check.svg";
|
||||
import { MutinyTagItem } from "~/utils/tags";
|
||||
@@ -75,7 +75,6 @@ export const SettingsCard: ParentComponent<{
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const SafeArea: ParentComponent = (props) => {
|
||||
return (
|
||||
<div class="h-[100dvh] safe-left safe-right">
|
||||
@@ -166,7 +165,7 @@ export const LargeHeader: ParentComponent<{ action?: JSX.Element }> = (
|
||||
) => {
|
||||
return (
|
||||
<header class="w-full flex justify-between items-center mt-4 mb-2">
|
||||
<h1 class="text-3xl font-semibold">{props.children}</h1>
|
||||
<h1 class="text-2xl font-semibold">{props.children}</h1>
|
||||
<Show when={props.action}>{props.action}</Show>
|
||||
</header>
|
||||
);
|
||||
@@ -282,3 +281,38 @@ export function ModalCloseButton() {
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export const SIMPLE_OVERLAY = "fixed inset-0 z-50 bg-black/70 backdrop-blur-md";
|
||||
export const SIMPLE_DIALOG_POSITIONER =
|
||||
"fixed inset-0 z-50 flex items-center justify-center";
|
||||
export const SIMPLE_DIALOG_CONTENT =
|
||||
"max-w-[500px] w-[90vw] max-h-[100dvh] overflow-y-scroll disable-scrollbars mx-4 p-4 bg-neutral-800/80 backdrop-blur-md shadow-xl rounded-xl border border-white/10";
|
||||
|
||||
export const SimpleDialog: ParentComponent<{
|
||||
title: string;
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
}> = (props) => {
|
||||
return (
|
||||
<Dialog.Root open={props.open} onOpenChange={props.setOpen}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay class={SIMPLE_OVERLAY} />
|
||||
<div class={SIMPLE_DIALOG_POSITIONER}>
|
||||
<Dialog.Content class={SIMPLE_DIALOG_CONTENT}>
|
||||
<div class="flex justify-between mb-2 items-center">
|
||||
<Dialog.Title>
|
||||
<SmallHeader>{props.title}</SmallHeader>
|
||||
</Dialog.Title>
|
||||
<Dialog.CloseButton>
|
||||
<ModalCloseButton />
|
||||
</Dialog.CloseButton>
|
||||
</div>
|
||||
<Dialog.Description class="flex flex-col gap-4">
|
||||
{props.children}
|
||||
</Dialog.Description>
|
||||
</Dialog.Content>
|
||||
</div>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user