nitro-web 0.0.86 → 0.0.87

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 (41) hide show
  1. package/client/globals.ts +10 -6
  2. package/package.json +14 -6
  3. package/types/{required-globals.d.ts → globals.d.ts} +3 -1
  4. package/.editorconfig +0 -9
  5. package/components/auth/auth.api.js +0 -411
  6. package/components/auth/reset.tsx +0 -86
  7. package/components/auth/signin.tsx +0 -76
  8. package/components/auth/signup.tsx +0 -62
  9. package/components/billing/stripe.api.js +0 -268
  10. package/components/dashboard/dashboard.tsx +0 -32
  11. package/components/partials/element/accordion.tsx +0 -102
  12. package/components/partials/element/avatar.tsx +0 -40
  13. package/components/partials/element/button.tsx +0 -98
  14. package/components/partials/element/calendar.tsx +0 -125
  15. package/components/partials/element/dropdown.tsx +0 -248
  16. package/components/partials/element/filters.tsx +0 -194
  17. package/components/partials/element/github-link.tsx +0 -16
  18. package/components/partials/element/initials.tsx +0 -66
  19. package/components/partials/element/message.tsx +0 -141
  20. package/components/partials/element/modal.tsx +0 -90
  21. package/components/partials/element/sidebar.tsx +0 -195
  22. package/components/partials/element/tooltip.tsx +0 -154
  23. package/components/partials/element/topbar.tsx +0 -15
  24. package/components/partials/form/checkbox.tsx +0 -150
  25. package/components/partials/form/drop-handler.tsx +0 -68
  26. package/components/partials/form/drop.tsx +0 -141
  27. package/components/partials/form/field-color.tsx +0 -86
  28. package/components/partials/form/field-currency.tsx +0 -158
  29. package/components/partials/form/field-date.tsx +0 -252
  30. package/components/partials/form/field.tsx +0 -231
  31. package/components/partials/form/form-error.tsx +0 -27
  32. package/components/partials/form/location.tsx +0 -225
  33. package/components/partials/form/select.tsx +0 -360
  34. package/components/partials/is-first-render.ts +0 -14
  35. package/components/partials/not-found.tsx +0 -7
  36. package/components/partials/styleguide.tsx +0 -407
  37. package/semver-updater.cjs +0 -13
  38. package/tsconfig.json +0 -38
  39. package/tsconfig.types.json +0 -15
  40. package/types/core-only-globals.d.ts +0 -9
  41. package/types.ts +0 -60
@@ -1,150 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { twMerge, deepFind, getErrorFromState } from 'nitro-web/util'
3
- import { Errors, type Error } from 'nitro-web/types'
4
-
5
- type CheckboxProps = React.InputHTMLAttributes<HTMLInputElement> & {
6
- /** field name or path on state (used to match errors), e.g. 'date', 'company.email' */
7
- name: string
8
- /** name is applied if id is not provided. Used for radios */
9
- id?: string
10
- /** state object to get the value, and check errors against */
11
- state?: { errors?: Errors, [key: string]: any }
12
- size?: number
13
- subtext?: string|React.ReactNode
14
- text?: string|React.ReactNode
15
- type?: 'checkbox' | 'radio' | 'toggle'
16
- checkboxClassName?: string
17
- svgClassName?: string
18
- labelClassName?: string
19
- /** title used to find related error messages */
20
- errorTitle?: string|RegExp
21
- }
22
-
23
- export function Checkbox({
24
- state, size, subtext, text, type='checkbox', className, checkboxClassName, svgClassName, labelClassName, errorTitle, ...props
25
- }: CheckboxProps) {
26
- // Checkbox/radio/toggle component
27
- let value!: boolean
28
- const error = getErrorFromState(state, errorTitle || props.name)
29
- const id = props.id || props.name
30
-
31
- if (!props.name) throw new Error('Checkbox requires a `name` prop')
32
-
33
- // Value: Input is always controlled if state is passed in
34
- if (typeof props.checked !== 'undefined') value = props.checked
35
- else if (typeof state == 'object') {
36
- const v = deepFind(state, props.name) as boolean | undefined
37
- value = v ?? false
38
- }
39
-
40
- const BORDER = 2
41
- const checkboxSize = size ?? 14
42
- const toggleHeight = size ?? 18
43
- const toggleWidth = toggleHeight * 2 - BORDER * 2
44
- const toggleAfterSize = toggleHeight - BORDER * 2
45
-
46
- return (
47
- <div
48
- className={'mt-2.5 mb-6 ' + twMerge(`mt-input-before mb-input-after text-sm nitro-checkbox ${className}`)}
49
- >
50
- <div className="flex gap-3 items-baseline">
51
- <div className="shrink-0 flex items-center">
52
- <div className="w-0">&nbsp;</div>
53
- <div className="group relative">
54
- {
55
- type !== 'toggle'
56
- ? <>
57
- <input
58
- {...props}
59
- id={id}
60
- type={type}
61
- style={{ width: checkboxSize, height: checkboxSize }}
62
- checked={value}
63
- className={
64
- twMerge(
65
- `${type === 'radio' ? 'rounded-full' : 'rounded'} appearance-none border border-gray-300 bg-white forced-colors:appearance-auto disabled:border-gray-300 disabled:bg-gray-100 disabled:checked:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 ` +
66
- // Variable-selected theme colors (was .*-blue-600)
67
- 'checked:border-variable-selected checked:bg-variable-selected indeterminate:border-variable-selected indeterminate:bg-variable-selected focus-visible:outline-variable-selected ' +
68
- // Dark mode not used yet... dark:focus-visible:outline-blue-800
69
- checkboxClassName
70
- )
71
- }
72
- />
73
- <svg
74
- fill="none"
75
- viewBox="0 0 14 14"
76
- style={{ width: checkboxSize, height: checkboxSize }}
77
- className={twMerge('absolute top-0 left-0 pointer-events-none justify-self-center stroke-white group-has-[:disabled]:stroke-gray-950/25', svgClassName)}
78
- >
79
- {
80
- type === 'radio'
81
- ? <circle
82
- // cx={(_size.checkbox.match(/\d+/)?.[0] as unknown as number) / 2}
83
- // cy={(_size.checkbox.match(/\d+/)?.[0] as unknown as number) / 2}
84
- // r={(_size.checkbox.match(/\d+/)?.[0] as unknown as number) / 6}
85
- cx={7}
86
- cy={7}
87
- r={2.5}
88
- className="fill-white opacity-0 group-has-[:checked]:opacity-100"
89
- />
90
- : <>
91
- <path
92
- d="M4 8L6 10L10 4.5"
93
- strokeWidth={2}
94
- strokeLinecap="round"
95
- strokeLinejoin="round"
96
- className="opacity-0 group-has-[:checked]:opacity-100"
97
- />
98
- <path
99
- d="M4 7H10"
100
- strokeWidth={2}
101
- strokeLinecap="round"
102
- strokeLinejoin="round"
103
- className="opacity-0 group-has-[:indeterminate]:opacity-100"
104
- />
105
- </>
106
- }
107
- </svg>
108
- </>
109
- : <>
110
- <input
111
- {...props}
112
- id={id}
113
- type="checkbox"
114
- className="sr-only peer"
115
- checked={value}
116
- />
117
- <label
118
- for={id}
119
- style={{ width: toggleWidth, height: toggleHeight }}
120
- className={
121
- twMerge(
122
- 'block bg-gray-200 rounded-full transition-colors peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 ' +
123
- // Variable-selected theme colors (was .*-blue-600)
124
- 'peer-checked:bg-variable-selected peer-focus-visible:outline-variable-selected ' +
125
- labelClassName
126
- )
127
- }
128
- >
129
- <span
130
- style={{ width: toggleAfterSize, height: toggleAfterSize }}
131
- className={
132
- 'absolute top-[2px] start-[2px] bg-white border-gray-300 border rounded-full transition-all group-has-[:checked]:border-white group-has-[:checked]:translate-x-full '
133
- }
134
- />
135
- </label>
136
- </>
137
- }
138
- </div>
139
- </div>
140
- {text &&
141
- <label for={id} className="text-[length:inherit] leading-[inherit] select-none">
142
- <span className="text-gray-900">{text}</span>
143
- <span className="ml-2 text-gray-500">{subtext}</span>
144
- </label>
145
- }
146
- </div>
147
- {error && <div class="mt-1.5 text-xs text-danger-foreground nitro-error">{error.detail}</div>}
148
- </div>
149
- )
150
- }
@@ -1,68 +0,0 @@
1
- type DropHandlerProps = {
2
- onDrop: (files: FileList) => void
3
- children: React.ReactNode
4
- className?: string
5
- }
6
-
7
- export const DropHandler = ({ onDrop, children, className }: DropHandlerProps) => {
8
- const dropRef = useRef<HTMLDivElement>(null)
9
- let dragCounter = useRef(0).current
10
- const [dragging, setDragging] = useState(false)
11
-
12
- useEffect(() => {
13
- const div = dropRef.current
14
- div?.addEventListener('dragenter', handleDragIn)
15
- div?.addEventListener('dragleave', handleDragOut)
16
- div?.addEventListener('dragover', handleDragOver)
17
- div?.addEventListener('drop', handleDrop)
18
- return () => {
19
- div?.removeEventListener('dragenter', handleDragIn)
20
- div?.removeEventListener('dragleave', handleDragOut)
21
- div?.removeEventListener('dragover', handleDragOver)
22
- div?.removeEventListener('drop', handleDrop)
23
- }
24
- }, [])
25
-
26
- const handleDragIn = (e: DragEvent) => {
27
- e.preventDefault()
28
- e.stopPropagation()
29
- dragCounter++
30
- if (e.dataTransfer?.items && e.dataTransfer.items.length > 0) {
31
- setDragging(true)
32
- }
33
- }
34
-
35
- const handleDragOut = (e: DragEvent) => {
36
- e.preventDefault()
37
- e.stopPropagation()
38
- dragCounter--
39
- if (dragCounter === 0) {
40
- setDragging(false)
41
- }
42
- }
43
-
44
- const handleDragOver = (e: DragEvent) => {
45
- e.preventDefault()
46
- e.stopPropagation()
47
- }
48
-
49
- const handleDrop = (e: DragEvent) => {
50
- e.preventDefault()
51
- e.stopPropagation()
52
- setDragging(false)
53
- if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
54
- onDrop(e.dataTransfer.files)
55
- // e.dataTransfer.clearData() // causes an error in firefox
56
- dragCounter = 0
57
- }
58
- }
59
-
60
- return (
61
- <div
62
- ref={dropRef}
63
- class={`${className} relative w-full p-[20px] border-2 border-dashed border-input-border rounded-md ${dragging ? 'border-primary before:content-[""] before:absolute before:inset-0 before:bg-primary before:opacity-5' : ''}`}
64
- >
65
- {children}
66
- </div>
67
- )
68
- }
@@ -1,141 +0,0 @@
1
- // @ts-nocheck
2
- import { deepFind, s3Image, getErrorFromState } from 'nitro-web/util'
3
- import { DropHandler } from 'nitro-web'
4
- import noImage from 'nitro-web/client/imgs/no-image.svg'
5
- import { Errors, MonasteryImage } from 'nitro-web/types'
6
- import { twMerge } from 'nitro-web/util'
7
-
8
- type DropProps = {
9
- awsUrl?: string
10
- className?: string
11
- /** Field name or path on state (used to match errors), e.g. 'avatar', 'company.avatar' */
12
- name: string
13
- /** Optional ID for the input element. Defaults to name if not provided */
14
- id?: string
15
- /** Called when file is selected or dropped */
16
- onChange?: (event: { target: { name: string, value: File|FileList } }) => void
17
- /** Whether to allow multiple file selection */
18
- multiple?: boolean
19
- /** State object to get the value and check errors against */
20
- state?: {
21
- errors?: Errors
22
- [key: string]: unknown
23
- }
24
- /** title used to find related error messages */
25
- errorTitle?: string|RegExp
26
- /** Props to pass to the input element */
27
- [key: string]: unknown
28
- }
29
-
30
- type Image = File | FileList | MonasteryImage | null
31
-
32
- export function Drop({ awsUrl, className, id, name, onChange, multiple, state, errorTitle, ...props }: DropProps) {
33
- if (!name) throw new Error('Drop component requires a `name` prop')
34
- let value: Image = null
35
- const error = getErrorFromState(state, errorTitle || name)
36
- const inputId = id || name
37
- const [urls, setUrls] = useState([])
38
- const stateRef = useRef(state)
39
- stateRef.current = state
40
-
41
- // Input is always controlled if state is passed in
42
- if (typeof props.value !== 'undefined') value = props.value as Image
43
- else if (typeof state == 'object') value = deepFind(state, name) as Image
44
- if (typeof value == 'undefined') value = null
45
-
46
- useEffect(() => {
47
- (async () => setUrls(await getUrls(value as File | FileList | MonasteryImage | null)))()
48
- }, [value])
49
-
50
- function tryAgain (e: { preventDefault: Function }) {
51
- e.preventDefault()
52
- // clear file input to allow reupload
53
- const input = document.getElementById(name) as HTMLInputElement
54
- if (input) input.value = ''
55
- if (onChange) {
56
- const errors = (stateRef?.current?.errors || []).filter((e: Errors[]) => e?.title != name)
57
- onChange({
58
- // remove file from state
59
- target: { name: name, value: null },
60
- // reset (server) errors
61
- errors: errors.length ? errors : undefined,
62
- })
63
- }
64
- }
65
-
66
- async function onFileAttach (files: FileList) {
67
- // files is a FileList object
68
- if (onChange) onChange({ target: { name: name, value: multiple ? files : files[0] } })
69
- }
70
-
71
- async function getUrls(objectOrFileListItem: File | FileList | MonasteryImage | null) {
72
- /**
73
- * @param {object|FileList} objectOrFileListItem - FileList object or monastery image object
74
- * @returns {Promise} - Resolves to an array of image URLs
75
- */
76
- // Make sure FileLists are converted to a real array
77
- if (!objectOrFileListItem) return []
78
- const array = 'length' in objectOrFileListItem ? Array.from(objectOrFileListItem) : [objectOrFileListItem]
79
- return Promise.all(array.map((item) => {
80
- return new Promise((resolve, reject) => {
81
- if ('lastModified' in item) {
82
- const reader = new FileReader()
83
- reader.onload = () => resolve(reader.result)
84
- reader.onerror = reject
85
- reader.readAsDataURL(item)
86
- } else {
87
- resolve(s3Image(awsUrl, item))
88
- }
89
- })
90
- }))
91
- }
92
-
93
- // function getFilename (objectOrFile) {
94
- // if (objectOrFile.lastModified) return objectOrFile.name
95
- // else return 'avatar.jpg'
96
- // }
97
-
98
- return (
99
- <div class={'mt-2.5 mb-6 ' + twMerge(`mt-input-before mb-input-after nitro-field nitro-drop ${className || ''}`)}>
100
- <input
101
- {...props}
102
- id={inputId}
103
- type="file"
104
- onChange={(e) => onFileAttach(e.target.files as FileList)}
105
- hidden
106
- />
107
- <DropHandler
108
- onDrop={onFileAttach}
109
- className="flex flex-column justify-center items-center text-center gap-2 text-grey-300 text-sm px-8 min-h-[300px]"
110
- >
111
- {
112
- !value &&
113
- <>
114
- {/* {todo upload svg here} */}
115
- <div>
116
- Drag and drop your file here&nbsp;
117
- <label class="weight-500 inline-block text-sm text-primary" for={inputId}>or select a file</label>
118
- </div>
119
- </>
120
- }
121
- {
122
- !!value &&
123
- <>
124
- {
125
- urls.map((url, i) => (
126
- <div key={i} class="flex align-items-center gap-1">
127
- <img src={url || noImage} width="100%" />
128
- </div>
129
- ))
130
- }
131
- <div>
132
- Your file has been added successfully.&nbsp;
133
- <Link to="#" class="text-primary" onClick={tryAgain}>Use another file?</Link>
134
- </div>
135
- </>
136
- }
137
- </DropHandler>
138
- {error && <div class="form-error mt-0-5">{error.detail}</div>}
139
- </div>
140
- )
141
- }
@@ -1,86 +0,0 @@
1
- import { hsvaToHex, hexToHsva, validHex, HsvaColor } from '@uiw/color-convert'
2
- import Saturation from '@uiw/react-color-saturation'
3
- import Hue from '@uiw/react-color-hue'
4
- import { Dropdown, util } from 'nitro-web'
5
-
6
- export type FieldColorProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'|'value'> & {
7
- name: string
8
- /** name is applied if id is not provided */
9
- id?: string
10
- defaultColor?: string
11
- Icon?: React.ReactNode
12
- onChange?: (event: { target: { name: string, value: string } }) => void
13
- value?: string
14
- }
15
-
16
- export function FieldColor({ defaultColor='#333', Icon, onChange: onChangeProp, value: valueProp, ...props }: FieldColorProps) {
17
- const [lastChanged, setLastChanged] = useState(() => `ic-${Date.now()}`)
18
- const isInvalid = props.className?.includes('is-invalid') ? 'is-invalid' : ''
19
- const id = props.id || props.name
20
-
21
- // Since value and onChange are optional, we need to hold the value in state if not provided
22
- const [internalValue, setInternalValue] = useState(valueProp ?? defaultColor)
23
- const value = valueProp ?? internalValue
24
- const onChange = onChangeProp ?? ((e: { target: { name: string, value: string } }) => setInternalValue(e.target.value))
25
-
26
- function onInputChange(e: { target: { name: string, value: string } }) {
27
- setLastChanged(`ic-${Date.now()}`)
28
- onChange(e)
29
- }
30
-
31
- return (
32
- <Dropdown
33
- dir="bottom-left"
34
- menuToggles={false}
35
- menuContent={
36
- <ColorPicker key={lastChanged} defaultColor={defaultColor} name={props.name} value={value} onChange={onChange} />
37
- }
38
- >
39
- <div className="grid grid-cols-1">
40
- {Icon}
41
- <input
42
- {...props}
43
- className={(props.className || '') + ' ' + isInvalid}
44
- id={id}
45
- value={value}
46
- onChange={onInputChange}
47
- onBlur={() => !validHex(value||'') && onInputChange({ target: { name: props.name, value: '' }})}
48
- autoComplete="off"
49
- type="text"
50
- />
51
- </div>
52
- </Dropdown>
53
- )
54
- }
55
-
56
- function ColorPicker({ name='', onChange, value='', defaultColor='' }: FieldColorProps) {
57
- const [hsva, setHsva] = useState(() => hexToHsva(validHex(value) ? value : defaultColor))
58
- const [debounce] = useState(() => util.throttle(callOnChange, 50))
59
-
60
- function callOnChange(newHsva: HsvaColor) {
61
- if (onChange) onChange({ target: { name: name, value: hsvaToHex(newHsva) }})
62
- }
63
-
64
- return (
65
- <>
66
- <Saturation
67
- className="!w-[100%] !h-[150px]"
68
- hsva={hsva}
69
- onChange={(newHsva) => {
70
- setHsva(newHsva)
71
- if (onChange) debounce(newHsva)
72
- }}
73
- />
74
- <Hue
75
- hue={hsva.h}
76
- onChange={(newHue) => {
77
- setHsva({ ...hsva, ...newHue })
78
- if (onChange) debounce({ ...hsva, ...newHue })
79
- }}
80
- />
81
- </>
82
- )
83
- }
84
-
85
-
86
-
@@ -1,158 +0,0 @@
1
- import { NumericFormat } from 'react-number-format'
2
- import { getPrefixWidth } from 'nitro-web/util'
3
-
4
- // Declaring the type here because typescript fails to infer type when referencing NumericFormatProps from react-number-format
5
- type NumericFormatProps = React.InputHTMLAttributes<HTMLInputElement> & {
6
- thousandSeparator?: boolean | string;
7
- decimalSeparator?: string;
8
- allowedDecimalSeparators?: Array<string>;
9
- thousandsGroupStyle?: 'thousand' | 'lakh' | 'wan' | 'none';
10
- decimalScale?: number;
11
- fixedDecimalScale?: boolean;
12
- allowNegative?: boolean;
13
- allowLeadingZeros?: boolean;
14
- suffix?: string;
15
- prefix?: string;
16
- }
17
-
18
- export type FieldCurrencyProps = NumericFormatProps & {
19
- name: string
20
- /** name is applied if id is not provided */
21
- id?: string
22
- /** e.g. { currencies: { nzd: { symbol: '$', digits: 2 } } } (check out the nitro example for more info) */
23
- config: {
24
- currencies: { [key: string]: { symbol: string, digits: number } },
25
- countries: { [key: string]: { numberFormats: { currency: string } } }
26
- }
27
- /** currency iso, e.g. 'nzd' */
28
- currency: string
29
- onChange?: (event: { target: { name: string, value: string|number|null } }) => void
30
- /** value should be in cents */
31
- value?: string|number|null
32
- defaultValue?: number | string | null
33
- }
34
-
35
- export function FieldCurrency({ config, currency='nzd', onChange, value, defaultValue, ...props }: FieldCurrencyProps) {
36
- const [dontFix, setDontFix] = useState(false)
37
- const [settings, setSettings] = useState(() => getCurrencySettings(currency))
38
- const [dollars, setDollars] = useState(() => toDollars(value, true, settings))
39
- const [prefixWidth, setPrefixWidth] = useState(0)
40
- const ref = useRef({ settings, dontFix }) // was null
41
- const id = props.id || props.name
42
- ref.current = { settings, dontFix }
43
-
44
- useEffect(() => {
45
- if (settings.currency !== currency) {
46
- const settings = getCurrencySettings(currency)
47
- setSettings(settings)
48
- setDollars(toDollars(value, true, settings)) // required latest _settings
49
- }
50
- }, [currency])
51
-
52
- useEffect(() => {
53
- if (ref.current.dontFix) {
54
- setDollars(toDollars(value))
55
- setDontFix(false)
56
- } else {
57
- setDollars(toDollars(value, true))
58
- }
59
- }, [value])
60
-
61
-
62
- useEffect(() => {
63
- // Get the prefix content width
64
- setPrefixWidth(settings.prefix == '$' ? getPrefixWidth(settings.prefix, 1) : 0)
65
- }, [settings.prefix])
66
-
67
- function toCents(value?: string|number|null) {
68
- const maxDecimals = ref.current.settings.maxDecimals
69
- const parsed = parseFloat(value + '')
70
- if (!parsed && parsed !== 0) return null
71
- if (!maxDecimals) return parsed
72
- const value2 = Math.round(parsed * Math.pow(10, maxDecimals)) // e.g. 1.23 => 123
73
- // console.log('toCents', parsed, value2)
74
- return value2
75
- }
76
-
77
- function toDollars(value?: string|number|null, toFixed?: boolean, settings?: { maxDecimals?: number }) {
78
- const maxDecimals = (settings || ref.current.settings).maxDecimals
79
- const parsed = parseFloat(value + '')
80
- if (!parsed && parsed !== 0) return null
81
- if (!maxDecimals) return parsed
82
- const value2 = parsed / Math.pow(10, maxDecimals) // e.g. 1.23 => 123
83
- // console.log('toDollars', value, value2)
84
- return toFixed ? value2.toFixed(maxDecimals) : value2
85
- }
86
-
87
- function getCurrencySettings(currency: string) {
88
- // parse CLDR currency string format, e.g. '¤#,##0.00'
89
- const output: {
90
- currency: string, // e.g. 'nzd'
91
- decimalSeparator?: string, // e.g. '.'
92
- thousandSeparator?: string, // e.g. ','
93
- minDecimals?: number, // e.g. 2
94
- maxDecimals?: number, // e.g. 2
95
- prefix?: string, // e.g. '$'
96
- suffix?: string // e.g. ''
97
- } = { currency }
98
- const { symbol, digits } = config.currencies[currency]
99
- let format = config.countries['nz'].numberFormats.currency
100
-
101
- // Check for currency symbol (¤) and determine its position
102
- if (format.indexOf('¤') !== -1) {
103
- const position = format.indexOf('¤') === 0 ? 'prefix' : 'suffix'
104
- output[position] = symbol
105
- format = format.replace('¤', '')
106
- }
107
-
108
- // Find and set the thousands separator
109
- const thousandMatch = format.match(/[^0-9#]/)
110
- if (thousandMatch) output.thousandSeparator = thousandMatch[0]
111
-
112
- // Find and set the decimal separator and fraction digits
113
- const decimalMatch = format.match(/0[^0-9]/)
114
- if (decimalMatch) {
115
- output.decimalSeparator = decimalMatch[0].slice(1)
116
- if (typeof digits !== 'undefined') {
117
- output.minDecimals = digits
118
- output.maxDecimals = digits
119
- } else {
120
- const fractionDigits = format.split(output.decimalSeparator)[1]
121
- if (fractionDigits) {
122
- output.minDecimals = fractionDigits.length
123
- output.maxDecimals = fractionDigits.length
124
- }
125
- }
126
- }
127
- return output
128
- }
129
-
130
- return (
131
- <div className="relative">
132
- <NumericFormat
133
- {...props}
134
- id={id}
135
- name={props.name}
136
- decimalSeparator={settings.decimalSeparator}
137
- thousandSeparator={settings.thousandSeparator}
138
- decimalScale={settings.maxDecimals}
139
- onValueChange={!onChange ? undefined : ({ floatValue }, e) => {
140
- // console.log('onValueChange', floatValue, e)
141
- if (e.source === 'event') setDontFix(true)
142
- onChange({ target: { name: props.name, value: toCents(floatValue) }})
143
- }}
144
- onBlur={() => { setDollars(toDollars(value, true))}}
145
- placeholder={props.placeholder || '0.00'}
146
- value={dollars}
147
- style={{ textIndent: `${prefixWidth}px` }}
148
- type="text"
149
- defaultValue={defaultValue}
150
- />
151
- <span
152
- class={`absolute top-0 bottom-0 left-[12px] left-input-x inline-flex items-center select-none text-gray-500 text-input-base ${dollars !== null && settings.prefix == '$' ? 'text-foreground' : ''}`}
153
- >
154
- {settings.prefix || settings.suffix}
155
- </span>
156
- </div>
157
- )
158
- }