Rounder corners, thicker borders, highlight highest availability
This commit is contained in:
parent
8b24b2e27a
commit
d2e5bcc4cb
|
|
@ -87,7 +87,10 @@ const AvailabilityEditor = ({
|
||||||
{isSpecificDates && <DateLabel>{parsedDate.format('MMM D')}</DateLabel>}
|
{isSpecificDates && <DateLabel>{parsedDate.format('MMM D')}</DateLabel>}
|
||||||
<DayLabel>{parsedDate.format('ddd')}</DayLabel>
|
<DayLabel>{parsedDate.format('ddd')}</DayLabel>
|
||||||
|
|
||||||
<Times>
|
<Times
|
||||||
|
borderRight={last}
|
||||||
|
borderLeft={x === 0 || (parsedDate).diff(isSpecificDates ? dayjs(dates[x-1], 'DDMMYYYY') : dayjs().day(dates[x-1]), 'day') > 1}
|
||||||
|
>
|
||||||
{timeLabels.map((timeLabel, y) => {
|
{timeLabels.map((timeLabel, y) => {
|
||||||
if (!timeLabel.time) return null;
|
if (!timeLabel.time) return null;
|
||||||
if (!times.includes(`${timeLabel.time}-${date}`)) {
|
if (!times.includes(`${timeLabel.time}-${date}`)) {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ const AvailabilityViewer = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [tooltip, setTooltip] = useState(null);
|
const [tooltip, setTooltip] = useState(null);
|
||||||
const timeFormat = useSettingsStore(state => state.timeFormat);
|
const timeFormat = useSettingsStore(state => state.timeFormat);
|
||||||
|
const highlight = useSettingsStore(state => state.highlight);
|
||||||
const [filteredPeople, setFilteredPeople] = useState([]);
|
const [filteredPeople, setFilteredPeople] = useState([]);
|
||||||
const [touched, setTouched] = useState(false);
|
const [touched, setTouched] = useState(false);
|
||||||
const [tempFocus, setTempFocus] = useState(null);
|
const [tempFocus, setTempFocus] = useState(null);
|
||||||
|
|
@ -118,7 +119,10 @@ const AvailabilityViewer = ({
|
||||||
{isSpecificDates && <DateLabel>{parsedDate.format('MMM D')}</DateLabel>}
|
{isSpecificDates && <DateLabel>{parsedDate.format('MMM D')}</DateLabel>}
|
||||||
<DayLabel>{parsedDate.format('ddd')}</DayLabel>
|
<DayLabel>{parsedDate.format('ddd')}</DayLabel>
|
||||||
|
|
||||||
<Times>
|
<Times
|
||||||
|
borderRight={last}
|
||||||
|
borderLeft={i === 0 || (parsedDate).diff(isSpecificDates ? dayjs(dates[i-1], 'DDMMYYYY') : dayjs().day(dates[i-1]), 'day') > 1}
|
||||||
|
>
|
||||||
{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}`)) {
|
||||||
|
|
@ -140,6 +144,7 @@ const AvailabilityViewer = ({
|
||||||
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)}
|
||||||
|
highlight={highlight}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
const cellBox = e.currentTarget.getBoundingClientRect();
|
const cellBox = e.currentTarget.getBoundingClientRect();
|
||||||
const wrapperBox = wrapper?.current?.getBoundingClientRect() ?? { x: 0, y: 0 };
|
const wrapperBox = wrapper?.current?.getBoundingClientRect() ?? { x: 0, y: 0 };
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,19 @@ export const Times = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: ${props => props.theme.text};
|
background-color: ${props => props.theme.text};
|
||||||
|
border-top: 1px solid transparent;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
|
||||||
|
${props => props.borderLeft && `
|
||||||
|
border-left: 1px solid transparent;
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
border-bottom-left-radius: 3px;
|
||||||
|
`}
|
||||||
|
${props => props.borderRight && `
|
||||||
|
border-right: 1px solid transparent;
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
border-bottom-right-radius: 3px;
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const DateLabel = styled.label`
|
export const DateLabel = styled.label`
|
||||||
|
|
@ -56,6 +69,7 @@ export const Time = styled.div`
|
||||||
height: 10px;
|
height: 10px;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
background-color: ${props => props.theme.background};
|
background-color: ${props => props.theme.background};
|
||||||
|
background-origin: border-box;
|
||||||
|
|
||||||
${props => props.time.slice(2, 4) !== '00' && `
|
${props => props.time.slice(2, 4) !== '00' && `
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
|
|
@ -66,10 +80,20 @@ export const Time = styled.div`
|
||||||
border-top: 2px dotted ${props.theme.text};
|
border-top: 2px dotted ${props.theme.text};
|
||||||
`}
|
`}
|
||||||
|
|
||||||
background-image: linear-gradient(
|
${props => props.highlight && props.peopleCount === props.maxPeople ? `
|
||||||
${props => `${props.theme.primary}${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`},
|
background-image: repeating-linear-gradient(
|
||||||
${props => `${props.theme.primary}${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`}
|
45deg,
|
||||||
);
|
${props.theme.primary},
|
||||||
|
${props.theme.primary} 4.3px,
|
||||||
|
${props.theme.primaryDark} 4.3px,
|
||||||
|
${props.theme.primaryDark} 8.6px
|
||||||
|
);
|
||||||
|
` : `
|
||||||
|
background-image: linear-gradient(
|
||||||
|
${`${props.theme.primary}${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`},
|
||||||
|
${`${props.theme.primary}${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`}
|
||||||
|
);
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Spacer = styled.div`
|
export const Spacer = styled.div`
|
||||||
|
|
@ -174,7 +198,7 @@ export const Person = styled.button`
|
||||||
|
|
||||||
${props => props.filtered && `
|
${props => props.filtered && `
|
||||||
background: ${props.theme.primary};
|
background: ${props.theme.primary};
|
||||||
color: ${props.theme.background};
|
color: #FFFFFF;
|
||||||
border-color: ${props.theme.primary};
|
border-color: ${props.theme.primary};
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,9 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => {
|
||||||
|
|
||||||
const importAvailability = () => {
|
const importAvailability = () => {
|
||||||
setFreeBusyLoading(true);
|
setFreeBusyLoading(true);
|
||||||
|
gtag('event', 'google_cal_sync', {
|
||||||
|
'event_category': 'event',
|
||||||
|
});
|
||||||
window.gapi.client.calendar.freebusy.query({
|
window.gapi.client.calendar.freebusy.query({
|
||||||
timeMin,
|
timeMin,
|
||||||
timeMax,
|
timeMax,
|
||||||
|
|
@ -145,11 +148,12 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => {
|
||||||
)}
|
)}
|
||||||
{calendars !== undefined && (
|
{calendars !== undefined && (
|
||||||
<>
|
<>
|
||||||
<Info>Importing will overwrite your currently inputted availability</Info>
|
<Info>Importing will overwrite your current availability</Info>
|
||||||
<Button
|
<Button
|
||||||
buttonWidth="170px"
|
buttonWidth="170px"
|
||||||
buttonHeight="35px"
|
buttonHeight="35px"
|
||||||
isLoading={freeBusyLoading}
|
isLoading={freeBusyLoading}
|
||||||
|
disabled={freeBusyLoading}
|
||||||
onClick={() => importAvailability()}
|
onClick={() => importAvailability()}
|
||||||
>Import availability</Button>
|
>Import availability</Button>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
|
import { useSettingsStore } from 'stores';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Wrapper,
|
Wrapper,
|
||||||
|
|
@ -15,16 +16,23 @@ const Legend = ({
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const highlight = useSettingsStore(state => state.highlight);
|
||||||
|
const setHighlight = useSettingsStore(state => state.setHighlight);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Label>{min}/{total} available</Label>
|
<Label>{min}/{total} available</Label>
|
||||||
|
|
||||||
<Bar onMouseOut={() => onSegmentFocus(null)}>
|
<Bar
|
||||||
|
onMouseOut={() => onSegmentFocus(null)}
|
||||||
|
onClick={() => setHighlight(!highlight)}
|
||||||
|
title="Click to highlight highest availability"
|
||||||
|
>
|
||||||
{[...Array(max+1-min).keys()].map(i => i+min).map(i =>
|
{[...Array(max+1-min).keys()].map(i => i+min).map(i =>
|
||||||
<Grade
|
<Grade
|
||||||
key={i}
|
key={i}
|
||||||
color={`${theme.primary}${Math.round((i/(max))*255).toString(16)}`}
|
color={`${theme.primary}${Math.round((i/(max))*255).toString(16)}`}
|
||||||
|
highlight={highlight && i === max}
|
||||||
onMouseOver={() => onSegmentFocus(i)}
|
onMouseOver={() => onSegmentFocus(i)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,14 @@ export const Bar = styled.div`
|
||||||
export const Grade = styled.div`
|
export const Grade = styled.div`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background-color: ${props => props.color};
|
background-color: ${props => props.color};
|
||||||
|
|
||||||
|
${props => props.highlight && `
|
||||||
|
background-image: repeating-linear-gradient(
|
||||||
|
45deg,
|
||||||
|
${props.theme.primary},
|
||||||
|
${props.theme.primary} 4.5px,
|
||||||
|
${props.theme.primaryDark} 4.5px,
|
||||||
|
${props.theme.primaryDark} 9px
|
||||||
|
);
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,16 @@ const Settings = () => {
|
||||||
value={store.theme}
|
value={store.theme}
|
||||||
onChange={value => store.setTheme(value)}
|
onChange={value => store.setTheme(value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ToggleField
|
||||||
|
label="Highlight highest availability"
|
||||||
|
name="highlight"
|
||||||
|
id="highlight"
|
||||||
|
title="Make the highest availability on the heatmap stand out"
|
||||||
|
options={['Off', 'On']}
|
||||||
|
value={store.highlight ? 'On' : 'Off'}
|
||||||
|
onChange={value => store.setHighlight(value === 'On')}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,14 @@ const ToggleField = ({
|
||||||
label,
|
label,
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
title = '',
|
||||||
options = [],
|
options = [],
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
...props
|
...props
|
||||||
}) => (
|
}) => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
{label && <StyledLabel>{label}</StyledLabel>}
|
{label && <StyledLabel title={title}>{label}</StyledLabel>}
|
||||||
|
|
||||||
<ToggleContainer>
|
<ToggleContainer>
|
||||||
{options.map(option =>
|
{options.map(option =>
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,10 @@ export const LoginForm = styled.form`
|
||||||
}
|
}
|
||||||
@media (max-width: 400px) {
|
@media (max-width: 400px) {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|
||||||
|
& div:last-child {
|
||||||
|
--btn-width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,12 @@ export const useSettingsStore = create(persist(
|
||||||
weekStart: 0,
|
weekStart: 0,
|
||||||
timeFormat: '12h',
|
timeFormat: '12h',
|
||||||
theme: 'System',
|
theme: 'System',
|
||||||
|
highlight: false,
|
||||||
|
|
||||||
setWeekStart: weekStart => set({ weekStart }),
|
setWeekStart: weekStart => set({ weekStart }),
|
||||||
setTimeFormat: timeFormat => set({ timeFormat }),
|
setTimeFormat: timeFormat => set({ timeFormat }),
|
||||||
setTheme: theme => set({ theme }),
|
setTheme: theme => set({ theme }),
|
||||||
|
setHighlight: highlight => set({ highlight }),
|
||||||
}),
|
}),
|
||||||
{ name: 'crabfit-settings' },
|
{ name: 'crabfit-settings' },
|
||||||
));
|
));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue