masked-components-cli 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +189 -0
  2. package/dist/cli.js +103 -0
  3. package/package.json +40 -0
  4. package/templates/masked-buttons/MaskedButton/Base/BaseButton.styles.ts +155 -0
  5. package/templates/masked-buttons/MaskedButton/Base/BaseButton.tsx +75 -0
  6. package/templates/masked-buttons/MaskedButton/MaskedButton.tsx +38 -0
  7. package/templates/masked-buttons/MaskedButton/MaskedButton.types.ts +67 -0
  8. package/templates/masked-buttons/MaskedButton/variants/Default/DefaultButton.styles.ts +53 -0
  9. package/templates/masked-buttons/MaskedButton/variants/Default/DefaultButton.tsx +10 -0
  10. package/templates/masked-buttons/MaskedButton/variants/Ghost/GhostButton.styles.ts +52 -0
  11. package/templates/masked-buttons/MaskedButton/variants/Ghost/GhostButton.tsx +10 -0
  12. package/templates/masked-buttons/MaskedButton/variants/Gradient/GradientButton.styles.ts +52 -0
  13. package/templates/masked-buttons/MaskedButton/variants/Gradient/GradientButton.tsx +10 -0
  14. package/templates/masked-buttons/MaskedButton/variants/Link/LinkButton.styles.ts +55 -0
  15. package/templates/masked-buttons/MaskedButton/variants/Link/LinkButton.tsx +10 -0
  16. package/templates/masked-buttons/MaskedButton/variants/Neon/NeonButton.styles.ts +57 -0
  17. package/templates/masked-buttons/MaskedButton/variants/Neon/NeonButton.tsx +10 -0
  18. package/templates/masked-buttons/MaskedButton/variants/Outline/OutlineButton.styles.ts +53 -0
  19. package/templates/masked-buttons/MaskedButton/variants/Outline/OutlineButton.tsx +10 -0
  20. package/templates/masked-buttons/MaskedButton/variants/Toggle/ToggleButton.styles.ts +70 -0
  21. package/templates/masked-buttons/MaskedButton/variants/Toggle/ToggleButton.tsx +15 -0
  22. package/templates/masked-cards/MaskedCards/MaskedCards.styles.ts +0 -0
  23. package/templates/masked-cards/MaskedCards/MaskedCards.tsx +0 -0
  24. package/templates/masked-cards/MaskedCards/MaskedCardsStyles.ts +0 -0
  25. package/templates/masked-input/MaskedInput/FormikMaskedInput.tsx +44 -0
  26. package/templates/masked-input/MaskedInput/MaskedInput.styles.ts +298 -0
  27. package/templates/masked-input/MaskedInput/MaskedInput.tsx +42 -0
  28. package/templates/masked-input/MaskedInput/MaskedInput.types.ts +79 -0
  29. package/templates/masked-input/MaskedInput/formikVariants/FormikCurrencyInput.tsx +13 -0
  30. package/templates/masked-input/MaskedInput/formikVariants/FormikFileInput.tsx +13 -0
  31. package/templates/masked-input/MaskedInput/formikVariants/FormikMaskedTextInput.tsx +13 -0
  32. package/templates/masked-input/MaskedInput/formikVariants/FormikPasswordInput.tsx +13 -0
  33. package/templates/masked-input/MaskedInput/formikVariants/FormikSearchInput.tsx +13 -0
  34. package/templates/masked-input/MaskedInput/formikVariants/FormikSelectInput.tsx +13 -0
  35. package/templates/masked-input/MaskedInput/formikVariants/FormikTextInput.tsx +13 -0
  36. package/templates/masked-input/MaskedInput/formikVariants/FormikTextareaInput.tsx +13 -0
  37. package/templates/masked-input/MaskedInput/variants/CurrencyInput.tsx +100 -0
  38. package/templates/masked-input/MaskedInput/variants/FileInput.tsx +94 -0
  39. package/templates/masked-input/MaskedInput/variants/MaskedInput.tsx +35 -0
  40. package/templates/masked-input/MaskedInput/variants/PasswordInput.tsx +39 -0
  41. package/templates/masked-input/MaskedInput/variants/SearchInput.tsx +47 -0
  42. package/templates/masked-input/MaskedInput/variants/SelectInput.tsx +67 -0
  43. package/templates/masked-input/MaskedInput/variants/TextInput.tsx +34 -0
  44. package/templates/masked-input/MaskedInput/variants/TextareaInput.tsx +33 -0
@@ -0,0 +1,298 @@
1
+ import { transitions } from '@/styles/animations'
2
+ import { theme } from '@/styles/theme'
3
+ import styled from 'styled-components'
4
+
5
+ type props = {
6
+ $variant?: string
7
+ $hasToggle?: boolean
8
+ $radius?: number
9
+ }
10
+
11
+ /* ============================================================
12
+ * CONTAINER
13
+ * ============================================================ */
14
+
15
+ export const MaskedInputContainer = styled.div<props>`
16
+ position: relative;
17
+ width: 100%;
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: 6px;
21
+
22
+ input,
23
+ textarea,
24
+ select,
25
+ button {
26
+ width: 100%;
27
+ padding: 12px;
28
+ border-radius: ${({ $radius }) => ($radius ? `${$radius}px` : '18px')};
29
+ border: 2px solid ${theme.colors.baseBlue.light20};
30
+ font-size: 1rem;
31
+ line-height: 1.4;
32
+ color: ${theme.colors.baseBlue.light30};
33
+ background-color: ${theme.colors.baseBlue.light02};
34
+ z-index: 2;
35
+ text-align: left;
36
+
37
+ ${transitions.slow}
38
+
39
+ ${({ $hasToggle }) => $hasToggle && `padding-right: 44px;`}
40
+
41
+ input.input-hidden {
42
+ position: absolute;
43
+ inset: 0;
44
+ opacity: 0;
45
+ cursor: pointer;
46
+ }
47
+
48
+ /* Hover */
49
+ &:hover {
50
+ border-color: ${theme.colors.baseBlue.base};
51
+ }
52
+
53
+ /* Focus */
54
+ &:focus {
55
+ outline: none;
56
+ background-color: ${theme.colors.baseBlue.light04};
57
+ border-color: ${theme.colors.baseBlue.light};
58
+ box-shadow: 0px 0px 10px 2px ${theme.colors.baseBlue.light};
59
+
60
+ &::placeholder {
61
+ color: ${theme.colors.baseBlue.light40};
62
+ }
63
+ }
64
+
65
+ /* Disabled */
66
+ &:disabled {
67
+ background-color: ${theme.colors.baseBlue.light20};
68
+ cursor: not-allowed;
69
+ opacity: 0.7;
70
+ }
71
+
72
+ /* Error */
73
+ &.error {
74
+ border-color: ${theme.colors.baseRed.base};
75
+ background-color: ${theme.colors.baseRed.light02};
76
+ color: ${theme.colors.baseRed.light20};
77
+
78
+ &:focus {
79
+ box-shadow: 0 0 0 3px ${theme.colors.baseRed.light20};
80
+ }
81
+
82
+ &.error::placeholder {
83
+ color: ${theme.colors.baseRed.light20};
84
+ }
85
+ }
86
+
87
+ &::placeholder {
88
+ color: ${theme.colors.baseBlue.light40};
89
+ }
90
+ }
91
+
92
+ input,
93
+ textarea,
94
+ select {
95
+ min-height: 44px;
96
+ padding: 0 12px;
97
+ line-height: 40px;
98
+ }
99
+
100
+ /* ===================== TEXTAREA ===================== */
101
+
102
+ textarea {
103
+ min-height: 96px;
104
+ width: 100%;
105
+ resize: none;
106
+ scrollbar-width: thin;
107
+ scrollbar-color: ${theme.colors.baseBlue.base} ${theme.colors.baseBlue.light20};
108
+ }
109
+ `
110
+
111
+ /* ============================================================
112
+ * SEARCH ICON
113
+ * ============================================================ */
114
+
115
+ export const SearchIcon = styled.div`
116
+ position: absolute;
117
+ left: 12px;
118
+ top: 42px;
119
+ font-size: 1.3rem;
120
+ color: ${theme.colors.baseBlue.light30};
121
+ pointer-events: none;
122
+ z-index: 3;
123
+ `
124
+
125
+ /* ============================================================
126
+ * PASSWORD TOGGLE
127
+ * ============================================================ */
128
+
129
+ export const PasswordToggle = styled.div`
130
+ position: absolute;
131
+ right: 12px;
132
+ top: 43px;
133
+ background: none;
134
+ border: none;
135
+ cursor: pointer;
136
+ z-index: 2;
137
+ color: ${theme.colors.baseBlue.light};
138
+
139
+ svg {
140
+ font-size: 1.5rem;
141
+ }
142
+ `
143
+
144
+ /* ============================================================
145
+ * FILE PREVIEW
146
+ * ============================================================ */
147
+
148
+ export const PreviewImageDiv = styled.div`
149
+ margin-top: 10px;
150
+ display: grid;
151
+ grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
152
+ gap: 10px;
153
+
154
+ img {
155
+ border-radius: 10px;
156
+ object-fit: cover;
157
+ border: 2px solid ${theme.colors.baseBlue.light20};
158
+ background: ${theme.colors.baseBlue.light02};
159
+ }
160
+ `
161
+
162
+ /* ============================================================
163
+ * FILE BUTTON
164
+ * ============================================================ */
165
+
166
+ export const FileTrigger = styled.button`
167
+ width: 100%;
168
+ height: 40px;
169
+
170
+ display: flex;
171
+ align-items: center;
172
+ justify-content: center;
173
+ gap: 8px;
174
+
175
+ border-radius: 16px;
176
+ border: 2px solid ${theme.colors.baseBlue.light20};
177
+ color: ${theme.colors.baseBlue.light40};
178
+ background-color: ${theme.colors.baseBlue.light02};
179
+ font-size: 0.95rem;
180
+ font-weight: 500;
181
+
182
+ cursor: pointer;
183
+
184
+ transition:
185
+ border-color 0.2s ease,
186
+ background-color 0.2s ease,
187
+ color 0.2s ease;
188
+
189
+ &:hover {
190
+ border-color: ${theme.colors.baseBlue.base};
191
+ background: ${theme.colors.baseBlue.light};
192
+ }
193
+
194
+ &:focus-visible {
195
+ outline: none;
196
+ box-shadow: 0 0 0 3px ${theme.colors.baseBlue.light20};
197
+ }
198
+ `
199
+
200
+ /* ============================================================
201
+ * ERROR
202
+ * ============================================================ */
203
+
204
+ export const ErrorDiv = styled.div`
205
+ color: ${theme.colors.baseRed.light30};
206
+ font-size: 0.85rem;
207
+ font-weight: 500;
208
+ background-color: ${theme.colors.baseRed.light04};
209
+ padding: 6px 12px;
210
+ border-radius: 10px;
211
+ `
212
+
213
+ /* ============================================================
214
+ * LABEL
215
+ * ============================================================ */
216
+ export const InputLabel = styled.label`
217
+ color: ${theme.colors.baseBlue.light30};
218
+ font-size: 18px;
219
+ font-weight: 500;
220
+ margin-bottom: 6px;
221
+ display: flex;
222
+ align-items: center;
223
+ gap: 4px;
224
+
225
+ svg {
226
+ font-size: 20px;
227
+ }
228
+ `
229
+
230
+ /* ============================================================
231
+ * SELECT
232
+ * ============================================================ */
233
+ export const SelectTrigger = styled.button`
234
+ width: 100%;
235
+ padding: 12px 14px;
236
+ border-radius: 16px;
237
+ color: ${theme.colors.baseBlue.light40};
238
+ background-color: ${theme.colors.baseBlue.light02};
239
+ border: 2px solid ${theme.colors.baseBlue.light20};
240
+ display: flex;
241
+ justify-content: space-between;
242
+ align-items: center;
243
+ cursor: pointer;
244
+ `
245
+
246
+ export const SelectDropdown = styled.div`
247
+ position: absolute;
248
+ top: 100%;
249
+ padding: 4px 4px 0px 4px;
250
+ left: 0;
251
+ width: 100%;
252
+ border-radius: 14px;
253
+ overflow: hidden;
254
+ background: ${theme.colors.baseBlue.base};
255
+ z-index: 5;
256
+ `
257
+
258
+ export const SelectOption = styled.div`
259
+ width: 100%;
260
+ padding: 8px;
261
+ border-radius: 14px;
262
+ margin-bottom: 4px;
263
+ background: ${theme.colors.baseBlue.dark20};
264
+ color: ${theme.colors.baseBlue.light50};
265
+ border: 2px solid ${theme.colors.baseBlue.light20};
266
+ display: flex;
267
+ justify-content: space-between;
268
+ align-items: center;
269
+ cursor: pointer;
270
+ font-weight: 800;
271
+
272
+ &:hover {
273
+ background: ${theme.colors.baseBlue.light20};
274
+ color: ${theme.colors.baseBlue.base};
275
+ border-color: ${theme.colors.baseBlue.dark20};
276
+ }
277
+ `
278
+
279
+ /* ============================================================
280
+ * CURRENCY
281
+ * ============================================================ */
282
+ export const CurrencyWrapper = styled.div`
283
+ position: relative;
284
+
285
+ span {
286
+ position: absolute;
287
+ left: 14px;
288
+ top: 50%;
289
+ transform: translateY(-50%);
290
+ font-weight: 600;
291
+ color: ${theme.colors.baseBlue.light30};
292
+ pointer-events: none;
293
+ }
294
+
295
+ input {
296
+ padding-left: 44px;
297
+ }
298
+ `
@@ -0,0 +1,42 @@
1
+ 'use client'
2
+
3
+ import { InputProps } from './MaskedInput.types'
4
+ import { CurrencyInput } from './variants/CurrencyInput'
5
+ import { FileInput } from './variants/FileInput'
6
+ import { MaskedInput } from './variants/MaskedInput'
7
+ import { PasswordInput } from './variants/PasswordInput'
8
+ import { SearchInput } from './variants/SearchInput'
9
+ import { SelectInput } from './variants/SelectInput'
10
+ import { TextareaInput } from './variants/TextareaInput'
11
+ import { TextInput } from './variants/TextInput'
12
+
13
+ export function MInput(props: InputProps) {
14
+ switch (props.variant) {
15
+ case 'default':
16
+ return <TextInput {...props} />
17
+
18
+ case 'textarea':
19
+ return <TextareaInput {...props} />
20
+
21
+ case 'masked':
22
+ return <MaskedInput {...props} />
23
+
24
+ case 'password':
25
+ return <PasswordInput {...props} />
26
+
27
+ case 'select':
28
+ return <SelectInput {...props} />
29
+
30
+ case 'file':
31
+ return <FileInput {...props} />
32
+
33
+ case 'search':
34
+ return <SearchInput {...props} />
35
+
36
+ case 'currency':
37
+ return <CurrencyInput {...props} />
38
+
39
+ default:
40
+ return null
41
+ }
42
+ }
@@ -0,0 +1,79 @@
1
+ import { ReactNode } from 'react'
2
+
3
+ /* ================= BASE ================= */
4
+
5
+ export type BaseProps<T> = {
6
+ name?: string
7
+ value?: T
8
+ onChange?: (value: T) => void
9
+ radius?: number
10
+
11
+ id?: string
12
+ className?: string
13
+ placeholder?: string
14
+
15
+ icon?: ReactNode
16
+ label?: string
17
+ helperText?: string
18
+
19
+ error?: string
20
+ touched?: boolean
21
+ showError?: boolean
22
+ }
23
+
24
+ /* ================= VARIANT MAP ================= */
25
+
26
+ export type InputVariantMap = {
27
+ default: BaseProps<string> & {
28
+ type?: 'text' | 'email' | 'number'
29
+ }
30
+
31
+ textarea: BaseProps<string>
32
+
33
+ password: BaseProps<string>
34
+
35
+ masked: BaseProps<string> & {
36
+ mask: string
37
+ }
38
+
39
+ select: BaseProps<string> & {
40
+ options: { value: string; label: string }[]
41
+ }
42
+
43
+ file: BaseProps<string> & {
44
+ multiple?: boolean
45
+ fileMode?: 'local' | 'cloudinary'
46
+ previewMode?: 'normal' | 'replace'
47
+
48
+ uploadPreset?: string
49
+ cloudName?: string
50
+
51
+ onChange?: (files: File | File[] | string | string[]) => void
52
+ onFileChange?: (payload: { files: File[]; previews: string[] }) => void
53
+ onUploadingChange?: (uploading: boolean) => void
54
+
55
+ error?: string
56
+ touched?: boolean
57
+ showError?: boolean
58
+ }
59
+
60
+ search: BaseProps<string> & {
61
+ placeholder?: string
62
+ onSearch?: (value: string) => void
63
+ }
64
+
65
+ currency: BaseProps<string> & {
66
+ currencyConfig?: {
67
+ locale?: string
68
+ currency?: string
69
+ symbol?: string
70
+ symbolPosition?: 'prefix' | 'suffix'
71
+ }
72
+ }
73
+ }
74
+
75
+ /* ================= UNION AUTOMÁTICA ================= */
76
+
77
+ export type InputProps = {
78
+ [K in keyof InputVariantMap]: { variant: K } & InputVariantMap[K]
79
+ }[keyof InputVariantMap]
@@ -0,0 +1,13 @@
1
+ 'use client'
2
+
3
+ import { useField } from 'formik'
4
+ import { InputVariantMap } from '../MaskedInput.types'
5
+ import { CurrencyInput } from '../variants/CurrencyInput'
6
+
7
+ type Props = { name: string } & InputVariantMap['currency']
8
+
9
+ export function FormikCurrencyInput({ name, ...props }: Props) {
10
+ const [field, meta, helpers] = useField(name)
11
+
12
+ return <CurrencyInput {...props} variant="currency" value={field.value} onChange={helpers.setValue} error={meta.error} touched={meta.touched} />
13
+ }
@@ -0,0 +1,13 @@
1
+ 'use client'
2
+
3
+ import { useField } from 'formik'
4
+ import { InputVariantMap } from '../MaskedInput.types'
5
+ import { FileInput } from '../variants/FileInput'
6
+
7
+ type Props = { name: string } & InputVariantMap['file']
8
+
9
+ export function FormikFileInput({ name, ...props }: Props) {
10
+ const [, , helpers] = useField(name)
11
+
12
+ return <FileInput {...props} variant="file" onChange={helpers.setValue} />
13
+ }
@@ -0,0 +1,13 @@
1
+ 'use client'
2
+
3
+ import { useField } from 'formik'
4
+ import { InputVariantMap } from '../MaskedInput.types'
5
+ import { MaskedInput } from '../variants/MaskedInput'
6
+
7
+ type Props = { name: string } & InputVariantMap['masked']
8
+
9
+ export function FormikMaskedTextInput({ name, ...props }: Props) {
10
+ const [field, meta, helpers] = useField(name)
11
+
12
+ return <MaskedInput {...props} variant="masked" value={field.value} onChange={helpers.setValue} error={meta.error} touched={meta.touched} />
13
+ }
@@ -0,0 +1,13 @@
1
+ 'use client'
2
+
3
+ import { useField } from 'formik'
4
+ import { InputVariantMap } from '../MaskedInput.types'
5
+ import { PasswordInput } from '../variants/PasswordInput'
6
+
7
+ type Props = { name: string } & InputVariantMap['password']
8
+
9
+ export function FormikPasswordInput({ name, ...props }: Props) {
10
+ const [field, meta, helpers] = useField(name)
11
+
12
+ return <PasswordInput {...props} variant="password" value={field.value} onChange={helpers.setValue} error={meta.error} touched={meta.touched} />
13
+ }
@@ -0,0 +1,13 @@
1
+ 'use client'
2
+
3
+ import { useField } from 'formik'
4
+ import { InputVariantMap } from '../MaskedInput.types'
5
+ import { SearchInput } from '../variants/SearchInput'
6
+
7
+ type Props = { name: string } & InputVariantMap['search']
8
+
9
+ export function FormikSearchInput({ name, ...props }: Props) {
10
+ const [field, meta, helpers] = useField(name)
11
+
12
+ return <SearchInput {...props} variant="search" value={field.value} onChange={helpers.setValue} error={meta.error} touched={meta.touched} />
13
+ }
@@ -0,0 +1,13 @@
1
+ 'use client'
2
+
3
+ import { useField } from 'formik'
4
+ import { InputVariantMap } from '../MaskedInput.types'
5
+ import { SelectInput } from '../variants/SelectInput'
6
+
7
+ type Props = { name: string } & InputVariantMap['select']
8
+
9
+ export function FormikSelectInput({ name, ...props }: Props) {
10
+ const [field, meta, helpers] = useField(name)
11
+
12
+ return <SelectInput {...props} variant="select" value={field.value} onChange={helpers.setValue} error={meta.error} touched={meta.touched} />
13
+ }
@@ -0,0 +1,13 @@
1
+ 'use client'
2
+
3
+ import { useField } from 'formik'
4
+ import { InputVariantMap } from '../MaskedInput.types'
5
+ import { TextInput } from '../variants/TextInput'
6
+
7
+ type Props = { name: string } & InputVariantMap['default']
8
+
9
+ export function FormikTextInput({ name, ...props }: Props) {
10
+ const [field, meta, helpers] = useField(name)
11
+
12
+ return <TextInput {...props} variant="default" value={field.value} onChange={helpers.setValue} error={meta.error} touched={meta.touched} />
13
+ }
@@ -0,0 +1,13 @@
1
+ 'use client'
2
+
3
+ import { useField } from 'formik'
4
+ import { InputVariantMap } from '../MaskedInput.types'
5
+ import { TextareaInput } from '../variants/TextareaInput'
6
+
7
+ type Props = { name: string } & InputVariantMap['textarea']
8
+
9
+ export function FormikTextareaInput({ name, ...props }: Props) {
10
+ const [field, meta, helpers] = useField(name)
11
+
12
+ return <TextareaInput {...props} variant="textarea" value={field.value} onChange={helpers.setValue} error={meta.error} touched={meta.touched} />
13
+ }
@@ -0,0 +1,100 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import {
5
+ CurrencyWrapper,
6
+ ErrorDiv,
7
+ InputLabel,
8
+ MaskedInputContainer,
9
+ } from '../MaskedInput.styles'
10
+ import { InputVariantMap } from '../MaskedInput.types'
11
+
12
+ type Props = { variant: 'currency' } & InputVariantMap['currency']
13
+
14
+ export function CurrencyInput(props: Props) {
15
+ const hasError = props.touched && Boolean(props.error)
16
+
17
+ /* ============================================================
18
+ * CONFIGURAÇÕES DE FORMATAÇÃO DE MOEDA
19
+ * ============================================================ */
20
+ const {
21
+ currencyConfig: {
22
+ symbol = 'R$',
23
+ locale = 'pt-BR',
24
+ currency = 'BRL',
25
+ } = {},
26
+ } = props
27
+
28
+ /* ============================================================
29
+ * ESTADO INTERNO (EM CENTAVOS)
30
+ * Inicializa UMA VEZ a partir do value
31
+ * ============================================================ */
32
+ const [cents, setCents] = useState(() =>
33
+ Math.round(Number(props.value ?? 0) * 100)
34
+ )
35
+
36
+ /* ============================================================
37
+ * FORMATADOR
38
+ * ============================================================ */
39
+ function formatCurrencyFromCents(value: number) {
40
+ return new Intl.NumberFormat(locale, {
41
+ currency,
42
+ style: 'decimal',
43
+ minimumFractionDigits: 2,
44
+ maximumFractionDigits: 2,
45
+ }).format(value / 100)
46
+ }
47
+
48
+ /* ============================================================
49
+ * INPUT LOGIC (direita → esquerda)
50
+ * ============================================================ */
51
+ function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
52
+ if (e.key >= '0' && e.key <= '9') {
53
+ e.preventDefault()
54
+ setCents(prev => prev * 10 + Number(e.key))
55
+ return
56
+ }
57
+
58
+ if (e.key === 'Backspace') {
59
+ e.preventDefault()
60
+ setCents(prev => Math.floor(prev / 10))
61
+ }
62
+ }
63
+
64
+ /* ============================================================
65
+ * EMITE VALOR FINAL (ex: "54.85")
66
+ * ============================================================ */
67
+ function handleBlur() {
68
+ props.onChange?.((cents / 100).toFixed(2))
69
+ }
70
+
71
+ return (
72
+ <MaskedInputContainer $variant="currency" $radius={props.radius} data-error={hasError}>
73
+ {props.label && (
74
+ <InputLabel htmlFor={props.id}>
75
+ {props.icon}
76
+ <span>{props.label}</span>
77
+ </InputLabel>
78
+ )}
79
+
80
+ <CurrencyWrapper>
81
+ <span>{symbol}</span>
82
+
83
+ <input
84
+ id={props.id}
85
+ inputMode="numeric"
86
+ value={formatCurrencyFromCents(cents)}
87
+ placeholder="0,00"
88
+ onKeyDown={handleKeyDown}
89
+ onBlur={handleBlur}
90
+ readOnly
91
+ className={hasError ? 'error' : ''}
92
+ aria-invalid={hasError ? 'true' : undefined}
93
+ aria-describedby={hasError ? `${props.id}-error` : undefined}
94
+ />
95
+ </CurrencyWrapper>
96
+
97
+ {props.showError && hasError && <ErrorDiv id={`${props.id}-error`}>{props.error}</ErrorDiv>}
98
+ </MaskedInputContainer>
99
+ )
100
+ }