mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-22 00:34:26 +01:00
wip tag-editor
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
"@nostr-dev-kit/ndk": "^0.0.13",
|
||||
"@solidjs/meta": "^0.28.4",
|
||||
"@solidjs/router": "^0.8.2",
|
||||
"@thisbeyond/solid-select": "^0.14.0",
|
||||
"class-variance-authority": "^0.4.0",
|
||||
"nostr-tools": "^1.10.1",
|
||||
"qr-scanner": "^1.4.2",
|
||||
|
||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@@ -28,6 +28,9 @@ dependencies:
|
||||
'@solidjs/router':
|
||||
specifier: ^0.8.2
|
||||
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:
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0(typescript@4.9.5)
|
||||
@@ -1971,6 +1974,14 @@ packages:
|
||||
tslib: 2.5.0
|
||||
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:
|
||||
resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==}
|
||||
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";
|
||||
|
||||
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) => {
|
||||
return (
|
||||
<div class="safe-top safe-left safe-right safe-bottom flex flex-col h-screen-safe">
|
||||
<div class="flex-1 disable-scrollbars overflow-y-scroll md:pl-[8rem] md:pr-[6rem]">
|
||||
<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]"> */}
|
||||
{props.children}
|
||||
<div class="h-32" />
|
||||
</div>
|
||||
{/* </div> */}
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import party from '~/assets/party.gif';
|
||||
import { Amount } from "~/components/Amount";
|
||||
import { FullscreenModal } from "~/components/layout/FullscreenModal";
|
||||
import { BackButton } from "~/components/layout/BackButton";
|
||||
import { TagEditor } from "~/components/TagEditor";
|
||||
|
||||
type OnChainTx = {
|
||||
transaction: {
|
||||
@@ -181,28 +182,21 @@ export default function Receive() {
|
||||
return (
|
||||
<NodeManagerGuard>
|
||||
<SafeArea>
|
||||
<DefaultMain>
|
||||
<main class="max-w-[600px] flex flex-col gap-4 mx-auto p-4">
|
||||
<BackButton />
|
||||
<LargeHeader>Receive Bitcoin</LargeHeader>
|
||||
<Switch>
|
||||
<Match when={!unified() || receiveState() === "edit"}>
|
||||
<dl>
|
||||
<dd>
|
||||
|
||||
<AmountEditable initialAmountSats={amount() || "0"} setAmountSats={setAmount} onSave={handleAmountSave} />
|
||||
<div>
|
||||
<Button intent="glowy" layout="xs">Tag the sender</Button>
|
||||
</div>
|
||||
<form class="flex flex-col gap-4" onSubmit={onSubmit} >
|
||||
<TextField.Root
|
||||
value={label()}
|
||||
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 >
|
||||
</dd>
|
||||
<dd>
|
||||
<TagEditor />
|
||||
</dd>
|
||||
</dl>
|
||||
<Button class="w-full" disabled={!amount() || !label()} intent="green" onClick={onSubmit}>Create Invoice</Button>
|
||||
</Match>
|
||||
<Match when={unified() && receiveState() === "show"}>
|
||||
<div class="w-full bg-white rounded-xl">
|
||||
@@ -247,7 +241,7 @@ export default function Receive() {
|
||||
</FullscreenModal>
|
||||
</Match>
|
||||
</Switch>
|
||||
</DefaultMain>
|
||||
</main>
|
||||
<NavBar activeTab="receive" />
|
||||
</SafeArea >
|
||||
</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