Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 15 additions & 29 deletions packages/gamut/src/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { MiniArrowLeftIcon, MiniArrowRightIcon } from '@codecademy/gamut-icons';
import { useElementDir } from '@codecademy/gamut-styles';
import {
useCallback,
useEffect,
Expand All @@ -9,7 +7,6 @@ import {
useState,
} from 'react';

import { Box, FlexBox } from '../Box';
import { PopoverContainer } from '../PopoverContainer';
import { DatePickerCalendar } from './DatePickerCalendar';
import {
Expand All @@ -22,6 +19,7 @@ import type {
DatePickerRangeContextValue,
} from './DatePickerContext/types';
import { DatePickerInput } from './DatePickerInput';
import { DatePickerRangeInputWrapper } from './DatePickerInput/DatePickerRangeInputWrapper';
import type { DatePickerProps } from './types';
import { useResolvedLocale } from './utils/locale';
import { DEFAULT_DATE_PICKER_TRANSLATIONS } from './utils/translations';
Expand All @@ -36,6 +34,7 @@ export const DatePicker: React.FC<DatePickerProps> = (props) => {
inputSize,
quickActions,
placement = 'inline',
description,
} = props;
const [isCalendarOpen, setIsCalendarOpen] = useState(false);
const [focusGridSignal, setFocusGridSignal] = useState(false);
Expand All @@ -45,7 +44,6 @@ export const DatePicker: React.FC<DatePickerProps> = (props) => {
const inputRef = useRef<HTMLDivElement | null>(null);
const dialogId = useId();
const calendarDialogId = `datepicker-dialog-${dialogId.replace(/:/g, '')}`;
const isRtl = useElementDir() === 'rtl';

const clearGridFocusRequest = useCallback(() => {
setGridFocusRequested(false);
Expand Down Expand Up @@ -148,31 +146,19 @@ export const DatePicker: React.FC<DatePickerProps> = (props) => {
children
) : (
<>
<FlexBox
gap={inputSize === 'small' ? 4 : 8}
ref={inputRef}
width="fit-content"
>
{mode === 'range' ? (
<>
<DatePickerInput
name="datePickerInputStart"
rangePart="start"
size={inputSize}
/>
<Box alignSelf="center" mt={32}>
{isRtl ? <MiniArrowLeftIcon /> : <MiniArrowRightIcon />}
</Box>
<DatePickerInput
name="datePickerInputEnd"
rangePart="end"
size={inputSize}
/>
</>
) : (
<DatePickerInput size={inputSize} />
)}
</FlexBox>
{mode === 'range' ? (
<DatePickerRangeInputWrapper
description={description}
ref={inputRef}
size={inputSize}
/>
) : (
<DatePickerInput
description={description}
ref={inputRef}
size={inputSize}
/>
)}
<PopoverContainer
alignment="bottom-left"
allowPageInteraction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,12 @@ export const CalendarBody: React.FC<CalendarBodyProps> = ({
<thead>
<tr>
{weekdayLabels.map((label, i) => (
<TableHeader abbr={weekdayFullNames[i]} key={label} scope="col">
<TableHeader
abbr={weekdayFullNames[i]}
aria-label={weekdayFullNames[i]}
key={label}
scope="col"
>
{label}
</TableHeader>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createRef } from 'react';
import { getIsoFirstDayFromLocale } from '../../../utils/locale';
import { CalendarBody } from '../CalendarBody';
import { getMonthGrid } from '../utils/dateGrid';
import { formatDateForAriaLabel } from '../utils/format';
import { formatDateForAriaLabel, getWeekdayNames } from '../utils/format';

const displayDate = new Date(2024, 2, 1);
const focusedDate = new Date(2024, 2, 15);
Expand Down Expand Up @@ -203,14 +203,21 @@ describe('CalendarBody', () => {
await waitFor(() => expect(march15).toHaveFocus());
});

it('renders seven weekday column headers with scope and abbreviations', () => {
it('renders seven weekday column headers with full accessible names', () => {
const { view } = renderView();
const locale = new Intl.Locale('en-US');
const firstWeekday = getIsoFirstDayFromLocale(locale);
const fullNames = getWeekdayNames({
format: 'long',
locale,
firstWeekday,
});

const headers = view.getAllByRole('columnheader');
expect(headers).toHaveLength(7);
headers.forEach((th) => {
headers.forEach((th, i) => {
expect(th).toHaveAttribute('scope', 'col');
expect(th).toHaveAttribute('abbr');
expect(th).toHaveAccessibleName(fullNames[i]);
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { css, variant } from '@codecademy/gamut-styles';
import { StyleProps } from '@codecademy/variance';
import styled from '@emotion/styled';

import { Box, FlexBox } from '../../../Box';
import { FormError } from '../../../Form/elements/FormError';
import {
formFieldFocusStyles,
formFieldStyles,
inputSizeStyles,
} from '../../../Form/styles';
import {
DATE_PICKER_ERROR_SLOT_HEIGHT,
DATE_PICKER_FIELD_WIDTH,
} from '../../constants';

export const DatePickerInputShellContainer = styled(Box)(
css({
width: 'fit-content',
})
);

export const DatePickerInputShellField = styled(Box)(
css({
position: 'relative',
width: 'fit-content',
})
);

export const DatePickerInputShellErrorSpacer = styled(Box)(
css({
minHeight: DATE_PICKER_ERROR_SLOT_HEIGHT,
})
);

export const DatePickerInputShellError = styled(FormError)(
css({
left: 0,
position: 'absolute',
top: '100%',
width: DATE_PICKER_FIELD_WIDTH,
whiteSpace: 'normal',
})
);

const shellFocusStyles = variant({
variants: {
error: {
borderColor: 'feedback-error',
'&:hover': {
borderColor: 'feedback-error',
},
'&:focus': {
borderColor: 'feedback-error',
boxShadow: `inset 0 0 0 1px feedback-error`,
},
'&:focus-within': {
borderColor: 'feedback-error',
boxShadow: `inset 0 0 0 1px feedback-error`,
},
},
default: {
'&:focus-within': formFieldFocusStyles,
},
},
});

interface SegmentedShellProps
extends StyleProps<typeof inputSizeStyles>,
StyleProps<typeof shellFocusStyles> {}

/**
* Shell uses the same styles as `Input`. `formFieldStyles` targets `&:focus`, but the host is a
* `div` — focus is on inner spinbuttons, so we mirror `Input` focus visuals with `&:focus-within`.
*/
export const SegmentedShell = styled(FlexBox)<SegmentedShellProps>(
formFieldStyles,
inputSizeStyles,
shellFocusStyles,
css({
flexGrow: 0,
flexShrink: 0,
maxWidth: DATE_PICKER_FIELD_WIDTH,
minWidth: DATE_PICKER_FIELD_WIDTH,
width: DATE_PICKER_FIELD_WIDTH,
})
);
Loading
Loading