@codeleap/mobile 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/components/Button/index.d.ts +0 -1
  2. package/dist/components/Button/index.js +5 -12
  3. package/dist/components/Button/index.js.map +1 -1
  4. package/dist/components/List/styles.js +1 -1
  5. package/dist/components/List/styles.js.map +1 -1
  6. package/dist/components/Touchable/index.js +11 -9
  7. package/dist/components/Touchable/index.js.map +1 -1
  8. package/dist/utils/theme.d.ts +1 -0
  9. package/dist/utils/theme.js +7 -2
  10. package/dist/utils/theme.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/components/ActionIcon/index.tsx +32 -0
  13. package/src/components/ActionIcon/styles.ts +97 -0
  14. package/src/components/ActivityIndicator/index.tsx +50 -0
  15. package/src/components/ActivityIndicator/styles.ts +68 -0
  16. package/src/components/Animated.tsx +34 -0
  17. package/src/components/AutoComplete/index.tsx +163 -0
  18. package/src/components/AutoComplete/styles.ts +44 -0
  19. package/src/components/Backdrop/index.tsx +48 -0
  20. package/src/components/Backdrop/styles.ts +33 -0
  21. package/src/components/Button/index.tsx +154 -0
  22. package/src/components/Button/styles.ts +129 -0
  23. package/src/components/Calendar/index.tsx +65 -0
  24. package/src/components/Calendar/style.ts +35 -0
  25. package/src/components/Calendar/types.ts +102 -0
  26. package/src/components/Checkbox/index.tsx +91 -0
  27. package/src/components/Checkbox/styles.ts +81 -0
  28. package/src/components/ContentView/index.tsx +63 -0
  29. package/src/components/ContentView/styles.ts +24 -0
  30. package/src/components/Drawer/index.tsx +33 -0
  31. package/src/components/Drawer/styles.ts +43 -0
  32. package/src/components/EmptyPlaceholder/index.tsx +88 -0
  33. package/src/components/EmptyPlaceholder/styles.ts +58 -0
  34. package/src/components/FileInput/index.tsx +181 -0
  35. package/src/components/FileInput/styles.ts +15 -0
  36. package/src/components/Grid/index.tsx +117 -0
  37. package/src/components/Grid/styles.ts +11 -0
  38. package/src/components/Icon/index.tsx +69 -0
  39. package/src/components/Icon/styles.ts +57 -0
  40. package/src/components/Image/index.tsx +91 -0
  41. package/src/components/Image/styles.ts +20 -0
  42. package/src/components/ImageView/Spotlight.tsx +157 -0
  43. package/src/components/ImageView/component.tsx +38 -0
  44. package/src/components/ImageView/index.ts +2 -0
  45. package/src/components/InputLabel/index.tsx +38 -0
  46. package/src/components/InputLabel/styles.ts +19 -0
  47. package/src/components/List/PaginationIndicator.tsx +71 -0
  48. package/src/components/List/index.tsx +114 -0
  49. package/src/components/List/styles.ts +19 -0
  50. package/src/components/Modal/index.tsx +218 -0
  51. package/src/components/Modal/styles.ts +153 -0
  52. package/src/components/MultiSelect/index.tsx +138 -0
  53. package/src/components/MultiSelect/styles.ts +18 -0
  54. package/src/components/MultiSelect/types.ts +42 -0
  55. package/src/components/Navigation/Navigation.tsx +54 -0
  56. package/src/components/Navigation/constants.ts +8 -0
  57. package/src/components/Navigation/index.tsx +3 -0
  58. package/src/components/Navigation/types.ts +35 -0
  59. package/src/components/Navigation/utils.tsx +57 -0
  60. package/src/components/Pager/index.tsx +121 -0
  61. package/src/components/Pager/styles.ts +81 -0
  62. package/src/components/RadioInput/index.tsx +106 -0
  63. package/src/components/RadioInput/styles.ts +67 -0
  64. package/src/components/Scroll/index.tsx +124 -0
  65. package/src/components/Scroll/styles.ts +18 -0
  66. package/src/components/Sections/index.tsx +91 -0
  67. package/src/components/SegmentedControl/index.tsx +204 -0
  68. package/src/components/SegmentedControl/styles.ts +89 -0
  69. package/src/components/Select/index.tsx +167 -0
  70. package/src/components/Select/styles.ts +62 -0
  71. package/src/components/Select/types.ts +43 -0
  72. package/src/components/Slider/Mark.tsx +46 -0
  73. package/src/components/Slider/Thumb.tsx +29 -0
  74. package/src/components/Slider/index.tsx +130 -0
  75. package/src/components/Slider/styles.ts +76 -0
  76. package/src/components/Slider/types.ts +30 -0
  77. package/src/components/Switch/index.tsx +91 -0
  78. package/src/components/Switch/styles.ts +38 -0
  79. package/src/components/Text/index.tsx +97 -0
  80. package/src/components/Text/styles.ts +50 -0
  81. package/src/components/TextInput/index.tsx +319 -0
  82. package/src/components/TextInput/styles.ts +127 -0
  83. package/src/components/Touchable/index.tsx +174 -0
  84. package/src/components/Touchable/styles.ts +28 -0
  85. package/src/components/View/index.tsx +103 -0
  86. package/src/components/View/styles.ts +24 -0
  87. package/src/components/components.ts +42 -0
  88. package/src/components/defaultStyles.ts +62 -0
  89. package/src/components/legacy/Modal/index.tsx +163 -0
  90. package/src/components/legacy/Modal/styles.ts +125 -0
  91. package/src/components/legacy/Pager/index.tsx +242 -0
  92. package/src/components/legacy/Pager/styles.ts +51 -0
  93. package/src/components/legacy/index.ts +2 -0
  94. package/src/modules/documentPicker.ts +7 -0
  95. package/src/modules/fastImage.ts +2 -0
  96. package/src/modules/imageCropPicker.d.ts +497 -0
  97. package/src/modules/index.d.ts +682 -0
  98. package/src/modules/reactNavigation.ts +15 -0
  99. package/src/modules/textInputMask.ts +11 -0
  100. package/src/modules/types/documentPicker.d.ts +215 -0
  101. package/src/modules/types/fileTypes.ts +138 -0
  102. package/src/modules/types/textInputMask.ts +9 -0
  103. package/src/types/index.ts +1 -0
  104. package/src/types/utility.ts +9 -0
  105. package/src/utils/KeyboardAware/context.tsx +75 -0
  106. package/src/utils/KeyboardAware/index.ts +17 -0
  107. package/src/utils/KeyboardAware/keyboardHooks.ts +124 -0
  108. package/src/utils/KeyboardAware/lib/KeyboardAwareFlatList.ts +4 -0
  109. package/src/utils/KeyboardAware/lib/KeyboardAwareHOC.tsx +618 -0
  110. package/src/utils/KeyboardAware/lib/KeyboardAwareInterface.ts +13 -0
  111. package/src/utils/KeyboardAware/lib/KeyboardAwareScrollView.ts +6 -0
  112. package/src/utils/KeyboardAware/lib/KeyboardAwareSectionList.ts +6 -0
  113. package/src/utils/KeyboardAware/types.ts +159 -0
  114. package/src/utils/ModalManager/components.tsx +112 -0
  115. package/src/utils/ModalManager/context.tsx +260 -0
  116. package/src/utils/ModalManager/index.ts +16 -0
  117. package/src/utils/OSAlert.ts +180 -0
  118. package/src/utils/PermissionManager/context.tsx +302 -0
  119. package/src/utils/PermissionManager/index.ts +20 -0
  120. package/src/utils/PermissionManager/types.ts +24 -0
  121. package/src/utils/hooks.ts +163 -0
  122. package/src/utils/index.ts +11 -0
  123. package/src/utils/input.ts +51 -0
  124. package/src/utils/misc.ts +83 -0
  125. package/src/utils/notifications.ts +206 -0
  126. package/src/utils/theme.ts +58 -0
@@ -0,0 +1,81 @@
1
+ import {
2
+ createDefaultVariantFactory,
3
+ includePresets,
4
+ } from '@codeleap/common'
5
+ import { Easing } from 'react-native'
6
+
7
+ export type PagerComposition =
8
+ | 'page'
9
+ | 'page:transition'
10
+ | 'page:previous'
11
+ | 'page:next'
12
+ | 'page:current'
13
+ | 'wrapper'
14
+
15
+ const createPagerStyle = createDefaultVariantFactory<PagerComposition>()
16
+
17
+ const presets = includePresets((style) => createPagerStyle(() => ({ wrapper: style })),
18
+ )
19
+ export const defaultPagerTransition = {
20
+ type: 'timing',
21
+ duration: 300,
22
+ easing: Easing.linear,
23
+ }
24
+
25
+ export function pagerAnimation(height, width, translate = 'X', transition = defaultPagerTransition) {
26
+ const translateProp = `translate${translate}`
27
+
28
+ const translateVal = translate === 'X' ? width : height
29
+
30
+ return {
31
+ wrapper: {
32
+ height,
33
+ width,
34
+ overflow: 'hidden',
35
+ },
36
+ 'page:transition': {
37
+ [translateProp]: transition,
38
+ },
39
+ 'page:next': {
40
+ [translateProp]: translateVal,
41
+
42
+ },
43
+ 'page:current': {
44
+ [translateProp]: 0,
45
+ },
46
+ 'page:previous': {
47
+ [translateProp]: -translateVal,
48
+ },
49
+ }
50
+ }
51
+
52
+ export const PagerStyles = {
53
+ ...presets,
54
+ default: createPagerStyle((theme) => {
55
+ const width = theme.values.width
56
+ const height = theme.values.window.height * 0.8
57
+ return {
58
+ ...pagerAnimation(height, width, 'X'),
59
+ page: {
60
+ width: '100%',
61
+ height: '100%',
62
+ position: 'absolute',
63
+ left: 0,
64
+ right: 0,
65
+ bottom: 0,
66
+ top: 0,
67
+ },
68
+ }
69
+ }),
70
+ horizontal: createPagerStyle((theme) => {
71
+
72
+ const width = theme.values.width
73
+ const height = theme.values.window.height * 0.8
74
+ return pagerAnimation(height, width, 'X')
75
+ }),
76
+ vertical: createPagerStyle((theme) => {
77
+ const height = theme.values.window.height * 0.8
78
+ const width = theme.values.width
79
+ return pagerAnimation(height, width, 'Y')
80
+ }),
81
+ }
@@ -0,0 +1,106 @@
1
+ import * as React from 'react'
2
+ import { ReactNode, ComponentPropsWithoutRef } from 'react'
3
+
4
+ import { Text } from '../Text'
5
+ import { Touchable } from '../Touchable'
6
+ import {
7
+ ComponentVariants,
8
+ getNestedStylesByKey,
9
+ StylesOf,
10
+ useDefaultComponentStyle,
11
+ } from '@codeleap/common'
12
+ import { View } from '../View'
13
+ import { RadioInputComposition, RadioInputStyles } from './styles'
14
+ import { InputLabel } from '../InputLabel'
15
+
16
+ export * from './styles'
17
+
18
+ type RadioItem<T extends unknown = any> = {
19
+ value: T
20
+ label: ReactNode
21
+ }
22
+
23
+ const getRadioStyle = (props) => useDefaultComponentStyle<'u:RadioInput', typeof RadioInputStyles>('u:RadioInput', props)
24
+
25
+ export type RadioButtonProps = Omit<
26
+ ComponentPropsWithoutRef<typeof Touchable>,
27
+ 'style'
28
+ > & {
29
+ item: RadioItem
30
+ select: () => void
31
+ style: StylesOf<RadioInputComposition>
32
+ checked: boolean
33
+ defaultValue?: number
34
+ }
35
+
36
+ export type RadioGroupProps<T> = {
37
+ options: RadioItem<T>[]
38
+ value: T
39
+ onValueChange(value: T): void
40
+ required?: boolean
41
+ label: ReactNode
42
+ styles?: StylesOf<RadioInputComposition>
43
+ } & ComponentVariants<typeof RadioInputStyles>
44
+
45
+ export const RadioButton: React.FC<RadioButtonProps> = ({
46
+ item,
47
+ select,
48
+ style,
49
+ checked,
50
+ ...props
51
+ }) => {
52
+ return (
53
+ <Touchable onPress={select} style={style.itemWrapper} debugName={'Change radioButton value'} styles={{
54
+ feedback: style.buttonFeedback,
55
+ }}>
56
+ <View style={[style.button, checked && style['button:checked']]}>
57
+ <View
58
+ style={[style.buttonMark, checked && style['buttonMark:checked']]}
59
+ />
60
+ </View>
61
+ {typeof item.label === 'string' ? (
62
+ <Text text={item.label} style={style.text} />
63
+ ) : (
64
+ item.label
65
+ )}
66
+ </Touchable>
67
+ )
68
+ }
69
+
70
+ export const RadioGroup = <T extends unknown>(
71
+ radioGroupProps: RadioGroupProps<T>,
72
+ ) => {
73
+ const {
74
+ options,
75
+ value,
76
+ onValueChange,
77
+ label,
78
+ responsiveVariants,
79
+ required = false,
80
+ variants,
81
+ styles,
82
+ } = radioGroupProps
83
+
84
+ const radioStyle = getRadioStyle({
85
+ responsiveVariants,
86
+ variants,
87
+ styles,
88
+ })
89
+ return (
90
+ <View style={radioStyle.wrapper}>
91
+ <InputLabel required={required} label={label} styles={getNestedStylesByKey('label', radioStyle)}/>
92
+ <View style={radioStyle.list}>
93
+ {options?.map((item, idx) => (
94
+ <RadioButton
95
+ debugName={'RadioButton'}
96
+ item={item}
97
+ key={idx}
98
+ style={radioStyle}
99
+ checked={value === item.value}
100
+ select={() => onValueChange(item.value)}
101
+ />
102
+ ))}
103
+ </View>
104
+ </View>
105
+ )
106
+ }
@@ -0,0 +1,67 @@
1
+ import {
2
+ createDefaultVariantFactory, includePresets,
3
+ } from '@codeleap/common'
4
+ import { InputLabelComposition } from '../InputLabel'
5
+ type RadioParts = 'button' | 'itemWrapper' | 'text' | 'buttonMark' | 'buttonFeedback'
6
+
7
+ type RadioGroupParts = `label${Capitalize<InputLabelComposition>}` | 'wrapper' | 'list'
8
+
9
+ export type RadioInputComposition =
10
+ | `${RadioParts}:checked`
11
+ | RadioParts
12
+ | RadioGroupParts
13
+
14
+ const createRadioStyle =
15
+ createDefaultVariantFactory<RadioInputComposition>()
16
+
17
+ const presets = includePresets(style => createRadioStyle(() => ({ wrapper: style })))
18
+
19
+ export const RadioInputStyles = {
20
+ ...presets,
21
+ default: createRadioStyle((theme) => {
22
+
23
+ const itemHeight = theme.typography.baseFontSize * 1.2
24
+ const markHeight = itemHeight / 2
25
+ const translateX = -(markHeight / 2)
26
+ const translateY = -(markHeight / 2)
27
+ return {
28
+ itemWrapper: {
29
+ ...theme.presets.row,
30
+ ...theme.spacing.marginVertical(1.3),
31
+ },
32
+ button: {
33
+ height: itemHeight,
34
+ width: itemHeight,
35
+ ...theme.border.primary(1),
36
+ borderRadius: theme.borderRadius.large,
37
+
38
+ position: 'relative',
39
+ ...theme.spacing.marginRight(1),
40
+ },
41
+ buttonFeedback: { type: 'opacity', value: 0.5 },
42
+ buttonMark: {
43
+ backgroundColor: theme.colors.primary,
44
+ position: 'absolute',
45
+ left: '50%',
46
+ top: '50%',
47
+ height: markHeight,
48
+ width: markHeight,
49
+
50
+ transform: [{ translateX }, { translateY }],
51
+ borderRadius: theme.borderRadius.large,
52
+ opacity: 0,
53
+ },
54
+ 'buttonMark:checked': {
55
+ opacity: 1,
56
+ },
57
+ }
58
+ }),
59
+ square: createRadioStyle(() => ({
60
+ buttonMark: {
61
+ borderRadius: 0,
62
+ },
63
+ button: {
64
+ borderRadius: 0,
65
+ },
66
+ })),
67
+ }
@@ -0,0 +1,124 @@
1
+ import * as React from 'react'
2
+ import { forwardRef, useState } from 'react'
3
+ import {
4
+ deepEqual,
5
+ onUpdate,
6
+ useDefaultComponentStyle,
7
+ usePrevious,
8
+ } from '@codeleap/common'
9
+
10
+ import { RefreshControl, RefreshControlProps, ScrollView, StyleSheet } from 'react-native'
11
+ import { ViewProps } from '../View'
12
+ import { KeyboardAwareScrollViewTypes } from '../../modules'
13
+ import { StylesOf } from '../../types'
14
+ import { ScrollComposition, ScrollStyles } from './styles'
15
+ import { GetKeyboardAwarePropsOptions, useKeyboardAwareView } from '../../utils'
16
+ import { ScrollView as MotiScrollView } from 'moti'
17
+ // import { KeyboardAwareScrollView } from '../../utils'
18
+
19
+ type KeyboardAwareScrollViewProps = KeyboardAwareScrollViewTypes.KeyboardAwareScrollViewProps
20
+
21
+ export type ScrollProps = KeyboardAwareScrollViewProps &
22
+ ViewProps & {
23
+ onRefresh?: () => void
24
+ refreshTimeout?: number
25
+ changeData?: any
26
+ keyboardAware?: GetKeyboardAwarePropsOptions
27
+ refreshing?: boolean
28
+ styles?: StylesOf<ScrollComposition>
29
+ refreshControlProps?: Partial<RefreshControlProps>
30
+ debugName?: string
31
+ }
32
+
33
+ export const Scroll = forwardRef<ScrollView, ScrollProps>(
34
+ (scrollProps, ref) => {
35
+ const {
36
+ variants = [],
37
+ style,
38
+ refreshTimeout = 3000,
39
+ children,
40
+ changeData,
41
+ styles = {},
42
+ refreshControlProps = {},
43
+ contentContainerStyle,
44
+ keyboardAware,
45
+ debugName = '',
46
+ animated = true,
47
+ ...props
48
+ } = scrollProps
49
+ const hasRefresh = !!props.onRefresh
50
+ const [refreshingState, setRefreshing] = useState(false)
51
+ const refreshingDisplay = props.refreshing !== undefined ? props.refreshing : refreshingState
52
+
53
+ const timer = React.useRef(null)
54
+ const previousData = usePrevious(changeData)
55
+
56
+ const onRefresh = () => {
57
+ if (timer.current) {
58
+ clearTimeout(timer.current)
59
+ }
60
+
61
+ setRefreshing(true)
62
+
63
+ props.onRefresh()
64
+
65
+ timer.current = setTimeout(() => {
66
+ setRefreshing(false)
67
+ }, refreshTimeout)
68
+ }
69
+ onUpdate(() => {
70
+ if (refreshingDisplay && !deepEqual(previousData, changeData)) {
71
+ setRefreshing(false)
72
+ if (timer.current) {
73
+ clearTimeout(timer.current)
74
+ }
75
+ }
76
+ }, [refreshingDisplay, changeData])
77
+
78
+ const variantStyles = useDefaultComponentStyle<'u:Scroll', typeof ScrollStyles>('u:Scroll', {
79
+ variants,
80
+ styles,
81
+ transform: StyleSheet.flatten,
82
+ rootElement: 'content',
83
+ })
84
+
85
+ const refreshStyles = StyleSheet.flatten([variantStyles.refreshControl, styles.refreshControl])
86
+ const _scrollProps = {
87
+ style: [variantStyles.wrapper, style],
88
+ contentContainerStyle: [variantStyles.content, contentContainerStyle],
89
+ ref: ref as unknown as ScrollView,
90
+ refreshControl: hasRefresh && (
91
+ <RefreshControl
92
+ refreshing={refreshingDisplay}
93
+ onRefresh={onRefresh}
94
+ tintColor={refreshStyles?.color}
95
+ colors={[refreshStyles?.color]}
96
+ {...refreshControlProps}
97
+ />
98
+ ),
99
+ ...props,
100
+ }
101
+ const keyboard = useKeyboardAwareView({
102
+ debugName,
103
+ })
104
+
105
+ const rootProps = keyboard.getKeyboardAwareProps(_scrollProps, {
106
+ adapt: 'marginBottom',
107
+ baseStyleProp: 'style',
108
+ animated,
109
+ ...keyboardAware,
110
+
111
+ })
112
+ const Component = animated ? MotiScrollView : ScrollView
113
+
114
+ return (
115
+ <Component
116
+ {...rootProps}
117
+
118
+ >
119
+ {children}
120
+ </Component>
121
+ )
122
+ },
123
+ )
124
+ export * from './styles'
@@ -0,0 +1,18 @@
1
+ import { createDefaultVariantFactory } from '@codeleap/common'
2
+
3
+ export type ScrollComposition = 'wrapper' |'content' | 'refreshControl'
4
+
5
+ const createScrollStyle = createDefaultVariantFactory<ScrollComposition>()
6
+
7
+ export const ScrollStyles = {
8
+ default: createScrollStyle((theme) => {
9
+ return {
10
+ wrapper: {
11
+ ...theme.presets.fullHeight,
12
+ },
13
+ refreshControl: {
14
+ color: theme.colors.primary,
15
+ },
16
+ }
17
+ }),
18
+ }
@@ -0,0 +1,91 @@
1
+ import * as React from 'react'
2
+ import { forwardRef, useState } from 'react'
3
+ import {
4
+ deepEqual,
5
+ onUpdate,
6
+ useDefaultComponentStyle,
7
+ usePrevious,
8
+ useCodeleapContext,
9
+ } from '@codeleap/common'
10
+
11
+ import { RefreshControl, SectionList } from 'react-native'
12
+ import { View, ViewProps } from '../View'
13
+ import { KeyboardAwareScrollViewTypes } from '../../modules'
14
+ import { KeyboardAwareSectionList } from '../../utils'
15
+
16
+ export type SectionListProps = KeyboardAwareScrollViewTypes.KeyboardAwareSectionListProps<any> &
17
+ ViewProps & {
18
+ onRefresh?: () => void
19
+ refreshTimeout?: number
20
+ changeData?: any
21
+ separators?: boolean
22
+ }
23
+
24
+ export const Sections = forwardRef<SectionList, SectionListProps>(
25
+ (flatListProps, ref) => {
26
+ const {
27
+ variants = [],
28
+ style,
29
+ refreshTimeout = 3000,
30
+ changeData,
31
+ ...props
32
+ } = flatListProps
33
+ const hasRefresh = !!props.onRefresh
34
+ const [refreshing, setRefreshing] = useState(false)
35
+
36
+ const timer = React.useRef(null)
37
+ const previousData = usePrevious(changeData)
38
+
39
+ const onRefresh = () => {
40
+ if (timer.current) {
41
+ clearTimeout(timer.current)
42
+ }
43
+
44
+ setRefreshing(true)
45
+
46
+ props.onRefresh()
47
+
48
+ timer.current = setTimeout(() => {
49
+ setRefreshing(false)
50
+ }, refreshTimeout)
51
+ }
52
+ onUpdate(() => {
53
+ if (refreshing && !deepEqual(previousData, changeData)) {
54
+ setRefreshing(false)
55
+ if (timer.current) {
56
+ clearTimeout(timer.current)
57
+ }
58
+ }
59
+ }, [refreshing, changeData])
60
+ const { Theme } = useCodeleapContext()
61
+
62
+ const variantStyles = useDefaultComponentStyle('View', {
63
+ variants,
64
+ })
65
+
66
+ const renderSeparator = () => {
67
+ return (
68
+ <View variants={['separator']}></View>
69
+ )
70
+ }
71
+
72
+ const separatorProp = props.separators
73
+ const isEmpty = !props.data || !props.data.length
74
+ const separator = !isEmpty && separatorProp == true && renderSeparator
75
+
76
+ return (
77
+ <KeyboardAwareSectionList
78
+ style={[Theme.presets.full, style]}
79
+ contentContainerStyle={[variantStyles.wrapper]}
80
+ ref={ref as unknown as SectionList}
81
+ ItemSeparatorComponent={separator}
82
+ {...props}
83
+ refreshControl={
84
+ hasRefresh && (
85
+ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
86
+ )
87
+ }
88
+ />
89
+ )
90
+ },
91
+ )
@@ -0,0 +1,204 @@
1
+ import React, { ReactElement, useImperativeHandle, useMemo, useRef } from 'react'
2
+ import { Scroll, ScrollProps } from '../Scroll'
3
+
4
+ import { Easing, EasingFunction, StyleSheet } from 'react-native'
5
+ import { FormTypes, getNestedStylesByKey, PropsOf, useDefaultComponentStyle } from '@codeleap/common'
6
+ import { SegmentedControlComposition, SegmentedControlStyles } from './styles'
7
+ import { Touchable } from '../Touchable'
8
+ import { StylesOf } from '../../types/utility'
9
+ import { Text, TextProps } from '../Text'
10
+ import { KeyboardAwareScrollViewTypes } from '../../modules'
11
+ import { View } from '../View'
12
+ import { InputLabel } from '../InputLabel'
13
+ export * from './styles'
14
+ export type SegmentedControlRef =KeyboardAwareScrollViewTypes.KeyboardAwareScrollView & {
15
+ scrollTo: (index: number) => void
16
+ scrollToCurrent: () => void
17
+ }
18
+
19
+ export type SegmentedControlProps<T = string> = ScrollProps & {
20
+ options : {label: string; value: T }[]
21
+ onValueChange: (value: T) => any
22
+ value: T
23
+ debugName: string
24
+ animation?: {
25
+ duration?: number
26
+ easing?: EasingFunction
27
+ }
28
+ textProps?: Partial<PropsOf<typeof Text>>
29
+ touchableProps?: Partial<PropsOf<typeof Touchable>>
30
+ styles?: StylesOf<SegmentedControlComposition>
31
+ scrollProps?: any
32
+ label?: FormTypes.Label
33
+ RenderButton?: (props: SegmentedControlProps & {
34
+ touchableProps: PropsOf<typeof Touchable>
35
+ textProps: PropsOf<typeof Text>
36
+ option: {label: string; value: any}
37
+ }) => ReactElement
38
+ RenderAnimatedView?: (props: Partial<SegmentedControlProps>) => ReactElement
39
+ getItemWidth?: (item:{label: string; value: T }, idx: number, arr: {label: string; value: T }[]) => number
40
+ }
41
+
42
+ const defaultAnimation = {
43
+ type: 'timing',
44
+ duration: 200,
45
+ easing: Easing.linear,
46
+ }
47
+
48
+ const _SegmentedControl = React.forwardRef<SegmentedControlRef, SegmentedControlProps>((props, ref) => {
49
+ const {
50
+ options = [],
51
+ onValueChange,
52
+ debugName,
53
+ label,
54
+ value,
55
+ styles = {},
56
+ animation = {},
57
+ variants = [],
58
+ scrollProps = {},
59
+ getItemWidth = (i) => i.label.length * 20,
60
+ RenderAnimatedView,
61
+ RenderButton,
62
+ } = props
63
+
64
+ let _animation = {
65
+ ...defaultAnimation, ...animation,
66
+ }
67
+
68
+ let variantStyles = useDefaultComponentStyle<'u:SegmentedControl', typeof SegmentedControlStyles>('u:SegmentedControl', {
69
+ styles,
70
+ transform: StyleSheet.flatten,
71
+ variants,
72
+ })
73
+
74
+ const scrollRef = useRef<KeyboardAwareScrollViewTypes.KeyboardAwareScrollView>(null)
75
+
76
+ function scrollTo(idx:number) {
77
+ if (!scrollRef.current) return
78
+ setTimeout(() => {
79
+ scrollRef.current?.scrollToPosition?.(widthStyle.width * idx, 0, true)
80
+ })
81
+ }
82
+
83
+ const widthStyle = useMemo(() => {
84
+ const sizes = options.map(getItemWidth)
85
+ const maxWidth = sizes.sort((a, b) => b - a)[0]
86
+
87
+ return { width: maxWidth }
88
+ }, [options])
89
+
90
+ const currentOptionIdx = options.findIndex(o => o.value === value) || 0
91
+
92
+ const translateX = widthStyle.width * currentOptionIdx
93
+
94
+ const onPress = (txt:string, idx: number) => {
95
+ return () => {
96
+ onValueChange(txt)
97
+ scrollTo(idx)
98
+ }
99
+ }
100
+
101
+ const hasScrolledInitially = useRef(false)
102
+
103
+ useImperativeHandle(ref, () => {
104
+ if (!scrollRef.current) return null
105
+
106
+ return {
107
+ ...(scrollRef.current),
108
+ scrollTo,
109
+ scrollToCurrent() {
110
+ if (!scrollRef.current) return
111
+ scrollTo(currentOptionIdx)
112
+ },
113
+ } as SegmentedControlRef
114
+ })
115
+
116
+ if (!hasScrolledInitially.current && scrollRef.current) {
117
+ scrollTo(currentOptionIdx)
118
+ hasScrolledInitially.current = true
119
+ }
120
+
121
+ const AnimatedView = RenderAnimatedView || View
122
+ variantStyles = JSON.parse(JSON.stringify(variantStyles))
123
+ _animation = JSON.parse(JSON.stringify(_animation))
124
+
125
+ const labelStyles = getNestedStylesByKey('label', variantStyles)
126
+ return (<>
127
+ <InputLabel label={label} styles={labelStyles} required={false}/>
128
+ <Scroll
129
+ horizontal
130
+ showsHorizontalScrollIndicator={false}
131
+ style={variantStyles.scroll}
132
+ contentContainerStyle={variantStyles.scrollContent}
133
+ {...scrollProps}
134
+ keyboardAware={{
135
+ enabled: false,
136
+ }}
137
+ ref={scrollRef}
138
+ >
139
+ <View style={variantStyles.wrapper}>
140
+ <AnimatedView
141
+ options={options}
142
+ styles={variantStyles}
143
+
144
+ animated
145
+ style={[variantStyles.selectedBubble, widthStyle]}
146
+ animate={{
147
+ translateX,
148
+ }}
149
+ transition={{
150
+ translateX: _animation,
151
+ }}
152
+
153
+ />
154
+ {options.map((o, idx) => {
155
+ const selected = value === o.value
156
+
157
+ const touchableProps = {
158
+ key: idx,
159
+ debugName: `Segmented Control ${debugName}, option ${o.label}`,
160
+ onPress: onPress(o.value, idx),
161
+ style: [widthStyle, variantStyles.button],
162
+ ...props.touchableProps,
163
+
164
+ }
165
+
166
+ const textProps:TextProps = {
167
+ text: o.label as string,
168
+ colorChangeConfig: _animation,
169
+ style: StyleSheet.flatten([variantStyles.text, selected && variantStyles['text:selected']]),
170
+ animated: true,
171
+ ...props.textProps,
172
+ }
173
+
174
+ if (RenderButton) {
175
+ return (
176
+ <RenderButton {...props} touchableProps={touchableProps} key={touchableProps.key} textProps={textProps} option={o}/>
177
+ )
178
+ }
179
+ return <Touchable
180
+ {...touchableProps}
181
+ noFeedback={selected}
182
+ key={touchableProps.key}
183
+ styles={{
184
+ feedback: variantStyles.buttonFeedback,
185
+ }}
186
+ >
187
+ <Text
188
+
189
+ {...textProps}
190
+ />
191
+
192
+ </Touchable>
193
+
194
+ })}
195
+ </View>
196
+ </Scroll>
197
+ </>
198
+ )
199
+
200
+ })
201
+
202
+ type SegControlComponent = <T = string>(props: SegmentedControlProps<T> & {ref?: React.Ref<SegmentedControlRef>}) => ReactElement
203
+
204
+ export const SegmentedControl = _SegmentedControl as SegControlComponent