Added loaders

This commit is contained in:
Pijus Kamandulis 2024-10-19 11:58:02 +03:00
parent 3f04eabfd6
commit 56c0275db0
13 changed files with 141 additions and 31 deletions

View File

@ -1,4 +1,5 @@
import classNames from 'classnames';
import Loader, { LoaderSize } from './Loader';
enum ButtonColor {
Primary = 'primary',
@ -10,6 +11,7 @@ enum ButtonColor {
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
color?: ButtonColor;
fullWidth?: boolean;
isLoading?: boolean;
}
const Button: React.FC<ButtonProps> = ({
@ -17,10 +19,13 @@ const Button: React.FC<ButtonProps> = ({
color = ButtonColor.Primary,
fullWidth = false,
disabled = false,
isLoading = false,
...props
}) => {
disabled = disabled || isLoading;
const buttonClass = classNames(
'flex justify-center rounded-md px-3 py-1.5 text-sm font-semibold leading-6 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
'flex justify-center rounded-md px-3 py-1.5 text-sm font-semibold leading-6 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 text-center items-center',
{
'bg-indigo-600 hover:bg-indigo-500 focus-visible:outline-indigo-600':
color === ButtonColor.Primary && !disabled,
@ -38,6 +43,7 @@ const Button: React.FC<ButtonProps> = ({
return (
<button className={buttonClass} disabled={disabled} {...props}>
{isLoading && <Loader className="me-2" center size={LoaderSize.Small} />}
{children}
</button>
);

48
src/components/Loader.tsx Normal file
View File

@ -0,0 +1,48 @@
import classNames from 'classnames';
import { LoaderIcon } from './icons';
export enum LoaderSize {
Small,
Medium,
Big,
}
interface LoaderProps {
className?: string;
fullHeight?: boolean;
center?: boolean;
size?: LoaderSize;
}
const Loader: React.FC<LoaderProps> = ({
className,
fullHeight = false,
center = false,
size = LoaderSize.Medium,
}) => {
const containerClassName = classNames(
{
['flex items-center justify-center']: center,
['h-full']: fullHeight,
},
className,
);
const loaderClassName = classNames(
{
['h-4 w-4']: size === LoaderSize.Small,
['h-8 w-8']: size === LoaderSize.Medium,
['h-12 w-12']: size === LoaderSize.Big,
},
'inline animate-spin fill-blue-600 text-gray-200 dark:text-gray-600',
);
return (
<div role="status" className={containerClassName}>
<LoaderIcon className={loaderClassName} />
<span className="sr-only">Loading...</span>
</div>
);
};
export default Loader;

View File

@ -56,9 +56,16 @@ const CreateEstimationSessionForm: React.FC<
/>
)}
</form.Field>
<form.Subscribe selector={(state) => [state.canSubmit]}>
{([canSubmit]) => (
<Button type="submit" disabled={!canSubmit} fullWidth>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<Button
type="submit"
disabled={!canSubmit}
isLoading={isSubmitting}
fullWidth
>
Create
</Button>
)}

View File

@ -0,0 +1,24 @@
import React from 'react';
const LoaderIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 100 101"
className={`me-3 inline h-4 w-4 animate-spin text-white ${props.className}`}
aria-hidden="true"
role="status"
{...props}
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="#E5E7EB"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentColor"
/>
</svg>
);
export default LoaderIcon;

View File

@ -1,3 +1,4 @@
import PencilIcon from './PencilIcon';
import LoaderIcon from './LoaderIcon';
export { PencilIcon };
export { PencilIcon, LoaderIcon };

View File

@ -1,16 +1,18 @@
import Input from './Input';
import Button, { ButtonColor } from './Button';
import GridList from './GridList';
import Card from './Card';
import Drawer from './Drawer';
import CreateEstimationSessionForm from './forms/CreateEstimationSessionForm';
import Drawer from './Drawer';
import GridList from './GridList';
import Input from './Input';
import Loader from './Loader';
export {
Input,
Button,
ButtonColor,
GridList,
Card,
Drawer,
CreateEstimationSessionForm,
Drawer,
GridList,
Input,
Loader,
};

View File

@ -18,6 +18,7 @@ import Estimation from './pages/Estimation/Estimation';
import Header from './components/Header';
import Profile from './pages/Profile';
import Join from './pages/Join';
import { Loader } from './components';
interface RouterContext {
userContext: UserContextType;
@ -108,7 +109,7 @@ const InnerApp = () => {
const userContext = useUser();
return userContext.isLoading ? (
<p>Loading...</p>
<Loader className="h-screen" fullHeight center />
) : (
<RouterProvider router={router} context={{ userContext }} />
);

View File

@ -4,7 +4,7 @@ import { getRouteApi } from '@tanstack/react-router';
import TaskSidebar from './components/TaskSidebar';
import VoteSelection from './components/VoteSelection';
import VoteList from './components/VoteList';
import { Drawer } from '../../components';
import { Drawer, Loader } from '../../components';
import EditTicketForm from './components/EditTicketForm';
import PlayerList from './components/PlayerList';
import HtmlEmbed from '../../components/HtmlEmbed';
@ -22,8 +22,8 @@ const Estimation: React.FC = () => {
useEffect(() => estimationState?.setSessionId(sessionId), [sessionId]);
if (!estimationState?.currentSessionData) {
return null; // TODO: Add a loader
if (estimationState?.currentSessionData?.id !== sessionId) {
return <Loader fullHeight center />;
}
const {

View File

@ -81,9 +81,16 @@ const EditTicketForm: React.FC<EditTicketFormProps> = ({
/>
)}
</form.Field>
<form.Subscribe selector={(state) => [state.canSubmit]}>
{([canSubmit]) => (
<Button type="submit" disabled={!canSubmit} fullWidth>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<Button
type="submit"
disabled={!canSubmit}
isLoading={isSubmitting}
fullWidth
>
Update
</Button>
)}

View File

@ -81,9 +81,15 @@ const EstimationResult: React.FC<VoteListProps> = ({
/>
)}
</form.Field>
<form.Subscribe selector={(state) => [state.canSubmit]}>
{([canSubmit]) => (
<Button type="submit" disabled={!canSubmit}>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<Button
type="submit"
disabled={!canSubmit}
isLoading={isSubmitting}
>
Save
</Button>
)}

View File

@ -5,6 +5,7 @@ import {
SessionInviteInfo,
} from '../lib/functions/estimationSessionInvite';
import { getRouteApi } from '@tanstack/react-router';
import { Loader } from '../components';
const route = getRouteApi('/_authenticated/join/$sessionId');
@ -40,8 +41,7 @@ const Join = () => {
};
if (!sessionInfo || isLoading) {
// TODO: add loader
return <p>Loading...</p>;
return <Loader fullHeight center />;
}
if (!sessionInfo.success) {

View File

@ -65,13 +65,14 @@ const Login = () => {
)}
</form.Field>
<form.Subscribe selector={(state) => [state.canSubmit]}>
{([canSubmit]) => (
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<div>
{/* TODO: Add loader when [state.isSubmitting] */}
<div className="flex items-center justify-between gap-4">
<Button
disabled={!canSubmit}
disabled={!canSubmit || isSubmitting}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@ -85,7 +86,7 @@ const Login = () => {
</Button>
<Button
disabled={!canSubmit}
disabled={!canSubmit || isSubmitting}
color={ButtonColor.Secondary}
onClick={(e) => {
e.preventDefault();

View File

@ -50,9 +50,16 @@ const Profile = () => {
);
}}
</updateUsernameForm.Field>
<updateUsernameForm.Subscribe selector={(state) => [state.canSubmit]}>
{([canSubmit]) => (
<Button type="submit" disabled={!canSubmit} fullWidth>
<updateUsernameForm.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<Button
type="submit"
disabled={!canSubmit}
isLoading={isSubmitting}
fullWidth
>
Update
</Button>
)}