contextual loading bar updates, and better sandbox tester

alignments
This commit is contained in:
wil committed 2024-09-05 11:48:21 -04:00
1 parent cbe208f013
commit 5cbfb775e5
7 files changed
+247 -200

No files matched your search

+26 -32
View File
@@ -2,13 +2,7 @@ import { Transition } from "@headlessui/react";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCheck,
faArrowRotateLeft,
faStop,
} from "@fortawesome/pro-thin-svg-icons";
import { Spinner } from "./Spinner";
import { faArrowRotateLeft } from "@fortawesome/pro-thin-svg-icons";
export enum LoadingBarStatus {
IDLE = "idle",
@@ -16,7 +10,8 @@ export enum LoadingBarStatus {
SUCCESS = "success",
ERROR = "error",
}
export type LoadingBarProps = {
export interface LoadingBarProps {
width?: number;
progress: number;
status: LoadingBarStatus;
isShowing: boolean;
@@ -26,9 +21,10 @@ export type LoadingBarProps = {
};
message?: string;
onRetry?: () => void;
};
}
export const LoadingBar = ({
width,
progress = 0,
position,
status,
@@ -41,9 +37,10 @@ export const LoadingBar = ({
<div
className={twMerge(
// default styles
"flex w-full gap-2",
"flex w-full flex-col gap-2",
// position styles
position && `fixed w-96`,
position && `fixed`,
position && !width && `w-96`,
// base transition properties
"transition-opacity ease-in-out",
// Shared closed styles
@@ -56,31 +53,28 @@ export const LoadingBar = ({
style={{
left: position?.x,
top: position?.y,
width: width ? `${width}px` : undefined,
}}
>
<div className="relative flex-grow">
<div className="h-2.5 w-full rounded-full bg-gray-200">
<div
className="h-2.5 rounded-full bg-primary-500"
style={{ width: `${progress}%` }}
/>
</div>
{message && (
<label className="mt-2 block w-full text-center">{message}</label>
)}
<div className="h-2.5 w-full rounded-full bg-gray-200">
<div
className={twMerge(
"h-2.5 rounded-full bg-primary-500",
status === LoadingBarStatus.LOADING && "animate-pulse",
)}
style={{ width: `${progress}%` }}
/>
</div>
<div className="-mt-1.5">
{status === LoadingBarStatus.IDLE && (
<FontAwesomeIcon icon={faStop} />
)}
{status === LoadingBarStatus.LOADING && (
<Spinner className="size-5" />
)}
{status === LoadingBarStatus.SUCCESS && (
<FontAwesomeIcon icon={faCheck} />
)}
<div className="flex grow items-center justify-center gap-2">
{message && <label>{message}</label>}
{status === LoadingBarStatus.ERROR && (
<button onClick={onRetry}>
<button
onClick={onRetry}
className="flex items-center gap-2 hover:text-primary"
>
<label className="cursor-pointer">Retry</label>
<FontAwesomeIcon icon={faArrowRotateLeft} />
</button>
)}
-160
View File
@@ -1,160 +0,0 @@
import { useRef } from "react";
import { useSignals } from "@preact/signals-react/runtime";
import { ContextualToolbarImage } from "~/components/features/KonvaContainer/ContextualToolbarImage";
import { ContextualLoadingBar } from "~/components/features/KonvaContainer/ContextualLoadingBar";
import { LoadingBarStatus } from "~/components/ui/LoadingBar";
import {
imageToolbar,
loadingBar,
ContextualLoadingBarProps,
} from "~/signals/konvaContextuals";
export const Sandbox = () => {
const xImageToolbarRef = useRef(0);
const yImageToolbarRef = useRef(0);
const loadingBarPropsRef = useRef<ContextualLoadingBarProps>({
position: {
x: 0,
y: 0,
},
progress: 0,
status: LoadingBarStatus.IDLE,
isShowing: true,
message: "Loading...",
});
return (
<div className="p-2">
<ContextualToolbarImage />
<ContextualLoadingBar />
<h1>Sandbox</h1>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<label>Image Toolbar Position</label>
<input
className="w-20 rounded-md border border-black p-2"
type="text"
placeholder="X"
onChange={(e) => {
xImageToolbarRef.current = parseInt(e.target.value);
}}
/>
<input
className="w-20 rounded-md border border-black p-2"
type="text"
placeholder="Y"
onChange={(e) => {
yImageToolbarRef.current = parseInt(e.target.value);
}}
/>
<button
className="rounded-md border border-black bg-gray-200 p-2 hover:bg-gray-300"
onClick={() =>
imageToolbar.setPosition({
x: xImageToolbarRef.current,
y: yImageToolbarRef.current,
})
}
>
Set Position
</button>
<button
className="rounded-md border border-black bg-gray-200 p-2 hover:bg-gray-300"
onClick={() => imageToolbar.hide()}
>
Hide
</button>
</div>
<div className="flex flex-col">
<div className="flex items-center gap-2">
<label>Loading Bar Props</label>
<input
className="w-20 rounded-md border border-black p-2"
type="text"
placeholder="X"
onChange={(e) => {
loadingBarPropsRef.current.position.x = parseInt(
e.target.value,
);
}}
/>
<input
className="w-20 rounded-md border border-black p-2"
type="text"
placeholder="Y"
onChange={(e) => {
loadingBarPropsRef.current.position.y = parseInt(
e.target.value,
);
}}
/>
<input
className="w-20 rounded-md border border-black p-2"
type="number"
min={0}
max={100}
placeholder="%"
onChange={(e) => {
loadingBarPropsRef.current.progress = parseInt(e.target.value);
}}
/>
<button
className="rounded-md border border-black bg-gray-200 p-2 hover:bg-gray-300"
onClick={() =>
loadingBar.show({
status: LoadingBarStatus.LOADING,
progress: loadingBarPropsRef.current.progress,
message: "Test Message...",
position: {
x: loadingBarPropsRef.current.position.x,
y: loadingBarPropsRef.current.position.y,
},
})
}
>
Show
</button>
<button
className="rounded-md border border-black bg-gray-200 p-2 hover:bg-gray-300"
onClick={() =>
loadingBar.update({
status: LoadingBarStatus.LOADING,
progress: loadingBarPropsRef.current.progress,
message: loadingBarPropsRef.current.message,
position: {
x: loadingBarPropsRef.current.position.x,
y: loadingBarPropsRef.current.position.y,
},
})
}
>
Update
</button>
<button
className="rounded-md border border-black bg-gray-200 p-2 hover:bg-gray-300"
onClick={() => loadingBar.hide()}
>
Hide
</button>
</div>
<PrintLoadingBarProps />
</div>
</div>
</div>
);
};
const PrintLoadingBarProps = () => {
useSignals();
return (
<>
<p>Status: {loadingBar.signal.value.status}</p>
<p>Message: {loadingBar.signal.value.message}</p>
</>
);
};
@@ -0,0 +1,140 @@
import { useState } from "react";
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
import { faChevronDown } from "@fortawesome/pro-thin-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
loadingBar,
ContextualLoadingBarProps,
} from "~/signals/konvaContextuals";
import { Input, Button, LoadingBarStatus } from "~/components/ui";
type LoadingBarPropsType = Omit<ContextualLoadingBarProps, "isShowing">;
export const ContextualLoadingBarForm = () => {
const [loadingBarProps, setLoadingBarProps] = useState<LoadingBarPropsType>({
status: LoadingBarStatus.IDLE,
progress: 0,
position: {
x: 0,
y: 0,
},
});
return (
<div className="flex flex-col gap-2">
<label className="font-bold">Loading Bar Props</label>
<div className="flex items-center gap-2">
<label>X:</label>
<Input
className="w-20"
type="text"
placeholder="X"
value={loadingBarProps.position.x}
onChange={(e) => {
setLoadingBarProps((curr: LoadingBarPropsType) => ({
...curr,
position: {
...curr.position,
x: parseInt(e.target.value) || 0,
},
}));
}}
/>
<label>Y:</label>
<Input
className="w-20"
type="text"
placeholder="Y"
value={loadingBarProps.position.y}
onChange={(e) => {
setLoadingBarProps((curr: LoadingBarPropsType) => ({
...curr,
position: {
...curr.position,
y: parseInt(e.target.value) || 0,
},
}));
}}
/>
<label>Width:</label>
<Input
className="w-20"
type="text"
placeholder="Width"
value={loadingBarProps.width}
onChange={(e) => {
setLoadingBarProps((curr: LoadingBarPropsType) => ({
...curr,
width: parseInt(e.target.value) || undefined,
}));
}}
/>
<label>Progress:</label>
<Input
className="w-20"
type="number"
min={0}
max={100}
placeholder="%"
value={loadingBarProps.progress}
onChange={(e) => {
setLoadingBarProps((curr: LoadingBarPropsType) => ({
...curr,
progress: parseInt(e.target.value) || 0,
}));
}}
/>
</div>
<div className="flex items-center gap-2">
<label>Message:</label>
<Input
className="w-72"
type="text"
placeholder="Message"
value={loadingBarProps.message}
onChange={(e) => {
setLoadingBarProps((curr: LoadingBarPropsType) => ({
...curr,
message: e.target.value || undefined,
}));
}}
/>
<label>Status:</label>
<Menu>
<MenuButton>
<div className="flex items-center gap-2 rounded-md bg-gray-200 px-4 py-2">
<label className="cursor-pointer">{loadingBarProps.status}</label>
<FontAwesomeIcon icon={faChevronDown} />
</div>
</MenuButton>
<MenuItems anchor="bottom">
{Object.values(LoadingBarStatus).map((status, idx) => {
return (
<MenuItem key={idx}>
<div
className="block cursor-pointer data-[focus]:bg-primary-100"
onClick={() => {
setLoadingBarProps((curr: LoadingBarPropsType) => ({
...curr,
status: status,
}));
}}
>
{status}
</div>
</MenuItem>
);
})}
</MenuItems>
</Menu>
</div>
<div className="flex gap-2">
<Button onClick={() => loadingBar.show(loadingBarProps)}>Show</Button>
<Button onClick={() => loadingBar.update(loadingBarProps)}>
Update
</Button>
<Button onClick={() => loadingBar.hide()}>Hide</Button>
</div>
</div>
);
};
@@ -0,0 +1,46 @@
import { useState } from "react";
import { Input, Button } from "~/components/ui";
import { imageToolbar } from "~/signals";
export const ContextualToolbarForm = () => {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
return (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<label>Image Toolbar Position</label>
<Input
className="w-20"
type="text"
placeholder="X"
value={x}
onChange={(e) => {
setX(parseInt(e.target.value) || 0);
}}
/>
<Input
className="w-20"
type="text"
placeholder="Y"
value={y}
onChange={(e) => {
setY(parseInt(e.target.value) || 0);
}}
/>
<Button
onClick={() =>
imageToolbar.setPosition({
x: x,
y: y,
})
}
>
Set Position
</Button>
<Button onClick={() => imageToolbar.hide()}>Hide</Button>
</div>
</div>
);
};
+24
View File
@@ -0,0 +1,24 @@
import { ContextualToolbarImage } from "~/components/features/KonvaContainer/ContextualToolbarImage";
import { ContextualLoadingBar } from "~/components/features/KonvaContainer/ContextualLoadingBar";
import { useRenderCounter } from "~/hooks/useRenderCounter";
import { ContextualToolbarForm } from "./ContextualToolbarForm";
import { ContextualLoadingBarForm } from "./ContextualLoadingBarForm";
export const Sandbox = () => {
useRenderCounter("Sandbox");
return (
<div className="p-2">
<ContextualToolbarImage />
<ContextualLoadingBar />
<div className="flex flex-col gap-8">
<h1>Sandbox</h1>
<ContextualToolbarForm />
<ContextualLoadingBarForm />
</div>
</div>
);
};
@@ -19,6 +19,7 @@ export const loadingBar = {
update(props: Omit<ContextualLoadingBarProps, "isShowing">) {
loadingBarSignal.value = {
...loadingBarSignal.value,
isShowing: true,
...props,
};
},
@@ -28,6 +29,12 @@ export const loadingBar = {
message,
};
},
updateWidth(width: number) {
loadingBarSignal.value = {
...loadingBarSignal.value,
width,
};
},
updatePosition(position: { x: number; y: number }) {
loadingBarSignal.value = {
...loadingBarSignal.value,
+4 -8
View File
@@ -1,5 +1,4 @@
import { LoadingBarStatus } from "~/components/ui";
export { LoadingBarStatus };
import { LoadingBarProps } from "~/components/ui";
export interface ContextualUi {
position: {
@@ -9,9 +8,6 @@ export interface ContextualUi {
isShowing: boolean;
}
export interface ContextualLoadingBarProps extends ContextualUi {
progress: number;
status: LoadingBarStatus;
message?: string;
onRetry?: () => void;
}
export interface ContextualLoadingBarProps
extends ContextualUi,
Omit<LoadingBarProps, "position"> {}