Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: switch template UI #9093

Merged
merged 22 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions packages/client/components/MeetingOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, {useState} from 'react'
import IconLabel from './IconLabel'
import {Menu} from '../ui/Menu/Menu'
import {MenuItem} from '../ui/Menu/MenuItem'
import SwapHorizIcon from '@mui/icons-material/SwapHoriz'
import {OptionsButton} from './TeamPrompt/TeamPromptOptions'
import {Tooltip} from '../ui/Tooltip/Tooltip'
import {TooltipTrigger} from '../ui/Tooltip/TooltipTrigger'
import {TooltipContent} from '../ui/Tooltip/TooltipContent'

type Props = {
setShowDrawer: (showDrawer: boolean) => void
showDrawer: boolean
hasReflections: boolean
}

const MeetingOptions = (props: Props) => {
const {setShowDrawer, showDrawer, hasReflections} = props
const [isOpen, setIsOpen] = useState(false)

const handleClick = () => {
if (hasReflections) return
setShowDrawer(!showDrawer)
}

const handleMouseEnter = () => {
if (hasReflections) {
setIsOpen(true)
}
}

const handleMouseLeave = () => {
setIsOpen(false)
}

return (
<Menu
trigger={
<OptionsButton>
<IconLabel icon='tune' iconLarge />
<div className='text-slate-700'>Options</div>
</OptionsButton>
}
>
<Tooltip open={isOpen}>
<div onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<TooltipTrigger asChild>
<MenuItem onClick={handleClick} isDisabled={hasReflections}>
<div className='mr-3 flex text-slate-700'>{<SwapHorizIcon />}</div>
Change template
</MenuItem>
</TooltipTrigger>
</div>
<TooltipContent>
{'You can only change the template before reflections have been added.'}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 I would rephrase this slightly to "You can only change the template if no reflections have been added." because it also works if you remove them again.

</TooltipContent>
</Tooltip>
</Menu>
)
}

export default MeetingOptions
17 changes: 15 additions & 2 deletions packages/client/components/MeetingTopBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from '@emotion/styled'
import {Comment} from '@mui/icons-material'
import React, {ReactElement, ReactNode} from 'react'
import React, {ReactElement, ReactNode, useState} from 'react'
import {PALETTE} from '~/styles/paletteV3'
import {meetingAvatarMediaQueries} from '../styles/meeting'
import hasToken from '../utils/hasToken'
Expand All @@ -9,6 +9,7 @@ import makeMinWidthMediaQuery from '../utils/makeMinWidthMediaQuery'
import DemoCreateAccountButton from './DemoCreateAccountButton'
import PlainButton from './PlainButton/PlainButton'
import SidebarToggle from './SidebarToggle'
import RetroDrawerRoot from './RetroDrawerRoot'

const localHeaderBreakpoint = makeMinWidthMediaQuery(600)

Expand Down Expand Up @@ -148,6 +149,7 @@ interface Props {
isRightDrawerOpen?: boolean
toggleSidebar: () => void
toggleDrawer?: () => void
meetingId?: string
}

const MeetingTopBar = (props: Props) => {
Expand All @@ -158,10 +160,14 @@ const MeetingTopBar = (props: Props) => {
isMeetingSidebarCollapsed,
isRightDrawerOpen,
toggleDrawer,
toggleSidebar
toggleSidebar,
meetingId
} = props
const showButton = isDemoRoute() && !hasToken()
const showDiscussionButton = toggleDrawer && !isRightDrawerOpen
const [showDrawer, setShowDrawer] = useState(false)
const isOptionsVisible = !!meetingId && !isDemoRoute()

return (
<MeetingTopBarStyles>
<HeadingBlock isMeetingSidebarCollapsed={isMeetingSidebarCollapsed}>
Expand All @@ -177,6 +183,13 @@ const MeetingTopBar = (props: Props) => {
</PrimaryActionBlock>
)}
{avatarGroup}
{isOptionsVisible && (
<RetroDrawerRoot
meetingId={meetingId}
setShowDrawer={setShowDrawer}
showDrawer={showDrawer}
/>
)}
{showDiscussionButton && toggleDrawer && (
<ButtonContainer>
<Badge>
Expand Down
102 changes: 102 additions & 0 deletions packages/client/components/RetroDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {Close} from '@mui/icons-material'
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import {PreloadedQuery, usePreloadedQuery} from 'react-relay'
import {Breakpoint, DiscussionThreadEnum} from '../types/constEnums'
import ResponsiveDashSidebar from './ResponsiveDashSidebar'
import MeetingOptions from './MeetingOptions'
import RetroDrawerTemplateCard from './RetroDrawerTemplateCard'
import {Drawer} from './TeamPrompt/TeamPromptDrawer'
import {RetroDrawerQuery} from '../__generated__/RetroDrawerQuery.graphql'
import useBreakpoint from '../hooks/useBreakpoint'

interface Props {
setShowDrawer: (showDrawer: boolean) => void
showDrawer: boolean
queryRef: PreloadedQuery<RetroDrawerQuery>
}

const RetroDrawer = (props: Props) => {
const {queryRef, showDrawer, setShowDrawer} = props
const {viewer} = usePreloadedQuery<RetroDrawerQuery>(
graphql`
query RetroDrawerQuery($first: Int!, $type: MeetingTypeEnum!, $meetingId: ID!) {
viewer {
meeting(meetingId: $meetingId) {
... on RetrospectiveMeeting {
reflectionGroups {
id
}
localPhase {
... on ReflectPhase {
phaseType
}
}
}
}
availableTemplates(first: $first, type: $type)
@connection(key: "RetroDrawer_availableTemplates") {
edges {
node {
...RetroDrawerTemplateCard_template
id
}
}
}
}
}
`,
queryRef
)

const templates = viewer.availableTemplates.edges
const meeting = viewer.meeting
const hasReflections = !!(meeting?.reflectionGroups && meeting.reflectionGroups.length > 0)
const phaseType = meeting?.localPhase?.phaseType
const isMobile = !useBreakpoint(Breakpoint.FUZZY_TABLET)
const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT)

const toggleDrawer = () => {
setShowDrawer(!showDrawer)
}

if (!phaseType || phaseType !== 'reflect') return null
return (
<>
<MeetingOptions
hasReflections={hasReflections}
showDrawer={showDrawer}
setShowDrawer={setShowDrawer}
/>
<ResponsiveDashSidebar
isOpen={showDrawer}
isRightDrawer
onToggle={toggleDrawer}
sidebarWidth={DiscussionThreadEnum.WIDTH}
>
<Drawer
className='overflow-scroll'
isDesktop={isDesktop}
isMobile={isMobile}
isOpen={showDrawer}
>
<div className='py-4'>
<div className='flex justify-between px-4'>
<div className='pb-4 text-base font-semibold'>Templates</div>
<div
className='cursor-pointer text-slate-600 hover:opacity-50'
onClick={toggleDrawer}
>
<Close />
</div>
</div>
{templates.map((template) => (
<RetroDrawerTemplateCard key={template.node.id} templateRef={template.node} />
))}
</div>
</Drawer>
</ResponsiveDashSidebar>
</>
)
}
export default RetroDrawer
28 changes: 28 additions & 0 deletions packages/client/components/RetroDrawerRoot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, {Suspense} from 'react'
import useQueryLoaderNow from '../hooks/useQueryLoaderNow'
import retroDrawerQuery, {RetroDrawerQuery} from '../__generated__/RetroDrawerQuery.graphql'
import RetroDrawer from './RetroDrawer'

type Props = {
showDrawer: boolean
setShowDrawer: (showDrawer: boolean) => void
meetingId: string
}

const RetroDrawerRoot = (props: Props) => {
const {showDrawer, setShowDrawer, meetingId} = props
const queryRef = useQueryLoaderNow<RetroDrawerQuery>(retroDrawerQuery, {
first: 200,
type: 'retrospective',
meetingId
})
return (
<Suspense fallback={''}>
{queryRef && (
<RetroDrawer showDrawer={showDrawer} setShowDrawer={setShowDrawer} queryRef={queryRef} />
)}
</Suspense>
)
}

export default RetroDrawerRoot
51 changes: 51 additions & 0 deletions packages/client/components/RetroDrawerTemplateCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {ActivityBadge} from './ActivityLibrary/ActivityBadge'
import {ActivityLibraryCardDescription} from './ActivityLibrary/ActivityLibraryCardDescription'
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import {useFragment} from 'react-relay'
import {ActivityLibraryCard} from './ActivityLibrary/ActivityLibraryCard'
import {ActivityCardImage} from './ActivityLibrary/ActivityCard'
import {RetroDrawerTemplateCard_template$key} from '~/__generated__/RetroDrawerTemplateCard_template.graphql'
import {CategoryID, CATEGORY_THEMES} from '././ActivityLibrary/Categories'

interface Props {
templateRef: RetroDrawerTemplateCard_template$key
}

const RetroDrawerTemplateCard = (props: Props) => {
const {templateRef} = props
const template = useFragment(
graphql`
fragment RetroDrawerTemplateCard_template on MeetingTemplate {
...ActivityLibraryCardDescription_template
name
category
illustrationUrl
isFree
}
`,
templateRef
)

return (
<div className='px-4 py-2'>
<ActivityLibraryCard
className='group aspect-[256/160] flex-1 hover:cursor-pointer'
theme={CATEGORY_THEMES[template.category as CategoryID]}
title={template.name}
badge={
!template.isFree ? (
<ActivityBadge className='m-2 bg-gold-300 text-grape-700'>Premium</ActivityBadge>
) : null
}
>
<ActivityCardImage className='group-hover/card:hidden' src={template.illustrationUrl} />
<ActivityLibraryCardDescription
className='hidden group-hover/card:flex'
templateRef={template}
/>
</ActivityLibraryCard>
</div>
)
}
export default RetroDrawerTemplateCard
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const RetroReflectPhase = (props: Props) => {
...StageTimerDisplay_meeting
...StageTimerControl_meeting
...PhaseItemColumn_meeting
id
endedAt
localPhase {
...RetroReflectPhase_phase @relay(mask: false)
Expand Down Expand Up @@ -59,6 +60,7 @@ const RetroReflectPhase = (props: Props) => {
<MeetingContent ref={callbackRef}>
<MeetingHeaderAndPhase hideBottomBar={!!endedAt}>
<MeetingTopBar
meetingId={meeting.id}
avatarGroup={avatarGroup}
isMeetingSidebarCollapsed={!showSidebar}
toggleSidebar={toggleSidebar}
Expand Down
2 changes: 1 addition & 1 deletion packages/client/components/TeamPrompt/TeamPromptDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import useBreakpoint from '../../hooks/useBreakpoint'
import findStageById from '../../utils/meetings/findStageById'
import SendClientSideEvent from '../../utils/SendClientSideEvent'

const Drawer = styled('div')<{isDesktop: boolean; isMobile: boolean; isOpen: boolean}>(
export const Drawer = styled('div')<{isDesktop: boolean; isMobile: boolean; isOpen: boolean}>(
({isDesktop, isMobile, isOpen}) => ({
boxShadow: isDesktop ? desktopSidebarShadow : undefined,
backgroundColor: '#FFFFFF',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import TeamPromptOptionsMenu from './TeamPromptOptionsMenu'

const COPIED_TOOLTIP_DURATION_MS = 2000

const OptionsButton = styled(BaseButton)({
export const OptionsButton = styled(BaseButton)({
color: PALETTE.SKY_500,
display: 'flex',
flexDirection: 'column',
Expand Down
25 changes: 25 additions & 0 deletions packages/client/ui/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import React from 'react'
import {twMerge} from 'tailwind-merge'

interface MenuProps {
trigger: React.ReactNode
children: React.ReactNode
}

export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(({trigger, children}, ref) => {
const contentClass = twMerge(
nickoferrall marked this conversation as resolved.
Show resolved Hide resolved
'border-rad w-auto min-w-[200px] max-w-[400px] rounded-md bg-white shadow-lg outline-none'
)

return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>{trigger}</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content align='end' className={contentClass} sideOffset={10} ref={ref}>
{children}
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
)
})
24 changes: 24 additions & 0 deletions packages/client/ui/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import React from 'react'
import {twMerge} from 'tailwind-merge'

interface MenuItemProps {
onClick: (event: Event) => void
isDisabled?: boolean
children: React.ReactNode
}

export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
({onClick, isDisabled, children}, ref) => {
const itemClass = twMerge(
'flex w-full items-center rounded-md px-4 py-3 text-sm text-slate-700 outline-none hover:bg-slate-100 focus:bg-slate-100',
isDisabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
)

return (
<DropdownMenu.Item className={itemClass} onSelect={onClick} ref={ref}>
{children}
</DropdownMenu.Item>
)
}
)
2 changes: 1 addition & 1 deletion packages/client/ui/Tooltip/TooltipContent.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Content, Portal} from '@radix-ui/react-tooltip'
import * as React from 'react'
import {twMerge} from 'tailwind-merge'
import {forwardRadix} from '../fordwardRadix'
import {forwardRadix} from '../forwardRadix'

export const TooltipContent = forwardRadix<typeof Content>(
({className, children, ...props}, ref) => (
Expand Down
Loading
Loading