@neko-os/ui 0.0.8 → 0.0.10
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/DynamicStyleTag.js +5 -0
- package/dist/DynamicStyleTag.native.js +1 -0
- package/dist/NekoUI.js +1 -1
- package/dist/abstractions/ActivityIndicator.native.js +1 -1
- package/dist/abstractions/ActivityIndicator.web.js +1 -0
- package/dist/abstractions/AnimatedView.web.js +1 -0
- package/dist/abstractions/BlurView.web.js +1 -0
- package/dist/abstractions/FlatList.js +1 -0
- package/dist/abstractions/FlatList.native.js +1 -0
- package/dist/abstractions/FlatList.web.js +1 -0
- package/dist/abstractions/ScrollView.web.js +1 -0
- package/dist/abstractions/StaticList.js +1 -0
- package/dist/abstractions/helpers/storage.js +1 -0
- package/dist/abstractions/helpers/storage.native.js +1 -0
- package/dist/abstractions/helpers/useSafeAreaInsets.js +1 -0
- package/dist/abstractions/helpers/useSafeAreaInsets.native.js +1 -0
- package/dist/components/actions/Button.js +1 -1
- package/dist/components/actions/Dropdown.js +1 -1
- package/dist/components/actions/FloatingButton.js +1 -0
- package/dist/components/actions/index.js +1 -1
- package/dist/components/actions/menu/VerticalMenu.js +1 -1
- package/dist/components/calendar/_helpers/calendarDays.js +1 -1
- package/dist/components/feedback/alerter.js +1 -1
- package/dist/components/feedback/confirmer.js +1 -1
- package/dist/components/helpers/ConditionalLazyRender.js +1 -0
- package/dist/components/helpers/LazyAction.js +1 -0
- package/dist/components/helpers/LazyRender.js +1 -1
- package/dist/components/helpers/LazyRender.native.js +1 -1
- package/dist/components/helpers/index.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/inputs/DateInput.js +1 -1
- package/dist/components/inputs/InputWrapper.js +1 -1
- package/dist/components/inputs/LinkInput.js +1 -1
- package/dist/components/inputs/NumberInput.js +1 -0
- package/dist/components/inputs/Picker.js +1 -1
- package/dist/components/inputs/Radio.js +1 -1
- package/dist/components/inputs/RateInput.js +1 -0
- package/dist/components/inputs/SegmentedPicker.js +1 -0
- package/dist/components/inputs/Select.js +1 -0
- package/dist/components/inputs/datePicker/DayPicker.js +1 -1
- package/dist/components/inputs/datePicker/MonthPicker.js +1 -1
- package/dist/components/inputs/datePicker/QuarterPicker.js +1 -1
- package/dist/components/inputs/datePicker/WeekPicker.js +1 -1
- package/dist/components/inputs/datePicker/YearPicker.js +1 -1
- package/dist/components/inputs/index.js +1 -1
- package/dist/components/layout/Layout.js +1 -1
- package/dist/components/list/FlatList.js +1 -0
- package/dist/components/list/index.js +1 -1
- package/dist/components/presentation/Rate.js +1 -0
- package/dist/components/presentation/RateTag.js +1 -0
- package/dist/components/presentation/Result.js +1 -1
- package/dist/components/presentation/Tooltip.js +1 -1
- package/dist/components/presentation/index.js +1 -1
- package/dist/components/state/LoadingView.js +1 -1
- package/dist/components/structure/Accordion.js +1 -1
- package/dist/components/structure/Row.js +1 -1
- package/dist/components/structure/Segment.js +1 -0
- package/dist/components/structure/View.js +1 -1
- package/dist/components/structure/bottomDrawer/native/BottomDrawer.js +1 -1
- package/dist/components/structure/bottomDrawer/native/utils.js +1 -1
- package/dist/components/structure/bottomDrawer/web/BottomDrawer.js +1 -1
- package/dist/components/structure/index.js +1 -1
- package/dist/components/structure/overlay/OverlayHandler.js +1 -1
- package/dist/components/structure/popover/Popover.js +1 -1
- package/dist/components/structure/popover/Popover.native.js +1 -1
- package/dist/components/structure/popover/Popover_BU.js +1 -0
- package/dist/components/tabs/ActiveTabContent.js +1 -0
- package/dist/components/tabs/TabsHandler.js +1 -0
- package/dist/components/tabs/TabsMenu.js +1 -0
- package/dist/components/tabs/index.js +1 -0
- package/dist/components/theme/ThemePicker.js +1 -0
- package/dist/components/theme/ThemePickerDrawer.js +1 -0
- package/dist/components/theme/ThemeStatusBar.js +1 -0
- package/dist/components/theme/ThemeStatusBar.native.js +1 -0
- package/dist/components/theme/ThemeThumb.js +1 -0
- package/dist/components/theme/index.js +1 -0
- package/dist/helpers/index.js +1 -1
- package/dist/helpers/storage.js +1 -0
- package/dist/helpers/string.js +1 -1
- package/dist/i18n/I18n.js +1 -0
- package/dist/i18n/I18nProvider.js +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/index.css +4 -0
- package/dist/index.js +1 -1
- package/dist/modifiers/animations/fadeEffect.web.js +1 -0
- package/dist/modifiers/animations/scrollEffect.web.js +1 -0
- package/dist/modifiers/animations/slideEffect.web.js +1 -0
- package/dist/modifiers/fullColor.js +1 -1
- package/dist/modifiers/overflow.js +1 -1
- package/dist/modifiers/position.js +1 -1
- package/dist/theme/ThemeHandler.js +1 -1
- package/dist/theme/default/base.js +1 -1
- package/dist/theme/default/blackTheme.js +1 -1
- package/dist/theme/default/cyberpunkTheme.js +1 -1
- package/dist/theme/default/darkTheme.js +1 -1
- package/dist/theme/default/deepWoodsTheme.js +1 -1
- package/dist/theme/default/forestTheme.js +1 -1
- package/dist/theme/default/hackerTheme.js +1 -1
- package/dist/theme/default/lightTheme.js +1 -1
- package/dist/theme/default/midnightTheme.js +1 -1
- package/dist/theme/default/msdosTheme.js +1 -1
- package/dist/theme/default/oceanTheme.js +1 -1
- package/dist/theme/default/paperTheme.js +1 -0
- package/dist/theme/default/pastelTheme.js +1 -1
- package/dist/theme/default/sunsetTheme.js +1 -1
- package/dist/theme/default/themes.js +1 -1
- package/dist/theme/format/formatTheme.js +1 -1
- package/dist/theme/helpers/contrastColor.js +1 -1
- package/package.json +1 -1
- package/src/DynamicStyleTag.js +21 -0
- package/src/DynamicStyleTag.native.js +3 -0
- package/src/NekoUI.js +21 -4
- package/src/abstractions/ActivityIndicator.native.js +3 -4
- package/src/abstractions/ActivityIndicator.web.js +43 -0
- package/src/abstractions/AnimatedView.web.js +3 -0
- package/src/abstractions/BlurView.web.js +39 -0
- package/src/abstractions/FlatList.js +3 -0
- package/src/abstractions/FlatList.native.js +36 -0
- package/src/abstractions/FlatList.web.js +3 -0
- package/src/abstractions/ScrollView.web.js +3 -0
- package/src/abstractions/StaticList.js +51 -0
- package/src/abstractions/Text.web.js +15 -0
- package/src/abstractions/helpers/storage.js +32 -0
- package/src/abstractions/helpers/storage.native.js +34 -0
- package/src/abstractions/helpers/useSafeAreaInsets.js +3 -0
- package/src/abstractions/helpers/useSafeAreaInsets.native.js +3 -0
- package/src/components/actions/Button.js +1 -0
- package/src/components/actions/Dropdown.js +24 -5
- package/src/components/actions/FloatingButton.js +87 -0
- package/src/components/actions/index.js +1 -0
- package/src/components/actions/menu/VerticalMenu.js +30 -5
- package/src/components/calendar/_helpers/calendarDays.js +2 -0
- package/src/components/feedback/alerter.js +1 -1
- package/src/components/feedback/confirmer.js +2 -2
- package/src/components/helpers/ConditionalLazyRender.js +6 -0
- package/src/components/helpers/LazyAction.js +22 -0
- package/src/components/helpers/LazyRender.js +2 -2
- package/src/components/helpers/LazyRender.native.js +1 -1
- package/src/components/helpers/index.js +1 -0
- package/src/components/index.js +2 -0
- package/src/components/inputs/DateInput.js +11 -1
- package/src/components/inputs/InputWrapper.js +0 -1
- package/src/components/inputs/LinkInput.js +3 -3
- package/src/components/inputs/NumberInput.js +105 -0
- package/src/components/inputs/Picker.js +61 -9
- package/src/components/inputs/Radio.js +1 -1
- package/src/components/inputs/RateInput.js +62 -0
- package/src/components/inputs/SegmentedPicker.js +62 -0
- package/src/components/inputs/Select.js +189 -0
- package/src/components/inputs/datePicker/DayPicker.js +4 -5
- package/src/components/inputs/datePicker/MonthPicker.js +2 -2
- package/src/components/inputs/datePicker/QuarterPicker.js +2 -2
- package/src/components/inputs/datePicker/WeekPicker.js +2 -2
- package/src/components/inputs/datePicker/YearPicker.js +9 -6
- package/src/components/inputs/index.js +4 -0
- package/src/components/layout/Layout.js +1 -1
- package/src/components/list/FlatList.js +91 -0
- package/src/components/list/index.js +1 -0
- package/src/components/presentation/Rate.js +58 -0
- package/src/components/presentation/RateTag.js +35 -0
- package/src/components/presentation/Result.js +2 -2
- package/src/components/presentation/Tooltip.js +1 -0
- package/src/components/presentation/index.js +2 -0
- package/src/components/state/LoadingView.js +10 -1
- package/src/components/structure/Accordion.js +1 -1
- package/src/components/structure/Row.js +9 -1
- package/src/components/structure/Segment.js +51 -0
- package/src/components/structure/View.js +2 -0
- package/src/components/structure/bottomDrawer/native/BottomDrawer.js +19 -3
- package/src/components/structure/bottomDrawer/native/utils.js +29 -22
- package/src/components/structure/bottomDrawer/web/BottomDrawer.js +3 -1
- package/src/components/structure/index.js +1 -0
- package/src/components/structure/overlay/OverlayHandler.js +6 -1
- package/src/components/structure/popover/Popover.js +44 -21
- package/src/components/structure/popover/Popover.native.js +3 -2
- package/src/components/structure/popover/Popover_BU.js +157 -0
- package/src/components/tabs/ActiveTabContent.js +35 -0
- package/src/components/tabs/TabsHandler.js +16 -0
- package/src/components/tabs/TabsMenu.js +15 -0
- package/src/components/tabs/index.js +3 -0
- package/src/components/theme/ThemePicker.js +49 -0
- package/src/components/theme/ThemePickerDrawer.js +13 -0
- package/src/components/theme/ThemeStatusBar.js +3 -0
- package/src/components/theme/ThemeStatusBar.native.js +9 -0
- package/src/components/theme/ThemeThumb.js +98 -0
- package/src/components/theme/index.js +3 -0
- package/src/helpers/index.js +1 -0
- package/src/helpers/storage.js +54 -0
- package/src/helpers/string.js +18 -1
- package/src/i18n/I18n.js +97 -0
- package/src/i18n/I18nProvider.js +40 -0
- package/src/i18n/index.js +2 -0
- package/src/index.css +4 -0
- package/src/index.js +1 -0
- package/src/modifiers/animations/fadeEffect.web.js +3 -0
- package/src/modifiers/animations/scrollEffect.web.js +3 -0
- package/src/modifiers/animations/slideEffect.web.js +3 -0
- package/src/modifiers/fullColor.js +2 -2
- package/src/modifiers/overflow.js +6 -1
- package/src/modifiers/position.js +7 -0
- package/src/theme/ThemeHandler.js +18 -2
- package/src/theme/default/base.js +12 -8
- package/src/theme/default/blackTheme.js +4 -1
- package/src/theme/default/cyberpunkTheme.js +3 -1
- package/src/theme/default/darkTheme.js +3 -1
- package/src/theme/default/deepWoodsTheme.js +4 -2
- package/src/theme/default/forestTheme.js +3 -1
- package/src/theme/default/hackerTheme.js +3 -1
- package/src/theme/default/lightTheme.js +3 -1
- package/src/theme/default/midnightTheme.js +3 -1
- package/src/theme/default/msdosTheme.js +18 -4
- package/src/theme/default/oceanTheme.js +4 -2
- package/src/theme/default/paperTheme.js +35 -0
- package/src/theme/default/pastelTheme.js +3 -1
- package/src/theme/default/sunsetTheme.js +5 -3
- package/src/theme/default/themes.js +7 -10
- package/src/theme/format/formatTheme.js +9 -3
- package/src/theme/helpers/contrastColor.js +49 -11
- package/dist/abstractions/TouchableOpacity.web.js +0 -1
- package/src/abstractions/TouchableOpacity.web.js +0 -3
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { is } from 'ramda'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { LazyRender } from './LazyRender'
|
|
5
|
+
|
|
6
|
+
function InnerContent({ action }) {
|
|
7
|
+
React.useEffect(() => {
|
|
8
|
+
action?.()
|
|
9
|
+
}, [])
|
|
10
|
+
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function LazyAction({ children, disabled, action, minHeight: initMinHeight, ...props }) {
|
|
15
|
+
if (!action || !is(Function, action) || !!disabled) return false
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<LazyRender whenVisible minHeight={2} {...props}>
|
|
19
|
+
<InnerContent action={action} />
|
|
20
|
+
</LazyRender>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -42,13 +42,13 @@ export function LazyRender({
|
|
|
42
42
|
}, [])
|
|
43
43
|
|
|
44
44
|
React.useEffect(() => {
|
|
45
|
-
if (ref.current
|
|
45
|
+
if (ref.current) {
|
|
46
46
|
setMinHeight(ref.current.offsetHeight)
|
|
47
47
|
}
|
|
48
48
|
}, [open])
|
|
49
49
|
|
|
50
50
|
return (
|
|
51
|
-
<View className="neko-lazy-render" {...props} minHeight={minHeight} ref={ref}>
|
|
51
|
+
<View className="neko-lazy-render" flex="0 0 auto" {...props} minHeight={minHeight} ref={ref}>
|
|
52
52
|
{open ? children : null}
|
|
53
53
|
</View>
|
|
54
54
|
)
|
|
@@ -51,7 +51,7 @@ export function LazyRender({
|
|
|
51
51
|
}, [open])
|
|
52
52
|
|
|
53
53
|
return (
|
|
54
|
-
<View className="neko-lazy-render" {...props} minHeight={minHeight} ref={ref}>
|
|
54
|
+
<View className="neko-lazy-render" flex="0 0 auto" {...props} minHeight={minHeight} ref={ref}>
|
|
55
55
|
{open ? children : null}
|
|
56
56
|
</View>
|
|
57
57
|
)
|
package/src/components/index.js
CHANGED
|
@@ -30,6 +30,14 @@ export function getDateInputDefaultFormat(type) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function FullWidthInputWrapper({ ref, ...props }) {
|
|
34
|
+
return (
|
|
35
|
+
<View fullW ref={ref}>
|
|
36
|
+
<MaskInput {...props} />
|
|
37
|
+
</View>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
export function DateInput({
|
|
34
42
|
value,
|
|
35
43
|
onChange,
|
|
@@ -75,7 +83,7 @@ export function DateInput({
|
|
|
75
83
|
setInputValue(!!value ? dayjs(value).format(format) : '')
|
|
76
84
|
}, [value])
|
|
77
85
|
|
|
78
|
-
const Input = useBottomDrawer ? LinkInput :
|
|
86
|
+
const Input = useBottomDrawer ? LinkInput : FullWidthInputWrapper
|
|
79
87
|
|
|
80
88
|
return (
|
|
81
89
|
<Popover
|
|
@@ -83,6 +91,8 @@ export function DateInput({
|
|
|
83
91
|
placement={placement || 'bottomLeft'}
|
|
84
92
|
snapPoints={[350]}
|
|
85
93
|
useBottomDrawer={useBottomDrawer}
|
|
94
|
+
bottomDrawerProps={{ contentProps: { padding: 'md' } }}
|
|
95
|
+
watch={[value?.format?.('YYYYMMDD')]}
|
|
86
96
|
renderContent={({ onClose }) => (
|
|
87
97
|
<View flex centerH>
|
|
88
98
|
<DatePicker
|
|
@@ -4,12 +4,12 @@ import { Text } from '../text/Text'
|
|
|
4
4
|
import { View } from '../structure/View'
|
|
5
5
|
import { useColors } from '../../theme/ThemeHandler'
|
|
6
6
|
|
|
7
|
-
export function LinkInput({ onPress, onClick, placeholder, value, disabled, ...props }) {
|
|
7
|
+
export function LinkInput({ ref, onPress, onClick, placeholder, value, disabled, ...props }) {
|
|
8
8
|
return (
|
|
9
|
-
<Link onPress={!props.loading ? onPress || onClick : undefined} flex fullW centerV disabled={disabled}>
|
|
9
|
+
<Link ref={ref} onPress={!props.loading ? onPress || onClick : undefined} flex fullW centerV disabled={disabled}>
|
|
10
10
|
<InputWrapper {...props}>
|
|
11
11
|
<View centerV flex fullW>
|
|
12
|
-
<Text color={!!value ? 'text' : 'text_op30'} label={value || placeholder} />
|
|
12
|
+
<Text color={!!value ? 'text' : 'text_op30'} label={value || placeholder} numberOfLines={1} />
|
|
13
13
|
</View>
|
|
14
14
|
</InputWrapper>
|
|
15
15
|
</Link>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { endsWith, is } from 'ramda'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { TextInput } from './TextInput'
|
|
5
|
+
|
|
6
|
+
function isValidNumber(stringValue, options = {}) {
|
|
7
|
+
const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER, useInt, precision } = options
|
|
8
|
+
|
|
9
|
+
if (stringValue === null || stringValue === undefined || stringValue === '') return true
|
|
10
|
+
|
|
11
|
+
if (isNaN(stringValue)) return false
|
|
12
|
+
const numericValue = parseFloat(stringValue)
|
|
13
|
+
|
|
14
|
+
if (numericValue < min) return false
|
|
15
|
+
if (numericValue > max) return false
|
|
16
|
+
|
|
17
|
+
const decimalPart = stringValue?.toString()?.split?.('.')[1]
|
|
18
|
+
|
|
19
|
+
if (decimalPart && is(Number, precision)) {
|
|
20
|
+
if (decimalPart.length > precision) return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function formatNumericValue(newValue, prevValue, options = {}) {
|
|
27
|
+
const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER, useInt, precision } = options
|
|
28
|
+
let numericValue = newValue
|
|
29
|
+
|
|
30
|
+
// Handle number to string conversion
|
|
31
|
+
if (is(Number, newValue)) newValue = newValue.toString()
|
|
32
|
+
|
|
33
|
+
// Handle null/undefined/empty
|
|
34
|
+
if (newValue === null || newValue === undefined || newValue === '') return null
|
|
35
|
+
|
|
36
|
+
// Normalize decimal separator (comma to dot)
|
|
37
|
+
if (is(String, newValue)) newValue = newValue.replace(',', '.')
|
|
38
|
+
|
|
39
|
+
// Allow negative sign as intermediate state
|
|
40
|
+
if (newValue === '-') return newValue
|
|
41
|
+
|
|
42
|
+
if (useInt) {
|
|
43
|
+
// For integers, don't allow decimal points
|
|
44
|
+
if (newValue.includes('.')) return prevValue
|
|
45
|
+
numericValue = parseInt(newValue, 10)
|
|
46
|
+
} else {
|
|
47
|
+
// For floats, handle decimal points
|
|
48
|
+
const dotsCount = newValue.split('.').length
|
|
49
|
+
if (dotsCount > 2) return prevValue
|
|
50
|
+
|
|
51
|
+
// Allow "1." as intermediate state
|
|
52
|
+
if (endsWith('.', newValue)) return newValue
|
|
53
|
+
|
|
54
|
+
numericValue = parseFloat(newValue)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isNaN(numericValue)) return prevValue
|
|
58
|
+
|
|
59
|
+
// Check min/max
|
|
60
|
+
if (numericValue < min) return min
|
|
61
|
+
if (numericValue > max) return max
|
|
62
|
+
|
|
63
|
+
// Handle decimal precision
|
|
64
|
+
const decimalPart = newValue.split('.')[1]
|
|
65
|
+
if (decimalPart && !!precision) {
|
|
66
|
+
// Exceeded precision, reject
|
|
67
|
+
if (decimalPart.length > precision) return prevValue
|
|
68
|
+
// Within precision, keep as string to preserve "1.0" while typing "1.05"
|
|
69
|
+
return newValue
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// No decimals, return as number
|
|
73
|
+
return numericValue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function NumberInput({ onChange, value, useInt, precision, min, max, error, ...props }) {
|
|
77
|
+
const [hasError, setHasError] = React.useState(false)
|
|
78
|
+
const [inputValue, setInputValue] = React.useState(value)
|
|
79
|
+
const [localValue, setLocalValue] = React.useState(value)
|
|
80
|
+
React.useEffect(() => setInputValue(value), [value])
|
|
81
|
+
|
|
82
|
+
if (useInt) precision = 0
|
|
83
|
+
if (!useInt && precision === 0) useInt = true
|
|
84
|
+
const opts = { useInt, precision, min, max }
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<TextInput
|
|
88
|
+
onChange={(newValue) => {
|
|
89
|
+
const numericValue = formatNumericValue(newValue, localValue, opts)
|
|
90
|
+
setInputValue(newValue?.toString() || '')
|
|
91
|
+
setLocalValue(numericValue)
|
|
92
|
+
onChange?.(numericValue)
|
|
93
|
+
setHasError(!isValidNumber(newValue, opts))
|
|
94
|
+
}}
|
|
95
|
+
onBlur={() => {
|
|
96
|
+
setInputValue(localValue)
|
|
97
|
+
setHasError(!isValidNumber(localValue, opts))
|
|
98
|
+
}}
|
|
99
|
+
value={inputValue}
|
|
100
|
+
keyboardType={useInt ? 'number-pad' : 'decimal-pad'}
|
|
101
|
+
error={error || hasError}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
@@ -1,10 +1,35 @@
|
|
|
1
|
+
import { is } from 'ramda'
|
|
1
2
|
import React from 'react'
|
|
2
3
|
|
|
3
4
|
import { Col } from '../structure/Col'
|
|
5
|
+
import { FlatList } from '../list/FlatList'
|
|
4
6
|
import { LoadingView } from '../state/LoadingView'
|
|
5
7
|
import { Row } from '../structure/Row'
|
|
8
|
+
import { normalizeString } from '../../helpers/string'
|
|
6
9
|
import { useOptions } from '../../helpers/options'
|
|
7
10
|
|
|
11
|
+
export function getOption(options, value, config = {}) {
|
|
12
|
+
if (!options?.length) return value
|
|
13
|
+
const option = options.find((option) => compareOptionsValues(option, value, config))
|
|
14
|
+
return option || value
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getOptionLabel(options, value, config = {}) {
|
|
18
|
+
if (!options?.length) return ''
|
|
19
|
+
const { labelKey } = config
|
|
20
|
+
const selectedOption = getOption(options, value, config)
|
|
21
|
+
const label = selectedOption?.[labelKey] || value
|
|
22
|
+
if (!is(String, label)) return ''
|
|
23
|
+
return label
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function searchOptions(options, search, config = {}) {
|
|
27
|
+
const { labelKey } = config
|
|
28
|
+
if (!options?.length) return options
|
|
29
|
+
if (!search) return options
|
|
30
|
+
return options.filter((item) => normalizeString(item?.[labelKey])?.includes(normalizeString(search)))
|
|
31
|
+
}
|
|
32
|
+
|
|
8
33
|
function isSelected(value, option, config = {}) {
|
|
9
34
|
return !!config.multiple
|
|
10
35
|
? value?.some?.((item) => compareOptionsValues(item, option, config))
|
|
@@ -53,12 +78,33 @@ function PickerItem({ option, onChange, value, renderOption, useRawOption, multi
|
|
|
53
78
|
const handleChange = () => {
|
|
54
79
|
const formatChangeValueFunc = multiple ? formatMultipleChangeValue : formatSingleChangeValue
|
|
55
80
|
const formattedValue = formatChangeValueFunc(option, value, { selected, useRawOption, valueKey })
|
|
56
|
-
onChange(formattedValue)
|
|
81
|
+
onChange(formattedValue, option)
|
|
57
82
|
}
|
|
58
83
|
|
|
59
84
|
return <Col {...props}>{renderOption({ option, selected, onChange: handleChange, valueKey, labelKey })}</Col>
|
|
60
85
|
}
|
|
61
86
|
|
|
87
|
+
function DefaultPickerWrapper({ renderItem, options, ...props }) {
|
|
88
|
+
return (
|
|
89
|
+
<Row className="neko-picker" gap="md" {...props}>
|
|
90
|
+
{options?.map?.((option) => renderItem(option))}
|
|
91
|
+
</Row>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function FlatListPickerWrapper({ renderItem, options, valueKey, ...props }) {
|
|
96
|
+
return (
|
|
97
|
+
<FlatList
|
|
98
|
+
keyExtractor={(i) => i[valueKey]}
|
|
99
|
+
data={options}
|
|
100
|
+
divider
|
|
101
|
+
fullH
|
|
102
|
+
renderItem={({ item: option }) => renderItem(option)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
62
108
|
export function Picker({
|
|
63
109
|
value,
|
|
64
110
|
initialValue,
|
|
@@ -68,23 +114,24 @@ export function Picker({
|
|
|
68
114
|
renderOption,
|
|
69
115
|
colProps,
|
|
70
116
|
useRawOption,
|
|
117
|
+
useFlatList,
|
|
71
118
|
multiple,
|
|
72
119
|
valueKey,
|
|
73
120
|
labelKey,
|
|
121
|
+
Wrapper,
|
|
74
122
|
...rootProps
|
|
75
123
|
}) {
|
|
76
124
|
const [localValue, setLocalValue] = React.useState(initialValue)
|
|
77
125
|
value = value === undefined ? localValue : value
|
|
78
126
|
onChange = onChange || setLocalValue
|
|
127
|
+
const { options: finalOptions, isFirstLoad } = useOptions(options, {})
|
|
79
128
|
|
|
80
|
-
const handleChange = (v) => {
|
|
129
|
+
const handleChange = (v, option) => {
|
|
81
130
|
if (!!disabled) return
|
|
82
131
|
setLocalValue(v)
|
|
83
|
-
onChange?.(v)
|
|
132
|
+
onChange?.(v, option)
|
|
84
133
|
}
|
|
85
134
|
|
|
86
|
-
const { options: finalOptions, isFirstLoad } = useOptions(options, {})
|
|
87
|
-
|
|
88
135
|
valueKey = valueKey || 'value'
|
|
89
136
|
labelKey = labelKey || 'label'
|
|
90
137
|
|
|
@@ -93,10 +140,15 @@ export function Picker({
|
|
|
93
140
|
return false
|
|
94
141
|
}
|
|
95
142
|
|
|
143
|
+
Wrapper = Wrapper || (useFlatList ? FlatListPickerWrapper : DefaultPickerWrapper)
|
|
144
|
+
|
|
96
145
|
return (
|
|
97
146
|
<LoadingView active={isFirstLoad} replaceChildren>
|
|
98
|
-
<
|
|
99
|
-
{
|
|
147
|
+
<Wrapper
|
|
148
|
+
{...rootProps}
|
|
149
|
+
valueKey={valueKey}
|
|
150
|
+
options={finalOptions}
|
|
151
|
+
renderItem={(option) => (
|
|
100
152
|
<PickerItem
|
|
101
153
|
key={option.value}
|
|
102
154
|
option={option}
|
|
@@ -109,8 +161,8 @@ export function Picker({
|
|
|
109
161
|
labelKey={labelKey}
|
|
110
162
|
{...colProps}
|
|
111
163
|
/>
|
|
112
|
-
)
|
|
113
|
-
|
|
164
|
+
)}
|
|
165
|
+
/>
|
|
114
166
|
</LoadingView>
|
|
115
167
|
)
|
|
116
168
|
}
|
|
@@ -33,7 +33,7 @@ export function Radio({ value, onChange, disabled, initialValue, ...rootProps })
|
|
|
33
33
|
size={sizeCode}
|
|
34
34
|
gap={8}
|
|
35
35
|
content={
|
|
36
|
-
<View height={size * 0.65} ratio={1} border={2} padding={
|
|
36
|
+
<View height={size * 0.65} ratio={1} border={2} padding={2} borderColor={color} br={size} center>
|
|
37
37
|
{!!value && <View bg={color} br={size} flex fullW fullH />}
|
|
38
38
|
</View>
|
|
39
39
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { pipe, range, is } from 'ramda'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { Icon } from '../presentation'
|
|
5
|
+
import { Link } from '../actions/Link'
|
|
6
|
+
import { LoadingView } from '../state'
|
|
7
|
+
import { View } from '../structure/View'
|
|
8
|
+
import { moveScale } from '../../theme/helpers/sizeScale'
|
|
9
|
+
import { useColorConverter } from '../../modifiers/colorConverter'
|
|
10
|
+
import { useDefaultModifier } from '../../modifiers/default'
|
|
11
|
+
import { useSizeConverter } from '../../modifiers/sizeConverter'
|
|
12
|
+
import { useThemeComponentModifier } from '../../modifiers/themeComponent'
|
|
13
|
+
|
|
14
|
+
const DEFAULT_PROPS = {
|
|
15
|
+
color: 'primary',
|
|
16
|
+
inactiveColor: 'text4_op50',
|
|
17
|
+
max: 5,
|
|
18
|
+
icon: 'star-fill',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function RateInput({ value, onChange, disabled, loading, ...rootProps }) {
|
|
22
|
+
let [{ size, sizeCode, color }, formattedProps] = pipe(
|
|
23
|
+
useColorConverter('primary'),
|
|
24
|
+
useSizeConverter('icons', 'md'),
|
|
25
|
+
useThemeComponentModifier('RateInput'),
|
|
26
|
+
useDefaultModifier(DEFAULT_PROPS)
|
|
27
|
+
)([{}, rootProps])
|
|
28
|
+
|
|
29
|
+
const [localValue, setLocalValue] = React.useState(value)
|
|
30
|
+
React.useEffect(() => setLocalValue(value), [value])
|
|
31
|
+
|
|
32
|
+
const { icon, max, inactiveColor, ...props } = formattedProps
|
|
33
|
+
|
|
34
|
+
const handleChange = (v) => {
|
|
35
|
+
if (!!disabled) return
|
|
36
|
+
const newValue = v === localValue ? null : v
|
|
37
|
+
setLocalValue(newValue)
|
|
38
|
+
onChange?.(newValue)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<LoadingView active={loading} width="fit-content">
|
|
43
|
+
<View className="neko-rate-input" row gap="xs" centerV minHeight={sizeCode} {...props}>
|
|
44
|
+
{range(1, max + 1).map((i) => {
|
|
45
|
+
const active = localValue >= i
|
|
46
|
+
|
|
47
|
+
let finalIcon = icon
|
|
48
|
+
if (is(Function, icon)) finalIcon = icon?.({ value: localValue, optionValue: i, active })
|
|
49
|
+
|
|
50
|
+
let finalColor = color
|
|
51
|
+
if (is(Function, color)) finalColor = color?.({ value: localValue, optionValue: i, active })
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Link onPress={() => handleChange(i)} disabled={disabled} center key={i}>
|
|
55
|
+
<Icon name={finalIcon} size={moveScale(sizeCode, 1)} color={active ? finalColor : inactiveColor} />
|
|
56
|
+
</Link>
|
|
57
|
+
)
|
|
58
|
+
})}
|
|
59
|
+
</View>
|
|
60
|
+
</LoadingView>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { pipe } from 'ramda'
|
|
2
|
+
|
|
3
|
+
import { Button } from '../actions'
|
|
4
|
+
import { Picker } from './Picker'
|
|
5
|
+
import { View } from '../structure'
|
|
6
|
+
import { moveScale } from '../../theme/helpers/sizeScale'
|
|
7
|
+
import { useDefaultModifier } from '../../modifiers/default'
|
|
8
|
+
import { useSizeConverter } from '../../modifiers/sizeConverter'
|
|
9
|
+
import { useThemeComponentModifier } from '../../modifiers/themeComponent'
|
|
10
|
+
|
|
11
|
+
const DEFAULT_PROPS = ([{ sizeCode }]) => ({
|
|
12
|
+
gap: 1,
|
|
13
|
+
br: sizeCode,
|
|
14
|
+
minHeight: sizeCode,
|
|
15
|
+
bg: 'overlayBG',
|
|
16
|
+
padding: 2,
|
|
17
|
+
wrap: false,
|
|
18
|
+
row: true,
|
|
19
|
+
border: true,
|
|
20
|
+
buttonProps: {
|
|
21
|
+
fullH: true,
|
|
22
|
+
size: sizeCode,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
function PickerWrapper({ renderItem, options, ...props }) {
|
|
27
|
+
return (
|
|
28
|
+
<View row>
|
|
29
|
+
<View {...props}>{options?.map?.((option) => renderItem(option))}</View>
|
|
30
|
+
</View>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function SegmentedPicker({ ...rootProps }) {
|
|
35
|
+
const [{ sizeCode }, formattedProps] = pipe(
|
|
36
|
+
useSizeConverter('elementHeights', 'md'),
|
|
37
|
+
useThemeComponentModifier('SegmentPicker'),
|
|
38
|
+
useDefaultModifier(DEFAULT_PROPS)
|
|
39
|
+
)([{}, rootProps])
|
|
40
|
+
|
|
41
|
+
const { buttonProps, color, ...props } = formattedProps
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Picker
|
|
45
|
+
className="neko-segmented-picker"
|
|
46
|
+
Wrapper={PickerWrapper}
|
|
47
|
+
{...props}
|
|
48
|
+
renderOption={({ option, selected, onChange, labelKey, ...props }) => (
|
|
49
|
+
<Button
|
|
50
|
+
label={option[labelKey]}
|
|
51
|
+
onPress={onChange}
|
|
52
|
+
color={selected ? color || 'primary' : rootProps?.bg || 'overlayBG'}
|
|
53
|
+
round={rootProps?.round}
|
|
54
|
+
textProps={{ strong: selected }}
|
|
55
|
+
opacity={!selected && 0.8}
|
|
56
|
+
{...option}
|
|
57
|
+
{...buttonProps}
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { Icon, IconLabel } from '../presentation'
|
|
4
|
+
import { Link } from '../actions'
|
|
5
|
+
import { LinkInput } from './LinkInput'
|
|
6
|
+
import { Picker, getOptionLabel, searchOptions } from './Picker'
|
|
7
|
+
import { Popover } from '../structure/popover/Popover'
|
|
8
|
+
import { TextInput } from './TextInput'
|
|
9
|
+
import { View } from '../structure'
|
|
10
|
+
import { useResponsiveValue } from '../../responsive'
|
|
11
|
+
|
|
12
|
+
function FullWidthInputWrapper({ ref, ...props }) {
|
|
13
|
+
return (
|
|
14
|
+
<View fullW ref={ref}>
|
|
15
|
+
<TextInput {...props} />
|
|
16
|
+
</View>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Select({
|
|
21
|
+
value,
|
|
22
|
+
onChange,
|
|
23
|
+
onChangeSearch,
|
|
24
|
+
options,
|
|
25
|
+
placement,
|
|
26
|
+
placeholder,
|
|
27
|
+
initialLabel,
|
|
28
|
+
useBottomDrawer = { native: true, sm: true, md: true },
|
|
29
|
+
useSearch,
|
|
30
|
+
renderOption,
|
|
31
|
+
labelKey,
|
|
32
|
+
valueKey,
|
|
33
|
+
useRawOption,
|
|
34
|
+
multiple,
|
|
35
|
+
onEndReached,
|
|
36
|
+
renderFooter,
|
|
37
|
+
renderHeader,
|
|
38
|
+
pickerProps,
|
|
39
|
+
popoverProps,
|
|
40
|
+
popoverMaxHeight,
|
|
41
|
+
...props
|
|
42
|
+
}) {
|
|
43
|
+
const [focus, setFocus] = React.useState(false)
|
|
44
|
+
const [search, setSearch] = React.useState('')
|
|
45
|
+
const [inputValue, setInputValue] = React.useState(initialLabel || '')
|
|
46
|
+
const [localValue, setLocalValue] = React.useState(value)
|
|
47
|
+
|
|
48
|
+
labelKey = labelKey || pickerProps?.labelKey || 'label'
|
|
49
|
+
valueKey = valueKey || pickerProps?.valueKey || 'value'
|
|
50
|
+
useRawOption = useRawOption || pickerProps?.useRawOption
|
|
51
|
+
multiple = multiple || pickerProps?.multiple
|
|
52
|
+
onEndReached = onEndReached || pickerProps?.onEndReached
|
|
53
|
+
renderFooter = renderFooter || pickerProps?.renderFooter
|
|
54
|
+
renderHeader = renderHeader || pickerProps?.renderHeader
|
|
55
|
+
pickerProps = { ...pickerProps, labelKey, valueKey, useRawOption, multiple, onEndReached, renderFooter, renderHeader }
|
|
56
|
+
|
|
57
|
+
popoverMaxHeight = popoverMaxHeight || 300
|
|
58
|
+
|
|
59
|
+
useBottomDrawer = useResponsiveValue(useBottomDrawer)
|
|
60
|
+
|
|
61
|
+
value = value || localValue
|
|
62
|
+
|
|
63
|
+
const handleChange = React.useCallback(
|
|
64
|
+
(value, option) => {
|
|
65
|
+
if (!!multiple) {
|
|
66
|
+
setInputValue(value.map((item) => getOptionLabel(options, item, { valueKey, labelKey })).join(', '))
|
|
67
|
+
} else {
|
|
68
|
+
setInputValue(option?.[labelKey] || getOptionLabel(options, option, { valueKey, labelKey }))
|
|
69
|
+
}
|
|
70
|
+
setLocalValue(value)
|
|
71
|
+
onChange?.(value)
|
|
72
|
+
},
|
|
73
|
+
[labelKey, valueKey]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
React.useEffect(() => {
|
|
77
|
+
if (!!multiple) {
|
|
78
|
+
setInputValue(value?.map?.((item) => getOptionLabel(options, item, { valueKey, labelKey })).join(', '))
|
|
79
|
+
} else {
|
|
80
|
+
const label = value?.[labelKey] || getOptionLabel(options, value, { valueKey, labelKey })
|
|
81
|
+
if (!!label || !value) setInputValue(label)
|
|
82
|
+
}
|
|
83
|
+
}, [value])
|
|
84
|
+
|
|
85
|
+
const handleChangeSearch = React.useCallback((v) => {
|
|
86
|
+
onChangeSearch?.(v)
|
|
87
|
+
setSearch(v)
|
|
88
|
+
}, [])
|
|
89
|
+
|
|
90
|
+
const Input = !useSearch || useBottomDrawer ? LinkInput : FullWidthInputWrapper
|
|
91
|
+
const valueWatcher = multiple && localValue
|
|
92
|
+
|
|
93
|
+
const finalRenderOption = React.useCallback(
|
|
94
|
+
(params) => {
|
|
95
|
+
if (!!renderOption) return renderOption(params)
|
|
96
|
+
const { option, labelKey, selected } = params
|
|
97
|
+
return <IconLabel {...option} label={option?.[labelKey]} flex strong={selected} />
|
|
98
|
+
},
|
|
99
|
+
[renderOption]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<Popover
|
|
104
|
+
trigger="click"
|
|
105
|
+
placement={placement || 'bottomLeft'}
|
|
106
|
+
snapPoints={[450]}
|
|
107
|
+
useBottomDrawer={useBottomDrawer}
|
|
108
|
+
parentWidth
|
|
109
|
+
padding={0}
|
|
110
|
+
watch={[search, options, valueWatcher]}
|
|
111
|
+
unmountOnClose
|
|
112
|
+
maxHeight={popoverMaxHeight}
|
|
113
|
+
{...popoverProps}
|
|
114
|
+
renderContent={({ onClose }) => (
|
|
115
|
+
<>
|
|
116
|
+
{useBottomDrawer && useSearch && (
|
|
117
|
+
<View padding="md" paddingB="xs">
|
|
118
|
+
<TextInput
|
|
119
|
+
prefixIcon="search-line"
|
|
120
|
+
prefixIconColor="text4"
|
|
121
|
+
value={search}
|
|
122
|
+
onChange={handleChangeSearch}
|
|
123
|
+
/>
|
|
124
|
+
</View>
|
|
125
|
+
)}
|
|
126
|
+
<Picker
|
|
127
|
+
row={false}
|
|
128
|
+
options={searchOptions(options, search, { labelKey })}
|
|
129
|
+
value={value}
|
|
130
|
+
gap={0}
|
|
131
|
+
maxHeight={!useBottomDrawer && popoverMaxHeight}
|
|
132
|
+
useFlatList
|
|
133
|
+
onlyOnScreen
|
|
134
|
+
itemMinHeight={30}
|
|
135
|
+
onChange={(v, option) => {
|
|
136
|
+
handleChange(v, option)
|
|
137
|
+
if (!multiple) onClose()
|
|
138
|
+
}}
|
|
139
|
+
{...pickerProps}
|
|
140
|
+
renderOption={({ option, selected, onChange }) => (
|
|
141
|
+
<Link
|
|
142
|
+
row
|
|
143
|
+
paddingH={useBottomDrawer ? 'md' : 'sm'}
|
|
144
|
+
paddingV="xs"
|
|
145
|
+
minHeight={useBottomDrawer ? 'xl' : 'md'}
|
|
146
|
+
gap="sm"
|
|
147
|
+
onMouseDown={(e) => !!multiple && e.preventDefault()}
|
|
148
|
+
onPress={onChange}
|
|
149
|
+
centerV
|
|
150
|
+
bg={selected && 'primary_op10'}
|
|
151
|
+
>
|
|
152
|
+
<View flex row>
|
|
153
|
+
{finalRenderOption({ option, labelKey, selected })}
|
|
154
|
+
</View>
|
|
155
|
+
{selected && <Icon name="checkbox-circle-fill" primary />}
|
|
156
|
+
</Link>
|
|
157
|
+
)}
|
|
158
|
+
/>
|
|
159
|
+
</>
|
|
160
|
+
)}
|
|
161
|
+
>
|
|
162
|
+
<Input
|
|
163
|
+
value={!!focus ? search : inputValue}
|
|
164
|
+
onChange={handleChangeSearch}
|
|
165
|
+
onFocus={() => {
|
|
166
|
+
handleChangeSearch('')
|
|
167
|
+
setFocus(true)
|
|
168
|
+
}}
|
|
169
|
+
onBlur={() => {
|
|
170
|
+
setTimeout(() => {
|
|
171
|
+
setFocus(false)
|
|
172
|
+
}, 200)
|
|
173
|
+
}}
|
|
174
|
+
// When the option to use tags presentation, use prefix to render the tags
|
|
175
|
+
// _prefix={
|
|
176
|
+
// multiple &&
|
|
177
|
+
// value?.length && (
|
|
178
|
+
// <Text>{value?.map((item) => getOptionLabel(options, item, { labelKey, valueKey })).join(', ')}</Text>
|
|
179
|
+
// )
|
|
180
|
+
// }
|
|
181
|
+
placeholder={(!multiple || !value?.length) && placeholder}
|
|
182
|
+
suffixIcon="arrow-down-s-fill"
|
|
183
|
+
suffixIconColor="text4"
|
|
184
|
+
fullW
|
|
185
|
+
{...props}
|
|
186
|
+
/>
|
|
187
|
+
</Popover>
|
|
188
|
+
)
|
|
189
|
+
}
|