Skip to content

Commit

Permalink
Merge pull request #1325 from vtex/feature/calendar
Browse files Browse the repository at this point in the history
  • Loading branch information
matheusps authored Dec 5, 2023
2 parents c5e7ed5 + d77478b commit 6dd5f67
Show file tree
Hide file tree
Showing 37 changed files with 4,932 additions and 11,816 deletions.
6 changes: 3 additions & 3 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"@vtex/shoreline-cli": "workspace",
"@vtex/shoreline-preset-admin": "workspace",
"@vtex/shoreline-theme": "workspace",
"@vtex/shoreline-utils": "^0.0.1-dev.0",
"match-sorter": "6.3.1",
"postcss": "8.4.31",
"postcss-preset-env": "9.3.0",
Expand All @@ -58,11 +57,12 @@
"@react-aria/checkbox": "3.11.2",
"@react-aria/focus": "3.14.3",
"@react-aria/interactions": "3.19.1",
"@react-aria/i18n": "3.9.0",
"@react-stately/toggle": "3.6.3",
"@tanstack/react-table": "8.10.7",
"@tanstack/react-virtual": "3.0.0-beta.68",
"@vtex/shoreline-icons": "^0.2.3",
"@vtex/shoreline-utils": "^0.5.1",
"@vtex/shoreline-icons": "workspace",
"@vtex/shoreline-utils": "workspace",
"react-hot-toast": "2.4.1",
"react-virtual": "^2.10.4",
"tiny-invariant": "1.3.1"
Expand Down
6 changes: 5 additions & 1 deletion packages/components/src/locale/locale-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react'
import React from 'react'
import { I18nProvider } from '@react-aria/i18n'
import { LocaleContext } from './locale-context'

/**
Expand All @@ -13,7 +14,10 @@ export function LocaleProvider(props: LocaleProviderProps) {
const { locale = 'en-US', children } = props

return (
<LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>
<LocaleContext.Provider value={locale}>
{/** Some react-aria components require this to translate */}
<I18nProvider locale={locale}>{children}</I18nProvider>
</LocaleContext.Provider>
)
}

Expand Down
2 changes: 0 additions & 2 deletions packages/date/.gitignore

This file was deleted.

6 changes: 5 additions & 1 deletion packages/date/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@
"postcss-preset-env": "9.3.0"
},
"dependencies": {
"@vtex/shoreline-icons": "workspace",
"@vtex/shoreline-utils": "workspace",
"@vtex/shoreline-store": "workspace",
"@internationalized/date": "3.5.0",
"@react-aria/calendar": "3.5.3",
"@react-aria/datepicker": "3.9.0",
"@react-stately/datepicker": "3.9.0",
"@vtex/shoreline-utils": "workspace"
"@react-stately/calendar": "3.4.2"
}
}
26 changes: 26 additions & 0 deletions packages/date/src/calendar/calendar-cell.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@layer sl-extended-components {
[data-sl-calendar-cell-button] {
width: 100%;
height: 100%;

&[data-selected='true'] {
background: var(--sl-bg-accent-strong);
color: var(--sl-fg-inverted);

&:hover,
&:focus {
background: var(--sl-bg-accent-strong-hover);
color: var(--sl-fg-inverted);
}

&:active {
background: var(--sl-bg-accent-strong-pressed);
color: var(--sl-fg-inverted);
}

&:focus-visible {
box-shadow: var(--sl-focus-ring-accent);
}
}
}
}
53 changes: 53 additions & 0 deletions packages/date/src/calendar/calendar-cell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useRef } from 'react'
import { useCalendarCell } from '@react-aria/calendar'
import { IconButton } from '@vtex/shoreline-components'
import type { CalendarDate } from '@internationalized/date'

import './calendar-cell.css'
import { useCalendarContext } from './calendar-provider'

/**
* Cell of a calendar grid
*/
export function CalendarCell(props: CalendarCellProps) {
const { date } = props
const ref = useRef(null)
const store = useCalendarContext()

const {
cellProps,
buttonProps,
isSelected,
isOutsideVisibleRange,
isDisabled,
isUnavailable,
formattedDate,
isFocused,
} = useCalendarCell({ date }, store.state, ref)

return (
<td data-sl-calendar-cell {...cellProps}>
<IconButton
{...buttonProps}
ref={ref}
variant="tertiary"
data-sl-calendar-cell-button
hidden={isOutsideVisibleRange}
data-selected={isSelected}
data-disabled={isDisabled}
data-unavailable={isUnavailable}
data-focused={isFocused}
label={formattedDate}
>
<span>{formattedDate}</span>
</IconButton>
</td>
)
}

interface CalendarCellProps {
/**
* Date that the cell represents
*/
date: CalendarDate
}
17 changes: 17 additions & 0 deletions packages/date/src/calendar/calendar-grid.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@layer sl-components {
[data-sl-calendar-grid] {
display: grid;
grid-template-columns: repeat(7, 1fr);

& > thead,
tbody,
tr {
display: contents;
}
}

[data-sl-calendar-grid-header] {
font: var(--sl-text-body-font);
letter-spacing: var(--sl-text-body-letter-spacing);
}
}
52 changes: 52 additions & 0 deletions packages/date/src/calendar/calendar-grid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react'
import type { AriaCalendarGridProps } from '@react-aria/calendar'
import { useCalendarGrid } from '@react-aria/calendar'
import { useLocale } from '@vtex/shoreline-components'

import { getWeeksInMonth } from '../utils'
import { CalendarCell } from './calendar-cell'
import { useCalendarContext } from './calendar-provider'
import './calendar-grid.css'

/**
* Grid of a calendar
*/
export function CalendarGrid(props: CalendarGridProps) {
const locale = useLocale()
const store = useCalendarContext()
const { gridProps, headerProps, weekDays } = useCalendarGrid(
props,
store.state
)

const weeksInMonth = getWeeksInMonth(store.state.visibleRange.start, locale)

return (
<table data-sl-calendar-grid {...gridProps}>
<thead data-sl-calendar-grid-header {...headerProps}>
<tr>
{weekDays.map((day, index) => (
<th key={index}>{day}</th>
))}
</tr>
</thead>
<tbody>
{[...new Array(weeksInMonth).keys()].map((weekIndex) => (
<tr key={weekIndex}>
{store.state
.getDatesInWeek(weekIndex)
.map((date: any, i: number) =>
date ? (
<CalendarCell key={i} date={date} />
) : (
<td data-sl-calendar-cell key={i} />
)
)}
</tr>
))}
</tbody>
</table>
)
}

export type CalendarGridProps = AriaCalendarGridProps
22 changes: 22 additions & 0 deletions packages/date/src/calendar/calendar-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { CalendarState } from '@react-stately/calendar'
import React, { createContext, useContext } from 'react'
import type { Store } from '@vtex/shoreline-store'
import { invariant } from '@vtex/shoreline-utils'

export const CalendarContext = createContext<Store<CalendarState> | null>(null)

export function CalendarProvider({ store, children }: any) {
return (
<CalendarContext.Provider value={store}>
{children}
</CalendarContext.Provider>
)
}

export function useCalendarContext() {
const context = useContext(CalendarContext)

invariant(context, 'Calendar components must be wrapped by CalendarProvider')

return context
}
23 changes: 23 additions & 0 deletions packages/date/src/calendar/calendar-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { CalendarDate } from '@internationalized/date'
import { createCalendar } from '@internationalized/date'
import type { CalendarStateOptions } from '@react-stately/calendar'
import { useCalendarState } from '@react-stately/calendar'
import { Store } from '@vtex/shoreline-store'
import { useMemo } from 'react'

/**
* Returns a calendar store
*/
export function useCalendarStore(props: UseCalendarStoreProps) {
const state = useCalendarState({
...props,
createCalendar,
})

return useMemo(() => new Store(state), [state])
}

export type UseCalendarStoreProps = Omit<
CalendarStateOptions<CalendarDate>,
'createCalendar'
>
19 changes: 19 additions & 0 deletions packages/date/src/calendar/calendar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@layer sl-components {
[data-sl-calendar] {
width: fit-content;
display: grid;
grid-template-rows: repeat(3, auto);
gap: var(--sl-space-gap);
}

[data-sl-calendar-header] {
display: flex;
align-items: center;
justify-content: space-between;
}

[data-sl-calendar-title] {
font: var(--sl-text-display-4-font);
letter-spacing: var(--sl-text-display-4-letter-spacing);
}
}
62 changes: 62 additions & 0 deletions packages/date/src/calendar/calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react'
import type { AriaCalendarProps } from '@react-aria/calendar'
import { useCalendar } from '@react-aria/calendar'
import type { CalendarDate } from '@internationalized/date'
import { useLocale, IconButton } from '@vtex/shoreline-components'
import { IconCaretLeft, IconCaretRight } from '@vtex/shoreline-icons'

import { CalendarGrid } from './calendar-grid'
import { CalendarProvider } from './calendar-provider'
import { useCalendarStore } from './calendar-store'

import './calendar.css'

/**
* Allow users to select a date
* @example
* <Calendar />
*/
export function Calendar(props: CalendarProps) {
const locale = useLocale()
const store = useCalendarStore({
...props,
locale,
})

const { calendarProps, prevButtonProps, nextButtonProps, title } =
useCalendar(props, store.state)

return (
<CalendarProvider store={store}>
<div data-sl-calendar {...calendarProps}>
<div data-sl-calendar-header>
<IconButton
label={prevButtonProps['aria-label']}
variant="tertiary"
disabled={prevButtonProps.isDisabled}
onClick={prevButtonProps.onPress as any}
onFocus={prevButtonProps.onFocusChange as any}
>
<IconCaretLeft />
</IconButton>
<h2 data-sl-calendar-title>{title}</h2>
<IconButton
label={nextButtonProps['aria-label']}
variant="tertiary"
disabled={nextButtonProps.isDisabled}
onClick={nextButtonProps.onPress as any}
onFocus={nextButtonProps.onFocusChange as any}
>
<IconCaretRight />
</IconButton>
</div>
<CalendarGrid />
</div>
</CalendarProvider>
)
}

export type CalendarProps = Omit<
AriaCalendarProps<CalendarDate>,
'createCalendar' | 'locale'
>
1 change: 1 addition & 0 deletions packages/date/src/calendar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './calendar'
49 changes: 49 additions & 0 deletions packages/date/src/calendar/stories/calendar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useState } from 'react'
import { LocaleProvider } from '@vtex/shoreline-components'

import { Calendar } from '../index'
import { getLocalTimeZone, today } from '../../utils'

export default {
title: 'date/calendar',
}

export function Default() {
return <Calendar />
}

export function Controlled() {
const now = today(getLocalTimeZone())
const [value, setValue] = useState(now)
const [focusedValue, setFocusedValue] = useState(now)

return (
<>
<p>Selected Date: {value.toString()}</p>
<p>Focused Date: {focusedValue.toString()}</p>

<button
onClick={() => {
setValue(now)
setFocusedValue(now)
}}
>
Today
</button>
<Calendar
value={value}
onChange={setValue}
focusedValue={focusedValue}
onFocusChange={setFocusedValue}
/>
</>
)
}

export function Locale() {
return (
<LocaleProvider locale="ja-JP">
<Calendar />
</LocaleProvider>
)
}
Loading

0 comments on commit 6dd5f67

Please sign in to comment.