@neko-os/ui 0.4.0 → 0.5.1
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.
- package/dist/abstractions/KeyboardDismissView.js +3 -0
- package/dist/abstractions/KeyboardDismissView.native.js +9 -0
- package/dist/abstractions/index.js +1 -0
- package/dist/components/actions/ClearLink.js +6 -0
- package/dist/components/actions/FloatingMenu.js +1 -1
- package/dist/components/calendar/CalendarNav.js +6 -6
- package/dist/components/carousel/Carousel.js +48 -0
- package/dist/components/carousel/CarouselArrows.js +40 -0
- package/dist/components/carousel/CarouselArrows.native.js +40 -0
- package/dist/components/carousel/CarouselDots.js +32 -0
- package/dist/components/carousel/CarouselDots.native.js +36 -0
- package/dist/components/carousel/CarouselHandler.js +86 -0
- package/dist/components/carousel/CarouselSlider.js +124 -0
- package/dist/components/carousel/CarouselSlider.native.js +121 -0
- package/dist/components/carousel/InfiniteCarousel.js +50 -0
- package/dist/components/carousel/index.js +6 -0
- package/dist/components/form/Form.js +5 -3
- package/dist/components/index.js +3 -1
- package/dist/components/inputs/DateInput.js +7 -4
- package/dist/components/inputs/InputWrapper.js +1 -2
- package/dist/components/inputs/Select.js +56 -52
- package/dist/components/inputs/TextInput.js +7 -6
- package/dist/components/inputs/datePicker/DayPicker.js +71 -23
- package/dist/components/inputs/datePicker/MonthPicker.js +61 -32
- package/dist/components/inputs/datePicker/QuarterPicker.js +62 -33
- package/dist/components/inputs/datePicker/WeekPicker.js +65 -24
- package/dist/components/inputs/datePicker/YearPicker.js +69 -40
- package/dist/components/keyboard/KeyboardDismissButton.js +3 -0
- package/dist/components/keyboard/KeyboardDismissButton.native.js +38 -0
- package/dist/components/keyboard/index.js +1 -0
- package/dist/components/modals/bottomDrawer/native/BottomDrawer.js +28 -7
- package/dist/components/presentation/LabelValue.js +1 -1
- package/dist/components/presentation/Result.js +11 -3
- package/dist/components/structure/KeyboardAvoidingView.js +9 -2
- package/dist/components/theme/ThemePicker.js +7 -12
- package/dist/components/theme/ThemeThumb.js +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/ThemeHandler.js +31 -3
- package/package.json +1 -1
- package/src/abstractions/KeyboardDismissView.js +3 -0
- package/src/abstractions/KeyboardDismissView.native.js +9 -0
- package/src/abstractions/index.js +1 -0
- package/src/components/actions/ClearLink.js +6 -0
- package/src/components/actions/FloatingMenu.js +1 -1
- package/src/components/calendar/CalendarNav.js +6 -6
- package/src/components/carousel/Carousel.js +48 -0
- package/src/components/carousel/CarouselArrows.js +40 -0
- package/src/components/carousel/CarouselArrows.native.js +40 -0
- package/src/components/carousel/CarouselDots.js +32 -0
- package/src/components/carousel/CarouselDots.native.js +36 -0
- package/src/components/carousel/CarouselHandler.js +86 -0
- package/src/components/carousel/CarouselSlider.js +124 -0
- package/src/components/carousel/CarouselSlider.native.js +121 -0
- package/src/components/carousel/InfiniteCarousel.js +50 -0
- package/src/components/carousel/index.js +6 -0
- package/src/components/form/Form.js +2 -0
- package/src/components/index.js +2 -0
- package/src/components/inputs/DateInput.js +4 -1
- package/src/components/inputs/InputWrapper.js +1 -2
- package/src/components/inputs/Select.js +19 -15
- package/src/components/inputs/TextInput.js +5 -4
- package/src/components/inputs/datePicker/DayPicker.js +69 -21
- package/src/components/inputs/datePicker/MonthPicker.js +60 -31
- package/src/components/inputs/datePicker/QuarterPicker.js +60 -31
- package/src/components/inputs/datePicker/WeekPicker.js +63 -22
- package/src/components/inputs/datePicker/YearPicker.js +68 -39
- package/src/components/keyboard/KeyboardDismissButton.js +3 -0
- package/src/components/keyboard/KeyboardDismissButton.native.js +38 -0
- package/src/components/keyboard/index.js +1 -0
- package/src/components/modals/bottomDrawer/native/BottomDrawer.js +27 -6
- package/src/components/presentation/LabelValue.js +1 -1
- package/src/components/presentation/Result.js +10 -2
- package/src/components/structure/KeyboardAvoidingView.js +9 -2
- package/src/components/theme/ThemePicker.js +8 -13
- package/src/components/theme/ThemeThumb.js +1 -1
- package/src/index.js +1 -0
- package/src/theme/ThemeHandler.js +31 -3
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
|
|
3
|
+
import Animated, { useSharedValue, useAnimatedStyle, withTiming, runOnJS } from 'react-native-reanimated'
|
|
4
|
+
|
|
5
|
+
import { View } from '../structure/View'
|
|
6
|
+
import { useCarousel } from './CarouselHandler'
|
|
7
|
+
|
|
8
|
+
function SlideContent({ item }) {
|
|
9
|
+
const Content = React.useMemo(
|
|
10
|
+
() => item.render || item.renderContent || item.Content,
|
|
11
|
+
[item.render, item.renderContent, item.Content]
|
|
12
|
+
)
|
|
13
|
+
return Content ? <Content /> : null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function clampIndex(index, count, loop) {
|
|
17
|
+
'worklet'
|
|
18
|
+
if (loop) return ((index % count) + count) % count
|
|
19
|
+
return Math.max(0, Math.min(index, count - 1))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function CarouselSlider() {
|
|
23
|
+
const { items, activeIndex, itemsCount, draggable, goTo, afterChange, pauseAutoplay, resumeAutoplay, loop } =
|
|
24
|
+
useCarousel()
|
|
25
|
+
|
|
26
|
+
const [slideWidth, setSlideWidth] = React.useState(0)
|
|
27
|
+
const translateX = useSharedValue(0)
|
|
28
|
+
const gestureStartX = useSharedValue(0)
|
|
29
|
+
const prevItemsRef = React.useRef(items)
|
|
30
|
+
const gestureAnimatingRef = React.useRef(false)
|
|
31
|
+
|
|
32
|
+
const setGestureAnimating = React.useCallback((v) => {
|
|
33
|
+
gestureAnimatingRef.current = v
|
|
34
|
+
}, [])
|
|
35
|
+
|
|
36
|
+
React.useEffect(() => {
|
|
37
|
+
if (slideWidth > 0) {
|
|
38
|
+
if (prevItemsRef.current !== items) {
|
|
39
|
+
prevItemsRef.current = items
|
|
40
|
+
translateX.value = -activeIndex * slideWidth
|
|
41
|
+
} else if (gestureAnimatingRef.current) {
|
|
42
|
+
// Gesture onEnd already animating — skip to avoid double animation
|
|
43
|
+
gestureAnimatingRef.current = false
|
|
44
|
+
} else {
|
|
45
|
+
translateX.value = withTiming(-activeIndex * slideWidth, { duration: 300 }, (finished) => {
|
|
46
|
+
if (finished && afterChange) runOnJS(afterChange)(items?.[activeIndex]?.key, activeIndex)
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}, [activeIndex, slideWidth, items])
|
|
51
|
+
|
|
52
|
+
const onLayout = React.useCallback((e) => {
|
|
53
|
+
setSlideWidth(e.nativeEvent.layout.width)
|
|
54
|
+
}, [])
|
|
55
|
+
|
|
56
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
57
|
+
transform: [{ translateX: translateX.value }],
|
|
58
|
+
}))
|
|
59
|
+
|
|
60
|
+
if (!items?.length) return null
|
|
61
|
+
|
|
62
|
+
const minTranslate = -(itemsCount - 1) * slideWidth
|
|
63
|
+
const maxTranslate = 0
|
|
64
|
+
|
|
65
|
+
const panGesture = Gesture.Pan()
|
|
66
|
+
.activeOffsetX([-10, 10])
|
|
67
|
+
.failOffsetY([-10, 10])
|
|
68
|
+
.onStart(() => {
|
|
69
|
+
gestureStartX.value = translateX.value
|
|
70
|
+
runOnJS(pauseAutoplay)()
|
|
71
|
+
})
|
|
72
|
+
.onUpdate((e) => {
|
|
73
|
+
const raw = gestureStartX.value + e.translationX
|
|
74
|
+
if (loop) {
|
|
75
|
+
translateX.value = raw
|
|
76
|
+
} else {
|
|
77
|
+
// Rubber band resistance at edges
|
|
78
|
+
if (raw > maxTranslate) {
|
|
79
|
+
translateX.value = maxTranslate + (raw - maxTranslate) * 0.3
|
|
80
|
+
} else if (raw < minTranslate) {
|
|
81
|
+
translateX.value = minTranslate + (raw - minTranslate) * 0.3
|
|
82
|
+
} else {
|
|
83
|
+
translateX.value = raw
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
.onEnd((e) => {
|
|
88
|
+
const threshold = slideWidth * 0.25
|
|
89
|
+
let targetIndex = activeIndex
|
|
90
|
+
|
|
91
|
+
if (e.translationX < -threshold || e.velocityX < -500) {
|
|
92
|
+
targetIndex = activeIndex + 1
|
|
93
|
+
} else if (e.translationX > threshold || e.velocityX > 500) {
|
|
94
|
+
targetIndex = activeIndex - 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const clamped = clampIndex(targetIndex, itemsCount, loop)
|
|
98
|
+
translateX.value = withTiming(-clamped * slideWidth, { duration: 300 }, (finished) => {
|
|
99
|
+
if (finished && afterChange) runOnJS(afterChange)(items?.[clamped]?.key, clamped)
|
|
100
|
+
})
|
|
101
|
+
runOnJS(setGestureAnimating)(true)
|
|
102
|
+
runOnJS(goTo)(targetIndex)
|
|
103
|
+
runOnJS(resumeAutoplay)()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const track = (
|
|
107
|
+
<Animated.View style={[{ flexDirection: 'row', width: slideWidth * itemsCount }, animatedStyle]}>
|
|
108
|
+
{items.map((item) => (
|
|
109
|
+
<View key={item.key} style={{ width: slideWidth }}>
|
|
110
|
+
<SlideContent item={item} />
|
|
111
|
+
</View>
|
|
112
|
+
))}
|
|
113
|
+
</Animated.View>
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<View hiddenOverflow fullW onLayout={onLayout}>
|
|
118
|
+
{draggable ? <GestureDetector gesture={panGesture}>{track}</GestureDetector> : track}
|
|
119
|
+
</View>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { Carousel } from './Carousel'
|
|
4
|
+
|
|
5
|
+
function buildItems(value, min, max, renderSlideRef) {
|
|
6
|
+
const items = []
|
|
7
|
+
if (min === undefined || value - 1 >= min) {
|
|
8
|
+
items.push({ key: value - 1, render: () => renderSlideRef.current(value - 1) })
|
|
9
|
+
}
|
|
10
|
+
items.push({ key: value, render: () => renderSlideRef.current(value) })
|
|
11
|
+
if (max === undefined || value + 1 <= max) {
|
|
12
|
+
items.push({ key: value + 1, render: () => renderSlideRef.current(value + 1) })
|
|
13
|
+
}
|
|
14
|
+
return items
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function InfiniteCarousel({ value, onChange, renderSlide, min, max, ...carouselProps }) {
|
|
18
|
+
const renderSlideRef = React.useRef(renderSlide)
|
|
19
|
+
renderSlideRef.current = renderSlide
|
|
20
|
+
|
|
21
|
+
const [items, setItems] = React.useState(() => buildItems(value, min, max, renderSlideRef))
|
|
22
|
+
const [activeKey, setActiveKey] = React.useState(value)
|
|
23
|
+
|
|
24
|
+
React.useEffect(() => {
|
|
25
|
+
setItems(buildItems(value, min, max, renderSlideRef))
|
|
26
|
+
setActiveKey(value)
|
|
27
|
+
}, [value, min, max])
|
|
28
|
+
|
|
29
|
+
const handleChange = React.useCallback((key) => {
|
|
30
|
+
setActiveKey(key)
|
|
31
|
+
}, [])
|
|
32
|
+
|
|
33
|
+
const handleAfterChange = React.useCallback(
|
|
34
|
+
(key) => {
|
|
35
|
+
if (key !== value) onChange?.(key)
|
|
36
|
+
},
|
|
37
|
+
[value, onChange]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Carousel
|
|
42
|
+
items={items}
|
|
43
|
+
activeKey={activeKey}
|
|
44
|
+
onChange={handleChange}
|
|
45
|
+
afterChange={handleAfterChange}
|
|
46
|
+
draggable
|
|
47
|
+
{...carouselProps}
|
|
48
|
+
/>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
3
|
import { FormWrapperComponent } from './FormWrapperComponent'
|
|
4
|
+
import { KeyboardDismissButton } from '../keyboard/KeyboardDismissButton'
|
|
4
5
|
import { LoadingView } from '../state/LoadingView'
|
|
5
6
|
import { useNewForm } from './useNewForm'
|
|
6
7
|
|
|
@@ -24,6 +25,7 @@ export function Form({ form, onSubmit, onValuesChange, initialValues, children,
|
|
|
24
25
|
<LoadingView active={loading} noWrapper>
|
|
25
26
|
<FormWrapperComponent form={form} onSubmit={onSubmit} gap="md" {...props}>
|
|
26
27
|
{children}
|
|
28
|
+
<KeyboardDismissButton />
|
|
27
29
|
</FormWrapperComponent>
|
|
28
30
|
</LoadingView>
|
|
29
31
|
</FormContext.Provider>
|
package/src/components/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from './modals'
|
|
|
7
7
|
export * from './text'
|
|
8
8
|
export * from './helpers'
|
|
9
9
|
export * from './inputs'
|
|
10
|
+
export * from './keyboard'
|
|
10
11
|
export * from './state'
|
|
11
12
|
export * from './layout'
|
|
12
13
|
export * from './table'
|
|
@@ -18,3 +19,4 @@ export * from './theme'
|
|
|
18
19
|
export * from './sections'
|
|
19
20
|
export * from './filter'
|
|
20
21
|
export * from './steps'
|
|
22
|
+
export * from './carousel'
|
|
@@ -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={[
|
|
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)}
|
|
@@ -2,6 +2,7 @@ import { dissoc } from 'ramda'
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
|
|
4
4
|
import { Icon, IconLabel } from '../presentation'
|
|
5
|
+
import { KeyboardDismissButton } from '../keyboard'
|
|
5
6
|
import { Link } from '../actions'
|
|
6
7
|
import { LinkInput } from './LinkInput'
|
|
7
8
|
import { Picker, getOptionLabel, searchOptions } from './Picker'
|
|
@@ -134,6 +135,8 @@ export function Select({
|
|
|
134
135
|
maxHeight={popoverMaxHeight}
|
|
135
136
|
{...popoverProps}
|
|
136
137
|
renderContent={({ onClose }) => (
|
|
138
|
+
<>
|
|
139
|
+
{useBottomDrawer && useSearch && <KeyboardDismissButton />}
|
|
137
140
|
<Picker
|
|
138
141
|
row={false}
|
|
139
142
|
options={searchOptions(options, search, { labelKey })}
|
|
@@ -152,21 +155,21 @@ export function Select({
|
|
|
152
155
|
}}
|
|
153
156
|
{...pickerProps}
|
|
154
157
|
renderHeader={
|
|
155
|
-
useBottomDrawer && useSearch
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
158
|
+
useBottomDrawer && useSearch ? (
|
|
159
|
+
<>
|
|
160
|
+
<View padding="md" paddingB="xs">
|
|
161
|
+
<TextInput
|
|
162
|
+
prefixIcon="search-line"
|
|
163
|
+
prefixIconColor="text4"
|
|
164
|
+
value={search}
|
|
165
|
+
onChange={handleChangeSearch}
|
|
166
|
+
/>
|
|
167
|
+
</View>
|
|
168
|
+
{renderHeader?.()}
|
|
169
|
+
</>
|
|
170
|
+
) : (
|
|
171
|
+
renderHeader
|
|
172
|
+
)
|
|
170
173
|
}
|
|
171
174
|
renderOption={({ option, selected, onChange }) => (
|
|
172
175
|
<Link
|
|
@@ -187,6 +190,7 @@ export function Select({
|
|
|
187
190
|
</Link>
|
|
188
191
|
)}
|
|
189
192
|
/>
|
|
193
|
+
</>
|
|
190
194
|
)}
|
|
191
195
|
>
|
|
192
196
|
<Input
|
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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,29 @@ import { WeekDaysBar } from '../../calendar/WeekDaysBar'
|
|
|
11
13
|
import { isDateDisabled } from '../../calendar/_helpers/dateDisabled'
|
|
12
14
|
import { useCalendarDays } from '../../calendar/_helpers/calendarDays'
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
27
|
+
const MonthDays = React.memo(function MonthDays({ monthValue, selectedKey, onSelect, min, max, onCheckDisabled }) {
|
|
28
|
+
const month = fromMonthValue(monthValue)
|
|
29
|
+
const selectedValue = selectedKey ? dayjs(selectedKey) : null
|
|
30
|
+
const { cells } = useCalendarDays(month)
|
|
31
31
|
|
|
32
32
|
return (
|
|
33
|
-
<View
|
|
34
|
-
<CalendarNav value={currentMonth} onChange={setCurrentMonth} />
|
|
33
|
+
<View>
|
|
35
34
|
<WeekDaysBar />
|
|
36
|
-
|
|
37
35
|
<Grid className="neko-day-picker-days" colSpan={24 / 7} gap="sm">
|
|
38
36
|
{cells.map((day, i) => {
|
|
39
|
-
const dateVal =
|
|
40
|
-
const isActive = !!
|
|
37
|
+
const dateVal = month.date(day)
|
|
38
|
+
const isActive = !!selectedValue && !!day && dateVal.isSame(selectedValue, 'day')
|
|
41
39
|
const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
|
|
42
40
|
|
|
43
41
|
return (
|
|
@@ -47,7 +45,7 @@ export function DayPicker({ value, onChange, min, max, onCheckDisabled, ...props
|
|
|
47
45
|
fullW
|
|
48
46
|
center
|
|
49
47
|
br="md"
|
|
50
|
-
onPress={() => !!day &&
|
|
48
|
+
onPress={() => !!day && onSelect(dateVal)}
|
|
51
49
|
bg={isActive && 'primary'}
|
|
52
50
|
disabled={disabled}
|
|
53
51
|
>
|
|
@@ -61,4 +59,54 @@ export function DayPicker({ value, onChange, min, max, onCheckDisabled, ...props
|
|
|
61
59
|
</Grid>
|
|
62
60
|
</View>
|
|
63
61
|
)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
export function DayPicker({ value, onChange, min, max, onCheckDisabled, allowClear, ...props }) {
|
|
65
|
+
if (!!value) value = dayjs(value)
|
|
66
|
+
const [localValue, setLocalValue] = React.useState(value)
|
|
67
|
+
const [currentMonth, setCurrentMonth] = React.useState(() => dayjs(value || undefined).startOf('month'))
|
|
68
|
+
value = value === undefined ? localValue : value
|
|
69
|
+
|
|
70
|
+
React.useEffect(() => {
|
|
71
|
+
setLocalValue(value)
|
|
72
|
+
if (value?.isValid?.()) setCurrentMonth(value.startOf('month'))
|
|
73
|
+
}, [value?.day?.(), value?.month?.(), value?.year?.()])
|
|
74
|
+
|
|
75
|
+
const handleChange = React.useCallback(
|
|
76
|
+
(v) => {
|
|
77
|
+
setLocalValue(v)
|
|
78
|
+
onChange?.(v)
|
|
79
|
+
},
|
|
80
|
+
[onChange]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const monthValue = toMonthValue(currentMonth)
|
|
84
|
+
const minMonth = min ? toMonthValue(dayjs(min).startOf('month')) : undefined
|
|
85
|
+
const maxMonth = max ? toMonthValue(dayjs(max).startOf('month')) : undefined
|
|
86
|
+
const selectedKey = value?.valueOf?.()
|
|
87
|
+
|
|
88
|
+
const renderSlide = (v) => (
|
|
89
|
+
<MonthDays
|
|
90
|
+
monthValue={v}
|
|
91
|
+
selectedKey={selectedKey}
|
|
92
|
+
onSelect={handleChange}
|
|
93
|
+
min={min}
|
|
94
|
+
max={max}
|
|
95
|
+
onCheckDisabled={onCheckDisabled}
|
|
96
|
+
/>
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<View className="neko-day-picker" width={275} maxW={350} {...props}>
|
|
101
|
+
<CalendarNav value={currentMonth} onChange={setCurrentMonth} />
|
|
102
|
+
<InfiniteCarousel
|
|
103
|
+
value={monthValue}
|
|
104
|
+
onChange={(v) => setCurrentMonth(fromMonthValue(v))}
|
|
105
|
+
renderSlide={renderSlide}
|
|
106
|
+
min={minMonth}
|
|
107
|
+
max={maxMonth}
|
|
108
|
+
/>
|
|
109
|
+
<ClearLink hide={!allowClear} value={value} onChange={onChange} />
|
|
110
|
+
</View>
|
|
111
|
+
)
|
|
64
112
|
}
|
|
@@ -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,39 @@ 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
|
-
|
|
17
|
+
const MonthGrid = React.memo(function MonthGrid({ year, selectedKey, onSelect, min, max, onCheckDisabled }) {
|
|
18
|
+
const yearDate = dayjs().year(year).startOf('year')
|
|
19
|
+
const selectedValue = selectedKey ? dayjs(selectedKey) : null
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Grid colSpan={8} gap="xs">
|
|
23
|
+
{months.map((month) => {
|
|
24
|
+
const dateVal = yearDate.month(month)
|
|
25
|
+
const isActive = !!selectedValue && dateVal.isSame(selectedValue, 'month')
|
|
26
|
+
const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Col key={month}>
|
|
30
|
+
<Link
|
|
31
|
+
fullW
|
|
32
|
+
br="md"
|
|
33
|
+
padding="sm"
|
|
34
|
+
onPress={() => onSelect(dateVal)}
|
|
35
|
+
bg={isActive && 'primary'}
|
|
36
|
+
disabled={disabled}
|
|
37
|
+
>
|
|
38
|
+
<Text text2={!isActive} strong={isActive} center>
|
|
39
|
+
{dateVal.format('MMM')}
|
|
40
|
+
</Text>
|
|
41
|
+
</Link>
|
|
42
|
+
</Col>
|
|
43
|
+
)
|
|
44
|
+
})}
|
|
45
|
+
</Grid>
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export function MonthPicker({ value, onChange, min, max, onCheckDisabled, allowClear, ...props }) {
|
|
16
50
|
const [localValue, setLocalValue] = React.useState(value)
|
|
17
51
|
const [currentYear, setCurrentYear] = React.useState(() => dayjs(value || undefined).startOf('year'))
|
|
18
52
|
value = value === undefined ? localValue : value
|
|
@@ -22,41 +56,36 @@ export function MonthPicker({ value, onChange, min, max, onCheckDisabled, ...pro
|
|
|
22
56
|
if (value?.isValid?.()) setCurrentYear(value.startOf('year'))
|
|
23
57
|
}, [value?.month?.(), value?.year?.()])
|
|
24
58
|
|
|
25
|
-
const handleChange = (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
59
|
+
const handleChange = React.useCallback(
|
|
60
|
+
(v) => {
|
|
61
|
+
const newValue = v.startOf('month')
|
|
62
|
+
setLocalValue(newValue)
|
|
63
|
+
onChange?.(newValue)
|
|
64
|
+
},
|
|
65
|
+
[onChange]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const yearValue = currentYear.year()
|
|
69
|
+
const minYear = min ? dayjs(min).year() : undefined
|
|
70
|
+
const maxYear = max ? dayjs(max).year() : undefined
|
|
71
|
+
const selectedKey = value?.valueOf?.()
|
|
72
|
+
|
|
73
|
+
const renderSlide = (v) => (
|
|
74
|
+
<MonthGrid year={v} selectedKey={selectedKey} onSelect={handleChange} min={min} max={max} onCheckDisabled={onCheckDisabled} />
|
|
75
|
+
)
|
|
30
76
|
|
|
31
77
|
return (
|
|
32
78
|
<View className="neko-day-picker" width={275} {...props}>
|
|
33
79
|
<CalendarNav value={currentYear} onChange={setCurrentYear} level="year" />
|
|
34
80
|
<Divider />
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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>
|
|
81
|
+
<InfiniteCarousel
|
|
82
|
+
value={yearValue}
|
|
83
|
+
onChange={(v) => setCurrentYear(dayjs().year(v).startOf('year'))}
|
|
84
|
+
renderSlide={renderSlide}
|
|
85
|
+
min={minYear}
|
|
86
|
+
max={maxYear}
|
|
87
|
+
/>
|
|
88
|
+
<ClearLink hide={!allowClear} value={value} onChange={onChange} />
|
|
60
89
|
</View>
|
|
61
90
|
)
|
|
62
91
|
}
|