@neko-os/ui 0.5.4 → 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 (135) hide show
  1. package/dist/NekoUI.js +12 -9
  2. package/dist/abstractions/WindowOverlay.js +3 -0
  3. package/dist/abstractions/WindowOverlay.native.js +21 -0
  4. package/dist/abstractions/helpers/storage.js +14 -4
  5. package/dist/abstractions/helpers/storage.native.js +9 -1
  6. package/dist/components/feedback/notifications/NotificationsHandler.js +10 -6
  7. package/dist/components/index.js +3 -1
  8. package/dist/components/inputs/DateInput.js +10 -6
  9. package/dist/components/inputs/InputWrapper.js +2 -3
  10. package/dist/components/inputs/NumberWheelInput.js +50 -0
  11. package/dist/components/inputs/NumberWheelPicker.js +43 -0
  12. package/dist/components/inputs/UploadInput.js +4 -4
  13. package/dist/components/inputs/WheelPicker.js +49 -0
  14. package/dist/components/inputs/WheelPicker.native.js +88 -0
  15. package/dist/components/inputs/WheelPicker.web.js +1 -0
  16. package/dist/components/inputs/dateWheelPicker/DateWheelPicker.js +24 -0
  17. package/dist/components/inputs/dateWheelPicker/DayWheelPicker.js +48 -0
  18. package/dist/components/inputs/dateWheelPicker/MonthWheelPicker.js +19 -0
  19. package/dist/components/inputs/dateWheelPicker/QuarterWheelPicker.js +61 -0
  20. package/dist/components/inputs/dateWheelPicker/WeekWheelPicker.js +66 -0
  21. package/dist/components/inputs/dateWheelPicker/YearWheelPicker.js +35 -0
  22. package/dist/components/inputs/index.js +5 -1
  23. package/dist/components/inputs/upload/Upload.native.js +60 -52
  24. package/dist/components/inputs/upload/useUploadState.js +11 -3
  25. package/dist/components/measurements/FeetInchesInput.js +91 -0
  26. package/dist/components/measurements/LengthInput.js +32 -0
  27. package/dist/components/measurements/LengthText.js +10 -0
  28. package/dist/components/measurements/MeasurementHandler.js +26 -0
  29. package/dist/components/measurements/WeightInput.js +25 -0
  30. package/dist/components/measurements/WeightText.js +10 -0
  31. package/dist/components/measurements/helpers/detectMeasurementSystem.js +15 -0
  32. package/dist/components/measurements/helpers/detectMeasurementSystem.native.js +9 -0
  33. package/dist/components/measurements/helpers/index.js +2 -0
  34. package/dist/components/measurements/helpers/length.js +112 -0
  35. package/dist/components/measurements/helpers/weight.js +56 -0
  36. package/dist/components/measurements/index.js +9 -0
  37. package/dist/components/measurements/useLengthFormatter.js +35 -0
  38. package/dist/components/measurements/useLocalInputValue.js +32 -0
  39. package/dist/components/measurements/useWeightFormatter.js +29 -0
  40. package/dist/components/routing/ReturnButton.js +20 -0
  41. package/dist/components/routing/ReturnButton.native.js +20 -0
  42. package/dist/components/routing/ReturnButton.web.js +2 -0
  43. package/dist/components/routing/ReturnLink.js +25 -0
  44. package/dist/components/routing/ReturnLink.native.js +25 -0
  45. package/dist/components/routing/ReturnLink.web.js +2 -0
  46. package/dist/components/routing/RoutedStepsContent.js +21 -0
  47. package/dist/components/routing/RoutedStepsContent.native.js +94 -0
  48. package/dist/components/routing/RoutedStepsContent.web.js +3 -0
  49. package/dist/components/routing/index.js +3 -0
  50. package/dist/components/state/StatePresenter.js +1 -1
  51. package/dist/components/steps/StepsHandler.js +2 -0
  52. package/dist/components/structure/TopBar.js +18 -16
  53. package/dist/components/theme/ThemePickerDrawer.js +1 -1
  54. package/dist/helpers/compress.js +61 -0
  55. package/dist/helpers/compress.native.js +49 -0
  56. package/dist/helpers/files.js +7 -0
  57. package/dist/helpers/files.native.js +55 -0
  58. package/dist/helpers/index.js +6 -1
  59. package/dist/helpers/media.js +4 -0
  60. package/dist/helpers/media.native.js +41 -0
  61. package/dist/helpers/numbers.js +13 -0
  62. package/dist/helpers/pickAssets.js +7 -0
  63. package/dist/helpers/pickAssets.native.js +66 -0
  64. package/dist/helpers/storage.js +17 -0
  65. package/dist/i18n/I18n.js +4 -4
  66. package/dist/index.js +1 -1
  67. package/dist/responsive/responsiveHooks.js +14 -0
  68. package/package.json +2 -14
  69. package/src/NekoUI.js +16 -13
  70. package/src/abstractions/WindowOverlay.js +3 -0
  71. package/src/abstractions/WindowOverlay.native.js +21 -0
  72. package/src/abstractions/helpers/storage.js +13 -3
  73. package/src/abstractions/helpers/storage.native.js +8 -0
  74. package/src/components/feedback/notifications/NotificationsHandler.js +12 -8
  75. package/src/components/index.js +2 -0
  76. package/src/components/inputs/DateInput.js +8 -4
  77. package/src/components/inputs/InputWrapper.js +1 -2
  78. package/src/components/inputs/NumberWheelInput.js +50 -0
  79. package/src/components/inputs/NumberWheelPicker.js +43 -0
  80. package/src/components/inputs/UploadInput.js +2 -2
  81. package/src/components/inputs/WheelPicker.js +49 -0
  82. package/src/components/inputs/WheelPicker.native.js +88 -0
  83. package/src/components/inputs/WheelPicker.web.js +1 -0
  84. package/src/components/inputs/dateWheelPicker/DateWheelPicker.js +24 -0
  85. package/src/components/inputs/dateWheelPicker/DayWheelPicker.js +48 -0
  86. package/src/components/inputs/dateWheelPicker/MonthWheelPicker.js +19 -0
  87. package/src/components/inputs/dateWheelPicker/QuarterWheelPicker.js +61 -0
  88. package/src/components/inputs/dateWheelPicker/WeekWheelPicker.js +66 -0
  89. package/src/components/inputs/dateWheelPicker/YearWheelPicker.js +35 -0
  90. package/src/components/inputs/index.js +4 -0
  91. package/src/components/inputs/upload/Upload.native.js +58 -50
  92. package/src/components/inputs/upload/useUploadState.js +11 -3
  93. package/src/components/measurements/FeetInchesInput.js +91 -0
  94. package/src/components/measurements/LengthInput.js +32 -0
  95. package/src/components/measurements/LengthText.js +10 -0
  96. package/src/components/measurements/MeasurementHandler.js +26 -0
  97. package/src/components/measurements/WeightInput.js +25 -0
  98. package/src/components/measurements/WeightText.js +10 -0
  99. package/src/components/measurements/helpers/detectMeasurementSystem.js +15 -0
  100. package/src/components/measurements/helpers/detectMeasurementSystem.native.js +9 -0
  101. package/src/components/measurements/helpers/index.js +2 -0
  102. package/src/components/measurements/helpers/length.js +112 -0
  103. package/src/components/measurements/helpers/weight.js +56 -0
  104. package/src/components/measurements/index.js +9 -0
  105. package/src/components/measurements/useLengthFormatter.js +35 -0
  106. package/src/components/measurements/useLocalInputValue.js +32 -0
  107. package/src/components/measurements/useWeightFormatter.js +29 -0
  108. package/src/components/routing/ReturnButton.js +20 -0
  109. package/src/components/routing/ReturnButton.native.js +20 -0
  110. package/src/components/routing/ReturnButton.web.js +2 -0
  111. package/src/components/routing/ReturnLink.js +25 -0
  112. package/src/components/routing/ReturnLink.native.js +25 -0
  113. package/src/components/routing/ReturnLink.web.js +2 -0
  114. package/src/components/routing/RoutedStepsContent.js +21 -0
  115. package/src/components/routing/RoutedStepsContent.native.js +94 -0
  116. package/src/components/routing/RoutedStepsContent.web.js +3 -0
  117. package/src/components/routing/index.js +3 -0
  118. package/src/components/state/StatePresenter.js +1 -1
  119. package/src/components/steps/StepsHandler.js +2 -0
  120. package/src/components/structure/TopBar.js +16 -14
  121. package/src/components/theme/ThemePickerDrawer.js +1 -1
  122. package/src/helpers/compress.js +61 -0
  123. package/src/helpers/compress.native.js +49 -0
  124. package/src/helpers/files.js +7 -0
  125. package/src/helpers/files.native.js +55 -0
  126. package/src/helpers/index.js +6 -1
  127. package/src/helpers/media.js +4 -0
  128. package/src/helpers/media.native.js +41 -0
  129. package/src/helpers/numbers.js +13 -0
  130. package/src/helpers/pickAssets.js +7 -0
  131. package/src/helpers/pickAssets.native.js +66 -0
  132. package/src/helpers/storage.js +17 -0
  133. package/src/i18n/I18n.js +2 -2
  134. package/src/index.js +1 -1
  135. package/src/responsive/responsiveHooks.js +14 -0
@@ -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}
@@ -13,10 +13,9 @@ 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
- br: sizeCode,
20
19
  })
21
20
 
22
21
  export function InputWrapper({
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,61 @@
1
+ import { useMemo } from 'react'
2
+ import dayjs from 'dayjs'
3
+
4
+ import { ClearLink } from '../../actions/ClearLink'
5
+ import { View } from '../../structure'
6
+ import { WheelPicker } from '../WheelPicker'
7
+
8
+ const QUARTERS = [
9
+ { value: 1, label: 'Q1' },
10
+ { value: 2, label: 'Q2' },
11
+ { value: 3, label: 'Q3' },
12
+ { value: 4, label: 'Q4' },
13
+ ]
14
+
15
+ function getYearOptions(min, max) {
16
+ const minYear = min ? dayjs(min).year() : dayjs().year() - 100
17
+ const maxYear = max ? dayjs(max).year() : dayjs().year() + 100
18
+ const options = []
19
+ for (let y = minYear; y <= maxYear; y++) options.push({ value: y, label: String(y) })
20
+ return options
21
+ }
22
+
23
+ export function QuarterWheelPicker({ value, onChange, min, max, allowClear }) {
24
+ const current = value ? dayjs(value) : dayjs()
25
+ const quarter = Math.floor(current.month() / 3) + 1
26
+ const year = current.year()
27
+
28
+ const yearOptions = useMemo(() => getYearOptions(min, max), [min, max])
29
+
30
+ const handleQuarterChange = (q) => {
31
+ onChange?.(
32
+ dayjs()
33
+ .year(year)
34
+ .month((q - 1) * 3)
35
+ .startOf('month')
36
+ )
37
+ }
38
+
39
+ const handleYearChange = (y) => {
40
+ onChange?.(
41
+ dayjs()
42
+ .year(y)
43
+ .month((quarter - 1) * 3)
44
+ .startOf('month')
45
+ )
46
+ }
47
+
48
+ return (
49
+ <View>
50
+ <View row>
51
+ <View flex>
52
+ <WheelPicker options={QUARTERS} value={quarter} onChange={handleQuarterChange} />
53
+ </View>
54
+ <View flex>
55
+ <WheelPicker options={yearOptions} value={year} onChange={handleYearChange} />
56
+ </View>
57
+ </View>
58
+ <ClearLink hide={!allowClear} value={value} onChange={onChange} />
59
+ </View>
60
+ )
61
+ }
@@ -0,0 +1,66 @@
1
+ import { useMemo } from 'react'
2
+ import dayjs from 'dayjs'
3
+ import weekOfYear from 'dayjs/esm/plugin/weekOfYear'
4
+
5
+ import { ClearLink } from '../../actions/ClearLink'
6
+ import { View } from '../../structure'
7
+ import { WheelPicker } from '../WheelPicker'
8
+
9
+ dayjs.extend(weekOfYear)
10
+
11
+ function getWeekOptions(year) {
12
+ const options = []
13
+ let d = dayjs().year(year).startOf('year').startOf('week')
14
+
15
+ while (d.year() <= year) {
16
+ const weekStart = d
17
+ const weekEnd = d.endOf('week')
18
+ const label = `W${d.week()} ${weekStart.format('MMM D')} - ${weekEnd.format('MMM D')}`
19
+ options.push({ value: d.week(), label, date: weekStart })
20
+ d = d.add(1, 'week')
21
+ }
22
+
23
+ return options
24
+ }
25
+
26
+ function getYearOptions(min, max) {
27
+ const minYear = min ? dayjs(min).year() : dayjs().year() - 100
28
+ const maxYear = max ? dayjs(max).year() : dayjs().year() + 100
29
+ const options = []
30
+ for (let y = minYear; y <= maxYear; y++) options.push({ value: y, label: String(y) })
31
+ return options
32
+ }
33
+
34
+ export function WeekWheelPicker({ value, onChange, min, max, allowClear }) {
35
+ const current = value ? dayjs(value) : dayjs()
36
+ const week = current.week()
37
+ const year = current.year()
38
+
39
+ const weekOptions = useMemo(() => getWeekOptions(year), [year])
40
+ const yearOptions = useMemo(() => getYearOptions(min, max), [min, max])
41
+
42
+ const handleWeekChange = (w) => {
43
+ const opt = weekOptions.find((o) => o.value === w)
44
+ if (opt) onChange?.(opt.date.startOf('week'))
45
+ }
46
+
47
+ const handleYearChange = (y) => {
48
+ const newWeeks = getWeekOptions(y)
49
+ const closest = newWeeks.find((o) => o.value === week) || newWeeks[0]
50
+ onChange?.(closest.date.startOf('week'))
51
+ }
52
+
53
+ return (
54
+ <View>
55
+ <View row>
56
+ <View flex>
57
+ <WheelPicker options={weekOptions} value={week} onChange={handleWeekChange} />
58
+ </View>
59
+ <View width={120}>
60
+ <WheelPicker options={yearOptions} value={year} onChange={handleYearChange} />
61
+ </View>
62
+ </View>
63
+ <ClearLink hide={!allowClear} value={value} onChange={onChange} />
64
+ </View>
65
+ )
66
+ }
@@ -0,0 +1,35 @@
1
+ import { View } from '../../structure'
2
+ import { DayWheelPicker } from './DayWheelPicker'
3
+
4
+ let DatePickerDate, DatePickerMonth
5
+ try {
6
+ const dp = require('@quidone/react-native-wheel-picker').DatePicker
7
+ DatePickerDate = dp.Date
8
+ DatePickerMonth = dp.Month
9
+ } catch {
10
+ DatePickerDate = null
11
+ DatePickerMonth = null
12
+ }
13
+
14
+ const renderHiddenDate = () => (
15
+ <View width={0} hiddenOverflow pointerEvents="none">
16
+ {DatePickerDate ? <DatePickerDate /> : null}
17
+ </View>
18
+ )
19
+
20
+ const renderHiddenMonth = () => (
21
+ <View width={0} hiddenOverflow pointerEvents="none">
22
+ {DatePickerMonth ? <DatePickerMonth /> : null}
23
+ </View>
24
+ )
25
+
26
+ export function YearWheelPicker({ value, ...props }) {
27
+ return (
28
+ <DayWheelPicker
29
+ {...props}
30
+ value={value?.startOf?.('year') ?? value}
31
+ renderDate={renderHiddenDate}
32
+ renderMonth={renderHiddenMonth}
33
+ />
34
+ )
35
+ }
@@ -21,3 +21,7 @@ export * from './SegmentedPicker'
21
21
  export * from './Editable'
22
22
  export * from './upload/Upload'
23
23
  export * from './UploadInput'
24
+ export * from './WheelPicker'
25
+ export * from './dateWheelPicker/DateWheelPicker'
26
+ export * from './NumberWheelInput'
27
+ export * from './NumberWheelPicker'