diff --git a/pkg/app/web/package.json b/pkg/app/web/package.json index 7ecaf53bc2..ea338babeb 100644 --- a/pkg/app/web/package.json +++ b/pkg/app/web/package.json @@ -63,9 +63,11 @@ "webpack-merge": "^4.2.2" }, "dependencies": { + "@date-io/dayjs": "^1.3.13", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.56", + "@material-ui/pickers": "^3.2.10", "@primer/octicons-react": "^10.0.0", "@reduxjs/toolkit": "^1.3.6", "@types/dagre": "^0.7.44", diff --git a/pkg/app/web/src/components/week-picker.stories.tsx b/pkg/app/web/src/components/week-picker.stories.tsx new file mode 100644 index 0000000000..9c574e6a0d --- /dev/null +++ b/pkg/app/web/src/components/week-picker.stories.tsx @@ -0,0 +1,16 @@ +import { action } from "@storybook/addon-actions"; +import React from "react"; +import { WeekPicker } from "./week-picker"; + +export default { + title: "COMMON/WeekPicker", + component: WeekPicker, +}; + +export const overview: React.FC = () => ( + +); diff --git a/pkg/app/web/src/components/week-picker.tsx b/pkg/app/web/src/components/week-picker.tsx new file mode 100644 index 0000000000..10fef2fd1e --- /dev/null +++ b/pkg/app/web/src/components/week-picker.tsx @@ -0,0 +1,117 @@ +import DayJSUtils from "@date-io/dayjs"; +import { DateType } from "@date-io/type"; +import { IconButton, makeStyles } from "@material-ui/core"; +import { DatePicker, MuiPickersUtilsProvider } from "@material-ui/pickers"; +import { MaterialUiPickersDate } from "@material-ui/pickers/typings/date"; +import clsx from "clsx"; +import React, { FC, memo, useCallback } from "react"; + +const useStyles = makeStyles((theme) => ({ + ayWrapper: { + position: "relative", + }, + day: { + width: 36, + height: 36, + fontSize: theme.typography.caption.fontSize, + margin: "0 2px", + color: "inherit", + }, + customDayHighlight: { + position: "absolute", + top: 0, + bottom: 0, + left: "2px", + right: "2px", + border: `1px solid ${theme.palette.secondary.main}`, + borderRadius: "50%", + }, + nonCurrentMonthDay: { + color: theme.palette.text.disabled, + }, + highlightNonCurrentMonthDay: { + color: "#999999", + }, + highlight: { + background: theme.palette.primary.main, + color: theme.palette.common.white, + }, + firstHighlight: { + extend: "highlight", + borderTopLeftRadius: "50%", + borderBottomLeftRadius: "50%", + }, + endHighlight: { + extend: "highlight", + borderTopRightRadius: "50%", + borderBottomRightRadius: "50%", + }, +})); + +const formatWeekSelectLabel = ( + date: MaterialUiPickersDate, + invalidLabel: string +): string => { + return date ? `Week of ${date.day(0).format("MMM Do")}` : invalidLabel; +}; + +interface Props { + value: Date | null; + label: string; + onChange: (date: MaterialUiPickersDate) => void; +} + +export const WeekPicker: FC = memo(function WeekPicker({ + value, + label, + onChange, +}) { + const classes = useStyles(); + + const renderWeekDay = useCallback( + (date: DateType, selected: DateType, dayInCurrentMonth: boolean) => { + const start = selected.day(0); + const end = selected.day(6); + + const dayIsBetween = date.isBetween(start, end, "day", "[]"); + const isFirstDay = date.isSame(start, "day"); + const isLastDay = date.isSame(end, "day"); + + const wrapperClassName = clsx({ + [classes.highlight]: dayIsBetween, + [classes.firstHighlight]: isFirstDay, + [classes.endHighlight]: isLastDay, + }); + + const dayClasses = clsx(classes.day, { + [classes.nonCurrentMonthDay]: !dayInCurrentMonth, + [classes.highlightNonCurrentMonthDay]: + !dayInCurrentMonth && dayIsBetween, + }); + + return ( +
+ + {date.format("D")} + +
+ ); + }, + [classes] + ); + + return ( + + + + ); +}); diff --git a/pkg/app/web/src/utils/setup-dayjs.ts b/pkg/app/web/src/utils/setup-dayjs.ts index 1e33d4ad5a..9854f43010 100644 --- a/pkg/app/web/src/utils/setup-dayjs.ts +++ b/pkg/app/web/src/utils/setup-dayjs.ts @@ -1,6 +1,10 @@ import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; +import isBetween from "dayjs/plugin/isBetween"; +import advancedFormat from "dayjs/plugin/advancedFormat"; -export const setupDayjs = () => { +export const setupDayjs = (): void => { dayjs.extend(relativeTime); + dayjs.extend(isBetween); + dayjs.extend(advancedFormat); }; diff --git a/pkg/app/web/yarn.lock b/pkg/app/web/yarn.lock index acb77061ab..def86bacad 100644 --- a/pkg/app/web/yarn.lock +++ b/pkg/app/web/yarn.lock @@ -1902,7 +1902,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5": +"@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.6.0": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== @@ -2055,6 +2055,18 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@date-io/core@1.x", "@date-io/core@^1.3.13": + version "1.3.13" + resolved "https://registry.yarnpkg.com/@date-io/core/-/core-1.3.13.tgz#90c71da493f20204b7a972929cc5c482d078b3fa" + integrity sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA== + +"@date-io/dayjs@^1.3.13": + version "1.3.13" + resolved "https://registry.yarnpkg.com/@date-io/dayjs/-/dayjs-1.3.13.tgz#3a9edf5a7227b31b0f00a4f640f8715626833a61" + integrity sha512-nD39xWYwQjDMIdpUzHIcADHxY9m1hm1DpOaRn3bc2rBdgmwQC0PfW0WYaHyGGP/6LEzEguINRbHuotMhf+T9Sg== + dependencies: + "@date-io/core" "^1.3.13" + "@emotion/cache@^10.0.27": version "10.0.29" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" @@ -2433,6 +2445,18 @@ prop-types "^15.7.2" react-is "^16.8.0" +"@material-ui/pickers@^3.2.10": + version "3.2.10" + resolved "https://registry.yarnpkg.com/@material-ui/pickers/-/pickers-3.2.10.tgz#19df024895876eb0ec7cd239bbaea595f703f0ae" + integrity sha512-B8G6Obn5S3RCl7hwahkQj9sKUapwXWFjiaz/Bsw1fhYFdNMnDUolRiWQSoKPb1/oKe37Dtfszoywi1Ynbo3y8w== + dependencies: + "@babel/runtime" "^7.6.0" + "@date-io/core" "1.x" + "@types/styled-jsx" "^2.2.8" + clsx "^1.0.2" + react-transition-group "^4.0.0" + rifm "^0.7.0" + "@material-ui/styles@^4.10.0": version "4.10.0" resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.10.0.tgz#2406dc23aa358217aa8cc772e6237bd7f0544071" @@ -3812,6 +3836,13 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/styled-jsx@^2.2.8": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@types/styled-jsx/-/styled-jsx-2.2.8.tgz#b50d13d8a3c34036282d65194554cf186bab7234" + integrity sha512-Yjye9VwMdYeXfS71ihueWRSxrruuXTwKCbzue4+5b2rjnQ//AtyM7myZ1BEhNhBQ/nL/RE7bdToUoLln2miKvg== + dependencies: + "@types/react" "*" + "@types/tapable@*", "@types/tapable@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02" @@ -5875,7 +5906,7 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -clsx@^1.0.4, clsx@^1.1.1: +clsx@^1.0.2, clsx@^1.0.4, clsx@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== @@ -13175,7 +13206,7 @@ react-textarea-autosize@^8.1.1: use-composed-ref "^1.0.0" use-latest "^1.0.0" -react-transition-group@^4.4.0: +react-transition-group@^4.0.0, react-transition-group@^4.4.0: version "4.4.1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== @@ -13728,6 +13759,13 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rifm@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.7.0.tgz#debe951a9c83549ca6b33e5919f716044c2230be" + integrity sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ== + dependencies: + "@babel/runtime" "^7.3.1" + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"