mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-29 16:04:45 +00:00
cleanup
This commit is contained in:
1 parent
d854614a87
commit
48c3e15a38
295 files changed
+178378
-1036
No files matched your search
@@ -0,0 +1,240 @@
|
||||
import { JSX, createSignal, onMount, Show } from "solid-js";
|
||||
|
||||
export type CharacterPose =
|
||||
| "main"
|
||||
| "hello"
|
||||
| "laptop"
|
||||
| "sleep"
|
||||
| "lookup"
|
||||
| "success"
|
||||
| "like"
|
||||
| "walk"
|
||||
| "announcement"
|
||||
| "educate"
|
||||
| "happy_note"
|
||||
| "maintenance"
|
||||
| "sad"
|
||||
// New mascot poses
|
||||
| "time"
|
||||
| "flag"
|
||||
| "error"
|
||||
| "headphones"
|
||||
| "checkbox"
|
||||
| "coffee"
|
||||
| "coffe"
|
||||
| "angry_bulb"
|
||||
| "anoyed_lamp"
|
||||
// Grayscaled error states
|
||||
| "404"
|
||||
| "forbidden"
|
||||
| "connection_error"
|
||||
| "fixing";
|
||||
|
||||
interface BookraCharacterProps {
|
||||
pose: CharacterPose;
|
||||
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
|
||||
animate?: boolean;
|
||||
class?: string;
|
||||
alt?: string;
|
||||
}
|
||||
|
||||
const sizeMap = {
|
||||
xs: "w-16 h-16",
|
||||
sm: "w-24 h-24",
|
||||
md: "w-32 h-32",
|
||||
lg: "w-48 h-48",
|
||||
xl: "w-64 h-64",
|
||||
"2xl": "w-80 h-80",
|
||||
};
|
||||
|
||||
const illustrationMap: Record<CharacterPose, string> = {
|
||||
main: "/bookra-illustrations/main.svg",
|
||||
hello: "/bookra-illustrations/hello.svg",
|
||||
laptop: "/bookra-illustrations/laptop.svg",
|
||||
sleep: "/bookra-illustrations/sleep.svg",
|
||||
lookup: "/bookra-illustrations/lookup.svg",
|
||||
success: "/bookra-illustrations/success.svg",
|
||||
like: "/bookra-illustrations/like.svg",
|
||||
walk: "/bookra-illustrations/walk.svg",
|
||||
announcement: "/bookra-illustrations/announcement.svg",
|
||||
educate: "/bookra-illustrations/educate.svg",
|
||||
happy_note: "/bookra-illustrations/happy-note.svg",
|
||||
maintenance: "/bookra-illustrations/maintenance.svg",
|
||||
sad: "/bookra-illustrations/sad.svg",
|
||||
time: "/bookra-illustrations/clock.svg",
|
||||
flag: "/bookra-illustrations/flag.svg",
|
||||
error: "/bookra-illustrations/sad.svg",
|
||||
headphones: "/bookra-illustrations/headphones.svg",
|
||||
checkbox: "/bookra-illustrations/happy-note.svg",
|
||||
coffee: "/bookra-illustrations/coffee.svg",
|
||||
coffe: "/bookra-illustrations/coffee.svg",
|
||||
angry_bulb: "/bookra-illustrations/angry-bulb.svg",
|
||||
anoyed_lamp: "/bookra-illustrations/angry-bulb.svg",
|
||||
"404": "/bookra-illustrations/404.svg",
|
||||
forbidden: "/bookra-illustrations/forbidden.svg",
|
||||
connection_error: "/bookra-illustrations/connection-error.svg",
|
||||
fixing: "/bookra-illustrations/maintenance.svg",
|
||||
};
|
||||
|
||||
export function BookraCharacter(props: BookraCharacterProps): JSX.Element {
|
||||
const [isLoaded, setIsLoaded] = createSignal(false);
|
||||
const [hasError, setHasError] = createSignal(false);
|
||||
|
||||
const sizeClass = () => sizeMap[props.size || "md"];
|
||||
const animationClass = () => props.animate !== false ? "animate-float" : "";
|
||||
|
||||
const characterPath = () => illustrationMap[props.pose];
|
||||
const label = () => props.alt || `Bookra illustration - ${props.pose.replaceAll("_", " ")}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
class={`relative inline-flex items-center justify-center ${sizeClass()} ${props.class || ""}`}
|
||||
aria-label={label()}
|
||||
>
|
||||
<Show when={!hasError()}>
|
||||
<img
|
||||
src={characterPath()}
|
||||
alt={label()}
|
||||
class={`w-full h-full object-contain transition-all duration-500 ${
|
||||
isLoaded() ? "opacity-100 scale-100" : "opacity-0 scale-95"
|
||||
} ${animationClass()}`}
|
||||
onLoad={() => setIsLoaded(true)}
|
||||
onError={() => setHasError(true)}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={hasError()}>
|
||||
<div class="w-full h-full rounded-2xl bg-accent/10 flex items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-accent">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2" />
|
||||
<line x1="16" y1="2" x2="16" y2="6" />
|
||||
<line x1="8" y1="2" x2="8" y2="6" />
|
||||
<line x1="3" y1="10" x2="21" y2="10" />
|
||||
</svg>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Character with speech bubble for contextual messages
|
||||
interface CharacterMessageProps extends BookraCharacterProps {
|
||||
message: string;
|
||||
position?: "left" | "right" | "top" | "bottom";
|
||||
}
|
||||
|
||||
export function CharacterWithMessage(props: CharacterMessageProps): JSX.Element {
|
||||
const positionClass = () => {
|
||||
switch (props.position || "right") {
|
||||
case "left": return "flex-row-reverse";
|
||||
case "right": return "flex-row";
|
||||
case "top": return "flex-col-reverse";
|
||||
case "bottom": return "flex-col";
|
||||
}
|
||||
};
|
||||
|
||||
const bubblePosition = () => {
|
||||
switch (props.position || "right") {
|
||||
case "left": return "mr-4";
|
||||
case "right": return "ml-4";
|
||||
case "top": return "mb-3";
|
||||
case "bottom": return "mt-3";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={`flex items-center ${positionClass()}`}>
|
||||
<BookraCharacter
|
||||
pose={props.pose}
|
||||
size={props.size}
|
||||
animate={props.animate}
|
||||
class={props.class}
|
||||
alt={props.alt}
|
||||
/>
|
||||
<div class={`surface-elevated px-4 py-3 rounded-2xl rounded-${
|
||||
props.position === "left" ? "r" :
|
||||
props.position === "right" ? "l" :
|
||||
props.position === "top" ? "b" : "t"
|
||||
}-lg max-w-xs ${bubblePosition()}`}>
|
||||
<p class="text-ink text-sm leading-relaxed">{props.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Empty state with character
|
||||
interface CharacterEmptyStateProps {
|
||||
pose: CharacterPose;
|
||||
title: string;
|
||||
message: string;
|
||||
action?: {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
size?: "sm" | "md" | "lg";
|
||||
}
|
||||
|
||||
export function CharacterEmptyState(props: CharacterEmptyStateProps): JSX.Element {
|
||||
return (
|
||||
<div class="flex flex-col items-center text-center py-12 px-4">
|
||||
<BookraCharacter
|
||||
pose={props.pose}
|
||||
size={props.size || "lg"}
|
||||
animate={true}
|
||||
class="mb-6"
|
||||
/>
|
||||
<h3 class="font-display text-xl font-semibold text-ink mb-2">
|
||||
{props.title}
|
||||
</h3>
|
||||
<p class="text-ink-muted max-w-sm mb-6 leading-relaxed">
|
||||
{props.message}
|
||||
</p>
|
||||
{props.action && (
|
||||
<button
|
||||
onClick={props.action.onClick}
|
||||
class="btn-primary"
|
||||
>
|
||||
{props.action.label}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Success state with character celebration
|
||||
interface CharacterSuccessStateProps {
|
||||
title: string;
|
||||
message: string;
|
||||
action?: {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export function CharacterSuccessState(props: CharacterSuccessStateProps): JSX.Element {
|
||||
return (
|
||||
<div class="flex flex-col items-center text-center py-12 px-4 animate-fade-in">
|
||||
<div class="relative mb-6">
|
||||
<BookraCharacter
|
||||
pose="success"
|
||||
size="xl"
|
||||
animate={true}
|
||||
/>
|
||||
<div class="absolute -top-2 -right-2 text-3xl animate-bounce">✨</div>
|
||||
</div>
|
||||
<h3 class="font-display text-2xl font-semibold text-ink mb-3">
|
||||
{props.title}
|
||||
</h3>
|
||||
<p class="text-ink-muted max-w-md mb-8 leading-relaxed">
|
||||
{props.message}
|
||||
</p>
|
||||
{props.action && (
|
||||
<button
|
||||
onClick={props.action.onClick}
|
||||
class="btn-primary inline-flex items-center gap-2"
|
||||
>
|
||||
{props.action.label}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user