From f9a2e7ac6b910b7887a00b795bf0108c2ac69550 Mon Sep 17 00:00:00 2001 From: Katalina Okano Date: Mon, 15 Mar 2021 19:30:05 -0400 Subject: [PATCH] feat: add skeleton masthead and generic loading page --- .../atoms/dot-overlay/DotOverlay.stories.tsx | 1 + .../atoms/dot-overlay/DotOverlay.tsx | 21 ++++++- .../atoms/loading-text/Loading.stories.tsx | 12 ++++ .../atoms/loading-text/Loading.tsx | 30 +++++++++ .../design-system/atoms/loading-text/index.ts | 1 + .../atoms/placeholder/Placeholder.stories.tsx | 13 ++++ .../atoms/placeholder/Placeholder.tsx | 55 ++++++++++++++++ .../design-system/atoms/placeholder/index.ts | 1 + .../atoms/spinner/Spinner.stories.tsx | 13 ++++ .../design-system/atoms/spinner/Spinner.tsx | 62 +++++++++++++++++++ packages/design-system/atoms/spinner/index.ts | 1 + .../UserAvatarGroup.stories.tsx | 7 +++ .../UserAvatarGroupSkeleton.tsx | 17 +++++ .../molecules/user-avatar-group/index.ts | 1 + .../organisms/app-shell/AppShell.tsx | 38 +++++++++--- .../organisms/masthead/Masthead.stories.tsx | 13 ++-- .../organisms/masthead/Skeleton.tsx | 27 ++++++++ .../design-system/organisms/masthead/index.ts | 1 + .../GenericLoading.stories.tsx | 7 +++ .../generic-loading/GenericLoading.styled.ts | 6 ++ .../generic-loading/GenericLoading.tsx | 28 +++++++++ .../templates/generic-loading/index.ts | 1 + packages/web/src/app-router/AppRouter.tsx | 11 ++-- packages/web/src/pages/auth/login.tsx | 5 +- packages/web/src/pages/landing.tsx | 2 +- packages/web/src/pages/machinery/logout.tsx | 3 +- .../web/src/pages/machinery/new-session.tsx | 14 +++-- 27 files changed, 362 insertions(+), 29 deletions(-) create mode 100644 packages/design-system/atoms/loading-text/Loading.stories.tsx create mode 100644 packages/design-system/atoms/loading-text/Loading.tsx create mode 100644 packages/design-system/atoms/loading-text/index.ts create mode 100644 packages/design-system/atoms/placeholder/Placeholder.stories.tsx create mode 100644 packages/design-system/atoms/placeholder/Placeholder.tsx create mode 100644 packages/design-system/atoms/placeholder/index.ts create mode 100644 packages/design-system/atoms/spinner/Spinner.stories.tsx create mode 100644 packages/design-system/atoms/spinner/Spinner.tsx create mode 100644 packages/design-system/atoms/spinner/index.ts create mode 100644 packages/design-system/molecules/user-avatar-group/UserAvatarGroupSkeleton.tsx create mode 100644 packages/design-system/organisms/masthead/Skeleton.tsx create mode 100644 packages/design-system/templates/generic-loading/GenericLoading.stories.tsx create mode 100644 packages/design-system/templates/generic-loading/GenericLoading.styled.ts create mode 100644 packages/design-system/templates/generic-loading/GenericLoading.tsx create mode 100644 packages/design-system/templates/generic-loading/index.ts diff --git a/packages/design-system/atoms/dot-overlay/DotOverlay.stories.tsx b/packages/design-system/atoms/dot-overlay/DotOverlay.stories.tsx index 4047561..7f6618c 100644 --- a/packages/design-system/atoms/dot-overlay/DotOverlay.stories.tsx +++ b/packages/design-system/atoms/dot-overlay/DotOverlay.stories.tsx @@ -7,3 +7,4 @@ export default { export const Dark = () => ; export const Light = () => ; +export const Skeleton = () => ; diff --git a/packages/design-system/atoms/dot-overlay/DotOverlay.tsx b/packages/design-system/atoms/dot-overlay/DotOverlay.tsx index 1a84233..f503145 100644 --- a/packages/design-system/atoms/dot-overlay/DotOverlay.tsx +++ b/packages/design-system/atoms/dot-overlay/DotOverlay.tsx @@ -1,3 +1,4 @@ +import { animateOpacity } from '@roleypoly/design-system/atoms/placeholder'; import * as React from 'react'; import styled from 'styled-components'; @@ -33,6 +34,22 @@ const DotOverlayLight = styled(dotOverlayBase)` ); `; -export const DotOverlay = ({ light }: { light?: boolean }) => { - return light ? : ; +const DotOverlaySkeleton = styled(DotOverlayDark)` + ${animateOpacity} +`; + +export const DotOverlay = ({ + light, + skeleton, +}: { + light?: boolean; + skeleton?: boolean; +}) => { + return skeleton ? ( + + ) : light ? ( + + ) : ( + + ); }; diff --git a/packages/design-system/atoms/loading-text/Loading.stories.tsx b/packages/design-system/atoms/loading-text/Loading.stories.tsx new file mode 100644 index 0000000..8d776a3 --- /dev/null +++ b/packages/design-system/atoms/loading-text/Loading.stories.tsx @@ -0,0 +1,12 @@ +import { Hero } from '@roleypoly/design-system/atoms/hero'; +import { LoadingFill } from './Loading'; +export default { + title: 'Atoms/Loading Text', + component: LoadingFill, +}; + +export const loading = (args) => ( + + + +); diff --git a/packages/design-system/atoms/loading-text/Loading.tsx b/packages/design-system/atoms/loading-text/Loading.tsx new file mode 100644 index 0000000..ec93eb0 --- /dev/null +++ b/packages/design-system/atoms/loading-text/Loading.tsx @@ -0,0 +1,30 @@ +const loadingTexts = [ + 'Loading Roleypoly...', + 'Reticulating splines...', + 'Mining cryptocoins...', + 'Going to Mars...', + 'Building the Box Signature Edition...', + 'Hiring a new CEO...', + 'Firing the new CEO...', + 'Doing a calculation...', + 'Doin a fixy boi...', + 'Feeling like a plastic bag...', + 'Levelling up...', + 'Your Roleypoly is evolving!!!', + 'Adding subtitles...', + 'Rolling... Rolling...', +]; + +export const LoadingFill = (props: { forceIndex?: keyof typeof loadingTexts }) => { + const useEasterEgg = Math.floor(Math.random() * 10) === 0; + const index = + props.forceIndex !== undefined + ? props.forceIndex + : useEasterEgg + ? Math.floor(Math.random() * loadingTexts.length) + : 0; + + const text = loadingTexts[index]; + + return <>{text}; +}; diff --git a/packages/design-system/atoms/loading-text/index.ts b/packages/design-system/atoms/loading-text/index.ts new file mode 100644 index 0000000..bc5115b --- /dev/null +++ b/packages/design-system/atoms/loading-text/index.ts @@ -0,0 +1 @@ +export * from './Loading'; diff --git a/packages/design-system/atoms/placeholder/Placeholder.stories.tsx b/packages/design-system/atoms/placeholder/Placeholder.stories.tsx new file mode 100644 index 0000000..2fc770b --- /dev/null +++ b/packages/design-system/atoms/placeholder/Placeholder.stories.tsx @@ -0,0 +1,13 @@ +import { palette } from '../colors'; +import { PlaceholderBox } from './Placeholder'; + +export default { + title: 'Atoms/Placeholder', + component: PlaceholderBox, + args: { + firstColor: palette.taupe100, + secondColor: palette.taupe300, + }, +}; + +export const placeholderBox = (args) => ; diff --git a/packages/design-system/atoms/placeholder/Placeholder.tsx b/packages/design-system/atoms/placeholder/Placeholder.tsx new file mode 100644 index 0000000..3663132 --- /dev/null +++ b/packages/design-system/atoms/placeholder/Placeholder.tsx @@ -0,0 +1,55 @@ +import styled, { css, keyframes } from 'styled-components'; +import { palette } from '../colors'; + +export const fadeInOut = keyframes` + from { + background-color: var(--placeholder-first-color); + } + to { + background-color: var(--placeholder-second-color); + } +`; + +export const animateFade = (firstColor?: string, secondColor?: string) => css` + @media (prefers-reduced-motion: no-preference) { + animation: ${fadeInOut} 2s ease-in-out infinite alternate; + } + + --placeholder-first-color: ${firstColor}; + --placeholder-second-color: ${secondColor}; +`; + +type PlaceholderProps = { + forceReduceMotion?: boolean; + firstColor?: string; + secondColor?: string; +}; + +export const PlaceholderBox = styled.div` + width: 7em; + height: 1em; + background-color: ${(props) => props.firstColor || palette.taupe200}; + display: inline-block; + border-radius: 2px; + position: relative; + top: 0.2em; + ${(props) => + props.secondColor && + !props.forceReduceMotion && + animateFade(props.firstColor, props.secondColor)} +`; + +export const opacityInOut = keyframes` + from { + opacity: 0.6; + } + to { + opacity: 0.3; + } +`; + +export const animateOpacity = css` + @media (prefers-reduced-motion: no-preference) { + animation: ${opacityInOut} 5s ease-in-out infinite alternate; + } +`; diff --git a/packages/design-system/atoms/placeholder/index.ts b/packages/design-system/atoms/placeholder/index.ts new file mode 100644 index 0000000..5c37725 --- /dev/null +++ b/packages/design-system/atoms/placeholder/index.ts @@ -0,0 +1 @@ +export * from './Placeholder'; diff --git a/packages/design-system/atoms/spinner/Spinner.stories.tsx b/packages/design-system/atoms/spinner/Spinner.stories.tsx new file mode 100644 index 0000000..530bf52 --- /dev/null +++ b/packages/design-system/atoms/spinner/Spinner.stories.tsx @@ -0,0 +1,13 @@ +import { Hero } from '@roleypoly/design-system/atoms/hero'; +import { Spinner } from './Spinner'; + +export default { + title: 'Atoms/Spinner', + component: Spinner, +}; + +export const spinner = (args) => ( + + + +); diff --git a/packages/design-system/atoms/spinner/Spinner.tsx b/packages/design-system/atoms/spinner/Spinner.tsx new file mode 100644 index 0000000..5b2aa2a --- /dev/null +++ b/packages/design-system/atoms/spinner/Spinner.tsx @@ -0,0 +1,62 @@ +import styled, { keyframes } from 'styled-components'; +import { palette } from '../colors'; + +type SpinnerProps = { + size?: number; + reverse?: boolean; + color?: string; + speed?: number; +}; + +const spinnerKeyframes = keyframes` + 0%, 100% { + border-width: 0; + border-right-width: 3px; + } + 25% { + border-width: 0; + border-top-width: 3px; + } + 50% { + border-width: 0; + border-left-width: 3px; + } + 75% { + border-width: 0; + border-bottom-width: 3px; + } +`; + +const SpinnerStyled = styled.div>` + @media (prefers-reduced-motion: no-preference) { + animation: ${spinnerKeyframes} ${(props) => props.speed}s linear infinite + ${(props) => (props.reverse ? `reverse` : '')}; + transform: rotateZ(0); + } + + border: 0 solid ${(props) => props.color}; + width: ${(props) => props.size}px; + height: ${(props) => props.size}px; + border-radius: ${(props) => props.size * 0.7}px; + display: flex; + align-items: center; + justify-content: center; +`; + +export const Spinner = ( + props: SpinnerProps & { innerColor?: string; outerColor?: string } +) => ( + + + +); diff --git a/packages/design-system/atoms/spinner/index.ts b/packages/design-system/atoms/spinner/index.ts new file mode 100644 index 0000000..b259397 --- /dev/null +++ b/packages/design-system/atoms/spinner/index.ts @@ -0,0 +1 @@ +export * from './Spinner'; diff --git a/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.stories.tsx b/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.stories.tsx index bf60186..b81d941 100644 --- a/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.stories.tsx +++ b/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.stories.tsx @@ -2,6 +2,7 @@ import { Hero } from '@roleypoly/design-system/atoms/hero'; import * as React from 'react'; import { user } from '../../fixtures/storyData'; import { UserAvatarGroup } from './UserAvatarGroup'; +import { UserAvatarGroupSkeleton } from './UserAvatarGroupSkeleton'; export default { title: 'Molecules/User Avatar Group', @@ -17,3 +18,9 @@ export const Default = (args) => ( ); + +export const skeleton = () => ( + + + +); diff --git a/packages/design-system/molecules/user-avatar-group/UserAvatarGroupSkeleton.tsx b/packages/design-system/molecules/user-avatar-group/UserAvatarGroupSkeleton.tsx new file mode 100644 index 0000000..b6a21aa --- /dev/null +++ b/packages/design-system/molecules/user-avatar-group/UserAvatarGroupSkeleton.tsx @@ -0,0 +1,17 @@ +import { Avatar } from '@roleypoly/design-system/atoms/avatar'; +import { palette } from '@roleypoly/design-system/atoms/colors'; +import { PlaceholderBox } from '@roleypoly/design-system/atoms/placeholder'; +import * as React from 'react'; +import { Collapse, Group, GroupText } from './UserAvatarGroup.styled'; + +export const UserAvatarGroupSkeleton = () => ( + + + + + +   + + + +); diff --git a/packages/design-system/molecules/user-avatar-group/index.ts b/packages/design-system/molecules/user-avatar-group/index.ts index 52cf06c..1e1e2b2 100644 --- a/packages/design-system/molecules/user-avatar-group/index.ts +++ b/packages/design-system/molecules/user-avatar-group/index.ts @@ -1 +1,2 @@ export * from './UserAvatarGroup'; +export * from './UserAvatarGroupSkeleton'; diff --git a/packages/design-system/organisms/app-shell/AppShell.tsx b/packages/design-system/organisms/app-shell/AppShell.tsx index ab8b771..2e348cd 100644 --- a/packages/design-system/organisms/app-shell/AppShell.tsx +++ b/packages/design-system/organisms/app-shell/AppShell.tsx @@ -15,13 +15,35 @@ export type AppShellProps = { guilds?: GuildSlug[]; recentGuilds?: string[]; disableGuildPicker?: boolean; + skeleton?: boolean; +}; + +const OptionallyScroll = (props: { + shouldScroll: boolean; + children: React.ReactNode; +}) => { + if (props.shouldScroll) { + return ( + + {props.children} + + ); + } + + return <>{props.children}; }; export const AppShell = (props: AppShellProps) => ( <> - {props.user ? ( + {props.skeleton ? ( + + ) : props.user ? ( ( ) : ( )} - - {props.children} - {props.showFooter &&