Skip to content

Commit

Permalink
feat: switch template UI (#9093)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickoferrall authored Mar 25, 2024
1 parent fe71841 commit 2171065
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 7 deletions.
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 if no reflections have been added.'}
</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
108 changes: 108 additions & 0 deletions packages/client/components/RetroDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import {Close} from '@mui/icons-material'
import graphql from 'babel-plugin-relay/macro'
import React, {useEffect} 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)
}

useEffect(() => {
if (hasReflections && showDrawer) {
setShowDrawer(false)
}
}, [hasReflections])

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
55 changes: 55 additions & 0 deletions packages/client/components/RetroDrawerTemplateCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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
category='retrospective'
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
32 changes: 32 additions & 0 deletions packages/client/ui/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import React from 'react'
import {twMerge} from 'tailwind-merge'

interface MenuProps {
trigger: React.ReactNode
className?: string
children: React.ReactNode
}

export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(
({trigger, className, children}, ref) => {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>{trigger}</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
align='end'
className={twMerge(
'border-rad w-auto min-w-[200px] max-w-[400px] rounded-md bg-white shadow-lg outline-none',
className
)}
sideOffset={10}
ref={ref}
>
{children}
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
)
}
)
Loading

0 comments on commit 2171065

Please sign in to comment.