nitro-web 0.2.12 → 0.2.13
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.
|
@@ -22,8 +22,14 @@ export type FieldCurrencyProps = Omit<NumericFormatProps, 'onChange' | 'value' |
|
|
|
22
22
|
name: string
|
|
23
23
|
/** name is applied if id is not provided */
|
|
24
24
|
id?: string
|
|
25
|
-
/** currency
|
|
26
|
-
|
|
25
|
+
/** render as a percent field: no currency symbol, '%' suffix */
|
|
26
|
+
percent?: boolean
|
|
27
|
+
/** minimum decimal places to show (percent only; defaults to 0) */
|
|
28
|
+
minDecimals?: number
|
|
29
|
+
/** maximum decimal places to show (percent only; defaults to 2) */
|
|
30
|
+
maxDecimals?: number
|
|
31
|
+
/** currency iso, e.g. 'nzd' (defaults to 'nzd'; ignored when percent) */
|
|
32
|
+
currency?: string
|
|
27
33
|
/** override the default currencies array used to lookup currency symbol and digits, e.g. {nzd: { symbol: '$', digits: 2 }} */
|
|
28
34
|
currencies?: Currencies,
|
|
29
35
|
/** override the default CLDR country currency format, e.g. '¤#,##0.00' */
|
|
@@ -34,10 +40,14 @@ export type FieldCurrencyProps = Omit<NumericFormatProps, 'onChange' | 'value' |
|
|
|
34
40
|
defaultValue?: Cents // defined to just fix typescript error
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
export function FieldCurrency({
|
|
43
|
+
export function FieldCurrency({
|
|
44
|
+
currency='nzd', currencies, format, percent, minDecimals, maxDecimals, onChange: onChangeProp, ...props
|
|
45
|
+
}: FieldCurrencyProps) {
|
|
38
46
|
const [dontFix, setDontFix] = useState(false)
|
|
39
47
|
const [lastBlurred, setLastBlurred] = useState(0)
|
|
40
|
-
const [settings, setSettings] = useState(() =>
|
|
48
|
+
const [settings, setSettings] = useState(() => (
|
|
49
|
+
getCurrencySettings(currency, currencies, format, props.name, percent, minDecimals, maxDecimals)
|
|
50
|
+
))
|
|
41
51
|
const [prefixWidth, setPrefixWidth] = useState(0)
|
|
42
52
|
const [inputPaddingLeft, setInputPaddingLeft] = useState(12)
|
|
43
53
|
const ref = useRef({ dontFix }) // was null
|
|
@@ -64,30 +74,36 @@ export function FieldCurrency({ currency='nzd', currencies, format, onChange: on
|
|
|
64
74
|
|
|
65
75
|
// Update the settings if the setting parameters change
|
|
66
76
|
useEffect(() => {
|
|
67
|
-
const _settings = getCurrencySettings(currency, currencies, format, props.name)
|
|
77
|
+
const _settings = getCurrencySettings(currency, currencies, format, props.name, percent, minDecimals, maxDecimals)
|
|
68
78
|
if (settings.key !== _settings.key) setSettings(_settings)
|
|
69
|
-
}, [currency, currencies, format])
|
|
79
|
+
}, [currency, currencies, format, percent, minDecimals, maxDecimals])
|
|
70
80
|
|
|
71
81
|
// Get the prefix content width
|
|
72
82
|
useEffect(() => {
|
|
73
83
|
setPrefixWidth(settings.prefix ? getPrefixWidth(settings.prefix, 1) : 0)
|
|
74
84
|
}, [settings.prefix])
|
|
75
85
|
|
|
76
|
-
function to(type: 'dollars' | 'cents', value?: Cents, settings?: { maxDecimals?: number }) {
|
|
86
|
+
function to(type: 'dollars' | 'cents', value?: Cents, settings?: { maxDecimals?: number, minDecimals?: number }) {
|
|
77
87
|
const maxDecimals = settings?.maxDecimals
|
|
88
|
+
const minDecimals = Math.min(settings?.minDecimals ?? 0, maxDecimals ?? 0)
|
|
78
89
|
const parsed = parseFloat(value + '')
|
|
79
90
|
if (!parsed && parsed !== 0) return null
|
|
80
91
|
if (!maxDecimals) return parsed
|
|
81
92
|
|
|
82
|
-
const value2 = type === 'dollars'
|
|
83
|
-
? parsed / Math.pow(10, maxDecimals)
|
|
93
|
+
const value2 = type === 'dollars'
|
|
94
|
+
? parsed / Math.pow(10, maxDecimals)
|
|
84
95
|
: parsed * Math.pow(10, maxDecimals) // e.g. 1.23 => 123
|
|
85
96
|
|
|
86
97
|
// dont fix when the user is typing.
|
|
87
98
|
if (type === 'dollars' && ref.current.dontFix) { setDontFix(false) }
|
|
88
99
|
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
if (type === 'cents' || ref.current.dontFix || !minDecimals) return value2
|
|
101
|
+
|
|
102
|
+
// pad to maxDecimals then trim trailing zeros back down to (at least) minDecimals
|
|
103
|
+
const [whole, decimals=''] = value2.toFixed(maxDecimals).split('.')
|
|
104
|
+
let trimmed = decimals
|
|
105
|
+
while (trimmed.length > minDecimals && trimmed.endsWith('0')) trimmed = trimmed.slice(0, -1)
|
|
106
|
+
return trimmed ? `${whole}.${trimmed}` : whole
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
function onChange(source: 'event' | 'prop', floatValue?: number) {
|
|
@@ -96,6 +112,10 @@ export function FieldCurrency({ currency='nzd', currencies, format, onChange: on
|
|
|
96
112
|
else setInternalValue(to('cents', floatValue, settings))
|
|
97
113
|
}
|
|
98
114
|
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
console.log('settings', settings)
|
|
117
|
+
}, [settings])
|
|
118
|
+
|
|
99
119
|
return (
|
|
100
120
|
<div className="relative">
|
|
101
121
|
<NumericFormat
|
|
@@ -110,6 +130,7 @@ export function FieldCurrency({ currency='nzd', currencies, format, onChange: on
|
|
|
110
130
|
onBlur={() => { setLastBlurred(Date.now())}}
|
|
111
131
|
placeholder={props.placeholder || '0.00'}
|
|
112
132
|
value={inputValue}
|
|
133
|
+
suffix={settings.suffix}
|
|
113
134
|
style={{ textIndent: `${prefixWidth}px` }}
|
|
114
135
|
type="text"
|
|
115
136
|
/>
|
|
@@ -117,15 +138,28 @@ export function FieldCurrency({ currency='nzd', currencies, format, onChange: on
|
|
|
117
138
|
class={`absolute top-0 bottom-0 inline-flex items-center select-none text-gray-500 text-input-base ${inputValue !== null && settings.prefix == '$' ? 'text-foreground' : ''}`}
|
|
118
139
|
style={{ left: `${inputPaddingLeft}px` }}
|
|
119
140
|
>
|
|
120
|
-
{settings.prefix
|
|
141
|
+
{settings.prefix}
|
|
121
142
|
</span>
|
|
122
143
|
</div>
|
|
123
144
|
)
|
|
124
145
|
}
|
|
125
146
|
|
|
126
|
-
function getCurrencySettings(
|
|
147
|
+
function getCurrencySettings(
|
|
148
|
+
currency: string, currencies?: Currencies, format?: string, name?: string, percent?: boolean,
|
|
149
|
+
minDecimals?: number, maxDecimals?: number
|
|
150
|
+
) {
|
|
151
|
+
// percent reuses the currency machinery but with a '%' suffix and no currency lookup
|
|
152
|
+
if (percent) {
|
|
153
|
+
const _minDecimals = minDecimals ?? 0
|
|
154
|
+
const _maxDecimals = maxDecimals ?? 2
|
|
155
|
+
return {
|
|
156
|
+
key: `percent:${_minDecimals}:${_maxDecimals}`, currency: currency, decimalSeparator: '.', thousandSeparator: ',',
|
|
157
|
+
minDecimals: _minDecimals, maxDecimals: _maxDecimals, prefix: undefined, suffix: '%',
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
127
161
|
// parse CLDR currency string format, e.g. '¤#,##0.00'
|
|
128
|
-
const output: {
|
|
162
|
+
const output: {
|
|
129
163
|
key?: string
|
|
130
164
|
currency: string, // e.g. 'nzd'
|
|
131
165
|
decimalSeparator?: string, // e.g. '.'
|
|
@@ -9,7 +9,7 @@ import { Errors, type Error } from 'nitro-web/types'
|
|
|
9
9
|
import { MailIcon, CalendarIcon, FunnelIcon, SearchIcon, EyeIcon, EyeOffIcon, ClockIcon, PaperclipIcon } from 'lucide-react'
|
|
10
10
|
import { memo, useState } from 'react'
|
|
11
11
|
|
|
12
|
-
type FieldType = 'text' | 'number' | 'password' | 'email' | 'filter' | 'search' | 'textarea' | 'currency' | 'date' | 'color' | 'file'
|
|
12
|
+
type FieldType = 'text' | 'number' | 'password' | 'email' | 'filter' | 'search' | 'textarea' | 'currency' | 'percent' | 'date' | 'color' | 'file'
|
|
13
13
|
type InputProps = React.InputHTMLAttributes<HTMLInputElement>
|
|
14
14
|
type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
|
|
15
15
|
type FieldExtraProps = {
|
|
@@ -42,6 +42,7 @@ export type FieldProps = (
|
|
|
42
42
|
| ({ type?: 'text' | 'number' | 'password' | 'email' | 'filter' | 'search' } & InputProps & FieldExtraProps)
|
|
43
43
|
| ({ type: 'textarea' } & TextareaProps & FieldExtraProps)
|
|
44
44
|
| ({ type: 'currency' } & FieldCurrencyProps & FieldExtraProps)
|
|
45
|
+
| ({ type: 'percent' } & Omit<FieldCurrencyProps, 'currency'> & FieldExtraProps)
|
|
45
46
|
| ({ type: 'color' } & FieldColorProps & FieldExtraProps)
|
|
46
47
|
| ({ type: 'date' } & FieldDateProps & FieldExtraProps)
|
|
47
48
|
| ({ type: 'file' } & InputProps & FieldExtraProps)
|
|
@@ -140,7 +141,13 @@ function FieldBase({ state, icon, iconPos: ip, errorTitle, inputClassName, ...pr
|
|
|
140
141
|
{Icon}<FieldCurrency {...props} {...commonProps} />
|
|
141
142
|
</FieldContainer>
|
|
142
143
|
)
|
|
143
|
-
} else if (type == '
|
|
144
|
+
} else if (type == 'percent') {
|
|
145
|
+
return (
|
|
146
|
+
<FieldContainer error={error} className={props.className}>
|
|
147
|
+
{Icon}<FieldCurrency {...props} {...commonProps} percent />
|
|
148
|
+
</FieldContainer>
|
|
149
|
+
)
|
|
150
|
+
} else if (type == 'date') {
|
|
144
151
|
return (
|
|
145
152
|
<FieldContainer error={error} className={props.className}>
|
|
146
153
|
<FieldDate {...props} {...commonProps} Icon={Icon} />
|
|
@@ -69,6 +69,7 @@ export function Styleguide({ className, elements, children, currencies, groups }
|
|
|
69
69
|
colorsMulti: ['blue', 'green'],
|
|
70
70
|
country: 'cd',
|
|
71
71
|
currency: 'nzd',
|
|
72
|
+
percent: 1250,
|
|
72
73
|
customer: '1',
|
|
73
74
|
date: Date.now(),
|
|
74
75
|
dateRange: [Date.now(), Date.now() + 1000 * 60 * 60 * 24 * 33],
|
|
@@ -89,7 +90,8 @@ export function Styleguide({ className, elements, children, currencies, groups }
|
|
|
89
90
|
brandColor: '#8656ED',
|
|
90
91
|
colorsMulti: ['blue'],
|
|
91
92
|
country: 'au',
|
|
92
|
-
currency: 'btc',
|
|
93
|
+
currency: 'btc',
|
|
94
|
+
percent: 875,
|
|
93
95
|
date: Date.now() + 1000 * 60 * 60 * 24 * 1.2,
|
|
94
96
|
dateRange: [Date.now(), Date.now() + 1000 * 60 * 60 * 24 * 5.2],
|
|
95
97
|
dateMultiple: [Date.now(), Date.now() + 1000 * 60 * 60 * 24 * 3.2],
|
|
@@ -223,7 +225,7 @@ export function Styleguide({ className, elements, children, currencies, groups }
|
|
|
223
225
|
options={[
|
|
224
226
|
{ label: 'Set Status', onClick: () => { console.log('set', row._id) } },
|
|
225
227
|
{ label: 'Delete', onClick: () => { console.log('remove', row._id) } },
|
|
226
|
-
]}
|
|
228
|
+
]}
|
|
227
229
|
dir={rows.slice(0, perPage).length - 3 < i ? 'top-right' : 'bottom-right'}
|
|
228
230
|
minWidth={100}
|
|
229
231
|
>
|
|
@@ -583,13 +585,20 @@ export function Styleguide({ className, elements, children, currencies, groups }
|
|
|
583
585
|
<div>
|
|
584
586
|
<label for="amount">Amount ({state.amount})</label>
|
|
585
587
|
<Field
|
|
586
|
-
name="amount"
|
|
588
|
+
name="amount"
|
|
589
|
+
type="currency"
|
|
590
|
+
state={state}
|
|
591
|
+
currency={state.currency || 'nzd'}
|
|
587
592
|
// Example of using a custom format and currencies, e.g.
|
|
588
593
|
format={'¤#,##0.00'}
|
|
589
594
|
currencies={currencies}
|
|
590
595
|
onChange={(e) => onChange(e, setState)}
|
|
591
596
|
/>
|
|
592
597
|
</div>
|
|
598
|
+
<div>
|
|
599
|
+
<label for="percent">Percent ({state.percent})</label>
|
|
600
|
+
<Field name="percent" type="percent" state={state} onChange={(e) => onChange(e, setState)} minDecimals={1} maxDecimals={4} />
|
|
601
|
+
</div>
|
|
593
602
|
<div>
|
|
594
603
|
<label for="firstName">First Name (disabled)</label>
|
|
595
604
|
<Field name="firstName" state={state} onChange={(e) => onChange(e, setState)} disabled />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-web",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"repository": "github:boycce/nitro-web",
|
|
5
5
|
"homepage": "https://boycce.github.io/nitro-web/",
|
|
6
6
|
"description": "Nitro is a battle-tested, modular base project to turbocharge your projects, styled using Tailwind 🚀",
|