Skip to content

Commit

Permalink
draft: fab (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
bepyan authored Aug 20, 2024
1 parent 81a8ab8 commit 6aa9629
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 5 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"sonner": "^1.5.0",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.1",
"zod": "^3.23.8",
"zustand": "^4.5.4"
},
Expand Down
6 changes: 5 additions & 1 deletion src/app/(main)/i/[subdomain]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata, ResolvingMetadata } from "next";
import { notFound } from "next/navigation";
import Recursive from "~/components/editor/elements/recursive";
import FloatingActionButton from "~/components/editor/fab";
import EditorProvider from "~/components/editor/provider";
import { getInvitationByEventUrl } from "~/lib/db/schema/invitations.query";

Expand Down Expand Up @@ -45,10 +46,13 @@ export default async function Page({ params }: Props) {
editorData={invitation.customFields}
editorState={{ isPreviewMode: true }}
>
<main className="mx-auto max-w-md">
<main className="relative mx-auto max-w-lg">
{invitation.customFields.elements.map((childElement) => (
<Recursive key={childElement.id} element={childElement} />
))}
{invitation.customFields?.fab?.type === "invitation_response" && (
<FloatingActionButton />
)}
</main>
</EditorProvider>
);
Expand Down
4 changes: 4 additions & 0 deletions src/components/editor/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const kakaoMapDefaultStyles: React.CSSProperties = {
export const editorTabValue = {
ELEMENTS: "Elements",
SETTINGS: "Settings",
INVITATION_RESPONSE: "Invitation Response",
ELEMENT_SETTINGS: "Element Settings",
} as const;

Expand All @@ -61,6 +62,9 @@ export const bodyElement = {

export const initialEditorData: EditorData = {
elements: [bodyElement],
fab: {
type: "invitation_response",
},
};

export const initialEditorState: EditorState = {
Expand Down
15 changes: 15 additions & 0 deletions src/components/editor/fab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import BottomSheet from "~/app/(playground)/pg/bottom-sheet/_components/bottom-sheet";
import { useEditor } from "~/components/editor/provider";

export default function FloatingActionButton() {
const { editor } = useEditor();

switch (editor.data.fab.type) {
case "invitation_response":
return <BottomSheet />;
default:
return null;
}
}
48 changes: 48 additions & 0 deletions src/components/editor/fab/invitation-response-fab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client";

import { useForm } from "@tanstack/react-form";
import { toast } from "sonner";
import { Drawer } from "vaul";
import { SheetHeader, SheetTitle } from "~/components/ui/sheet";

export default function InvitationResponseFab() {
return (
<Drawer.Root>
<div className="absolute inset-x-0 bottom-0 mx-auto max-w-lg text-center">
<Drawer.Trigger className="mb-[5%] h-[63px] w-[80%] text-primary-foreground">
세션 참여 조사하기
</Drawer.Trigger>
</div>
<Drawer.Portal>
<Drawer.Overlay className="fixed inset-0 z-50 bg-black/80" />
<Drawer.Content className="fixed inset-x-0 bottom-0 z-50 mx-auto mt-24 flex h-auto max-w-lg flex-col rounded-t-[10px] border bg-background">
<Drawer.Handle className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
<InvitationResponseForm />
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
);
}

function InvitationResponseForm() {
const form = useForm({
defaultValues: {
name: "",
attendance: "",
},
onSubmit: async ({ value }) => {
const { name, attendance } = value;
console.log(name, attendance);
// await createInvitationResponses(name, attendance as unknown as boolean);
toast("참여가 완료되었습니다.", { duration: 2000 });
},
});

return (
<div className="h-[400px]">
<SheetHeader>
<SheetTitle className="text-left text-2xl">세션 참여 조사</SheetTitle>
</SheetHeader>
</div>
);
}
4 changes: 3 additions & 1 deletion src/components/editor/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { EyeOff } from "lucide-react";
import ElementHelper from "~/components/editor/elements/element-helper";
import Recursive from "~/components/editor/elements/recursive";
import FloatingActionButton from "~/components/editor/fab";
import { useEditor } from "~/components/editor/provider";
import { Button } from "~/components/ui/button";
import { ScrollArea } from "~/components/ui/scroll-area";
Expand All @@ -24,7 +25,7 @@ export default function EditorMain() {

return (
<div
className="flex flex-1 items-center justify-center"
className="relative flex flex-1 items-center justify-center"
onClick={handleClick}
>
<ScrollArea
Expand All @@ -50,6 +51,7 @@ export default function EditorMain() {
{editor.data.elements.map((childElement) => (
<Recursive key={childElement.id} element={childElement} />
))}
<FloatingActionButton />
</ScrollArea>
<ElementHelper />
</div>
Expand Down
7 changes: 5 additions & 2 deletions src/components/editor/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const EditorContext = createContext<{
});

export type EditorProps = {
editorData?: EditorData;
editorData?: Partial<EditorData>;
editorConfig?: Partial<EditorConfig>;
editorState?: Partial<Editor["state"]>;
};
Expand All @@ -38,7 +38,10 @@ export default function EditorProvider({
}: EditorProviderProps) {
const [editor, dispatch] = useReducer(editorReducer, {
...initialEditor,
data: editorData ?? initialEditor.data,
data: {
...initialEditor.data,
...editorData,
},
config: {
...initialEditorConfig,
...editorConfig,
Expand Down
8 changes: 7 additions & 1 deletion src/components/editor/sidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"use client";

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@radix-ui/react-tabs";
import { PlusIcon, SettingsIcon, WrenchIcon } from "lucide-react";
import { MailIcon, PlusIcon, SettingsIcon, WrenchIcon } from "lucide-react";
import { editorTabValue } from "~/components/editor/constant";
import { useEditor } from "~/components/editor/provider";
import SidebarElementSettingsTab from "~/components/editor/sidebar/sidebar-element-settings-tab";
import SidebarElementsTab from "~/components/editor/sidebar/sidebar-elements-tab";
import SidebarInvitationResponseTab from "~/components/editor/sidebar/sidebar-invitation-response-tab";
import SidebarSettingsTab from "~/components/editor/sidebar/sidebar-settings-tab";
import type { EditorTabTypeValue } from "~/components/editor/type";
import { isValidSelectEditorElement } from "~/components/editor/util";
Expand All @@ -29,6 +30,11 @@ export default function EditorSidebar() {
icon: <SettingsIcon />,
content: <SidebarSettingsTab />,
},
{
value: editorTabValue.INVITATION_RESPONSE,
icon: <MailIcon />,
content: <SidebarInvitationResponseTab />,
},
{
value: editorTabValue.ELEMENT_SETTINGS,
icon: <WrenchIcon />,
Expand Down
36 changes: 36 additions & 0 deletions src/components/editor/sidebar/sidebar-invitation-response-tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";

import { useEditor } from "~/components/editor/provider";
import {
SheetDescription,
SheetHeader,
SheetTitle,
} from "~/components/ui/sheet";
import { Switch } from "~/components/ui/switch";

export default function SidebarInvitationResponseTab() {
const { editor, dispatch } = useEditor();

return (
<div className="w-full border-b">
<SheetHeader className="p-6">
<div className="flex items-center justify-between">
<SheetTitle>초대 응답 설정</SheetTitle>
<Switch />
</div>
<SheetDescription>
이벤트에 대한 초대 응답 설정을 관리합니다.
</SheetDescription>
</SheetHeader>
{editor.config && <InvitationResponseContent />}
</div>
);
}

function InvitationResponseContent() {
return (
<div className="grid w-full grid-cols-9 gap-1 border-t p-6">
<div className="col-span-9"></div>
</div>
);
}
3 changes: 3 additions & 0 deletions src/components/editor/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ export type EditorState = {

export type EditorData = {
elements: EditorElement[];
fab: {
type: "" | "invitation_response";
};
};

export type EditorHistory = {
Expand Down
118 changes: 118 additions & 0 deletions src/components/ui/drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"use client";

import * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul";

import { cn } from "~/lib/utils";

const Drawer = ({
shouldScaleBackground = true,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root
shouldScaleBackground={shouldScaleBackground}
{...props}
/>
);
Drawer.displayName = "Drawer";

const DrawerTrigger = DrawerPrimitive.Trigger;

const DrawerPortal = DrawerPrimitive.Portal;

const DrawerClose = DrawerPrimitive.Close;

const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props}
/>
));
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;

const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className,
)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
));
DrawerContent.displayName = "DrawerContent";

const DrawerHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
{...props}
/>
);
DrawerHeader.displayName = "DrawerHeader";

const DrawerFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
);
DrawerFooter.displayName = "DrawerFooter";

const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
));
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;

const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;

export {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
DrawerPortal,
DrawerTitle,
DrawerTrigger,
};

0 comments on commit 6aa9629

Please sign in to comment.