@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.
- package/dist/NekoUI.js +12 -9
- package/dist/abstractions/Image.native.js +1 -1
- package/dist/abstractions/Image.web.js +1 -1
- package/dist/abstractions/WindowOverlay.js +3 -0
- package/dist/abstractions/WindowOverlay.native.js +21 -0
- package/dist/abstractions/helpers/storage.js +14 -4
- package/dist/abstractions/helpers/storage.native.js +9 -1
- package/dist/components/feedback/notifications/NotificationsHandler.js +10 -6
- package/dist/components/form/useNewForm.js +2 -0
- package/dist/components/index.js +3 -1
- package/dist/components/inputs/DateInput.js +10 -6
- package/dist/components/inputs/InputWrapper.js +11 -11
- package/dist/components/inputs/NumberWheelInput.js +50 -0
- package/dist/components/inputs/NumberWheelPicker.js +43 -0
- package/dist/components/inputs/SegmentedPicker.js +3 -2
- package/dist/components/inputs/UploadInput.js +4 -4
- package/dist/components/inputs/WheelPicker.js +49 -0
- package/dist/components/inputs/WheelPicker.native.js +88 -0
- package/dist/components/inputs/WheelPicker.web.js +1 -0
- package/dist/components/inputs/dateWheelPicker/DateWheelPicker.js +24 -0
- package/dist/components/inputs/dateWheelPicker/DayWheelPicker.js +48 -0
- package/dist/components/inputs/dateWheelPicker/MonthWheelPicker.js +19 -0
- package/dist/components/inputs/dateWheelPicker/QuarterWheelPicker.js +61 -0
- package/dist/components/inputs/dateWheelPicker/WeekWheelPicker.js +66 -0
- package/dist/components/inputs/dateWheelPicker/YearWheelPicker.js +35 -0
- package/dist/components/inputs/index.js +5 -1
- package/dist/components/inputs/upload/Upload.native.js +60 -52
- package/dist/components/inputs/upload/useUploadState.js +11 -3
- package/dist/components/measurements/FeetInchesInput.js +91 -0
- package/dist/components/measurements/LengthInput.js +32 -0
- package/dist/components/measurements/LengthText.js +10 -0
- package/dist/components/measurements/MeasurementHandler.js +26 -0
- package/dist/components/measurements/WeightInput.js +25 -0
- package/dist/components/measurements/WeightText.js +10 -0
- package/dist/components/measurements/helpers/detectMeasurementSystem.js +15 -0
- package/dist/components/measurements/helpers/detectMeasurementSystem.native.js +9 -0
- package/dist/components/measurements/helpers/index.js +2 -0
- package/dist/components/measurements/helpers/length.js +112 -0
- package/dist/components/measurements/helpers/weight.js +56 -0
- package/dist/components/measurements/index.js +9 -0
- package/dist/components/measurements/useLengthFormatter.js +35 -0
- package/dist/components/measurements/useLocalInputValue.js +32 -0
- package/dist/components/measurements/useWeightFormatter.js +29 -0
- package/dist/components/presentation/Avatar.js +3 -3
- package/dist/components/routing/ReturnButton.js +20 -0
- package/dist/components/routing/ReturnButton.native.js +20 -0
- package/dist/components/routing/ReturnButton.web.js +2 -0
- package/dist/components/routing/ReturnLink.js +25 -0
- package/dist/components/routing/ReturnLink.native.js +25 -0
- package/dist/components/routing/ReturnLink.web.js +2 -0
- package/dist/components/routing/RoutedStepsContent.js +21 -0
- package/dist/components/routing/RoutedStepsContent.native.js +94 -0
- package/dist/components/routing/RoutedStepsContent.web.js +3 -0
- package/dist/components/routing/index.js +3 -0
- package/dist/components/state/StatePresenter.js +1 -1
- package/dist/components/steps/StepsHandler.js +2 -0
- package/dist/components/structure/TopBar.js +18 -16
- package/dist/components/theme/ThemePickerDrawer.js +1 -1
- package/dist/helpers/compress.js +61 -0
- package/dist/helpers/compress.native.js +49 -0
- package/dist/helpers/files.js +7 -0
- package/dist/helpers/files.native.js +55 -0
- package/dist/helpers/index.js +6 -1
- package/dist/helpers/media.js +4 -0
- package/dist/helpers/media.native.js +41 -0
- package/dist/helpers/numbers.js +13 -0
- package/dist/helpers/pickAssets.js +7 -0
- package/dist/helpers/pickAssets.native.js +66 -0
- package/dist/helpers/storage.js +17 -0
- package/dist/i18n/I18n.js +4 -4
- package/dist/index.js +1 -1
- package/dist/modifiers/flex.js +8 -3
- package/dist/responsive/responsiveHooks.js +14 -0
- package/dist/theme/default/blackTheme.js +3 -1
- package/dist/theme/default/cyberpunkTheme.js +3 -1
- package/dist/theme/default/darkTheme.js +3 -1
- package/dist/theme/default/hackerTheme.js +3 -1
- package/dist/theme/default/lightTheme.js +3 -1
- package/dist/theme/default/paperTheme.js +3 -1
- package/package.json +2 -14
- package/src/NekoUI.js +16 -13
- package/src/abstractions/Image.native.js +1 -1
- package/src/abstractions/Image.web.js +1 -1
- package/src/abstractions/WindowOverlay.js +3 -0
- package/src/abstractions/WindowOverlay.native.js +21 -0
- package/src/abstractions/helpers/storage.js +13 -3
- package/src/abstractions/helpers/storage.native.js +8 -0
- package/src/components/feedback/notifications/NotificationsHandler.js +12 -8
- package/src/components/form/useNewForm.js +2 -0
- package/src/components/index.js +2 -0
- package/src/components/inputs/DateInput.js +8 -4
- package/src/components/inputs/InputWrapper.js +3 -3
- package/src/components/inputs/NumberWheelInput.js +50 -0
- package/src/components/inputs/NumberWheelPicker.js +43 -0
- package/src/components/inputs/SegmentedPicker.js +2 -1
- package/src/components/inputs/UploadInput.js +2 -2
- package/src/components/inputs/WheelPicker.js +49 -0
- package/src/components/inputs/WheelPicker.native.js +88 -0
- package/src/components/inputs/WheelPicker.web.js +1 -0
- package/src/components/inputs/dateWheelPicker/DateWheelPicker.js +24 -0
- package/src/components/inputs/dateWheelPicker/DayWheelPicker.js +48 -0
- package/src/components/inputs/dateWheelPicker/MonthWheelPicker.js +19 -0
- package/src/components/inputs/dateWheelPicker/QuarterWheelPicker.js +61 -0
- package/src/components/inputs/dateWheelPicker/WeekWheelPicker.js +66 -0
- package/src/components/inputs/dateWheelPicker/YearWheelPicker.js +35 -0
- package/src/components/inputs/index.js +4 -0
- package/src/components/inputs/upload/Upload.native.js +58 -50
- package/src/components/inputs/upload/useUploadState.js +11 -3
- package/src/components/measurements/FeetInchesInput.js +91 -0
- package/src/components/measurements/LengthInput.js +32 -0
- package/src/components/measurements/LengthText.js +10 -0
- package/src/components/measurements/MeasurementHandler.js +26 -0
- package/src/components/measurements/WeightInput.js +25 -0
- package/src/components/measurements/WeightText.js +10 -0
- package/src/components/measurements/helpers/detectMeasurementSystem.js +15 -0
- package/src/components/measurements/helpers/detectMeasurementSystem.native.js +9 -0
- package/src/components/measurements/helpers/index.js +2 -0
- package/src/components/measurements/helpers/length.js +112 -0
- package/src/components/measurements/helpers/weight.js +56 -0
- package/src/components/measurements/index.js +9 -0
- package/src/components/measurements/useLengthFormatter.js +35 -0
- package/src/components/measurements/useLocalInputValue.js +32 -0
- package/src/components/measurements/useWeightFormatter.js +29 -0
- package/src/components/presentation/Avatar.js +2 -2
- package/src/components/routing/ReturnButton.js +20 -0
- package/src/components/routing/ReturnButton.native.js +20 -0
- package/src/components/routing/ReturnButton.web.js +2 -0
- package/src/components/routing/ReturnLink.js +25 -0
- package/src/components/routing/ReturnLink.native.js +25 -0
- package/src/components/routing/ReturnLink.web.js +2 -0
- package/src/components/routing/RoutedStepsContent.js +21 -0
- package/src/components/routing/RoutedStepsContent.native.js +94 -0
- package/src/components/routing/RoutedStepsContent.web.js +3 -0
- package/src/components/routing/index.js +3 -0
- package/src/components/state/StatePresenter.js +1 -1
- package/src/components/steps/StepsHandler.js +2 -0
- package/src/components/structure/TopBar.js +16 -14
- package/src/components/theme/ThemePickerDrawer.js +1 -1
- package/src/helpers/compress.js +61 -0
- package/src/helpers/compress.native.js +49 -0
- package/src/helpers/files.js +7 -0
- package/src/helpers/files.native.js +55 -0
- package/src/helpers/index.js +6 -1
- package/src/helpers/media.js +4 -0
- package/src/helpers/media.native.js +41 -0
- package/src/helpers/numbers.js +13 -0
- package/src/helpers/pickAssets.js +7 -0
- package/src/helpers/pickAssets.native.js +66 -0
- package/src/helpers/storage.js +17 -0
- package/src/i18n/I18n.js +2 -2
- package/src/index.js +1 -1
- package/src/modifiers/flex.js +7 -2
- package/src/responsive/responsiveHooks.js +14 -0
- package/src/theme/default/blackTheme.js +2 -0
- package/src/theme/default/cyberpunkTheme.js +2 -0
- package/src/theme/default/darkTheme.js +2 -0
- package/src/theme/default/hackerTheme.js +2 -0
- package/src/theme/default/lightTheme.js +2 -0
- package/src/theme/default/paperTheme.js +2 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const CM_TO_INCH = 0.393701
|
|
2
|
+
const CM_TO_FOOT = 0.0328084
|
|
3
|
+
const CM_PER_M = 100
|
|
4
|
+
const CM_PER_KM = 100000
|
|
5
|
+
const MI_TO_KM = 1.60934
|
|
6
|
+
|
|
7
|
+
export function cmToIn(cm) {
|
|
8
|
+
if (!cm) return cm
|
|
9
|
+
return cm * CM_TO_INCH
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function inToCm(inches) {
|
|
13
|
+
if (!inches) return inches
|
|
14
|
+
return inches / CM_TO_INCH
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function cmToFt(cm) {
|
|
18
|
+
if (!cm) return cm
|
|
19
|
+
return cm * CM_TO_FOOT
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ftToCm(feet) {
|
|
23
|
+
if (!feet) return feet
|
|
24
|
+
return feet / CM_TO_FOOT
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function cmToFtIn(cm) {
|
|
28
|
+
if (!cm) return false
|
|
29
|
+
const totalInches = cmToIn(cm)
|
|
30
|
+
let feet = Math.floor(totalInches / 12)
|
|
31
|
+
let inches = Math.round(totalInches % 12)
|
|
32
|
+
if (inches === 12) {
|
|
33
|
+
feet += 1
|
|
34
|
+
inches = 0
|
|
35
|
+
}
|
|
36
|
+
return { feet, inches }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function ftInToCm(value) {
|
|
40
|
+
if (!value) return false
|
|
41
|
+
const { feet, inches } = value || {}
|
|
42
|
+
const totalInches = (feet || 0) * 12 + (inches || 0)
|
|
43
|
+
return inToCm(totalInches)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function cmToM(cm) {
|
|
47
|
+
if (!cm) return cm
|
|
48
|
+
return cm / CM_PER_M
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function mToCm(m) {
|
|
52
|
+
if (!m) return m
|
|
53
|
+
return m * CM_PER_M
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function mToFt(m) {
|
|
57
|
+
if (!m) return m
|
|
58
|
+
return cmToFt(m * CM_PER_M)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function ftToM(ft) {
|
|
62
|
+
if (!ft) return ft
|
|
63
|
+
return ftToCm(ft) / CM_PER_M
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function mToFtIn(m) {
|
|
67
|
+
if (!m) return false
|
|
68
|
+
return cmToFtIn(m * CM_PER_M)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function ftInToM(value) {
|
|
72
|
+
if (!value) return false
|
|
73
|
+
return ftInToCm(value) / CM_PER_M
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function mToIn(m) {
|
|
77
|
+
if (!m) return m
|
|
78
|
+
return cmToIn(m * CM_PER_M)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function inToM(inches) {
|
|
82
|
+
if (!inches) return inches
|
|
83
|
+
return inToCm(inches) / CM_PER_M
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function kmToMi(km) {
|
|
87
|
+
if (!km) return km
|
|
88
|
+
return km / MI_TO_KM
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function miToKm(mi) {
|
|
92
|
+
if (!mi) return mi
|
|
93
|
+
return mi * MI_TO_KM
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const LENGTH_CONVERTERS = {
|
|
97
|
+
cm: {
|
|
98
|
+
'ft+in': { to: cmToFtIn, from: ftInToCm },
|
|
99
|
+
in: { to: cmToIn, from: inToCm },
|
|
100
|
+
ft: { to: cmToFt, from: ftToCm },
|
|
101
|
+
},
|
|
102
|
+
m: {
|
|
103
|
+
ft: { to: mToFt, from: ftToM },
|
|
104
|
+
'ft+in': { to: mToFtIn, from: ftInToM },
|
|
105
|
+
in: { to: mToIn, from: inToM },
|
|
106
|
+
},
|
|
107
|
+
km: {
|
|
108
|
+
mi: { to: kmToMi, from: miToKm },
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const LENGTH_IMPERIAL_DEFAULTS = { cm: 'ft+in', m: 'ft', km: 'mi' }
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const KG_TO_LB = 2.20462
|
|
2
|
+
const OZ_PER_LB = 16
|
|
3
|
+
const G_PER_KG = 1000
|
|
4
|
+
|
|
5
|
+
export function kgToLbs(kg) {
|
|
6
|
+
if (!kg) return kg
|
|
7
|
+
return kg * KG_TO_LB
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function lbsToKg(lbs) {
|
|
11
|
+
if (!lbs) return lbs
|
|
12
|
+
return lbs / KG_TO_LB
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function kgToOz(kg) {
|
|
16
|
+
if (!kg) return kg
|
|
17
|
+
return kg * KG_TO_LB * OZ_PER_LB
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ozToKg(oz) {
|
|
21
|
+
if (!oz) return oz
|
|
22
|
+
return oz / (KG_TO_LB * OZ_PER_LB)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function gToOz(g) {
|
|
26
|
+
if (!g) return g
|
|
27
|
+
return kgToOz(g / G_PER_KG)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ozToG(oz) {
|
|
31
|
+
if (!oz) return oz
|
|
32
|
+
return ozToKg(oz) * G_PER_KG
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function gToLbs(g) {
|
|
36
|
+
if (!g) return g
|
|
37
|
+
return kgToLbs(g / G_PER_KG)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function lbsToG(lbs) {
|
|
41
|
+
if (!lbs) return lbs
|
|
42
|
+
return lbsToKg(lbs) * G_PER_KG
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const WEIGHT_CONVERTERS = {
|
|
46
|
+
kg: {
|
|
47
|
+
lbs: { to: kgToLbs, from: lbsToKg },
|
|
48
|
+
oz: { to: kgToOz, from: ozToKg },
|
|
49
|
+
},
|
|
50
|
+
g: {
|
|
51
|
+
oz: { to: gToOz, from: ozToG },
|
|
52
|
+
lbs: { to: gToLbs, from: lbsToG },
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const WEIGHT_IMPERIAL_DEFAULTS = { kg: 'lbs', g: 'oz' }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './helpers'
|
|
2
|
+
export * from './MeasurementHandler'
|
|
3
|
+
export * from './useLengthFormatter'
|
|
4
|
+
export * from './useWeightFormatter'
|
|
5
|
+
export * from './LengthText'
|
|
6
|
+
export * from './WeightText'
|
|
7
|
+
export * from './FeetInchesInput'
|
|
8
|
+
export * from './LengthInput'
|
|
9
|
+
export * from './WeightInput'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { LENGTH_CONVERTERS, LENGTH_IMPERIAL_DEFAULTS } from './helpers/length'
|
|
2
|
+
import { fixedDecimals } from '../../helpers/numbers'
|
|
3
|
+
import { useIsImperial } from './MeasurementHandler'
|
|
4
|
+
|
|
5
|
+
export function useLengthFormatter({
|
|
6
|
+
measurementSystem,
|
|
7
|
+
metricPrecision = 'cm',
|
|
8
|
+
imperialPrecision,
|
|
9
|
+
withoutSuffix,
|
|
10
|
+
} = {}) {
|
|
11
|
+
const isImperial = useIsImperial(measurementSystem)
|
|
12
|
+
const impPrec = imperialPrecision || LENGTH_IMPERIAL_DEFAULTS[metricPrecision] || 'ft+in'
|
|
13
|
+
|
|
14
|
+
return (value) => {
|
|
15
|
+
if (!value && value !== 0) return null
|
|
16
|
+
|
|
17
|
+
if (isImperial) {
|
|
18
|
+
const converter = LENGTH_CONVERTERS[metricPrecision]?.[impPrec]
|
|
19
|
+
if (!converter) return `${fixedDecimals(value)} ${metricPrecision}`
|
|
20
|
+
|
|
21
|
+
const converted = converter.to(value)
|
|
22
|
+
|
|
23
|
+
if (impPrec === 'ft+in') {
|
|
24
|
+
const v = typeof converted === 'object' ? converted : { feet: 0, inches: 0 }
|
|
25
|
+
return `${v.feet}'${v.inches}"`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (withoutSuffix) return fixedDecimals(converted)
|
|
29
|
+
return `${fixedDecimals(converted)} ${impPrec}`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (withoutSuffix) return fixedDecimals(value)
|
|
33
|
+
return `${fixedDecimals(value)} ${metricPrecision}`
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
// Local display state for measurement inputs.
|
|
4
|
+
// Resyncs when the unit changes or the value is changed externally,
|
|
5
|
+
// without clobbering what the user is typing (their own onChange echoes back).
|
|
6
|
+
export function useLocalInputValue({ value, formattedValue, suffix, onChange, converter }) {
|
|
7
|
+
const [localValue, setLocalValue] = React.useState(formattedValue)
|
|
8
|
+
const [prevSuffix, setPrevSuffix] = React.useState(suffix)
|
|
9
|
+
const lastEmitted = React.useRef(value)
|
|
10
|
+
|
|
11
|
+
if (suffix !== prevSuffix) {
|
|
12
|
+
setPrevSuffix(suffix)
|
|
13
|
+
setLocalValue(formattedValue)
|
|
14
|
+
lastEmitted.current = value
|
|
15
|
+
} else if (value !== lastEmitted.current) {
|
|
16
|
+
lastEmitted.current = value
|
|
17
|
+
setLocalValue(formattedValue)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function handleChange(newValue) {
|
|
21
|
+
setLocalValue(newValue)
|
|
22
|
+
if (!newValue && newValue !== 0) {
|
|
23
|
+
lastEmitted.current = newValue
|
|
24
|
+
return onChange(newValue)
|
|
25
|
+
}
|
|
26
|
+
const converted = converter ? converter.from(newValue) : newValue
|
|
27
|
+
lastEmitted.current = converted
|
|
28
|
+
onChange(converted)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return [localValue, handleChange]
|
|
32
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { WEIGHT_CONVERTERS, WEIGHT_IMPERIAL_DEFAULTS } from './helpers/weight'
|
|
2
|
+
import { fixedDecimals } from '../../helpers/numbers'
|
|
3
|
+
import { useIsImperial } from './MeasurementHandler'
|
|
4
|
+
|
|
5
|
+
export function useWeightFormatter({
|
|
6
|
+
measurementSystem,
|
|
7
|
+
metricPrecision = 'kg',
|
|
8
|
+
imperialPrecision,
|
|
9
|
+
withoutSuffix,
|
|
10
|
+
} = {}) {
|
|
11
|
+
const isImperial = useIsImperial(measurementSystem)
|
|
12
|
+
const impPrec = imperialPrecision || WEIGHT_IMPERIAL_DEFAULTS[metricPrecision] || 'lbs'
|
|
13
|
+
|
|
14
|
+
return (value) => {
|
|
15
|
+
if (!value && value !== 0) return null
|
|
16
|
+
|
|
17
|
+
if (isImperial) {
|
|
18
|
+
const converter = WEIGHT_CONVERTERS[metricPrecision]?.[impPrec]
|
|
19
|
+
if (!converter) return `${fixedDecimals(value)} ${metricPrecision}`
|
|
20
|
+
|
|
21
|
+
const converted = converter.to(value)
|
|
22
|
+
if (withoutSuffix) return fixedDecimals(converted)
|
|
23
|
+
return `${fixedDecimals(converted)} ${impPrec}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (withoutSuffix) return fixedDecimals(value)
|
|
27
|
+
return `${fixedDecimals(value)} ${metricPrecision}`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -54,7 +54,7 @@ export function Avatar(rootProps) {
|
|
|
54
54
|
useOverflowModifier
|
|
55
55
|
)([{}, rootProps])
|
|
56
56
|
|
|
57
|
-
let { initials, name, icon, src, invert, textProps, iconProps, iconSize, ...props } = formattedProps
|
|
57
|
+
let { initials, name, icon, src, invert, textProps, iconProps, iconSize, imageProps, ...props } = formattedProps
|
|
58
58
|
initials = initials || getInitials(name)
|
|
59
59
|
|
|
60
60
|
let content = (
|
|
@@ -69,7 +69,7 @@ export function Avatar(rootProps) {
|
|
|
69
69
|
iconProps={{ size: iconSize, ...iconProps }}
|
|
70
70
|
/>
|
|
71
71
|
)
|
|
72
|
-
if (!!src) content = <Image br={0} src={src} width={sizeCode} height={sizeCode} />
|
|
72
|
+
if (!!src) content = <Image br={0} src={src} width={sizeCode} height={sizeCode} {...imageProps} />
|
|
73
73
|
|
|
74
74
|
return (
|
|
75
75
|
<AbsView className="neko-avatar" {...props}>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Button } from '../actions'
|
|
2
|
+
|
|
3
|
+
// Plain ReactJS (Vite/Next/CRA): react-router goBack via navigate(-1). require() in try/catch so a
|
|
4
|
+
// web app without react-router-dom degrades gracefully instead of failing the module load.
|
|
5
|
+
let useNavigate
|
|
6
|
+
try {
|
|
7
|
+
useNavigate = require('react-router-dom').useNavigate
|
|
8
|
+
} catch {
|
|
9
|
+
useNavigate = () => () => console.warn('ReturnButton: react-router-dom not installed.')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// A back/close Button. Defaults to a left-arrow icon that calls navigate(-1). `close` swaps to a
|
|
13
|
+
// close icon; `icon` overrides the icon name; `onPress` overrides goBack. Extra props pass to the
|
|
14
|
+
// Button (label, outline, size, color, etc.).
|
|
15
|
+
export function ReturnButton({ icon, close, onPress, ...props }) {
|
|
16
|
+
const navigate = useNavigate()
|
|
17
|
+
const name = icon || (close ? 'close-line' : 'arrow-left-s-line')
|
|
18
|
+
|
|
19
|
+
return <Button icon={name} onPress={onPress || (() => navigate(-1))} {...props} />
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Button } from '../actions'
|
|
2
|
+
|
|
3
|
+
// react-navigation goBack. require() in try/catch so a native app without react-navigation degrades
|
|
4
|
+
// gracefully instead of failing the module load.
|
|
5
|
+
let useNavigation
|
|
6
|
+
try {
|
|
7
|
+
useNavigation = require('@react-navigation/native').useNavigation
|
|
8
|
+
} catch {
|
|
9
|
+
useNavigation = () => ({ goBack: () => console.warn('ReturnButton: @react-navigation/native not installed.') })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// A back/close Button. Defaults to a left-arrow icon that calls navigation.goBack(). `close` swaps to
|
|
13
|
+
// a close icon; `icon` overrides the icon name; `onPress` overrides goBack. Extra props pass to the
|
|
14
|
+
// Button (label, outline, size, color, etc.).
|
|
15
|
+
export function ReturnButton({ icon, close, onPress, ...props }) {
|
|
16
|
+
const navigation = useNavigation()
|
|
17
|
+
const name = icon || (close ? 'close-line' : 'arrow-left-s-line')
|
|
18
|
+
|
|
19
|
+
return <Button icon={name} onPress={onPress || (() => navigation.goBack())} {...props} />
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Icon } from '../presentation'
|
|
2
|
+
import { Link } from '../actions'
|
|
3
|
+
|
|
4
|
+
// Plain ReactJS (Vite/Next/CRA): react-router goBack via navigate(-1). require() in try/catch so a
|
|
5
|
+
// web app without react-router-dom degrades gracefully instead of failing the module load.
|
|
6
|
+
let useNavigate
|
|
7
|
+
try {
|
|
8
|
+
useNavigate = require('react-router-dom').useNavigate
|
|
9
|
+
} catch {
|
|
10
|
+
useNavigate = () => () => console.warn('ReturnLink: react-router-dom not installed.')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// A back/close Link: a Link wrapping an Icon. Defaults to a left-arrow that calls navigate(-1).
|
|
14
|
+
// `close` swaps to a close icon; `icon` overrides the icon name entirely; `onPress` overrides goBack.
|
|
15
|
+
// Extra props pass to the Icon (size, color, etc.).
|
|
16
|
+
export function ReturnLink({ icon, close, onPress, ...props }) {
|
|
17
|
+
const navigate = useNavigate()
|
|
18
|
+
const name = icon || (close ? 'close-line' : 'arrow-left-s-line')
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Link onPress={onPress || (() => navigate(-1))}>
|
|
22
|
+
<Icon name={name} {...props} />
|
|
23
|
+
</Link>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Icon } from '../presentation'
|
|
2
|
+
import { Link } from '../actions'
|
|
3
|
+
|
|
4
|
+
// react-navigation goBack. require() in try/catch so a native app without react-navigation degrades
|
|
5
|
+
// gracefully instead of failing the module load.
|
|
6
|
+
let useNavigation
|
|
7
|
+
try {
|
|
8
|
+
useNavigation = require('@react-navigation/native').useNavigation
|
|
9
|
+
} catch {
|
|
10
|
+
useNavigation = () => ({ goBack: () => console.warn('ReturnLink: @react-navigation/native not installed.') })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// A back/close Link: a Link wrapping an Icon. Defaults to a left-arrow that calls navigation.goBack().
|
|
14
|
+
// `close` swaps to a close icon; `icon` overrides the icon name entirely; `onPress` overrides goBack.
|
|
15
|
+
// Extra props pass to the Icon (size, color, etc.).
|
|
16
|
+
export function ReturnLink({ icon, close, onPress, ...props }) {
|
|
17
|
+
const navigation = useNavigation()
|
|
18
|
+
const name = icon || (close ? 'close-line' : 'arrow-left-s-line')
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Link onPress={onPress || (() => navigation.goBack())}>
|
|
22
|
+
<Icon name={name} {...props} />
|
|
23
|
+
</Link>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ActiveStepContent } from '../steps'
|
|
2
|
+
import { View } from '../structure'
|
|
3
|
+
|
|
4
|
+
// Plain ReactJS (Vite/Next/CRA) fallback — no react-navigation. Renders the active step in place,
|
|
5
|
+
// driven by StepsHandler's activeIndex, so a wizard built with these pieces still works on plain web
|
|
6
|
+
// (just without native stack transitions). React Native / RNW use the .native variant.
|
|
7
|
+
let warned = false
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line no-unused-vars -- absorb the native-only screenOptions so it doesn't leak onto View
|
|
10
|
+
export function RoutedStepsContent({ screenOptions, ...props }) {
|
|
11
|
+
if (!warned) {
|
|
12
|
+
warned = true
|
|
13
|
+
console.warn('RoutedStepsContent is native-only (react-navigation). Rendering steps in place on plain web.')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<View flex {...props}>
|
|
18
|
+
<ActiveStepContent />
|
|
19
|
+
</View>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { View } from '../structure'
|
|
4
|
+
import { useSteps } from '../steps'
|
|
5
|
+
|
|
6
|
+
// Native-only deps. require() inside try/catch keeps web bundlers from pulling react-navigation into
|
|
7
|
+
// the plain ReactJS build (which uses RoutedStepsContent.js instead).
|
|
8
|
+
let NavigationContainer
|
|
9
|
+
let NavigationIndependentTree
|
|
10
|
+
let StackActions
|
|
11
|
+
let createNativeStackNavigator
|
|
12
|
+
let available = true
|
|
13
|
+
try {
|
|
14
|
+
const nav = require('@react-navigation/native')
|
|
15
|
+
NavigationContainer = nav.NavigationContainer
|
|
16
|
+
NavigationIndependentTree = nav.NavigationIndependentTree
|
|
17
|
+
StackActions = nav.StackActions
|
|
18
|
+
createNativeStackNavigator = require('@react-navigation/native-stack').createNativeStackNavigator
|
|
19
|
+
} catch {
|
|
20
|
+
available = false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let warned = false
|
|
24
|
+
|
|
25
|
+
// Renders a step's content exactly like ActiveStepContent — raw, no wrapper. Any scroll/padding is
|
|
26
|
+
// the consumer's call inside their own render/renderContent.
|
|
27
|
+
const stepContent = (item) => {
|
|
28
|
+
const Content = item.render || item.renderContent || item.Content
|
|
29
|
+
return Content ? <Content /> : null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The swapping content region — the routed equivalent of ActiveStepContent. Drop it inside any
|
|
33
|
+
// StepsHandler, alongside whatever fixed chrome you want. It adds NO state of its own: StepsHandler
|
|
34
|
+
// stays the single source of truth. This component just renders a self-contained native stack
|
|
35
|
+
// (independent from the host NavigationContainer) and keeps it in sync with activeIndex both ways.
|
|
36
|
+
export function RoutedStepsContent({ screenOptions, ...props }) {
|
|
37
|
+
const { items, activeIndex, moveToIndex } = useSteps()
|
|
38
|
+
const navRef = React.useRef(null)
|
|
39
|
+
const Stack = React.useRef(available ? createNativeStackNavigator() : null).current
|
|
40
|
+
|
|
41
|
+
// StepsHandler -> stack: when activeIndex changes (Next/Back buttons, StepsMenu, custom controls),
|
|
42
|
+
// drive the stack to the matching screen. Compare against the current TOP route (getCurrentRoute)
|
|
43
|
+
// and be explicit about direction: push forward, pop back. Using navigate() here is wrong — it can
|
|
44
|
+
// push a duplicate instead of popping. If the route already matches (e.g. a native gesture back
|
|
45
|
+
// already moved us), this is a no-op.
|
|
46
|
+
React.useEffect(() => {
|
|
47
|
+
const ref = navRef.current
|
|
48
|
+
if (!ref?.isReady?.()) return
|
|
49
|
+
const current = Number(ref.getCurrentRoute?.()?.name) || 0
|
|
50
|
+
if (activeIndex === current) return
|
|
51
|
+
ref.dispatch(
|
|
52
|
+
activeIndex > current ? StackActions.push(String(activeIndex)) : StackActions.popTo(String(activeIndex))
|
|
53
|
+
)
|
|
54
|
+
}, [activeIndex])
|
|
55
|
+
|
|
56
|
+
if (!available) {
|
|
57
|
+
if (!warned) {
|
|
58
|
+
warned = true
|
|
59
|
+
console.warn(
|
|
60
|
+
'RoutedStepsContent requires @react-navigation/native and @react-navigation/native-stack; neither is installed.'
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// stack -> StepsHandler: native gesture / hardware back changes the route without going through
|
|
67
|
+
// moveToIndex. Push that index back into StepsHandler (backward never validates, matching Steps).
|
|
68
|
+
const handleStateChange = () => {
|
|
69
|
+
const index = Number(navRef.current?.getCurrentRoute?.()?.name) || 0
|
|
70
|
+
if (index !== activeIndex) moveToIndex(index)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const navigator = (
|
|
74
|
+
<Stack.Navigator screenOptions={{ headerShown: false, ...screenOptions }}>
|
|
75
|
+
{items.map((item, index) => (
|
|
76
|
+
<Stack.Screen key={index} name={String(index)} options={{ title: item.label }}>
|
|
77
|
+
{() => stepContent(item)}
|
|
78
|
+
</Stack.Screen>
|
|
79
|
+
))}
|
|
80
|
+
</Stack.Navigator>
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
// Self-contained: its own navigation world, independent of the host app's NavigationContainer, so
|
|
84
|
+
// it never touches the host back stack (react-navigation v7+).
|
|
85
|
+
return (
|
|
86
|
+
<View flex {...props}>
|
|
87
|
+
<NavigationIndependentTree>
|
|
88
|
+
<NavigationContainer ref={navRef} onStateChange={handleStateChange}>
|
|
89
|
+
{navigator}
|
|
90
|
+
</NavigationContainer>
|
|
91
|
+
</NavigationIndependentTree>
|
|
92
|
+
</View>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
@@ -24,7 +24,7 @@ export function StatePresenter({
|
|
|
24
24
|
if (error) {
|
|
25
25
|
return (
|
|
26
26
|
<View flex center {...props}>
|
|
27
|
-
<Result type="error" title={errorTitle} description={errorDescription || error.message} />
|
|
27
|
+
<Result type="error" title={errorTitle} description={errorDescription || (typeof error === 'string' ? error : error.message)} />
|
|
28
28
|
</View>
|
|
29
29
|
)
|
|
30
30
|
}
|
|
@@ -8,6 +8,7 @@ export function StepsHandler({ children, items, onSubmit, onValidateStep, onStep
|
|
|
8
8
|
const [maxIndexReleased, setMaxIndexReleased] = React.useState(0)
|
|
9
9
|
const [loading, setLoading] = React.useState(false)
|
|
10
10
|
const activeStep = items[activeIndex]
|
|
11
|
+
const isFirstStep = activeIndex === 0
|
|
11
12
|
const isLastStep = activeIndex === items.length - 1
|
|
12
13
|
|
|
13
14
|
const moveToIndex = async (index) => {
|
|
@@ -44,6 +45,7 @@ export function StepsHandler({ children, items, onSubmit, onValidateStep, onStep
|
|
|
44
45
|
activeStep,
|
|
45
46
|
maxIndexReleased,
|
|
46
47
|
loading,
|
|
48
|
+
isFirstStep,
|
|
47
49
|
isLastStep,
|
|
48
50
|
moveToNextStep,
|
|
49
51
|
moveToPrevStep,
|
|
@@ -4,14 +4,24 @@ import { Text } from '../text'
|
|
|
4
4
|
import { View } from './View'
|
|
5
5
|
import { useDefaultModifier } from '../../modifiers/default'
|
|
6
6
|
import { useResponsiveConverter } from '../../modifiers/responsiveConverter'
|
|
7
|
-
import { useThemeComponentModifier } from '../../modifiers/themeComponent'
|
|
8
7
|
import { useSafeAreaInsets } from '../../abstractions/helpers/useSafeAreaInsets'
|
|
8
|
+
import { useThemeComponentModifier } from '../../modifiers/themeComponent'
|
|
9
9
|
|
|
10
10
|
const DEFAULT_PROPS = {
|
|
11
11
|
borderB: 'overlayDivider',
|
|
12
|
+
titleProps: {
|
|
13
|
+
center: true,
|
|
14
|
+
size: 'h6',
|
|
15
|
+
numberOfLines: 1,
|
|
16
|
+
},
|
|
17
|
+
subtitleProps: {
|
|
18
|
+
center: true,
|
|
19
|
+
size: 'xs',
|
|
20
|
+
numberOfLines: 1,
|
|
21
|
+
},
|
|
12
22
|
}
|
|
13
23
|
|
|
14
|
-
export function TopBar({ right, left, WrapperView, children, ...rootProps }) {
|
|
24
|
+
export function TopBar({ title, subtitle, right, left, WrapperView, children, ...rootProps }) {
|
|
15
25
|
const { top: safeTop } = useSafeAreaInsets()
|
|
16
26
|
|
|
17
27
|
const [_, props] = pipe(
|
|
@@ -19,7 +29,7 @@ export function TopBar({ right, left, WrapperView, children, ...rootProps }) {
|
|
|
19
29
|
useDefaultModifier(DEFAULT_PROPS),
|
|
20
30
|
useResponsiveConverter([])
|
|
21
31
|
)([{}, rootProps])
|
|
22
|
-
let { useSafeArea = true,
|
|
32
|
+
let { useSafeArea = true, titleProps, subtitleProps } = props
|
|
23
33
|
|
|
24
34
|
const hasContent = !!title || !!subtitle || !!children || !!right || !!left
|
|
25
35
|
|
|
@@ -34,17 +44,9 @@ export function TopBar({ right, left, WrapperView, children, ...rootProps }) {
|
|
|
34
44
|
</View>
|
|
35
45
|
|
|
36
46
|
<View center flex={3}>
|
|
37
|
-
{children ||
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</Text>
|
|
41
|
-
)}
|
|
42
|
-
|
|
43
|
-
{subtitle && (
|
|
44
|
-
<Text center xs numberOfLines={1}>
|
|
45
|
-
{subtitle}
|
|
46
|
-
</Text>
|
|
47
|
-
)}
|
|
47
|
+
{children || <Text {...titleProps}>{title}</Text>}
|
|
48
|
+
|
|
49
|
+
{subtitle && <Text {...subtitleProps}>{subtitle}</Text>}
|
|
48
50
|
</View>
|
|
49
51
|
|
|
50
52
|
<View flex={1} toRight>
|
|
@@ -3,7 +3,7 @@ import { ThemePicker } from './ThemePicker'
|
|
|
3
3
|
|
|
4
4
|
export function ThemePickerDrawer({ open, onClose, onChange }) {
|
|
5
5
|
return (
|
|
6
|
-
<BottomDrawer open={open} onClose={onClose} maxWidth={550} snapPoints={['
|
|
6
|
+
<BottomDrawer open={open} onClose={onClose} maxWidth={550} snapPoints={['60%', '85%']} useSafeArea={false}>
|
|
7
7
|
<DrawerScrollView padding="md">
|
|
8
8
|
<ThemePicker onChange={onChange} />
|
|
9
9
|
</DrawerScrollView>
|