@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,331 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import IconArrowRight from '../icons/default/IconArrowRight.astro'
|
|
4
|
+
import IconCheck from '../icons/default/IconCheck.astro'
|
|
5
|
+
import IconLoader from '../icons/default/IconLoader.astro'
|
|
6
|
+
import IconSlash from '../icons/default/IconSlash.astro'
|
|
7
|
+
import '../../dist/components/SpecialButton/styles.css'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The React version takes `onConfirm`. Astro has no callback props: the
|
|
11
|
+
* controller emits `au:confirm` from the root (the `<button>` for 'press', the
|
|
12
|
+
* wrapper for 'slider') once the hold finishes or the knob passes 70% of the
|
|
13
|
+
* track, and then puts the component in the success state. The label is the
|
|
14
|
+
* default slot; the slider hint is the `hint` slot (falls back to
|
|
15
|
+
* `incompleteHint`). Every HTML attribute is forwarded to the
|
|
16
|
+
* `.au-special-button` element; `style` is not, because it carries the CSS
|
|
17
|
+
* variables the controller drives.
|
|
18
|
+
*/
|
|
19
|
+
export type Props = Omit<HTMLAttributes<'div'>, 'style'> & {
|
|
20
|
+
/**
|
|
21
|
+
* Interaction mode: 'press' is hold-to-confirm (Figma: Press Button),
|
|
22
|
+
* 'slider' is swipe-to-confirm (Figma: Swipe Button).
|
|
23
|
+
*/
|
|
24
|
+
type?: 'press' | 'slider'
|
|
25
|
+
disabled?: boolean
|
|
26
|
+
loading?: boolean
|
|
27
|
+
/** Renders already confirmed. The component also enters it after a completed interaction. */
|
|
28
|
+
success?: boolean
|
|
29
|
+
/** Press only: time in milliseconds the user must keep pressing to confirm. */
|
|
30
|
+
holdDuration?: number
|
|
31
|
+
/** Press only: stretches the button to fill the container width. */
|
|
32
|
+
expand?: 'x'
|
|
33
|
+
/** Slider only: visual variant (Figma: Swipe Button, ◇ Type Primary | Secondary). */
|
|
34
|
+
variant?: 'primary' | 'secondary'
|
|
35
|
+
/** Slider only: renders an empty skeleton placeholder. */
|
|
36
|
+
skeleton?: boolean
|
|
37
|
+
/**
|
|
38
|
+
* Slider only: accessible label for the draggable knob. Enter or Space on
|
|
39
|
+
* the focused knob confirms directly (keyboard alternative to the swipe).
|
|
40
|
+
*/
|
|
41
|
+
knobAriaLabel?: string
|
|
42
|
+
/** Slider only: text shown below the button after an incomplete drag. */
|
|
43
|
+
incompleteHint?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
type = 'press',
|
|
48
|
+
disabled = false,
|
|
49
|
+
loading = false,
|
|
50
|
+
success = false,
|
|
51
|
+
holdDuration = 1500,
|
|
52
|
+
expand,
|
|
53
|
+
variant = 'primary',
|
|
54
|
+
skeleton = false,
|
|
55
|
+
knobAriaLabel = 'Arraste para confirmar',
|
|
56
|
+
incompleteHint = 'Arraste até o final para confirmar ação',
|
|
57
|
+
class: className,
|
|
58
|
+
...rest
|
|
59
|
+
} = Astro.props
|
|
60
|
+
|
|
61
|
+
const isSlider = type === 'slider'
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
isSlider ? (
|
|
66
|
+
<div class="au-special-button-wrapper" data-element="au-special-button">
|
|
67
|
+
<div
|
|
68
|
+
class:list={[
|
|
69
|
+
'au-special-button',
|
|
70
|
+
'au-special-button--type-slider',
|
|
71
|
+
`au-special-button--variant-${variant}`,
|
|
72
|
+
disabled && 'au-special-button--disabled',
|
|
73
|
+
loading && 'au-special-button--loading',
|
|
74
|
+
success && 'au-special-button--success',
|
|
75
|
+
skeleton && 'au-special-button--skeleton',
|
|
76
|
+
className,
|
|
77
|
+
]}
|
|
78
|
+
{...rest}
|
|
79
|
+
style="--au-special-button-progress: 0">
|
|
80
|
+
{loading && <IconLoader />}
|
|
81
|
+
{!loading && !skeleton && (
|
|
82
|
+
<Fragment>
|
|
83
|
+
<span class="au-special-button__label">
|
|
84
|
+
<slot>Slide to action</slot>
|
|
85
|
+
</span>
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
class="au-special-button__knob"
|
|
89
|
+
aria-label={knobAriaLabel}
|
|
90
|
+
disabled={disabled}>
|
|
91
|
+
{success ? (
|
|
92
|
+
<IconCheck />
|
|
93
|
+
) : disabled ? (
|
|
94
|
+
<IconSlash />
|
|
95
|
+
) : (
|
|
96
|
+
<IconArrowRight />
|
|
97
|
+
)}
|
|
98
|
+
</button>
|
|
99
|
+
</Fragment>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
{!loading && !skeleton && !success && (
|
|
103
|
+
<template data-au-special-button="hint">
|
|
104
|
+
<span class="au-special-button__hint" role="status">
|
|
105
|
+
<slot name="hint">{incompleteHint}</slot>
|
|
106
|
+
</span>
|
|
107
|
+
</template>
|
|
108
|
+
)}
|
|
109
|
+
{!loading && !skeleton && !success && !disabled && (
|
|
110
|
+
<template data-au-special-button="check">
|
|
111
|
+
<IconCheck />
|
|
112
|
+
</template>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
) : (
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
class:list={[
|
|
119
|
+
'au-special-button',
|
|
120
|
+
'au-special-button--type-press',
|
|
121
|
+
disabled && 'au-special-button--disabled',
|
|
122
|
+
loading && 'au-special-button--loading',
|
|
123
|
+
success && 'au-special-button--success',
|
|
124
|
+
expand && `au-special-button--expand-${expand}`,
|
|
125
|
+
className,
|
|
126
|
+
]}
|
|
127
|
+
disabled={disabled || loading}
|
|
128
|
+
aria-busy={loading ? 'true' : 'false'}
|
|
129
|
+
data-element="au-special-button"
|
|
130
|
+
{...rest}
|
|
131
|
+
style={`--au-special-button-duration: ${holdDuration}ms`}>
|
|
132
|
+
<span class="au-special-button__fill" aria-hidden="true" />
|
|
133
|
+
{loading ? (
|
|
134
|
+
<IconLoader />
|
|
135
|
+
) : (
|
|
136
|
+
<span class="au-special-button__label">
|
|
137
|
+
{disabled && <IconSlash />}
|
|
138
|
+
<slot>Hold to confirm</slot>
|
|
139
|
+
</span>
|
|
140
|
+
)}
|
|
141
|
+
</button>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
<script>
|
|
146
|
+
import { elementController } from '@consumidor-positivo/aurora/astro/runtime'
|
|
147
|
+
import type { ControllerContext } from '@consumidor-positivo/aurora/astro/runtime'
|
|
148
|
+
|
|
149
|
+
const KNOB_SIZE = 40
|
|
150
|
+
const TRACK_PADDING = 4
|
|
151
|
+
// Figma (Behavior): from ~70% of the track the action already counts as
|
|
152
|
+
// confirmed and the button completes on its own.
|
|
153
|
+
const CONFIRM_THRESHOLD = 0.7
|
|
154
|
+
const COMPLETE_ANIMATION_MS = 400
|
|
155
|
+
const DEFAULT_HOLD_MS = 1500
|
|
156
|
+
|
|
157
|
+
const CLASS = {
|
|
158
|
+
holding: 'au-special-button--holding',
|
|
159
|
+
dragging: 'au-special-button--dragging',
|
|
160
|
+
completing: 'au-special-button--completing',
|
|
161
|
+
disabled: 'au-special-button--disabled',
|
|
162
|
+
loading: 'au-special-button--loading',
|
|
163
|
+
success: 'au-special-button--success',
|
|
164
|
+
skeleton: 'au-special-button--skeleton',
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const isActivationKey = (key: string) => key === 'Enter' || key === ' '
|
|
168
|
+
|
|
169
|
+
function hasAny(element: Element, ...classes: string[]) {
|
|
170
|
+
return classes.some((name) => element.classList.contains(name))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function setupPress({ root, on, emit }: ControllerContext) {
|
|
174
|
+
// Read back from the CSS variable the markup already carries.
|
|
175
|
+
const holdDuration =
|
|
176
|
+
parseFloat(root.style.getPropertyValue('--au-special-button-duration')) ||
|
|
177
|
+
DEFAULT_HOLD_MS
|
|
178
|
+
let timer: number | undefined
|
|
179
|
+
|
|
180
|
+
const isHolding = () => root.classList.contains(CLASS.holding)
|
|
181
|
+
const isInteractive = () =>
|
|
182
|
+
!hasAny(root, CLASS.disabled, CLASS.loading, CLASS.success)
|
|
183
|
+
|
|
184
|
+
function startHold() {
|
|
185
|
+
if (!isInteractive() || isHolding()) return
|
|
186
|
+
root.classList.add(CLASS.holding)
|
|
187
|
+
timer = window.setTimeout(() => {
|
|
188
|
+
root.classList.remove(CLASS.holding)
|
|
189
|
+
root.classList.add(CLASS.success)
|
|
190
|
+
emit('au:confirm')
|
|
191
|
+
}, holdDuration)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function cancelHold() {
|
|
195
|
+
if (!isHolding()) return
|
|
196
|
+
window.clearTimeout(timer)
|
|
197
|
+
root.classList.remove(CLASS.holding)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
on('pointerdown', startHold)
|
|
201
|
+
on('pointerup', cancelHold)
|
|
202
|
+
on('pointerleave', cancelHold)
|
|
203
|
+
on('pointercancel', cancelHold)
|
|
204
|
+
|
|
205
|
+
on('keydown', (event) => {
|
|
206
|
+
if (!isActivationKey(event.key) || event.repeat) return
|
|
207
|
+
event.preventDefault()
|
|
208
|
+
startHold()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
on('keyup', (event) => {
|
|
212
|
+
if (isActivationKey(event.key)) cancelHold()
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function setupSlider({ root, query, emit }: ControllerContext) {
|
|
217
|
+
const container = query<HTMLElement>('.au-special-button')
|
|
218
|
+
const knob = query<HTMLButtonElement>('.au-special-button__knob')
|
|
219
|
+
const hintTemplate = query<HTMLTemplateElement>(
|
|
220
|
+
'template[data-au-special-button="hint"]',
|
|
221
|
+
)
|
|
222
|
+
const checkTemplate = query<HTMLTemplateElement>(
|
|
223
|
+
'template[data-au-special-button="check"]',
|
|
224
|
+
)
|
|
225
|
+
// React renders the hint and the success icon only when needed: keep
|
|
226
|
+
// them in memory and take the templates out of the page.
|
|
227
|
+
const hintMarkup = hintTemplate?.content.firstElementChild ?? null
|
|
228
|
+
const checkIcon = checkTemplate?.content.firstElementChild ?? null
|
|
229
|
+
hintTemplate?.remove()
|
|
230
|
+
checkTemplate?.remove()
|
|
231
|
+
if (!container || !knob) return
|
|
232
|
+
|
|
233
|
+
let startX = 0
|
|
234
|
+
let dragX = 0
|
|
235
|
+
let hint: Element | null = null
|
|
236
|
+
|
|
237
|
+
const isDragging = () => container.classList.contains(CLASS.dragging)
|
|
238
|
+
const isInteractive = () =>
|
|
239
|
+
!hasAny(
|
|
240
|
+
container,
|
|
241
|
+
CLASS.disabled,
|
|
242
|
+
CLASS.loading,
|
|
243
|
+
CLASS.success,
|
|
244
|
+
CLASS.skeleton,
|
|
245
|
+
CLASS.completing,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
function maxDrag() {
|
|
249
|
+
return container!.clientWidth - KNOB_SIZE - TRACK_PADDING * 2
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function setDragX(value: number) {
|
|
253
|
+
dragX = value
|
|
254
|
+
const max = maxDrag()
|
|
255
|
+
knob!.style.transform = value > 0 ? `translateX(${value}px)` : ''
|
|
256
|
+
container!.style.setProperty(
|
|
257
|
+
'--au-special-button-progress',
|
|
258
|
+
String(max > 0 ? value / max : 0),
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function showHint(isVisible: boolean) {
|
|
263
|
+
if (!isVisible) {
|
|
264
|
+
hint?.remove()
|
|
265
|
+
hint = null
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
if (hint || !hintMarkup) return
|
|
269
|
+
hint = hintMarkup.cloneNode(true) as Element
|
|
270
|
+
root.append(hint)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function confirm() {
|
|
274
|
+
container!.classList.add(CLASS.success)
|
|
275
|
+
showHint(false)
|
|
276
|
+
if (checkIcon) knob!.replaceChildren(checkIcon.cloneNode(true))
|
|
277
|
+
emit('au:confirm')
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function complete() {
|
|
281
|
+
container!.classList.remove(CLASS.dragging)
|
|
282
|
+
container!.classList.add(CLASS.completing)
|
|
283
|
+
showHint(false)
|
|
284
|
+
setDragX(maxDrag())
|
|
285
|
+
window.setTimeout(() => {
|
|
286
|
+
container!.classList.remove(CLASS.completing)
|
|
287
|
+
setDragX(0)
|
|
288
|
+
confirm()
|
|
289
|
+
}, COMPLETE_ANIMATION_MS)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function endDrag() {
|
|
293
|
+
if (!isDragging()) return
|
|
294
|
+
container!.classList.remove(CLASS.dragging)
|
|
295
|
+
if (dragX > 0) showHint(true)
|
|
296
|
+
setDragX(0)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
knob.addEventListener('pointerdown', (event) => {
|
|
300
|
+
if (!isInteractive()) return
|
|
301
|
+
container.classList.add(CLASS.dragging)
|
|
302
|
+
startX = event.clientX
|
|
303
|
+
knob.setPointerCapture?.(event.pointerId)
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
knob.addEventListener('pointermove', (event) => {
|
|
307
|
+
if (!isDragging()) return
|
|
308
|
+
const max = maxDrag()
|
|
309
|
+
const next = Math.min(Math.max(event.clientX - startX, 0), max)
|
|
310
|
+
setDragX(next)
|
|
311
|
+
if (max > 0 && next >= max * CONFIRM_THRESHOLD) complete()
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
knob.addEventListener('pointerup', endDrag)
|
|
315
|
+
knob.addEventListener('pointercancel', endDrag)
|
|
316
|
+
|
|
317
|
+
knob.addEventListener('keydown', (event) => {
|
|
318
|
+
if (!isInteractive() || !isActivationKey(event.key)) return
|
|
319
|
+
event.preventDefault()
|
|
320
|
+
confirm()
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
elementController('au-special-button', (context) => {
|
|
325
|
+
if (context.root.classList.contains('au-special-button--type-press')) {
|
|
326
|
+
setupPress(context)
|
|
327
|
+
} else {
|
|
328
|
+
setupSlider(context)
|
|
329
|
+
}
|
|
330
|
+
})
|
|
331
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import { COLOR_BRAND_BLUE_40, COLOR_NEUTRAL_00 } from '@consumidor-positivo/aurora/astro/tokens'
|
|
4
|
+
import '../../dist/components/Spinner/styles.css'
|
|
5
|
+
|
|
6
|
+
const SIZE_MAP = {
|
|
7
|
+
small: 16,
|
|
8
|
+
medium: 20,
|
|
9
|
+
large: 32,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Same props as React; every HTML attribute is forwarded to the root `div`.
|
|
14
|
+
* The size is an inline `style`, so a `style` passed here is ignored.
|
|
15
|
+
*/
|
|
16
|
+
export type Props = Omit<HTMLAttributes<'div'>, 'style'> & {
|
|
17
|
+
/**
|
|
18
|
+
* Token size (Figma: Spinner, ↕ Size). small = 16px, medium = 20px, large = 32px.
|
|
19
|
+
*/
|
|
20
|
+
size?: 'small' | 'medium' | 'large'
|
|
21
|
+
/** White spinner for colored backgrounds (Figma: Negative). */
|
|
22
|
+
negative?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
size = 'medium',
|
|
27
|
+
negative = false,
|
|
28
|
+
class: className,
|
|
29
|
+
...rest
|
|
30
|
+
} = Astro.props
|
|
31
|
+
|
|
32
|
+
// Per instance, like React's `useId`: two spinners on the same page must not
|
|
33
|
+
// share the gradient.
|
|
34
|
+
const gradientId = `au-spinner-gradient-${crypto.randomUUID().slice(0, 8)}`
|
|
35
|
+
const px = SIZE_MAP[size]
|
|
36
|
+
const color = negative ? COLOR_NEUTRAL_00 : COLOR_BRAND_BLUE_40
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<div
|
|
40
|
+
class:list={[
|
|
41
|
+
'au-spinner',
|
|
42
|
+
`au-spinner--size-${size}`,
|
|
43
|
+
negative && 'au-spinner--negative',
|
|
44
|
+
className,
|
|
45
|
+
]}
|
|
46
|
+
role="status"
|
|
47
|
+
aria-live="polite"
|
|
48
|
+
aria-label="Carregando"
|
|
49
|
+
{...rest}
|
|
50
|
+
style={`width: ${px}px; height: ${px}px`}>
|
|
51
|
+
<svg
|
|
52
|
+
aria-hidden="true"
|
|
53
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
54
|
+
viewBox="0 0 64 64"
|
|
55
|
+
width={px}
|
|
56
|
+
height={px}
|
|
57
|
+
color={color}>
|
|
58
|
+
<defs>
|
|
59
|
+
<linearGradient
|
|
60
|
+
id={gradientId}
|
|
61
|
+
gradientUnits="objectBoundingBox"
|
|
62
|
+
x1="10%"
|
|
63
|
+
y1="0%"
|
|
64
|
+
x2="90%"
|
|
65
|
+
y2="10%">
|
|
66
|
+
<stop offset="0" stop-color={color} stop-opacity="1"></stop>
|
|
67
|
+
<stop offset="1" stop-color={color} stop-opacity="0"></stop>
|
|
68
|
+
</linearGradient>
|
|
69
|
+
</defs>
|
|
70
|
+
<g stroke-width="7" stroke-linecap="round" fill="none">
|
|
71
|
+
<path stroke={color} d="M60,32 A28,28 0 1 1 4,32"></path>
|
|
72
|
+
<path d="M60,32 A28,28.5 0 1 0 4,32" stroke={`url(#${gradientId})`}></path>
|
|
73
|
+
</g>
|
|
74
|
+
</svg>
|
|
75
|
+
</div>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import Button from '../Button/index.astro'
|
|
4
|
+
import Container from '../Container/index.astro'
|
|
5
|
+
import Text from '../Text/index.astro'
|
|
6
|
+
import IconChevronLeft from '../icons/default/IconChevronLeft.astro'
|
|
7
|
+
import IconHelpCircle from '../icons/default/IconHelpCircle.astro'
|
|
8
|
+
import '../../dist/components/SubHeader/styles.css'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The React version takes `handleReturn` and `handleHelpInfo`. Astro has no
|
|
12
|
+
* callback props, so each button carries `data-au-sub-header="return"` /
|
|
13
|
+
* `="help"` for the page's own `<script>`. `showHelp` renders the help
|
|
14
|
+
* button, the same way React renders it when `handleHelpInfo` is passed.
|
|
15
|
+
* Every HTML attribute is forwarded to the root `div`.
|
|
16
|
+
*/
|
|
17
|
+
export type Props = HTMLAttributes<'div'> & {
|
|
18
|
+
title: string
|
|
19
|
+
/** Shows the help button as a `data-au-sub-header="help"` button. */
|
|
20
|
+
showHelp?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { title, showHelp = false, class: className, ...rest } = Astro.props
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
<div
|
|
27
|
+
class:list={['au-sub-header', showHelp && 'au-sub-header--with-help', className]}
|
|
28
|
+
{...rest}>
|
|
29
|
+
<Container class="au-sub-header__container">
|
|
30
|
+
<Button
|
|
31
|
+
class="au-sub-header__button"
|
|
32
|
+
type="ghost"
|
|
33
|
+
aria-label="Voltar"
|
|
34
|
+
data-au-sub-header="return">
|
|
35
|
+
<IconChevronLeft />
|
|
36
|
+
</Button>
|
|
37
|
+
<Text
|
|
38
|
+
class="au-sub-header__title"
|
|
39
|
+
variant="heading-micro"
|
|
40
|
+
variantDesk="heading-small"
|
|
41
|
+
weight="bold"
|
|
42
|
+
as="div">
|
|
43
|
+
{title}
|
|
44
|
+
</Text>
|
|
45
|
+
{
|
|
46
|
+
showHelp && (
|
|
47
|
+
<Button
|
|
48
|
+
class="au-sub-header__button"
|
|
49
|
+
type="ghost"
|
|
50
|
+
aria-label="Ajuda"
|
|
51
|
+
data-au-sub-header="help">
|
|
52
|
+
<IconHelpCircle />
|
|
53
|
+
</Button>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
</Container>
|
|
57
|
+
</div>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import PureSwitch from '../Pure/index.astro'
|
|
4
|
+
import '../../../dist/components/Card/styles2.css'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Differences from React: `isActive` is only the initial state, and the
|
|
8
|
+
* controller keeps the wrapper classes (`--active`, `--fade-out`) in sync with
|
|
9
|
+
* the checkbox. No callbacks: listen to the native `change` event, which
|
|
10
|
+
* bubbles to the root. `fadeOutDelayInSeconds` is dropped (React declares it
|
|
11
|
+
* but never reads it; the delay is the 1s in the stylesheet).
|
|
12
|
+
*/
|
|
13
|
+
export type Props = HTMLAttributes<'div'> & {
|
|
14
|
+
label: string
|
|
15
|
+
id: string
|
|
16
|
+
/** Initial state of the switch. */
|
|
17
|
+
isActive?: boolean
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
/** Collapses and fades the card out once the switch is on. */
|
|
20
|
+
shouldFadeOutAfterActivate?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
label,
|
|
25
|
+
id,
|
|
26
|
+
isActive = false,
|
|
27
|
+
disabled = false,
|
|
28
|
+
shouldFadeOutAfterActivate = false,
|
|
29
|
+
class: className,
|
|
30
|
+
...rest
|
|
31
|
+
} = Astro.props
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
<div
|
|
35
|
+
class:list={['au-card-switch', className]}
|
|
36
|
+
data-element="au-card-switch"
|
|
37
|
+
data-fade-out={shouldFadeOutAfterActivate ? 'true' : undefined}
|
|
38
|
+
{...rest}>
|
|
39
|
+
<div
|
|
40
|
+
class:list={[
|
|
41
|
+
'au-card-switch-wrapper',
|
|
42
|
+
shouldFadeOutAfterActivate && isActive && 'au-card-switch-wrapper--fade-out',
|
|
43
|
+
isActive && 'au-card-switch-wrapper--active',
|
|
44
|
+
]}>
|
|
45
|
+
<PureSwitch isActive={isActive} label={label} id={id} disabled={disabled} />
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<script>
|
|
50
|
+
import { elementController } from '@consumidor-positivo/aurora/astro/runtime'
|
|
51
|
+
|
|
52
|
+
const ACTIVE_CLASS = 'au-card-switch-wrapper--active'
|
|
53
|
+
const FADE_OUT_CLASS = 'au-card-switch-wrapper--fade-out'
|
|
54
|
+
|
|
55
|
+
elementController('au-card-switch', ({ root, query, on }) => {
|
|
56
|
+
const wrapper = query<HTMLElement>('.au-card-switch-wrapper')
|
|
57
|
+
const fadesOut = root.dataset.fadeOut === 'true'
|
|
58
|
+
// Controller setting only; React's root carries no such attribute.
|
|
59
|
+
root.removeAttribute('data-fade-out')
|
|
60
|
+
|
|
61
|
+
on('change', (event) => {
|
|
62
|
+
const input = event.target as HTMLInputElement | null
|
|
63
|
+
if (!wrapper || !input?.matches('.au-switch-checkbox')) return
|
|
64
|
+
|
|
65
|
+
wrapper.classList.toggle(ACTIVE_CLASS, input.checked)
|
|
66
|
+
wrapper.classList.toggle(FADE_OUT_CLASS, fadesOut && input.checked)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
</script>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import '../../../dist/components/Pure/styles.css'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Differences from React: the native checkbox is uncontrolled, `isActive` is
|
|
7
|
+
* only its initial state. No `activateCallBack`/`deactivateCallBack`: listen
|
|
8
|
+
* to the input's native `change` event (it bubbles to the root) and read
|
|
9
|
+
* `event.target.checked`.
|
|
10
|
+
*/
|
|
11
|
+
export type Props = HTMLAttributes<'div'> & {
|
|
12
|
+
/** Initial state of the switch. */
|
|
13
|
+
isActive?: boolean
|
|
14
|
+
label: string
|
|
15
|
+
/** Id of the native checkbox, tied to the label. */
|
|
16
|
+
id: string
|
|
17
|
+
disabled?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
isActive = false,
|
|
22
|
+
label,
|
|
23
|
+
id,
|
|
24
|
+
disabled = false,
|
|
25
|
+
class: className,
|
|
26
|
+
...rest
|
|
27
|
+
} = Astro.props
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<div class:list={['au-switch', className]} {...rest}>
|
|
31
|
+
<label for={id} class="au-switch-label">
|
|
32
|
+
<span>{label}</span>
|
|
33
|
+
<div class="au-switch-container">
|
|
34
|
+
<input
|
|
35
|
+
id={id}
|
|
36
|
+
type="checkbox"
|
|
37
|
+
class="au-switch-checkbox"
|
|
38
|
+
checked={isActive}
|
|
39
|
+
role="switch"
|
|
40
|
+
disabled={disabled}
|
|
41
|
+
/>
|
|
42
|
+
<div class="au-switch-track">
|
|
43
|
+
<div class="au-switch-thumb"></div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</label>
|
|
47
|
+
</div>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import '../../dist/components/Tooltip/styles.css'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Same props as React. The trigger (React `children`) is the default slot;
|
|
7
|
+
* every HTML attribute is forwarded to the container.
|
|
8
|
+
*/
|
|
9
|
+
export type Props = HTMLAttributes<'div'> & {
|
|
10
|
+
text: string
|
|
11
|
+
position?: 'top' | 'bottom' | 'left' | 'right'
|
|
12
|
+
/** Keeps the tooltip visible without hover. */
|
|
13
|
+
open?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
text,
|
|
18
|
+
position = 'top',
|
|
19
|
+
open = false,
|
|
20
|
+
class: className,
|
|
21
|
+
...rest
|
|
22
|
+
} = Astro.props
|
|
23
|
+
|
|
24
|
+
// Per instance, like React's `useId`.
|
|
25
|
+
const tooltipId = `au-tooltip-${crypto.randomUUID().slice(0, 8)}`
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<div
|
|
29
|
+
class:list={[
|
|
30
|
+
'tooltip-container',
|
|
31
|
+
`tooltip-${position}`,
|
|
32
|
+
open && 'tooltip-open',
|
|
33
|
+
className,
|
|
34
|
+
]}
|
|
35
|
+
aria-describedby={tooltipId}
|
|
36
|
+
{...rest}>
|
|
37
|
+
<slot /><div id={tooltipId} class="tooltip" role="tooltip">{text}</div>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
import { COLOR_NEUTRAL_00 } from '@consumidor-positivo/aurora/astro/tokens'
|
|
4
|
+
import IconCheck from '../../../icons/default/IconCheck.astro'
|
|
5
|
+
import FieldErrorMessage from '../../Field/ErrorMessage/index.astro'
|
|
6
|
+
import '../../../../dist/components/Field/styles.css'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Same as React: every attribute except `style` and `class` goes to the
|
|
10
|
+
* native `<input type="checkbox">` (`name`, `value`, `checked`, `required`,
|
|
11
|
+
* `data-*`…), so listen to its native `change` event instead of `onChange`.
|
|
12
|
+
* Like React's `className`, `class` lands on the input and replaces
|
|
13
|
+
* `au-checkbox__input`.
|
|
14
|
+
* A string `label` is rendered as HTML, like React's `dangerouslySetInnerHTML`;
|
|
15
|
+
* for markup built from components, use the `label` slot.
|
|
16
|
+
*/
|
|
17
|
+
export type Props = Omit<HTMLAttributes<'input'>, 'type'> & {
|
|
18
|
+
label?: string
|
|
19
|
+
error?: boolean
|
|
20
|
+
errorMessage?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
label,
|
|
25
|
+
error = false,
|
|
26
|
+
errorMessage,
|
|
27
|
+
disabled = false,
|
|
28
|
+
id,
|
|
29
|
+
style,
|
|
30
|
+
class: className,
|
|
31
|
+
...rest
|
|
32
|
+
} = Astro.props
|
|
33
|
+
|
|
34
|
+
// The React version falls back to `Math.random()`; here it is fixed at render.
|
|
35
|
+
const safeId = id ?? `au-checkbox-${crypto.randomUUID().slice(0, 8)}`
|
|
36
|
+
const hasLabelSlot = Astro.slots.has('label')
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<div
|
|
40
|
+
class:list={[
|
|
41
|
+
'au-checkbox',
|
|
42
|
+
error && 'au-checkbox--error',
|
|
43
|
+
disabled && 'au-checkbox--disabled',
|
|
44
|
+
]}
|
|
45
|
+
style={style}>
|
|
46
|
+
<label for={safeId} class="au-checkbox__holder">
|
|
47
|
+
<input
|
|
48
|
+
class={className ?? 'au-checkbox__input'}
|
|
49
|
+
type="checkbox"
|
|
50
|
+
id={safeId}
|
|
51
|
+
disabled={disabled}
|
|
52
|
+
{...rest}
|
|
53
|
+
/>
|
|
54
|
+
<span class="au-checkbox__check">
|
|
55
|
+
<IconCheck rawColor={COLOR_NEUTRAL_00} size="small" />
|
|
56
|
+
</span>
|
|
57
|
+
{
|
|
58
|
+
hasLabelSlot ? (
|
|
59
|
+
<span class="au-checkbox__label">
|
|
60
|
+
<slot name="label" />
|
|
61
|
+
</span>
|
|
62
|
+
) : (
|
|
63
|
+
<span class="au-checkbox__label" set:html={label ?? ''} />
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
</label>
|
|
67
|
+
<FieldErrorMessage hasError={error} message={errorMessage} />
|
|
68
|
+
</div>
|