-
Notifications
You must be signed in to change notification settings - Fork 2
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
Showing
34 changed files
with
2,008 additions
and
15 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
Empty file.
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,211 @@ | ||
import { | ||
DrawEmoji, | ||
Emoji, | ||
characters, | ||
colors, | ||
createDrawEmoji, | ||
emotions, | ||
} from "@/emoji/drawEmoji"; | ||
import { DebugCanvas } from "@/lib/react/DebugCanvasComponent"; | ||
import { keys } from "@/lib/utils"; | ||
import { RadioGroup } from "@headlessui/react"; | ||
import classNames from "classnames"; | ||
import { Fragment, useEffect, useState } from "react"; | ||
|
||
export function EmojiListing() { | ||
const [drawEmoji, setDrawEmoji] = useState<DrawEmoji>(); | ||
const [emoji, setEmoji] = useState<Emoji>({ | ||
character: "blob", | ||
emotion: 0, | ||
color: { name: "auto", level: 40 }, | ||
}); | ||
|
||
useEffect(() => { | ||
let isCancelled = false; | ||
void (async () => { | ||
const drawEmoji = await createDrawEmoji(); | ||
if (!isCancelled) { | ||
setDrawEmoji(() => drawEmoji); | ||
} | ||
})(); | ||
return () => { | ||
isCancelled = true; | ||
}; | ||
}, []); | ||
|
||
if (!drawEmoji) { | ||
return <div>Loading...</div>; | ||
} | ||
|
||
return ( | ||
<> | ||
<div className="flex p-1 gap-1"> | ||
<RadioGroup | ||
value={emoji.character} | ||
onChange={(character) => setEmoji({ ...emoji, character })} | ||
className="flex bg-stone-200 rounded p-1 gap-1" | ||
> | ||
{characters.map((character, index) => ( | ||
<RadioGroup.Option | ||
key={index} | ||
value={character} | ||
className="rounded-sm px-2 ui-checked:bg-stone-50 hover:bg-stone-100 cursor-pointer" | ||
> | ||
{character} | ||
</RadioGroup.Option> | ||
))} | ||
</RadioGroup> | ||
<RadioGroup | ||
value={emoji.color.name} | ||
onChange={(color) => | ||
setEmoji({ | ||
...emoji, | ||
color: { ...emoji.color, name: color }, | ||
}) | ||
} | ||
className="flex bg-stone-200 rounded p-1 gap-1" | ||
> | ||
{[...keys(colors)].map((color, index) => ( | ||
<RadioGroup.Option | ||
key={index} | ||
value={color} | ||
className="rounded-sm px-2 ui-checked:bg-stone-50 hover:bg-stone-100 cursor-pointer" | ||
> | ||
{color} | ||
</RadioGroup.Option> | ||
))} | ||
</RadioGroup> | ||
<RadioGroup | ||
value={emoji.emotion} | ||
onChange={(emotion) => | ||
setEmoji({ | ||
...emoji, | ||
emotion, | ||
}) | ||
} | ||
className="flex bg-stone-200 rounded p-1 gap-1" | ||
> | ||
{emotions.map((emotion, index) => ( | ||
<RadioGroup.Option | ||
key={index} | ||
value={emotion} | ||
className="rounded-sm px-2 ui-checked:bg-stone-50 hover:bg-stone-100 cursor-pointer" | ||
> | ||
{emotion} | ||
</RadioGroup.Option> | ||
))} | ||
</RadioGroup> | ||
</div> | ||
<CrossFade | ||
value={emoji} | ||
render={(emoji) => ( | ||
<EmojiRender | ||
sizePx={256} | ||
emoji={emoji} | ||
drawEmoji={drawEmoji} | ||
/> | ||
)} | ||
/> | ||
<div className="flex"> | ||
{[...keys(colors)].map((color, index) => ( | ||
<div key={index} className="flex flex-col"> | ||
{characters.map((character, index) => ( | ||
<Fragment key={index}> | ||
{emotions.map((emotion, index) => ( | ||
<div key={index}> | ||
<EmojiRender | ||
sizePx={128} | ||
emoji={{ | ||
color: { | ||
name: color, | ||
level: 40, | ||
}, | ||
character, | ||
emotion, | ||
}} | ||
drawEmoji={drawEmoji} | ||
/> | ||
</div> | ||
))} | ||
</Fragment> | ||
))} | ||
</div> | ||
))} | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
function EmojiRender({ | ||
sizePx, | ||
emoji, | ||
drawEmoji, | ||
}: { | ||
sizePx: number; | ||
emoji: Emoji; | ||
drawEmoji: DrawEmoji; | ||
}) { | ||
return ( | ||
<DebugCanvas | ||
width={sizePx} | ||
height={sizePx} | ||
draw={(c) => { | ||
drawEmoji(c.ctx, emoji, sizePx); | ||
}} | ||
/> | ||
); | ||
} | ||
|
||
function CrossFade<T>({ | ||
value, | ||
render, | ||
className = "relative", | ||
itemClassName = "bg-stone-100", | ||
}: { | ||
value: T; | ||
render: (value: T) => JSX.Element; | ||
className?: string; | ||
itemClassName?: string; | ||
}) { | ||
const [state, setState] = useState({ | ||
items: [{ value, index: 0 }], | ||
index: 0, | ||
}); | ||
|
||
if (state.items[state.items.length - 1].value !== value) { | ||
setState({ | ||
items: [...state.items, { value, index: state.index + 1 }], | ||
index: state.index + 1, | ||
}); | ||
} | ||
|
||
return ( | ||
<div className={className}> | ||
{state.items.map((item, i) => { | ||
const isLast = i === state.items.length - 1; | ||
return ( | ||
<div | ||
key={item.index} | ||
className={classNames( | ||
"animate-[fade_0.2s_both]", | ||
itemClassName, | ||
isLast ? "relative z-10" : ( | ||
"absolute top-0 left-0 z-0" | ||
), | ||
)} | ||
onAnimationEnd={() => { | ||
setState((prev) => ({ | ||
...prev, | ||
items: prev.items.filter( | ||
(i) => i.index >= item.index, | ||
), | ||
})); | ||
}} | ||
> | ||
{render(item.value)} | ||
</div> | ||
); | ||
})} | ||
</div> | ||
); | ||
} |
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,53 @@ | ||
import { ReactNode } from "react"; | ||
|
||
export function Post({ | ||
name = "Charlie Yeti", | ||
funZone, | ||
}: { | ||
name?: string; | ||
funZone?: ReactNode; | ||
}) { | ||
return ( | ||
<div className="bg-white rounded-md shadow-sm text-sm"> | ||
<div className="flex items-center gap-3 px-5 py-2 pt-5"> | ||
<div className="flex-none aspect-square rounded-full bg-gray-300 w-8 flex items-center justify-center"> | ||
{initials(name)} | ||
</div> | ||
<div className="flex-auto text-xs"> | ||
<div className="font-semibold">{name}</div> | ||
<div className="text-gray-600">Jan 2 at 7:46pm</div> | ||
</div> | ||
</div> | ||
<div className="flex flex-col gap-2 px-5 py-2"> | ||
<p> | ||
A spectre is haunting Europe — the spectre of communism. All | ||
the powers of old Europe have entered into a holy alliance | ||
to exorcise this spectre: Pope and Tsar, Metternich and | ||
Guizot, French Radicals and German police-spies. | ||
</p> | ||
<p> | ||
Where is the party in opposition that has not been decried | ||
as communistic by its opponents in power? Where is the | ||
opposition that has not hurled back the branding reproach of | ||
communism, against the more advanced opposition parties, as | ||
well as against its reactionary adversaries? | ||
</p> | ||
</div> | ||
<div className="flex border-t mt-2 mx-5 py-3"> | ||
{funZone} | ||
<button className="ml-auto text-xs hover:bg-gray-100 rounded px-3 py-2"> | ||
0 comments | ||
</button> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
function initials(name: string) { | ||
return name | ||
.split(" ") | ||
.filter(Boolean) | ||
.map((word) => word[0].toUpperCase()) | ||
.join("") | ||
.slice(0, 2); | ||
} |
Oops, something went wrong.