Add toast on copy link
It animates in and out too
This commit is contained in:
parent
191f4447d5
commit
6da5a4f320
5 changed files with 122 additions and 47 deletions
|
|
@ -33,6 +33,7 @@ import Link from 'next/link'
|
|||
import LoginModal from '~components/LoginModal'
|
||||
import SignupModal from '~components/SignupModal'
|
||||
import AccountModal from '~components/AccountModal'
|
||||
import Toast from '~components/Toast'
|
||||
|
||||
const Header = () => {
|
||||
// Localization
|
||||
|
|
@ -42,7 +43,7 @@ const Header = () => {
|
|||
const router = useRouter()
|
||||
|
||||
// State management
|
||||
const [open, setOpen] = useState(false)
|
||||
const [copyToastOpen, setCopyToastOpen] = useState(false)
|
||||
const [leftMenuOpen, setLeftMenuOpen] = useState(false)
|
||||
const [rightMenuOpen, setRightMenuOpen] = useState(false)
|
||||
|
||||
|
|
@ -50,6 +51,14 @@ const Header = () => {
|
|||
const { account } = useSnapshot(accountState)
|
||||
const { party } = useSnapshot(appState)
|
||||
|
||||
function handleCopyToastOpenChanged(open: boolean) {
|
||||
setCopyToastOpen(open)
|
||||
}
|
||||
|
||||
function handleCopyToastCloseClicked() {
|
||||
setCopyToastOpen(false)
|
||||
}
|
||||
|
||||
function handleLeftMenuButtonClicked() {
|
||||
setLeftMenuOpen(!leftMenuOpen)
|
||||
}
|
||||
|
|
@ -58,10 +67,6 @@ const Header = () => {
|
|||
setRightMenuOpen(!rightMenuOpen)
|
||||
}
|
||||
|
||||
function onClickOutsideMenu() {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
function handleLeftMenuOpenChange(open: boolean) {
|
||||
setLeftMenuOpen(open)
|
||||
}
|
||||
|
|
@ -86,11 +91,15 @@ const Header = () => {
|
|||
el.select()
|
||||
document.execCommand('copy')
|
||||
el.remove()
|
||||
|
||||
setCopyToastOpen(true)
|
||||
}
|
||||
|
||||
function newParty() {
|
||||
function handleNewParty(event: React.MouseEvent, path: string) {
|
||||
event.preventDefault()
|
||||
|
||||
// Push the root URL
|
||||
router.push('/')
|
||||
router.push(path)
|
||||
|
||||
// Clean state
|
||||
const resetState = clonedeep(initialAppState)
|
||||
|
|
@ -100,6 +109,9 @@ const Header = () => {
|
|||
|
||||
// Set party to be editable
|
||||
appState.party.editable = true
|
||||
|
||||
// Close right menu
|
||||
closeRightMenu()
|
||||
}
|
||||
|
||||
function logout() {
|
||||
|
|
@ -141,7 +153,7 @@ const Header = () => {
|
|||
else console.error('Failed to unsave team: No party ID')
|
||||
}
|
||||
|
||||
const title = () => {
|
||||
const pageTitle = () => {
|
||||
let title = ''
|
||||
let hasAccessory = false
|
||||
|
||||
|
|
@ -178,22 +190,7 @@ const Header = () => {
|
|||
)
|
||||
}
|
||||
|
||||
const saveButton = () => {
|
||||
return (
|
||||
<Button
|
||||
leftAccessoryIcon={<SaveIcon />}
|
||||
className={classNames({
|
||||
Save: true,
|
||||
Saved: party.favorited,
|
||||
})}
|
||||
blended={true}
|
||||
text={party.favorited ? 'Saved' : 'Save'}
|
||||
onClick={toggleFavorite}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const image = () => {
|
||||
const profileImage = () => {
|
||||
let image
|
||||
|
||||
const user = accountState.account.user
|
||||
|
|
@ -214,6 +211,34 @@ const Header = () => {
|
|||
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 = () => {
|
||||
return (
|
||||
<section>
|
||||
|
|
@ -235,7 +260,7 @@ const Header = () => {
|
|||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
{title()}
|
||||
{pageTitle()}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
|
@ -249,13 +274,6 @@ const Header = () => {
|
|||
? saveButton()
|
||||
: ''}
|
||||
|
||||
{/* <Button
|
||||
leftAccessoryIcon={<AddIcon className="Add" />}
|
||||
blended={true}
|
||||
text={t('buttons.new')}
|
||||
onClick={newParty}
|
||||
/> */}
|
||||
|
||||
<DropdownMenu
|
||||
open={rightMenuOpen}
|
||||
onOpenChange={handleRightMenuOpenChange}
|
||||
|
|
@ -263,7 +281,7 @@ const Header = () => {
|
|||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
className={classNames({ Active: rightMenuOpen })}
|
||||
leftAccessoryIcon={image()}
|
||||
leftAccessoryIcon={profileImage()}
|
||||
rightAccessoryIcon={<ArrowIcon />}
|
||||
rightAccessoryClassName="Arrow"
|
||||
onClick={handleRightMenuButtonClicked}
|
||||
|
|
@ -340,12 +358,15 @@ const Header = () => {
|
|||
items = (
|
||||
<>
|
||||
<DropdownMenuGroup className="MenuGroup">
|
||||
<DropdownMenuItem className="MenuItem" onClick={closeRightMenu}>
|
||||
<Link href={`/new` || ''} passHref>
|
||||
<DropdownMenuItem className="MenuItem">
|
||||
<Link
|
||||
onClick={(e: React.MouseEvent) => handleNewParty(e, '/new')}
|
||||
href="/new"
|
||||
>
|
||||
New party
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="MenuItem" onClick={closeRightMenu}>
|
||||
<DropdownMenuItem className="MenuItem">
|
||||
<Link href={`/${account.user.username}` || ''} passHref>
|
||||
Your profile
|
||||
</Link>
|
||||
|
|
@ -377,7 +398,10 @@ const Header = () => {
|
|||
<>
|
||||
<DropdownMenuGroup className="MenuGroup">
|
||||
<DropdownMenuItem className="MenuItem">
|
||||
<Link href={`/new` || ''} passHref>
|
||||
<Link
|
||||
onClick={(e: React.MouseEvent) => handleNewParty(e, '/new')}
|
||||
href="/new"
|
||||
>
|
||||
New party
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
|
@ -398,6 +422,7 @@ const Header = () => {
|
|||
<nav id="Header">
|
||||
{left()}
|
||||
{right()}
|
||||
{urlCopyToast()}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,33 @@
|
|||
.Toast {
|
||||
background: var(--dialog-bg);
|
||||
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;
|
||||
flex-direction: column;
|
||||
gap: $unit-2x;
|
||||
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 {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -17,22 +17,24 @@ const Toast = ({
|
|||
content,
|
||||
...props
|
||||
}: PropsWithChildren<Props>) => {
|
||||
const { onCloseClick, ...toastProps } = props
|
||||
|
||||
const classes = classNames(props.className, {
|
||||
Toast: true,
|
||||
})
|
||||
|
||||
return (
|
||||
<ToastPrimitive.Root {...props} className={classes}>
|
||||
<div className="Header">
|
||||
{title && (
|
||||
<ToastPrimitive.Root {...toastProps} className={classes}>
|
||||
{title && (
|
||||
<div className="Header">
|
||||
<ToastPrimitive.Title asChild>
|
||||
<h3>{title}</h3>
|
||||
</ToastPrimitive.Title>
|
||||
)}
|
||||
<ToastPrimitive.Close aria-label="Close" onClick={props.onCloseClick}>
|
||||
<span aria-hidden>×</span>
|
||||
</ToastPrimitive.Close>
|
||||
</div>
|
||||
<ToastPrimitive.Close aria-label="Close" onClick={onCloseClick}>
|
||||
<span aria-hidden>×</span>
|
||||
</ToastPrimitive.Close>
|
||||
</div>
|
||||
)}
|
||||
<ToastPrimitive.Description asChild>
|
||||
<p>{content}</p>
|
||||
</ToastPrimitive.Description>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ function MyApp({ Component, pageProps }: AppProps) {
|
|||
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<ToastProvider>
|
||||
<ToastProvider swipeDirection="right">
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
|
|
|
|||
|
|
@ -368,6 +368,15 @@ i.tag {
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes openModalDesktop {
|
||||
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 {
|
||||
from {
|
||||
backdrop-filter: blur(5px) saturate(100%) brightness(80%) opacity(0);
|
||||
|
|
|
|||
Loading…
Reference in a new issue