diff --git a/app/scenes/ApiKeyNew/components/ExpiryDatePicker.tsx b/app/scenes/ApiKeyNew/components/ExpiryDatePicker.tsx index 55c145a35b..eee6cdfc0c 100644 --- a/app/scenes/ApiKeyNew/components/ExpiryDatePicker.tsx +++ b/app/scenes/ApiKeyNew/components/ExpiryDatePicker.tsx @@ -1,9 +1,9 @@ import { format as formatDate } from "date-fns"; import { CalendarIcon } from "outline-icons"; import * as React from "react"; -import { DayPicker } from "react-day-picker"; import { useTranslation } from "react-i18next"; -import styled, { useTheme } from "styled-components"; +import styled from "styled-components"; +import { Calendar } from "@shared/components/Calendar"; import { dateLocale } from "@shared/utils/date"; import Button from "~/components/Button"; import { @@ -21,25 +21,10 @@ type Props = { const ExpiryDatePicker = ({ selectedDate, onSelect }: Props) => { const { t } = useTranslation(); const [open, setOpen] = React.useState(false); - const theme = useTheme(); const userLocale = useUserLocale(); const locale = dateLocale(userLocale); - const styles = React.useMemo( - () => - ({ - "--rdp-caption-font-size": "16px", - "--rdp-cell-size": "34px", - "--rdp-selected-text": theme.accentText, - "--rdp-accent-color": theme.accent, - "--rdp-accent-color-dark": theme.accent, - "--rdp-background-color": theme.listItemHoverBackground, - "--rdp-background-color-dark": theme.listItemHoverBackground, - }) as React.CSSProperties, - [theme] - ); - const handleSelect = React.useCallback( (date: Date) => { setOpen(false); @@ -63,12 +48,12 @@ const ExpiryDatePicker = ({ selectedDate, onSelect }: Props) => { side="right" shrink > - diff --git a/app/scenes/ApiKeyNew/index.tsx b/app/scenes/ApiKeyNew/index.tsx index 8e31af2876..c2c503a347 100644 --- a/app/scenes/ApiKeyNew/index.tsx +++ b/app/scenes/ApiKeyNew/index.tsx @@ -13,7 +13,6 @@ import Text from "~/components/Text"; import useStores from "~/hooks/useStores"; import useUserLocale from "~/hooks/useUserLocale"; import { dateToExpiry } from "~/utils/date"; -import "react-day-picker/dist/style.css"; import ExpiryDatePicker from "./components/ExpiryDatePicker"; import { ExpiryType, ExpiryValues, calculateExpiryDate } from "./utils"; diff --git a/shared/components/Calendar.tsx b/shared/components/Calendar.tsx new file mode 100644 index 0000000000..c45568e517 --- /dev/null +++ b/shared/components/Calendar.tsx @@ -0,0 +1,167 @@ +import * as React from "react"; +import { DayPicker } from "react-day-picker"; +import styled from "styled-components"; +import { s } from "../styles"; + +type Props = React.ComponentProps; + +/** + * A themed calendar built on react-day-picker. It is styled from scratch (the + * library's base stylesheet is intentionally not relied upon) so that it looks + * consistent everywhere it is used. Outside (previous/next month) days are + * shown de-emphasised, the selected day is a solid accent-filled circle, and + * today is highlighted with the accent colour. + * + * @param props the underlying react-day-picker props; `showOutsideDays` and + * `fixedWeeks` default to true but may be overridden. + * @returns the rendered calendar. + */ +export function Calendar(props: Props) { + return ( + + + + ); +} + +const Wrapper = styled.div` + padding: 12px; + color: ${s("text")}; + + .rdp { + margin: 0; + } + + /* Visually-hidden accessibility labels (would otherwise show without the + base stylesheet). */ + .rdp-vhidden { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + appearance: none; + } + + .rdp-month { + width: 100%; + } + + .rdp-caption { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 2px 8px; + } + + .rdp-caption_label { + font-size: 14px; + font-weight: 600; + color: ${s("text")}; + } + + .rdp-nav { + display: flex; + gap: 2px; + } + + .rdp-nav_button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + border: 0; + background: none; + border-radius: 4px; + color: ${s("textSecondary")}; + cursor: pointer; + transition: background 100ms ease; + + &:hover { + background: ${s("listItemHoverBackground")}; + } + } + + .rdp-nav_icon { + width: 16px; + height: 16px; + } + + .rdp-table { + border-collapse: collapse; + width: 100%; + } + + .rdp-head_cell { + font-size: 11px; + font-weight: 500; + text-transform: none; + color: ${s("textTertiary")}; + padding: 4px 0; + text-align: center; + } + + .rdp-cell { + padding: 1px; + text-align: center; + } + + .rdp-day { + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border: 0; + background: none; + border-radius: 50%; + font-family: inherit; + font-size: 13px; + font-variant-numeric: tabular-nums; + color: ${s("text")}; + cursor: pointer; + transition: background 100ms ease; + + &:hover:not([disabled]):not(.rdp-day_selected) { + background: ${s("listItemHoverBackground")}; + } + + &:focus-visible:not([disabled]) { + outline: 2px solid ${s("accent")}; + outline-offset: -2px; + } + } + + /* Today, when not selected, is emphasised with the accent colour. */ + .rdp-day_today:not(.rdp-day_selected) { + font-weight: 700; + color: ${s("accent")}; + } + + /* Days belonging to the previous/next month are clearly de-emphasised. */ + .rdp-day_outside { + color: ${s("textTertiary")}; + opacity: 0.5; + } + + .rdp-day[disabled] { + color: ${s("textTertiary")}; + opacity: 0.4; + cursor: default; + } + + /* The selected day is a solid accent-filled circle. */ + .rdp-day_selected, + .rdp-day_selected:hover, + .rdp-day_selected:focus-visible { + background: ${s("accent")}; + color: ${s("accentText")}; + font-weight: 500; + opacity: 1; + } +`; diff --git a/shared/editor/components/Mentions.tsx b/shared/editor/components/Mentions.tsx index 8c60b7838b..2d68497727 100644 --- a/shared/editor/components/Mentions.tsx +++ b/shared/editor/components/Mentions.tsx @@ -9,7 +9,6 @@ import { } from "outline-icons"; import type { Node } from "prosemirror-model"; import * as React from "react"; -import { DayPicker } from "react-day-picker"; import { useTranslation } from "react-i18next"; import { RemoveScroll } from "react-remove-scroll"; import { Link } from "react-router-dom"; @@ -23,6 +22,7 @@ import { toISODate, } from "../../utils/date"; import { Backticks } from "../../components/Backticks"; +import { Calendar } from "../../components/Calendar"; import Flex from "../../components/Flex"; import Icon from "../../components/Icon"; import { IssueStatusIcon } from "../../components/IssueStatusIcon"; @@ -582,11 +582,9 @@ export const MentionDate = observer(function MentionDate_(props: DateProps) { {/* Lock page scroll while open, matching the inline editor menu. */} -