Skip to content

Commit

Permalink
feat: Add day picker for dimensions whose time unit is Day
Browse files Browse the repository at this point in the history
  • Loading branch information
ptbrowne committed Jan 25, 2022
1 parent fd6259c commit 890bc3f
Show file tree
Hide file tree
Showing 10 changed files with 472 additions and 33 deletions.
91 changes: 90 additions & 1 deletion app/components/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ import {
Select as TUISelect,
SelectProps,
} from "theme-ui";
import { ReactNode, useMemo } from "react";
import { ChangeEvent, ReactNode, useCallback, useMemo } from "react";
import { FieldProps, Option } from "../configurator";
import { Icon } from "../icons";
import { useId } from "@reach/auto-id";
import { useLocale } from "../locales/use-locale";
import { DayPickerInputProps, DayPickerProps } from "react-day-picker";
import DayPickerInput from "react-day-picker/DayPickerInput";
import "react-day-picker/lib/style.css";
import { useTimeFormatUnit } from "../configurator/components/ui-helpers";
import { TimeUnit } from "../graphql/query-hooks";

export const Label = ({
label,
Expand Down Expand Up @@ -292,6 +297,90 @@ export const Input = ({
</Box>
);

export const DayPickerField = ({
label,
name,
value,
onChange,
disabled,
controls,
...props
}: {
name: string;
value: Date;
onChange: (event: ChangeEvent<HTMLSelectElement>) => void;
controls?: ReactNode;
label?: string | ReactNode;
disabled?: boolean;
} & Omit<DayPickerInputProps, "onChange">) => {
const handleDayClick = useCallback(
(day: Date) => {
const ev = {
currentTarget: {
value: day.toISOString().slice(0, 10),
},
} as ChangeEvent<HTMLSelectElement>;
onChange(ev);
},
[onChange]
);
const formatDateAuto = useTimeFormatUnit();
const inputProps = useMemo(() => {
return {
name,
formatDate: formatDateAuto,
value: formatDateAuto(value, TimeUnit.Day),
disabled,
style: {
padding: "0.625rem 0.75rem",
color: disabled ? "monochrome300" : "monochrome700",
fontSize: "1rem",
minHeight: "1.5rem",
display: "block",
borderRadius: "0.25rem",
width: "100%",
border: "1px solid var(--theme-ui-colors-monochrome500)",

// @ts-ignore
...props.inputProps?.style,
},
...props.inputProps,
} as DayPickerInputProps;
}, [name, formatDateAuto, value, disabled, props.inputProps]);

const dayPickerProps = useMemo(() => {
return {
onDayClick: handleDayClick,
...props.dayPickerProps,
} as DayPickerProps;
}, [handleDayClick, props.dayPickerProps]);

return (
<Box
sx={{
color: disabled ? "monochrome300" : "monochrome700",
fontSize: 4,
pb: 2,
}}
>
{label && name && (
<Label htmlFor={name} smaller disabled={disabled}>
{label}
{controls}
</Label>
)}
<DayPickerInput
value={value}
style={{ width: "100%", color: "inherit" }}
{...props}
inputProps={inputProps}
dayPickerProps={dayPickerProps}
/>
{/* <DayPicker selectedDays={new Date()} /> */}
</Box>
);
};

export const SearchField = ({
id,
label,
Expand Down
28 changes: 28 additions & 0 deletions app/components/react-day-picker-custom-styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside) {
background-color: var(--theme-ui-colors-primary);
}

.DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside):hover {
background-color: var(--theme-ui-colors-primary);
cursor: pointer;
}

.DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside):hover {
background-color: var(--theme-ui-colors-primaryLight);
cursor: pointer;
}

.DayPicker-Caption > div {
margin-top: 0.25rem;
font-size: 0.875rem;
font-weight: 700;
}

.DayPicker-Weekday {
color: var(--theme-ui-colors-monochrome700);
font-size: 0.75rem;
}

.DayPickerInput > input[disabled] {
color: var(--theme-ui-colors-monochrome300);
}
31 changes: 25 additions & 6 deletions app/configurator/components/chart-configurator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ import {
DataFilterSelect,
ControlTabField,
DataFilterSelectTime,
DataFilterSelectDay,
} from "./field";
import { Loading } from "../../components/hint";
import { Box } from "theme-ui";
import produce from "immer";
import { useCallback } from "react";
import { mapValues, sortBy } from "lodash";
import { sortBy } from "lodash";

const DataFilterSelectGeneric = ({
dimension,
Expand Down Expand Up @@ -70,7 +70,8 @@ const DataFilterSelectGeneric = ({
);
return (
<Box sx={{ px: 2, mb: 2 }}>
{dimension.__typename === "TemporalDimension" ? (
{dimension.__typename === "TemporalDimension" &&
dimension.timeUnit !== "Day" ? (
<DataFilterSelectTime
dimensionIri={dimension.iri}
label={`${index + 1}. ${dimension.label}`}
Expand All @@ -83,7 +84,21 @@ const DataFilterSelectGeneric = ({
id={`select-single-filter-${index}`}
isOptional={!dimension.isKeyDimension}
/>
) : (
) : null}
{dimension.__typename === "TemporalDimension" &&
dimension.timeUnit === "Day" &&
dimension.values ? (
<DataFilterSelectDay
dimensionIri={dimension.iri}
label={`${index + 1}. ${dimension.label}`}
controls={controls}
options={dimension.values}
disabled={disabled}
id={`select-single-filter-${index}`}
isOptional={!dimension.isKeyDimension}
/>
) : null}
{dimension.__typename !== "TemporalDimension" ? (
<DataFilterSelect
dimensionIri={dimension.iri}
label={`${index + 1}. ${dimension.label}`}
Expand All @@ -93,7 +108,7 @@ const DataFilterSelectGeneric = ({
id={`select-single-filter-${index}`}
isOptional={!dimension.isKeyDimension}
/>
)}
) : null}
</Box>
);
};
Expand Down Expand Up @@ -126,9 +141,13 @@ export const ChartConfigurator = ({
return;
}

const dimension = data?.dataCubeByIri?.dimensions.find(
(d) => d.iri === dimensionIri
);
const chartConfig = moveFilterField(state.chartConfig, {
dimensionIri,
delta,
possibleValues: dimension ? dimension.values : [],
});

dispatch({
Expand All @@ -139,7 +158,7 @@ export const ChartConfigurator = ({
},
});
},
[dispatch, metaData, state.chartConfig]
[data?.dataCubeByIri?.dimensions, dispatch, metaData, state.chartConfig]
);

React.useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion app/configurator/components/chart-controls/section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const ControlSection = forwardRef<
overflowX: "hidden",
overflowY: "auto",
backgroundColor: isHighlighted ? "primaryLight" : "monochrome100",

flexShrink: 0,
"&:first-of-type": {
borderTopWidth: 0,
},
Expand Down
105 changes: 102 additions & 3 deletions app/configurator/components/field.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { t } from "@lingui/macro";
import { TimeLocaleObject } from "d3";
import { extent, TimeLocaleObject } from "d3";
import get from "lodash/get";
import { ChangeEvent, ReactNode, useCallback, useMemo, useState } from "react";
import { Flex } from "theme-ui";
Expand All @@ -12,7 +12,14 @@ import {
useMetaField,
useSingleFilterField,
} from "..";
import { Checkbox, Input, Label, Radio, Select } from "../../components/form";
import {
Checkbox,
Input,
Label,
Radio,
Select,
DayPickerField,
} from "../../components/form";
import { DimensionMetaDataFragment, TimeUnit } from "../../graphql/query-hooks";
import { DataCubeMetadata } from "../../graphql/types";
import { IconName } from "../../icons";
Expand All @@ -33,6 +40,8 @@ import {
getTimeIntervalWithProps,
useTimeFormatLocale,
} from "./ui-helpers";
import "react-day-picker/lib/style.css";
import parse from "date-fns/parse";

export const ControlTabField = ({
component,
Expand Down Expand Up @@ -114,6 +123,93 @@ export const DataFilterSelect = ({
);
};

export const DataFilterSelectDay = ({
dimensionIri,
label,
options,
id,
disabled,
isOptional,
controls,
}: {
dimensionIri: string;
label: string;
options: Option[];
id: string;
disabled?: boolean;
isOptional?: boolean;
controls: React.ReactNode;
}) => {
const fieldProps = useSingleFilterSelect({ dimensionIri });

const noneLabel = t({
id: "controls.dimensionvalue.none",
message: `No Filter`,
});

const optionalLabel = t({
id: "controls.select.optional",
message: `optional`,
});

const allOptions = useMemo(() => {
return isOptional
? [
{
value: FIELD_VALUE_NONE,
label: noneLabel,
isNoneValue: true,
},
...options,
]
: options;
}, [isOptional, options, noneLabel]);

const allOptionsSet = useMemo(() => {
return new Set(
allOptions.map((x) => new Date(x.value).toISOString().slice(0, 10))
);
}, [allOptions]);
const isDisabled = useCallback(
(date: Date) => {
return !allOptionsSet.has(date.toISOString().slice(0, 10));
},
[allOptionsSet]
);

const dateValue = useMemo(() => {
return fieldProps.value ? parse(fieldProps.value) : new Date();
}, [fieldProps.value]);

const [fromMonth, toMonth] = useMemo(() => {
const [min, max] = extent(Array.from(allOptionsSet));
if (!min || !max) {
return [];
}
return [new Date(min), new Date(max)] as const;
}, [allOptionsSet]);

return (
<DayPickerField
label={isOptional ? `${label} (${optionalLabel})` : label}
disabled={disabled}
controls={controls}
onChange={fieldProps.onChange}
name={dimensionIri}
value={dateValue}
inputProps={{
id,
disabled,
}}
dayPickerProps={{
fromMonth,
toMonth,
disabledDays: isDisabled,
}}
/>
);
};

export const DataFilterSelectTime = ({
dimensionIri,
label,
Expand Down Expand Up @@ -191,13 +287,14 @@ export const DataFilterSelectTime = ({
sortOptions={false}
controls={controls}
{...fieldProps}
></Select>
/>
);
}

return (
<TimeInput
id={id}
controls={controls}
label={fullLabel}
value={fieldProps.value}
timeFormat={timeFormat}
Expand All @@ -214,6 +311,7 @@ export const TimeInput = ({
value,
timeFormat,
formatLocale,
controls,
isOptional,
onChange,
}: {
Expand All @@ -222,6 +320,7 @@ export const TimeInput = ({
value: string | undefined;
timeFormat: string;
formatLocale: TimeLocaleObject;
controls?: React.ReactNode;
isOptional: boolean | undefined;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
}) => {
Expand Down
Loading

0 comments on commit 890bc3f

Please sign in to comment.