nitro-web 0.0.144 → 0.0.146
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/client/index.ts +5 -5
- package/components/partials/element/calendar.tsx +63 -40
- package/components/partials/element/filters.tsx +98 -69
- package/components/partials/element/table.tsx +40 -53
- package/components/partials/element/timepicker.tsx +119 -0
- package/components/partials/form/field-color.tsx +27 -19
- package/components/partials/form/field-currency.tsx +108 -102
- package/components/partials/form/field-date.tsx +167 -93
- package/components/partials/form/field.tsx +16 -29
- package/components/partials/styleguide.tsx +94 -40
- package/package.json +3 -4
- package/types/util.d.ts +3 -8
- package/types/util.d.ts.map +1 -1
- package/util.js +9 -24
- package/components/partials/form/field-time.tsx +0 -214
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { isValid, format } from 'date-fns'
|
|
2
|
+
import { TZDate } from '@date-fns/tz'
|
|
3
|
+
import { twMerge } from 'nitro-web'
|
|
4
|
+
import { dayButtonClassName } from '../element/calendar'
|
|
5
|
+
|
|
6
|
+
type Timestamp = null | number
|
|
7
|
+
export type TimePickerProps = {
|
|
8
|
+
className?: string
|
|
9
|
+
onChange?: (value: Timestamp) => void
|
|
10
|
+
tz?: string
|
|
11
|
+
value?: Timestamp
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function TimePicker({ value, onChange, className, tz }: TimePickerProps) {
|
|
15
|
+
const refs = {
|
|
16
|
+
hour: useRef<HTMLDivElement>(null),
|
|
17
|
+
minute: useRef<HTMLDivElement>(null),
|
|
18
|
+
period: useRef<HTMLDivElement>(null),
|
|
19
|
+
}
|
|
20
|
+
const lists = [
|
|
21
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // hours
|
|
22
|
+
[
|
|
23
|
+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
|
|
24
|
+
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
|
|
25
|
+
51, 52, 53, 54, 55, 56, 57, 58, 59,
|
|
26
|
+
], // minutes
|
|
27
|
+
['AM', 'PM'], // AM/PM
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
// Convert the value to an valid* date
|
|
31
|
+
const internalValue = useMemo(() => {
|
|
32
|
+
return value && isValid(value) ? new TZDate(value, tz) : undefined
|
|
33
|
+
}, [value])
|
|
34
|
+
|
|
35
|
+
// Get current values from date or use defaults
|
|
36
|
+
const hour = useMemo(() => internalValue ? parseInt(format(internalValue, 'h')) : undefined, [internalValue])
|
|
37
|
+
const minute = useMemo(() => internalValue ? parseInt(format(internalValue, 'm')) : undefined, [internalValue])
|
|
38
|
+
const period = useMemo(() => internalValue ? format(internalValue, 'a') : undefined, [internalValue])
|
|
39
|
+
|
|
40
|
+
// Scroll into view when the date changes
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (hour !== undefined) scrollIntoView('hour', hour)
|
|
43
|
+
if (minute !== undefined) scrollIntoView('minute', minute)
|
|
44
|
+
if (period) scrollIntoView('period', period)
|
|
45
|
+
}, [hour, minute, period])
|
|
46
|
+
|
|
47
|
+
const handleTimeChange = (type: 'hour' | 'minute' | 'period', value: string | number) => {
|
|
48
|
+
// use original internValue or new TZDate, make sure to use the same timezone to base it from
|
|
49
|
+
const _internalValue = internalValue ?? new TZDate(new Date(), tz)
|
|
50
|
+
const isPm = format(_internalValue, 'a') === 'PM'
|
|
51
|
+
if (type === 'hour') {
|
|
52
|
+
const newHour = value === 12 ? 0 : value as number
|
|
53
|
+
_internalValue.setHours(newHour + (isPm ? 12 : 0))
|
|
54
|
+
} else if (type === 'minute') {
|
|
55
|
+
_internalValue.setMinutes(value as number)
|
|
56
|
+
} else if (type === 'period') {
|
|
57
|
+
const newHours = _internalValue.getHours() + (isPm && value === 'AM' ? -12 : !isPm && value === 'PM' ? 12 : 0)
|
|
58
|
+
_internalValue.setHours(newHours)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onChange?.(_internalValue.getTime())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function scrollIntoView (type: 'hour' | 'minute' | 'period', value: string | number) {
|
|
65
|
+
const container = refs[type].current
|
|
66
|
+
if (!container) return
|
|
67
|
+
const element = container.querySelector(`[data-val="${value}"]`) as HTMLElement
|
|
68
|
+
if (!element) return
|
|
69
|
+
|
|
70
|
+
const target =
|
|
71
|
+
element.offsetTop
|
|
72
|
+
- (container.clientHeight / 2)
|
|
73
|
+
+ (element.clientHeight / 2)
|
|
74
|
+
|
|
75
|
+
container.scrollTo({ top: target, behavior: 'smooth' })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className={twMerge('flex justify-center min-h-[250px]', className)}>
|
|
80
|
+
{
|
|
81
|
+
lists.map((list, i) => {
|
|
82
|
+
const type = i === 0 ? 'hour' : i === 1 ? 'minute' : 'period'
|
|
83
|
+
const currentValue = i === 0 ? hour : i === 1 ? minute : period
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
key={i}
|
|
87
|
+
ref={refs[type]}
|
|
88
|
+
className="w-[60px] relative overflow-hidden hover:overflow-y-auto border-l border-gray-100 sm-scrollbar first:border-l-0"
|
|
89
|
+
>
|
|
90
|
+
<div className="w-[60px] absolute flex flex-col items-center py-2">
|
|
91
|
+
{/* using absolute since the scrollbar takes up space */}
|
|
92
|
+
{list.map(item => (
|
|
93
|
+
<div
|
|
94
|
+
className="py-[1px] flex group cursor-pointer"
|
|
95
|
+
data-val={item}
|
|
96
|
+
key={item}
|
|
97
|
+
onClick={(_e) => {
|
|
98
|
+
handleTimeChange(type, item)
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<button
|
|
102
|
+
key={item}
|
|
103
|
+
className={
|
|
104
|
+
`${dayButtonClassName} rounded-full flex justify-center items-center group-hover:bg-gray-100 `
|
|
105
|
+
+ (item === currentValue ? '!bg-input-border-focus text-white' : '')
|
|
106
|
+
}
|
|
107
|
+
>
|
|
108
|
+
{item.toString().padStart(2, '0').toLowerCase()}
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
</div>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
import { hsvaToHex, hexToHsva, validHex, HsvaColor } from '@uiw/color-convert'
|
|
2
2
|
import Saturation from '@uiw/react-color-saturation'
|
|
3
3
|
import Hue from '@uiw/react-color-hue'
|
|
4
|
-
import { Dropdown, util } from 'nitro-web'
|
|
4
|
+
import { currency, Dropdown, util } from 'nitro-web'
|
|
5
5
|
|
|
6
|
-
export type FieldColorProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'|'value'> & {
|
|
6
|
+
export type FieldColorProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value' | 'defaultValue'> & {
|
|
7
7
|
name: string
|
|
8
8
|
/** name is applied if id is not provided */
|
|
9
9
|
id?: string
|
|
10
|
-
defaultColor?: string
|
|
11
10
|
Icon?: React.ReactNode
|
|
12
11
|
onChange?: (event: { target: { name: string, value: string } }) => void
|
|
13
|
-
value?: string
|
|
12
|
+
value?: string // e.g. '#333'
|
|
13
|
+
defaultValue?: string // e.g. '#333'
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function FieldColor({
|
|
17
|
-
const [lastChanged, setLastChanged] = useState(() => `ic-${Date.now()}`)
|
|
16
|
+
export function FieldColor({ defaultValue='#000', Icon, onChange: onChangeProp, ...props }: FieldColorProps) {
|
|
18
17
|
const isInvalid = props.className?.includes('is-invalid') ? 'is-invalid' : ''
|
|
19
18
|
const id = props.id || props.name
|
|
20
19
|
|
|
21
|
-
// Since value and onChange are optional, we need to
|
|
22
|
-
const [internalValue, setInternalValue] = useState(
|
|
23
|
-
const
|
|
24
|
-
const onChange = onChangeProp ?? ((e: { target: { name: string, value: string } }) => setInternalValue(e.target.value))
|
|
20
|
+
// Since value and onChange are optional, we need need to create an internal value state
|
|
21
|
+
const [internalValue, setInternalValue] = useState<FieldColorProps['value']>(props.value ?? defaultValue)
|
|
22
|
+
const inputValue = internalValue
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
// Update the internal value when the value changes outside of the component
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (internalValue !== (props.value ?? defaultValue)) setInternalValue(props.value ?? defaultValue)
|
|
27
|
+
}, [props.value])
|
|
28
|
+
|
|
29
|
+
function onChange(e: { target: { name: string, value: string } }) {
|
|
30
|
+
if (onChangeProp) onChangeProp({ target: { name: props.name, value: e.target.value }})
|
|
31
|
+
else setInternalValue(e.target.value)
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
return (
|
|
@@ -33,7 +36,7 @@ export function FieldColor({ defaultColor='#333', Icon, onChange: onChangeProp,
|
|
|
33
36
|
dir="bottom-left"
|
|
34
37
|
menuToggles={false}
|
|
35
38
|
menuContent={
|
|
36
|
-
<ColorPicker
|
|
39
|
+
<ColorPicker defaultValue={defaultValue} name={props.name} value={internalValue} onChange={onChange} />
|
|
37
40
|
}
|
|
38
41
|
>
|
|
39
42
|
<div className="grid grid-cols-1">
|
|
@@ -42,9 +45,9 @@ export function FieldColor({ defaultColor='#333', Icon, onChange: onChangeProp,
|
|
|
42
45
|
{...props}
|
|
43
46
|
className={(props.className || '') + ' ' + isInvalid}
|
|
44
47
|
id={id}
|
|
45
|
-
value={
|
|
46
|
-
onChange={
|
|
47
|
-
onBlur={() => !validHex(
|
|
48
|
+
value={inputValue}
|
|
49
|
+
onChange={onChange}
|
|
50
|
+
onBlur={() => !validHex(internalValue||'') && onChange({ target: { name: props.name, value: '' }})} // wipe if invalid
|
|
48
51
|
autoComplete="off"
|
|
49
52
|
type="text"
|
|
50
53
|
/>
|
|
@@ -53,10 +56,15 @@ export function FieldColor({ defaultColor='#333', Icon, onChange: onChangeProp,
|
|
|
53
56
|
)
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
function ColorPicker({ name='', onChange, value='',
|
|
57
|
-
const [hsva, setHsva] = useState(() => hexToHsva(validHex(value) ? value :
|
|
59
|
+
function ColorPicker({ name='', onChange, value='', defaultValue='' }: FieldColorProps) {
|
|
60
|
+
const [hsva, setHsva] = useState(() => hexToHsva(validHex(value) ? value : defaultValue))
|
|
58
61
|
const [debounce] = useState(() => util.throttle(callOnChange, 50))
|
|
59
62
|
|
|
63
|
+
// Update the hsva when the internal value changes
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (validHex(value)) setHsva(hexToHsva(value))
|
|
66
|
+
}, [value])
|
|
67
|
+
|
|
60
68
|
function callOnChange(newHsva: HsvaColor) {
|
|
61
69
|
if (onChange) onChange({ target: { name: name, value: hsvaToHex(newHsva) }})
|
|
62
70
|
}
|
|
@@ -15,128 +15,74 @@ type NumericFormatProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
|
|
15
15
|
prefix?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
type Currencies = { [key: string]: { symbol: string, digits: number } }
|
|
19
|
+
type Cents = string|number|null
|
|
20
|
+
export type FieldCurrencyProps = Omit<NumericFormatProps, 'onChange' | 'value' | 'defaultValue'> & {
|
|
19
21
|
name: string
|
|
20
22
|
/** name is applied if id is not provided */
|
|
21
23
|
id?: string
|
|
22
24
|
/** currency iso, e.g. 'nzd' */
|
|
23
25
|
currency: string
|
|
24
26
|
/** override the default currencies array used to lookup currency symbol and digits, e.g. {nzd: { symbol: '$', digits: 2 }} */
|
|
25
|
-
currencies?:
|
|
27
|
+
currencies?: Currencies,
|
|
26
28
|
/** override the default CLDR country currency format, e.g. '¤#,##0.00' */
|
|
27
29
|
format?: string,
|
|
28
|
-
onChange?: (event: { target: { name: string, value:
|
|
30
|
+
onChange?: (event: { target: { name: string, value: Cents } }) => void
|
|
29
31
|
/** value should be in cents */
|
|
30
|
-
value?:
|
|
31
|
-
defaultValue?:
|
|
32
|
+
value?: Cents
|
|
33
|
+
defaultValue?: Cents // defined to just fix typescript error
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
export function FieldCurrency({ currency='nzd', currencies, format, onChange
|
|
36
|
+
export function FieldCurrency({ currency='nzd', currencies, format, onChange: onChangeProp, ...props }: FieldCurrencyProps) {
|
|
35
37
|
const [dontFix, setDontFix] = useState(false)
|
|
36
|
-
const [
|
|
37
|
-
const [
|
|
38
|
+
const [lastBlurred, setLastBlurred] = useState(0)
|
|
39
|
+
const [settings, setSettings] = useState(() => getCurrencySettings(currency, currencies, format, props.name))
|
|
38
40
|
const [prefixWidth, setPrefixWidth] = useState(0)
|
|
39
|
-
const ref = useRef({
|
|
41
|
+
const ref = useRef({ dontFix }) // was null
|
|
40
42
|
const id = props.id || props.name
|
|
41
|
-
ref.current = {
|
|
43
|
+
ref.current = { dontFix }
|
|
42
44
|
|
|
45
|
+
// Since value and onChange are optional, we need need to create an internal value state
|
|
46
|
+
const [internalValue, setInternalValue] = useState<FieldCurrencyProps['value']>(props.value ?? props.defaultValue)
|
|
47
|
+
const inputValue = useMemo(() => to('dollars', internalValue, settings), [internalValue, settings.key, lastBlurred])
|
|
48
|
+
|
|
49
|
+
// Update the internal value when the value changes outside of the component
|
|
43
50
|
useEffect(() => {
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
setSettings(settings)
|
|
47
|
-
setDollars(toDollars(value, true, settings)) // required latest _settings
|
|
48
|
-
}
|
|
49
|
-
}, [currency])
|
|
51
|
+
if (internalValue !== (props.value ?? props.defaultValue)) setInternalValue(props.value ?? props.defaultValue)
|
|
52
|
+
}, [props.value])
|
|
50
53
|
|
|
54
|
+
// Update the settings if the setting parameters change
|
|
51
55
|
useEffect(() => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} else {
|
|
56
|
-
setDollars(toDollars(value, true))
|
|
57
|
-
}
|
|
58
|
-
}, [value])
|
|
56
|
+
const _settings = getCurrencySettings(currency, currencies, format, props.name)
|
|
57
|
+
if (settings.key !== _settings.key) setSettings(_settings)
|
|
58
|
+
}, [currency, currencies, format])
|
|
59
59
|
|
|
60
|
+
// Get the prefix content width
|
|
60
61
|
useEffect(() => {
|
|
61
|
-
// Get the prefix content width
|
|
62
62
|
setPrefixWidth(settings.prefix ? getPrefixWidth(settings.prefix, 1) : 0)
|
|
63
63
|
}, [settings.prefix])
|
|
64
64
|
|
|
65
|
-
function
|
|
66
|
-
const maxDecimals =
|
|
65
|
+
function to(type: 'dollars' | 'cents', value?: Cents, settings?: { maxDecimals?: number }) {
|
|
66
|
+
const maxDecimals = settings?.maxDecimals
|
|
67
67
|
const parsed = parseFloat(value + '')
|
|
68
68
|
if (!parsed && parsed !== 0) return null
|
|
69
69
|
if (!maxDecimals) return parsed
|
|
70
|
-
const value2 = Math.round(parsed * Math.pow(10, maxDecimals)) // e.g. 1.23 => 123
|
|
71
|
-
// console.log('toCents', parsed, value2)
|
|
72
|
-
return value2
|
|
73
|
-
}
|
|
74
70
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!parsed && parsed !== 0) return null
|
|
79
|
-
if (!maxDecimals) return parsed
|
|
80
|
-
const value2 = parsed / Math.pow(10, maxDecimals) // e.g. 1.23 => 123
|
|
81
|
-
// console.log('toDollars', value, value2)
|
|
82
|
-
return toFixed ? value2.toFixed(maxDecimals) : value2
|
|
83
|
-
}
|
|
71
|
+
const value2 = type === 'dollars'
|
|
72
|
+
? parsed / Math.pow(10, maxDecimals)
|
|
73
|
+
: parsed * Math.pow(10, maxDecimals) // e.g. 1.23 => 123
|
|
84
74
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const output: {
|
|
88
|
-
currency: string, // e.g. 'nzd'
|
|
89
|
-
decimalSeparator?: string, // e.g. '.'
|
|
90
|
-
thousandSeparator?: string, // e.g. ','
|
|
91
|
-
minDecimals?: number, // e.g. 2
|
|
92
|
-
maxDecimals?: number, // e.g. 2
|
|
93
|
-
prefix?: string, // e.g. '$'
|
|
94
|
-
suffix?: string // e.g. ''
|
|
95
|
-
} = { currency }
|
|
96
|
-
|
|
97
|
-
let _format = format || defaultFormat
|
|
98
|
-
const _currencies = currencies ?? defaultCurrencies
|
|
99
|
-
const currencyObject = _currencies[currency as keyof typeof _currencies]
|
|
100
|
-
if (!currencyObject && currencies) {
|
|
101
|
-
console.error(
|
|
102
|
-
`The currency field "${props.name}" is using the currency "${currency}" which is not found in your currencies object`
|
|
103
|
-
)
|
|
104
|
-
} else if (!currencyObject && !currencies) {
|
|
105
|
-
console.error(
|
|
106
|
-
`The currency field "${props.name}" is using the currency "${currency}" which is not found in the
|
|
107
|
-
default currencies, please provide a currencies object.`
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
const symbol = currencyObject ? currencyObject.symbol : ''
|
|
111
|
-
const digits = currencyObject ? currencyObject.digits : 2
|
|
112
|
-
|
|
113
|
-
// Check for currency symbol (¤) and determine its position
|
|
114
|
-
if (_format.indexOf('¤') !== -1) {
|
|
115
|
-
const position = _format.indexOf('¤') === 0 ? 'prefix' : 'suffix'
|
|
116
|
-
output[position] = symbol
|
|
117
|
-
_format = _format.replace('¤', '')
|
|
118
|
-
}
|
|
75
|
+
// dont fix when the user is typing.
|
|
76
|
+
if (type === 'dollars' && ref.current.dontFix) { setDontFix(false) }
|
|
119
77
|
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
if (typeof digits !== 'undefined') {
|
|
129
|
-
output.minDecimals = digits
|
|
130
|
-
output.maxDecimals = digits
|
|
131
|
-
} else {
|
|
132
|
-
const fractionDigits = _format.split(output.decimalSeparator)[1]
|
|
133
|
-
if (fractionDigits) {
|
|
134
|
-
output.minDecimals = fractionDigits.length
|
|
135
|
-
output.maxDecimals = fractionDigits.length
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return output
|
|
78
|
+
// console.log('to', type, value, value2)
|
|
79
|
+
return type === 'cents' || ref.current.dontFix ? value2 : value2.toFixed(maxDecimals)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function onChange(source: 'event' | 'prop', floatValue?: number) {
|
|
83
|
+
if (source === 'event') setDontFix(true)
|
|
84
|
+
if (onChangeProp) onChangeProp({ target: { name: props.name, value: to('cents', floatValue, settings) }})
|
|
85
|
+
else setInternalValue(to('cents', floatValue, settings))
|
|
140
86
|
}
|
|
141
87
|
|
|
142
88
|
return (
|
|
@@ -148,20 +94,15 @@ export function FieldCurrency({ currency='nzd', currencies, format, onChange, va
|
|
|
148
94
|
decimalSeparator={settings.decimalSeparator}
|
|
149
95
|
thousandSeparator={settings.thousandSeparator}
|
|
150
96
|
decimalScale={settings.maxDecimals}
|
|
151
|
-
onValueChange={
|
|
152
|
-
|
|
153
|
-
if (e.source === 'event') setDontFix(true)
|
|
154
|
-
onChange({ target: { name: props.name, value: toCents(floatValue) }})
|
|
155
|
-
}}
|
|
156
|
-
onBlur={() => { setDollars(toDollars(value, true))}}
|
|
97
|
+
onValueChange={({ floatValue }, e) => onChange(e.source, floatValue)}
|
|
98
|
+
onBlur={() => { setLastBlurred(Date.now())}}
|
|
157
99
|
placeholder={props.placeholder || '0.00'}
|
|
158
|
-
value={
|
|
100
|
+
value={inputValue}
|
|
159
101
|
style={{ textIndent: `${prefixWidth}px` }}
|
|
160
102
|
type="text"
|
|
161
|
-
defaultValue={defaultValue}
|
|
162
103
|
/>
|
|
163
104
|
<span
|
|
164
|
-
class={`absolute top-0 bottom-0 left-[12px] left-input-x inline-flex items-center select-none text-gray-500 text-input-base ${
|
|
105
|
+
class={`absolute top-0 bottom-0 left-[12px] left-input-x inline-flex items-center select-none text-gray-500 text-input-base ${inputValue !== null && settings.prefix == '$' ? 'text-foreground' : ''}`}
|
|
165
106
|
>
|
|
166
107
|
{settings.prefix || settings.suffix}
|
|
167
108
|
</span>
|
|
@@ -169,6 +110,71 @@ export function FieldCurrency({ currency='nzd', currencies, format, onChange, va
|
|
|
169
110
|
)
|
|
170
111
|
}
|
|
171
112
|
|
|
113
|
+
function getCurrencySettings(currency: string, currencies?: Currencies, format?: string, name?: string) {
|
|
114
|
+
// parse CLDR currency string format, e.g. '¤#,##0.00'
|
|
115
|
+
const output: {
|
|
116
|
+
key?: string
|
|
117
|
+
currency: string, // e.g. 'nzd'
|
|
118
|
+
decimalSeparator?: string, // e.g. '.'
|
|
119
|
+
thousandSeparator?: string, // e.g. ','
|
|
120
|
+
minDecimals?: number, // e.g. 2
|
|
121
|
+
maxDecimals?: number, // e.g. 2
|
|
122
|
+
prefix?: string, // e.g. '$'
|
|
123
|
+
suffix?: string // e.g. ''
|
|
124
|
+
} = { currency }
|
|
125
|
+
|
|
126
|
+
let _format = format || defaultFormat
|
|
127
|
+
const _currencies = currencies ?? defaultCurrencies
|
|
128
|
+
const currencyObject = _currencies[currency as keyof typeof _currencies]
|
|
129
|
+
if (!currencyObject && currencies) {
|
|
130
|
+
console.error(
|
|
131
|
+
`The currency field "${name||''}" is using the currency "${currency}" which is not found in your currencies object`
|
|
132
|
+
)
|
|
133
|
+
} else if (!currencyObject && !currencies) {
|
|
134
|
+
console.error(
|
|
135
|
+
`The currency field "${name||''}" is using the currency "${currency}" which is not found in the
|
|
136
|
+
default currencies, please provide a currencies object.`
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
const symbol = currencyObject ? currencyObject.symbol : ''
|
|
140
|
+
const digits = currencyObject ? currencyObject.digits : 2
|
|
141
|
+
|
|
142
|
+
// Check for currency symbol (¤) and determine its position
|
|
143
|
+
if (_format.indexOf('¤') !== -1) {
|
|
144
|
+
const position = _format.indexOf('¤') === 0 ? 'prefix' : 'suffix'
|
|
145
|
+
output[position] = symbol
|
|
146
|
+
_format = _format.replace('¤', '')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Find and set the thousands separator
|
|
150
|
+
const thousandMatch = _format.match(/[^0-9#]/)
|
|
151
|
+
if (thousandMatch) output.thousandSeparator = thousandMatch[0]
|
|
152
|
+
|
|
153
|
+
// Find and set the decimal separator and fraction digits
|
|
154
|
+
const decimalMatch = _format.match(/0[^0-9]/)
|
|
155
|
+
if (decimalMatch) {
|
|
156
|
+
output.decimalSeparator = decimalMatch[0].slice(1)
|
|
157
|
+
if (typeof digits !== 'undefined') {
|
|
158
|
+
output.minDecimals = digits
|
|
159
|
+
output.maxDecimals = digits
|
|
160
|
+
} else {
|
|
161
|
+
const fractionDigits = _format.split(output.decimalSeparator)[1]
|
|
162
|
+
if (fractionDigits) {
|
|
163
|
+
output.minDecimals = fractionDigits.length
|
|
164
|
+
output.maxDecimals = fractionDigits.length
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// create stable key from final output
|
|
170
|
+
output.key = Object.keys(output)
|
|
171
|
+
.filter(k => k !== 'key')
|
|
172
|
+
.sort()
|
|
173
|
+
.map(k => `${k}:${output[k as keyof typeof output] ?? ''}`)
|
|
174
|
+
.join('|')
|
|
175
|
+
return output
|
|
176
|
+
}
|
|
177
|
+
|
|
172
178
|
export const defaultCurrencies: { [key: string]: { name: string, symbol: string, digits: number } } = {
|
|
173
179
|
nzd: { name: 'New Zealand Dollar', symbol: '$', digits: 2 },
|
|
174
180
|
aud: { name: 'Australian Dollar', symbol: '$', digits: 2 },
|