diff --git a/src/components/experimental/Calendar/Calendar.styled.ts b/src/components/experimental/Calendar/Calendar.styled.ts new file mode 100644 index 00000000..17de9c63 --- /dev/null +++ b/src/components/experimental/Calendar/Calendar.styled.ts @@ -0,0 +1,131 @@ +import styled from 'styled-components'; +import { + Button as BaseButton, + CalendarCell, + CalendarGrid as BaseCalendarGrid, + CalendarHeaderCell, + Heading as BaseHeading +} from 'react-aria-components'; +import { get } from '../../../utils/experimental/themeGet'; +import { getSemanticValue } from '../../../essentials/experimental'; + +export const Header = styled.header` + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: ${get('space.3')}; +`; + +export const Button = styled(BaseButton)` + appearance: none; + background: none; + border: none; + display: flex; + cursor: pointer; + margin: 0; + padding: 0; + color: ${getSemanticValue('on-surface')}; + outline: 0; + + &[data-focused] { + outline: ${getSemanticValue('interactive')} solid 0.125rem; + border-radius: ${get('radii.2')}; + } + + &[data-disabled] { + opacity: 0; + } +`; + +export const Heading = styled(BaseHeading)` + margin: 0; + color: ${getSemanticValue('on-surface')}; + font-size: var(--wave-exp-typescale-title-2-size); + font-weight: var(--wave-exp-typescale-title-2-weight); + line-height: var(--wave-exp-typescale-title-2-line-height); +`; + +export const CalendarGrid = styled(BaseCalendarGrid)` + border-collapse: separate; + border-spacing: 0 0.125rem; + + td { + padding: 0; + } + + th { + padding: 0 0 ${get('space.1')}; + } +`; + +export const WeekDay = styled(CalendarHeaderCell)` + color: ${getSemanticValue('on-surface')}; + font-size: var(--wave-exp-typescale-label-2-size); + font-weight: var(--wave-exp-typescale-label-2-weight); + line-height: var(--wave-exp-typescale-label-2-line-height); +`; + +export const MonthGrid = styled.div` + display: flex; + gap: 1.5rem; +`; + +export const Day = styled(CalendarCell)` + position: relative; + display: flex; + align-items: center; + justify-content: center; + color: ${getSemanticValue('on-surface')}; + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + outline: 0; + font-size: var(--wave-exp-typescale-label-2-size); + font-weight: var(--wave-exp-typescale-label-2-weight); + line-height: var(--wave-exp-typescale-label-2-line-height); + transition: background ease 200ms; + + &::after { + content: ''; + position: absolute; + inset: 0; + border-radius: 50%; + } + + &[data-focused]::after { + z-index: 1; + outline: ${getSemanticValue('interactive')} solid 0.125rem; + } + + &[data-hovered] { + cursor: pointer; + background: ${getSemanticValue('surface-variant')}; + } + + &[data-selected] { + background: ${getSemanticValue('interactive-container')}; + color: ${getSemanticValue('on-interactive-container')}; + } + + &[data-disabled] { + opacity: 0.38; + } + + &[data-outside-month] { + opacity: 0; + } + + [data-selection-type='range'] &[data-selected] { + border-radius: 0; + } + + &[data-selection-start][data-selected] { + border-start-start-radius: 50%; + border-end-start-radius: 50%; + } + + &[data-selection-end][data-selected] { + border-start-end-radius: 50%; + border-end-end-radius: 50%; + } +`; diff --git a/src/components/experimental/Calendar/Calendar.tsx b/src/components/experimental/Calendar/Calendar.tsx index 98f310f1..0f51f2b2 100644 --- a/src/components/experimental/Calendar/Calendar.tsx +++ b/src/components/experimental/Calendar/Calendar.tsx @@ -2,133 +2,83 @@ import React, { ReactElement } from 'react'; import { Calendar as BaseCalendar, CalendarProps as BaseCalendarProps, - CalendarCell, - CalendarGrid as BaseCalendarGrid, + RangeCalendarProps, CalendarGridHeader, CalendarGridBody, - CalendarHeaderCell, - Heading as BaseHeading, DateValue, - Button as BaseButton + RangeCalendar } from 'react-aria-components'; -import styled from 'styled-components'; import ChevronLeftIcon from '../../../icons/arrows/ChevronLeftIcon'; import ChevronRightIcon from '../../../icons/arrows/ChevronRightIcon'; -import { getSemanticValue } from '../../../essentials/experimental'; -import { textStyles } from '../Text/Text'; -import { get } from '../../../utils/experimental/themeGet'; -const Header = styled.header` - display: flex; - align-items: center; - justify-content: space-between; - padding-bottom: ${get('space.3')}; -`; - -const Button = styled(BaseButton)` - appearance: none; - background: none; - border: none; - display: flex; - cursor: pointer; - margin: 0; - padding: 0; - color: ${getSemanticValue('on-surface')}; - outline: 0; - - &[data-focused] { - outline: ${getSemanticValue('interactive')} solid 0.125rem; - border-radius: ${get('radii.2')}; - } - - &[data-disabled] { - opacity: 0; - } -`; - -const Heading = styled(BaseHeading)` - margin: 0; - color: ${getSemanticValue('on-surface')}; - ${textStyles.variants.title2} -`; - -const CalendarGrid = styled(BaseCalendarGrid)` - border-collapse: collapse; - border-spacing: 0; - - td { - padding: 0; - } - - th { - padding: 0 0 ${get('space.1')}; - } -`; - -const WeekDay = styled(CalendarHeaderCell)` - color: ${getSemanticValue('on-surface')}; - ${textStyles.variants.label2} -`; - -const Day = styled(CalendarCell)` - display: flex; - align-items: center; - justify-content: center; - color: ${getSemanticValue('on-surface')}; - width: 2.5rem; - height: 2.5rem; - border-radius: 50%; - ${textStyles.variants.label2} - transition: background ease 200ms; - - &[data-focused] { - outline: ${getSemanticValue('interactive')} solid 0.125rem; - } - - &[data-hovered] { - cursor: pointer; - background: ${getSemanticValue('surface-variant')}; - } - - &[data-selected] { - background: ${getSemanticValue('interactive-container')}; - color: ${getSemanticValue('on-interactive-container')}; - } - - &[data-disabled] { - opacity: 0.38; - } +import * as Styled from './Calendar.styled'; + +type CalendarProps = { visibleMonths?: 1 | 2 | 3 } & ( + | ({ selectionType?: 'single' } & Omit, 'visibleDuration'>) + | ({ selectionType: 'range' } & Omit, 'visibleDuration'>) +); + +function Calendar({ + value, + minValue, + defaultValue, + maxValue, + onChange, + selectionType = 'single', + visibleMonths = 1, + ...props +}: CalendarProps): ReactElement { + const calendarInner = ( + <> + + + + + + + + + + + {Array.from({ length: visibleMonths }).map((_, index) => ( + // eslint-disable-next-line react/no-array-index-key + + {weekDay => {weekDay}} + + {date => ( + + {({ formattedDate }) => + formattedDate.length > 1 ? formattedDate : `0${formattedDate}` + } + + )} + + + ))} + + + ); - &[data-outside-month] { - opacity: 0; + if (selectionType === 'single') { + return ( + )} + visibleDuration={{ months: visibleMonths }} + data-selection-type="single" + > + {calendarInner} + + ); } -`; -type CalendarProps = BaseCalendarProps; - -function Calendar({ value, minValue, defaultValue, maxValue, onChange, ...props }: CalendarProps): ReactElement { return ( - -
- - - -
- - {weekDay => {weekDay}} - - {date => ( - - {({ formattedDate }) => (formattedDate.length > 1 ? formattedDate : `0${formattedDate}`)} - - )} - - -
+ )} + visibleDuration={{ months: visibleMonths }} + data-selection-type="range" + > + {calendarInner} + ); } diff --git a/src/components/experimental/Calendar/docs/Calendar.stories.tsx b/src/components/experimental/Calendar/docs/Calendar.stories.tsx index d5408203..08e54a01 100644 --- a/src/components/experimental/Calendar/docs/Calendar.stories.tsx +++ b/src/components/experimental/Calendar/docs/Calendar.stories.tsx @@ -27,3 +27,15 @@ export const WithMinValue: Story = { minValue: TODAY } }; + +export const MultiMonth: Story = { + args: { + visibleMonths: 2 + } +}; + +export const RangeSelection: Story = { + args: { + selectionType: 'range' + } +}; diff --git a/src/essentials/experimental/globalStyles.ts b/src/essentials/experimental/globalStyles.ts index f4232fd7..5f7d403b 100644 --- a/src/essentials/experimental/globalStyles.ts +++ b/src/essentials/experimental/globalStyles.ts @@ -37,11 +37,14 @@ export const createThemeGlobalStyle = ( ${semanticCssVariablesForLightTheme} ${generateCssVariables( { + 'title-2-size': '1rem', + 'title-2-weight': 500, + 'title-2-line-height': '1.5rem', 'body-1-size': '1rem', - 'body-1-weight': 'normal', + 'body-1-weight': 400, 'body-1-line-height': '1.5rem', 'label-2-size': '0.875rem', - 'label-2-weight': 'normal', + 'label-2-weight': 400, 'label-2-line-height': '1.25rem' }, 'typescale'