-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e8a4d7e
commit bf22880
Showing
79 changed files
with
660,642 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { cn } from "@/utils/cn"; | ||
import React from "react"; | ||
|
||
export function Button({ children, className, ...rest }: React.ComponentPropsWithoutRef<"button">) { | ||
return ( | ||
<button | ||
className={cn( | ||
"max-sm:flex-1 bg-black text-smokewhite rounded-lg px-6 py-2 text-sm active:shadow-none active:translate-y-[2px] shadow-b-small shadow-border border border-border disabled:text-opacity-50 disabled:bg-zinc disabled:pointer-events-none", | ||
className | ||
)} | ||
{...rest} | ||
> | ||
{children} | ||
</button> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import { ChineseCharacter } from "@/data"; | ||
import { BASE_URL } from "@/pages/_app"; | ||
import { cn } from "@/utils/cn"; | ||
import clsx from "clsx"; | ||
import Link from "next/link"; | ||
import React from "react"; | ||
import { preload } from "swr"; | ||
|
||
export async function preloadHanziDetails(hanzi: string) { | ||
await preload(`hanzi/${hanzi}`, async (url) => { | ||
const response = await fetch(`${BASE_URL}/api/${url}`); | ||
const data = await response.json(); | ||
return data; | ||
}); | ||
} | ||
|
||
export function CharacterCard({ | ||
id, | ||
hanzi, | ||
pinyin, | ||
translations, | ||
isFlipped, | ||
isCompleted, | ||
hanziHref, | ||
onFlip, | ||
onCompleteToggle, | ||
}: ChineseCharacter & { | ||
hanziHref: string; | ||
isCompleted: boolean; | ||
onCompleteToggle: () => void; | ||
onFlip: () => void; | ||
isFlipped: boolean; | ||
}) { | ||
return ( | ||
<> | ||
<style jsx>{` | ||
.card { | ||
transition: transform 0.5s; | ||
transform-style: preserve-3d; | ||
} | ||
.card-flipped { | ||
transform: rotateY(180deg); | ||
} | ||
.card-content { | ||
-webkit-backface-visibility: hidden; /* Safari */ | ||
backface-visibility: hidden; | ||
} | ||
.text-flipped { | ||
transform: rotateY(180deg); | ||
} | ||
`}</style> | ||
<div onClick={onFlip} className="select-none font-chinese text-3xl aspect-square"> | ||
<div className={clsx("relative w-full h-full card", isFlipped && "card-flipped")}> | ||
<div | ||
className={clsx( | ||
"card-content has-[input:active]:scale-[98%] transition absolute inset-0 border-2 shadow-b-small rounded-lg grid place-items-center bg-softblack", | ||
isCompleted ? "border-mossgreen shadow-mossgreen text-wheat" : "border-border shadow-border" | ||
)} | ||
> | ||
{hanzi} | ||
|
||
<MarkAsCompleted | ||
className={isCompleted ? "bg-transparent" : ""} | ||
isCompleted={isCompleted} | ||
onClick={onCompleteToggle} | ||
/> | ||
|
||
<div | ||
className={clsx( | ||
"absolute top-2 right-2 transition p-2 rounded-md", | ||
!isCompleted ? "text-gray/50 active:bg-zinc" : "active:bg-mossgreen/10" | ||
)} | ||
> | ||
<Link | ||
onMouseEnter={() => preloadHanziDetails(hanzi)} | ||
onClick={(e) => e.stopPropagation()} | ||
href={hanziHref} | ||
shallow | ||
> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
width="24" | ||
height="24" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
stroke="currentColor" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
> | ||
<circle cx="12" cy="12" r="10" /> | ||
<path d="M12 16v-4" /> | ||
<path d="M12 8h.01" /> | ||
</svg> | ||
</Link> | ||
</div> | ||
|
||
<div className="absolute left-4 top-4 text-sm">{id}</div> | ||
</div> | ||
<div | ||
className={clsx( | ||
"card-content absolute inset-0 border-2 shadow-b-small rounded-lg bg-softblack flex flex-col items-center justify-center text-flipped px-4", | ||
isCompleted ? "border-mossgreen shadow-mossgreen text-wheat" : "border-border shadow-border" | ||
)} | ||
> | ||
<div className="text-2xl">{pinyin}</div> | ||
<div className="text-sm text-center">{translations.join(", ")}</div> | ||
</div> | ||
</div> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
export function MarkAsCompleted({ | ||
className, | ||
checkmarkClassName, | ||
isCompleted, | ||
onClick, | ||
}: { | ||
className?: string; | ||
checkmarkClassName?: string; | ||
isCompleted: boolean; | ||
onClick: () => void; | ||
}) { | ||
return ( | ||
<div | ||
onClick={(e) => e.stopPropagation()} | ||
className={cn( | ||
"absolute right-2 bottom-2 w-10 h-10 grid place-items-center transition active:scale-95 hover:opacity-100 rounded-md text-sm active:bg-mossgreen/10", | ||
isCompleted && "bg-mossgreen/10", | ||
className | ||
)} | ||
> | ||
<input | ||
checked={isCompleted} | ||
onChange={onClick} | ||
type="checkbox" | ||
className="peer absolute inset-0 w-full h-full opacity-0 cursor-pointer" | ||
/> | ||
<Checkmark className={checkmarkClassName} /> | ||
</div> | ||
); | ||
} | ||
|
||
export function Checkmark({ className }: { className?: string }) { | ||
return ( | ||
<svg | ||
className={cn( | ||
"h-6 w-6 text-smokewhite/20 peer-active:text-mossgreen/50 peer-checked:text-mossgreen transition pointer-events-none", | ||
className | ||
)} | ||
fill="none" | ||
viewBox="0 0 24 24" | ||
stroke="currentColor" | ||
strokeWidth="3" | ||
> | ||
<path | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
d="M5 13l4 4L19 7" | ||
pathLength="1" | ||
strokeDashoffset="0px" | ||
strokeDasharray="1px 1px" | ||
></path> | ||
</svg> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { ChineseCharacter } from "@/data"; | ||
import { BASE_URL } from "@/pages/_app"; | ||
import { cn } from "@/utils/cn"; | ||
import clsx from "clsx"; | ||
import Link from "next/link"; | ||
import React from "react"; | ||
import { preload } from "swr"; | ||
|
||
async function preloadHanziDetails(hanzi: string) { | ||
await preload(`hanzi/${hanzi}`, async (url) => { | ||
const response = await fetch(`${BASE_URL}/api/${url}`); | ||
const data = await response.json(); | ||
return data; | ||
}); | ||
} | ||
|
||
export function CharacterRow({ | ||
id, | ||
hanzi, | ||
pinyin, | ||
translations, | ||
isCompleted, | ||
onClick, | ||
onCompleteToggle, | ||
}: ChineseCharacter & { | ||
onClick: () => void; | ||
isCompleted: boolean; | ||
onCompleteToggle: () => void; | ||
}) { | ||
return ( | ||
<div onClick={onClick} className="relative active:scale-95 transition select-none font-chinese text-3xl"> | ||
<div | ||
className={clsx( | ||
"pl-3 pr-4 pt-6 pb-3 flex gap-2 items-center transition border-2 shadow-b-small rounded-lg bg-softblack", | ||
isCompleted ? "border-mossgreen shadow-mossgreen text-wheat" : "border-border shadow-border" | ||
)} | ||
> | ||
<div className="shrink-0">{hanzi}</div> | ||
|
||
<div className="overflow-x-hidden flex-1"> | ||
<div className="text-sm font-medium">{pinyin}</div> | ||
<div className="text-sm line-clamp-1 max-w-[90%]">{translations.join(", ")}</div> | ||
</div> | ||
|
||
<MarkAsCompleted | ||
className={isCompleted ? "bg-transparent" : ""} | ||
isCompleted={isCompleted} | ||
onClick={onCompleteToggle} | ||
/> | ||
|
||
<div className="absolute left-4 top-3 text-xs">{id}</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
function MarkAsCompleted({ | ||
className, | ||
checkmarkClassName, | ||
isCompleted, | ||
onClick, | ||
}: { | ||
className?: string; | ||
checkmarkClassName?: string; | ||
isCompleted: boolean; | ||
onClick: () => void; | ||
}) { | ||
return ( | ||
<div | ||
onClick={(e) => e.stopPropagation()} | ||
className={cn( | ||
"absolute right-2 bottom-2 w-10 h-10 grid place-items-center transition active:scale-95 hover:opacity-100 rounded-md text-sm active:bg-mossgreen/10", | ||
isCompleted && "bg-mossgreen/10", | ||
className | ||
)} | ||
> | ||
<input | ||
checked={isCompleted} | ||
onChange={onClick} | ||
type="checkbox" | ||
className="peer absolute inset-0 w-full h-full opacity-0 cursor-pointer" | ||
/> | ||
<Checkmark className={checkmarkClassName} /> | ||
</div> | ||
); | ||
} | ||
|
||
function Checkmark({ className }: { className?: string }) { | ||
return ( | ||
<svg | ||
className={cn( | ||
"h-6 w-6 text-smokewhite/20 peer-active:text-mossgreen/50 peer-checked:text-mossgreen transition pointer-events-none", | ||
className | ||
)} | ||
fill="none" | ||
viewBox="0 0 24 24" | ||
stroke="currentColor" | ||
strokeWidth="3" | ||
> | ||
<path | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
d="M5 13l4 4L19 7" | ||
pathLength="1" | ||
strokeDashoffset="0px" | ||
strokeDasharray="1px 1px" | ||
></path> | ||
</svg> | ||
); | ||
} |
Oops, something went wrong.