-
Notifications
You must be signed in to change notification settings - Fork 31
feat(DatePicker): New DatePicker component #3286
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
aresnik11
wants to merge
48
commits into
main
Choose a base branch
from
ajr-datepicker-localization
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
48 commits
Select commit
Hold shift + click to select a range
fd2fbce
calendar phase 1
aresnik11 0ffb424
add focus management
aresnik11 b8a856d
add input
aresnik11 c096fe9
datepicker
aresnik11 9346ac9
clean up
aresnik11 00a8584
refactor datepicker
aresnik11 1946d73
clean up hooks
aresnik11 39d0de0
update exports
aresnik11 23f593d
add missing validation file
aresnik11 c1c9245
clean up inputref
aresnik11 9a8f70d
wip fix for typing date
aresnik11 82854c8
cleaning up
aresnik11 4f39ff9
update range logic
aresnik11 d194237
range logic when specific input is focused
aresnik11 3bbeb01
deselect logic
aresnik11 a6cbe80
fix lint
aresnik11 a56426f
fix story
aresnik11 f79ce4a
fix lint again
aresnik11 e0ee942
update snapshot
aresnik11 a417554
fix lint
aresnik11 cf4a438
show 2 months on larger screens
aresnik11 d3f6fac
style updates
aresnik11 e615f9f
PR feedback
aresnik11 a19fc7b
fix today in range color
aresnik11 9c30d34
clean up context
aresnik11 b657556
update placeholder text to be locale based
aresnik11 1924433
update next/last month text to be locale based
aresnik11 a83a99a
capitalize
aresnik11 40a0c8b
Merge branch 'main' into ajr-datepicker-styles
aresnik11 bfd8206
translations
aresnik11 f6463b4
add formgroup
aresnik11 7de8b27
Merge branch 'ajr-datepicker-styles' into ajr-datepicker-localization
aresnik11 c9a7576
fix calendar alignment and add shadow
aresnik11 d3f6fba
clear button logic
aresnik11 2834206
more translations
aresnik11 b436daa
fix disabled hover
aresnik11 5b7a8be
make disabledDates optional and add to story
aresnik11 496e740
disabled date in range logic
aresnik11 5df9d1a
fix keyboard nav now that showing 2 months
aresnik11 38f81a9
PR feedback
aresnik11 b9aa903
toLocaleUpperCase
aresnik11 b400a75
small fixes
aresnik11 0e10748
fix range styling
aresnik11 dfdabe8
fix alignments
aresnik11 1ee01fc
rename props
aresnik11 a6c8388
move around helpers
aresnik11 3bfab02
fix calendar story
aresnik11 afaea71
support small input size
aresnik11 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { CheckerDense } from '@codecademy/gamut-patterns'; | ||
| import * as React from 'react'; | ||
|
|
||
| import { Box } from '../../Box'; | ||
|
|
||
| /** | ||
| * Outer wrapper for the calendar (header + body + footer). | ||
| * Used by DatePickerCalendar to group the calendar content. | ||
| * Renders a CheckerDense pattern shadow at offset left 8, top 8. | ||
| */ | ||
| export const Calendar: React.FC<{ children: React.ReactNode }> = ({ | ||
| children, | ||
| }) => ( | ||
| <Box position="relative" width="max-content"> | ||
| <CheckerDense left={8} position="absolute" top={8} /> | ||
| <Box | ||
| bg="background" | ||
| border={1} | ||
| borderRadius="sm" | ||
| position="relative" | ||
| zIndex={1} | ||
| > | ||
| {children} | ||
| </Box> | ||
| </Box> | ||
| ); | ||
243 changes: 243 additions & 0 deletions
243
packages/gamut/src/DatePicker/Calendar/CalendarBody.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,243 @@ | ||
| import { css, states } from '@codecademy/gamut-styles'; | ||
| import styled from '@emotion/styled'; | ||
| import { useCallback, useEffect, useMemo, useRef } from 'react'; | ||
| import * as React from 'react'; | ||
|
|
||
| import { TextButton } from '../../Button'; | ||
| import { CalendarBodyProps } from './types'; | ||
| import { | ||
| getDatesWithRow, | ||
| getMonthGrid, | ||
| isDateDisabled, | ||
| isDateInRange, | ||
| isSameDay, | ||
| } from './utils/dateGrid'; | ||
| import { getWeekdayNames } from './utils/format'; | ||
| import { keyHandler } from './utils/keyHandler'; | ||
|
|
||
| const TableHeader = styled.th( | ||
| css({ | ||
| fontSize: 14, | ||
| fontWeight: 'base', | ||
| color: 'text-disabled', | ||
| textAlign: 'center', | ||
| }) | ||
| ); | ||
|
|
||
| const DateCell = styled.td( | ||
| css({ | ||
| padding: 0, | ||
| }) | ||
| ); | ||
|
|
||
| const DateButton = styled(TextButton)( | ||
| states({ | ||
| isToday: { | ||
aresnik11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| position: 'relative', | ||
| '&::after': { | ||
| content: '""', | ||
| position: 'absolute', | ||
| bottom: 4, | ||
| width: 4, | ||
| height: 4, | ||
| borderRadius: 'full', | ||
| bg: 'hyper', | ||
| }, | ||
| }, | ||
| isSelected: { | ||
aresnik11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| bg: 'text', | ||
| color: 'background', | ||
| '&:hover, &:focus': { | ||
| bg: 'secondary-hover', | ||
| color: 'background', | ||
| }, | ||
| '&::after': { | ||
| bg: 'background', | ||
| }, | ||
| }, | ||
| isRangeStart: { | ||
| borderRadiusRight: 'none', | ||
| }, | ||
| isRangeEnd: { | ||
| borderRadiusLeft: 'none', | ||
| }, | ||
| isInRange: { | ||
| bg: 'text-disabled', | ||
| color: 'background', | ||
| borderRadius: 'none', | ||
| '&:hover, &:focus': { | ||
| bg: 'secondary-hover', | ||
| color: 'background', | ||
| }, | ||
| '&::after': { | ||
| bg: 'background', | ||
| }, | ||
| }, | ||
| disabled: { | ||
| color: 'text-disabled', | ||
| textDecoration: 'line-through', | ||
| '&:hover': { | ||
| textDecoration: 'line-through', | ||
| }, | ||
| }, | ||
| }), | ||
| css({ | ||
| fontWeight: 'base', | ||
| width: '32px', | ||
| }) | ||
| ); | ||
|
|
||
| export const CalendarBody: React.FC<CalendarBodyProps> = ({ | ||
| displayDate, | ||
| selectedDate, | ||
| endDate = null, | ||
| disabledDates = [], | ||
| onDateSelect, | ||
| locale, | ||
| weekStartsOn = 0, | ||
| labelledById, | ||
| focusedDate, | ||
| onFocusedDateChange, | ||
| onDisplayDateChange, | ||
| onEscapeKeyPress, | ||
| hasAdjacentMonthRight, | ||
| hasAdjacentMonthLeft, | ||
| }) => { | ||
| const year = displayDate.getFullYear(); | ||
| const month = displayDate.getMonth(); | ||
| const weeks = getMonthGrid(year, month, weekStartsOn); | ||
| const weekdayLabels = getWeekdayNames('short', locale, weekStartsOn); | ||
| const weekdayFullNames = getWeekdayNames('long', locale, weekStartsOn); | ||
| const buttonRefs = useRef<Map<number, HTMLElement>>(new Map()); | ||
|
|
||
| const datesWithRow = useMemo(() => getDatesWithRow(weeks), [weeks]); | ||
| const focusTarget = focusedDate ?? selectedDate; | ||
|
|
||
| const isToday = useCallback( | ||
| (date: Date | null) => date !== null && isSameDay(date, new Date()), | ||
| [] | ||
| ); | ||
|
|
||
| const focusButton = useCallback((date: Date | null) => { | ||
| if (date === null) return; | ||
| const key = new Date( | ||
| date.getFullYear(), | ||
| date.getMonth(), | ||
| date.getDate() | ||
| ).getTime(); | ||
| buttonRefs.current.get(key)?.focus(); | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| if (focusTarget !== null) focusButton(focusTarget); | ||
| }, [focusTarget, focusButton]); | ||
|
|
||
| const handleKeyDown = useCallback( | ||
| (e: React.KeyboardEvent, date: Date) => | ||
| keyHandler( | ||
| e, | ||
| date, | ||
| onFocusedDateChange, | ||
| datesWithRow, | ||
| month, | ||
| year, | ||
| disabledDates, | ||
| onDateSelect, | ||
| onEscapeKeyPress, | ||
| onDisplayDateChange, | ||
| hasAdjacentMonthRight, | ||
| hasAdjacentMonthLeft | ||
| ), | ||
| [ | ||
| onFocusedDateChange, | ||
| datesWithRow, | ||
| month, | ||
| year, | ||
| disabledDates, | ||
| onDateSelect, | ||
| onEscapeKeyPress, | ||
| onDisplayDateChange, | ||
| hasAdjacentMonthLeft, | ||
| hasAdjacentMonthRight, | ||
| ] | ||
| ); | ||
|
|
||
| const setButtonRef = useCallback((date: Date, el: HTMLElement | null) => { | ||
| const k = new Date( | ||
| date.getFullYear(), | ||
| date.getMonth(), | ||
| date.getDate() | ||
| ).getTime(); | ||
| if (el) buttonRefs.current.set(k, el); | ||
| else buttonRefs.current.delete(k); | ||
| }, []); | ||
|
|
||
| return ( | ||
| <table aria-labelledby={labelledById} role="grid" width="100%"> | ||
| <thead> | ||
| <tr> | ||
| {weekdayLabels.map((label, i) => ( | ||
| <TableHeader abbr={weekdayFullNames[i]} key={label} scope="col"> | ||
| {label} | ||
| </TableHeader> | ||
| ))} | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| {weeks.map((week, rowIndex) => ( | ||
| <tr key={week.join('-')}> | ||
| {week.map((date, colIndex) => { | ||
| if (date === null) { | ||
| return ( | ||
| // fix this error | ||
| // eslint-disable-next-line react/no-array-index-key, jsx-a11y/control-has-associated-label | ||
| <DateCell | ||
| key={`empty-${rowIndex}-${colIndex}`} | ||
|
Check failure on line 195 in packages/gamut/src/DatePicker/Calendar/CalendarBody.tsx
|
||
| role="gridcell" | ||
| /> | ||
| ); | ||
| } | ||
| const selected = | ||
| isSameDay(date, selectedDate) || isSameDay(date, endDate); | ||
| const range = !!selectedDate && !!endDate; | ||
| const inRange = | ||
| range && isDateInRange(date, selectedDate, endDate); | ||
| const disabled = isDateDisabled(date, disabledDates); | ||
| const today = isToday(date); | ||
| // this is making the selected date a differnet color bc it is focused, look into further | ||
| const isFocused = | ||
| focusTarget !== null && isSameDay(date, focusTarget); | ||
|
|
||
| return ( | ||
| <DateCell | ||
| aria-selected={selected} | ||
| key={date.getTime()} | ||
| role="gridcell" | ||
| > | ||
| <DateButton | ||
| disabled={disabled} | ||
| isInRange={inRange} | ||
| isRangeEnd={range && isSameDay(date, endDate)} | ||
| isRangeStart={range && isSameDay(date, selectedDate)} | ||
| isSelected={selected} | ||
| isToday={today} | ||
| ref={(el) => setButtonRef(date, el as HTMLElement | null)} | ||
| tabIndex={isFocused ? 0 : -1} | ||
| variant="secondary" | ||
| onClick={() => onDateSelect(date)} | ||
| onFocus={() => onFocusedDateChange?.(date)} | ||
| onKeyDown={(e: React.KeyboardEvent) => | ||
| handleKeyDown(e, date) | ||
| } | ||
| > | ||
| {date.getDate()} | ||
| </DateButton> | ||
| </DateCell> | ||
| ); | ||
| })} | ||
| </tr> | ||
| ))} | ||
| </tbody> | ||
| </table> | ||
| ); | ||
| }; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.