Add wildcard/calendar component based on 'react-calendar' npm package (#40679)

This commit is contained in:
Erzhan Torokulov 2022-08-24 21:25:12 +06:00 committed by GitHub
parent 7d48b11932
commit 93341e0069
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 285 additions and 1 deletions

View File

@ -0,0 +1,75 @@
// stylelint-disable selector-class-pattern
/*
* The 'react-calendar' is imported without styles but with predefined BEM-classes.
* This file styles the calendar to follow Sourcegraph designs styles.
*/
.container {
width: 21rem;
display: inline-block;
:global {
.react-calendar {
&__navigation__arrow,
&__navigation__label,
&__tile {
background-color: inherit;
border: none;
padding: 0.25rem;
}
&__navigation {
display: flex;
margin-bottom: 0.5rem;
&__arrow {
border: 1px solid var(--secondary);
margin: 0 0.25rem;
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
&__label {
font-weight: bold;
pointer-events: none;
}
}
&__month-view__weekdays {
&__weekday {
text-align: center;
font-weight: bold;
}
}
&__tile {
&--rangeStart {
border-top-left-radius: 0.25rem;
}
&--rangeEnd {
border-bottom-right-radius: 0.25rem;
}
&--rangeBothEnds {
border-radius: 0.25rem;
}
&--active {
background-color: var(--primary-2);
}
&:disabled {
color: var(--secondary);
}
}
&__month-view__days__day--neighboringMonth {
color: var(--text-muted);
}
}
}
}
.highlight-today :global(.react-calendar__tile--now) {
font-weight: 500;
text-decoration: underline;
text-decoration-thickness: 0.1rem;
}

View File

@ -0,0 +1,109 @@
import { useState } from 'react'
import { DecoratorFn, Meta, Story } from '@storybook/react'
import { addDays, startOfDay, subDays } from 'date-fns'
import { BrandedStory } from '@sourcegraph/branded/src/components/BrandedStory'
import webStyles from '@sourcegraph/web/src/SourcegraphWebApp.scss'
import { Badge, Text } from '..'
import { Calendar } from './Calendar'
const decorator: DecoratorFn = story => (
<BrandedStory styles={webStyles}>
{() => (
<div className="container mt-3">
<Text>
This is an{' '}
<Badge variant="merged" className="mb-2">
Experimental
</Badge>{' '}
component and built on top of `react-calendar` package with Sourcegraph CSS styling on top. It
intentionally, omits other `react-calendar` props/features to not over-complicate and use as simple
calendar, in case if we migrate to another calendar library or build our own.
</Text>
<div className="d-flex flex-column">{story()}</div>
</div>
)}
</BrandedStory>
)
const config: Meta = {
title: 'wildcard/Calendar',
component: Calendar,
decorators: [decorator],
}
export default config
// NOTE: hardcoded in order to screenshot test the calendar
const today = startOfDay(new Date('2022-08-22'))
export const Single: Story = () => {
const [value, onChange] = useState(today)
return <Calendar value={value} onChange={onChange} />
}
Single.parameters = {
chromatic: {
enableDarkMode: true,
disableSnapshot: false,
},
}
export const Range: Story = () => {
const [value, onChange] = useState<[Date, Date]>([subDays(today, 7), today])
return <Calendar isRange={true} value={value} onChange={onChange} />
}
Range.parameters = {
chromatic: {
enableDarkMode: true,
disableSnapshot: false,
},
}
export const MinMaxDates: Story = () => {
const [value, onChange] = useState<[Date, Date]>([subDays(today, 7), today])
return (
<Calendar
isRange={true}
value={value}
onChange={onChange}
minDate={subDays(today, 10)}
maxDate={addDays(today, 10)}
/>
)
}
MinMaxDates.parameters = {
chromatic: {
enableDarkMode: true,
disableSnapshot: false,
},
}
export const HighlightToday: Story = () => {
const [value, onChange] = useState(new Date())
return <Calendar value={value} onChange={onChange} highlightToday={true} />
}
HighlightToday.parameters = {
chromatic: {
enableDarkMode: true,
disableSnapshot: true,
},
}
export const HighlightTodayRange: Story = () => {
const [value, onChange] = useState<[Date, Date]>([subDays(new Date(), 4), addDays(new Date(), 3)])
return <Calendar isRange={true} value={value} onChange={onChange} highlightToday={true} />
}
HighlightTodayRange.parameters = {
chromatic: {
enableDarkMode: true,
disableSnapshot: true,
},
}

View File

@ -0,0 +1,62 @@
import classNames from 'classnames'
import ReactCalendar from 'react-calendar'
import { Container } from '@sourcegraph/wildcard'
import styles from './Calendar.module.scss'
interface CalendarDateProps {
isRange?: false
value?: Date | null
onChange: (value: Date) => void
}
interface CalendarDateRangeProps {
isRange: true
value?: [Date | null, Date | null] | null
onChange: (value: [Date, Date]) => void
}
type CalendarProps = {
className?: string
maxDate?: Date
minDate?: Date
highlightToday?: boolean
} & (CalendarDateRangeProps | CalendarDateProps)
/**
* Renders a calendar component which supports single date or range selection.
*
* **NOTE:** This is an `EXPERIMENTAL` component and built on top of `react-calendar` package with Sourcegraph CSS styling on top.
* It intentionally, omits other `react-calendar` props/features to not over-complicate and use as simple calendar, in case if we migrate to another calendar library or build our own.
*
* Depending on `isRange` value `true | false`, the component will render a single or range selection calendar as well as infer correct TS props for the range selection.
*
*/
export const Calendar: React.FunctionComponent<CalendarProps> = ({
className,
value,
onChange,
isRange,
minDate,
maxDate,
highlightToday,
}) => (
<Container className={classNames(styles.container, highlightToday && styles.highlightToday, className)}>
<ReactCalendar
/**
* The underlying react-calendar component "onChange" handler arguments differ depending on the "selectRange"
* however type-wise is not differentiated thus TS cannot infer the correct type.
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
onChange={onChange}
value={value}
selectRange={isRange}
view="month"
maxDate={maxDate}
minDate={minDate}
showFixedNumberOfWeeks={true}
/>
</Container>
)

View File

@ -0,0 +1 @@
export * from './Calendar'

View File

@ -2,6 +2,7 @@
export { Button, ButtonGroup, BUTTON_SIZES } from './Button'
export type { ButtonGroupProps } from './Button'
export { Alert, AlertLink } from './Alert'
export { Calendar } from './Calendar'
export { Container } from './Container'
export { LineChart, BarChart, PieChart, LegendList, LegendItem, ScrollBox, ParentSize } from './Charts'
export {

View File

@ -202,6 +202,7 @@
"@types/pollyjs__persister-fs": "2.0.1",
"@types/puppeteer": "^5.4.5",
"@types/react": "18.0.8",
"@types/react-calendar": "^3.5.2",
"@types/react-circular-progressbar": "1.0.2",
"@types/react-dom": "18.0.2",
"@types/react-grid-layout": "1.3.0",
@ -449,6 +450,7 @@
"pretty-bytes": "^5.3.0",
"prop-types": "^15.7.2",
"react": "18.1.0",
"react-calendar": "^3.7.0",
"react-circular-progressbar": "^2.0.3",
"react-dom": "18.1.0",
"react-dom-confetti": "^0.1.4",

View File

@ -6079,6 +6079,13 @@
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.2.tgz#fa8e1ad1d474688a757140c91de6dace6f4abc8d"
integrity sha512-HtKGu+qG1NPvYe1z7ezLsyIaXYyi8SoAVqWDZgDQ8dLrsZvSzUNCwZyfX33uhWxL/SU0ZDQZ3nwZ0nimt507Kw==
"@types/react-calendar@^3.5.2":
version "3.5.2"
resolved "https://registry.npmjs.org/@types/react-calendar/-/react-calendar-3.5.2.tgz#e401034e4bb82f4510ba87aa490e98b5746e16e0"
integrity sha512-8gkU9KaE33VVbu3YWvxXjEk4BsalgSYR3c/5XF9XNJiQ/2MKxiGkTg/PfOHUX/BvcADykRBMAEJiCi6jFPEE3A==
dependencies:
"@types/react" "*"
"@types/react-circular-progressbar@1.0.2":
version "1.0.2"
resolved "https://registry.npmjs.org/@types/react-circular-progressbar/-/react-circular-progressbar-1.0.2.tgz#a23e2d0f4e14a89d75c7c2286f8e96702f9862f2"
@ -6978,6 +6985,11 @@
resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.5.1.tgz#b5fde2f0f79c1e120307c415a4c1d5eb15a6f278"
integrity sha512-4vSVUiOPJLmr45S8rMGy7WDvpWxfFxfP/Qx/cxZFCfvoypTYpPPL1X8VIZMe0WTA+Jr7blUxwUSEZNkjoMTgSw==
"@wojtekmaj/date-utils@^1.0.2":
version "1.0.3"
resolved "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.0.3.tgz#2dcfd92881425c5923e429c2aec86fb3609032a1"
integrity sha512-1VPkkTBk07gMR1fjpBtse4G+oJqpmE+0gUFB0dg3VIL7qJmUVaBoD/vlzMm/jNeOPfvlmerl1lpnsZyBUFIRuw==
"@wry/context@^0.6.0":
version "0.6.0"
resolved "https://registry.npmjs.org/@wry/context/-/context-0.6.0.tgz#f903eceb89d238ef7e8168ed30f4511f92d83e06"
@ -13680,6 +13692,13 @@ get-uri@3:
fs-extra "^8.1.0"
ftp "^0.3.10"
get-user-locale@^1.2.0:
version "1.5.1"
resolved "https://registry.npmjs.org/get-user-locale/-/get-user-locale-1.5.1.tgz#18a9ba2cfeed0e713ea00968efa75d620523a5ea"
integrity sha512-WiNpoFRcHn1qxP9VabQljzGwkAQDrcpqUtaP0rNBEkFxJdh4f3tik6MfZsMYZc+UgQJdGCxWEjL9wnCUlRQXag==
dependencies:
lodash.memoize "^4.1.1"
get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
@ -17521,7 +17540,7 @@ lodash.isstring@^4.0.1:
resolved "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
lodash.memoize@^4.1.2:
lodash.memoize@^4.1.1, lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
@ -18009,6 +18028,11 @@ meow@^9.0.0:
type-fest "^0.18.0"
yargs-parser "^20.2.3"
merge-class-names@^1.1.1:
version "1.4.2"
resolved "https://registry.npmjs.org/merge-class-names/-/merge-class-names-1.4.2.tgz#78d6d95ab259e7e647252a7988fd25a27d5a8835"
integrity sha512-bOl98VzwCGi25Gcn3xKxnR5p/WrhWFQB59MS/aGENcmUc6iSm96yrFDF0XSNurX9qN4LbJm0R9kfvsQ17i8zCw==
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@ -20863,6 +20887,16 @@ rc@1.2.8, rc@^1.2.7, rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-calendar@^3.7.0:
version "3.7.0"
resolved "https://registry.npmjs.org/react-calendar/-/react-calendar-3.7.0.tgz#951d56e91afb33b1c1e019cb790349fbffcc6894"
integrity sha512-zkK95zWLWLC6w3O7p3SHx/FJXEyyD2UMd4jr3CrKD+G73N+G5vEwrXxYQCNivIPoFNBjqoyYYGlkHA+TBDPLCw==
dependencies:
"@wojtekmaj/date-utils" "^1.0.2"
get-user-locale "^1.2.0"
merge-class-names "^1.1.1"
prop-types "^15.6.0"
react-circular-progressbar@^2.0.3:
version "2.0.3"
resolved "https://registry.npmjs.org/react-circular-progressbar/-/react-circular-progressbar-2.0.3.tgz#fa8eb59f8db168d2904bae4590641792c80f5991"