@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.
Files changed (77) hide show
  1. package/dist/abstractions/KeyboardDismissView.js +3 -0
  2. package/dist/abstractions/KeyboardDismissView.native.js +9 -0
  3. package/dist/abstractions/index.js +1 -0
  4. package/dist/components/actions/ClearLink.js +6 -0
  5. package/dist/components/actions/FloatingMenu.js +1 -1
  6. package/dist/components/calendar/CalendarNav.js +6 -6
  7. package/dist/components/carousel/Carousel.js +48 -0
  8. package/dist/components/carousel/CarouselArrows.js +40 -0
  9. package/dist/components/carousel/CarouselArrows.native.js +40 -0
  10. package/dist/components/carousel/CarouselDots.js +32 -0
  11. package/dist/components/carousel/CarouselDots.native.js +36 -0
  12. package/dist/components/carousel/CarouselHandler.js +86 -0
  13. package/dist/components/carousel/CarouselSlider.js +124 -0
  14. package/dist/components/carousel/CarouselSlider.native.js +121 -0
  15. package/dist/components/carousel/InfiniteCarousel.js +50 -0
  16. package/dist/components/carousel/index.js +6 -0
  17. package/dist/components/form/Form.js +5 -3
  18. package/dist/components/index.js +3 -1
  19. package/dist/components/inputs/DateInput.js +7 -4
  20. package/dist/components/inputs/InputWrapper.js +1 -2
  21. package/dist/components/inputs/Select.js +56 -52
  22. package/dist/components/inputs/TextInput.js +7 -6
  23. package/dist/components/inputs/datePicker/DayPicker.js +71 -23
  24. package/dist/components/inputs/datePicker/MonthPicker.js +61 -32
  25. package/dist/components/inputs/datePicker/QuarterPicker.js +62 -33
  26. package/dist/components/inputs/datePicker/WeekPicker.js +65 -24
  27. package/dist/components/inputs/datePicker/YearPicker.js +69 -40
  28. package/dist/components/keyboard/KeyboardDismissButton.js +3 -0
  29. package/dist/components/keyboard/KeyboardDismissButton.native.js +38 -0
  30. package/dist/components/keyboard/index.js +1 -0
  31. package/dist/components/modals/bottomDrawer/native/BottomDrawer.js +28 -7
  32. package/dist/components/presentation/LabelValue.js +1 -1
  33. package/dist/components/presentation/Result.js +11 -3
  34. package/dist/components/structure/KeyboardAvoidingView.js +9 -2
  35. package/dist/components/theme/ThemePicker.js +7 -12
  36. package/dist/components/theme/ThemeThumb.js +1 -1
  37. package/dist/index.js +1 -0
  38. package/dist/theme/ThemeHandler.js +31 -3
  39. package/package.json +1 -1
  40. package/src/abstractions/KeyboardDismissView.js +3 -0
  41. package/src/abstractions/KeyboardDismissView.native.js +9 -0
  42. package/src/abstractions/index.js +1 -0
  43. package/src/components/actions/ClearLink.js +6 -0
  44. package/src/components/actions/FloatingMenu.js +1 -1
  45. package/src/components/calendar/CalendarNav.js +6 -6
  46. package/src/components/carousel/Carousel.js +48 -0
  47. package/src/components/carousel/CarouselArrows.js +40 -0
  48. package/src/components/carousel/CarouselArrows.native.js +40 -0
  49. package/src/components/carousel/CarouselDots.js +32 -0
  50. package/src/components/carousel/CarouselDots.native.js +36 -0
  51. package/src/components/carousel/CarouselHandler.js +86 -0
  52. package/src/components/carousel/CarouselSlider.js +124 -0
  53. package/src/components/carousel/CarouselSlider.native.js +121 -0
  54. package/src/components/carousel/InfiniteCarousel.js +50 -0
  55. package/src/components/carousel/index.js +6 -0
  56. package/src/components/form/Form.js +2 -0
  57. package/src/components/index.js +2 -0
  58. package/src/components/inputs/DateInput.js +4 -1
  59. package/src/components/inputs/InputWrapper.js +1 -2
  60. package/src/components/inputs/Select.js +19 -15
  61. package/src/components/inputs/TextInput.js +5 -4
  62. package/src/components/inputs/datePicker/DayPicker.js +69 -21
  63. package/src/components/inputs/datePicker/MonthPicker.js +60 -31
  64. package/src/components/inputs/datePicker/QuarterPicker.js +60 -31
  65. package/src/components/inputs/datePicker/WeekPicker.js +63 -22
  66. package/src/components/inputs/datePicker/YearPicker.js +68 -39
  67. package/src/components/keyboard/KeyboardDismissButton.js +3 -0
  68. package/src/components/keyboard/KeyboardDismissButton.native.js +38 -0
  69. package/src/components/keyboard/index.js +1 -0
  70. package/src/components/modals/bottomDrawer/native/BottomDrawer.js +27 -6
  71. package/src/components/presentation/LabelValue.js +1 -1
  72. package/src/components/presentation/Result.js +10 -2
  73. package/src/components/structure/KeyboardAvoidingView.js +9 -2
  74. package/src/components/theme/ThemePicker.js +8 -13
  75. package/src/components/theme/ThemeThumb.js +1 -1
  76. package/src/index.js +1 -0
  77. package/src/theme/ThemeHandler.js +31 -3
@@ -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,39 @@ 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
+ const QuarterGrid = React.memo(function QuarterGrid({ year, selectedKey, onSelect, min, max, onCheckDisabled }) {
21
+ const yearDate = dayjs().year(year).startOf('year')
22
+ const selectedValue = selectedKey ? dayjs(selectedKey) : null
23
+
24
+ return (
25
+ <Grid colSpan={6} gap="xs">
26
+ {quarters.map((quarter) => {
27
+ const dateVal = yearDate.quarter(quarter)
28
+ const isActive = !!selectedValue && dateVal.isSame(selectedValue, 'quarter')
29
+ const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
30
+
31
+ return (
32
+ <Col key={quarter}>
33
+ <Link
34
+ fullW
35
+ br="md"
36
+ padding="sm"
37
+ onPress={() => onSelect(dateVal)}
38
+ bg={isActive && 'primary'}
39
+ disabled={disabled}
40
+ >
41
+ <Text text2={!isActive} strong={isActive} center>
42
+ Q{dateVal.quarter()}
43
+ </Text>
44
+ </Link>
45
+ </Col>
46
+ )
47
+ })}
48
+ </Grid>
49
+ )
50
+ })
51
+
52
+ export function QuarterPicker({ value, onChange, min, max, onCheckDisabled, allowClear, ...props }) {
19
53
  const [localValue, setLocalValue] = React.useState(value)
20
54
  const [currentYear, setCurrentYear] = React.useState(() => dayjs(value || undefined).startOf('year'))
21
55
  value = value === undefined ? localValue : value
@@ -25,41 +59,36 @@ export function QuarterPicker({ value, onChange, min, max, onCheckDisabled, ...p
25
59
  if (value?.isValid?.()) setCurrentYear(value.startOf('year'))
26
60
  }, [value?.quarter?.(), value?.year?.()])
27
61
 
28
- const handleChange = (v) => {
29
- const newValue = v.startOf('quarter')
30
- setLocalValue(newValue)
31
- onChange?.(newValue)
32
- }
62
+ const handleChange = React.useCallback(
63
+ (v) => {
64
+ const newValue = v.startOf('quarter')
65
+ setLocalValue(newValue)
66
+ onChange?.(newValue)
67
+ },
68
+ [onChange]
69
+ )
70
+
71
+ const yearValue = currentYear.year()
72
+ const minYear = min ? dayjs(min).year() : undefined
73
+ const maxYear = max ? dayjs(max).year() : undefined
74
+ const selectedKey = value?.valueOf?.()
75
+
76
+ const renderSlide = (v) => (
77
+ <QuarterGrid year={v} selectedKey={selectedKey} onSelect={handleChange} min={min} max={max} onCheckDisabled={onCheckDisabled} />
78
+ )
33
79
 
34
80
  return (
35
81
  <View className="neko-day-picker" width={275} {...props}>
36
82
  <CalendarNav value={currentYear} onChange={setCurrentYear} level="year" />
37
83
  <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>
84
+ <InfiniteCarousel
85
+ value={yearValue}
86
+ onChange={(v) => setCurrentYear(dayjs().year(v).startOf('year'))}
87
+ renderSlide={renderSlide}
88
+ min={minYear}
89
+ max={maxYear}
90
+ />
91
+ <ClearLink hide={!allowClear} value={value} onChange={onChange} />
63
92
  </View>
64
93
  )
65
94
  }
@@ -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,31 @@ 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
+ const MonthWeeks = React.memo(function MonthWeeks({ monthValue, selectedKey, onSelect, min, max, onCheckDisabled }) {
29
+ const month = fromMonthValue(monthValue)
30
+ const selectedValue = selectedKey ? dayjs(selectedKey) : null
31
+ const { cells } = useCalendarDays(month)
32
32
  const weeks = splitEvery(7, cells)
33
33
 
34
34
  return (
35
- <View className="neko-day-picker" width={275} {...props}>
36
- <CalendarNav value={currentMonth} onChange={setCurrentMonth} />
35
+ <View>
37
36
  <WeekDaysBar />
38
-
39
37
  <View colSpan={24 / 7} gap="sm">
40
38
  {weeks.map((week, wi) => {
41
39
  const firstDay = week.find(identity)
42
- const dateVal = currentMonth.date(firstDay)
43
- const isActive = !!value && !!firstDay && dateVal.isSame(value, 'week')
40
+ const dateVal = month.date(firstDay)
41
+ const isActive = !!selectedValue && !!firstDay && dateVal.isSame(selectedValue, 'week')
44
42
  const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
45
43
 
46
44
  return (
@@ -48,13 +46,13 @@ export function WeekPicker({ value, onChange, min, max, onCheckDisabled, ...prop
48
46
  key={firstDay ? dateVal.format('YYYYMMDD') : wi}
49
47
  fullW
50
48
  br="md"
51
- onPress={() => !!firstDay && handleChange(dateVal)}
49
+ onPress={() => !!firstDay && onSelect(dateVal)}
52
50
  bg={isActive && 'primary'}
53
51
  disabled={disabled}
54
52
  >
55
53
  <Row colSpan={24 / 7} gap="sm">
56
54
  {week.map((day, i) => {
57
- const dateVal = currentMonth.date(day)
55
+ const dateVal = month.date(day)
58
56
 
59
57
  return (
60
58
  <Col key={day ? dateVal.format('YYYYMMDD') : i} className="day-cell" center ratio={1}>
@@ -71,4 +69,47 @@ export function WeekPicker({ value, onChange, min, max, onCheckDisabled, ...prop
71
69
  </View>
72
70
  </View>
73
71
  )
72
+ })
73
+
74
+ export function WeekPicker({ value, onChange, min, max, onCheckDisabled, allowClear, ...props }) {
75
+ const [localValue, setLocalValue] = React.useState(value)
76
+ const [currentMonth, setCurrentMonth] = React.useState(() => dayjs(value || undefined).startOf('month'))
77
+ value = value === undefined ? localValue : value
78
+
79
+ React.useEffect(() => {
80
+ setLocalValue(value)
81
+ if (value?.isValid?.()) setCurrentMonth(value.startOf('month'))
82
+ }, [value?.day?.(), value?.month?.(), value?.year?.()])
83
+
84
+ const handleChange = React.useCallback(
85
+ (v) => {
86
+ const newValue = v.startOf('week')
87
+ setLocalValue(newValue)
88
+ onChange?.(newValue)
89
+ },
90
+ [onChange]
91
+ )
92
+
93
+ const monthValue = toMonthValue(currentMonth)
94
+ const minMonth = min ? toMonthValue(dayjs(min).startOf('month')) : undefined
95
+ const maxMonth = max ? toMonthValue(dayjs(max).startOf('month')) : undefined
96
+ const selectedKey = value?.valueOf?.()
97
+
98
+ const renderSlide = (v) => (
99
+ <MonthWeeks monthValue={v} selectedKey={selectedKey} onSelect={handleChange} min={min} max={max} onCheckDisabled={onCheckDisabled} />
100
+ )
101
+
102
+ return (
103
+ <View className="neko-day-picker" width={275} {...props}>
104
+ <CalendarNav value={currentMonth} onChange={setCurrentMonth} />
105
+ <InfiniteCarousel
106
+ value={monthValue}
107
+ onChange={(v) => setCurrentMonth(fromMonthValue(v))}
108
+ renderSlide={renderSlide}
109
+ min={minMonth}
110
+ max={maxMonth}
111
+ />
112
+ <ClearLink hide={!allowClear} value={value} onChange={onChange} />
113
+ </View>
114
+ )
74
115
  }
@@ -3,68 +3,97 @@ 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
+ const DecadeGrid = React.memo(function DecadeGrid({ decadeIndex, selectedKey, onSelect, min, max, onCheckDisabled }) {
25
+ const decadeStart = decadeFromIndex(decadeIndex)
26
+ const selectedValue = selectedKey ? dayjs(selectedKey) : null
27
+ const years = range(decadeStart.year(), decadeStart.year() + 10)
28
+
29
+ return (
30
+ <Grid colSpan={12} gap="xs">
31
+ {years.map((year) => {
32
+ const dateVal = decadeStart.year(year)
33
+ const isActive = !!selectedValue && dateVal.isSame(selectedValue, 'year')
34
+ const disabled = isDateDisabled(dateVal, { min, max, onCheckDisabled })
35
+
36
+ return (
37
+ <Col key={year}>
38
+ <Link
39
+ fullW
40
+ br="md"
41
+ padding="sm"
42
+ onPress={() => onSelect(dateVal)}
43
+ bg={isActive && 'primary'}
44
+ disabled={disabled}
45
+ >
46
+ <Text text2={!isActive} strong={isActive} center>
47
+ {dateVal.year()}
48
+ </Text>
49
+ </Link>
50
+ </Col>
51
+ )
52
+ })}
53
+ </Grid>
54
+ )
55
+ })
56
+
57
+ export function YearPicker({ value, onChange, min, max, onCheckDisabled, allowClear, ...props }) {
21
58
  const [localValue, setLocalValue] = React.useState(value)
22
- const [currentDecade, setCurrentDecade] = React.useState(() => getDecade(value))
59
+ const [currentDecade, setCurrentDecade] = React.useState(() => getDecadeIndex(value))
23
60
 
24
61
  value = value === undefined ? localValue : value
25
62
 
26
63
  React.useEffect(() => {
27
64
  setLocalValue(value)
28
- if (value?.isValid?.()) setCurrentDecade(getDecade(value))
65
+ if (value?.isValid?.()) setCurrentDecade(getDecadeIndex(value))
29
66
  }, [value?.year?.()])
30
67
 
31
- const handleChange = (v) => {
32
- const newValue = v.startOf('year')
33
- setLocalValue(newValue)
34
- onChange?.(newValue)
35
- }
68
+ const handleChange = React.useCallback(
69
+ (v) => {
70
+ const newValue = v.startOf('year')
71
+ setLocalValue(newValue)
72
+ onChange?.(newValue)
73
+ },
74
+ [onChange]
75
+ )
76
+
77
+ const minDecade = min ? getDecadeIndex(min) : undefined
78
+ const maxDecade = max ? getDecadeIndex(max) : undefined
79
+ const selectedKey = value?.valueOf?.()
36
80
 
37
- const years = range(currentDecade.year(), currentDecade.year() + 10)
81
+ const renderSlide = (v) => (
82
+ <DecadeGrid decadeIndex={v} selectedKey={selectedKey} onSelect={handleChange} min={min} max={max} onCheckDisabled={onCheckDisabled} />
83
+ )
38
84
 
39
85
  return (
40
86
  <View className="neko-day-picker" width={275} {...props}>
41
- <CalendarNav value={currentDecade} onChange={setCurrentDecade} level="decade" />
87
+ <CalendarNav value={decadeFromIndex(currentDecade)} onChange={(v) => setCurrentDecade(getDecadeIndex(v))} level="decade" />
42
88
  <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>
89
+ <InfiniteCarousel
90
+ value={currentDecade}
91
+ onChange={setCurrentDecade}
92
+ renderSlide={renderSlide}
93
+ min={minDecade}
94
+ max={maxDecade}
95
+ />
96
+ <ClearLink hide={!allowClear} value={value} onChange={onChange} />
68
97
  </View>
69
98
  )
70
99
  }
@@ -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
+ }
@@ -0,0 +1 @@
1
+ export * from './KeyboardDismissButton'
@@ -4,6 +4,8 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
4
4
  import Animated, {
5
5
  useSharedValue,
6
6
  useAnimatedStyle,
7
+ useAnimatedKeyboard,
8
+ KeyboardState,
7
9
  withSpring,
8
10
  runOnJS,
9
11
  } from 'react-native-reanimated'
@@ -27,6 +29,7 @@ function InnerContent({
27
29
  enableOverScroll = true,
28
30
  enableHandlePanningGesture = true,
29
31
  enableContentPanningGesture = true,
32
+ keyboardBehavior = 'interactive',
30
33
  animationConfig = {
31
34
  damping: 50,
32
35
  stiffness: 500,
@@ -44,6 +47,7 @@ function InnerContent({
44
47
  const bottomInset = useSafeArea ? insets.bottom : 0
45
48
 
46
49
  const colors = useColors()
50
+ const keyboard = useAnimatedKeyboard()
47
51
 
48
52
  const translateY = useSharedValue(SCREEN_HEIGHT)
49
53
  const snapIndex = useSharedValue(0)
@@ -122,11 +126,9 @@ function InnerContent({
122
126
  const currentPosition = SCREEN_HEIGHT - translateY.value
123
127
  const shouldClose =
124
128
  !!handleClose &&
125
- (
126
- velocityY.value > 1500 ||
129
+ (velocityY.value > 1500 ||
127
130
  (velocityY.value > 800 && currentPosition < minSnapPoint) ||
128
- currentPosition < minSnapPoint * 0.35
129
- )
131
+ currentPosition < minSnapPoint * 0.35)
130
132
 
131
133
  if (shouldClose) {
132
134
  runOnJS(handleClose)()
@@ -148,10 +150,28 @@ function InnerContent({
148
150
  handleClose,
149
151
  ])
150
152
 
153
+ const topInset = insets.top
151
154
  const animatedSheetStyle = useAnimatedStyle(() => {
152
- const currentHeight = SCREEN_HEIGHT - translateY.value
155
+ let kbShift = 0
156
+
157
+ const kbVisible = keyboard.state.value === KeyboardState.OPEN || keyboard.state.value === KeyboardState.OPENING
158
+ if (keyboardBehavior !== 'none' && kbVisible) {
159
+ const kbHeight = keyboard.height.value
160
+
161
+ if (keyboardBehavior === 'interactive') {
162
+ // Shift up by keyboard height, but don't go above safe area
163
+ const maxShift = Math.max(0, translateY.value - topInset)
164
+ kbShift = Math.min(kbHeight, maxShift)
165
+ } else if (keyboardBehavior === 'extend') {
166
+ // Snap to fill available space above keyboard
167
+ kbShift = Math.max(0, translateY.value - topInset)
168
+ }
169
+ }
170
+
171
+ const adjustedY = translateY.value - kbShift
172
+ const currentHeight = SCREEN_HEIGHT - adjustedY
153
173
  return {
154
- transform: [{ translateY: translateY.value }],
174
+ transform: [{ translateY: adjustedY }],
155
175
  maxHeight: currentHeight,
156
176
  }
157
177
  })
@@ -200,6 +220,7 @@ function InnerContent({
200
220
  marginR="auto"
201
221
  fullW
202
222
  {...props}
223
+ minH={minSnapPoint}
203
224
  >
204
225
  <DrawerHandle hide={hideHandle} />
205
226
  <View flex {...contentProps}>
@@ -18,7 +18,7 @@ const DEFAULT_PROPS = ([{ sizeCode, color }, { vertical, spread }]) => {
18
18
  labelProps: {
19
19
  size: moveScale(sizeCode, !vertical ? 0 : -2),
20
20
  moveIconSizeScale: !vertical ? -1 : -2,
21
- color: color || 'text3',
21
+ color: color || 'text2',
22
22
  },
23
23
  valueProps: {
24
24
  size: sizeCode,
@@ -2,6 +2,7 @@ import { Divider } from '../helpers/Separator'
2
2
  import { Icon } from './Icon'
3
3
  import { Text } from '../text/Text'
4
4
  import { View } from '../structure/View'
5
+ import { useResponsiveValue } from '../../responsive'
5
6
 
6
7
  export const RESULT_TYPES = {
7
8
  error: {
@@ -23,6 +24,12 @@ export const RESULT_TYPES = {
23
24
  icon: 'information-2-fill',
24
25
  color: 'blue',
25
26
  },
27
+
28
+ empty: {
29
+ icon: 'inbox-line',
30
+ color: 'text4',
31
+ opacity: 0.7,
32
+ },
26
33
  }
27
34
 
28
35
  export function Result({
@@ -41,12 +48,13 @@ export function Result({
41
48
  const typeProps = RESULT_TYPES[type] || {}
42
49
  icon = icon || typeProps.icon
43
50
  iconColor = iconColor || typeProps.color || 'primary'
51
+ const size = useResponsiveValue({ lgu: 'h4', df: 'h5' })
44
52
 
45
53
  return (
46
- <View className="neko-result" center padding="lg" {...props}>
54
+ <View className="neko-result" center padding="lg" opacity={typeProps.opacity} {...props}>
47
55
  {!!icon && <Icon name={icon} color={iconColor} size={42} primary {...iconProps} />}
48
56
  {!!icon && <Divider height={10} />}
49
- <Text h4 center {...textProps} {...titleProps}>
57
+ <Text size={size} center color="text2" {...textProps} {...titleProps}>
50
58
  {title}
51
59
  </Text>
52
60
  {!!description && (
@@ -1,6 +1,7 @@
1
1
  import { pipe } from 'ramda'
2
2
 
3
3
  import { AbsKeyboardAvoidingView } from '../../abstractions/KeyboardAvoidingView'
4
+ import { AbsKeyboardDismissView } from '../../abstractions/KeyboardDismissView'
4
5
  import { Platform } from '../../abstractions/Platform'
5
6
  import { useSafeAreaInsets } from '../../abstractions/helpers/useSafeAreaInsets'
6
7
  import { useAnimationModifier } from '../../modifiers/animation'
@@ -19,7 +20,7 @@ import { useSizeModifier } from '../../modifiers/size'
19
20
  import { useStateModifier } from '../../modifiers/state'
20
21
  import { useThemeComponentModifier } from '../../modifiers/themeComponent'
21
22
 
22
- export function KeyboardAvoidingView({ children, keyboardVerticalOffset = 0, ...rootProps }) {
23
+ export function KeyboardAvoidingView({ children, keyboardVerticalOffset = 0, dismissOnTap = true, ...rootProps }) {
23
24
  const { bottom } = useSafeAreaInsets()
24
25
 
25
26
  const [_, props] = pipe(
@@ -46,7 +47,13 @@ export function KeyboardAvoidingView({ children, keyboardVerticalOffset = 0, ...
46
47
  keyboardVerticalOffset={keyboardVerticalOffset + bottom}
47
48
  {...props}
48
49
  >
49
- {children}
50
+ {dismissOnTap ? (
51
+ <AbsKeyboardDismissView style={{ flex: 1 }}>
52
+ {children}
53
+ </AbsKeyboardDismissView>
54
+ ) : (
55
+ children
56
+ )}
50
57
  </AbsKeyboardAvoidingView>
51
58
  )
52
59
  }
@@ -1,25 +1,20 @@
1
- import { mapObjIndexed, mergeDeepRight, values, pipe, filter } from 'ramda'
1
+ import { mapObjIndexed, values, pipe, reject, propEq } from 'ramda'
2
2
 
3
- import { DEFAULT_THEMES, useThemeHandler } from '../../theme'
3
+ import { useAllThemes, useThemeHandler } from '../../theme'
4
4
  import { IconLabel } from '../presentation'
5
5
  import { Link } from '../actions'
6
6
  import { Picker } from '../inputs'
7
7
  import { ThemeThumb } from './ThemeThumb'
8
8
 
9
- export function ThemePicker({ onChange, onlyKeys, hideKeys }) {
10
- const { activeThemeKey, themes, onChangeTheme } = useThemeHandler()
9
+ export function ThemePicker({ onChange }) {
10
+ const { activeThemeKey, onChangeTheme } = useThemeHandler()
11
+ const allThemes = useAllThemes()
11
12
 
12
- let options = pipe(
13
- mergeDeepRight(DEFAULT_THEMES),
13
+ const options = pipe(
14
14
  mapObjIndexed((obj, key) => ({ ...obj, value: key, key })),
15
15
  values,
16
- filter((item) => {
17
- if (item.value === '_all') return false
18
- if (onlyKeys && onlyKeys.includes(item.value)) return true
19
- if (hideKeys && hideKeys.includes(item.value)) return false
20
- return true
21
- })
22
- )(themes)
16
+ reject(propEq('_all', 'value'))
17
+ )(allThemes)
23
18
 
24
19
  return (
25
20
  <Picker
@@ -4,7 +4,7 @@ import { useResponsiveValue } from '../../responsive'
4
4
  import { useThemeHandler } from '../../theme'
5
5
 
6
6
  export function ThemeThumb({ value }) {
7
- const { themes } = useThemeHandler()
7
+ const { rawThemesParam: themes } = useThemeHandler()
8
8
  const { colors, label } = useFormattedTheme(themes, value)
9
9
  const isMobile = useResponsiveValue({ smd: true, df: false })
10
10
 
package/src/index.js CHANGED
@@ -4,5 +4,6 @@ export * from './theme'
4
4
  export * from './responsive'
5
5
  export * from './i18n'
6
6
  export * from './NekoUI'
7
+ export * from './abstractions'
7
8
 
8
9
  export const version = 41