mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:51:43 +00:00
Add wildcard/calendar component based on 'react-calendar' npm package (#40679)
This commit is contained in:
parent
7d48b11932
commit
93341e0069
75
client/wildcard/src/components/Calendar/Calendar.module.scss
Normal file
75
client/wildcard/src/components/Calendar/Calendar.module.scss
Normal 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;
|
||||
}
|
||||
109
client/wildcard/src/components/Calendar/Calendar.story.tsx
Normal file
109
client/wildcard/src/components/Calendar/Calendar.story.tsx
Normal 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,
|
||||
},
|
||||
}
|
||||
62
client/wildcard/src/components/Calendar/Calendar.tsx
Normal file
62
client/wildcard/src/components/Calendar/Calendar.tsx
Normal 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>
|
||||
)
|
||||
1
client/wildcard/src/components/Calendar/index.ts
Normal file
1
client/wildcard/src/components/Calendar/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Calendar'
|
||||
@ -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 {
|
||||
|
||||
@ -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",
|
||||
|
||||
36
yarn.lock
36
yarn.lock
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user