adding short text note modal

This commit is contained in:
zmeyer44 2023-10-18 08:15:06 -04:00
parent 307adbcd8a
commit 593bb467ad
7 changed files with 394 additions and 79 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -3,6 +3,7 @@
import { type ReactNode } from "react";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { HiOutlineChevronDown } from "react-icons/hi2";
import {
FieldErrors,
useForm,
@ -29,6 +30,19 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Textarea } from "@/components/ui/textarea";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
@ -53,13 +67,33 @@ type DefaultFieldType<TSchema> = {
value?: string | number | boolean;
custom?: ReactNode;
condition?: keyof z.infer<z.Schema<TSchema>> & string;
options?: { label: string; value: string; icon?: ReactNode }[];
options?: {
label: string;
value: string;
icon?: ReactNode;
description?: string;
}[];
};
type FieldType<TSchema> = DefaultFieldType<TSchema> &
(
| {
type: "select-search";
options: {
label: string;
description?: string;
value: string;
icon?: ReactNode;
}[];
}
| {
type: "select";
options: { label: string; value: string; icon?: ReactNode }[];
description?: string;
options: {
label: string;
value: string;
icon?: ReactNode;
description?: string;
}[];
}
| {
type: FieldOptions;
@ -111,81 +145,10 @@ export default function FormModal<TSchema extends FieldValues>({
condition,
...fieldProps
}) => {
if (!condition) {
return (
<FormField
key={slug}
control={form.control}
name={slug as Path<TSchema>}
render={({ field }) => (
<FormItem
className={cn(
type === "toggle" &&
"flex items-center gap-x-3 space-y-0",
)}
>
<FormLabel>{label}</FormLabel>
{type === "input" ? (
<FormControl>
<Input placeholder={placeholder} {...field} />
</FormControl>
) : type === "text-area" ? (
<FormControl>
<Textarea
placeholder={placeholder}
{...field}
className="auto-sizing"
/>
</FormControl>
) : type === "select" ? (
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
</FormControl>
<SelectContent className="z-modal+">
{fieldProps.options?.map((o) => (
<SelectItem key={o.value} value={o.value}>
{o.label}
</SelectItem>
))}
</SelectContent>
</Select>
) : type === "toggle" ? (
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
) : type === "number" ? (
<FormControl>
<Input
placeholder={placeholder}
type="number"
{...field}
/>
</FormControl>
) : (
<FormControl>
<Input placeholder={placeholder} {...field} />
</FormControl>
)}
{!!description && (
<FormDescription>{description}</FormDescription>
)}
<FormMessage />
</FormItem>
)}
/>
);
if (condition) {
const state = watch(condition as Path<TSchema>);
if (!state) return;
}
const state = watch(condition as Path<TSchema>);
if (!state) return;
return (
<FormField
key={slug}
@ -236,7 +199,71 @@ export default function FormModal<TSchema extends FieldValues>({
))}
</SelectContent>
</Select>
) : type === "toggle" ? (
) : type === "select-search" ? (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="flex">
{(() => {
const val = watch(slug as Path<TSchema>);
const selectedOption = fieldProps.options?.find(
(o) => o.value === val,
);
if (selectedOption) {
return selectedOption.label;
}
return "no list selected";
})()}{" "}
<HiOutlineChevronDown className="ml-2 h-4 w-4 text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent
className="z-modal+ p-0"
align="start"
>
<Command>
<CommandInput placeholder={placeholder} />
<CommandList>
<CommandEmpty>No options found.</CommandEmpty>
<CommandGroup className="p-1.5">
{fieldProps.options?.map((o) => (
<CommandItem
key={o.value}
onClick={() =>
console.log("Captured", o.value)
}
className="teamaspace-y-1 flex flex-col items-start px-4 py-2"
>
<p>{o.label}</p>
{!!o.description && (
<p className="text-sm text-muted-foreground">
{o.description}
</p>
)}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
) : // <Select
// onValueChange={field.onChange}
// defaultValue={field.value}
// >
// <FormControl>
// <SelectTrigger>
// <SelectValue placeholder={placeholder} />
// </SelectTrigger>
// </FormControl>
// <SelectContent className="z-modal+">
// {fieldProps.options?.map((o) => (
// <SelectItem key={o.value} value={o.value}>
// {o.label}
// </SelectItem>
// ))}
// </SelectContent>
// </Select>
type === "toggle" ? (
<FormControl>
<Switch
checked={field.value}

View File

@ -17,7 +17,7 @@ import { RiSubtractFill, RiAddFill } from "react-icons/ri";
import { formatCount } from "@/lib/utils";
import LoginModal from "./Login";
import CreateList from "./CreateList";
import ShortTextNoteModal from "./ShortTextNote";
export default function NewEventModal() {
const modal = useModal();
return (
@ -25,7 +25,7 @@ export default function NewEventModal() {
<div className="flex flex-col gap-y-5">
<Button
onClick={() => {
modal?.swap(<LoginModal />);
modal?.swap(<ShortTextNoteModal />);
}}
className="w-full gap-x-1"
>

View File

@ -0,0 +1,100 @@
import { useEffect, useState } from "react";
import FormModal from "./FormModal";
import { z } from "zod";
import useEvents from "@/lib/hooks/useEvents";
import { updateList } from "@/lib/actions/create";
import { unixTimeNowInSeconds } from "@/lib/nostr/dates";
import { useModal } from "@/app/_providers/modal/provider";
import { toast } from "sonner";
import { useNDK } from "@/app/_providers/ndk";
import { NostrEvent } from "@nostr-dev-kit/ndk";
import { getTagValues } from "@/lib/nostr/utils";
const ShortTextNoteSchema = z.object({
content: z.string(),
image: z.string().optional(),
list: z.string().optional(),
isPrivate: z.boolean().optional(),
});
type ShortTextNoteType = z.infer<typeof ShortTextNoteSchema>;
export default function ShortTextNoteModal() {
const modal = useModal();
const [isLoading, setIsLoading] = useState(false);
const [sent, setSent] = useState(false);
const { ndk } = useNDK();
// const { events } = useEvents({
// filter: {
// kinds: [listEvent.kind as number],
// authors: [listEvent.pubkey],
// since: unixTimeNowInSeconds() - 10,
// limit: 1,
// },
// enabled: sent,
// });
// useEffect(() => {
// if (events.length) {
// console.log("Done!");
// setIsLoading(false);
// toast.success("List Updated!");
// modal?.hide();
// }
// }, [events]);
async function handleSubmit(listData: ShortTextNoteType) {
setIsLoading(true);
const newTags = Object.entries(listData);
setSent(true);
// const result = await updateList(ndk!, listEvent, newTags);
}
return (
<FormModal
title="Short Text Note"
fields={[
{
label: "Content",
type: "text-area",
slug: "content",
},
{
label: "Image",
type: "input",
slug: "image",
},
{
label: "Add to list",
type: "select-search",
placeholder: "Search your lists",
slug: "list",
options: [
{
label: "Spicy Takes 🌶️",
value: "325grg ",
},
{
label: "Public reading list",
value: "grherh ",
},
{
label: "Radnosm other",
value: "grhfaggferh ",
},
],
},
{
label: "Private",
type: "toggle",
slug: "isPrivate",
},
]}
formSchema={ShortTextNoteSchema}
onSubmit={handleSubmit}
isSubmitting={isLoading}
cta={{
text: "Publish",
}}
/>
);
}

155
components/ui/command.tsx Normal file
View File

@ -0,0 +1,155 @@
"use client"
import * as React from "react"
import { DialogProps } from "@radix-ui/react-dialog"
import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
import { Command as CommandPrimitive } from "cmdk"
import { cn } from "@/lib/utils"
import { Dialog, DialogContent } from "@/components/ui/dialog"
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<MagnifyingGlassIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
CommandShortcut.displayName = "CommandShortcut"
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

31
components/ui/popover.tsx Normal file
View File

@ -0,0 +1,31 @@
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent }

View File

@ -21,6 +21,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2",
@ -30,6 +31,7 @@
"@tailwindcss/container-queries": "^0.1.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cmdk": "^0.2.0",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.10",
"focus-trap-react": "^10.2.3",