@bitrise/bitkit 10.34.0 → 10.35.0-alpha-datepicker-rewrite.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +9 -20
  2. package/src/Components/DatePicker/DatePicker.context.ts +14 -0
  3. package/src/Components/DatePicker/DatePicker.stories.tsx +16 -0
  4. package/src/Components/DatePicker/DatePicker.test.tsx +94 -0
  5. package/src/Components/DatePicker/DatePicker.tsx +178 -0
  6. package/src/Components/DatePicker/DatePickerDay.theme.ts +108 -0
  7. package/src/Components/DatePicker/DatePickerDay.tsx +137 -0
  8. package/src/Components/DatePicker/DatePickerFooter.tsx +41 -0
  9. package/src/Components/DatePicker/DatePickerGrid.tsx +10 -0
  10. package/src/Components/DatePicker/DatePickerHeader.tsx +72 -0
  11. package/src/Components/DatePicker/DatePickerMonth.tsx +94 -0
  12. package/src/Components/DatePicker/DatePickerMonthSelector.tsx +132 -0
  13. package/src/Components/DatePicker/useDateRange.ts +48 -0
  14. package/src/Components/DatePicker/useViewDate.ts +35 -0
  15. package/src/Components/NumberInput/NumberInput.theme.ts +36 -0
  16. package/src/Foundations/Shadows/Shadows.ts +1 -0
  17. package/src/Foundations/Sizes/Sizes.ts +1 -0
  18. package/src/Old/hooks/index.ts +0 -1
  19. package/src/index.ts +3 -0
  20. package/src/old.ts +0 -3
  21. package/src/theme.ts +4 -0
  22. package/src/tsconfig.tsbuildinfo +1 -1
  23. package/src/utils/utils.ts +8 -0
  24. package/src/Old/DatePicker/DatePicker.css +0 -74
  25. package/src/Old/DatePicker/DatePicker.tsx +0 -194
  26. package/src/Old/DatePicker/DatePickerDay.tsx +0 -72
  27. package/src/Old/DatePicker/DatePickerGrid.tsx +0 -12
  28. package/src/Old/DatePicker/DatePickerMonth.tsx +0 -87
  29. package/src/Old/hooks/useMediaQuery.ts +0 -91
@@ -0,0 +1,72 @@
1
+ import { ReactNode } from 'react';
2
+ import { createContext } from '@chakra-ui/react-utils';
3
+ import { useObjectMemo } from '../../utils/utils';
4
+ import Box, { BoxProps } from '../Box/Box';
5
+ import IconButton from '../IconButton/IconButton';
6
+
7
+ const [HeaderContext, useHeaderContext] = createContext<{
8
+ onPrevious: () => void;
9
+ onNext: () => void;
10
+ controls: 'left' | 'right' | 'both';
11
+ }>();
12
+ export const DatePickerHeaderContent = (props: BoxProps) => (
13
+ <Box alignSelf="center" display="flex" justifyContent="center" gap="8" {...props} />
14
+ );
15
+ export const DatePickerHeaderPrevious = ({ label }: { label: string }) => {
16
+ const { onPrevious, controls } = useHeaderContext();
17
+ return (
18
+ <IconButton
19
+ size="small"
20
+ alignSelf="start"
21
+ visibility={controls === 'right' ? 'hidden' : undefined}
22
+ aria-label={label}
23
+ variant="tertiary"
24
+ as="button"
25
+ onClick={onPrevious}
26
+ iconName="ChevronLeft"
27
+ isTooltipDisabled
28
+ />
29
+ );
30
+ };
31
+ export const DatePickerHeaderNext = ({ label }: { label: string }) => {
32
+ const { onNext, controls } = useHeaderContext();
33
+ return (
34
+ <IconButton
35
+ alignSelf="end"
36
+ visibility={controls === 'left' ? 'hidden' : undefined}
37
+ size="small"
38
+ aria-label={label}
39
+ variant="tertiary"
40
+ onClick={onNext}
41
+ iconName="ChevronRight"
42
+ isTooltipDisabled
43
+ />
44
+ );
45
+ };
46
+
47
+ const DatePickerHeader = ({
48
+ onPrevious,
49
+ onNext,
50
+ children,
51
+ controls = 'both',
52
+ }: {
53
+ onPrevious: () => void;
54
+ onNext: () => void;
55
+ controls?: 'left' | 'right' | 'both';
56
+ children?: ReactNode;
57
+ }): JSX.Element => {
58
+ const ctx = useObjectMemo({ onPrevious, onNext, controls });
59
+ return (
60
+ <Box
61
+ marginBottom="24"
62
+ width="17.5rem"
63
+ display="grid"
64
+ gridTemplateColumns="2rem auto 2rem"
65
+ alignItems="center"
66
+ gap="4"
67
+ >
68
+ <HeaderContext value={ctx}>{children}</HeaderContext>
69
+ </Box>
70
+ );
71
+ };
72
+ export default DatePickerHeader;
@@ -0,0 +1,94 @@
1
+ import { createElement, Fragment, useCallback, useId } from 'react';
2
+ import { DateTime, Info } from 'luxon';
3
+ import {
4
+ NumberDecrementStepper,
5
+ NumberIncrementStepper,
6
+ NumberInput,
7
+ NumberInputField,
8
+ NumberInputStepper,
9
+ } from '@chakra-ui/react';
10
+ import { useObjectMemo } from '../../utils/utils';
11
+ import Button from '../Button/Button';
12
+ import Box from '../Box/Box';
13
+ import Icon from '../Icon/Icon';
14
+ import Text from '../Text/Text';
15
+ import DatePickerGrid from './DatePickerGrid';
16
+ import DatePickerDay, { DatePickerDayContext } from './DatePickerDay';
17
+ import DatePickerHeader, {
18
+ DatePickerHeaderContent,
19
+ DatePickerHeaderNext,
20
+ DatePickerHeaderPrevious,
21
+ } from './DatePickerHeader';
22
+
23
+ const daysOfTheWeek = Info.weekdays('short');
24
+ const daysCount = 6 * 7;
25
+
26
+ interface Props {
27
+ controls: 'left' | 'right' | 'both';
28
+ onViewDateChange: (viewDate: DateTime) => void;
29
+ onMonthClick: () => void;
30
+ viewDate: DateTime;
31
+ }
32
+
33
+ const days = createElement(
34
+ Fragment,
35
+ {},
36
+ ...Array.from({ length: daysCount }).map((_, i) => <DatePickerDay n={i + 1} />),
37
+ );
38
+ const DatePickerMonth = ({ controls, onViewDateChange, viewDate, onMonthClick }: Props) => {
39
+ const onNextMonth = useCallback(() => onViewDateChange(viewDate.plus({ months: 1 })), [onViewDateChange, viewDate]);
40
+ const onPreviousMonth = useCallback(
41
+ () => onViewDateChange(viewDate.minus({ months: 1 })),
42
+ [onViewDateChange, viewDate],
43
+ );
44
+
45
+ const dayContext = useObjectMemo({ onPreviousMonth, onNextMonth, viewDate });
46
+
47
+ const monthLabelId = useId();
48
+ return (
49
+ <Box>
50
+ <DatePickerHeader onPrevious={onPreviousMonth} onNext={onNextMonth} controls={controls}>
51
+ <DatePickerHeaderPrevious label="previous month" />
52
+ <DatePickerHeaderContent id={monthLabelId}>
53
+ <Button onClick={onMonthClick} size="small" variant="tertiary" flexShrink={0} rightIconName="ChevronDown">
54
+ {viewDate.monthLong}
55
+ </Button>
56
+ <NumberInput
57
+ aria-label="year"
58
+ flexShrink={1}
59
+ size="small"
60
+ min={1990}
61
+ w="4.5rem"
62
+ max={2100}
63
+ value={viewDate.year}
64
+ onChange={(_, year) => onViewDateChange(viewDate.set({ year }))}
65
+ >
66
+ <NumberInputField />
67
+ <NumberInputStepper>
68
+ <NumberIncrementStepper>
69
+ <Icon size="16" name="ChevronUp" />
70
+ </NumberIncrementStepper>
71
+ <NumberDecrementStepper>
72
+ <Icon size="16" name="ChevronDown" />
73
+ </NumberDecrementStepper>
74
+ </NumberInputStepper>
75
+ </NumberInput>
76
+ </DatePickerHeaderContent>
77
+ <DatePickerHeaderNext label="next month" />
78
+ </DatePickerHeader>
79
+
80
+ <DatePickerGrid alignItems="center" justifyItems="center" gridTemplateRows="2rem">
81
+ {daysOfTheWeek.map((day) => (
82
+ <Text key={day} size="2" color="neutral.50" textTransform="capitalize">
83
+ {day}
84
+ </Text>
85
+ ))}
86
+ </DatePickerGrid>
87
+ <DatePickerGrid aria-labelledby={monthLabelId} role="listbox" paddingTop="8">
88
+ <DatePickerDayContext value={dayContext}>{days}</DatePickerDayContext>
89
+ </DatePickerGrid>
90
+ </Box>
91
+ );
92
+ };
93
+
94
+ export default DatePickerMonth;
@@ -0,0 +1,132 @@
1
+ import { ReactNode, useCallback, useEffect, useId, useRef, useState } from 'react';
2
+ import {
3
+ NumberDecrementStepper,
4
+ NumberIncrementStepper,
5
+ NumberInput,
6
+ NumberInputField,
7
+ NumberInputStepper,
8
+ useMultiStyleConfig,
9
+ } from '@chakra-ui/react';
10
+ import { DateTime, Info } from 'luxon';
11
+ import Box from '../Box/Box';
12
+ import Text from '../Text/Text';
13
+ import Icon from '../Icon/Icon';
14
+ import DatePickerHeader, {
15
+ DatePickerHeaderContent,
16
+ DatePickerHeaderNext,
17
+ DatePickerHeaderPrevious,
18
+ } from './DatePickerHeader';
19
+
20
+ const DatePickerMonthItem = ({
21
+ children,
22
+ selected,
23
+ 'aria-label': label,
24
+ onClick,
25
+ n,
26
+ id,
27
+ }: {
28
+ id?: string;
29
+ 'aria-label': string;
30
+ children: ReactNode;
31
+ selected: boolean;
32
+ n: number;
33
+ onClick: (n: number) => void;
34
+ }) => {
35
+ const { text, day } = useMultiStyleConfig('DatePickerDay', {
36
+ selectable: true,
37
+ selection: selected ? 'incomplete' : undefined,
38
+ currentMonth: true,
39
+ });
40
+ return (
41
+ <Box
42
+ id={id}
43
+ aria-selected={selected ? true : undefined}
44
+ aria-label={label}
45
+ as="button"
46
+ role="option"
47
+ sx={day}
48
+ onClick={() => onClick(n)}
49
+ width="48"
50
+ >
51
+ <Text sx={{ ...text, width: '48' }}>{children}</Text>
52
+ </Box>
53
+ );
54
+ };
55
+
56
+ const monthNames = Info.months('short');
57
+ const longMonthNames = Info.months('long');
58
+ const DatePickerMonthSelector = ({
59
+ viewDate,
60
+ onMonthSelected,
61
+ }: {
62
+ viewDate: DateTime;
63
+ onMonthSelected: (month: number, year: number) => void;
64
+ }): JSX.Element => {
65
+ const [selectedYear, setSelectedYear] = useState(viewDate.year);
66
+ const onPreviousYear = useCallback(() => setSelectedYear((year) => year - 1), []);
67
+ const onNextYear = useCallback(() => setSelectedYear((year) => year + 1), []);
68
+ const yearRef = useRef<HTMLInputElement>(null);
69
+ useEffect(() => yearRef.current?.focus(), []);
70
+ const monthClicked = useCallback((m: number) => onMonthSelected(m, selectedYear), [selectedYear]);
71
+ const monthId = useId();
72
+ return (
73
+ <Box>
74
+ <DatePickerHeader onPrevious={onPreviousYear} onNext={onNextYear}>
75
+ <DatePickerHeaderPrevious label="previous year" />
76
+ <DatePickerHeaderContent>
77
+ <NumberInput
78
+ aria-label="year"
79
+ flexShrink={1}
80
+ size="small"
81
+ min={1990}
82
+ w="4.5rem"
83
+ max={2100}
84
+ value={selectedYear}
85
+ onChange={(_, year) => {
86
+ setSelectedYear(year);
87
+ }}
88
+ >
89
+ <NumberInputField ref={yearRef} />
90
+ <NumberInputStepper>
91
+ <NumberIncrementStepper>
92
+ <Icon size="16" name="ChevronUp" />
93
+ </NumberIncrementStepper>
94
+ <NumberDecrementStepper>
95
+ <Icon size="16" name="ChevronDown" />
96
+ </NumberDecrementStepper>
97
+ </NumberInputStepper>
98
+ </NumberInput>
99
+ </DatePickerHeaderContent>
100
+ <DatePickerHeaderNext label="next year" />
101
+ </DatePickerHeader>
102
+ <Box
103
+ display="grid"
104
+ rowGap="24"
105
+ alignItems="center"
106
+ justifyItems="center"
107
+ gridTemplateColumns="repeat(3,1fr)"
108
+ gridTemplateRows="repeat(4, 1fr)"
109
+ role="listbox"
110
+ aria-label="month"
111
+ aria-activedescendant={monthId}
112
+ >
113
+ {monthNames.map((month, idx) => {
114
+ const selected = viewDate.month === idx + 1;
115
+ return (
116
+ <DatePickerMonthItem
117
+ id={selected ? monthId : undefined}
118
+ onClick={monthClicked}
119
+ n={idx + 1}
120
+ aria-label={longMonthNames[idx]}
121
+ selected={selected}
122
+ key={month}
123
+ >
124
+ {month}
125
+ </DatePickerMonthItem>
126
+ );
127
+ })}
128
+ </Box>
129
+ </Box>
130
+ );
131
+ };
132
+ export default DatePickerMonthSelector;
@@ -0,0 +1,48 @@
1
+ import { useMemo } from 'react';
2
+ import { DateTime } from 'luxon';
3
+
4
+ export class DateRange {
5
+ public from?: DateTime;
6
+
7
+ public to?: DateTime;
8
+
9
+ constructor(from?: DateTime, to?: DateTime) {
10
+ this.from = from;
11
+ this.to = to;
12
+ }
13
+
14
+ contains(date: DateTime): boolean {
15
+ if (this.from && this.to) {
16
+ return this.to > date && this.from < date;
17
+ }
18
+ if (this.from && !this.to) {
19
+ return this.from < date;
20
+ }
21
+ if (this.to && !this.from) {
22
+ return this.to > date;
23
+ }
24
+ return true;
25
+ }
26
+ }
27
+
28
+ function useDateRange(range: [DateTime | undefined, DateTime | undefined]): DateRange;
29
+ function useDateRange(from?: DateTime, to?: DateTime): DateRange;
30
+ function useDateRange(arg1?: DateTime | [DateTime | undefined, DateTime | undefined], arg2?: DateTime): DateRange {
31
+ let from = arg1;
32
+ let to = arg2;
33
+ if (!to && Array.isArray(from)) {
34
+ to = DateTime.max(...(from.filter(Boolean) as DateTime[]));
35
+ from = DateTime.min(...(from.filter(Boolean) as DateTime[]));
36
+ } else {
37
+ from = from as DateTime | undefined;
38
+ to = to as DateTime | undefined;
39
+ }
40
+ const fromParts = from?.toObject();
41
+ const toParts = to?.toObject();
42
+ return useMemo(
43
+ () => new DateRange(from as DateTime | undefined, to as DateTime | undefined),
44
+ [fromParts?.year, fromParts?.month, fromParts?.day, toParts?.year, toParts?.month, toParts?.day],
45
+ );
46
+ }
47
+
48
+ export default useDateRange;
@@ -0,0 +1,35 @@
1
+ import { useCallback, useReducer } from 'react';
2
+ import { DateObjectUnits, DateTime } from 'luxon';
3
+
4
+ function useViewDate({ initalView }: { initalView?: DateTime }): {
5
+ leftViewDate: DateTime;
6
+ rightViewDate: DateTime;
7
+ updateLeftViewDate: (date: DateTime | DateObjectUnits) => void;
8
+ updateRightViewDate: (date: DateTime | DateObjectUnits) => void;
9
+ } {
10
+ const initLeft = (initalView || DateTime.now()).startOf('month');
11
+ const initRight = initLeft.plus({ months: 1 }).startOf('month');
12
+ const initialState = { left: initLeft, right: initRight };
13
+ function viewDateReducer(
14
+ state: typeof initialState,
15
+ action: { left: DateTime | DateObjectUnits } | { right: DateTime | DateObjectUnits },
16
+ ) {
17
+ if ('left' in action) {
18
+ const left = action.left instanceof DateTime ? action.left : state.left.set(action.left);
19
+ return { left, right: left.plus({ months: 1 }).startOf('month') };
20
+ }
21
+ const right = action.right instanceof DateTime ? action.right : state.right.set(action.right);
22
+ return { left: right.minus({ months: 1 }).startOf('month'), right };
23
+ }
24
+ const [{ left: leftViewDate, right: rightViewDate }, updateViewDate] = useReducer(viewDateReducer, initialState);
25
+ const updateLeftViewDate = useCallback((date: DateTime | DateObjectUnits) => updateViewDate({ left: date }), []);
26
+ const updateRightViewDate = useCallback((date: DateTime | DateObjectUnits) => updateViewDate({ right: date }), []);
27
+ return {
28
+ leftViewDate,
29
+ rightViewDate,
30
+ updateLeftViewDate,
31
+ updateRightViewDate,
32
+ };
33
+ }
34
+
35
+ export default useViewDate;
@@ -0,0 +1,36 @@
1
+ import { SystemStyleObject } from '@chakra-ui/react';
2
+
3
+ const NumberInputTheme = {
4
+ baseStyle: {
5
+ field: <SystemStyleObject>{
6
+ height: '42',
7
+ paddingLeft: '12',
8
+ borderRadius: '4',
9
+ borderStyle: 'solid',
10
+ borderColor: 'neutral.90',
11
+ borderWidth: '1px',
12
+ _focusVisible: {
13
+ boxShadow: 'outline',
14
+ outline: 'none',
15
+ },
16
+ },
17
+ },
18
+ sizes: {
19
+ small: {
20
+ field: {
21
+ height: '32',
22
+ fontSize: '2',
23
+ boxShadow: 'innerSmall',
24
+ },
25
+ },
26
+ medium: {
27
+ field: {
28
+ height: '48',
29
+ fontSize: '3',
30
+ boxShadow: 'inner',
31
+ },
32
+ },
33
+ },
34
+ };
35
+
36
+ export default NumberInputTheme;
@@ -3,6 +3,7 @@ const shadows = {
3
3
  medium: '0 0.125rem 0.75rem 0 rgba(0, 0, 0, 0.06)',
4
4
  large: '0 0.125rem 1.5rem 0 rgba(0, 0, 0, 0.08)',
5
5
  inner: '0 0.125rem 0.1875rem 0 rgba(0, 0, 0, 0.1) inset',
6
+ innerSmall: '0 0.0625rem 0.125rem 0 rgba(0, 0, 0, 0.1) inset',
6
7
  outline: '0 0 0 3px #C289E6',
7
8
  tooltip: '0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.2)',
8
9
  formFocus: '0 0.125rem 0.1875rem 0 rgba(0, 0, 0, 0.1) inset, inset 0 0 0 3px rgba(146, 71, 194, 0.3)',
@@ -5,6 +5,7 @@ const sizes = {
5
5
  '16': '1rem',
6
6
  '24': '1.5rem',
7
7
  '32': '2rem',
8
+ '40': '2.5rem',
8
9
  '48': '3rem',
9
10
  '64': '4rem',
10
11
  '96': '6rem',
@@ -1,3 +1,2 @@
1
1
  export { default as useEventListener } from './useEventListener';
2
- export { default as useMediaQuery } from './useMediaQuery';
3
2
  export { default as useSyncedStateAndProps } from './useSyncedStateAndProps';
package/src/index.ts CHANGED
@@ -202,6 +202,9 @@ export { default as Collapse } from './Components/Collapse/Collapse';
202
202
  export type { TextareaProps } from './Components/Form/Textarea/Textarea';
203
203
  export { default as Textarea } from './Components/Form/Textarea/Textarea';
204
204
 
205
+ export type { DatePickerProps } from './Components/DatePicker/DatePicker';
206
+ export { default as DatePicker, DateRange, useDateRange } from './Components/DatePicker/DatePicker';
207
+
205
208
  export type { FadeProps } from './Components/Fade/Fade';
206
209
  export { default as Fade } from './Components/Fade/Fade';
207
210
 
package/src/old.ts CHANGED
@@ -12,8 +12,5 @@ export type { TypeColors } from './Old/Base/Base';
12
12
  export type { TypeSizes } from './Old/Base/Base';
13
13
  export { default as Base } from './Old/Base/Base';
14
14
 
15
- export type { Props as DatePickerProps } from './Old/DatePicker/DatePicker';
16
- export { default as DatePicker } from './Old/DatePicker/DatePicker';
17
-
18
15
  export type { Props as FlexProps } from './Old/Flex/Flex';
19
16
  export { default as Flex } from './Old/Flex/Flex';
package/src/theme.ts CHANGED
@@ -26,6 +26,8 @@ import Popover from './Components/Popover/Popover.theme';
26
26
  import Toggle from './Components/Toggle/Toggle.theme';
27
27
  import Textarea from './Components/Form/Textarea/Textarea.theme';
28
28
  import Form from './Components/Form/Form.theme';
29
+ import DatePickerDay from './Components/DatePicker/DatePickerDay.theme';
30
+ import NumberInput from './Components/NumberInput/NumberInput.theme';
29
31
  import Skeleton from './Components/Skeleton/Skeleton.theme';
30
32
  import ProgressBar from './Components/ProgressBar/ProgressBar.theme';
31
33
 
@@ -101,6 +103,8 @@ const theme = {
101
103
  Popover,
102
104
  Switch: Toggle,
103
105
  Textarea,
106
+ DatePickerDay,
107
+ NumberInput,
104
108
  Skeleton,
105
109
  Progress: ProgressBar,
106
110
  },