-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
4810 display participants in the right drawer of the calendar event #4896
Merged
bosiraphael
merged 49 commits into
main
from
4810-display-participants-in-the-right-drawer-of-the-calendar-event
Apr 12, 2024
Merged
Changes from 33 commits
Commits
Show all changes
49 commits
Select commit
Hold shift + click to select a range
17cd986
Create ParticipantChip
bosiraphael 6cabfd8
create CalendarEventParticipantsResponseStatus component
bosiraphael 983e60a
modify calendarEventParticipant
bosiraphael 09e97af
augment depth in useFindOneRecord
bosiraphael 6f20834
wip
bosiraphael b13aa18
add typing
bosiraphael 1710c50
add labels and icons
bosiraphael 9b8c06c
improve styling
bosiraphael c0f4105
add chip variant
bosiraphael 472b0a8
order participants
bosiraphael 3c255d2
add a way to count the participants in view
bosiraphael 5afe8df
add right marginto the intersection observer
bosiraphael 6eb1424
use react intersection observer instead
bosiraphael 7aa3256
add root margin
bosiraphael dd9ab48
introduce IntersectionObserverWrapper
bosiraphael cb6fd97
create CalendarEventParticipantPlus
bosiraphael f7077db
improve plus button
bosiraphael 5aa8423
update root
bosiraphael 1e4b1c1
making the component generic
bosiraphael c609a91
improve component and fix bugs
bosiraphael d5fafa5
improve code
bosiraphael a329cac
show more on click
bosiraphael 9b6a2e1
update style
bosiraphael 78425ce
styling
bosiraphael 3b21d90
use dropdown
bosiraphael f867c7e
create portal
bosiraphael 1b8756c
add disableBorder prop to dropdown
bosiraphael a31c48e
make dropdown full width
bosiraphael c8b040e
add id
bosiraphael 822dbce
move components
bosiraphael 63510c3
reorder
bosiraphael 0cb3e39
Merge branch 'main' into 4810-display-participants-in-the-right-drawe…
bosiraphael 8331ba1
Merge branch 'main' into 4810-display-participants-in-the-right-drawe…
bosiraphael 5750062
Merge branch 'main' into 4810-display-participants-in-the-right-drawe…
bosiraphael a44d9a3
use theme
bosiraphael c062a63
decompose spacing
bosiraphael 02ad6f6
refactoring
bosiraphael 46fc049
remove margin prop
bosiraphael 20c3b37
add opacity logic to the intersection observer wrapper
bosiraphael 681cfd8
refactoring
bosiraphael 5cbb4f0
introduce ParticipantChipVariant
bosiraphael a5b33a8
remove portal and disableBorder
bosiraphael 3bc2f80
rename eventParticipants in calendarEventParticipants
bosiraphael 6b6f254
update style
bosiraphael 6d95c48
fix styling issues
bosiraphael c000397
fix font size
bosiraphael 3499072
move to ui
bosiraphael c5eeb94
use maxWidth
bosiraphael 82d011d
fix lint
bosiraphael File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
...nt/src/modules/activities/calendar/components/CalendarEventParticipantsResponseStatus.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import groupBy from 'lodash.groupby'; | ||
|
||
import { CalendarEventParticipantsResponseStatusField } from '@/activities/calendar/components/CalendarEventParticipantsResponseStatusField'; | ||
import { CalendarEventParticipant } from '@/activities/calendar/types/CalendarEventParticipant'; | ||
|
||
export const CalendarEventParticipantsResponseStatus = ({ | ||
participants, | ||
}: { | ||
participants: CalendarEventParticipant[]; | ||
}) => { | ||
const groupedParticipants = groupBy(participants, (participant) => { | ||
switch (participant.responseStatus) { | ||
case 'ACCEPTED': | ||
return 'Yes'; | ||
case 'DECLINED': | ||
return 'No'; | ||
case 'NEEDS_ACTION': | ||
case 'TENTATIVE': | ||
return 'Maybe'; | ||
default: | ||
return ''; | ||
} | ||
}); | ||
|
||
const responseStatusOrder: ('Yes' | 'Maybe' | 'No')[] = [ | ||
'Yes', | ||
'Maybe', | ||
'No', | ||
]; | ||
|
||
return ( | ||
<> | ||
{responseStatusOrder.map((responseStatus) => ( | ||
<CalendarEventParticipantsResponseStatusField | ||
key={responseStatus} | ||
responseStatus={responseStatus} | ||
participants={groupedParticipants[responseStatus] || []} | ||
/> | ||
))} | ||
</> | ||
); | ||
}; |
112 changes: 112 additions & 0 deletions
112
...c/modules/activities/calendar/components/CalendarEventParticipantsResponseStatusField.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { useRef } from 'react'; | ||
import { useTheme } from '@emotion/react'; | ||
import styled from '@emotion/styled'; | ||
import { IconCheck, IconQuestionMark, IconX } from 'twenty-ui'; | ||
import { v4 } from 'uuid'; | ||
|
||
import { CalendarEventParticipant } from '@/activities/calendar/types/CalendarEventParticipant'; | ||
import { ExpandableList } from '@/activities/components/ExpandableList/ExpandableList'; | ||
import { ParticipantChip } from '@/activities/components/ParticipantChip'; | ||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox'; | ||
import { EllipsisDisplay } from '@/ui/field/display/components/EllipsisDisplay'; | ||
|
||
const StyledInlineCellBaseContainer = styled.div` | ||
align-items: center; | ||
box-sizing: border-box; | ||
width: 100%; | ||
display: flex; | ||
|
||
gap: ${({ theme }) => theme.spacing(1)}; | ||
|
||
position: relative; | ||
user-select: none; | ||
`; | ||
|
||
const StyledPropertyBox = styled(PropertyBox)` | ||
height: ${({ theme }) => theme.spacing(6)}; | ||
padding: 0; | ||
`; | ||
|
||
const StyledIconContainer = styled.div` | ||
align-items: center; | ||
color: ${({ theme }) => theme.font.color.tertiary}; | ||
display: flex; | ||
width: 16px; | ||
|
||
svg { | ||
align-items: center; | ||
display: flex; | ||
height: 16px; | ||
justify-content: center; | ||
width: 16px; | ||
} | ||
`; | ||
|
||
const StyledLabelAndIconContainer = styled.div` | ||
align-items: center; | ||
color: ${({ theme }) => theme.font.color.tertiary}; | ||
display: flex; | ||
gap: ${({ theme }) => theme.spacing(1)}; | ||
`; | ||
|
||
const StyledLabelContainer = styled.div<{ width?: number }>` | ||
color: ${({ theme }) => theme.font.color.tertiary}; | ||
font-size: ${({ theme }) => theme.font.size.sm}; | ||
width: ${({ width }) => width}px; | ||
`; | ||
|
||
const StyledParticipantChip = styled(ParticipantChip)<{ inView?: boolean }>` | ||
opacity: ${({ inView }) => (inView === undefined || inView ? 1 : 0)}; | ||
`; | ||
|
||
export const CalendarEventParticipantsResponseStatusField = ({ | ||
responseStatus, | ||
participants, | ||
}: { | ||
responseStatus: 'Yes' | 'Maybe' | 'No'; | ||
participants: CalendarEventParticipant[]; | ||
}) => { | ||
const theme = useTheme(); | ||
|
||
const Icon = { | ||
Yes: <IconCheck stroke={theme.icon.stroke.sm} />, | ||
Maybe: <IconQuestionMark stroke={theme.icon.stroke.sm} />, | ||
No: <IconX stroke={theme.icon.stroke.sm} />, | ||
}[responseStatus]; | ||
|
||
// We want to display external participants first | ||
const orderedParticipants = [ | ||
...participants.filter((participant) => participant.person), | ||
...participants.filter( | ||
(participant) => !participant.person && !participant.workspaceMember, | ||
), | ||
...participants.filter((participant) => participant.workspaceMember), | ||
]; | ||
|
||
const participantsContainerRef = useRef<HTMLDivElement>(null); | ||
|
||
const StyledChips = orderedParticipants.map((participant) => ( | ||
<StyledParticipantChip participant={participant} /> | ||
)); | ||
|
||
return ( | ||
<StyledPropertyBox> | ||
<StyledInlineCellBaseContainer> | ||
<StyledLabelAndIconContainer> | ||
<StyledIconContainer>{Icon}</StyledIconContainer> | ||
|
||
<StyledLabelContainer width={72}> | ||
<EllipsisDisplay>{responseStatus}</EllipsisDisplay> | ||
</StyledLabelContainer> | ||
</StyledLabelAndIconContainer> | ||
|
||
<ExpandableList | ||
components={StyledChips} | ||
id={v4()} | ||
rootRef={participantsContainerRef} | ||
margin="0px -50px 0px 0px" | ||
/> | ||
</StyledInlineCellBaseContainer> | ||
</StyledPropertyBox> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
packages/twenty-front/src/modules/activities/calendar/types/CalendarEventParticipant.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Person } from '@/people/types/Person'; | ||
import { WorkspaceMember } from '~/generated-metadata/graphql'; | ||
|
||
export type CalendarEventParticipant = { | ||
id: string; | ||
handle: string; | ||
isOrganizer: boolean; | ||
displayName: string; | ||
person?: Person; | ||
workspaceMember?: WorkspaceMember; | ||
responseStatus: 'ACCEPTED' | 'DECLINED' | 'NEEDS_ACTION' | 'TENTATIVE'; | ||
}; |
109 changes: 109 additions & 0 deletions
109
packages/twenty-front/src/modules/activities/components/ExpandableList/ExpandableList.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import React, { ReactElement, useState } from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import styled from '@emotion/styled'; | ||
|
||
import { IntersectionObserverWrapper } from '@/activities/components/ExpandableList/IntersectionObserverWrapper'; | ||
import { Chip, ChipVariant } from '@/ui/display/chip/components/Chip'; | ||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; | ||
|
||
const StyledContainer = styled.div` | ||
align-items: center; | ||
display: flex; | ||
gap: ${({ theme }) => theme.spacing(1)}; | ||
position: relative; | ||
box-sizing: border-box; | ||
`; | ||
|
||
const StyledDiv = styled.div` | ||
align-items: center; | ||
display: flex; | ||
gap: ${({ theme }) => theme.spacing(1)}; | ||
position: relative; | ||
white-space: nowrap; | ||
max-width: 100%; | ||
width: 100%; | ||
box-sizing: border-box; | ||
overflow: hidden; | ||
`; | ||
|
||
const StyledExpendableCell = styled.div` | ||
display: flex; | ||
align-items: center; | ||
align-content: center; | ||
flex-wrap: wrap; | ||
gap: ${({ theme }) => theme.spacing(1)}; | ||
position: absolute; | ||
top: ${({ theme }) => `-${theme.spacing(2.25)}`}; | ||
bosiraphael marked this conversation as resolved.
Show resolved
Hide resolved
|
||
left: ${({ theme }) => `-${theme.spacing(2.25)}`}; | ||
width: 232px; | ||
z-index: 1; | ||
box-sizing: border-box; | ||
background: ${({ theme }) => theme.background.secondary}; | ||
padding: ${({ theme }) => theme.spacing(2)}; | ||
box-shadow: ${({ theme }) => theme.boxShadow.light}; | ||
backdrop-filter: blur(20px); | ||
border-radius: 4px; | ||
bosiraphael marked this conversation as resolved.
Show resolved
Hide resolved
|
||
border: 1px solid ${({ theme }) => theme.border.color.medium}; | ||
`; | ||
|
||
export const ExpandableList = ({ | ||
components, | ||
rootRef, | ||
id, | ||
margin, | ||
}: { | ||
components: ReactElement[]; | ||
bosiraphael marked this conversation as resolved.
Show resolved
Hide resolved
|
||
rootRef: React.RefObject<HTMLElement>; | ||
id: string; | ||
margin?: string; | ||
bosiraphael marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) => { | ||
const [componentsInView, setComponentsInView] = useState(new Set<number>()); | ||
|
||
const firstComponent = components[0]; | ||
|
||
const dropdownId = `expandable-list-dropdown-${id}`; | ||
|
||
const containerRef = React.useRef<HTMLDivElement>(null); | ||
|
||
return ( | ||
<StyledContainer ref={containerRef}> | ||
<StyledDiv> | ||
{firstComponent} | ||
{components.slice(1).map((component, index) => ( | ||
<React.Fragment key={index}> | ||
<IntersectionObserverWrapper | ||
set={setComponentsInView} | ||
id={index} | ||
rootRef={rootRef} | ||
margin={margin} | ||
> | ||
{component} | ||
</IntersectionObserverWrapper> | ||
{index === componentsInView.size - 1 && | ||
components.length - componentsInView.size - 1 !== 0 && ( | ||
<Dropdown | ||
dropdownId={dropdownId} | ||
dropdownHotkeyScope={{ | ||
scope: dropdownId, | ||
}} | ||
clickableComponent={ | ||
<Chip | ||
label={`+${ | ||
components.length - componentsInView.size - 1 | ||
}`} | ||
variant={ChipVariant.Highlighted} | ||
bosiraphael marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/> | ||
} | ||
dropdownComponents={ReactDOM.createPortal( | ||
<StyledExpendableCell>{components}</StyledExpendableCell>, | ||
containerRef.current as HTMLDivElement, | ||
)} | ||
disableBorder | ||
/> | ||
)} | ||
</React.Fragment> | ||
))} | ||
</StyledDiv> | ||
</StyledContainer> | ||
); | ||
}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move it to ui or nobody will find it! We are missing a story on this component too :)