@neko-os/ui 0.4.0 → 0.5.0

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 (71) hide show
  1. package/dist/abstractions/KeyboardDismissView.js +3 -0
  2. package/dist/abstractions/KeyboardDismissView.native.js +9 -0
  3. package/dist/components/actions/ClearLink.js +6 -0
  4. package/dist/components/actions/FloatingMenu.js +1 -1
  5. package/dist/components/calendar/CalendarNav.js +6 -6
  6. package/dist/components/carousel/Carousel.js +48 -0
  7. package/dist/components/carousel/CarouselArrows.js +40 -0
  8. package/dist/components/carousel/CarouselArrows.native.js +40 -0
  9. package/dist/components/carousel/CarouselDots.js +32 -0
  10. package/dist/components/carousel/CarouselDots.native.js +36 -0
  11. package/dist/components/carousel/CarouselHandler.js +86 -0
  12. package/dist/components/carousel/CarouselSlider.js +124 -0
  13. package/dist/components/carousel/CarouselSlider.native.js +110 -0
  14. package/dist/components/carousel/InfiniteCarousel.js +50 -0
  15. package/dist/components/carousel/index.js +6 -0
  16. package/dist/components/form/Form.js +5 -3
  17. package/dist/components/index.js +3 -1
  18. package/dist/components/inputs/DateInput.js +7 -4
  19. package/dist/components/inputs/InputWrapper.js +1 -2
  20. package/dist/components/inputs/TextInput.js +7 -6
  21. package/dist/components/inputs/datePicker/DayPicker.js +65 -23
  22. package/dist/components/inputs/datePicker/MonthPicker.js +51 -27
  23. package/dist/components/inputs/datePicker/QuarterPicker.js +52 -28
  24. package/dist/components/inputs/datePicker/WeekPicker.js +59 -24
  25. package/dist/components/inputs/datePicker/YearPicker.js +59 -35
  26. package/dist/components/keyboard/KeyboardDismissButton.js +3 -0
  27. package/dist/components/keyboard/KeyboardDismissButton.native.js +38 -0
  28. package/dist/components/keyboard/index.js +1 -0
  29. package/dist/components/modals/bottomDrawer/native/BottomDrawer.js +28 -7
  30. package/dist/components/presentation/LabelValue.js +1 -1
  31. package/dist/components/presentation/Result.js +11 -3
  32. package/dist/components/structure/KeyboardAvoidingView.js +9 -2
  33. package/dist/components/theme/ThemePicker.js +7 -12
  34. package/dist/components/theme/ThemeThumb.js +1 -1
  35. package/dist/theme/ThemeHandler.js +31 -3
  36. package/package.json +1 -1
  37. package/src/abstractions/KeyboardDismissView.js +3 -0
  38. package/src/abstractions/KeyboardDismissView.native.js +9 -0
  39. package/src/components/actions/ClearLink.js +6 -0
  40. package/src/components/actions/FloatingMenu.js +1 -1
  41. package/src/components/calendar/CalendarNav.js +6 -6
  42. package/src/components/carousel/Carousel.js +48 -0
  43. package/src/components/carousel/CarouselArrows.js +40 -0
  44. package/src/components/carousel/CarouselArrows.native.js +40 -0
  45. package/src/components/carousel/CarouselDots.js +32 -0
  46. package/src/components/carousel/CarouselDots.native.js +36 -0
  47. package/src/components/carousel/CarouselHandler.js +86 -0
  48. package/src/components/carousel/CarouselSlider.js +124 -0
  49. package/src/components/carousel/CarouselSlider.native.js +110 -0
  50. package/src/components/carousel/InfiniteCarousel.js +50 -0
  51. package/src/components/carousel/index.js +6 -0
  52. package/src/components/form/Form.js +2 -0
  53. package/src/components/index.js +2 -0
  54. package/src/components/inputs/DateInput.js +4 -1
  55. package/src/components/inputs/InputWrapper.js +1 -2
  56. package/src/components/inputs/TextInput.js +5 -4
  57. package/src/components/inputs/datePicker/DayPicker.js +63 -21
  58. package/src/components/inputs/datePicker/MonthPicker.js +50 -26
  59. package/src/components/inputs/datePicker/QuarterPicker.js +50 -26
  60. package/src/components/inputs/datePicker/WeekPicker.js +57 -22
  61. package/src/components/inputs/datePicker/YearPicker.js +58 -34
  62. package/src/components/keyboard/KeyboardDismissButton.js +3 -0
  63. package/src/components/keyboard/KeyboardDismissButton.native.js +38 -0
  64. package/src/components/keyboard/index.js +1 -0
  65. package/src/components/modals/bottomDrawer/native/BottomDrawer.js +27 -6
  66. package/src/components/presentation/LabelValue.js +1 -1
  67. package/src/components/presentation/Result.js +10 -2
  68. package/src/components/structure/KeyboardAvoidingView.js +9 -2
  69. package/src/components/theme/ThemePicker.js +8 -13
  70. package/src/components/theme/ThemeThumb.js +1 -1
  71. package/src/theme/ThemeHandler.js +31 -3
@@ -48,6 +48,7 @@ export function DateInput({
48
48
  type = 'day',
49
49
  format,
50
50
  startsOpen,
51
+ allowClear,
51
52
  useBottomDrawer = { native: true, sm: true, md: true },
52
53
  ...props
53
54
  }) {
@@ -91,7 +92,7 @@ export function DateInput({
91
92
  trigger="click"
92
93
  startsOpen={startsOpen}
93
94
  placement={placement || 'bottomLeft'}
94
- snapPoints={[350]}
95
+ snapPoints={[450]}
95
96
  useBottomDrawer={useBottomDrawer}
96
97
  bottomDrawerProps={{ contentProps: { padding: 'md' } }}
97
98
  watch={[value?.format?.('YYYYMMDD')]}
@@ -103,6 +104,8 @@ export function DateInput({
103
104
  handleChange(v)
104
105
  onClose()
105
106
  }}
107
+ width={useBottomDrawer ? '100%' : 275}
108
+ allowClear={allowClear}
106
109
  {...validations}
107
110
  type={type}
108
111
  />
@@ -65,8 +65,7 @@ export function InputWrapper({
65
65
  <View
66
66
  className="neko-input-wrapper"
67
67
  height={multiline ? undefined : size}
68
- minHeight={multiline ? size : undefined}
69
- paddingV={multiline ? 'sm' : undefined}
68
+ minHeight={multiline ? 1.5 * size : undefined}
70
69
  onPress={handlePress}
71
70
  borderColor={borderColor}
72
71
  onMouseEnter={() => setHover(true)}
@@ -1,9 +1,11 @@
1
1
  import { AbsTextInput } from '../../abstractions/TextInput'
2
2
  import { InputWrapper } from './InputWrapper'
3
3
  import { useColors } from '../../theme/ThemeHandler'
4
+ import { useSpaces } from '../../theme'
4
5
 
5
6
  export function TextInput({ onChange, multiline, rows, ...props }) {
6
7
  const colors = useColors()
8
+ const spaces = useSpaces()
7
9
 
8
10
  const STYLE = {
9
11
  width: '100%',
@@ -11,10 +13,9 @@ export function TextInput({ onChange, multiline, rows, ...props }) {
11
13
  background: 'transparent',
12
14
  outline: 'none',
13
15
  color: colors.text,
14
- // fontFamily: 'inherit',
15
- // fontSize: 'inherit',
16
- // lineHeight: 'inherit',
17
- ...(multiline ? { resize: 'none' } : { height: '100%' }),
16
+ flex: 1,
17
+ height: '100%',
18
+ ...(multiline ? { paddingTop: spaces.sm, resize: 'none', flex: 1, textAlignVertical: 'top' } : {}),
18
19
  }
19
20
 
20
21
  return (
@@ -2,8 +2,10 @@ import React from 'react'
2
2
  import dayjs from 'dayjs'
3
3
 
4
4
  import { CalendarNav } from '../../calendar/CalendarNav'
5
+ import { ClearLink } from '../../actions/ClearLink'
5
6
  import { Col } from '../../structure/Col'
6
7
  import { Grid } from '../../structure/Row'
8
+ import { InfiniteCarousel } from '../../carousel/InfiniteCarousel'
7
9
  import { Link } from '../../actions/Link'
8
10
  import { Text } from '../../text/Text'
9
11
  import { View } from '../../structure/View'
@@ -11,33 +13,27 @@ import { WeekDaysBar } from '../../calendar/WeekDaysBar'
11
13
  import { isDateDisabled } from '../../calendar/_helpers/dateDisabled'
12
14
  import { useCalendarDays } from '../../calendar/_helpers/calendarDays'
13
15
 
14
- export function DayPicker({ value, onChange, min, max, onCheckDisabled, ...props }) {
15
- if (!!value) value = dayjs(value)
16
- const [localValue, setLocalValue] = React.useState(value)
17
- const [currentMonth, setCurrentMonth] = React.useState(() => dayjs(value || undefined).startOf('month'))
18
- value = value === undefined ? localValue : value
19
-
20
- React.useEffect(() => {
21
- setLocalValue(value)
22
- if (value?.isValid?.()) setCurrentMonth(value.startOf('month'))
23
- }, [value?.day?.(), value?.month?.(), value?.year?.()])
16
+ function toMonthValue(date) {
17
+ return date.year() * 12 + date.month()
18
+ }
24
19
 
25
- const handleChange = (v) => {
26
- setLocalValue(v)
27
- onChange?.(v)
28
- }
20
+ function fromMonthValue(v) {
21
+ return dayjs()
22
+ .year(Math.floor(v / 12))
23
+ .month(v % 12)
24
+ .startOf('month')
25
+ }
29
26
 
30
- const { cells } = useCalendarDays(currentMonth)
27
+ function MonthDays({ month, selectedValue, onSelect, min, max, onCheckDisabled }) {
28
+ const { cells } = useCalendarDays(month)
31
29
 
32
30
  return (
33
- <View className="neko-day-picker" width={275} {...props}>
34
- <CalendarNav value={currentMonth} onChange={setCurrentMonth} />
31
+ <View>
35
32
  <WeekDaysBar />
36
-
37
33
  <Grid className="neko-day-picker-days" colSpan={24 / 7} gap="sm">
38
34
  {cells.map((day, i) => {
39
- const dateVal = currentMonth.date(day)
40
- const isActive = !!value && !!day && dateVal.isSame(value, 'day')
35
+ const dateVal = month.date(day)
36
+ const isActive = !!selectedValue && !!day && dateVal.isSame(selectedValue, 'day')
41
37
  const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
42
38
 
43
39
  return (
@@ -47,7 +43,7 @@ export function DayPicker({ value, onChange, min, max, onCheckDisabled, ...props
47
43
  fullW
48
44
  center
49
45
  br="md"
50
- onPress={() => !!day && handleChange(dateVal)}
46
+ onPress={() => !!day && onSelect(dateVal)}
51
47
  bg={isActive && 'primary'}
52
48
  disabled={disabled}
53
49
  >
@@ -62,3 +58,49 @@ export function DayPicker({ value, onChange, min, max, onCheckDisabled, ...props
62
58
  </View>
63
59
  )
64
60
  }
61
+
62
+ export function DayPicker({ value, onChange, min, max, onCheckDisabled, allowClear, ...props }) {
63
+ if (!!value) value = dayjs(value)
64
+ const [localValue, setLocalValue] = React.useState(value)
65
+ const [currentMonth, setCurrentMonth] = React.useState(() => dayjs(value || undefined).startOf('month'))
66
+ value = value === undefined ? localValue : value
67
+
68
+ React.useEffect(() => {
69
+ setLocalValue(value)
70
+ if (value?.isValid?.()) setCurrentMonth(value.startOf('month'))
71
+ }, [value?.day?.(), value?.month?.(), value?.year?.()])
72
+
73
+ const handleChange = (v) => {
74
+ setLocalValue(v)
75
+ onChange?.(v)
76
+ }
77
+
78
+ const monthValue = toMonthValue(currentMonth)
79
+ const minMonth = min ? toMonthValue(dayjs(min).startOf('month')) : undefined
80
+ const maxMonth = max ? toMonthValue(dayjs(max).startOf('month')) : undefined
81
+
82
+ const renderSlide = (v) => (
83
+ <MonthDays
84
+ month={fromMonthValue(v)}
85
+ selectedValue={value}
86
+ onSelect={handleChange}
87
+ min={min}
88
+ max={max}
89
+ onCheckDisabled={onCheckDisabled}
90
+ />
91
+ )
92
+
93
+ return (
94
+ <View className="neko-day-picker" width={275} maxW={350} {...props}>
95
+ <CalendarNav value={currentMonth} onChange={setCurrentMonth} />
96
+ <InfiniteCarousel
97
+ value={monthValue}
98
+ onChange={(v) => setCurrentMonth(fromMonthValue(v))}
99
+ renderSlide={renderSlide}
100
+ min={minMonth}
101
+ max={maxMonth}
102
+ />
103
+ <ClearLink hide={!allowClear} value={value} onChange={onChange} />
104
+ </View>
105
+ )
106
+ }
@@ -2,9 +2,11 @@ import React from 'react'
2
2
  import dayjs from 'dayjs'
3
3
 
4
4
  import { CalendarNav } from '../../calendar/CalendarNav'
5
+ import { ClearLink } from '../../actions/ClearLink'
5
6
  import { Col } from '../../structure/Col'
6
7
  import { Divider } from '../../helpers'
7
8
  import { Grid } from '../../structure/Row'
9
+ import { InfiniteCarousel } from '../../carousel/InfiniteCarousel'
8
10
  import { Link } from '../../actions/Link'
9
11
  import { Text } from '../../text/Text'
10
12
  import { View } from '../../structure/View'
@@ -12,7 +14,38 @@ import { isDateDisabled } from '../../calendar/_helpers/dateDisabled'
12
14
 
13
15
  const months = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
14
16
 
15
- export function MonthPicker({ value, onChange, min, max, onCheckDisabled, ...props }) {
17
+ function MonthGrid({ year, selectedValue, onSelect, min, max, onCheckDisabled }) {
18
+ const yearDate = dayjs().year(year).startOf('year')
19
+
20
+ return (
21
+ <Grid colSpan={8} gap="xs">
22
+ {months.map((month) => {
23
+ const dateVal = yearDate.month(month)
24
+ const isActive = !!selectedValue && dateVal.isSame(selectedValue, 'week')
25
+ const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
26
+
27
+ return (
28
+ <Col key={month}>
29
+ <Link
30
+ fullW
31
+ br="md"
32
+ padding="sm"
33
+ onPress={() => onSelect(dateVal)}
34
+ bg={isActive && 'primary'}
35
+ disabled={disabled}
36
+ >
37
+ <Text text2={!isActive} strong={isActive} center>
38
+ {dateVal.format('MMM')}
39
+ </Text>
40
+ </Link>
41
+ </Col>
42
+ )
43
+ })}
44
+ </Grid>
45
+ )
46
+ }
47
+
48
+ export function MonthPicker({ value, onChange, min, max, onCheckDisabled, allowClear, ...props }) {
16
49
  const [localValue, setLocalValue] = React.useState(value)
17
50
  const [currentYear, setCurrentYear] = React.useState(() => dayjs(value || undefined).startOf('year'))
18
51
  value = value === undefined ? localValue : value
@@ -28,35 +61,26 @@ export function MonthPicker({ value, onChange, min, max, onCheckDisabled, ...pro
28
61
  onChange?.(newValue)
29
62
  }
30
63
 
64
+ const yearValue = currentYear.year()
65
+ const minYear = min ? dayjs(min).year() : undefined
66
+ const maxYear = max ? dayjs(max).year() : undefined
67
+
68
+ const renderSlide = (v) => (
69
+ <MonthGrid year={v} selectedValue={value} onSelect={handleChange} min={min} max={max} onCheckDisabled={onCheckDisabled} />
70
+ )
71
+
31
72
  return (
32
73
  <View className="neko-day-picker" width={275} {...props}>
33
74
  <CalendarNav value={currentYear} onChange={setCurrentYear} level="year" />
34
75
  <Divider />
35
-
36
- <Grid colSpan={8} gap="xs">
37
- {months.map((month) => {
38
- const dateVal = currentYear.month(month)
39
- const isActive = !!value && dateVal.isSame(value, 'week')
40
- const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
41
-
42
- return (
43
- <Col key={month}>
44
- <Link
45
- fullW
46
- br="md"
47
- padding="sm"
48
- onPress={() => handleChange(dateVal)}
49
- bg={isActive && 'primary'}
50
- disabled={disabled}
51
- >
52
- <Text text2={!isActive} strong={isActive} center>
53
- {dateVal.format('MMM')}
54
- </Text>
55
- </Link>
56
- </Col>
57
- )
58
- })}
59
- </Grid>
76
+ <InfiniteCarousel
77
+ value={yearValue}
78
+ onChange={(v) => setCurrentYear(dayjs().year(v).startOf('year'))}
79
+ renderSlide={renderSlide}
80
+ min={minYear}
81
+ max={maxYear}
82
+ />
83
+ <ClearLink hide={!allowClear} value={value} onChange={onChange} />
60
84
  </View>
61
85
  )
62
86
  }
@@ -3,9 +3,11 @@ import dayjs from 'dayjs'
3
3
  import quarterOfYear from 'dayjs/esm/plugin/quarterOfYear'
4
4
 
5
5
  import { CalendarNav } from '../../calendar/CalendarNav'
6
+ import { ClearLink } from '../../actions/ClearLink'
6
7
  import { Col } from '../../structure/Col'
7
8
  import { Divider } from '../../helpers'
8
9
  import { Grid } from '../../structure/Row'
10
+ import { InfiniteCarousel } from '../../carousel/InfiniteCarousel'
9
11
  import { Link } from '../../actions/Link'
10
12
  import { Text } from '../../text/Text'
11
13
  import { View } from '../../structure/View'
@@ -15,7 +17,38 @@ dayjs.extend(quarterOfYear)
15
17
 
16
18
  const quarters = [1, 2, 3, 4]
17
19
 
18
- export function QuarterPicker({ value, onChange, min, max, onCheckDisabled, ...props }) {
20
+ function QuarterGrid({ year, selectedValue, onSelect, min, max, onCheckDisabled }) {
21
+ const yearDate = dayjs().year(year).startOf('year')
22
+
23
+ return (
24
+ <Grid colSpan={6} gap="xs">
25
+ {quarters.map((quarter) => {
26
+ const dateVal = yearDate.quarter(quarter)
27
+ const isActive = !!selectedValue && dateVal.isSame(selectedValue, 'week')
28
+ const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
29
+
30
+ return (
31
+ <Col key={quarter}>
32
+ <Link
33
+ fullW
34
+ br="md"
35
+ padding="sm"
36
+ onPress={() => onSelect(dateVal)}
37
+ bg={isActive && 'primary'}
38
+ disabled={disabled}
39
+ >
40
+ <Text text2={!isActive} strong={isActive} center>
41
+ Q{dateVal.quarter()}
42
+ </Text>
43
+ </Link>
44
+ </Col>
45
+ )
46
+ })}
47
+ </Grid>
48
+ )
49
+ }
50
+
51
+ export function QuarterPicker({ value, onChange, min, max, onCheckDisabled, allowClear, ...props }) {
19
52
  const [localValue, setLocalValue] = React.useState(value)
20
53
  const [currentYear, setCurrentYear] = React.useState(() => dayjs(value || undefined).startOf('year'))
21
54
  value = value === undefined ? localValue : value
@@ -31,35 +64,26 @@ export function QuarterPicker({ value, onChange, min, max, onCheckDisabled, ...p
31
64
  onChange?.(newValue)
32
65
  }
33
66
 
67
+ const yearValue = currentYear.year()
68
+ const minYear = min ? dayjs(min).year() : undefined
69
+ const maxYear = max ? dayjs(max).year() : undefined
70
+
71
+ const renderSlide = (v) => (
72
+ <QuarterGrid year={v} selectedValue={value} onSelect={handleChange} min={min} max={max} onCheckDisabled={onCheckDisabled} />
73
+ )
74
+
34
75
  return (
35
76
  <View className="neko-day-picker" width={275} {...props}>
36
77
  <CalendarNav value={currentYear} onChange={setCurrentYear} level="year" />
37
78
  <Divider />
38
-
39
- <Grid colSpan={6} gap="xs">
40
- {quarters.map((quarter) => {
41
- const dateVal = currentYear.quarter(quarter)
42
- const isActive = !!value && dateVal.isSame(value, 'week')
43
- const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
44
-
45
- return (
46
- <Col key={quarter}>
47
- <Link
48
- fullW
49
- br="md"
50
- padding="sm"
51
- onPress={() => handleChange(dateVal)}
52
- bg={isActive && 'primary'}
53
- disabled={disabled}
54
- >
55
- <Text text2={!isActive} strong={isActive} center>
56
- Q{dateVal.quarter()}
57
- </Text>
58
- </Link>
59
- </Col>
60
- )
61
- })}
62
- </Grid>
79
+ <InfiniteCarousel
80
+ value={yearValue}
81
+ onChange={(v) => setCurrentYear(dayjs().year(v).startOf('year'))}
82
+ renderSlide={renderSlide}
83
+ min={minYear}
84
+ max={maxYear}
85
+ />
86
+ <ClearLink hide={!allowClear} value={value} onChange={onChange} />
63
87
  </View>
64
88
  )
65
89
  }
@@ -3,7 +3,9 @@ import React from 'react'
3
3
  import dayjs from 'dayjs'
4
4
 
5
5
  import { CalendarNav } from '../../calendar/CalendarNav'
6
+ import { ClearLink } from '../../actions/ClearLink'
6
7
  import { Col } from '../../structure/Col'
8
+ import { InfiniteCarousel } from '../../carousel/InfiniteCarousel'
7
9
  import { Link } from '../../actions/Link'
8
10
  import { Row } from '../../structure/Row'
9
11
  import { Text } from '../../text/Text'
@@ -12,35 +14,29 @@ import { WeekDaysBar } from '../../calendar/WeekDaysBar'
12
14
  import { isDateDisabled } from '../../calendar/_helpers/dateDisabled'
13
15
  import { useCalendarDays } from '../../calendar/_helpers/calendarDays'
14
16
 
15
- export function WeekPicker({ value, onChange, min, max, onCheckDisabled, ...props }) {
16
- const [localValue, setLocalValue] = React.useState(value)
17
- const [currentMonth, setCurrentMonth] = React.useState(() => dayjs(value || undefined).startOf('month'))
18
- value = value === undefined ? localValue : value
19
-
20
- React.useEffect(() => {
21
- setLocalValue(value)
22
- if (value?.isValid?.()) setCurrentMonth(value.startOf('month'))
23
- }, [value?.day?.(), value?.month?.(), value?.year?.()])
17
+ function toMonthValue(date) {
18
+ return date.year() * 12 + date.month()
19
+ }
24
20
 
25
- const handleChange = (v) => {
26
- const newValue = v.startOf('week')
27
- setLocalValue(newValue)
28
- onChange?.(newValue)
29
- }
21
+ function fromMonthValue(v) {
22
+ return dayjs()
23
+ .year(Math.floor(v / 12))
24
+ .month(v % 12)
25
+ .startOf('month')
26
+ }
30
27
 
31
- const { cells } = useCalendarDays(currentMonth)
28
+ function MonthWeeks({ month, selectedValue, onSelect, min, max, onCheckDisabled }) {
29
+ const { cells } = useCalendarDays(month)
32
30
  const weeks = splitEvery(7, cells)
33
31
 
34
32
  return (
35
- <View className="neko-day-picker" width={275} {...props}>
36
- <CalendarNav value={currentMonth} onChange={setCurrentMonth} />
33
+ <View>
37
34
  <WeekDaysBar />
38
-
39
35
  <View colSpan={24 / 7} gap="sm">
40
36
  {weeks.map((week, wi) => {
41
37
  const firstDay = week.find(identity)
42
- const dateVal = currentMonth.date(firstDay)
43
- const isActive = !!value && !!firstDay && dateVal.isSame(value, 'week')
38
+ const dateVal = month.date(firstDay)
39
+ const isActive = !!selectedValue && !!firstDay && dateVal.isSame(selectedValue, 'week')
44
40
  const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
45
41
 
46
42
  return (
@@ -48,13 +44,13 @@ export function WeekPicker({ value, onChange, min, max, onCheckDisabled, ...prop
48
44
  key={firstDay ? dateVal.format('YYYYMMDD') : wi}
49
45
  fullW
50
46
  br="md"
51
- onPress={() => !!firstDay && handleChange(dateVal)}
47
+ onPress={() => !!firstDay && onSelect(dateVal)}
52
48
  bg={isActive && 'primary'}
53
49
  disabled={disabled}
54
50
  >
55
51
  <Row colSpan={24 / 7} gap="sm">
56
52
  {week.map((day, i) => {
57
- const dateVal = currentMonth.date(day)
53
+ const dateVal = month.date(day)
58
54
 
59
55
  return (
60
56
  <Col key={day ? dateVal.format('YYYYMMDD') : i} className="day-cell" center ratio={1}>
@@ -72,3 +68,42 @@ export function WeekPicker({ value, onChange, min, max, onCheckDisabled, ...prop
72
68
  </View>
73
69
  )
74
70
  }
71
+
72
+ export function WeekPicker({ value, onChange, min, max, onCheckDisabled, allowClear, ...props }) {
73
+ const [localValue, setLocalValue] = React.useState(value)
74
+ const [currentMonth, setCurrentMonth] = React.useState(() => dayjs(value || undefined).startOf('month'))
75
+ value = value === undefined ? localValue : value
76
+
77
+ React.useEffect(() => {
78
+ setLocalValue(value)
79
+ if (value?.isValid?.()) setCurrentMonth(value.startOf('month'))
80
+ }, [value?.day?.(), value?.month?.(), value?.year?.()])
81
+
82
+ const handleChange = (v) => {
83
+ const newValue = v.startOf('week')
84
+ setLocalValue(newValue)
85
+ onChange?.(newValue)
86
+ }
87
+
88
+ const monthValue = toMonthValue(currentMonth)
89
+ const minMonth = min ? toMonthValue(dayjs(min).startOf('month')) : undefined
90
+ const maxMonth = max ? toMonthValue(dayjs(max).startOf('month')) : undefined
91
+
92
+ const renderSlide = (v) => (
93
+ <MonthWeeks month={fromMonthValue(v)} selectedValue={value} onSelect={handleChange} min={min} max={max} onCheckDisabled={onCheckDisabled} />
94
+ )
95
+
96
+ return (
97
+ <View className="neko-day-picker" width={275} {...props}>
98
+ <CalendarNav value={currentMonth} onChange={setCurrentMonth} />
99
+ <InfiniteCarousel
100
+ value={monthValue}
101
+ onChange={(v) => setCurrentMonth(fromMonthValue(v))}
102
+ renderSlide={renderSlide}
103
+ min={minMonth}
104
+ max={maxMonth}
105
+ />
106
+ <ClearLink hide={!allowClear} value={value} onChange={onChange} />
107
+ </View>
108
+ )
109
+ }
@@ -3,29 +3,65 @@ import React from 'react'
3
3
  import dayjs from 'dayjs'
4
4
 
5
5
  import { CalendarNav } from '../../calendar/CalendarNav'
6
+ import { ClearLink } from '../../actions/ClearLink'
6
7
  import { Col } from '../../structure/Col'
7
8
  import { Divider } from '../../helpers'
8
9
  import { Grid } from '../../structure/Row'
10
+ import { InfiniteCarousel } from '../../carousel/InfiniteCarousel'
9
11
  import { Link } from '../../actions/Link'
10
12
  import { Text } from '../../text/Text'
11
13
  import { View } from '../../structure/View'
12
14
  import { isDateDisabled } from '../../calendar/_helpers/dateDisabled'
13
15
 
14
- function getDecade(value) {
15
- const year = dayjs(value || undefined).year()
16
- const decadeStartYear = Math.floor(year / 10) * 10
17
- return dayjs().year(decadeStartYear).startOf('year')
16
+ function getDecadeIndex(value) {
17
+ return Math.floor(dayjs(value || undefined).year() / 10)
18
18
  }
19
19
 
20
- export function YearPicker({ value, onChange, min, max, onCheckDisabled, ...props }) {
20
+ function decadeFromIndex(i) {
21
+ return dayjs().year(i * 10).startOf('year')
22
+ }
23
+
24
+ function DecadeGrid({ decadeIndex, selectedValue, onSelect, min, max, onCheckDisabled }) {
25
+ const decadeStart = decadeFromIndex(decadeIndex)
26
+ const years = range(decadeStart.year(), decadeStart.year() + 10)
27
+
28
+ return (
29
+ <Grid colSpan={12} gap="xs">
30
+ {years.map((year) => {
31
+ const dateVal = decadeStart.year(year)
32
+ const isActive = !!selectedValue && dateVal.isSame(selectedValue, 'week')
33
+ const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
34
+
35
+ return (
36
+ <Col key={year}>
37
+ <Link
38
+ fullW
39
+ br="md"
40
+ padding="sm"
41
+ onPress={() => onSelect(dateVal)}
42
+ bg={isActive && 'primary'}
43
+ disabled={disabled}
44
+ >
45
+ <Text text2={!isActive} strong={isActive} center>
46
+ {dateVal.year()}
47
+ </Text>
48
+ </Link>
49
+ </Col>
50
+ )
51
+ })}
52
+ </Grid>
53
+ )
54
+ }
55
+
56
+ export function YearPicker({ value, onChange, min, max, onCheckDisabled, allowClear, ...props }) {
21
57
  const [localValue, setLocalValue] = React.useState(value)
22
- const [currentDecade, setCurrentDecade] = React.useState(() => getDecade(value))
58
+ const [currentDecade, setCurrentDecade] = React.useState(() => getDecadeIndex(value))
23
59
 
24
60
  value = value === undefined ? localValue : value
25
61
 
26
62
  React.useEffect(() => {
27
63
  setLocalValue(value)
28
- if (value?.isValid?.()) setCurrentDecade(getDecade(value))
64
+ if (value?.isValid?.()) setCurrentDecade(getDecadeIndex(value))
29
65
  }, [value?.year?.()])
30
66
 
31
67
  const handleChange = (v) => {
@@ -34,37 +70,25 @@ export function YearPicker({ value, onChange, min, max, onCheckDisabled, ...prop
34
70
  onChange?.(newValue)
35
71
  }
36
72
 
37
- const years = range(currentDecade.year(), currentDecade.year() + 10)
73
+ const minDecade = min ? getDecadeIndex(min) : undefined
74
+ const maxDecade = max ? getDecadeIndex(max) : undefined
75
+
76
+ const renderSlide = (v) => (
77
+ <DecadeGrid decadeIndex={v} selectedValue={value} onSelect={handleChange} min={min} max={max} onCheckDisabled={onCheckDisabled} />
78
+ )
38
79
 
39
80
  return (
40
81
  <View className="neko-day-picker" width={275} {...props}>
41
- <CalendarNav value={currentDecade} onChange={setCurrentDecade} level="decade" />
82
+ <CalendarNav value={decadeFromIndex(currentDecade)} onChange={(v) => setCurrentDecade(getDecadeIndex(v))} level="decade" />
42
83
  <Divider />
43
-
44
- <Grid colSpan={12} gap="xs">
45
- {years.map((year) => {
46
- const dateVal = currentDecade.year(year)
47
- const isActive = !!value && dateVal.isSame(value, 'week')
48
- const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
49
-
50
- return (
51
- <Col key={year}>
52
- <Link
53
- fullW
54
- br="md"
55
- padding="sm"
56
- onPress={() => handleChange(dateVal)}
57
- bg={isActive && 'primary'}
58
- disabled={disabled}
59
- >
60
- <Text text2={!isActive} strong={isActive} center>
61
- {dateVal.year()}
62
- </Text>
63
- </Link>
64
- </Col>
65
- )
66
- })}
67
- </Grid>
84
+ <InfiniteCarousel
85
+ value={currentDecade}
86
+ onChange={setCurrentDecade}
87
+ renderSlide={renderSlide}
88
+ min={minDecade}
89
+ max={maxDecade}
90
+ />
91
+ <ClearLink hide={!allowClear} value={value} onChange={onChange} />
68
92
  </View>
69
93
  )
70
94
  }
@@ -0,0 +1,3 @@
1
+ export function KeyboardDismissButton() {
2
+ return false
3
+ }
@@ -0,0 +1,38 @@
1
+ import { Keyboard } from 'react-native'
2
+ import Animated, { useAnimatedKeyboard, useAnimatedStyle, KeyboardState, withTiming } from 'react-native-reanimated'
3
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
4
+
5
+ import { Button } from '../actions'
6
+
7
+ const STATIC_STYLE = { position: 'absolute', right: 5, zIndex: 1000 }
8
+
9
+ export function KeyboardDismissButton() {
10
+ const keyboard = useAnimatedKeyboard()
11
+ const { bottom: bottomInset } = useSafeAreaInsets()
12
+
13
+ const animatedStyle = useAnimatedStyle(() => {
14
+ const isVisible = keyboard.height.value > 0
15
+ return {
16
+ bottom: keyboard.height.value - bottomInset + 10,
17
+ opacity: keyboard.state.value === KeyboardState.CLOSING ? withTiming(0, { duration: 150 }) : isVisible ? 1 : 0,
18
+ pointerEvents: isVisible ? 'auto' : 'none',
19
+ }
20
+ })
21
+
22
+ return (
23
+ <Animated.View style={[STATIC_STYLE, animatedStyle]}>
24
+ <Button
25
+ icon="arrow-down-box-line"
26
+ onPress={Keyboard.dismiss}
27
+ ratio={1}
28
+ shadow
29
+ overlayBG
30
+ sm
31
+ opacity={0.85}
32
+ iconProps={{ size: 'md' }}
33
+ border
34
+ borderColor="divider"
35
+ />
36
+ </Animated.View>
37
+ )
38
+ }