mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-18 14:54:26 +01:00
add contact editor
This commit is contained in:
@@ -131,7 +131,7 @@ export function AmountEditable(props: { initialAmountSats: string, setAmountSats
|
||||
}
|
||||
|
||||
const DIALOG_POSITIONER = "fixed inset-0 safe-top safe-bottom z-50"
|
||||
const DIALOG_CONTENT = "h-full safe-bottom flex flex-col justify-between p-4 backdrop-blur-md bg-neutral-800/70"
|
||||
const DIALOG_CONTENT = "h-full safe-bottom flex flex-col justify-between p-4 backdrop-blur-xl bg-neutral-800/70"
|
||||
|
||||
return (
|
||||
<Dialog.Root isOpen={isOpen()}>
|
||||
|
||||
59
src/components/ColorRadioGroup.tsx
Normal file
59
src/components/ColorRadioGroup.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { RadioGroup as Kobalte } from '@kobalte/core';
|
||||
import { type JSX, Show, splitProps, For } from 'solid-js';
|
||||
|
||||
type RadioGroupProps = {
|
||||
name: string;
|
||||
label?: string | undefined;
|
||||
options: { label: string; value: string }[];
|
||||
value: string | undefined;
|
||||
error: string;
|
||||
required?: boolean | undefined;
|
||||
disabled?: boolean | undefined;
|
||||
ref: (element: HTMLInputElement | HTMLTextAreaElement) => void;
|
||||
onInput: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, InputEvent>;
|
||||
onChange: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, Event>;
|
||||
onBlur: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, FocusEvent>;
|
||||
};
|
||||
type Color = "blue" | "green" | "red" | "gray"
|
||||
|
||||
const colorVariants = {
|
||||
blue: "bg-m-blue",
|
||||
green: "bg-m-green",
|
||||
red: "bg-m-red",
|
||||
gray: "bg-[#898989]",
|
||||
}
|
||||
|
||||
export function ColorRadioGroup(props: RadioGroupProps) {
|
||||
const [rootProps, inputProps] = splitProps(
|
||||
props,
|
||||
['name', 'value', 'required', 'disabled'],
|
||||
['ref', 'onInput', 'onChange', 'onBlur']
|
||||
);
|
||||
return (
|
||||
<Kobalte.Root
|
||||
{...rootProps}
|
||||
validationState={props.error ? 'invalid' : 'valid'}
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<Show when={props.label}>
|
||||
<Kobalte.Label class="text-sm uppercase font-semibold">
|
||||
{props.label}
|
||||
</Kobalte.Label>
|
||||
</Show>
|
||||
<div class="flex gap-2">
|
||||
<For each={props.options}>
|
||||
{(option) => (
|
||||
<Kobalte.Item value={option.value} class="ui-checked:bg-neutral-950 rounded outline outline-black/50 ui-checked:outline-white ui-checked:outline-2">
|
||||
<Kobalte.ItemInput {...inputProps} />
|
||||
<Kobalte.ItemControl class={`${colorVariants[option.value as Color]} w-8 h-8 rounded`}>
|
||||
<Kobalte.ItemIndicator />
|
||||
</Kobalte.ItemControl>
|
||||
{/* <Kobalte.ItemLabel>{option.label}</Kobalte.ItemLabel> */}
|
||||
</Kobalte.Item>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<Kobalte.ErrorMessage>{props.error}</Kobalte.ErrorMessage>
|
||||
</Kobalte.Root>
|
||||
);
|
||||
}
|
||||
93
src/components/ContactEditor.tsx
Normal file
93
src/components/ContactEditor.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { For, Show, createEffect, createMemo, createSignal } from 'solid-js';
|
||||
import { Button, LargeHeader, VStack } from '~/components/layout';
|
||||
import { useMegaStore } from '~/state/megaStore';
|
||||
import { satsToUsd } from '~/utils/conversions';
|
||||
import { Amount } from './Amount';
|
||||
import { Dialog, RadioGroup } from '@kobalte/core';
|
||||
import close from "~/assets/icons/close.svg";
|
||||
import { SubmitHandler, createForm, required, reset, setValue } from '@modular-forms/solid';
|
||||
import { TextField } from './layout/TextField';
|
||||
import { ColorRadioGroup } from './ColorRadioGroup';
|
||||
|
||||
type Color = "blue" | "green" | "red" | "gray"
|
||||
|
||||
type Contact = {
|
||||
name: string;
|
||||
npub?: string;
|
||||
isExchange: boolean;
|
||||
color: string;
|
||||
}
|
||||
|
||||
// const colorOptions = ["blue", "green", "red", "gray"]
|
||||
|
||||
const colorOptions = [{ label: "blue", value: "blue" }, { label: "green", value: "green" }, { label: "red", value: "red" }, { label: "gray", value: "gray" }]
|
||||
|
||||
const INITIAL: Contact = { name: "", isExchange: false, color: "gray" }
|
||||
|
||||
export function ContactEditor(props: { createContact: (name: string) => void }) {
|
||||
const [isOpen, setIsOpen] = createSignal(true);
|
||||
|
||||
const [contactForm, { Form, Field }] = createForm<Contact>({ initialValues: INITIAL });
|
||||
|
||||
// What we're all here for in the first place: returning a value
|
||||
const handleSubmit: SubmitHandler<Contact> = (c: Contact) => {
|
||||
// e.preventDefault()
|
||||
|
||||
props.createContact(c.name)
|
||||
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
// When isOpen changes we reset the form
|
||||
if (isOpen()) {
|
||||
reset(contactForm, { initialValues: INITIAL })
|
||||
}
|
||||
})
|
||||
|
||||
const DIALOG_POSITIONER = "fixed inset-0 safe-top safe-bottom z-50"
|
||||
const DIALOG_CONTENT = "h-full safe-bottom flex flex-col justify-between p-4 backdrop-blur-xl bg-neutral-800/70"
|
||||
|
||||
return (
|
||||
<Dialog.Root isOpen={isOpen()}>
|
||||
<button onClick={() => setIsOpen(true)} class="border border-l-white/50 border-r-white/50 border-t-white/75 border-b-white/25 bg-black px-1 py-[0.5] rounded cursor-pointer hover:outline-white hover:outline-1">+ Add Contact</button>
|
||||
<Dialog.Portal>
|
||||
<div class={DIALOG_POSITIONER}>
|
||||
<Dialog.Content class={DIALOG_CONTENT} onEscapeKeyDown={() => setIsOpen(false)}>
|
||||
<div class="w-full flex justify-end">
|
||||
<button tabindex="-1" onClick={() => setIsOpen(false)} class="hover:bg-white/10 rounded-lg active:bg-m-blue">
|
||||
<img src={close} alt="Close" />
|
||||
</button>
|
||||
</div>
|
||||
<Form onSubmit={handleSubmit} class="flex flex-col flex-1 justify-around gap-4 max-w-[400px] mx-auto w-full">
|
||||
<div>
|
||||
|
||||
<LargeHeader>Create Contact</LargeHeader>
|
||||
<VStack>
|
||||
<Field name="name" validate={[required("We at least need a name")]}>
|
||||
{(field, props) => (
|
||||
<TextField {...props} placeholder='Satoshi' value={field.value} error={field.error} label="Name" />
|
||||
)}
|
||||
</Field>
|
||||
<Field name="npub" validate={[]}>
|
||||
{(field, props) => (
|
||||
<TextField {...props} placeholder='npub...' value={field.value} error={field.error} label="Nostr npub or NIP-05 (optional)" />
|
||||
)}
|
||||
</Field>
|
||||
<Field name="color">
|
||||
{(field, props) => (
|
||||
<ColorRadioGroup options={colorOptions} {...props} value={field.value} error={field.error} label="Color" />
|
||||
)}
|
||||
</Field>
|
||||
</VStack>
|
||||
</div>
|
||||
<Button type="submit" intent="blue" class="w-full flex-none">
|
||||
Create Contact
|
||||
</Button>
|
||||
</Form>
|
||||
</Dialog.Content>
|
||||
</div>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root >
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Select, createOptions } from "@thisbeyond/solid-select";
|
||||
import "~/styles/solid-select.css"
|
||||
import { SmallHeader } from "./layout";
|
||||
import { For } from "solid-js";
|
||||
import { ContactEditor } from "./ContactEditor";
|
||||
|
||||
// take two arrays, subtract the second from the first, then return the first
|
||||
function subtract<T>(a: T[], b: T[]) {
|
||||
@@ -41,6 +42,11 @@ export function TagEditor(props: { title: string, values: TagItem[], setValues:
|
||||
createable: createValue,
|
||||
});
|
||||
|
||||
const newContact = (name: string) => {
|
||||
const contact: TagItem = { id: createUniqueId(), name, kind: "contact" };
|
||||
onChange([...props.selectedValues, contact])
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-2 flex-grow flex-shrink flex-1" >
|
||||
<SmallHeader>{props.title}</SmallHeader>
|
||||
@@ -57,7 +63,8 @@ export function TagEditor(props: { title: string, values: TagItem[], setValues:
|
||||
<div onClick={() => onChange([...props.selectedValues, contact])} class="border border-l-white/50 border-r-white/50 border-t-white/75 border-b-white/25 bg-m-blue px-1 py-[0.5] rounded cursor-pointer hover:outline-white hover:outline-1">{contact.name}</div>
|
||||
)}
|
||||
</For>
|
||||
<button class="border border-l-white/50 border-r-white/50 border-t-white/75 border-b-white/25 bg-black px-1 py-[0.5] rounded cursor-pointer hover:outline-white hover:outline-1">+ Add Contact</button>
|
||||
{/* <button class="border border-l-white/50 border-r-white/50 border-t-white/75 border-b-white/25 bg-black px-1 py-[0.5] rounded cursor-pointer hover:outline-white hover:outline-1">+ Add Contact</button> */}
|
||||
<ContactEditor createContact={newContact} />
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user