Skip to content

Commit

Permalink
feat(Calendar): added component and tests (#1154)
Browse files Browse the repository at this point in the history
issue - #667
  • Loading branch information
gizeasy committed May 19, 2021
1 parent 6d95f87 commit 93751ca
Show file tree
Hide file tree
Showing 30 changed files with 1,896 additions and 2 deletions.
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -11,6 +11,7 @@
"@bem-react/classname": "^1.5.8",
"@bem-react/classnames": "^1.3.9",
"compute-scroll-into-view": "^1.0.14",
"date-fns": "^2.17.0",
"react-textarea-autosize": "^7.1.2",
"react-transition-group": "^4.4.1",
"tslib": "^2.1.0"
Expand Down Expand Up @@ -39,7 +40,8 @@
"pre-build": "node builder/preBuild.js --config=./build.config.js",
"test": "yarn lint && yarn tsc-dry-run && yarn unit",
"unit": "jest",
"unit:watch": "jest --watch"
"unit:watch": "jest --watch",
"unit:clear": "jest --clearCache"
},
"browserslist": [
"last 2 version",
Expand Down
3 changes: 3 additions & 0 deletions src/components/Calendar/Calendar.css
@@ -0,0 +1,3 @@
.Calendar {
--calendar-cell-width: calc(var(--control-height-s) + var(--space-2xs));
}
42 changes: 42 additions & 0 deletions src/components/Calendar/Calendar.tsx
@@ -0,0 +1,42 @@
import './Calendar.css';

import React from 'react';

import { cn } from '../../utils/bem';
import { getSizeByMap } from '../../utils/getSizeByMap';

import { CalendarViewOneMount } from './CalendarViewOneMount/CalendarViewOneMount';
import { CalendarViewSlider } from './CalendarViewSlider/CalendarViewSlider';
import { CalendarViewTwoMount } from './CalendarViewTwoMount/CalendarViewTwoMount';
import {
CalendarComponent,
CalendarPropView,
calendarPropViewDefault,
CalendarViewComponent,
} from './helpers';

export const cnCalendar = cn('Calendar');

const viewMap: Record<CalendarPropView, CalendarViewComponent> = {
oneMount: CalendarViewOneMount,
twoMounts: CalendarViewTwoMount,
slider: CalendarViewSlider,
};

export const Calendar: CalendarComponent = React.forwardRef((props, ref) => {
const { view = calendarPropViewDefault, className, ...otherProps } = props;

const ViewComponent = getSizeByMap(viewMap, view);

return <ViewComponent {...otherProps} ref={ref} className={cnCalendar(null, [className])} />;
});

export * from './CalendarСell/CalendarСell';
export * from './CalendarDay/CalendarDay';
export * from './CalendarMount/CalendarMount';
export * from './CalendarMountLabel/CalendarMountLabel';
export * from './CalendarMountToggler/CalendarMountToggler';
export * from './CalendarSlider/CalendarSlider';
export * from './CalendarViewOneMount/CalendarViewOneMount';
export * from './CalendarViewSlider/CalendarViewSlider';
export * from './CalendarViewTwoMount/CalendarViewTwoMount';
87 changes: 87 additions & 0 deletions src/components/Calendar/CalendarDay/CalendarDay.css
@@ -0,0 +1,87 @@
.CalendarDay {
--day-bg-color: transparent;
--day-bg-color-hover: transparent;
--day-border-color: var(--color-control-bg-primary);
--day-border-color-hover: var(--color-control-bg-primary-hover);
--day-event-color: var(--color-control-bg-primary);
--day-event-color-hover: var(--color-control-bg-primary-hover);
--day-text-color: var(--color-typo-primary);
--day-text-color-hover: var(--color-control-bg-primary-hover);
--bg-color: var(--day-bg-color);
--event-color: var(--day-event-color);
--border-color: var(--day-border-color);
--text-color: var(--day-text-color);
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: var(--control-height-s);
height: var(--control-height-s);
color: var(--text-color);
background: var(--bg-color);
border-radius: 50%;
font-size: var(--size-text-s);
line-height: var(--control-height-s);
transition: background 0.2s, color 0.2s, box-shadow 0.2s;

&:hover {
--bg-color: var(--day-bg-color-hover);
--event-color: var(--day-event-color-hover);
--border-color: var(--day-border-color-hover);
--text-color: var(--day-text-color-hover);
}

&_selected {
--day-bg-color: var(--color-control-bg-primary);
--day-bg-color-hover: var(--color-control-bg-primary-hover);
--day-border-color: transparent;
--day-border-color-hover: transparent;
--day-event-color: var(--color-control-typo-primary);
--day-event-color-hover: var(--color-control-typo-primary);
--day-text-color: var(--color-typo-primary);
--day-text-color-hover: var(--color-typo-primary);
}

&:not(&_disabled) {
cursor: pointer;
}

&_disabled {
--day-bg-color: transparent;
--day-bg-color-hover: transparent;
--day-border-color: var(--color-control-bg-primary);
--day-border-color-hover: var(--color-control-bg-primary);
--day-event-color: var(--color-control-typo-disable);
--day-event-color-hover: var(--color-control-typo-disable);
--day-text-color: var(--color-control-typo-disable);
--day-text-color-hover: var(--color-control-typo-disable);
}

&_selected.CalendarDay_disabled {
--day-bg-color: var(--color-control-bg-disable);
--day-bg-color-hover: var(--color-control-bg-disable);
--day-border-color: transparent;
--day-border-color-hover: transparent;
--day-event-color: var(--color-control-typo-disable);
--day-event-color-hover: var(--color-control-typo-disable);
--day-text-color: var(--color-control-typo-disable);
--day-text-color-hover: var(--color-control-typo-disable);
}

&_event {
&::before {
content: '';
position: absolute;
bottom: var(--space-2xs);
width: var(--space-2xs);
height: var(--space-2xs);
background: var(--event-color);
border-radius: 50%;
transition: background 0.2s;
}
}

&_today {
box-shadow: inset 0 0 0 1.3px var(--border-color);
}
}
44 changes: 44 additions & 0 deletions src/components/Calendar/CalendarDay/CalendarDay.tsx
@@ -0,0 +1,44 @@
import './CalendarDay.css';

import React from 'react';
import { classnames } from '@bem-react/classnames';

import { cn } from '../../../utils/bem';
import { PropsWithJsxAttributes } from '../../../utils/types/PropsWithJsxAttributes';
import { useTheme } from '../../Theme/Theme';

export type CalendarDayProps = PropsWithJsxAttributes<
{
number: number | string;
today?: boolean;
selected?: boolean;
disabled?: boolean;
event?: boolean;
role?: never;
children?: never;
},
'div'
>;

export const cnCalendarDay = cn('CalendarDay');

export const CalendarDay = React.forwardRef<HTMLDivElement, CalendarDayProps>((props, ref) => {
const { number, today, selected, event, disabled, ...otherProps } = props;
const { themeClassNames } = useTheme();

const className =
selected && !disabled
? classnames(props.className, themeClassNames.color.accent)
: props.className;

return (
<div
{...otherProps}
ref={ref}
className={cnCalendarDay({ event, today, disabled, selected }, [className])}
role="button"
>
{number}
</div>
);
});
8 changes: 8 additions & 0 deletions src/components/Calendar/CalendarMount/CalendarMount.css
@@ -0,0 +1,8 @@
.CalendarMount {
display: grid;
grid-template-columns: repeat(
7,
var(--calendar-cell-width, calc(var(--control-height-s) + var(--space-2xs)))
);
justify-content: start;
}
46 changes: 46 additions & 0 deletions src/components/Calendar/CalendarMount/CalendarMount.tsx
@@ -0,0 +1,46 @@
import './CalendarMount.css';

import React from 'react';

import { cn } from '../../../utils/bem';
import { PropsWithJsxAttributes } from '../../../utils/types/PropsWithJsxAttributes';
import { Text } from '../../Text/Text';
import { CalendarDay, CalendarDayProps } from '../CalendarDay/CalendarDay';
import { CalendarCell, CalendarCellProps } from '../CalendarСell/CalendarСell';

type DayOfMount = Omit<CalendarDayProps & CalendarCellProps, 'ref'>;

export type CalendarMountProps = PropsWithJsxAttributes<
{
children?: never;
daysOfWeek: string[];
daysOfMount: DayOfMount[];
},
'div'
>;

export const cnCalendarMount = cn('CalendarMount');

export const CalendarMount: React.FC<CalendarMountProps> = (props) => {
const { className, daysOfWeek, daysOfMount, ...otherProps } = props;

return (
<div {...otherProps} className={cnCalendarMount(null, [className])}>
{daysOfWeek.map((item, index) => (
<CalendarCell key={cnCalendarMount('DayOfWeek', { index, item })}>
<Text as="span" view="ghost" size="2xs" transform="uppercase">
{item}
</Text>
</CalendarCell>
))}
{daysOfMount.map(({ range, ...dayProps }, index) => (
<CalendarCell
key={cnCalendarMount('DayOfMount', { index, number: dayProps.number })}
range={range}
>
<CalendarDay {...dayProps} />
</CalendarCell>
))}
</div>
);
};
@@ -0,0 +1,3 @@
.CalendarMountLabel {
text-transform: capitalize;
}
34 changes: 34 additions & 0 deletions src/components/Calendar/CalendarMountLabel/CalendarMountLabel.tsx
@@ -0,0 +1,34 @@
import './CalendarMountLabel.css';

import React from 'react';

import { cn } from '../../../utils/bem';
import { PropsWithHTMLAttributes } from '../../../utils/types/PropsWithHTMLAttributes';
import { Text } from '../../Text/Text';

export type CalendarMountLabelProps = PropsWithHTMLAttributes<
{
label: string;
children?: never;
},
HTMLDivElement
>;

export const cnCalendarMountLabel = cn('CalendarMountLabel');

export const CalendarMountLabel: React.FC<CalendarMountLabelProps> = (props) => {
const { label, className, ...otherProps } = props;

return (
<Text
{...otherProps}
className={cnCalendarMountLabel(null, [className])}
as="span"
size="s"
align="center"
weight="bold"
>
{label}
</Text>
);
};
@@ -0,0 +1,24 @@
.CalendarMountToggler {
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
width: calc(var(--calendar-cell-width) * 7);

&:not(&_withPrevButton) {
padding-left: var(--control-height-s);
}

&:not(&_withNextButton) {
padding-right: var(--control-height-s);
}

&-Label {
flex: 1;
margin: 0 var(--space-xs);
}

&-Button_direction_prev {
transform: rotate(180deg);
}
}
@@ -0,0 +1,57 @@
import './CalendarMountToggler.css';

import React from 'react';

import { IconForward } from '../../../icons/IconForward/IconForward';
import { cn } from '../../../utils/bem';
import { PropsWithJsxAttributes } from '../../../utils/types/PropsWithJsxAttributes';
import { Button } from '../../Button/Button';
import { CalendarMountLabel } from '../CalendarMountLabel/CalendarMountLabel';

export type CalendarMountTogglerProps = PropsWithJsxAttributes<
{
prevOnClick?: React.EventHandler<React.MouseEvent<HTMLDivElement>>;
nextOnClick?: React.EventHandler<React.MouseEvent<HTMLDivElement>>;
label: string;
children?: never;
},
'div'
>;

export const cnCalendarMountToggler = cn('CalendarMountToggler');

export const CalendarMountToggler: React.FC<CalendarMountTogglerProps> = (props) => {
const { label, className, prevOnClick, nextOnClick, ...otherProps } = props;

return (
<div
{...otherProps}
className={cnCalendarMountToggler(
{ withPrevButton: Boolean(prevOnClick), withNextButton: Boolean(nextOnClick) },
[className],
)}
>
{prevOnClick && (
<Button
className={cnCalendarMountToggler('Button', { direction: 'prev' })}
onClick={prevOnClick}
iconLeft={IconForward}
size="s"
view="clear"
iconSize="s"
/>
)}
<CalendarMountLabel className={cnCalendarMountToggler('Label')} label={label} />
{nextOnClick && (
<Button
className={cnCalendarMountToggler('Button', { direction: 'next' })}
onClick={nextOnClick}
iconLeft={IconForward}
size="s"
view="clear"
iconSize="s"
/>
)}
</div>
);
};

0 comments on commit 93751ca

Please sign in to comment.