@consumidor-positivo/aurora 1.2.0 → 1.3.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/README.md +59 -3
- package/astro/AdBox/index.astro +28 -0
- package/astro/Alert/index.astro +257 -0
- package/astro/BadgeInfo/index.astro +55 -0
- package/astro/BadgeState/index.astro +79 -0
- package/astro/Button/index.astro +11 -3
- package/astro/Card/Container/index.astro +44 -0
- package/astro/Card/Emphasis/index.astro +39 -0
- package/astro/Card/Image/index.astro +26 -0
- package/astro/Card/Root/index.astro +53 -0
- package/astro/Card/Tag/index.astro +26 -0
- package/astro/Chip/index.astro +48 -0
- package/astro/ChipBanner/index.astro +160 -0
- package/astro/Container/index.astro +16 -0
- package/astro/Divider/index.astro +27 -0
- package/astro/Image/index.astro +26 -0
- package/astro/LazyImage/index.astro +55 -0
- package/astro/LinkButton/index.astro +54 -0
- package/astro/Logo/ac/BadgeFullWhite.astro +20 -0
- package/astro/Logo/ac/BadgeWhite.astro +23 -0
- package/astro/Logo/ac/BadgeWhiteStroke.astro +27 -0
- package/astro/Logo/ac/BadgeWhiteStrokeDefault.astro +28 -0
- package/astro/Logo/ac/Badget.astro +24 -0
- package/astro/Logo/ac/Primary.astro +28 -0
- package/astro/Logo/ac/PrimaryFillWhite.astro +28 -0
- package/astro/Logo/ac/PrimaryFullWhite.astro +20 -0
- package/astro/Logo/ac/PrimaryNegative.astro +20 -0
- package/astro/Logo/ac/PrimaryWhite.astro +20 -0
- package/astro/Logo/ac/TertiaryWhite.astro +72 -0
- package/astro/Logo/cp/BadgeWhiteStroke.astro +10 -0
- package/astro/Logo/cp/Badget.astro +14 -0
- package/astro/Logo/cp/PrimaryFillWhite.astro +63 -0
- package/astro/Logo/cp/PrimaryFullWhite.astro +27 -0
- package/astro/Logo/cp/PrimaryLogoWhite.astro +67 -0
- package/astro/Logo/cp/PrimaryNegative.astro +27 -0
- package/astro/Logo/cp/PrimaryWhite.astro +27 -0
- package/astro/Modal/index.astro +213 -0
- package/astro/NotificationsBar/Link/index.astro +70 -0
- package/astro/NotificationsBar/List/index.astro +38 -0
- package/astro/NotificationsBar/index.astro +24 -0
- package/astro/PartnerBanner/index.astro +52 -0
- package/astro/ProfileNav/index.astro +34 -0
- package/astro/ProgressBar/index.astro +70 -0
- package/astro/Skeleton/index.astro +50 -0
- package/astro/SpecialButton/index.astro +331 -0
- package/astro/Spinner/index.astro +75 -0
- package/astro/SubHeader/index.astro +57 -0
- package/astro/Switch/Card/index.astro +69 -0
- package/astro/Switch/Pure/index.astro +47 -0
- package/astro/Tooltip/index.astro +38 -0
- package/astro/form/Checkbox/Field/index.astro +68 -0
- package/astro/form/Checkbox/Group/index.astro +79 -0
- package/astro/form/Datepicker/Calendar/index.astro +53 -0
- package/astro/form/Datepicker/CalendarHeader/index.astro +57 -0
- package/astro/form/Datepicker/Segment/index.astro +53 -0
- package/astro/form/Datepicker/index.astro +756 -0
- package/astro/form/EmailField/index.astro +237 -0
- package/astro/form/Field/ErrorMessage/index.astro +20 -0
- package/astro/form/Field/Input/index.astro +15 -0
- package/astro/form/Field/InputHolder/index.astro +28 -0
- package/astro/form/Field/Label/index.astro +80 -0
- package/astro/form/Field/Message/index.astro +29 -0
- package/astro/form/Field/Root/index.astro +37 -0
- package/astro/form/Field/TextArea/index.astro +69 -0
- package/astro/form/InputField/index.astro +96 -0
- package/astro/form/PasswordField/index.astro +123 -0
- package/astro/form/Radio/Field/index.astro +52 -0
- package/astro/form/Radio/Group/index.astro +78 -0
- package/astro/form/SelectField/index.astro +561 -0
- package/astro/form/TextareaField/index.astro +81 -0
- package/astro/form/TokenField/index.astro +211 -0
- package/astro/tokens/package.json +6 -0
- package/dist/{tokens-C4ypB4bY.js → astro/tokens/index.es.js} +101 -101
- package/dist/astro/tokens/index.es.js.map +1 -0
- package/dist/components/Alert/index.es.js +1 -1
- package/dist/components/BadgeInfo/index.es.js +1 -1
- package/dist/components/BadgeState/index.es.js +1 -1
- package/dist/components/Image/index.es.js +1 -1
- package/dist/components/Spinner/index.es.js +1 -1
- package/dist/components/form/Checkbox/Field/index.es.js +1 -1
- package/dist/components/form/Datepicker/Calendar/index.es.js +2 -2
- package/dist/components/form/Datepicker/CalendarHeader/index.es.js +2 -2
- package/dist/components/form/Datepicker/PortalHolder/index.es.js +1 -1
- package/dist/components/form/Datepicker/Segment/index.es.js +1 -1
- package/dist/components/form/Datepicker/index.es.js +3 -3
- package/dist/components/form/Field/Label/index.es.js +1 -1
- package/dist/components/form/SelectField/index.es.js +1 -1
- package/dist/{index-BcC9liUl.js → index-Bkl5OBIY.js} +3 -3
- package/dist/{index-BcC9liUl.js.map → index-Bkl5OBIY.js.map} +1 -1
- package/dist/{index-CFYmtkje.js → index-CZfMkAoF.js} +2 -2
- package/dist/{index-CFYmtkje.js.map → index-CZfMkAoF.js.map} +1 -1
- package/dist/{index-CeClo_WS.js → index-Cw1KtRQr.js} +2 -2
- package/dist/{index-CeClo_WS.js.map → index-Cw1KtRQr.js.map} +1 -1
- package/dist/main.es.js +101 -101
- package/package.json +7 -1
- package/dist/tokens-C4ypB4bY.js.map +0 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import FieldRoot from '../Field/Root/index.astro'
|
|
4
|
+
import FieldLabel from '../Field/Label/index.astro'
|
|
5
|
+
import FieldInputHolder from '../Field/InputHolder/index.astro'
|
|
6
|
+
import FieldInput from '../Field/Input/index.astro'
|
|
7
|
+
import FieldMessage from '../Field/Message/index.astro'
|
|
8
|
+
import '../../../dist/components/EmailField/styles.css'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Same props as the React `EmailField`, and the same split: `class` and
|
|
12
|
+
* `style` go on the `au-field` root, every other HTML attribute (`name`,
|
|
13
|
+
* `value`, `required`, `autocomplete`, `data-*`...) lands on the `<input>`.
|
|
14
|
+
*
|
|
15
|
+
* The domain autocomplete is the component's own behavior, kept by an inline
|
|
16
|
+
* controller with the same domain list and keys as the React hook (ArrowUp,
|
|
17
|
+
* ArrowDown, Enter, Escape). The React `onChange(value)` has no counterpart:
|
|
18
|
+
* listen to the input's native `input` event (also fired when a suggestion is
|
|
19
|
+
* picked) or to `au:emailselect` (`detail.value`, the completed e-mail), which
|
|
20
|
+
* bubbles from the root. `rightSlot` is the `right` named slot; `inputRef` has
|
|
21
|
+
* no counterpart. Like the React version, the input only gets an `id` when one
|
|
22
|
+
* is passed.
|
|
23
|
+
*/
|
|
24
|
+
export type Props = Omit<HTMLAttributes<'input'>, 'type' | 'disabled'> & {
|
|
25
|
+
showOptionalLabel?: boolean
|
|
26
|
+
/** Marks the label as required. Pass `required` too for native validation. */
|
|
27
|
+
requiredInput?: boolean
|
|
28
|
+
success?: boolean
|
|
29
|
+
error?: boolean
|
|
30
|
+
errorMessage?: string
|
|
31
|
+
helpMessage?: string
|
|
32
|
+
label?: string
|
|
33
|
+
disabled?: boolean
|
|
34
|
+
/** Inline style of the `<input>` (`style` goes on the root). */
|
|
35
|
+
inputStyle?: HTMLAttributes<'input'>['style']
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const uid = crypto.randomUUID().slice(0, 8)
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
showOptionalLabel,
|
|
42
|
+
requiredInput,
|
|
43
|
+
success = false,
|
|
44
|
+
error = false,
|
|
45
|
+
errorMessage,
|
|
46
|
+
helpMessage,
|
|
47
|
+
label,
|
|
48
|
+
id: idProp,
|
|
49
|
+
disabled = false,
|
|
50
|
+
style,
|
|
51
|
+
class: className,
|
|
52
|
+
inputStyle,
|
|
53
|
+
...rest
|
|
54
|
+
} = Astro.props
|
|
55
|
+
|
|
56
|
+
const id = idProp
|
|
57
|
+
|
|
58
|
+
// Stands in for the React `useId()` of the listbox.
|
|
59
|
+
const listboxId = `au-email-${uid}-listbox`
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
<FieldRoot
|
|
63
|
+
style={style}
|
|
64
|
+
class={className}
|
|
65
|
+
success={success}
|
|
66
|
+
error={error}
|
|
67
|
+
disabled={disabled}
|
|
68
|
+
data-element="au-email-field">
|
|
69
|
+
<FieldLabel
|
|
70
|
+
text={label}
|
|
71
|
+
id={id}
|
|
72
|
+
showOptionalLabel={showOptionalLabel}
|
|
73
|
+
required={requiredInput}
|
|
74
|
+
success={success}
|
|
75
|
+
error={error}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
/>
|
|
78
|
+
<FieldInputHolder>
|
|
79
|
+
<FieldInput
|
|
80
|
+
id={id}
|
|
81
|
+
type="email"
|
|
82
|
+
disabled={disabled}
|
|
83
|
+
style={inputStyle}
|
|
84
|
+
role="combobox"
|
|
85
|
+
aria-haspopup="listbox"
|
|
86
|
+
aria-expanded="false"
|
|
87
|
+
aria-autocomplete="list"
|
|
88
|
+
aria-controls={listboxId}
|
|
89
|
+
{...rest}
|
|
90
|
+
/>
|
|
91
|
+
<ul
|
|
92
|
+
id={listboxId}
|
|
93
|
+
class="au-field__input-autocomplete"
|
|
94
|
+
tabindex="-1"
|
|
95
|
+
role="listbox"
|
|
96
|
+
style="overflow-y: auto">
|
|
97
|
+
</ul>
|
|
98
|
+
<slot name="right" slot="right" />
|
|
99
|
+
</FieldInputHolder>
|
|
100
|
+
<FieldMessage
|
|
101
|
+
hasError={error}
|
|
102
|
+
errorMessage={errorMessage}
|
|
103
|
+
helpMessage={helpMessage}
|
|
104
|
+
/>
|
|
105
|
+
</FieldRoot>
|
|
106
|
+
|
|
107
|
+
<script>
|
|
108
|
+
import { elementController } from '@consumidor-positivo/aurora/astro/runtime'
|
|
109
|
+
|
|
110
|
+
// Same list, same order, as the React hook (`EmailField/hook.ts`).
|
|
111
|
+
const EMAIL_DOMAINS = [
|
|
112
|
+
'gmail.com',
|
|
113
|
+
'hotmail.com',
|
|
114
|
+
'yahoo.com',
|
|
115
|
+
'outlook.com',
|
|
116
|
+
'yahoo.com.br',
|
|
117
|
+
'uol.com.br',
|
|
118
|
+
'ig.com.br',
|
|
119
|
+
'terra.com.br',
|
|
120
|
+
'aol.com',
|
|
121
|
+
'live.com',
|
|
122
|
+
'msn.com',
|
|
123
|
+
]
|
|
124
|
+
const OPEN_CLASS = 'au-field__input-autocomplete--open'
|
|
125
|
+
const OPTION_CLASS = 'au-field__input-option'
|
|
126
|
+
|
|
127
|
+
elementController('au-email-field', ({ query, on, emit }) => {
|
|
128
|
+
const input = query<HTMLInputElement>('.au-field__input')
|
|
129
|
+
const listbox = query<HTMLUListElement>('.au-field__input-autocomplete')
|
|
130
|
+
if (!input || !listbox) return
|
|
131
|
+
|
|
132
|
+
let suggestions: string[] = []
|
|
133
|
+
let activeIndex = -1
|
|
134
|
+
let isOpen = false
|
|
135
|
+
let isSelecting = false
|
|
136
|
+
|
|
137
|
+
const optionId = (index: number) => `${listbox.id}-option-${index}`
|
|
138
|
+
|
|
139
|
+
function syncActiveDescendant() {
|
|
140
|
+
if (isOpen && activeIndex >= 0) {
|
|
141
|
+
input!.setAttribute('aria-activedescendant', optionId(activeIndex))
|
|
142
|
+
} else {
|
|
143
|
+
input!.removeAttribute('aria-activedescendant')
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function setOpen(open: boolean) {
|
|
148
|
+
isOpen = open
|
|
149
|
+
listbox!.classList.toggle(OPEN_CLASS, open)
|
|
150
|
+
input!.setAttribute('aria-expanded', String(open))
|
|
151
|
+
syncActiveDescendant()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function setActive(index: number) {
|
|
155
|
+
activeIndex = index
|
|
156
|
+
Array.from(listbox!.children).forEach((option, i) => {
|
|
157
|
+
option.setAttribute('aria-selected', String(i === index))
|
|
158
|
+
})
|
|
159
|
+
syncActiveDescendant()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function renderSuggestions(next: string[]) {
|
|
163
|
+
suggestions = next
|
|
164
|
+
activeIndex = -1
|
|
165
|
+
listbox!.replaceChildren(
|
|
166
|
+
...next.map((suggestion, index) => {
|
|
167
|
+
const option = document.createElement('li')
|
|
168
|
+
option.id = optionId(index)
|
|
169
|
+
option.className = OPTION_CLASS
|
|
170
|
+
option.setAttribute('role', 'option')
|
|
171
|
+
option.setAttribute('aria-selected', 'false')
|
|
172
|
+
option.textContent = suggestion
|
|
173
|
+
return option
|
|
174
|
+
}),
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function update(value: string) {
|
|
179
|
+
const atIndex = value.indexOf('@')
|
|
180
|
+
if (atIndex < 0) {
|
|
181
|
+
renderSuggestions([])
|
|
182
|
+
setOpen(false)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const domainPart = value.slice(atIndex + 1)
|
|
187
|
+
const filtered = EMAIL_DOMAINS.filter((domain) => domain.startsWith(domainPart))
|
|
188
|
+
renderSuggestions(filtered)
|
|
189
|
+
const isComplete = filtered.some((domain) => value.endsWith(domain))
|
|
190
|
+
setOpen(filtered.length > 0 && !isComplete)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function select(suggestion: string) {
|
|
194
|
+
const atIndex = input!.value.indexOf('@')
|
|
195
|
+
if (atIndex > -1) {
|
|
196
|
+
const value = input!.value.slice(0, atIndex + 1) + suggestion
|
|
197
|
+
input!.value = value
|
|
198
|
+
// Lets the app's own `input` listeners see the completed value.
|
|
199
|
+
isSelecting = true
|
|
200
|
+
input!.dispatchEvent(new Event('input', { bubbles: true }))
|
|
201
|
+
isSelecting = false
|
|
202
|
+
emit('au:emailselect', { value })
|
|
203
|
+
}
|
|
204
|
+
renderSuggestions([])
|
|
205
|
+
setOpen(false)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
on('input', (event) => {
|
|
209
|
+
if (isSelecting || event.target !== input) return
|
|
210
|
+
update(input.value)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
on('click', (event) => {
|
|
214
|
+
const option = (event.target as Element | null)?.closest<HTMLElement>(
|
|
215
|
+
`.${OPTION_CLASS}`,
|
|
216
|
+
)
|
|
217
|
+
if (option && listbox.contains(option)) select(option.textContent ?? '')
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
on('keydown', (event) => {
|
|
221
|
+
if (event.target !== input || !isOpen || suggestions.length === 0) return
|
|
222
|
+
|
|
223
|
+
if (event.key === 'ArrowDown') {
|
|
224
|
+
event.preventDefault()
|
|
225
|
+
setActive((activeIndex + 1) % suggestions.length)
|
|
226
|
+
} else if (event.key === 'ArrowUp') {
|
|
227
|
+
event.preventDefault()
|
|
228
|
+
setActive(activeIndex <= 0 ? suggestions.length - 1 : activeIndex - 1)
|
|
229
|
+
} else if (event.key === 'Enter' && activeIndex >= 0) {
|
|
230
|
+
event.preventDefault()
|
|
231
|
+
select(suggestions[activeIndex])
|
|
232
|
+
} else if (event.key === 'Escape') {
|
|
233
|
+
setOpen(false)
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
</script>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
import '../../../../dist/components/Field/styles2.css'
|
|
3
|
+
|
|
4
|
+
/** Same props as the React `Field.ErrorMessage`. */
|
|
5
|
+
export type Props = {
|
|
6
|
+
hasError?: boolean
|
|
7
|
+
message?: string
|
|
8
|
+
id?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { hasError, message, id } = Astro.props
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
hasError && message && (
|
|
16
|
+
<p id={id} class="au-field__message au-field__message--error" role="alert">
|
|
17
|
+
{message}
|
|
18
|
+
</p>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import '../../../../dist/components/Field/styles2.css'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The bare `au-field__input`. Every HTML attribute lands on the `<input>`;
|
|
7
|
+
* the React `customclass` is the plain `class`, and `inputRef` has no
|
|
8
|
+
* counterpart (select the element by `id` or a `data-*` attribute).
|
|
9
|
+
*/
|
|
10
|
+
export type Props = HTMLAttributes<'input'>
|
|
11
|
+
|
|
12
|
+
const { class: className, 'class:list': classList, ...rest } = Astro.props
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<input class:list={['au-field__input', className, classList]} {...rest} />
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import '../../../../dist/components/Field/styles2.css'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The React `rightSideSlot` is the `right` named slot here. The
|
|
7
|
+
* `au-field__right-slot` wrapper only renders when that slot has content, so a
|
|
8
|
+
* parent can forward the slot unconditionally.
|
|
9
|
+
*/
|
|
10
|
+
export type Props = HTMLAttributes<'div'>
|
|
11
|
+
|
|
12
|
+
const { class: className, ...rest } = Astro.props
|
|
13
|
+
|
|
14
|
+
const right = Astro.slots.has('right')
|
|
15
|
+
? (await Astro.slots.render('right')).trim()
|
|
16
|
+
: ''
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<div class:list={['au-field__input-holder', className]} {...rest}>
|
|
20
|
+
<slot />
|
|
21
|
+
{
|
|
22
|
+
right && (
|
|
23
|
+
<div class="au-field__right-slot">
|
|
24
|
+
<Fragment set:html={right} />
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import IconAlertOctagon from '../../../icons/default/IconAlertOctagon.astro'
|
|
4
|
+
import IconCheck from '../../../icons/default/IconCheck.astro'
|
|
5
|
+
import IconSlash from '../../../icons/default/IconSlash.astro'
|
|
6
|
+
import {
|
|
7
|
+
COLOR_ERROR_50,
|
|
8
|
+
COLOR_NEUTRAL_40,
|
|
9
|
+
COLOR_SUCCESS_50,
|
|
10
|
+
} from '@consumidor-positivo/aurora/astro/tokens'
|
|
11
|
+
import '../../../../dist/components/Field/styles2.css'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Same props as the React `Field.Label`. `id` is the id of the control the
|
|
15
|
+
* label points to (`for`), not the id of this element. Rich label content,
|
|
16
|
+
* the React `text` as a ReactNode, goes in the default slot; the label renders
|
|
17
|
+
* nothing when neither `text` nor the slot has content. Other HTML attributes
|
|
18
|
+
* land on the `au-field__header` wrapper.
|
|
19
|
+
*/
|
|
20
|
+
export type Props = Omit<HTMLAttributes<'div'>, 'id'> & {
|
|
21
|
+
/** Id of the field control, rendered as the label's `for`. */
|
|
22
|
+
id?: string | null
|
|
23
|
+
/** Id of the `<label>` element itself, for `aria-labelledby`. */
|
|
24
|
+
labelId?: string | null
|
|
25
|
+
text?: string
|
|
26
|
+
required?: boolean
|
|
27
|
+
success?: boolean
|
|
28
|
+
error?: boolean
|
|
29
|
+
disabled?: boolean
|
|
30
|
+
/** Shows "(Opcional)" when the field is not required. Defaults to true. */
|
|
31
|
+
showOptionalLabel?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const {
|
|
35
|
+
id,
|
|
36
|
+
labelId,
|
|
37
|
+
text,
|
|
38
|
+
required = false,
|
|
39
|
+
success = false,
|
|
40
|
+
error = false,
|
|
41
|
+
disabled = false,
|
|
42
|
+
showOptionalLabel = true,
|
|
43
|
+
class: className,
|
|
44
|
+
...rest
|
|
45
|
+
} = Astro.props
|
|
46
|
+
|
|
47
|
+
const slotted = Astro.slots.has('default')
|
|
48
|
+
? (await Astro.slots.render('default')).trim()
|
|
49
|
+
: ''
|
|
50
|
+
const hasText = !!text || !!slotted
|
|
51
|
+
// Same precedence as React: success, then error, then disabled.
|
|
52
|
+
const state = success ? 'success' : error ? 'error' : disabled ? 'disabled' : null
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
{
|
|
56
|
+
hasText && (
|
|
57
|
+
<div class:list={['au-field__header', className]} {...rest}>
|
|
58
|
+
<label id={labelId} for={id} class="au-field__header-label">
|
|
59
|
+
{text}
|
|
60
|
+
<Fragment set:html={slotted} />{' '}
|
|
61
|
+
{showOptionalLabel && !required && (
|
|
62
|
+
<span class="au-field__header-label--optional">(Opcional)</span>
|
|
63
|
+
)}
|
|
64
|
+
{!showOptionalLabel && required && (
|
|
65
|
+
<Fragment>
|
|
66
|
+
<strong class="au-field__header-label--required" aria-hidden="true">
|
|
67
|
+
*
|
|
68
|
+
</strong>
|
|
69
|
+
<span class="au-sr-only">obrigatório</span>
|
|
70
|
+
</Fragment>
|
|
71
|
+
)}
|
|
72
|
+
</label>
|
|
73
|
+
<div class="au-field__header-icon" aria-hidden="true">
|
|
74
|
+
{state === 'success' && <IconCheck rawColor={COLOR_SUCCESS_50} />}
|
|
75
|
+
{state === 'error' && <IconAlertOctagon rawColor={COLOR_ERROR_50} />}
|
|
76
|
+
{state === 'disabled' && <IconSlash rawColor={COLOR_NEUTRAL_40} />}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
import '../../../../dist/components/Field/styles2.css'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Same props as the React `Field.Message`: the error wins over the help text,
|
|
6
|
+
* and nothing renders when neither applies.
|
|
7
|
+
*/
|
|
8
|
+
export type Props = {
|
|
9
|
+
hasError?: boolean
|
|
10
|
+
errorMessage?: string
|
|
11
|
+
helpMessage?: string
|
|
12
|
+
errorId?: string
|
|
13
|
+
helpId?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { hasError, errorMessage, helpMessage, errorId, helpId } = Astro.props
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
hasError && errorMessage ? (
|
|
21
|
+
<p id={errorId} class="au-field__message au-field__message--error" role="alert">
|
|
22
|
+
{errorMessage}
|
|
23
|
+
</p>
|
|
24
|
+
) : helpMessage ? (
|
|
25
|
+
<p id={helpId} class="au-field__message">
|
|
26
|
+
{helpMessage}
|
|
27
|
+
</p>
|
|
28
|
+
) : null
|
|
29
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import '../../../../dist/components/Field/styles2.css'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Same states as the React `Field.Root`. The React `customclass` is the plain
|
|
7
|
+
* `class` here, and every other HTML attribute (`id`, `style`, `data-*`) lands
|
|
8
|
+
* on the root `div`.
|
|
9
|
+
*/
|
|
10
|
+
export type Props = HTMLAttributes<'div'> & {
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
success?: boolean
|
|
13
|
+
error?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
disabled = false,
|
|
18
|
+
success = false,
|
|
19
|
+
error = false,
|
|
20
|
+
class: className,
|
|
21
|
+
'class:list': classList,
|
|
22
|
+
...rest
|
|
23
|
+
} = Astro.props
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
<div
|
|
27
|
+
class:list={[
|
|
28
|
+
'au-field',
|
|
29
|
+
className,
|
|
30
|
+
classList,
|
|
31
|
+
disabled && 'au-field--disabled',
|
|
32
|
+
success && 'au-field--success',
|
|
33
|
+
error && 'au-field--error',
|
|
34
|
+
]}
|
|
35
|
+
{...rest}>
|
|
36
|
+
<slot />
|
|
37
|
+
</div>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import Text from '../../../Text/index.astro'
|
|
4
|
+
import '../../../../dist/components/Field/styles2.css'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Every HTML attribute lands on the `<textarea>`; the React `customClass` is
|
|
8
|
+
* the plain `class`, and `textareaRef` has no counterpart. `value` becomes the
|
|
9
|
+
* textarea's initial content. With `maxLength` the character counter is kept
|
|
10
|
+
* up to date by an inline controller on every `input`; like React, it starts
|
|
11
|
+
* at 0 even when there is an initial value.
|
|
12
|
+
*/
|
|
13
|
+
export type Props = Omit<HTMLAttributes<'textarea'>, 'maxlength'> & {
|
|
14
|
+
/** Allows the user to resize the textarea horizontally. Not mapped in Figma. */
|
|
15
|
+
horizontalResize?: boolean
|
|
16
|
+
/** Native `maxlength`; also turns the `n/max` counter on. */
|
|
17
|
+
maxLength?: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
horizontalResize = false,
|
|
22
|
+
maxLength,
|
|
23
|
+
value,
|
|
24
|
+
class: className,
|
|
25
|
+
'class:list': classList,
|
|
26
|
+
...rest
|
|
27
|
+
} = Astro.props
|
|
28
|
+
|
|
29
|
+
const initialValue = value == null ? '' : String(value)
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
<div
|
|
33
|
+
class="au-field__textarea-container"
|
|
34
|
+
data-element={maxLength ? 'au-field-textarea' : undefined}>
|
|
35
|
+
<textarea
|
|
36
|
+
class:list={[
|
|
37
|
+
'au-field__textarea',
|
|
38
|
+
className,
|
|
39
|
+
classList,
|
|
40
|
+
horizontalResize && 'au-field__textarea--horizontal-resize',
|
|
41
|
+
]}
|
|
42
|
+
maxlength={maxLength}
|
|
43
|
+
{...rest}>{initialValue}</textarea>
|
|
44
|
+
{
|
|
45
|
+
maxLength && (
|
|
46
|
+
<Text variant="caption" class="au-field__char-counter" aria-live="polite">
|
|
47
|
+
{0}/{maxLength}
|
|
48
|
+
</Text>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<script>
|
|
54
|
+
import { elementController } from '@consumidor-positivo/aurora/astro/runtime'
|
|
55
|
+
|
|
56
|
+
elementController('au-field-textarea', ({ query, on }) => {
|
|
57
|
+
const textarea = query<HTMLTextAreaElement>('.au-field__textarea')
|
|
58
|
+
const counter = query('.au-field__char-counter')
|
|
59
|
+
if (!textarea || !counter) return
|
|
60
|
+
|
|
61
|
+
const update = () => {
|
|
62
|
+
counter.textContent = `${textarea.value.length}/${textarea.maxLength}`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Like the React hook: only a change updates the count (not the initial
|
|
66
|
+
// value, not a form reset).
|
|
67
|
+
on('input', update)
|
|
68
|
+
})
|
|
69
|
+
</script>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import FieldRoot from '../Field/Root/index.astro'
|
|
4
|
+
import FieldLabel from '../Field/Label/index.astro'
|
|
5
|
+
import FieldInputHolder from '../Field/InputHolder/index.astro'
|
|
6
|
+
import FieldInput from '../Field/Input/index.astro'
|
|
7
|
+
import FieldMessage from '../Field/Message/index.astro'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Same props as the React `InputField`, and the same split: `class` and
|
|
11
|
+
* `style` go on the `au-field` root, every other HTML attribute (`name`,
|
|
12
|
+
* `value`, `type`, `required`, `placeholder`, `data-*`...) lands on the
|
|
13
|
+
* `<input>`, as the React spreads its input props.
|
|
14
|
+
*
|
|
15
|
+
* Differences from React: `rightSlot` is the `right` named slot and a rich
|
|
16
|
+
* `label` goes in the `label` slot; `inputRef` and the `on*` callbacks have no
|
|
17
|
+
* counterpart (listen on the input through its `id` or a `data-*`). Like
|
|
18
|
+
* React, without `id` the label has no `for` and the input no `id`; the
|
|
19
|
+
* error/help message ids are generated (React's `useId`).
|
|
20
|
+
*/
|
|
21
|
+
export type Props = Omit<HTMLAttributes<'input'>, 'disabled'> & {
|
|
22
|
+
showOptionalLabel?: boolean
|
|
23
|
+
/** Marks the label as required. Pass `required` too for native validation. */
|
|
24
|
+
requiredInput?: boolean
|
|
25
|
+
success?: boolean
|
|
26
|
+
error?: boolean
|
|
27
|
+
errorMessage?: string
|
|
28
|
+
helpMessage?: string
|
|
29
|
+
label?: string
|
|
30
|
+
disabled?: boolean
|
|
31
|
+
/** Inline style of the `<input>` (`style` goes on the root). */
|
|
32
|
+
inputStyle?: HTMLAttributes<'input'>['style']
|
|
33
|
+
/** Opens the numeric keypad on mobile (`inputmode="numeric"`). */
|
|
34
|
+
numericKeypad?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const uid = crypto.randomUUID().slice(0, 8)
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
showOptionalLabel,
|
|
41
|
+
requiredInput,
|
|
42
|
+
success = false,
|
|
43
|
+
error = false,
|
|
44
|
+
errorMessage,
|
|
45
|
+
helpMessage,
|
|
46
|
+
label,
|
|
47
|
+
id,
|
|
48
|
+
disabled = false,
|
|
49
|
+
style,
|
|
50
|
+
class: className,
|
|
51
|
+
inputStyle,
|
|
52
|
+
numericKeypad = false,
|
|
53
|
+
...rest
|
|
54
|
+
} = Astro.props
|
|
55
|
+
|
|
56
|
+
const errorId = error && errorMessage ? `au-field-${uid}-error` : undefined
|
|
57
|
+
const helpId = helpMessage ? `au-field-${uid}-help` : undefined
|
|
58
|
+
const describedBy = [errorId, helpId].filter(Boolean).join(' ') || undefined
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
<FieldRoot
|
|
62
|
+
style={style}
|
|
63
|
+
class={className}
|
|
64
|
+
success={success}
|
|
65
|
+
error={error}
|
|
66
|
+
disabled={disabled}>
|
|
67
|
+
<FieldLabel
|
|
68
|
+
text={label}
|
|
69
|
+
id={id}
|
|
70
|
+
showOptionalLabel={showOptionalLabel}
|
|
71
|
+
required={requiredInput}
|
|
72
|
+
success={success}
|
|
73
|
+
error={error}
|
|
74
|
+
disabled={disabled}>
|
|
75
|
+
<slot name="label" />
|
|
76
|
+
</FieldLabel>
|
|
77
|
+
<FieldInputHolder>
|
|
78
|
+
<FieldInput
|
|
79
|
+
id={id}
|
|
80
|
+
inputmode={numericKeypad ? 'numeric' : undefined}
|
|
81
|
+
disabled={disabled}
|
|
82
|
+
style={inputStyle}
|
|
83
|
+
aria-invalid={error ? 'true' : 'false'}
|
|
84
|
+
aria-describedby={describedBy}
|
|
85
|
+
{...rest}
|
|
86
|
+
/>
|
|
87
|
+
<slot name="right" slot="right" />
|
|
88
|
+
</FieldInputHolder>
|
|
89
|
+
<FieldMessage
|
|
90
|
+
hasError={error}
|
|
91
|
+
errorMessage={errorMessage}
|
|
92
|
+
helpMessage={helpMessage}
|
|
93
|
+
errorId={errorId}
|
|
94
|
+
helpId={helpId}
|
|
95
|
+
/>
|
|
96
|
+
</FieldRoot>
|