@neko-os/ui 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/dist/NekoUI.js +12 -9
  2. package/dist/abstractions/Image.native.js +1 -1
  3. package/dist/abstractions/Image.web.js +1 -1
  4. package/dist/abstractions/WindowOverlay.js +3 -0
  5. package/dist/abstractions/WindowOverlay.native.js +21 -0
  6. package/dist/abstractions/helpers/storage.js +14 -4
  7. package/dist/abstractions/helpers/storage.native.js +9 -1
  8. package/dist/components/feedback/notifications/NotificationsHandler.js +10 -6
  9. package/dist/components/form/useNewForm.js +2 -0
  10. package/dist/components/index.js +3 -1
  11. package/dist/components/inputs/DateInput.js +10 -6
  12. package/dist/components/inputs/InputWrapper.js +11 -11
  13. package/dist/components/inputs/NumberWheelInput.js +50 -0
  14. package/dist/components/inputs/NumberWheelPicker.js +43 -0
  15. package/dist/components/inputs/SegmentedPicker.js +3 -2
  16. package/dist/components/inputs/UploadInput.js +4 -4
  17. package/dist/components/inputs/WheelPicker.js +49 -0
  18. package/dist/components/inputs/WheelPicker.native.js +88 -0
  19. package/dist/components/inputs/WheelPicker.web.js +1 -0
  20. package/dist/components/inputs/dateWheelPicker/DateWheelPicker.js +24 -0
  21. package/dist/components/inputs/dateWheelPicker/DayWheelPicker.js +48 -0
  22. package/dist/components/inputs/dateWheelPicker/MonthWheelPicker.js +19 -0
  23. package/dist/components/inputs/dateWheelPicker/QuarterWheelPicker.js +61 -0
  24. package/dist/components/inputs/dateWheelPicker/WeekWheelPicker.js +66 -0
  25. package/dist/components/inputs/dateWheelPicker/YearWheelPicker.js +35 -0
  26. package/dist/components/inputs/index.js +5 -1
  27. package/dist/components/inputs/upload/Upload.native.js +60 -52
  28. package/dist/components/inputs/upload/useUploadState.js +11 -3
  29. package/dist/components/measurements/FeetInchesInput.js +91 -0
  30. package/dist/components/measurements/LengthInput.js +32 -0
  31. package/dist/components/measurements/LengthText.js +10 -0
  32. package/dist/components/measurements/MeasurementHandler.js +26 -0
  33. package/dist/components/measurements/WeightInput.js +25 -0
  34. package/dist/components/measurements/WeightText.js +10 -0
  35. package/dist/components/measurements/helpers/detectMeasurementSystem.js +15 -0
  36. package/dist/components/measurements/helpers/detectMeasurementSystem.native.js +9 -0
  37. package/dist/components/measurements/helpers/index.js +2 -0
  38. package/dist/components/measurements/helpers/length.js +112 -0
  39. package/dist/components/measurements/helpers/weight.js +56 -0
  40. package/dist/components/measurements/index.js +9 -0
  41. package/dist/components/measurements/useLengthFormatter.js +35 -0
  42. package/dist/components/measurements/useLocalInputValue.js +32 -0
  43. package/dist/components/measurements/useWeightFormatter.js +29 -0
  44. package/dist/components/presentation/Avatar.js +3 -3
  45. package/dist/components/routing/ReturnButton.js +20 -0
  46. package/dist/components/routing/ReturnButton.native.js +20 -0
  47. package/dist/components/routing/ReturnButton.web.js +2 -0
  48. package/dist/components/routing/ReturnLink.js +25 -0
  49. package/dist/components/routing/ReturnLink.native.js +25 -0
  50. package/dist/components/routing/ReturnLink.web.js +2 -0
  51. package/dist/components/routing/RoutedStepsContent.js +21 -0
  52. package/dist/components/routing/RoutedStepsContent.native.js +94 -0
  53. package/dist/components/routing/RoutedStepsContent.web.js +3 -0
  54. package/dist/components/routing/index.js +3 -0
  55. package/dist/components/state/StatePresenter.js +1 -1
  56. package/dist/components/steps/StepsHandler.js +2 -0
  57. package/dist/components/structure/TopBar.js +18 -16
  58. package/dist/components/theme/ThemePickerDrawer.js +1 -1
  59. package/dist/helpers/compress.js +61 -0
  60. package/dist/helpers/compress.native.js +49 -0
  61. package/dist/helpers/files.js +7 -0
  62. package/dist/helpers/files.native.js +55 -0
  63. package/dist/helpers/index.js +6 -1
  64. package/dist/helpers/media.js +4 -0
  65. package/dist/helpers/media.native.js +41 -0
  66. package/dist/helpers/numbers.js +13 -0
  67. package/dist/helpers/pickAssets.js +7 -0
  68. package/dist/helpers/pickAssets.native.js +66 -0
  69. package/dist/helpers/storage.js +17 -0
  70. package/dist/i18n/I18n.js +4 -4
  71. package/dist/index.js +1 -1
  72. package/dist/modifiers/flex.js +8 -3
  73. package/dist/responsive/responsiveHooks.js +14 -0
  74. package/dist/theme/default/blackTheme.js +3 -1
  75. package/dist/theme/default/cyberpunkTheme.js +3 -1
  76. package/dist/theme/default/darkTheme.js +3 -1
  77. package/dist/theme/default/hackerTheme.js +3 -1
  78. package/dist/theme/default/lightTheme.js +3 -1
  79. package/dist/theme/default/paperTheme.js +3 -1
  80. package/package.json +2 -14
  81. package/src/NekoUI.js +16 -13
  82. package/src/abstractions/Image.native.js +1 -1
  83. package/src/abstractions/Image.web.js +1 -1
  84. package/src/abstractions/WindowOverlay.js +3 -0
  85. package/src/abstractions/WindowOverlay.native.js +21 -0
  86. package/src/abstractions/helpers/storage.js +13 -3
  87. package/src/abstractions/helpers/storage.native.js +8 -0
  88. package/src/components/feedback/notifications/NotificationsHandler.js +12 -8
  89. package/src/components/form/useNewForm.js +2 -0
  90. package/src/components/index.js +2 -0
  91. package/src/components/inputs/DateInput.js +8 -4
  92. package/src/components/inputs/InputWrapper.js +3 -3
  93. package/src/components/inputs/NumberWheelInput.js +50 -0
  94. package/src/components/inputs/NumberWheelPicker.js +43 -0
  95. package/src/components/inputs/SegmentedPicker.js +2 -1
  96. package/src/components/inputs/UploadInput.js +2 -2
  97. package/src/components/inputs/WheelPicker.js +49 -0
  98. package/src/components/inputs/WheelPicker.native.js +88 -0
  99. package/src/components/inputs/WheelPicker.web.js +1 -0
  100. package/src/components/inputs/dateWheelPicker/DateWheelPicker.js +24 -0
  101. package/src/components/inputs/dateWheelPicker/DayWheelPicker.js +48 -0
  102. package/src/components/inputs/dateWheelPicker/MonthWheelPicker.js +19 -0
  103. package/src/components/inputs/dateWheelPicker/QuarterWheelPicker.js +61 -0
  104. package/src/components/inputs/dateWheelPicker/WeekWheelPicker.js +66 -0
  105. package/src/components/inputs/dateWheelPicker/YearWheelPicker.js +35 -0
  106. package/src/components/inputs/index.js +4 -0
  107. package/src/components/inputs/upload/Upload.native.js +58 -50
  108. package/src/components/inputs/upload/useUploadState.js +11 -3
  109. package/src/components/measurements/FeetInchesInput.js +91 -0
  110. package/src/components/measurements/LengthInput.js +32 -0
  111. package/src/components/measurements/LengthText.js +10 -0
  112. package/src/components/measurements/MeasurementHandler.js +26 -0
  113. package/src/components/measurements/WeightInput.js +25 -0
  114. package/src/components/measurements/WeightText.js +10 -0
  115. package/src/components/measurements/helpers/detectMeasurementSystem.js +15 -0
  116. package/src/components/measurements/helpers/detectMeasurementSystem.native.js +9 -0
  117. package/src/components/measurements/helpers/index.js +2 -0
  118. package/src/components/measurements/helpers/length.js +112 -0
  119. package/src/components/measurements/helpers/weight.js +56 -0
  120. package/src/components/measurements/index.js +9 -0
  121. package/src/components/measurements/useLengthFormatter.js +35 -0
  122. package/src/components/measurements/useLocalInputValue.js +32 -0
  123. package/src/components/measurements/useWeightFormatter.js +29 -0
  124. package/src/components/presentation/Avatar.js +2 -2
  125. package/src/components/routing/ReturnButton.js +20 -0
  126. package/src/components/routing/ReturnButton.native.js +20 -0
  127. package/src/components/routing/ReturnButton.web.js +2 -0
  128. package/src/components/routing/ReturnLink.js +25 -0
  129. package/src/components/routing/ReturnLink.native.js +25 -0
  130. package/src/components/routing/ReturnLink.web.js +2 -0
  131. package/src/components/routing/RoutedStepsContent.js +21 -0
  132. package/src/components/routing/RoutedStepsContent.native.js +94 -0
  133. package/src/components/routing/RoutedStepsContent.web.js +3 -0
  134. package/src/components/routing/index.js +3 -0
  135. package/src/components/state/StatePresenter.js +1 -1
  136. package/src/components/steps/StepsHandler.js +2 -0
  137. package/src/components/structure/TopBar.js +16 -14
  138. package/src/components/theme/ThemePickerDrawer.js +1 -1
  139. package/src/helpers/compress.js +61 -0
  140. package/src/helpers/compress.native.js +49 -0
  141. package/src/helpers/files.js +7 -0
  142. package/src/helpers/files.native.js +55 -0
  143. package/src/helpers/index.js +6 -1
  144. package/src/helpers/media.js +4 -0
  145. package/src/helpers/media.native.js +41 -0
  146. package/src/helpers/numbers.js +13 -0
  147. package/src/helpers/pickAssets.js +7 -0
  148. package/src/helpers/pickAssets.native.js +66 -0
  149. package/src/helpers/storage.js +17 -0
  150. package/src/i18n/I18n.js +2 -2
  151. package/src/index.js +1 -1
  152. package/src/modifiers/flex.js +7 -2
  153. package/src/responsive/responsiveHooks.js +14 -0
  154. package/src/theme/default/blackTheme.js +2 -0
  155. package/src/theme/default/cyberpunkTheme.js +2 -0
  156. package/src/theme/default/darkTheme.js +2 -0
  157. package/src/theme/default/hackerTheme.js +2 -0
  158. package/src/theme/default/lightTheme.js +2 -0
  159. package/src/theme/default/paperTheme.js +2 -0
package/src/NekoUI.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { initFirstDayOfWeek } from './helpers/weekStartSetup'
2
2
  import { DynamicStyleTag } from './DynamicStyleTag'
3
3
  import { I18nProvider } from './i18n'
4
+ import { MeasurementHandler } from './components/measurements/MeasurementHandler'
4
5
  import { ModalsHandler } from './components/modals/modal/handler/ModalsHandler'
5
6
  import { NotificationsHandler } from './components/feedback/notifications/NotificationsHandler'
6
7
  import { OverlayHandler } from './components/structure/overlay/OverlayHandler'
@@ -12,23 +13,25 @@ import { useThemeHandler } from './theme'
12
13
 
13
14
  initFirstDayOfWeek()
14
15
 
15
- export function NekoUI({ children, i18n, ...props }) {
16
+ export function NekoUI({ children, i18n, measurementSystem, ...props }) {
16
17
  return (
17
18
  <ThemeHandler {...props}>
18
19
  <DynamicStyleTag />
19
20
  <ResponsiveHandler>
20
- <PortalHandler>
21
- <ModalsHandler>
22
- <I18nProvider i18n={i18n}>
23
- <NotificationsHandler>
24
- <OverlayHandler>
25
- {children}
26
- <FixedComponents />
27
- </OverlayHandler>
28
- </NotificationsHandler>
29
- </I18nProvider>
30
- </ModalsHandler>
31
- </PortalHandler>
21
+ <MeasurementHandler measurementSystem={measurementSystem}>
22
+ <PortalHandler>
23
+ <ModalsHandler>
24
+ <I18nProvider i18n={i18n}>
25
+ <NotificationsHandler>
26
+ <OverlayHandler>
27
+ {children}
28
+ <FixedComponents />
29
+ </OverlayHandler>
30
+ </NotificationsHandler>
31
+ </I18nProvider>
32
+ </ModalsHandler>
33
+ </PortalHandler>
34
+ </MeasurementHandler>
32
35
  </ResponsiveHandler>
33
36
  </ThemeHandler>
34
37
  )
@@ -1,7 +1,7 @@
1
1
  import { Image as RNImage } from 'react-native'
2
2
 
3
3
  export function AbsImage({ src, source, resizeMode = 'cover', ...props }) {
4
- if (!source && !!src) source = { uri: src }
4
+ if (!source && src != null) source = typeof src === 'string' ? { uri: src } : src
5
5
 
6
6
  return <RNImage source={source} resizeMode={resizeMode} {...props} />
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import { Image as RNImage } from 'react-native'
2
2
 
3
3
  export function AbsImage({ src, source, resizeMode = 'cover', ...props }) {
4
- if (!source && !!src) source = { uri: src }
4
+ if (!source && src != null) source = typeof src === 'string' ? { uri: src } : src
5
5
 
6
6
  return <RNImage source={source} resizeMode={resizeMode} {...props} />
7
7
  }
@@ -0,0 +1,3 @@
1
+ export function AbsWindowOverlay({ children }) {
2
+ return children
3
+ }
@@ -0,0 +1,21 @@
1
+ import { Platform } from 'react-native'
2
+
3
+ let AbsWindowOverlay = ({ children }) => children
4
+
5
+ // iOS native-stack modals (presentation: 'modal') present a UIViewController above
6
+ // the RN root view, covering anything rendered inside it regardless of zIndex.
7
+ // FullWindowOverlay renders above presented view controllers and passes touches
8
+ // through empty areas. On Android/web screens stay in the same window, so the
9
+ // passthrough is enough.
10
+ if (Platform.OS === 'ios') {
11
+ try {
12
+ const { FullWindowOverlay } = require('react-native-screens') || {}
13
+ if (FullWindowOverlay) {
14
+ AbsWindowOverlay = ({ children }) => <FullWindowOverlay>{children}</FullWindowOverlay>
15
+ }
16
+ } catch {
17
+ console.warn('react-native-screens not installed. Notifications may render behind native iOS modals.')
18
+ }
19
+ }
20
+
21
+ export { AbsWindowOverlay }
@@ -3,7 +3,7 @@ function set(key, value) {
3
3
  }
4
4
 
5
5
  function setAsync(key, value) {
6
- return Promise.resulve(set(key, value))
6
+ return Promise.resolve(set(key, value))
7
7
  }
8
8
 
9
9
  function get(key) {
@@ -11,7 +11,7 @@ function get(key) {
11
11
  }
12
12
 
13
13
  function getAsync(key) {
14
- return Promise.resulve(get(key))
14
+ return Promise.resolve(get(key))
15
15
  }
16
16
 
17
17
  function remove(key) {
@@ -19,7 +19,15 @@ function remove(key) {
19
19
  }
20
20
 
21
21
  function removeAsync(key) {
22
- return Promise.resulve(remove(key))
22
+ return Promise.resolve(remove(key))
23
+ }
24
+
25
+ function clear() {
26
+ return localStorage.clear()
27
+ }
28
+
29
+ function clearAsync() {
30
+ return Promise.resolve(clear())
23
31
  }
24
32
 
25
33
  export const AbsStorage = {
@@ -29,4 +37,6 @@ export const AbsStorage = {
29
37
  getAsync,
30
38
  remove,
31
39
  removeAsync,
40
+ clear,
41
+ clearAsync,
32
42
  }
@@ -7,6 +7,9 @@ let getAsync = () => Promise.resolve(get())
7
7
  let remove = () => console.warn('expo-sqlite not installed. Neko Storage needs expo-sqlite to work properly')
8
8
  let removeAsync = () => Promise.resolve(remove())
9
9
 
10
+ let clear = () => console.warn('expo-sqlite not installed. Neko Storage needs expo-sqlite to work properly')
11
+ let clearAsync = () => Promise.resolve(clear())
12
+
10
13
  try {
11
14
  const StorageModule = require('expo-sqlite/kv-store')
12
15
  if (StorageModule?.default) {
@@ -19,6 +22,9 @@ try {
19
22
 
20
23
  remove = Storage.removeItemSync.bind(Storage)
21
24
  removeAsync = Storage.removeItem.bind(Storage)
25
+
26
+ clear = Storage.clearSync.bind(Storage)
27
+ clearAsync = Storage.clearAsync.bind(Storage)
22
28
  }
23
29
  } catch (e) {
24
30
  console.log('expo-sqlite not available:', e)
@@ -31,4 +37,6 @@ export const AbsStorage = {
31
37
  getAsync,
32
38
  remove,
33
39
  removeAsync,
40
+ clear,
41
+ clearAsync,
34
42
  }
@@ -1,10 +1,11 @@
1
1
  import { is } from 'ramda'
2
2
  import React from 'react'
3
3
 
4
+ import { AbsWindowOverlay } from '../../../abstractions/WindowOverlay'
4
5
  import { Notification } from './Notification'
5
- import { SafeAreaView } from '../../structure/SafeAreaView'
6
6
  import { View } from '../../structure/View'
7
7
  import { useResponsiveValue } from '../../../responsive/responsiveHooks'
8
+ import { useSafeAreaInsets } from '../../../abstractions/helpers/useSafeAreaInsets'
8
9
 
9
10
  const NotificationsContext = React.createContext(null)
10
11
 
@@ -35,6 +36,7 @@ export function useNotifier() {
35
36
 
36
37
  export function NotificationsHandler({ children }) {
37
38
  const width = useResponsiveValue({ sm: '100%', df: 400 })
39
+ const insets = useSafeAreaInsets()
38
40
  const [messages, setMessages] = React.useState([])
39
41
 
40
42
  const add = React.useCallback((key, data) => {
@@ -52,13 +54,15 @@ export function NotificationsHandler({ children }) {
52
54
  {children}
53
55
 
54
56
  {!!messages.length && (
55
- <View fixed top={0} right={0} padding="md" width={width} maxWidth="100%" pointerEvents="box-none" zIndex={600}>
56
- <SafeAreaView gap="xs">
57
- {messages.map(({ key, ...item }) => (
58
- <Notification key={key} {...item} />
59
- ))}
60
- </SafeAreaView>
61
- </View>
57
+ <AbsWindowOverlay>
58
+ <View fixed top={0} right={0} padding="md" width={width} maxWidth="100%" pointerEvents="box-none" zIndex={600}>
59
+ <View paddingT={insets.top} paddingR={insets.right} gap="xs" pointerEvents="box-none">
60
+ {messages.map(({ key, ...item }) => (
61
+ <Notification key={key} {...item} />
62
+ ))}
63
+ </View>
64
+ </View>
65
+ </AbsWindowOverlay>
62
66
  )}
63
67
  </NotificationsContext.Provider>
64
68
  )
@@ -64,11 +64,13 @@ export function useNewForm({ initialValues = {}, validate, onSubmit, onValuesCha
64
64
 
65
65
  const isTouched = (name) => {
66
66
  if (!name) return touchedRef.current.size > 0
67
+ if (Array.isArray(name)) return name.some((n) => touchedRef.current.has(toKey(n)))
67
68
  return touchedRef.current.has(toKey(name))
68
69
  }
69
70
 
70
71
  const isDirty = (name) => {
71
72
  if (!name) return dirtyRef.current.size > 0
73
+ if (Array.isArray(name)) return name.some((n) => dirtyRef.current.has(toKey(n)))
72
74
  return dirtyRef.current.has(toKey(name))
73
75
  }
74
76
 
@@ -19,4 +19,6 @@ export * from './theme'
19
19
  export * from './sections'
20
20
  export * from './filter'
21
21
  export * from './steps'
22
+ export * from './routing'
22
23
  export * from './carousel'
24
+ export * from './measurements'
@@ -11,6 +11,7 @@ dayjs.extend(advancedFormat)
11
11
  dayjs.extend(weekOfYear)
12
12
 
13
13
  import { DatePicker } from './datePicker/DatePicker'
14
+ import { DateWheelPicker } from './dateWheelPicker/DateWheelPicker'
14
15
  import { MaskInput } from './MaskInput'
15
16
  import { Popover } from '../structure/popover/Popover'
16
17
  import { isValidDate } from '../calendar/_helpers/dateDisabled'
@@ -46,6 +47,7 @@ export function DateInput({
46
47
  onCheckDisabled,
47
48
  placement,
48
49
  type = 'day',
50
+ presentation = 'calendar',
49
51
  format,
50
52
  startsOpen,
51
53
  allowClear,
@@ -85,26 +87,28 @@ export function DateInput({
85
87
  setInputValue(!!value ? dayjs(value).format(format) : '')
86
88
  }, [value])
87
89
 
90
+ const isWheel = presentation === 'wheel'
88
91
  const Input = useBottomDrawer ? LinkInput : FullWidthInputWrapper
92
+ const Picker = isWheel ? DateWheelPicker : DatePicker
89
93
 
90
94
  return (
91
95
  <Popover
92
96
  trigger="click"
93
97
  startsOpen={startsOpen}
94
98
  placement={placement || 'bottomLeft'}
95
- snapPoints={[450]}
99
+ snapPoints={[isWheel ? 275 : 450]}
96
100
  useBottomDrawer={useBottomDrawer}
97
101
  bottomDrawerProps={{ contentProps: { padding: 'md' } }}
98
102
  watch={[value?.format?.('YYYYMMDD')]}
99
103
  renderContent={({ onClose }) => (
100
104
  <View flex centerH onMouseDown={(e) => e.preventDefault()}>
101
- <DatePicker
105
+ <Picker
102
106
  value={value}
103
107
  onChange={(v) => {
104
108
  handleChange(v)
105
- onClose()
109
+ if (!isWheel) onClose()
106
110
  }}
107
- width={useBottomDrawer ? '100%' : 275}
111
+ width={!isWheel && (useBottomDrawer ? '100%' : 275)}
108
112
  allowClear={allowClear}
109
113
  {...validations}
110
114
  type={type}
@@ -9,14 +9,14 @@ import { useDefaultModifier } from '../../modifiers/default'
9
9
  import { useSizeConverter } from '../../modifiers/sizeConverter'
10
10
  import { useThemeComponentModifier } from '../../modifiers/themeComponent'
11
11
 
12
- const DEFAULT_PROPS = {
12
+ const DEFAULT_PROPS = ([{ sizeCode }]) => ({
13
13
  paddingH: 'sm',
14
14
  bg: 'overlayBG',
15
15
  border: true,
16
- br: 'md',
16
+ br: sizeCode,
17
17
  row: true,
18
18
  gap: 'sm',
19
- }
19
+ })
20
20
 
21
21
  export function InputWrapper({
22
22
  prefix,
@@ -0,0 +1,50 @@
1
+ import React from 'react'
2
+
3
+ import { BottomDrawer } from '../modals/bottomDrawer'
4
+ import { LinkInput } from './LinkInput'
5
+ import { NumberWheelPicker } from './NumberWheelPicker'
6
+ import { View } from '../structure/View'
7
+
8
+ export function NumberWheelInput({
9
+ value,
10
+ onChange,
11
+ min,
12
+ max,
13
+ step,
14
+ decimalStep,
15
+ useInt,
16
+ precision,
17
+ suffix,
18
+ ...props
19
+ }) {
20
+ const [open, setOpen] = React.useState(false)
21
+ const [localValue, setLocalValue] = React.useState(value)
22
+ value = value ?? localValue
23
+
24
+ const handleChange = (v) => {
25
+ setLocalValue(v)
26
+ onChange?.(v)
27
+ }
28
+
29
+ const displayValue = value != null ? `${value}${suffix ? ` ${suffix}` : ''}` : ''
30
+
31
+ return (
32
+ <>
33
+ <LinkInput value={displayValue} onPress={() => setOpen(true)} {...props} />
34
+ <BottomDrawer open={open} onClose={() => setOpen(false)} snapPoints={[275]} contentProps={{ padding: 'md' }}>
35
+ <View flex centerH>
36
+ <NumberWheelPicker
37
+ value={value}
38
+ onChange={handleChange}
39
+ min={min}
40
+ max={max}
41
+ step={step}
42
+ decimalStep={decimalStep}
43
+ useInt={useInt}
44
+ precision={precision}
45
+ />
46
+ </View>
47
+ </BottomDrawer>
48
+ </>
49
+ )
50
+ }
@@ -0,0 +1,43 @@
1
+ import { Text } from '../text'
2
+ import { View } from '../structure'
3
+ import { WheelPicker } from './WheelPicker'
4
+
5
+ export function NumberWheelPicker({ value, onChange, min = 0, max = 100, step = 1, decimalStep = 1, useInt, precision = 2 }) {
6
+ if (useInt) precision = 0
7
+
8
+ const intValue = value != null ? Math.trunc(value) : min
9
+ const decFactor = Math.pow(10, precision)
10
+ const decValue = value != null ? Math.round((Math.abs(value) - Math.abs(intValue)) * decFactor) : 0
11
+
12
+ const handleIntChange = (v) => {
13
+ onChange?.(precision === 0 ? v : v + decValue / decFactor)
14
+ }
15
+
16
+ const handleDecChange = (v) => {
17
+ onChange?.(intValue + v / decFactor)
18
+ }
19
+
20
+ return (
21
+ <View row gap="xs" centerV>
22
+ <View flex>
23
+ <WheelPicker range={[min, max]} step={step} value={intValue} onChange={handleIntChange} />
24
+ </View>
25
+ {precision > 0 && (
26
+ <>
27
+ <Text h3 strong>
28
+ .
29
+ </Text>
30
+ <View flex>
31
+ <WheelPicker
32
+ range={[0, decFactor - 1]}
33
+ step={decimalStep}
34
+ value={decValue}
35
+ onChange={handleDecChange}
36
+ formatLabel={(v) => String(v).padStart(precision, '0')}
37
+ />
38
+ </View>
39
+ </>
40
+ )}
41
+ </View>
42
+ )
43
+ }
@@ -18,8 +18,9 @@ const DEFAULT_PROPS = ([{ sizeCode }]) => ({
18
18
  row: true,
19
19
  border: true,
20
20
  buttonProps: {
21
- fullH: true,
22
21
  size: sizeCode,
22
+ height: null,
23
+ flex: true,
23
24
  },
24
25
  })
25
26
 
@@ -111,7 +111,7 @@ function Content({ value, open, remove, isDragging, multiple, area, grid, maxCou
111
111
  return (
112
112
  <View row wrap gap="sm">
113
113
  {items.map((item) => (
114
- <GridItem key={item._id} value={item} remove={remove} />
114
+ <GridItem key={item._id ?? item.id} value={item} remove={remove} />
115
115
  ))}
116
116
  {showAdd && <AddTile onPress={open} isDragging={isDragging} />}
117
117
  </View>
@@ -129,7 +129,7 @@ function Content({ value, open, remove, isDragging, multiple, area, grid, maxCou
129
129
  <View gap="xs">
130
130
  {showAdd && link}
131
131
  {items.map((item) => (
132
- <ListItem key={item._id} value={item} remove={remove} />
132
+ <ListItem key={item._id ?? item.id} value={item} remove={remove} />
133
133
  ))}
134
134
  </View>
135
135
  )
@@ -0,0 +1,49 @@
1
+ import { NumberInput } from './NumberInput'
2
+
3
+ export function WheelPicker({
4
+ value,
5
+ onChange,
6
+ options,
7
+ suffix,
8
+ range,
9
+ step = 1,
10
+ labelKey = 'label',
11
+ valueKey = 'value',
12
+ useRawOption,
13
+ formatLabel,
14
+ ...props
15
+ }) {
16
+ let min = 0
17
+ let max = 100
18
+
19
+ if (!!range || !options) {
20
+ const [from, to] = range || [0, 100]
21
+ const count = Math.floor((to - from) / step) + 1
22
+ options = Array.from({ length: count }, (_, i) => {
23
+ const v = from + i * step
24
+ return { [labelKey]: formatLabel ? formatLabel(v) : v, [valueKey]: v }
25
+ })
26
+ }
27
+
28
+ const values = (options || []).map((o) => o[valueKey]).filter((v) => typeof v === 'number')
29
+ if (values.length) {
30
+ min = Math.min(...values)
31
+ max = Math.max(...values)
32
+ }
33
+
34
+ function handleChange(newValue) {
35
+ if (useRawOption) {
36
+ // NumberInput allows free typing — snap to the nearest option on miss
37
+ const match = options.find((o) => o[valueKey] === newValue)
38
+ return onChange(
39
+ match ||
40
+ options.reduce((best, o) =>
41
+ Math.abs(o[valueKey] - newValue) < Math.abs(best[valueKey] - newValue) ? o : best
42
+ )
43
+ )
44
+ }
45
+ onChange(newValue)
46
+ }
47
+
48
+ return <NumberInput value={value} onChange={handleChange} suffix={suffix} min={min} max={max} precision={0} {...props} />
49
+ }
@@ -0,0 +1,88 @@
1
+ import React from 'react'
2
+
3
+ import { Text } from '../text'
4
+ import { View } from '../structure'
5
+ import { useColors } from '../../theme'
6
+ import { debounce } from '../../helpers/debounce'
7
+
8
+ let QuidoneWheelPicker
9
+
10
+ try {
11
+ QuidoneWheelPicker = require('@quidone/react-native-wheel-picker').default
12
+ } catch {
13
+ QuidoneWheelPicker = null
14
+ }
15
+
16
+ export function WheelPicker({
17
+ value,
18
+ onChange,
19
+ options,
20
+ suffix,
21
+ range,
22
+ step = 1,
23
+ labelKey = 'label',
24
+ valueKey = 'value',
25
+ useRawOption,
26
+ formatLabel,
27
+ ...props
28
+ }) {
29
+ const colors = useColors()
30
+ const handleChange = React.useMemo(() => debounce(onChange, 300), [onChange])
31
+
32
+ if (!!range || !options) {
33
+ const [from, to] = range || [0, 100]
34
+ const count = Math.floor((to - from) / step) + 1
35
+ options = Array.from({ length: count }, (_, i) => {
36
+ const v = from + i * step
37
+ return { [labelKey]: formatLabel ? formatLabel(v) : v, [valueKey]: v }
38
+ })
39
+ }
40
+
41
+ const data = React.useMemo(() => {
42
+ if (!options) return []
43
+ return options.map((opt) => ({
44
+ value: opt[valueKey],
45
+ label: String(opt[labelKey] ?? opt[valueKey]),
46
+ }))
47
+ }, [options, labelKey, valueKey])
48
+
49
+ if (!QuidoneWheelPicker) {
50
+ console.warn('@quidone/react-native-wheel-picker not installed.')
51
+ return null
52
+ }
53
+
54
+ const resolveValue = (item) => {
55
+ if (useRawOption) return options.find((o) => o[valueKey] === item.value)
56
+ return item.value
57
+ }
58
+
59
+ return (
60
+ <View relative hiddenOverflow>
61
+ {!!suffix && (
62
+ <View absoluteFill row centerV>
63
+ <View flex />
64
+ <View flex paddingL={20}>
65
+ <Text text2>{suffix}</Text>
66
+ </View>
67
+ </View>
68
+ )}
69
+ <QuidoneWheelPicker
70
+ data={data}
71
+ value={value}
72
+ itemTextStyle={{ color: colors.text }}
73
+ itemHeight={40}
74
+ overlayItemStyle={{
75
+ backgroundColor: 'transparent',
76
+ borderWidth: 1,
77
+ borderLeftWidth: 0,
78
+ borderRightWidth: 0,
79
+ borderColor: colors.divider,
80
+ opacity: 1,
81
+ }}
82
+ _onValueChanging={({ item }) => handleChange(resolveValue(item))}
83
+ onValueChanged={({ item }) => onChange(resolveValue(item))}
84
+ {...props}
85
+ />
86
+ </View>
87
+ )
88
+ }
@@ -0,0 +1 @@
1
+ export { WheelPicker } from './WheelPicker.native'
@@ -0,0 +1,24 @@
1
+ import { DayWheelPicker } from './DayWheelPicker'
2
+ import { MonthWheelPicker } from './MonthWheelPicker'
3
+ import { QuarterWheelPicker } from './QuarterWheelPicker'
4
+ import { WeekWheelPicker } from './WeekWheelPicker'
5
+ import { YearWheelPicker } from './YearWheelPicker'
6
+
7
+ export function DateWheelPicker({ type, ...props }) {
8
+ switch (type) {
9
+ case 'year':
10
+ return <YearWheelPicker {...props} />
11
+
12
+ case 'quarter':
13
+ return <QuarterWheelPicker {...props} />
14
+
15
+ case 'month':
16
+ return <MonthWheelPicker {...props} />
17
+
18
+ case 'week':
19
+ return <WeekWheelPicker {...props} />
20
+
21
+ default:
22
+ return <DayWheelPicker {...props} />
23
+ }
24
+ }
@@ -0,0 +1,48 @@
1
+ import dayjs from 'dayjs'
2
+
3
+ import { ClearLink } from '../../actions/ClearLink'
4
+ import { View } from '../../structure'
5
+ import { useColors } from '../../../theme'
6
+
7
+ let DatePicker
8
+
9
+ try {
10
+ DatePicker = require('@quidone/react-native-wheel-picker').DatePicker
11
+ } catch {
12
+ DatePicker = null
13
+ }
14
+
15
+ export function DayWheelPicker({ value, onChange, min, max, onCheckDisabled, allowClear, ...props }) {
16
+ const colors = useColors()
17
+ if (!DatePicker) {
18
+ console.warn('@quidone/react-native-wheel-picker not installed.')
19
+ return null
20
+ }
21
+
22
+ value = value ? dayjs(value) : dayjs()
23
+
24
+ return (
25
+ <View center height={40 * 5} overflow="hidden" relative>
26
+ <DatePicker
27
+ locale={'pt'}
28
+ date={value.format('YYYY-MM-DD')}
29
+ minDate={min ? dayjs(min).format('YYYY-MM-DD') : undefined}
30
+ maxDate={max ? dayjs(max).format('YYYY-MM-DD') : undefined}
31
+ onDateChanged={({ date }) => onChange(dayjs(date))}
32
+ itemTextStyle={{ color: colors.text }}
33
+ itemHeight={40}
34
+ overlayItemStyle={{
35
+ backgroundColor: 'transparent',
36
+ borderWidth: 1,
37
+ borderLeftWidth: 0,
38
+ borderRightWidth: 0,
39
+ borderColor: colors.divider,
40
+ opacity: 1,
41
+ }}
42
+ {...props}
43
+ />
44
+
45
+ <ClearLink hide={!allowClear} value={value} onChange={onChange} />
46
+ </View>
47
+ )
48
+ }
@@ -0,0 +1,19 @@
1
+ import { View } from '../../structure'
2
+ import { DayWheelPicker } from './DayWheelPicker'
3
+
4
+ let DatePickerDate
5
+ try {
6
+ DatePickerDate = require('@quidone/react-native-wheel-picker').DatePicker.Date
7
+ } catch {
8
+ DatePickerDate = null
9
+ }
10
+
11
+ const renderHiddenDate = () => (
12
+ <View width={0} hiddenOverflow pointerEvents="none">
13
+ {DatePickerDate ? <DatePickerDate /> : null}
14
+ </View>
15
+ )
16
+
17
+ export function MonthWheelPicker({ value, ...props }) {
18
+ return <DayWheelPicker {...props} value={value?.startOf?.('month') ?? value} renderDate={renderHiddenDate} />
19
+ }