Skip to content

Commit a525e4e

Browse files
shiba-codesLena Rashkovan
and
Lena Rashkovan
authored
feat(calendar): add experimental component (#476)
* fix(text-field): padding -> margin to make outline nice * feat(calendar): add base component * feat(calendar): restyle button's focus state --------- Co-authored-by: Lena Rashkovan <[email protected]>
1 parent d89235a commit a525e4e

File tree

4 files changed

+172
-1
lines changed

4 files changed

+172
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import React, { ReactElement } from 'react';
2+
import {
3+
Calendar as BaseCalendar,
4+
CalendarProps as BaseCalendarProps,
5+
CalendarCell,
6+
CalendarGrid as BaseCalendarGrid,
7+
CalendarGridHeader,
8+
CalendarGridBody,
9+
CalendarHeaderCell,
10+
Heading as BaseHeading,
11+
DateValue,
12+
Button as BaseButton
13+
} from 'react-aria-components';
14+
import styled from 'styled-components';
15+
import { ChevronLeftIcon, ChevronRightIcon } from '../../../icons';
16+
import { getSemanticValue } from '../../../essentials/experimental';
17+
import { textStyles } from '../Text/Text';
18+
import { get } from '../../../utils/experimental/themeGet';
19+
20+
const Header = styled.header`
21+
display: flex;
22+
align-items: center;
23+
justify-content: space-between;
24+
padding-bottom: ${get('space.3')};
25+
`;
26+
27+
const Button = styled(BaseButton)`
28+
appearance: none;
29+
background: none;
30+
border: none;
31+
display: flex;
32+
cursor: pointer;
33+
margin: 0;
34+
padding: 0;
35+
color: ${getSemanticValue('on-surface')};
36+
outline: 0;
37+
38+
&[data-focused] {
39+
color: ${getSemanticValue('accent')};
40+
}
41+
42+
&[data-disabled] {
43+
opacity: 0;
44+
}
45+
`;
46+
47+
const Heading = styled(BaseHeading)`
48+
margin: 0;
49+
color: ${getSemanticValue('on-surface')};
50+
${textStyles.variants.title2}
51+
`;
52+
53+
const CalendarGrid = styled(BaseCalendarGrid)`
54+
border-collapse: collapse;
55+
border-spacing: 0;
56+
57+
td {
58+
padding: 0;
59+
}
60+
61+
th {
62+
padding: 0 0 ${get('space.1')};
63+
}
64+
`;
65+
66+
const WeekDay = styled(CalendarHeaderCell)`
67+
color: ${getSemanticValue('on-surface')};
68+
${textStyles.variants.label2}
69+
`;
70+
71+
const Day = styled(CalendarCell)`
72+
display: flex;
73+
align-items: center;
74+
justify-content: center;
75+
color: ${getSemanticValue('on-surface')};
76+
width: 2.5rem;
77+
height: 2.5rem;
78+
border-radius: 50%;
79+
${textStyles.variants.label2}
80+
transition: background ease 200ms;
81+
82+
&[data-focused] {
83+
outline: ${getSemanticValue('accent')} solid 0.125rem;
84+
}
85+
86+
&[data-hovered] {
87+
cursor: pointer;
88+
background: ${getSemanticValue('surface-variant')};
89+
}
90+
91+
&[data-selected] {
92+
outline: 0;
93+
background: ${getSemanticValue('interactive-container')};
94+
color: ${getSemanticValue('on-interactive-container')};
95+
}
96+
97+
&[data-disabled] {
98+
opacity: 0.38;
99+
}
100+
101+
&[data-outside-month] {
102+
opacity: 0;
103+
}
104+
`;
105+
106+
type CalendarProps<T extends DateValue> = BaseCalendarProps<T>;
107+
108+
function Calendar<T extends DateValue>(props: CalendarProps<T>): ReactElement {
109+
return (
110+
<BaseCalendar {...props}>
111+
<Header>
112+
<Button slot="previous">
113+
<ChevronLeftIcon size={24} />
114+
</Button>
115+
<Heading />
116+
<Button slot="next">
117+
<ChevronRightIcon size={24} />
118+
</Button>
119+
</Header>
120+
<CalendarGrid weekdayStyle="short">
121+
<CalendarGridHeader>{weekDay => <WeekDay>{weekDay}</WeekDay>}</CalendarGridHeader>
122+
<CalendarGridBody>
123+
{date => (
124+
<Day date={date}>
125+
{({ formattedDate }) => (formattedDate.length > 1 ? formattedDate : `0${formattedDate}`)}
126+
</Day>
127+
)}
128+
</CalendarGridBody>
129+
</CalendarGrid>
130+
</BaseCalendar>
131+
);
132+
}
133+
134+
export { Calendar };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { I18nProvider } from 'react-aria-components';
2+
import { getLocalTimeZone, today } from '@internationalized/date';
3+
import { StoryObj, Meta } from '@storybook/react';
4+
import React from 'react';
5+
import { Calendar } from '../Calendar';
6+
7+
const meta: Meta = {
8+
title: 'Experimental/Components/Calendar',
9+
component: Calendar,
10+
parameters: {
11+
layout: 'centered'
12+
},
13+
decorators: [
14+
Story => (
15+
<I18nProvider locale="de-DE">
16+
<Story />
17+
</I18nProvider>
18+
)
19+
],
20+
args: {
21+
'aria-label': 'Appointment date',
22+
defaultValue: today(getLocalTimeZone())
23+
}
24+
};
25+
26+
export default meta;
27+
28+
type Story = StoryObj<typeof Calendar>;
29+
30+
export const Default: Story = {};
31+
32+
export const WithMinValue: Story = {
33+
args: {
34+
minValue: today(getLocalTimeZone())
35+
}
36+
};

src/components/experimental/TextField/TextField.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const TopLine = styled.div`
6969
/* stylelint-disable selector-type-case, selector-type-no-unknown */
7070
& > :not(${InnerWrapper}) {
7171
flex-shrink: 0;
72-
padding-top: ${get('space.2')};
72+
margin-top: ${get('space.2')};
7373
}
7474
7575
&:hover {

src/components/experimental/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { Button } from './Button/Button';
2+
export { Calendar } from './Calendar/Calendar';
23
export { Chip } from './Chip/Chip';
34
export { Label } from './Label/Label';
45
export { Text } from './Text/Text';

0 commit comments

Comments
 (0)