Add toast on copy link

It animates in and out too
This commit is contained in:
Justin Edmund 2023-01-27 21:35:08 -08:00
parent 191f4447d5
commit 6da5a4f320
5 changed files with 122 additions and 47 deletions

View file

@ -33,6 +33,7 @@ import Link from 'next/link'
import LoginModal from '~components/LoginModal' import LoginModal from '~components/LoginModal'
import SignupModal from '~components/SignupModal' import SignupModal from '~components/SignupModal'
import AccountModal from '~components/AccountModal' import AccountModal from '~components/AccountModal'
import Toast from '~components/Toast'
const Header = () => { const Header = () => {
// Localization // Localization
@ -42,7 +43,7 @@ const Header = () => {
const router = useRouter() const router = useRouter()
// State management // State management
const [open, setOpen] = useState(false) const [copyToastOpen, setCopyToastOpen] = useState(false)
const [leftMenuOpen, setLeftMenuOpen] = useState(false) const [leftMenuOpen, setLeftMenuOpen] = useState(false)
const [rightMenuOpen, setRightMenuOpen] = useState(false) const [rightMenuOpen, setRightMenuOpen] = useState(false)
@ -50,6 +51,14 @@ const Header = () => {
const { account } = useSnapshot(accountState) const { account } = useSnapshot(accountState)
const { party } = useSnapshot(appState) const { party } = useSnapshot(appState)
function handleCopyToastOpenChanged(open: boolean) {
setCopyToastOpen(open)
}
function handleCopyToastCloseClicked() {
setCopyToastOpen(false)
}
function handleLeftMenuButtonClicked() { function handleLeftMenuButtonClicked() {
setLeftMenuOpen(!leftMenuOpen) setLeftMenuOpen(!leftMenuOpen)
} }
@ -58,10 +67,6 @@ const Header = () => {
setRightMenuOpen(!rightMenuOpen) setRightMenuOpen(!rightMenuOpen)
} }
function onClickOutsideMenu() {
setOpen(false)
}
function handleLeftMenuOpenChange(open: boolean) { function handleLeftMenuOpenChange(open: boolean) {
setLeftMenuOpen(open) setLeftMenuOpen(open)
} }
@ -86,11 +91,15 @@ const Header = () => {
el.select() el.select()
document.execCommand('copy') document.execCommand('copy')
el.remove() el.remove()
setCopyToastOpen(true)
} }
function newParty() { function handleNewParty(event: React.MouseEvent, path: string) {
event.preventDefault()
// Push the root URL // Push the root URL
router.push('/') router.push(path)
// Clean state // Clean state
const resetState = clonedeep(initialAppState) const resetState = clonedeep(initialAppState)
@ -100,6 +109,9 @@ const Header = () => {
// Set party to be editable // Set party to be editable
appState.party.editable = true appState.party.editable = true
// Close right menu
closeRightMenu()
} }
function logout() { function logout() {
@ -141,7 +153,7 @@ const Header = () => {
else console.error('Failed to unsave team: No party ID') else console.error('Failed to unsave team: No party ID')
} }
const title = () => { const pageTitle = () => {
let title = '' let title = ''
let hasAccessory = false let hasAccessory = false
@ -178,22 +190,7 @@ const Header = () => {
) )
} }
const saveButton = () => { const profileImage = () => {
return (
<Button
leftAccessoryIcon={<SaveIcon />}
className={classNames({
Save: true,
Saved: party.favorited,
})}
blended={true}
text={party.favorited ? 'Saved' : 'Save'}
onClick={toggleFavorite}
/>
)
}
const image = () => {
let image let image
const user = accountState.account.user const user = accountState.account.user
@ -214,6 +211,34 @@ const Header = () => {
return image return image
} }
const urlCopyToast = () => {
return (
<Toast
open={copyToastOpen}
duration={2400}
type="foreground"
content="This party's URL was copied to your clipboard"
onOpenChange={handleCopyToastOpenChanged}
onCloseClick={handleCopyToastCloseClicked}
/>
)
}
const saveButton = () => {
return (
<Button
leftAccessoryIcon={<SaveIcon />}
className={classNames({
Save: true,
Saved: party.favorited,
})}
blended={true}
text={party.favorited ? 'Saved' : 'Save'}
onClick={toggleFavorite}
/>
)
}
const left = () => { const left = () => {
return ( return (
<section> <section>
@ -235,7 +260,7 @@ const Header = () => {
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </div>
{title()} {pageTitle()}
</section> </section>
) )
} }
@ -249,13 +274,6 @@ const Header = () => {
? saveButton() ? saveButton()
: ''} : ''}
{/* <Button
leftAccessoryIcon={<AddIcon className="Add" />}
blended={true}
text={t('buttons.new')}
onClick={newParty}
/> */}
<DropdownMenu <DropdownMenu
open={rightMenuOpen} open={rightMenuOpen}
onOpenChange={handleRightMenuOpenChange} onOpenChange={handleRightMenuOpenChange}
@ -263,7 +281,7 @@ const Header = () => {
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
className={classNames({ Active: rightMenuOpen })} className={classNames({ Active: rightMenuOpen })}
leftAccessoryIcon={image()} leftAccessoryIcon={profileImage()}
rightAccessoryIcon={<ArrowIcon />} rightAccessoryIcon={<ArrowIcon />}
rightAccessoryClassName="Arrow" rightAccessoryClassName="Arrow"
onClick={handleRightMenuButtonClicked} onClick={handleRightMenuButtonClicked}
@ -340,12 +358,15 @@ const Header = () => {
items = ( items = (
<> <>
<DropdownMenuGroup className="MenuGroup"> <DropdownMenuGroup className="MenuGroup">
<DropdownMenuItem className="MenuItem" onClick={closeRightMenu}> <DropdownMenuItem className="MenuItem">
<Link href={`/new` || ''} passHref> <Link
onClick={(e: React.MouseEvent) => handleNewParty(e, '/new')}
href="/new"
>
New party New party
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="MenuItem" onClick={closeRightMenu}> <DropdownMenuItem className="MenuItem">
<Link href={`/${account.user.username}` || ''} passHref> <Link href={`/${account.user.username}` || ''} passHref>
Your profile Your profile
</Link> </Link>
@ -377,7 +398,10 @@ const Header = () => {
<> <>
<DropdownMenuGroup className="MenuGroup"> <DropdownMenuGroup className="MenuGroup">
<DropdownMenuItem className="MenuItem"> <DropdownMenuItem className="MenuItem">
<Link href={`/new` || ''} passHref> <Link
onClick={(e: React.MouseEvent) => handleNewParty(e, '/new')}
href="/new"
>
New party New party
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
@ -398,6 +422,7 @@ const Header = () => {
<nav id="Header"> <nav id="Header">
{left()} {left()}
{right()} {right()}
{urlCopyToast()}
</nav> </nav>
) )
} }

View file

@ -1,12 +1,33 @@
.Toast { .Toast {
background: var(--dialog-bg); background: var(--dialog-bg);
border-radius: $card-corner; border-radius: $card-corner;
box-shadow: 0 1px 12px rgba(0, 0, 0, 0.18); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12), 0 0 1px rgba(0, 0, 0, 0.1);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $unit-2x; gap: $unit-2x;
padding: $unit-3x; padding: $unit-3x;
&[data-state='open'] {
animation: slideLeft 150ms cubic-bezier(0.16, 1, 0.3, 1);
}
&[data-state='closed'] {
animation: fadeOut 300ms cubic-bezier(0.16, 1, 0.3, 1);
}
&[data-swipe='move'] {
transform: translateX(var(--radix-toast-swipe-move-x));
}
&[data-swipe='cancel'] {
transform: translateX(0);
transition: transform 200ms ease-out;
}
&[data-swipe='end'] {
animation: slideRight 100ms ease-out;
}
.Header { .Header {
align-items: center; align-items: center;
display: flex; display: flex;

View file

@ -17,22 +17,24 @@ const Toast = ({
content, content,
...props ...props
}: PropsWithChildren<Props>) => { }: PropsWithChildren<Props>) => {
const { onCloseClick, ...toastProps } = props
const classes = classNames(props.className, { const classes = classNames(props.className, {
Toast: true, Toast: true,
}) })
return ( return (
<ToastPrimitive.Root {...props} className={classes}> <ToastPrimitive.Root {...toastProps} className={classes}>
<div className="Header"> {title && (
{title && ( <div className="Header">
<ToastPrimitive.Title asChild> <ToastPrimitive.Title asChild>
<h3>{title}</h3> <h3>{title}</h3>
</ToastPrimitive.Title> </ToastPrimitive.Title>
)} <ToastPrimitive.Close aria-label="Close" onClick={onCloseClick}>
<ToastPrimitive.Close aria-label="Close" onClick={props.onCloseClick}> <span aria-hidden>×</span>
<span aria-hidden>×</span> </ToastPrimitive.Close>
</ToastPrimitive.Close> </div>
</div> )}
<ToastPrimitive.Description asChild> <ToastPrimitive.Description asChild>
<p>{content}</p> <p>{content}</p>
</ToastPrimitive.Description> </ToastPrimitive.Description>

View file

@ -44,7 +44,7 @@ function MyApp({ Component, pageProps }: AppProps) {
return ( return (
<ThemeProvider> <ThemeProvider>
<ToastProvider> <ToastProvider swipeDirection="right">
<Layout> <Layout>
<Component {...pageProps} /> <Component {...pageProps} />
</Layout> </Layout>

View file

@ -368,6 +368,15 @@ i.tag {
} }
} }
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes openModalDesktop { @keyframes openModalDesktop {
0% { 0% {
opacity: 0; opacity: 0;
@ -392,6 +401,24 @@ i.tag {
} }
} }
@keyframes slideLeft {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@keyframes slideRight {
from {
transform: translateX(var(--radix-toast-swipe-end-x));
}
to {
transform: translateX(100%);
}
}
@keyframes fadeInFilter { @keyframes fadeInFilter {
from { from {
backdrop-filter: blur(5px) saturate(100%) brightness(80%) opacity(0); backdrop-filter: blur(5px) saturate(100%) brightness(80%) opacity(0);