Prettier tooltips

This commit is contained in:
Ben Grant 2021-05-13 21:03:07 +10:00
parent 3b86432c73
commit ab7824aff7
2 changed files with 118 additions and 83 deletions

View file

@ -1,4 +1,4 @@
import { useState, useEffect, Fragment } from 'react'; import { useState, useEffect, useRef, Fragment } from 'react';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import localeData from 'dayjs/plugin/localeData'; import localeData from 'dayjs/plugin/localeData';
import customParseFormat from 'dayjs/plugin/customParseFormat'; import customParseFormat from 'dayjs/plugin/customParseFormat';
@ -8,6 +8,7 @@ import { useSettingsStore } from 'stores';
import { Legend, Center } from 'components'; import { Legend, Center } from 'components';
import { import {
Wrapper, Wrapper,
ScrollWrapper,
Container, Container,
Date, Date,
Times, Times,
@ -19,6 +20,7 @@ import {
TooltipTitle, TooltipTitle,
TooltipDate, TooltipDate,
TooltipContent, TooltipContent,
TooltipPerson,
TimeLabels, TimeLabels,
TimeLabel, TimeLabel,
TimeSpace, TimeSpace,
@ -47,6 +49,8 @@ const AvailabilityViewer = ({
const [tempFocus, setTempFocus] = useState(null); const [tempFocus, setTempFocus] = useState(null);
const [focusCount, setFocusCount] = useState(null); const [focusCount, setFocusCount] = useState(null);
const wrapper = useRef();
useEffect(() => { useEffect(() => {
setFilteredPeople(people.map(p => p.name)); setFilteredPeople(people.map(p => p.name));
setTouched(people.length <= 1); setTouched(people.length <= 1);
@ -92,82 +96,94 @@ const AvailabilityViewer = ({
)} )}
</StyledMain> </StyledMain>
<Wrapper> <Wrapper ref={wrapper}>
<Container> <ScrollWrapper>
<TimeLabels> <Container>
{!!timeLabels.length && timeLabels.map((label, i) => <TimeLabels>
<TimeSpace key={i}> {!!timeLabels.length && timeLabels.map((label, i) =>
{label.label?.length !== '' && <TimeLabel>{label.label}</TimeLabel>} <TimeSpace key={i}>
</TimeSpace> {label.label?.length !== '' && <TimeLabel>{label.label}</TimeLabel>}
)} </TimeSpace>
</TimeLabels> )}
{dates.map((date, i) => { </TimeLabels>
const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date); {dates.map((date, i) => {
const last = dates.length === i+1 || (isSpecificDates ? dayjs(dates[i+1], 'DDMMYYYY') : dayjs().day(dates[i+1])).diff(parsedDate, 'day') > 1; const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date);
return ( const last = dates.length === i+1 || (isSpecificDates ? dayjs(dates[i+1], 'DDMMYYYY') : dayjs().day(dates[i+1])).diff(parsedDate, 'day') > 1;
<Fragment key={i}> return (
<Date> <Fragment key={i}>
{isSpecificDates && <DateLabel>{parsedDate.format('MMM D')}</DateLabel>} <Date>
<DayLabel>{parsedDate.format('ddd')}</DayLabel> {isSpecificDates && <DateLabel>{parsedDate.format('MMM D')}</DateLabel>}
<DayLabel>{parsedDate.format('ddd')}</DayLabel>
<Times> <Times>
{timeLabels.map((timeLabel, i) => { {timeLabels.map((timeLabel, i) => {
if (!timeLabel.time) return null; if (!timeLabel.time) return null;
if (!times.includes(`${timeLabel.time}-${date}`)) { if (!times.includes(`${timeLabel.time}-${date}`)) {
return ( return (
<TimeSpace key={i} /> <TimeSpace key={i} />
); );
} }
const time = `${timeLabel.time}-${date}`; const time = `${timeLabel.time}-${date}`;
const peopleHere = tempFocus !== null const peopleHere = tempFocus !== null
? people.filter(person => person.availability.includes(time) && tempFocus === person.name).map(person => person.name) ? people.filter(person => person.availability.includes(time) && tempFocus === person.name).map(person => person.name)
: people.filter(person => person.availability.includes(time) && filteredPeople.includes(person.name)).map(person => person.name); : people.filter(person => person.availability.includes(time) && filteredPeople.includes(person.name)).map(person => person.name);
return ( return (
<Time <Time
key={i} key={i}
time={time} time={time}
className="time" className="time"
peopleCount={focusCount !== null && focusCount !== peopleHere.length ? 0 : peopleHere.length} peopleCount={focusCount !== null && focusCount !== peopleHere.length ? 0 : peopleHere.length}
aria-label={peopleHere.join(', ')} aria-label={peopleHere.join(', ')}
maxPeople={tempFocus !== null ? 1 : Math.min(max, filteredPeople.length)} maxPeople={tempFocus !== null ? 1 : Math.min(max, filteredPeople.length)}
minPeople={tempFocus !== null ? 0 : Math.min(min, filteredPeople.length)} minPeople={tempFocus !== null ? 0 : Math.min(min, filteredPeople.length)}
onMouseEnter={(e) => { onMouseEnter={(e) => {
const cellBox = e.currentTarget.getBoundingClientRect(); const cellBox = e.currentTarget.getBoundingClientRect();
const timeText = timeFormat === '12h' ? 'h:mma' : 'HH:mm'; const wrapperBox = wrapper?.current?.getBoundingClientRect() ?? { x: 0, y: 0 };
setTooltip({ const timeText = timeFormat === '12h' ? 'h:mma' : 'HH:mm';
x: Math.round(cellBox.x + cellBox.width/2), setTooltip({
y: Math.round(cellBox.y + cellBox.height)+6, x: Math.round(cellBox.x-wrapperBox.x + cellBox.width/2),
available: `${peopleHere.length} / ${people.length} available`, y: Math.round(cellBox.y-wrapperBox.y + cellBox.height)+6,
date: parsedDate.hour(time.slice(0, 2)).minute(time.slice(2, 4)).format(isSpecificDates ? `${timeText} ddd, D MMM YYYY` : `${timeText} ddd`), available: `${peopleHere.length} / ${people.length} available`,
people: peopleHere.join(', '), date: parsedDate.hour(time.slice(0, 2)).minute(time.slice(2, 4)).format(isSpecificDates ? `${timeText} ddd, D MMM YYYY` : `${timeText} ddd`),
}); people: peopleHere,
}} });
onMouseLeave={() => { }}
setTooltip(null); onMouseLeave={() => {
}} setTooltip(null);
/> }}
); />
})} );
</Times> })}
</Date> </Times>
{last && dates.length !== i+1 && ( </Date>
<Spacer /> {last && dates.length !== i+1 && (
)} <Spacer />
</Fragment> )}
); </Fragment>
})} );
</Container> })}
{tooltip && ( </Container>
<Tooltip {tooltip && (
x={tooltip.x} <Tooltip
y={tooltip.y} x={tooltip.x}
> y={tooltip.y}
<TooltipTitle>{tooltip.available}</TooltipTitle> >
<TooltipDate>{tooltip.date}</TooltipDate> <TooltipTitle>{tooltip.available}</TooltipTitle>
<TooltipContent>{tooltip.people}</TooltipContent> <TooltipDate>{tooltip.date}</TooltipDate>
</Tooltip> {!!filteredPeople.length && (
)} <TooltipContent>
{tooltip.people.map(person =>
<TooltipPerson key={person}>{person}</TooltipPerson>
)}
{filteredPeople.filter(p => !tooltip.people.includes(p)).map(person =>
<TooltipPerson key={person} disabled>{person}</TooltipPerson>
)}
</TooltipContent>
)}
</Tooltip>
)}
</ScrollWrapper>
</Wrapper> </Wrapper>
</> </>
); );

View file

@ -1,8 +1,13 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
export const Wrapper = styled.div` export const Wrapper = styled.div`
overflow-x: auto; overflow-y: visible;
margin: 20px 0; margin: 20px 0;
position: relative;
`;
export const ScrollWrapper = styled.div`
overflow-x: auto;
`; `;
export const Container = styled.div` export const Container = styled.div`
@ -62,8 +67,8 @@ export const Time = styled.div`
`} `}
background-image: linear-gradient( background-image: linear-gradient(
${props => `${props.theme.primary}${Math.round(((props.peopleCount)/(props.maxPeople))*255).toString(16)}`}, ${props => `${props.theme.primary}${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`},
${props => `${props.theme.primary}${Math.round(((props.peopleCount)/(props.maxPeople))*255).toString(16)}`} ${props => `${props.theme.primary}${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`}
); );
`; `;
@ -73,7 +78,7 @@ export const Spacer = styled.div`
`; `;
export const Tooltip = styled.div` export const Tooltip = styled.div`
position: fixed; position: absolute;
top: ${props => props.y}px; top: ${props => props.y}px;
left: ${props => props.x}px; left: ${props => props.x}px;
transform: translateX(-50%); transform: translateX(-50%);
@ -83,6 +88,7 @@ export const Tooltip = styled.div`
background-color: ${props => props.theme.background}DD; background-color: ${props => props.theme.background}DD;
max-width: 200px; max-width: 200px;
pointer-events: none; pointer-events: none;
z-index: 100;
`; `;
export const TooltipTitle = styled.span` export const TooltipTitle = styled.span`
@ -94,13 +100,26 @@ export const TooltipTitle = styled.span`
export const TooltipDate = styled.span` export const TooltipDate = styled.span`
font-size: 13px; font-size: 13px;
display: block; display: block;
opacity: .7; opacity: .8;
font-weight: 700; font-weight: 600;
`; `;
export const TooltipContent = styled.span` export const TooltipContent = styled.div`
font-size: 13px; font-size: 13px;
display: block; padding: 4px 0;
`;
export const TooltipPerson = styled.span`
display: inline-block;
margin: 2px;
padding: 1px 4px;
border: 1px solid ${props => props.theme.primary};
border-radius: 3px;
${props => props.disabled && `
opacity: .5;
border-color: ${props.theme.text}
`}
`; `;
export const TimeLabels = styled.div` export const TimeLabels = styled.div`