mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-21 16:24:22 +01:00
wip tag-editor
This commit is contained in:
@@ -38,6 +38,7 @@
|
|||||||
"@nostr-dev-kit/ndk": "^0.0.13",
|
"@nostr-dev-kit/ndk": "^0.0.13",
|
||||||
"@solidjs/meta": "^0.28.4",
|
"@solidjs/meta": "^0.28.4",
|
||||||
"@solidjs/router": "^0.8.2",
|
"@solidjs/router": "^0.8.2",
|
||||||
|
"@thisbeyond/solid-select": "^0.14.0",
|
||||||
"class-variance-authority": "^0.4.0",
|
"class-variance-authority": "^0.4.0",
|
||||||
"nostr-tools": "^1.10.1",
|
"nostr-tools": "^1.10.1",
|
||||||
"qr-scanner": "^1.4.2",
|
"qr-scanner": "^1.4.2",
|
||||||
|
|||||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@@ -28,6 +28,9 @@ dependencies:
|
|||||||
'@solidjs/router':
|
'@solidjs/router':
|
||||||
specifier: ^0.8.2
|
specifier: ^0.8.2
|
||||||
version: 0.8.2(solid-js@1.7.3)
|
version: 0.8.2(solid-js@1.7.3)
|
||||||
|
'@thisbeyond/solid-select':
|
||||||
|
specifier: ^0.14.0
|
||||||
|
version: 0.14.0(solid-js@1.7.3)
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.0
|
||||||
version: 0.4.0(typescript@4.9.5)
|
version: 0.4.0(typescript@4.9.5)
|
||||||
@@ -1971,6 +1974,14 @@ packages:
|
|||||||
tslib: 2.5.0
|
tslib: 2.5.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@thisbeyond/solid-select@0.14.0(solid-js@1.7.3):
|
||||||
|
resolution: {integrity: sha512-ecq4U3Vnc/nJbU84ARuPg2scNuYt994ljF5AmBlzuZW87x43mWiGJ5hEWufIJJMpDT6CcnCIx/xbrdDkaDEHQw==}
|
||||||
|
peerDependencies:
|
||||||
|
solid-js: ^1.5
|
||||||
|
dependencies:
|
||||||
|
solid-js: 1.7.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/babel__core@7.20.0:
|
/@types/babel__core@7.20.0:
|
||||||
resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==}
|
resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
84
src/components/TagEditor.tsx
Normal file
84
src/components/TagEditor.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { Select, createOptions } from "@thisbeyond/solid-select";
|
||||||
|
import { For, createSignal } from "solid-js";
|
||||||
|
import "~/styles/solid-select.css"
|
||||||
|
import { SmallHeader } from "./layout";
|
||||||
|
|
||||||
|
|
||||||
|
// take two arrays, subtract the second from the first, then return the first
|
||||||
|
function subtract<T>(a: T[], b: T[]) {
|
||||||
|
const set = new Set(b);
|
||||||
|
return a.filter(x => !set.has(x));
|
||||||
|
};
|
||||||
|
|
||||||
|
// combine two arrays and keep only the items that are unique
|
||||||
|
function combineUnique<T>(a: T[], b: T[]) {
|
||||||
|
const set = new Set([...a, ...b]);
|
||||||
|
return [...set];
|
||||||
|
};
|
||||||
|
|
||||||
|
// simple math.random based id generator
|
||||||
|
const createUniqueId = () => Math.random().toString(36).substr(2, 9);
|
||||||
|
|
||||||
|
type Contact = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fakeContacts: Contact[] = [
|
||||||
|
{ id: createUniqueId(), name: "👤 Alice" },
|
||||||
|
{ id: createUniqueId(), name: "👤 Tony" },
|
||||||
|
{ id: createUniqueId(), name: "👤 @benthecarman" },
|
||||||
|
{ id: createUniqueId(), name: "👀 Uknown" }
|
||||||
|
]
|
||||||
|
|
||||||
|
const createValue = (name: string) => {
|
||||||
|
return { id: createUniqueId(), name };
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TagEditor() {
|
||||||
|
const candidates = [...fakeContacts];
|
||||||
|
|
||||||
|
const [values, setValues] = createSignal(candidates);
|
||||||
|
const [selectedValues, setSelectedValues] = createSignal<Contact[]>([]);
|
||||||
|
|
||||||
|
const onChange = (selected: Contact[]) => {
|
||||||
|
setSelectedValues(selected);
|
||||||
|
|
||||||
|
const lastValue = selected[selected.length - 1];
|
||||||
|
if (lastValue && !values().includes(lastValue)) {
|
||||||
|
setValues([...values(), lastValue]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = createOptions(values, {
|
||||||
|
key: "name",
|
||||||
|
disable: (value) => selectedValues().includes(value),
|
||||||
|
filterable: true, // Default
|
||||||
|
createable: createValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex flex-col gap-2 flex-grow flex-shrink flex-1" >
|
||||||
|
<SmallHeader>Tag the origin</SmallHeader>
|
||||||
|
<Select
|
||||||
|
multiple
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder="Where's it coming from?"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<div class="flex gap-2 flex-wrap">
|
||||||
|
<For each={subtract(fakeContacts, selectedValues())}>
|
||||||
|
{(contact) => (
|
||||||
|
<div onClick={() => onChange([...selectedValues(), contact])} class="bg-m-blue px-1 rounded cursor-pointer hover:outline-white hover:outline-1">+ {contact.name}</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
{/* <div>
|
||||||
|
<pre>{JSON.stringify(selectedValues(), null, 2)}</pre>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<pre>{JSON.stringify(values(), null, 2)}</pre>
|
||||||
|
</div> */}
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
}
|
||||||
0
src/components/TagEditorCustom.tsx
Normal file
0
src/components/TagEditorCustom.tsx
Normal file
@@ -2,5 +2,5 @@ import { A } from "solid-start";
|
|||||||
import { Back } from "~/assets/svg/Back";
|
import { Back } from "~/assets/svg/Back";
|
||||||
|
|
||||||
export function BackButton(props: { href?: string, title?: string }) {
|
export function BackButton(props: { href?: string, title?: string }) {
|
||||||
return (<A href={props.href ? props.href : "/"} class="text-m-red text-xl font-semibold no-underline md:hidden flex items-center"><Back />{props.title ? props.title : "Home"}</A>)
|
return (<A href={props.href ? props.href : "/"} class="text-m-red active:text-m-red/80 text-xl font-semibold no-underline md:hidden flex items-center"><Back />{props.title ? props.title : "Home"}</A>)
|
||||||
}
|
}
|
||||||
@@ -40,11 +40,10 @@ const FancyCard: ParentComponent<{ title?: string, tag?: JSX.Element }> = (props
|
|||||||
|
|
||||||
const SafeArea: ParentComponent = (props) => {
|
const SafeArea: ParentComponent = (props) => {
|
||||||
return (
|
return (
|
||||||
<div class="safe-top safe-left safe-right safe-bottom flex flex-col h-screen-safe">
|
<div class="safe-top safe-left safe-right safe-bottom">
|
||||||
<div class="flex-1 disable-scrollbars overflow-y-scroll md:pl-[8rem] md:pr-[6rem]">
|
{/* <div class="flex-1 disable-scrollbars overflow-y-scroll md:pl-[8rem] md:pr-[6rem]"> */}
|
||||||
{props.children}
|
{props.children}
|
||||||
<div class="h-32" />
|
{/* </div> */}
|
||||||
</div>
|
|
||||||
</div >
|
</div >
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import party from '~/assets/party.gif';
|
|||||||
import { Amount } from "~/components/Amount";
|
import { Amount } from "~/components/Amount";
|
||||||
import { FullscreenModal } from "~/components/layout/FullscreenModal";
|
import { FullscreenModal } from "~/components/layout/FullscreenModal";
|
||||||
import { BackButton } from "~/components/layout/BackButton";
|
import { BackButton } from "~/components/layout/BackButton";
|
||||||
|
import { TagEditor } from "~/components/TagEditor";
|
||||||
|
|
||||||
type OnChainTx = {
|
type OnChainTx = {
|
||||||
transaction: {
|
transaction: {
|
||||||
@@ -181,28 +182,21 @@ export default function Receive() {
|
|||||||
return (
|
return (
|
||||||
<NodeManagerGuard>
|
<NodeManagerGuard>
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<main class="max-w-[600px] flex flex-col gap-4 mx-auto p-4">
|
||||||
<BackButton />
|
<BackButton />
|
||||||
<LargeHeader>Receive Bitcoin</LargeHeader>
|
<LargeHeader>Receive Bitcoin</LargeHeader>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={!unified() || receiveState() === "edit"}>
|
<Match when={!unified() || receiveState() === "edit"}>
|
||||||
|
<dl>
|
||||||
|
<dd>
|
||||||
|
|
||||||
<AmountEditable initialAmountSats={amount() || "0"} setAmountSats={setAmount} onSave={handleAmountSave} />
|
<AmountEditable initialAmountSats={amount() || "0"} setAmountSats={setAmount} onSave={handleAmountSave} />
|
||||||
<div>
|
</dd>
|
||||||
<Button intent="glowy" layout="xs">Tag the sender</Button>
|
<dd>
|
||||||
</div>
|
<TagEditor />
|
||||||
<form class="flex flex-col gap-4" onSubmit={onSubmit} >
|
</dd>
|
||||||
<TextField.Root
|
</dl>
|
||||||
value={label()}
|
<Button class="w-full" disabled={!amount() || !label()} intent="green" onClick={onSubmit}>Create Invoice</Button>
|
||||||
onValueChange={setLabel}
|
|
||||||
class="flex flex-col gap-2"
|
|
||||||
>
|
|
||||||
<TextField.Label><SmallHeader>Label (private)</SmallHeader></TextField.Label>
|
|
||||||
<TextField.Input
|
|
||||||
ref={el => labelInput = el}
|
|
||||||
class="w-full p-2 rounded-lg text-black" />
|
|
||||||
</TextField.Root>
|
|
||||||
<Button disabled={!amount() || !label()} intent="green" type="submit">Create Invoice</Button>
|
|
||||||
</form >
|
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={unified() && receiveState() === "show"}>
|
<Match when={unified() && receiveState() === "show"}>
|
||||||
<div class="w-full bg-white rounded-xl">
|
<div class="w-full bg-white rounded-xl">
|
||||||
@@ -247,7 +241,7 @@ export default function Receive() {
|
|||||||
</FullscreenModal>
|
</FullscreenModal>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</DefaultMain>
|
</main>
|
||||||
<NavBar activeTab="receive" />
|
<NavBar activeTab="receive" />
|
||||||
</SafeArea >
|
</SafeArea >
|
||||||
</NodeManagerGuard>
|
</NodeManagerGuard>
|
||||||
|
|||||||
67
src/styles/solid-select.css
Normal file
67
src/styles/solid-select.css
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
.solid-select-container[data-disabled="true"] {
|
||||||
|
@apply pointer-events-none;
|
||||||
|
}
|
||||||
|
.solid-select-container {
|
||||||
|
@apply relative;
|
||||||
|
}
|
||||||
|
.solid-select-control[data-disabled="true"] {
|
||||||
|
}
|
||||||
|
.solid-select-control {
|
||||||
|
@apply w-full p-2 rounded-lg bg-white/10 placeholder-neutral-400;
|
||||||
|
@apply grid leading-6;
|
||||||
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
.solid-select-control[data-multiple="true"][data-has-value="true"] {
|
||||||
|
@apply flex items-stretch gap-1 flex-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.solid-select-placeholder {
|
||||||
|
@apply text-neutral-400;
|
||||||
|
@apply col-start-1 row-start-1;
|
||||||
|
}
|
||||||
|
.solid-select-single-value {
|
||||||
|
@apply col-start-1 row-start-1;
|
||||||
|
}
|
||||||
|
.solid-select-multi-value {
|
||||||
|
@apply flex bg-white/20 rounded items-center px-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.solid-select-multi-value-remove {
|
||||||
|
/* TODO: there's gotta be a better way to vertically center this */
|
||||||
|
@apply pl-2 pr-1 leading-3 -mt-2 text-2xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.solid-select-input {
|
||||||
|
@apply bg-transparent caret-transparent flex-grow flex-shrink;
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
@apply col-start-1 row-start-1;
|
||||||
|
}
|
||||||
|
.solid-select-input:read-only {
|
||||||
|
@apply cursor-default;
|
||||||
|
}
|
||||||
|
.solid-select-input[data-multiple="true"] {
|
||||||
|
@apply caret-current;
|
||||||
|
}
|
||||||
|
.solid-select-input[data-is-active="true"] {
|
||||||
|
@apply caret-current;
|
||||||
|
}
|
||||||
|
|
||||||
|
.solid-select-list {
|
||||||
|
@apply max-h-[50vh] min-w-full overflow-y-auto absolute whitespace-nowrap z-10 bg-neutral-950 p-2 rounded-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.solid-select-option[data-focused="true"] {
|
||||||
|
}
|
||||||
|
|
||||||
|
.solid-select-option > mark {
|
||||||
|
@apply underline;
|
||||||
|
}
|
||||||
|
.solid-select-option {
|
||||||
|
@apply cursor-default select-none p-1 hover:bg-neutral-800 rounded;
|
||||||
|
}
|
||||||
|
.solid-select-option[data-disabled="true"] {
|
||||||
|
@apply pointer-events-none text-neutral-500;
|
||||||
|
}
|
||||||
|
.solid-select-list-placeholder {
|
||||||
|
@apply cursor-default select-none;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user