Skip to content
This repository has been archived by the owner on Nov 13, 2024. It is now read-only.

Commit

Permalink
feat: add acknowledgments section and appearance switch [NOTICKET]
Browse files Browse the repository at this point in the history
  • Loading branch information
boostvolt committed Aug 7, 2023
1 parent 3d99d8a commit 0a0ca5a
Show file tree
Hide file tree
Showing 8 changed files with 527 additions and 72 deletions.
7 changes: 1 addition & 6 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,7 @@ interface RootLayoutProps {
export default function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
</head>
<head />
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
Expand Down
54 changes: 11 additions & 43 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,16 @@

import { zodResolver } from "@hookform/resolvers/zod"
import html2canvas from "html2canvas"
import { Cog, Info } from "lucide-react"
import { Info } from "lucide-react"
import Image from "next/image"
import { useEffect, useRef, useState } from "react"
import { useForm } from "react-hook-form"
import * as z from "zod"
import AppleMusic from "@/components/icons/apple-music"
import { SettingsMenu } from "@/components/settings-menu"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Avatar } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
Expand All @@ -31,7 +23,6 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp
import { SfProDisplay } from "@/lib/fonts/sf-pro-display"

const formSchema = z.object({
imageFormat: z.enum(["png", "jpeg"]),
appleMusicLogo: z.boolean(),
bigTitle: z.string(),
subTitle: z.string(),
Expand All @@ -44,7 +35,6 @@ export default function Home() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: formSchema.parse({
imageFormat: "png",
appleMusicLogo: true,
bigTitle: "",
subTitle: "",
Expand All @@ -61,7 +51,7 @@ export default function Home() {
if (element) {
html2canvas(element, { useCORS: true, allowTaint: true }).then((canvas) => {
const link = document.createElement("a")
link.download = `playlist-cover.${form.watch("imageFormat")}`
link.download = "playlist-cover.png"
link.href = canvas.toDataURL()
link.click()
})
Expand Down Expand Up @@ -122,33 +112,7 @@ export default function Home() {
<CardTitle>iCover</CardTitle>
</div>

<div className="flex space-x-4">
<Button onClick={handleDownload}>Download</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" title="Toggle image format settings">
<Cog className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Image Format</DropdownMenuLabel>
<FormField
control={form.control}
name="imageFormat"
render={({ field }) => (
<FormItem>
<FormControl>
<DropdownMenuRadioGroup onValueChange={field.onChange} value={field.value}>
<DropdownMenuRadioItem value="png">PNG</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="jpeg">JPEG</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</FormControl>
</FormItem>
)}
/>
</DropdownMenuContent>
</DropdownMenu>
</div>
<SettingsMenu downloadOnClick={handleDownload} />
</div>
</CardHeader>
<CardContent>
Expand All @@ -175,23 +139,27 @@ export default function Home() {
<div style={{ position: "absolute", top: 55, left: 25 }}>
<h1
ref={bigTitleRef}
className="font-semibold text-white whitespace-nowrap"
className="whitespace-nowrap font-semibold text-white"
style={{ fontSize: `${bigTitleFontSize}em` }}
>
{isEdited ? form.watch("bigTitle") : "Big Title"}
</h1>
</div>
{/* Sub Title */}
<div style={{ position: "absolute", top: 105, left: 25 }}>
<h2 ref={subTitleRef} className="font-thin text-white whitespace-nowrap" style={{ fontSize: `${subTitleFontSize}em` }}>
<h2
ref={subTitleRef}
className="whitespace-nowrap font-thin text-white"
style={{ fontSize: `${subTitleFontSize}em` }}
>
{isEdited ? form.watch("subTitle") : "Sub Title"}
</h2>
</div>
{/* Footer */}
<div style={{ position: "absolute", bottom: 25, left: 25 }}>
<h3
ref={footerRef}
className="text-white text-opacity-60 whitespace-nowrap"
className="whitespace-nowrap text-white text-opacity-60"
style={{ fontSize: `${footerFontSize}em` }}
>
{isEdited ? form.watch("footer") : "Footer"}
Expand Down
97 changes: 97 additions & 0 deletions components/settings-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"use client"

import { Cog } from "lucide-react"
import { useTheme } from "next-themes"
import { FC, useState } from "react"
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "./ui/alert-dialog"
import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarTrigger } from "./ui/menubar"

interface SettingsMenuProps {
downloadOnClick: () => void
}

export const SettingsMenu: FC<SettingsMenuProps> = ({ downloadOnClick }) => {
const { setTheme, theme } = useTheme()
const [isAckknowledgmentsOpen, setAckknowledgmentsOpen] = useState(false)

return (
<>
<Menubar>
<MenubarMenu>
<MenubarTrigger onClick={downloadOnClick}>Download</MenubarTrigger>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger title="Settings">
<Cog className="h-5 w-5" />
</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Switch Appearance</MenubarItem>
<MenubarSeparator />
<MenubarItem onClick={() => setAckknowledgmentsOpen(true)}>Acknowledgments</MenubarItem>
<MenubarItem disabled>v1.0.1</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
<AlertDialog
open={isAckknowledgmentsOpen}
onOpenChange={(isAckknowledgmentsOpen) => {
if (isAckknowledgmentsOpen === true) return
setAckknowledgmentsOpen(false)
}}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> Acknowledgments</AlertDialogTitle>
<AlertDialogDescription>
We are deeply inspired by the creativity and innovation of countless individuals and projects, which have
fueled our own endeavors and shaped the direction of this work.
<ul className="mt-4 space-y-1">
<li>
<a
href="https://amac.zephra.org"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
Amac
</a>
</li>
<li>
<a
href="https://coverx.vercel.app"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
CoverX
</a>
</li>
<li>
<a
href="https://apps.apple.com/en/app/denim-playlist-cover-maker/id1532250420"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
Denim
</a>
</li>
</ul>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Close</AlertDialogCancel>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)
}
19 changes: 0 additions & 19 deletions components/theme-toggle.tsx

This file was deleted.

107 changes: 107 additions & 0 deletions components/ui/alert-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"use client"

import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import * as React from "react"

import { buttonVariants } from "@/components/ui/button"
import { cn } from "@/lib/utils"

const AlertDialog = AlertDialogPrimitive.Root

const AlertDialogTrigger = AlertDialogPrimitive.Trigger

const AlertDialogPortal = ({ className, ...props }: AlertDialogPrimitive.AlertDialogPortalProps) => (
<AlertDialogPrimitive.Portal className={cn(className)} {...props} />
)
AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName

const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName

const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName

const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
)
AlertDialogHeader.displayName = "AlertDialogHeader"

const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
)
AlertDialogFooter.displayName = "AlertDialogFooter"

const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} />
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName

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

const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName

const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName

export {
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}
Loading

0 comments on commit 0a0ca5a

Please sign in to comment.