Skip to content

Commit

Permalink
feat: insights block WIP
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Krick <[email protected]>
  • Loading branch information
mattkrick committed Feb 21, 2025
1 parent f32c0aa commit 5c3b199
Show file tree
Hide file tree
Showing 14 changed files with 470 additions and 5 deletions.
94 changes: 94 additions & 0 deletions packages/client/components/MeetingTypePickerCombobox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {ExpandMore} from '@mui/icons-material'
import CheckIcon from '@mui/icons-material/Check'
import * as Checkbox from '@radix-ui/react-checkbox'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import type {NodeViewProps} from '@tiptap/core'
import type {MeetingTypeEnum} from '../__generated__/ExportToCSVQuery.graphql'
import type {InsightsBlockAttrs} from '../tiptap/extensions/imageBlock/InsightsBlock'
import {Menu} from '../ui/Menu/Menu'
import {MenuContent} from '../ui/Menu/MenuContent'

interface Props {
updateAttributes: NodeViewProps['updateAttributes']
attrs: InsightsBlockAttrs
}

const MeetingTypeToReadable = {
action: 'Team Check-in',
poker: 'Sprint Poker',
retrospective: 'Retrospective',
teamPrompt: 'Standup'
} satisfies Record<MeetingTypeEnum, string>

export const MeetingTypePickerCombobox = (props: Props) => {
const {updateAttributes, attrs} = props
const {meetingTypes} = attrs
const toggleSelectedTeamId = (meetingType: MeetingTypeEnum) => {
const nextTypes = meetingTypes.includes(meetingType)
? meetingTypes.filter((curMeetingType) => curMeetingType !== meetingType)
: [...meetingTypes, meetingType]
updateAttributes({meetingTypes: nextTypes})
}
const label =
meetingTypes.map((type) => MeetingTypeToReadable[type]).join(', ') || 'Meeting types...'
return (
<Menu
className='data-[side=bottom]:animate-slide-down data-[side=top]:animate-slide-up'
trigger={
<div className='group flex cursor-pointer items-center justify-between rounded-md bg-white'>
<div className='p-2 leading-4'>{label}</div>
<div className='flex items-center'>
<ExpandMore className='text-slate-600 transition-transform group-data-[state=open]:rotate-180' />
</div>
</div>
}
>
<MenuContent>
<div className='z-10 max-h-56 overflow-auto rounded-md bg-white py-1 shadow-lg outline-hidden in-data-[placement="bottom-start"]:animate-slide-down in-data-[placement="top-start"]:animate-slide-up'>
<div className='py-2'>
{Object.entries(MeetingTypeToReadable).map((entry) => {
const [meetingType, label] = entry as [MeetingTypeEnum, string]
const checked = meetingTypes.includes(meetingType)
return (
<DropdownMenu.Item
key={meetingType}
asChild
onSelect={(e) => {
e.preventDefault()
}}
onClick={() => {
toggleSelectedTeamId(meetingType)
}}
>
<div className='mx-1 flex'>
<div
data-highlighted={checked}
className={
'group flex w-full cursor-pointer items-center space-x-2 rounded-md px-3 py-2 text-sm leading-8 text-slate-700 outline-hidden hover:bg-slate-200! hover:text-slate-900 focus:bg-slate-200 data-highlighted:bg-slate-100 data-highlighted:text-slate-900'
}
>
<div className='flex size-7 items-center justify-center rounded-sm bg-slate-200 group-hover:bg-slate-300 group-data-highlighted:bg-slate-300'>
<Checkbox.Root checked={checked}>
<Checkbox.Indicator asChild>
{checked && (
<CheckIcon className='flex size-5 self-center bg-slate-300' />
)}
</Checkbox.Indicator>
</Checkbox.Root>
{/* <command.icon className='size-5' /> */}
</div>
<div className='flex flex-col text-sm select-none'>
<span>{label}</span>
{/* <span className='text-xs text-slate-600'>{command.description}</span> */}
</div>
</div>
</div>
</DropdownMenu.Item>
)
})}
</div>
</div>
</MenuContent>
</Menu>
)
}
106 changes: 106 additions & 0 deletions packages/client/components/TeamPickerCombobox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {ExpandMore} from '@mui/icons-material'
import CheckIcon from '@mui/icons-material/Check'
import * as Checkbox from '@radix-ui/react-checkbox'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import type {NodeViewProps} from '@tiptap/core'
import graphql from 'babel-plugin-relay/macro'
import {usePreloadedQuery, type PreloadedQuery} from 'react-relay'
import type {TeamPickerComboboxQuery} from '../__generated__/TeamPickerComboboxQuery.graphql'
import type {InsightsBlockAttrs} from '../tiptap/extensions/imageBlock/InsightsBlock'
import {Menu} from '../ui/Menu/Menu'
import {MenuContent} from '../ui/Menu/MenuContent'
const query = graphql`
query TeamPickerComboboxQuery {
viewer {
teams {
id
name
}
}
}
`

interface Props {
updateAttributes: NodeViewProps['updateAttributes']
attrs: InsightsBlockAttrs
queryRef: PreloadedQuery<TeamPickerComboboxQuery>
}

export const TeamPickerCombobox = (props: Props) => {
const {updateAttributes, queryRef, attrs} = props
const data = usePreloadedQuery<TeamPickerComboboxQuery>(query, queryRef)
const {viewer} = data
const {teams} = viewer
const {teamIds} = attrs
const toggleSelectedTeamId = (teamId: string) => {
const nextTeamIds = teamIds.includes(teamId)
? teamIds.filter((curTeamId) => curTeamId !== teamId)
: [...teamIds, teamId]
updateAttributes({teamIds: nextTeamIds})
}

const label =
teams
.filter((team) => teamIds.includes(team.id))
.map((team) => team.name)
.join(', ') || 'Select your teams...'
return (
<Menu
className='data-[side=bottom]:animate-slide-down data-[side=top]:animate-slide-up'
trigger={
<div className='group flex cursor-pointer items-center justify-between rounded-md bg-white'>
<div className='p-2 leading-4'>{label}</div>
<div className='flex items-center'>
<ExpandMore className='text-slate-600 transition-transform group-data-[state=open]:rotate-180' />
</div>
</div>
}
>
<MenuContent>
<div className='z-10 max-h-56 overflow-auto rounded-md bg-white py-1 shadow-lg outline-hidden in-data-[placement="bottom-start"]:animate-slide-down in-data-[placement="top-start"]:animate-slide-up'>
<div className='py-2'>
{teams.map((team) => {
const checked = teamIds.includes(team.id)
return (
<DropdownMenu.Item
key={team.id}
asChild
onSelect={(e) => {
e.preventDefault()
}}
onClick={() => {
toggleSelectedTeamId(team.id)
}}
>
<div className='mx-1 flex'>
<div
data-highlighted={checked}
className={
'group flex w-full cursor-pointer items-center space-x-2 rounded-md px-3 py-2 text-sm leading-8 text-slate-700 outline-hidden hover:bg-slate-200! hover:text-slate-900 focus:bg-slate-200 data-highlighted:bg-slate-100 data-highlighted:text-slate-900'
}
>
<div className='flex size-7 items-center justify-center rounded-sm bg-slate-200 group-hover:bg-slate-300 group-data-highlighted:bg-slate-300'>
<Checkbox.Root checked={checked}>
<Checkbox.Indicator asChild>
{checked && (
<CheckIcon className='flex size-5 self-center bg-slate-300' />
)}
</Checkbox.Indicator>
</Checkbox.Root>
{/* <command.icon className='size-5' /> */}
</div>
<div className='flex flex-col text-sm select-none'>
<span>{team.name}</span>
{/* <span className='text-xs text-slate-600'>{command.description}</span> */}
</div>
</div>
</div>
</DropdownMenu.Item>
)
})}
</div>
</div>
</MenuContent>
</Menu>
)
}
25 changes: 25 additions & 0 deletions packages/client/components/TeamPickerComboboxRoot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type {NodeViewProps} from '@tiptap/core'
import {Suspense} from 'react'
import type {TeamPickerComboboxQuery} from '../__generated__/TeamPickerComboboxQuery.graphql'
import query from '../__generated__/TeamPickerComboboxQuery.graphql'
import useQueryLoaderNow from '../hooks/useQueryLoaderNow'
import type {InsightsBlockAttrs} from '../tiptap/extensions/imageBlock/InsightsBlock'
import {Loader} from '../utils/relay/renderLoader'
import {TeamPickerCombobox} from './TeamPickerCombobox'

interface Props {
updateAttributes: NodeViewProps['updateAttributes']
attrs: InsightsBlockAttrs
}

export const TeamPickerComboboxRoot = (props: Props) => {
const {attrs, updateAttributes} = props
const queryRef = useQueryLoaderNow<TeamPickerComboboxQuery>(query)
return (
<Suspense fallback={<Loader />}>
{queryRef && (
<TeamPickerCombobox queryRef={queryRef} attrs={attrs} updateAttributes={updateAttributes} />
)}
</Suspense>
)
}
4 changes: 3 additions & 1 deletion packages/client/hooks/useTipTapPageEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {themeBackgroundColors} from '../shared/themeBackgroundColors'
import {mentionConfig, serverTipTapExtensions} from '../shared/tiptap/serverTipTapExtensions'
import {toSlug} from '../shared/toSlug'
import ImageBlock from '../tiptap/extensions/imageBlock/ImageBlock'
import {InsightsBlock} from '../tiptap/extensions/imageBlock/InsightsBlock'
import {ImageUpload} from '../tiptap/extensions/imageUpload/ImageUpload'
import {SlashCommand} from '../tiptap/extensions/slashCommand/SlashCommand'
import {ElementWidth} from '../types/constEnums'
Expand Down Expand Up @@ -150,7 +151,8 @@ export const useTipTapPageEditor = (
name: preferredName,
color: `#${themeBackgroundColors[colorIdx]}`
}
})
}),
InsightsBlock
],
autofocus: true,
editable: true
Expand Down
3 changes: 2 additions & 1 deletion packages/client/hooks/useTipTapReflectionEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export const useTipTapReflectionEditor = (
SlashCommand.configure({
'Heading 1': false,
'Heading 2': false,
'To-do list': false
'To-do list': false,
Insights: false
}),
Focus,
ImageUpload.configure({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Dayjs} from 'dayjs'
import * as React from 'react'
import {PALETTE} from '../../../../styles/paletteV3'

const customStyles = {
export const customStyles = {
width: '100%',
'& .MuiOutlinedInput-root': {
'&:hover .MuiOutlinedInput-notchedOutline, &.Mui-focused .MuiOutlinedInput-notchedOutline, &.focus-within .MuiOutlinedInput-notchedOutline':
Expand Down
1 change: 1 addition & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"@mui/x-date-pickers": "^7.23.6",
"@radix-ui/react-alert-dialog": "1.1.2",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
Expand Down
37 changes: 37 additions & 0 deletions packages/client/shared/tiptap/extensions/InsightsBlockBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {Node} from '@tiptap/react'

declare module '@tiptap/core' {
interface Commands<ReturnType> {
insightsBlock: {
setInsights: () => ReturnType
}
}
}

export const InsightsBlockBase = Node.create({
name: 'insightsBlock',

isolating: true,

defining: true,

group: 'block',

draggable: true,

selectable: true,

inline: false,

parseHTML() {
return [
{
tag: `div[data-type="${this.name}"]`
}
]
},

renderHTML() {
return ['div', {'data-type': this.name}]
}
})
4 changes: 3 additions & 1 deletion packages/client/shared/tiptap/serverTipTapExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {LoomExtension} from '../../components/promptResponse/loomExtension'
import {ImageBlockBase} from '../../tiptap/extensions/imageBlock/ImageBlockBase'
import {tiptapTagConfig} from '../../utils/tiptapTagConfig'
import {ImageUploadBase} from './extensions/ImageUploadBase'
import {InsightsBlockBase} from './extensions/InsightsBlockBase'

export const mentionConfig: Partial<MentionOptions<any, MentionNodeAttrs>> = {
renderText({node}) {
Expand Down Expand Up @@ -38,5 +39,6 @@ export const serverTipTapExtensions = [
renderHTML({HTMLAttributes}) {
return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {class: 'link'}), 0]
}
})
}),
InsightsBlockBase
]
Loading

0 comments on commit 5c3b199

Please sign in to comment.