Added loading skeleton to components (#328)

* added sign-in skeleton

* added team switcher skeleton

* improved style
This commit is contained in:
Zai Shi 2024-11-02 01:44:52 +01:00 committed by GitHub
parent 7cbe56c260
commit 92f7b60ec6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 59 additions and 74 deletions

View File

@ -1,3 +1,3 @@
export default function Loading() {
return null;
return <div>Loading...</div>;
}

View File

@ -5,7 +5,5 @@ import { StackServerApp } from "@stackframe/stack";
export const stackServerApp = new StackServerApp({
tokenStore: "nextjs-cookie",
urls: {
signIn: "/signin",
signUp: "/signup",
}
});

View File

@ -3,64 +3,16 @@
import React from "react";
import { cn } from "../..";
// TODO: add this to the generated global CSS
const styleSheet = `
@keyframes animation-light {
0% {
filter: grayscale(1) contrast(0) brightness(0) invert(1) brightness(0.8);
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-primary/10", className)}
{...props}
/>
);
}
100% {
filter: grayscale(1) contrast(0) brightness(0) invert(1) brightness(0.9);
}
@keyframes animation-dark {
0% {
filter: grayscale(1) contrast(0) brightness(0) invert(1) brightness(0.2);
}
100% {
filter: grayscale(1) contrast(0) brightness(0) invert(1) brightness(0.1);
}
.stack-skeleton[data-stack-state="activated"],
.stack-skeleton[data-stack-state="activated"] * {
pointer-events: none !important;
-webkit-user-select: none !important;
-moz-user-select: none !important;
user-select: none !important;
cursor: default !important;
}
.stack-skeleton[data-stack-state="activated"] {
animation: animation-light 1s infinite alternate-reverse !important;
}
html[data-stack-theme='dark'] .stack-skeleton[data-stack-state="activated"] {
animation: animation-dark 1s infinite alternate-reverse !important;
}
html[data-stack-theme='dark'] .stack-skeleton[data-stack-state="activated"] {
animation: animation-dark 1s infinite alternate-reverse !important;
}
`;
const Skeleton = React.forwardRef<
React.ElementRef<"span">,
React.ComponentPropsWithoutRef<"span"> & { deactivated?: boolean }
>(
(props, ref) => {
return (
<>
<style>{styleSheet}</style>
<span
{...props}
ref={ref}
data-stack-state={props.deactivated ? "deactivated" : "activated"}
className={cn(props.className, "stack-skeleton")}
/>
</>
);
}
);
Skeleton.displayName = "Skeleton";
export { Skeleton };

View File

@ -1,8 +1,8 @@
'use client';
import { runAsynchronously } from '@stackframe/stack-shared/dist/utils/promises';
import { StyledLink, Tabs, TabsContent, TabsList, TabsTrigger, Typography, cn } from '@stackframe/stack-ui';
import { useEffect } from 'react';
import { Skeleton, StyledLink, Tabs, TabsContent, TabsList, TabsTrigger, Typography, cn } from '@stackframe/stack-ui';
import { Suspense, useEffect } from 'react';
import { useStackApp, useUser } from '..';
import { CredentialSignIn } from '../components/credential-sign-in';
import { CredentialSignUp } from '../components/credential-sign-up';
@ -11,10 +11,10 @@ import { SeparatorWithText } from '../components/elements/separator-with-text';
import { MagicLinkSignIn } from '../components/magic-link-sign-in';
import { PredefinedMessageCard } from '../components/message-cards/predefined-message-card';
import { OAuthButtonGroup } from '../components/oauth-button-group';
import { useTranslation } from '../lib/translations';
import { PasskeyButton } from '../components/passkey-button';
import { useTranslation } from '../lib/translations';
export function AuthPage(props: {
type Props = {
noPasswordRepeat?: boolean,
firstTab?: 'magic-link' | 'password',
fullPage?: boolean,
@ -32,7 +32,37 @@ export function AuthPage(props: {
}[],
},
},
}) {
}
export function AuthPage(props: Props) {
return <Suspense fallback={<Fallback {...props} />}>
<Inner {...props} />
</Suspense>;
}
function Fallback(props: Props) {
const { t } = useTranslation();
return (
<MaybeFullPage fullPage={!!props.fullPage}>
<div className='stack-scope flex flex-col items-stretch' style={{ maxWidth: '380px', flexBasis: '380px', padding: props.fullPage ? '1rem' : 0 }}>
<div className="text-center mb-6 flex flex-col">
<Skeleton className='h-9 w-2/3 self-center' />
<Skeleton className='h-3 w-16 mt-8' />
<Skeleton className='h-9 w-full mt-1' />
<Skeleton className='h-3 w-24 mt-2' />
<Skeleton className='h-9 w-full mt-1' />
<Skeleton className='h-9 w-full mt-6' />
</div>
</div>
</MaybeFullPage>
);
}
function Inner (props: Props) {
const stackApp = useStackApp();
const user = useUser();
const projectFromHook = stackApp.useProject();

View File

@ -10,11 +10,12 @@ import {
SelectSeparator,
SelectTrigger,
SelectValue,
Skeleton,
Typography
} from "@stackframe/stack-ui";
import { PlusCircle, Settings } from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect, useMemo } from "react";
import { Suspense, useEffect, useMemo } from "react";
import { Team, useStackApp, useUser } from "..";
import { useTranslation } from "../lib/translations";
import { TeamIcon } from "./team-icon";
@ -26,6 +27,16 @@ type SelectedTeamSwitcherProps = {
};
export function SelectedTeamSwitcher(props: SelectedTeamSwitcherProps) {
return <Suspense fallback={<Fallback />}>
<Inner {...props} />
</Suspense>;
}
function Fallback() {
return <Skeleton className="h-9 w-full max-w-64 stack-scope" />;
}
function Inner(props: SelectedTeamSwitcherProps) {
const { t } = useTranslation();
const app = useStackApp();
const user = useUser();

View File

@ -31,13 +31,7 @@ type UserButtonProps = {
export function UserButton(props: UserButtonProps) {
return (
<Suspense
fallback={
<Skeleton>
<UserButtonInnerInner {...props} user={null} />
</Skeleton>
}
>
<Suspense fallback={<Skeleton className="h-[34px] w-[34px] rounded-full stack-scope" />}>
<UserButtonInner {...props} />
</Suspense>
);