Added loaders
This commit is contained in:
parent
3f04eabfd6
commit
56c0275db0
|
@ -1,4 +1,5 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import Loader, { LoaderSize } from './Loader';
|
||||||
|
|
||||||
enum ButtonColor {
|
enum ButtonColor {
|
||||||
Primary = 'primary',
|
Primary = 'primary',
|
||||||
|
@ -10,6 +11,7 @@ enum ButtonColor {
|
||||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
color?: ButtonColor;
|
color?: ButtonColor;
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button: React.FC<ButtonProps> = ({
|
const Button: React.FC<ButtonProps> = ({
|
||||||
|
@ -17,10 +19,13 @@ const Button: React.FC<ButtonProps> = ({
|
||||||
color = ButtonColor.Primary,
|
color = ButtonColor.Primary,
|
||||||
fullWidth = false,
|
fullWidth = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
isLoading = false,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
|
disabled = disabled || isLoading;
|
||||||
|
|
||||||
const buttonClass = classNames(
|
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':
|
'bg-indigo-600 hover:bg-indigo-500 focus-visible:outline-indigo-600':
|
||||||
color === ButtonColor.Primary && !disabled,
|
color === ButtonColor.Primary && !disabled,
|
||||||
|
@ -38,6 +43,7 @@ const Button: React.FC<ButtonProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={buttonClass} disabled={disabled} {...props}>
|
<button className={buttonClass} disabled={disabled} {...props}>
|
||||||
|
{isLoading && <Loader className="me-2" center size={LoaderSize.Small} />}
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
|
@ -56,9 +56,16 @@ const CreateEstimationSessionForm: React.FC<
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
<form.Subscribe selector={(state) => [state.canSubmit]}>
|
<form.Subscribe
|
||||||
{([canSubmit]) => (
|
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
||||||
<Button type="submit" disabled={!canSubmit} fullWidth>
|
>
|
||||||
|
{([canSubmit, isSubmitting]) => (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={!canSubmit}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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;
|
|
@ -1,3 +1,4 @@
|
||||||
import PencilIcon from './PencilIcon';
|
import PencilIcon from './PencilIcon';
|
||||||
|
import LoaderIcon from './LoaderIcon';
|
||||||
|
|
||||||
export { PencilIcon };
|
export { PencilIcon, LoaderIcon };
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import Input from './Input';
|
|
||||||
import Button, { ButtonColor } from './Button';
|
import Button, { ButtonColor } from './Button';
|
||||||
import GridList from './GridList';
|
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
import Drawer from './Drawer';
|
|
||||||
import CreateEstimationSessionForm from './forms/CreateEstimationSessionForm';
|
import CreateEstimationSessionForm from './forms/CreateEstimationSessionForm';
|
||||||
|
import Drawer from './Drawer';
|
||||||
|
import GridList from './GridList';
|
||||||
|
import Input from './Input';
|
||||||
|
import Loader from './Loader';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Input,
|
|
||||||
Button,
|
Button,
|
||||||
ButtonColor,
|
ButtonColor,
|
||||||
GridList,
|
|
||||||
Card,
|
Card,
|
||||||
Drawer,
|
|
||||||
CreateEstimationSessionForm,
|
CreateEstimationSessionForm,
|
||||||
|
Drawer,
|
||||||
|
GridList,
|
||||||
|
Input,
|
||||||
|
Loader,
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +18,7 @@ import Estimation from './pages/Estimation/Estimation';
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import Profile from './pages/Profile';
|
import Profile from './pages/Profile';
|
||||||
import Join from './pages/Join';
|
import Join from './pages/Join';
|
||||||
|
import { Loader } from './components';
|
||||||
|
|
||||||
interface RouterContext {
|
interface RouterContext {
|
||||||
userContext: UserContextType;
|
userContext: UserContextType;
|
||||||
|
@ -108,7 +109,7 @@ const InnerApp = () => {
|
||||||
const userContext = useUser();
|
const userContext = useUser();
|
||||||
|
|
||||||
return userContext.isLoading ? (
|
return userContext.isLoading ? (
|
||||||
<p>Loading...</p>
|
<Loader className="h-screen" fullHeight center />
|
||||||
) : (
|
) : (
|
||||||
<RouterProvider router={router} context={{ userContext }} />
|
<RouterProvider router={router} context={{ userContext }} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { getRouteApi } from '@tanstack/react-router';
|
||||||
import TaskSidebar from './components/TaskSidebar';
|
import TaskSidebar from './components/TaskSidebar';
|
||||||
import VoteSelection from './components/VoteSelection';
|
import VoteSelection from './components/VoteSelection';
|
||||||
import VoteList from './components/VoteList';
|
import VoteList from './components/VoteList';
|
||||||
import { Drawer } from '../../components';
|
import { Drawer, Loader } from '../../components';
|
||||||
import EditTicketForm from './components/EditTicketForm';
|
import EditTicketForm from './components/EditTicketForm';
|
||||||
import PlayerList from './components/PlayerList';
|
import PlayerList from './components/PlayerList';
|
||||||
import HtmlEmbed from '../../components/HtmlEmbed';
|
import HtmlEmbed from '../../components/HtmlEmbed';
|
||||||
|
@ -22,8 +22,8 @@ const Estimation: React.FC = () => {
|
||||||
|
|
||||||
useEffect(() => estimationState?.setSessionId(sessionId), [sessionId]);
|
useEffect(() => estimationState?.setSessionId(sessionId), [sessionId]);
|
||||||
|
|
||||||
if (!estimationState?.currentSessionData) {
|
if (estimationState?.currentSessionData?.id !== sessionId) {
|
||||||
return null; // TODO: Add a loader
|
return <Loader fullHeight center />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -81,9 +81,16 @@ const EditTicketForm: React.FC<EditTicketFormProps> = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
<form.Subscribe selector={(state) => [state.canSubmit]}>
|
<form.Subscribe
|
||||||
{([canSubmit]) => (
|
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
||||||
<Button type="submit" disabled={!canSubmit} fullWidth>
|
>
|
||||||
|
{([canSubmit, isSubmitting]) => (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={!canSubmit}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -81,9 +81,15 @@ const EstimationResult: React.FC<VoteListProps> = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
<form.Subscribe selector={(state) => [state.canSubmit]}>
|
<form.Subscribe
|
||||||
{([canSubmit]) => (
|
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
||||||
<Button type="submit" disabled={!canSubmit}>
|
>
|
||||||
|
{([canSubmit, isSubmitting]) => (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={!canSubmit}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
SessionInviteInfo,
|
SessionInviteInfo,
|
||||||
} from '../lib/functions/estimationSessionInvite';
|
} from '../lib/functions/estimationSessionInvite';
|
||||||
import { getRouteApi } from '@tanstack/react-router';
|
import { getRouteApi } from '@tanstack/react-router';
|
||||||
|
import { Loader } from '../components';
|
||||||
|
|
||||||
const route = getRouteApi('/_authenticated/join/$sessionId');
|
const route = getRouteApi('/_authenticated/join/$sessionId');
|
||||||
|
|
||||||
|
@ -40,8 +41,7 @@ const Join = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!sessionInfo || isLoading) {
|
if (!sessionInfo || isLoading) {
|
||||||
// TODO: add loader
|
return <Loader fullHeight center />;
|
||||||
return <p>Loading...</p>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sessionInfo.success) {
|
if (!sessionInfo.success) {
|
||||||
|
|
|
@ -65,13 +65,14 @@ const Login = () => {
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
|
|
||||||
<form.Subscribe selector={(state) => [state.canSubmit]}>
|
<form.Subscribe
|
||||||
{([canSubmit]) => (
|
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
||||||
|
>
|
||||||
|
{([canSubmit, isSubmitting]) => (
|
||||||
<div>
|
<div>
|
||||||
{/* TODO: Add loader when [state.isSubmitting] */}
|
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<Button
|
<Button
|
||||||
disabled={!canSubmit}
|
disabled={!canSubmit || isSubmitting}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -85,7 +86,7 @@ const Login = () => {
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
disabled={!canSubmit}
|
disabled={!canSubmit || isSubmitting}
|
||||||
color={ButtonColor.Secondary}
|
color={ButtonColor.Secondary}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
@ -50,9 +50,16 @@ const Profile = () => {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</updateUsernameForm.Field>
|
</updateUsernameForm.Field>
|
||||||
<updateUsernameForm.Subscribe selector={(state) => [state.canSubmit]}>
|
<updateUsernameForm.Subscribe
|
||||||
{([canSubmit]) => (
|
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
||||||
<Button type="submit" disabled={!canSubmit} fullWidth>
|
>
|
||||||
|
{([canSubmit, isSubmitting]) => (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={!canSubmit}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
Loading…
Reference in New Issue