Added progress component

This commit is contained in:
2025-08-09 14:25:27 -04:00
parent e1b3000121
commit 4e3c984125
17 changed files with 447 additions and 2 deletions

5
TODO.txt Normal file
View File

@@ -0,0 +1,5 @@
Inputs:
slider, multi-value slider
Toaster

26
lib/component/progress.ts Normal file
View File

@@ -0,0 +1,26 @@
import DangerProgress from "./progress/DangerProgress";
import DarkProgress from "./progress/DarkProgress";
import InfoProgress from "./progress/InfoProgress";
import LightProgress from "./progress/LightProgress";
import MoltenProgress from "./progress/MoltenProgress";
import PrimaryProgress from "./progress/PrimaryProgress";
import Progress from "./progress/Progress";
import SecondaryProgress from "./progress/SecondaryProgress";
import SuccessProgress from "./progress/SuccessProgress";
import TertiaryProgress from "./progress/TertiaryProgress";
import WarningProgress from "./progress/WarningProgress";
export {
DangerProgress,
DarkProgress,
InfoProgress,
LightProgress,
MoltenProgress,
PrimaryProgress,
Progress,
SecondaryProgress,
SuccessProgress,
TertiaryProgress,
WarningProgress
};

View File

@@ -0,0 +1,13 @@
import type { ThemedProgressProps } from "$/types/Progress";
import Progress from "./Progress";
export default function DangerProgress(props: ThemedProgressProps){
return (
<Progress
backgroundColor="var(--color-gray-300)"
progressColor="var(--color-red-600)"
{...props}
/>
);
}

View File

@@ -0,0 +1,13 @@
import type { ThemedProgressProps } from "$/types/Progress";
import Progress from "./Progress";
export default function DarkProgress(props: ThemedProgressProps){
return (
<Progress
backgroundColor="var(--color-white)"
progressColor="var(--color-black)"
{...props}
/>
);
}

View File

@@ -0,0 +1,13 @@
import type { ThemedProgressProps } from "$/types/Progress";
import Progress from "./Progress";
export default function InfoProgress(props: ThemedProgressProps){
return (
<Progress
backgroundColor="var(--color-gray-300)"
progressColor="var(--color-cyan-500)"
{...props}
/>
);
}

View File

@@ -0,0 +1,13 @@
import type { ThemedProgressProps } from "$/types/Progress";
import Progress from "./Progress";
export default function LightProgress(props: ThemedProgressProps){
return (
<Progress
backgroundColor="var(--color-black)"
progressColor="var(--color-white)"
{...props}
/>
);
}

View File

@@ -0,0 +1,13 @@
import type { ThemedProgressProps } from "$/types/Progress";
import Progress from "./Progress";
export default function MoltenProgress(props: ThemedProgressProps){
return (
<Progress
backgroundColor="var(--color-gray-300)"
progressColor="var(--color-orange-600)"
{...props}
/>
);
}

View File

@@ -0,0 +1,13 @@
import type { ThemedProgressProps } from "$/types/Progress";
import Progress from "./Progress";
export default function PrimaryProgress(props: ThemedProgressProps){
return (
<Progress
backgroundColor="var(--color-gray-300)"
progressColor="var(--color-blue-500)"
{...props}
/>
);
}

View File

@@ -0,0 +1,79 @@
import type { ProgressProps } from "$/types/Progress";
import clsx from "clsx";
import { useMemo } from "react";
export default function Progress({
id,
className,
value,
min,
max,
size = "md",
rounding = "full",
label,
tabIndex,
progressColor,
backgroundColor
}: ProgressProps){
const percentage = useMemo(() => {
const num = !value || Number.isNaN(value) ? min : Math.max(value, min);
const den = (!value || Number.isNaN(value) ? max : Math.max(value, max)) - min;
const percentage = (num / den) * 100;
return percentage && percentage > max ? max : percentage;
}, [ min, max, value ]);
return (
<div
id={id}
className={clsx(
"relative w-full overflow-hidden",
//Size
{
"": size === "none",
"h-2": size === "xs",
"h-3": size === "sm",
"h-4": size === "md",
"h-5": size === "lg",
"h-6": size === "xl",
"h-full": size === "full"
},
//Rounding
{
"": rounding === "none",
"rounded-sm": rounding === "sm",
"rounded-md": rounding === "md",
"rounded-lg": rounding === "lg",
"rounded-full": rounding === "full"
},
className
)}
role="progressbar"
aria-valuemin={min}
aria-valuemax={max}
aria-valuenow={value ? Math.round(value * 10000) / 100 : undefined}
aria-label={label}
tabIndex={tabIndex ?? 0}
>
<div
className={clsx(
"absolute -left-[0.52px] h-full transition-all duration-250",
)}
style={{
backgroundColor: progressColor,
width: `calc(${percentage}% + 1.04px)`
}}
/>
<div
className={clsx(
"absolute -right-[0.52px] h-full transition-all duration-250",
)}
style={{
backgroundColor: backgroundColor,
width: `${100 - percentage}%`
}}
/>
</div>
);
}

View File

@@ -0,0 +1,13 @@
import type { ThemedProgressProps } from "$/types/Progress";
import Progress from "./Progress";
export default function SecondaryProgress(props: ThemedProgressProps){
return (
<Progress
backgroundColor="var(--color-gray-300)"
progressColor="var(--color-neutral-500)"
{...props}
/>
);
}

View File

@@ -0,0 +1,13 @@
import type { ThemedProgressProps } from "$/types/Progress";
import Progress from "./Progress";
export default function SuccessProgress(props: ThemedProgressProps){
return (
<Progress
backgroundColor="var(--color-gray-300)"
progressColor="var(--color-green-600)"
{...props}
/>
);
}

View File

@@ -0,0 +1,13 @@
import type { ThemedProgressProps } from "$/types/Progress";
import Progress from "./Progress";
export default function TertiaryProgress(props: ThemedProgressProps){
return (
<Progress
backgroundColor="var(--color-gray-300)"
progressColor="var(--color-purple-600)"
{...props}
/>
);
}

View File

@@ -0,0 +1,13 @@
import type { ThemedProgressProps } from "$/types/Progress";
import Progress from "./Progress";
export default function WarningProgress(props: ThemedProgressProps){
return (
<Progress
backgroundColor="var(--color-gray-300)"
progressColor="var(--color-yellow-500)"
{...props}
/>
);
}

29
lib/types/Progress.d.ts vendored Normal file
View File

@@ -0,0 +1,29 @@
export type ProgressSize = "none" | "xs" | "sm" | "md" | "lg" | "xl" | "full";
export type ProgressRounding = "none" | "sm" | "md" | "lg" | "full";
export interface ProgressProps {
id?: string;
className?: string;
value?: number;
min: number;
max: number;
size?: ProgressSize;
rounding?: ProgressRounding;
label: string;
tabIndex?: number;
progressColor: string;
backgroundColor: string;
}
export interface ThemedProgressProps {
id?: string;
className?: string;
value?: number;
min: number;
max: number;
size?: ProgressSize;
rounding?: ProgressRounding;
label: string;
tabIndex?: number;
}

View File

@@ -11,6 +11,7 @@
import { Route as rootRouteImport } from './routes/__root' import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
import { Route as TabIndexRouteImport } from './routes/tab/index' import { Route as TabIndexRouteImport } from './routes/tab/index'
import { Route as ProgressIndexRouteImport } from './routes/progress/index'
import { Route as ModalIndexRouteImport } from './routes/modal/index' import { Route as ModalIndexRouteImport } from './routes/modal/index'
import { Route as MessageIndexRouteImport } from './routes/message/index' import { Route as MessageIndexRouteImport } from './routes/message/index'
import { Route as LoadingIndexRouteImport } from './routes/loading/index' import { Route as LoadingIndexRouteImport } from './routes/loading/index'
@@ -27,6 +28,11 @@ const TabIndexRoute = TabIndexRouteImport.update({
path: '/tab/', path: '/tab/',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const ProgressIndexRoute = ProgressIndexRouteImport.update({
id: '/progress/',
path: '/progress/',
getParentRoute: () => rootRouteImport,
} as any)
const ModalIndexRoute = ModalIndexRouteImport.update({ const ModalIndexRoute = ModalIndexRouteImport.update({
id: '/modal/', id: '/modal/',
path: '/modal/', path: '/modal/',
@@ -60,6 +66,7 @@ export interface FileRoutesByFullPath {
'/loading': typeof LoadingIndexRoute '/loading': typeof LoadingIndexRoute
'/message': typeof MessageIndexRoute '/message': typeof MessageIndexRoute
'/modal': typeof ModalIndexRoute '/modal': typeof ModalIndexRoute
'/progress': typeof ProgressIndexRoute
'/tab': typeof TabIndexRoute '/tab': typeof TabIndexRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
@@ -69,6 +76,7 @@ export interface FileRoutesByTo {
'/loading': typeof LoadingIndexRoute '/loading': typeof LoadingIndexRoute
'/message': typeof MessageIndexRoute '/message': typeof MessageIndexRoute
'/modal': typeof ModalIndexRoute '/modal': typeof ModalIndexRoute
'/progress': typeof ProgressIndexRoute
'/tab': typeof TabIndexRoute '/tab': typeof TabIndexRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
@@ -79,6 +87,7 @@ export interface FileRoutesById {
'/loading/': typeof LoadingIndexRoute '/loading/': typeof LoadingIndexRoute
'/message/': typeof MessageIndexRoute '/message/': typeof MessageIndexRoute
'/modal/': typeof ModalIndexRoute '/modal/': typeof ModalIndexRoute
'/progress/': typeof ProgressIndexRoute
'/tab/': typeof TabIndexRoute '/tab/': typeof TabIndexRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
@@ -90,9 +99,18 @@ export interface FileRouteTypes {
| '/loading' | '/loading'
| '/message' | '/message'
| '/modal' | '/modal'
| '/progress'
| '/tab' | '/tab'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' | '/buttons' | '/input' | '/loading' | '/message' | '/modal' | '/tab' to:
| '/'
| '/buttons'
| '/input'
| '/loading'
| '/message'
| '/modal'
| '/progress'
| '/tab'
id: id:
| '__root__' | '__root__'
| '/' | '/'
@@ -101,6 +119,7 @@ export interface FileRouteTypes {
| '/loading/' | '/loading/'
| '/message/' | '/message/'
| '/modal/' | '/modal/'
| '/progress/'
| '/tab/' | '/tab/'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
@@ -111,6 +130,7 @@ export interface RootRouteChildren {
LoadingIndexRoute: typeof LoadingIndexRoute LoadingIndexRoute: typeof LoadingIndexRoute
MessageIndexRoute: typeof MessageIndexRoute MessageIndexRoute: typeof MessageIndexRoute
ModalIndexRoute: typeof ModalIndexRoute ModalIndexRoute: typeof ModalIndexRoute
ProgressIndexRoute: typeof ProgressIndexRoute
TabIndexRoute: typeof TabIndexRoute TabIndexRoute: typeof TabIndexRoute
} }
@@ -130,6 +150,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof TabIndexRouteImport preLoaderRoute: typeof TabIndexRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/progress/': {
id: '/progress/'
path: '/progress'
fullPath: '/progress'
preLoaderRoute: typeof ProgressIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/modal/': { '/modal/': {
id: '/modal/' id: '/modal/'
path: '/modal' path: '/modal'
@@ -175,6 +202,7 @@ const rootRouteChildren: RootRouteChildren = {
LoadingIndexRoute: LoadingIndexRoute, LoadingIndexRoute: LoadingIndexRoute,
MessageIndexRoute: MessageIndexRoute, MessageIndexRoute: MessageIndexRoute,
ModalIndexRoute: ModalIndexRoute, ModalIndexRoute: ModalIndexRoute,
ProgressIndexRoute: ProgressIndexRoute,
TabIndexRoute: TabIndexRoute, TabIndexRoute: TabIndexRoute,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport

View File

@@ -11,7 +11,8 @@ const navLinks = [
{ to: "/loading", label: "Loading" }, { to: "/loading", label: "Loading" },
{ to: "/message", label: "Message" }, { to: "/message", label: "Message" },
{ to: "/modal", label: "Modal" }, { to: "/modal", label: "Modal" },
{ to: "/tab", label: "Tab" }, { to: "/progress", label: "Progress" },
{ to: "/tab", label: "Tab" }
]; ];

View File

@@ -0,0 +1,147 @@
import { DangerButton, PrimaryButton } from '$/component/button';
import { NumberInput } from '$/component/input';
import { DangerProgress, DarkProgress, InfoProgress, LightProgress, MoltenProgress, PrimaryProgress, Progress, SecondaryProgress, SuccessProgress, TertiaryProgress, WarningProgress } from '$/component/progress';
import { createFileRoute } from '@tanstack/react-router';
import { useState } from 'react';
import { BsDashLg, BsPlusLg } from 'react-icons/bs';
export const Route = createFileRoute('/progress/')({
component: ProgressPage,
});
function ProgressPage() {
const [ value, setValue ] = useState(0);
return (
<div
className="flex flex-col items-center justify-center w-full gap-y-8"
>
<div
className="flex flex-row items-center justify-center gap-x-8"
>
<DangerButton shape="square" onClick={() => setValue(value - 1)}><BsDashLg size={22}/></DangerButton>
<PrimaryButton shape="square" onClick={() => setValue(value + 1)}><BsPlusLg size={22}/></PrimaryButton>
<NumberInput
className="w-12!"
onChange={setValue}
value={value}
/>
</div>
<ProgressBlock
label="Progress"
>
<Progress
backgroundColor="var(--color-gray-300)"
progressColor="var(--color-amber-500)"
min={0}
max={100}
value={value}
label="Progress"
/>
</ProgressBlock>
<ProgressBlock label="Primary Progress">
<PrimaryProgress
min={0}
max={100}
value={value}
label="Primary Progress"
/>
</ProgressBlock>
<ProgressBlock label="Secondary Progress">
<SecondaryProgress
min={0}
max={100}
value={value}
label="Secondary Progress"
/>
</ProgressBlock>
<ProgressBlock label="Tertiary Progress">
<TertiaryProgress
min={0}
max={100}
value={value}
label="Tertiary Progress"
/>
</ProgressBlock>
<ProgressBlock label="Info Progress">
<InfoProgress
min={0}
max={100}
value={value}
label="Info Progress"
/>
</ProgressBlock>
<ProgressBlock label="Success Progress">
<SuccessProgress
min={0}
max={100}
value={value}
label="Success Progress"
/>
</ProgressBlock>
<ProgressBlock label="Warning Progress">
<WarningProgress
min={0}
max={100}
value={value}
label="Warning Progress"
/>
</ProgressBlock>
<ProgressBlock label="Danger Progress">
<DangerProgress
min={0}
max={100}
value={value}
label="Danger Progress"
/>
</ProgressBlock>
<ProgressBlock label="Molten Progress">
<MoltenProgress
min={0}
max={100}
value={value}
label="Molten Progress"
/>
</ProgressBlock>
<ProgressBlock label="Light Progress">
<LightProgress
min={0}
max={100}
value={value}
label="Light Progress"
/>
</ProgressBlock>
<ProgressBlock label="Dark Progress">
<DarkProgress
min={0}
max={100}
value={value}
label="Dark Progress"
/>
</ProgressBlock>
</div>
);
}
function ProgressBlock({
label,
children
}:{
label: string;
children: React.ReactNode;
}){
return (
<div
className="flex flex-col items-center justify-center w-128 gap-y-2"
>
<h3
className="font-bold text-2xl"
>
{label}
</h3>
{children}
</div>
);
}