From 68432a71d44c202b9cc8e9b596df833c5e8653a5 Mon Sep 17 00:00:00 2001 From: Benedicte Raae Date: Fri, 14 Aug 2020 18:18:33 +0200 Subject: [PATCH 1/3] TimelineItems --- src/features/entries/slice.js | 1 - .../TimelineItem/CurrentTimelineItem.js | 10 +++++++ .../TimelineItem/FutureTimelineItem.js | 10 +++++++ .../timeline/TimelineItem/PastTimelineItem.js | 10 +++++++ src/features/timeline/TimelineItem/index.js | 28 +++++++++++++++++++ 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/features/timeline/TimelineItem/CurrentTimelineItem.js create mode 100644 src/features/timeline/TimelineItem/FutureTimelineItem.js create mode 100644 src/features/timeline/TimelineItem/PastTimelineItem.js create mode 100644 src/features/timeline/TimelineItem/index.js diff --git a/src/features/entries/slice.js b/src/features/entries/slice.js index 672d8c4d..1cc59721 100644 --- a/src/features/entries/slice.js +++ b/src/features/entries/slice.js @@ -33,7 +33,6 @@ export const upsertEntry = (entryId, entry) => { const transformItemToEntry = ({ itemId, item }) => { return { entryId: itemId, - date: makeDate(itemId), note: item.note, tags: uniq(tagsFromText(item.note)), } diff --git a/src/features/timeline/TimelineItem/CurrentTimelineItem.js b/src/features/timeline/TimelineItem/CurrentTimelineItem.js new file mode 100644 index 00000000..3bfe4592 --- /dev/null +++ b/src/features/timeline/TimelineItem/CurrentTimelineItem.js @@ -0,0 +1,10 @@ +import React from "react" +import PropTypes from "prop-types" + +const CurrentTimelineItem = () => { + return
+} + +CurrentTimelineItem.propTypes = {} + +export default CurrentTimelineItem diff --git a/src/features/timeline/TimelineItem/FutureTimelineItem.js b/src/features/timeline/TimelineItem/FutureTimelineItem.js new file mode 100644 index 00000000..27caad28 --- /dev/null +++ b/src/features/timeline/TimelineItem/FutureTimelineItem.js @@ -0,0 +1,10 @@ +import React from "react" +import PropTypes from "prop-types" + +const FutureTimelineItem = () => { + return
+} + +FutureTimelineItem.propTypes = {} + +export default FutureTimelineItem diff --git a/src/features/timeline/TimelineItem/PastTimelineItem.js b/src/features/timeline/TimelineItem/PastTimelineItem.js new file mode 100644 index 00000000..2de0ff8a --- /dev/null +++ b/src/features/timeline/TimelineItem/PastTimelineItem.js @@ -0,0 +1,10 @@ +import React from "react" +import PropTypes from "prop-types" + +const PastTimelineItem = () => { + return
+} + +PastTimelineItem.propTypes = {} + +export default PastTimelineItem diff --git a/src/features/timeline/TimelineItem/index.js b/src/features/timeline/TimelineItem/index.js new file mode 100644 index 00000000..e53959e4 --- /dev/null +++ b/src/features/timeline/TimelineItem/index.js @@ -0,0 +1,28 @@ +import React from "react" +import PropTypes from "prop-types" + +import { isBefore, isToday, entryIdFromDate } from "../utils/days" + +import PastTimelineItem from "./PastTimelineItem" +import { selectEntry } from "../../entries" +import CurrentTimelineItem from "./CurrentTimelineItem" +import FutureTimelineItem from "./FutureTimelineItem" + +const TimelineItem = ({ date }) => { + const entryId = entryIdFromDate(date) + const entry = useSelector((state) => selectEntry(state, { entryId })) + + if (isToday(date)) { + return + } else if (isBefore) { + return + } else { + return + } +} + +TimelineItem.propTypes = { + date: PropTypes.date, +} + +export default TimelineItem From df3bd64de2b203a5495f6e30bf21a0016111c28f Mon Sep 17 00:00:00 2001 From: Benedicte Raae Date: Fri, 14 Aug 2020 21:09:41 +0200 Subject: [PATCH 2/3] Redesign timeline item --- src/features/brand/BrandLayout.js | 12 +-- src/features/cycle/slice.js | 1 - src/features/entries/slice.js | 1 + src/features/timeline/TimelineIndexPage.js | 8 +- .../TimelineItem/CurrentTimelineItem.js | 10 --- src/features/timeline/TimelineItem/Entry.js | 44 ++++++++++ .../TimelineItem/FutureTimelineItem.js | 10 --- src/features/timeline/TimelineItem/Header.js | 52 +++++++++++ src/features/timeline/TimelineItem/Info.js | 46 ++++++++++ .../timeline/TimelineItem/PastTimelineItem.js | 10 --- .../timeline/TimelineItem/Predictions.js | 54 ++++++++++++ src/features/timeline/TimelineItem/index.js | 87 +++++++++++++++---- 12 files changed, 278 insertions(+), 57 deletions(-) delete mode 100644 src/features/timeline/TimelineItem/CurrentTimelineItem.js create mode 100644 src/features/timeline/TimelineItem/Entry.js delete mode 100644 src/features/timeline/TimelineItem/FutureTimelineItem.js create mode 100644 src/features/timeline/TimelineItem/Header.js create mode 100644 src/features/timeline/TimelineItem/Info.js delete mode 100644 src/features/timeline/TimelineItem/PastTimelineItem.js create mode 100644 src/features/timeline/TimelineItem/Predictions.js diff --git a/src/features/brand/BrandLayout.js b/src/features/brand/BrandLayout.js index 07c17e98..b27e6a75 100644 --- a/src/features/brand/BrandLayout.js +++ b/src/features/brand/BrandLayout.js @@ -27,14 +27,10 @@ import { useSignInNavItem, useSignUpNavItem } from "../navigation" const useStyles = makeStyles((theme) => ({ root: { - "& header": { - maxWidth: "55rem", - margin: "0 auto", - }, - "& main": { + "& > main": { maxWidth: "50rem", }, - "& footer": { + "& > footer": { maxWidth: "50rem", }, }, @@ -47,6 +43,10 @@ const useStyles = makeStyles((theme) => ({ appBar: { borderTop: `4px solid ${theme.palette.primary.main}`, borderBottom: `1px solid ${theme.palette.grey[200]}`, + "& header": { + maxWidth: "55rem", + margin: "0 auto", + }, }, offset: theme.mixins.toolbar, toolbar: { diff --git a/src/features/cycle/slice.js b/src/features/cycle/slice.js index 84068d6d..00f3cb3d 100644 --- a/src/features/cycle/slice.js +++ b/src/features/cycle/slice.js @@ -5,7 +5,6 @@ import { selectEntriesSortedByDate, selectEntryTags } from "../entries" import { selectMenstruationTag, selectInitialDaysBetween } from "../settings" import { - makeDate, daysBetweenDates, isDateBefore, isDateAfter, diff --git a/src/features/entries/slice.js b/src/features/entries/slice.js index 1cc59721..672d8c4d 100644 --- a/src/features/entries/slice.js +++ b/src/features/entries/slice.js @@ -33,6 +33,7 @@ export const upsertEntry = (entryId, entry) => { const transformItemToEntry = ({ itemId, item }) => { return { entryId: itemId, + date: makeDate(itemId), note: item.note, tags: uniq(tagsFromText(item.note)), } diff --git a/src/features/timeline/TimelineIndexPage.js b/src/features/timeline/TimelineIndexPage.js index c6b43264..1df8b474 100644 --- a/src/features/timeline/TimelineIndexPage.js +++ b/src/features/timeline/TimelineIndexPage.js @@ -9,14 +9,12 @@ import { Welcome } from "../onboarding" import { selectDaysBetween } from "../cycle" -import DaySummary from "./DaySummary" -import ForecastItem from "./ForecastItem" +import ForecastItem from "./TimelineItem" import DatePicker from "./DatePicker" const useStyles = makeStyles((theme) => ({ forecast: { maxWidth: "30rem", - margin: theme.spacing(2, 0, 4), }, })) @@ -29,9 +27,9 @@ const CycleIndexPage = ({ entryId }) => { return ( }> - - + + {afterInterval.map((date) => { return })} diff --git a/src/features/timeline/TimelineItem/CurrentTimelineItem.js b/src/features/timeline/TimelineItem/CurrentTimelineItem.js deleted file mode 100644 index 3bfe4592..00000000 --- a/src/features/timeline/TimelineItem/CurrentTimelineItem.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react" -import PropTypes from "prop-types" - -const CurrentTimelineItem = () => { - return
-} - -CurrentTimelineItem.propTypes = {} - -export default CurrentTimelineItem diff --git a/src/features/timeline/TimelineItem/Entry.js b/src/features/timeline/TimelineItem/Entry.js new file mode 100644 index 00000000..0fd05a25 --- /dev/null +++ b/src/features/timeline/TimelineItem/Entry.js @@ -0,0 +1,44 @@ +import React from "react" +import PropTypes from "prop-types" +import { Link } from "gatsby" +import { useSelector } from "react-redux" +import { isToday, isPast } from "date-fns" +import { Typography, ButtonBase, Paper } from "@material-ui/core" +import { Add as AddNoteIcon } from "@material-ui/icons" + +import { entryIdFromDate } from "../../utils/days" +import { selectEntryNote } from "../../entries" + +const Entry = ({ date, ...props }) => { + const entryId = entryIdFromDate(date) + const editPath = `/timeline/${entryId}/edit` + const entryNote = useSelector((state) => selectEntryNote(state, { date })) + const isEditable = isPast(date) || isToday(date) + + if (!isEditable) return null + + return ( + + + {entryNote ? ( + {entryNote} + ) : ( + <> + + Add note + + )} + + + ) +} + +Entry.propTypes = { + date: PropTypes.instanceOf(Date), +} + +export default Entry diff --git a/src/features/timeline/TimelineItem/FutureTimelineItem.js b/src/features/timeline/TimelineItem/FutureTimelineItem.js deleted file mode 100644 index 27caad28..00000000 --- a/src/features/timeline/TimelineItem/FutureTimelineItem.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react" -import PropTypes from "prop-types" - -const FutureTimelineItem = () => { - return
-} - -FutureTimelineItem.propTypes = {} - -export default FutureTimelineItem diff --git a/src/features/timeline/TimelineItem/Header.js b/src/features/timeline/TimelineItem/Header.js new file mode 100644 index 00000000..321d638c --- /dev/null +++ b/src/features/timeline/TimelineItem/Header.js @@ -0,0 +1,52 @@ +import React from "react" +import PropTypes from "prop-types" +import { useSelector } from "react-redux" +import { format, isToday } from "date-fns" +import { Typography } from "@material-ui/core" + +import { selectIsMenstruationForDate } from "../../entries" +import { + selectCycleDayForDate, + selectPredictedMenstruationForDate, +} from "../../cycle" + +const TimelineHeader = ({ date, ...props }) => { + const cycleDay = useSelector((state) => + selectCycleDayForDate(state, { date }) + ) + + const isLoggedMenstruation = useSelector((state) => + selectIsMenstruationForDate(state, { date }) + ) + + const isPredictedMenstruation = useSelector((state) => + selectPredictedMenstruationForDate(state, { date }) + ) + + const isMenstruation = isLoggedMenstruation || isPredictedMenstruation + + return ( +
+ + {isToday(date) ? "Today" : format(date, "EEEE, MMMM do")} + + + Day {cycleDay} + +
+ ) +} + +TimelineHeader.propTypes = { + date: PropTypes.instanceOf(Date), +} + +export default TimelineHeader diff --git a/src/features/timeline/TimelineItem/Info.js b/src/features/timeline/TimelineItem/Info.js new file mode 100644 index 00000000..f0d8ff52 --- /dev/null +++ b/src/features/timeline/TimelineItem/Info.js @@ -0,0 +1,46 @@ +import React from "react" +import { useSelector } from "react-redux" +import PropTypes from "prop-types" +import { isToday, format } from "date-fns" +import { Typography } from "@material-ui/core" + +import { + selectDaysBetween, + selectIsDaysBetweenCalculated, + selectIsDateCurrentCycle, + selectNextStartDate, +} from "../../cycle" +import { selectMenstruationTag } from "../../settings" + +const Info = ({ date, ...props }) => { + const menstruationTag = useSelector(selectMenstruationTag) + + const daysBetween = useSelector(selectDaysBetween) + const isDaysBetweenCalculated = useSelector(selectIsDaysBetweenCalculated) + + const isCurrentCycle = useSelector((state) => + selectIsDateCurrentCycle(state, { date }) + ) + const nextStartDate = useSelector((state) => + selectNextStartDate(state, { date }) + ) + + if (!isToday(date) || !nextStartDate) return null + + return ( +
+ + #{menstruationTag} {!isCurrentCycle && "was"} estimated + to arrive {format(nextStartDate, "EEEE, MMMM do")}{" "} + based on {isDaysBetweenCalculated ? "your average" : "a default"}{" "} + {daysBetween || "?"} day cycle.{" "} + +
+ ) +} + +Info.propTypes = { + date: PropTypes.instanceOf(Date), +} + +export default Info diff --git a/src/features/timeline/TimelineItem/PastTimelineItem.js b/src/features/timeline/TimelineItem/PastTimelineItem.js deleted file mode 100644 index 2de0ff8a..00000000 --- a/src/features/timeline/TimelineItem/PastTimelineItem.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react" -import PropTypes from "prop-types" - -const PastTimelineItem = () => { - return
-} - -PastTimelineItem.propTypes = {} - -export default PastTimelineItem diff --git a/src/features/timeline/TimelineItem/Predictions.js b/src/features/timeline/TimelineItem/Predictions.js new file mode 100644 index 00000000..c5724b37 --- /dev/null +++ b/src/features/timeline/TimelineItem/Predictions.js @@ -0,0 +1,54 @@ +import React from "react" +import PropTypes from "prop-types" +import classNames from "classnames" +import { useSelector } from "react-redux" +import { Chip, makeStyles } from "@material-ui/core" + +import { selectPredictedTagsForDate } from "../../cycle" + +const useStyles = makeStyles((theme) => ({ + tag: { + borderStyle: "dotted", + }, + loggedTag: { + borderColor: theme.palette.text.secondary, + backgroundColor: theme.palette.background.paper, + }, +})) + +const Predictions = ({ date, ...props }) => { + const classes = useStyles() + + const predictedTags = useSelector((state) => + selectPredictedTagsForDate(state, { date }) + ) + + if (predictedTags.length === 0) return null + + return ( + + ) +} + +Predictions.propTypes = { + date: PropTypes.instanceOf(Date), +} + +export default Predictions diff --git a/src/features/timeline/TimelineItem/index.js b/src/features/timeline/TimelineItem/index.js index e53959e4..2f4895fb 100644 --- a/src/features/timeline/TimelineItem/index.js +++ b/src/features/timeline/TimelineItem/index.js @@ -1,28 +1,85 @@ import React from "react" import PropTypes from "prop-types" -import { isBefore, isToday, entryIdFromDate } from "../utils/days" +import { makeStyles, fade } from "@material-ui/core" -import PastTimelineItem from "./PastTimelineItem" -import { selectEntry } from "../../entries" -import CurrentTimelineItem from "./CurrentTimelineItem" -import FutureTimelineItem from "./FutureTimelineItem" +import Info from "./Info" +import Header from "./Header" +import Entry from "./Entry" +import Predictions from "./Predictions" + +const useStyles = makeStyles((theme) => ({ + root: { + marginBottom: theme.spacing(3), + display: "flex", + flexDirection: "column", + }, + header: { + padding: theme.spacing(0, 1), + display: "flex", + justifyContent: "space-between", + "& strong": { + fontWeight: theme.typography.fontWeightBold, + }, + }, + info: { + padding: theme.spacing(0.5, 1), + // margin: theme.spacing(0, `${theme.shape.borderRadius / 2}px`), + marginBottom: theme.spacing(1.5), + // backgroundColor: theme.palette.grey[100], + }, + entry: { + padding: theme.spacing(2), + width: "100%", + display: "flex", + justifyContent: "flex-start", + "&:hover": { + backgroundColor: fade( + theme.palette.action.active, + theme.palette.action.hoverOpacity + ), + // Reset on touch devices, it doesn't add specificity + "@media (hover: none)": { + backgroundColor: "transparent", + }, + }, + "& svg": { + marginRight: theme.spacing(1), + }, + "& *": { + whiteSpace: "pre-line", + }, + }, + predictions: { + padding: theme.spacing(1.5), + margin: theme.spacing(0, `${theme.shape.borderRadius / 2}px`), + // backgroundColor: theme.palette.grey[100], + backgroundColor: fade( + theme.palette.secondary.main, + theme.palette.action.hoverOpacity + ), + zIndex: "-1", + "& > *": { + margin: theme.spacing(0.5), + }, + }, +})) const TimelineItem = ({ date }) => { - const entryId = entryIdFromDate(date) - const entry = useSelector((state) => selectEntry(state, { entryId })) + const classes = useStyles() - if (isToday(date)) { - return - } else if (isBefore) { - return - } else { - return - } + return ( +
+
+ + + +
+ ) } TimelineItem.propTypes = { - date: PropTypes.date, + date: PropTypes.instanceOf(Date).isRequired, } export default TimelineItem From 069dcd5dbe4eef0592243dcabee41cad2431c199 Mon Sep 17 00:00:00 2001 From: Benedicte Raae Date: Sat, 22 Aug 2020 21:02:09 +0200 Subject: [PATCH 3/3] Timeline scroll #165 --- gatsby-browser.js | 13 ++++++ src/features/timeline/TimelineIndexPage.js | 42 +++++++++++++++----- src/features/timeline/TimelineItem/Header.js | 8 ++-- src/features/timeline/TimelineItem/index.js | 29 ++++++++++---- 4 files changed, 71 insertions(+), 21 deletions(-) diff --git a/gatsby-browser.js b/gatsby-browser.js index 56b4a18b..8f900768 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,5 +1,6 @@ import { withTheme } from "./src/theme" import { withRoot } from "./src/rootElement" +import { entryIdFromDate, makeDate } from "./src/features/utils/days" export const wrapPageElement = ({ element }) => { return withTheme({ element }) @@ -8,3 +9,15 @@ export const wrapPageElement = ({ element }) => { export const wrapRootElement = ({ element }) => { return withRoot({ element }) } + +export const shouldUpdateScroll = ({ routerProps: { location } }) => { + const regex = /timeline\/?(\d\d\d\d-\d\d-\d\d)?$/ + const results = location.pathname.match(regex) + + if (results) { + const entryId = entryIdFromDate(makeDate(results[1])) + return `scrollTo-${entryId}` + } else { + return true + } +} diff --git a/src/features/timeline/TimelineIndexPage.js b/src/features/timeline/TimelineIndexPage.js index 1df8b474..56834af0 100644 --- a/src/features/timeline/TimelineIndexPage.js +++ b/src/features/timeline/TimelineIndexPage.js @@ -1,15 +1,16 @@ -import React from "react" +import React, { useEffect } from "react" import { useSelector } from "react-redux" import { List, makeStyles } from "@material-ui/core" +import { eachDayOfInterval, addDays, isToday } from "date-fns" -import { makeDate, intervalAfterDate } from "../utils/days" +import { makeDate, entryIdFromDate } from "../utils/days" import { BrandLayout } from "../brand" import { Welcome } from "../onboarding" import { selectDaysBetween } from "../cycle" -import ForecastItem from "./TimelineItem" +import TimelineItem from "./TimelineItem" import DatePicker from "./DatePicker" const useStyles = makeStyles((theme) => ({ @@ -19,19 +20,40 @@ const useStyles = makeStyles((theme) => ({ })) const CycleIndexPage = ({ entryId }) => { - const date = makeDate(entryId) const classes = useStyles() + const selectedDate = makeDate(entryId) const calculatedDaysBetween = useSelector(selectDaysBetween) - const afterInterval = intervalAfterDate(date, calculatedDaysBetween + 3) + + const range = eachDayOfInterval({ + start: addDays(selectedDate, calculatedDaysBetween * -1.5), + end: addDays(selectedDate, calculatedDaysBetween * 1.5), + }) + + useEffect(() => { + const scrollToId = `scrollTo-${entryIdFromDate(selectedDate)}` + const node = document.getElementById(scrollToId) + if (!node) return + + node.scrollIntoView({ + block: "start", + }) + }, [selectedDate]) return ( - }> + }> - - - {afterInterval.map((date) => { - return + {range.map((date) => { + return ( + <> + + {isToday(date) && } + + ) })} diff --git a/src/features/timeline/TimelineItem/Header.js b/src/features/timeline/TimelineItem/Header.js index 321d638c..09e6012b 100644 --- a/src/features/timeline/TimelineItem/Header.js +++ b/src/features/timeline/TimelineItem/Header.js @@ -1,7 +1,7 @@ import React from "react" import PropTypes from "prop-types" import { useSelector } from "react-redux" -import { format, isToday } from "date-fns" +import { format, isToday, isSameDay } from "date-fns" import { Typography } from "@material-ui/core" import { selectIsMenstruationForDate } from "../../entries" @@ -10,7 +10,7 @@ import { selectPredictedMenstruationForDate, } from "../../cycle" -const TimelineHeader = ({ date, ...props }) => { +const TimelineHeader = ({ date, selectedDate, ...props }) => { const cycleDay = useSelector((state) => selectCycleDayForDate(state, { date }) ) @@ -29,8 +29,8 @@ const TimelineHeader = ({ date, ...props }) => {
{isToday(date) ? "Today" : format(date, "EEEE, MMMM do")} diff --git a/src/features/timeline/TimelineItem/index.js b/src/features/timeline/TimelineItem/index.js index 2f4895fb..27817af8 100644 --- a/src/features/timeline/TimelineItem/index.js +++ b/src/features/timeline/TimelineItem/index.js @@ -1,8 +1,10 @@ import React from "react" import PropTypes from "prop-types" - +import { addDays } from "date-fns" import { makeStyles, fade } from "@material-ui/core" +import { entryIdFromDate } from "../../utils/days" + import Info from "./Info" import Header from "./Header" import Entry from "./Entry" @@ -10,10 +12,19 @@ import Predictions from "./Predictions" const useStyles = makeStyles((theme) => ({ root: { + position: "relative", marginBottom: theme.spacing(3), display: "flex", flexDirection: "column", }, + scrollTo: { + position: "absolute", + // Indicates how much of this entry (the day before selected) + // should show + bottom: "4rem", + width: "100%", + ...theme.mixins.toolbar, + }, header: { padding: theme.spacing(0, 1), display: "flex", @@ -65,15 +76,19 @@ const useStyles = makeStyles((theme) => ({ }, })) -const TimelineItem = ({ date }) => { +const TimelineItem = ({ date, selectedDate, ...props }) => { const classes = useStyles() + const scrollToId = `scrollTo-${entryIdFromDate(addDays(date, 1))}` + const itemProps = { date, selectedDate } + return ( -
-
- - - +
+
+
+ + +
) }