mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
Share one themed Calendar between the date mention and API key pickers
Extract the custom react-day-picker styling into a reusable Calendar component and use it in both the date mention picker and the API key expiry picker, so they look identical. The calendar owns its own padding and the API key scene no longer needs the library's base stylesheet.
This commit is contained in:
@@ -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
|
||||
>
|
||||
<DayPicker
|
||||
<Calendar
|
||||
required
|
||||
mode="single"
|
||||
selected={selectedDate}
|
||||
onSelect={handleSelect}
|
||||
style={styles}
|
||||
locale={locale}
|
||||
disabled={{ before: new Date() }}
|
||||
/>
|
||||
</PopoverContent>
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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<typeof DayPicker>;
|
||||
|
||||
/**
|
||||
* 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 (
|
||||
<Wrapper>
|
||||
<DayPicker showOutsideDays fixedWeeks {...props} />
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
`;
|
||||
@@ -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. */}
|
||||
<RemoveScroll as={Slot} allowPinchZoom>
|
||||
<DatePopoverContent>
|
||||
<DayPicker
|
||||
<Calendar
|
||||
required
|
||||
mode="single"
|
||||
showOutsideDays
|
||||
fixedWeeks
|
||||
selected={selectedDate}
|
||||
defaultMonth={selectedDate}
|
||||
onSelect={handleSelect}
|
||||
@@ -633,8 +631,6 @@ const DatePopoverContent = styled.div`
|
||||
box-shadow: ${s("menuShadow")};
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
padding: 12px;
|
||||
color: ${s("text")};
|
||||
|
||||
&[data-state="open"] {
|
||||
animation: fadeIn 150ms ease;
|
||||
@@ -648,145 +644,6 @@ const DatePopoverContent = styled.div`
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* react-day-picker is styled from scratch here as its base stylesheet is
|
||||
not loaded in the editor; this gives us full control over the look. */
|
||||
.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;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledWarningIcon = styled(WarningIcon)`
|
||||
|
||||
Reference in New Issue
Block a user