Implement dialog and notices

This implements the PartyVisibilityDialog in PartyHeader, lets the user invoke it from PartyDropdown, and adds notices so the user knows if a party is private or unlisted.
This commit is contained in:
Justin Edmund 2023-08-25 14:48:11 -07:00
parent 6e0973d406
commit 2cf8b028d1
4 changed files with 143 additions and 1 deletions

View file

@ -46,6 +46,10 @@
flex-grow: 1;
}
&.no-shrink {
flex-shrink: 0;
}
&.blended {
background: transparent;
}
@ -304,6 +308,15 @@
}
}
&.notice {
background-color: var(--notice-button-bg);
color: var(--notice-button-text);
&:hover {
background-color: var(--notice-button-bg-hover);
}
}
&.destructive {
background: $error;
color: white;

View file

@ -1,5 +1,5 @@
// Libraries
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next'
@ -33,12 +33,14 @@ interface Props {
editable: boolean
deleteTeamCallback: () => void
remixTeamCallback: () => void
teamVisibilityCallback: () => void
}
const PartyDropdown = ({
editable,
deleteTeamCallback,
remixTeamCallback,
teamVisibilityCallback,
}: Props) => {
// Localization
const { t } = useTranslation('common')
@ -81,6 +83,11 @@ const PartyDropdown = ({
// Methods: Event handlers
// Dialogs / Visibility
function visibilityCallback() {
teamVisibilityCallback()
}
// Alerts / Delete team
function openDeleteTeamAlert() {
setDeleteAlertOpen(true)
@ -125,6 +132,9 @@ const PartyDropdown = ({
const items = (
<>
<DropdownMenuGroup>
<DropdownMenuItem onClick={visibilityCallback}>
<span>{t('dropdown.party.visibility')}</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={copyToClipboard}>
<span>{t('dropdown.party.copy')}</span>
</DropdownMenuItem>

View file

@ -18,6 +18,41 @@
}
}
.notice {
align-items: center;
background: var(--notice-bg);
border-radius: $card-corner;
display: flex;
gap: $unit-2x;
font-size: $font-regular;
justify-content: space-between;
padding: $unit-4x;
overflow: hidden;
@include breakpoint(small-tablet) {
flex-direction: column;
gap: $unit;
padding: $unit-2x;
}
p {
color: var(--notice-text);
}
.buttons {
justify-content: flex-end;
display: flex;
flex-shrink: 0;
gap: $unit;
@include breakpoint(small-tablet) {
flex-direction: column;
justify-content: center;
width: 100%;
}
}
}
.details {
box-sizing: border-box;
display: block;

View file

@ -27,6 +27,8 @@ import SaveIcon from '~public/icons/Save.svg'
import type { DetailsObject } from 'types'
import styles from './index.module.scss'
import PartyVisibilityDialog from '../PartyVisibilityDialog'
import UrlCopiedToast from '~components/toasts/UrlCopiedToast'
// Props
interface Props {
@ -50,8 +52,10 @@ const PartyHeader = (props: Props) => {
// State: Component
const [detailsOpen, setDetailsOpen] = useState(false)
const [copyToastOpen, setCopyToastOpen] = useState(false)
const [remixAlertOpen, setRemixAlertOpen] = useState(false)
const [remixToastOpen, setRemixToastOpen] = useState(false)
const [visibilityDialogOpen, setVisibilityDialogOpen] = useState(false)
const userClass = classNames({
[styles.user]: true,
@ -122,12 +126,29 @@ const PartyHeader = (props: Props) => {
setDetailsOpen(open)
}
// Dialogs: Visibility
function visibilityDialogCallback() {
setVisibilityDialogOpen(true)
}
function handleVisibilityDialogChange(open: boolean) {
setVisibilityDialogOpen(open)
}
// Actions: Remix team
function remixTeamCallback() {
setRemixToastOpen(true)
props.remixCallback()
}
// Actions: Copy URL
function copyToClipboard() {
if (router.asPath.split('/')[1] === 'p') {
navigator.clipboard.writeText(window.location.href)
setCopyToastOpen(true)
}
}
// Alerts: Remix team
function openRemixTeamAlert() {
setRemixAlertOpen(true)
@ -146,6 +167,15 @@ const PartyHeader = (props: Props) => {
setRemixToastOpen(false)
}
// Toasts / Copy URL
function handleCopyToastOpenChanged(open: boolean) {
setCopyToastOpen(!open)
}
function handleCopyToastCloseClicked() {
setCopyToastOpen(false)
}
// Rendering
const userBlock = (username?: string, picture?: string, element?: string) => {
@ -298,6 +328,44 @@ const PartyHeader = (props: Props) => {
)
}
// Render: Notice
const unlistedNotice = (
<div className={styles.notice}>
<p>{t('party.notices.unlisted')}</p>
<div className={styles.buttons}>
<Button
bound={true}
className="notice no-shrink"
key="copy_link"
text={t('party.notices.buttons.copy_link')}
onClick={copyToClipboard}
/>
<Button
bound={true}
className="notice no-shrink"
key="change_visibility"
text={t('party.notices.buttons.change_visibility')}
onClick={() => handleVisibilityDialogChange(true)}
/>
</div>
</div>
)
const privateNotice = (
<div className={styles.notice}>
<p>{t('party.notices.private')}</p>
<div className={styles.buttons}>
<Button
bound={true}
className="notice"
key="change_visibility"
text={t('party.notices.buttons.change_visibility')}
onClick={() => handleVisibilityDialogChange(true)}
/>
</div>
</div>
)
// Render: Buttons
const saveButton = () => {
return (
@ -358,6 +426,8 @@ const PartyHeader = (props: Props) => {
return (
<>
<header className={styles.wrapper}>
{party.visibility == 2 && unlistedNotice}
{party.visibility == 3 && privateNotice}
<section className={styles.info}>
<div className={styles.left}>
<div className={styles.header}>
@ -399,6 +469,7 @@ const PartyHeader = (props: Props) => {
editable={props.editable}
deleteTeamCallback={props.deleteCallback}
remixTeamCallback={props.remixCallback}
teamVisibilityCallback={visibilityDialogCallback}
/>
)}
</div>
@ -412,6 +483,13 @@ const PartyHeader = (props: Props) => {
<section className={styles.tokens}>{renderTokens()}</section>
</header>
<PartyVisibilityDialog
open={visibilityDialogOpen}
value={party.visibility as 1 | 2 | 3}
onOpenChange={handleVisibilityDialogChange}
updateParty={props.updateCallback}
/>
<RemixTeamAlert
creator={props.editable}
name={partySnapshot.name ? partySnapshot.name : t('no_title')}
@ -426,6 +504,12 @@ const PartyHeader = (props: Props) => {
onOpenChange={handleRemixToastOpenChanged}
onCloseClick={handleRemixToastCloseClicked}
/>
<UrlCopiedToast
open={copyToastOpen}
onOpenChange={handleCopyToastOpenChanged}
onCloseClick={handleCopyToastCloseClicked}
/>
</>
)
}