@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.
Files changed (96) hide show
  1. package/README.md +59 -3
  2. package/astro/AdBox/index.astro +28 -0
  3. package/astro/Alert/index.astro +257 -0
  4. package/astro/BadgeInfo/index.astro +55 -0
  5. package/astro/BadgeState/index.astro +79 -0
  6. package/astro/Button/index.astro +11 -3
  7. package/astro/Card/Container/index.astro +44 -0
  8. package/astro/Card/Emphasis/index.astro +39 -0
  9. package/astro/Card/Image/index.astro +26 -0
  10. package/astro/Card/Root/index.astro +53 -0
  11. package/astro/Card/Tag/index.astro +26 -0
  12. package/astro/Chip/index.astro +48 -0
  13. package/astro/ChipBanner/index.astro +160 -0
  14. package/astro/Container/index.astro +16 -0
  15. package/astro/Divider/index.astro +27 -0
  16. package/astro/Image/index.astro +26 -0
  17. package/astro/LazyImage/index.astro +55 -0
  18. package/astro/LinkButton/index.astro +54 -0
  19. package/astro/Logo/ac/BadgeFullWhite.astro +20 -0
  20. package/astro/Logo/ac/BadgeWhite.astro +23 -0
  21. package/astro/Logo/ac/BadgeWhiteStroke.astro +27 -0
  22. package/astro/Logo/ac/BadgeWhiteStrokeDefault.astro +28 -0
  23. package/astro/Logo/ac/Badget.astro +24 -0
  24. package/astro/Logo/ac/Primary.astro +28 -0
  25. package/astro/Logo/ac/PrimaryFillWhite.astro +28 -0
  26. package/astro/Logo/ac/PrimaryFullWhite.astro +20 -0
  27. package/astro/Logo/ac/PrimaryNegative.astro +20 -0
  28. package/astro/Logo/ac/PrimaryWhite.astro +20 -0
  29. package/astro/Logo/ac/TertiaryWhite.astro +72 -0
  30. package/astro/Logo/cp/BadgeWhiteStroke.astro +10 -0
  31. package/astro/Logo/cp/Badget.astro +14 -0
  32. package/astro/Logo/cp/PrimaryFillWhite.astro +63 -0
  33. package/astro/Logo/cp/PrimaryFullWhite.astro +27 -0
  34. package/astro/Logo/cp/PrimaryLogoWhite.astro +67 -0
  35. package/astro/Logo/cp/PrimaryNegative.astro +27 -0
  36. package/astro/Logo/cp/PrimaryWhite.astro +27 -0
  37. package/astro/Modal/index.astro +213 -0
  38. package/astro/NotificationsBar/Link/index.astro +70 -0
  39. package/astro/NotificationsBar/List/index.astro +38 -0
  40. package/astro/NotificationsBar/index.astro +24 -0
  41. package/astro/PartnerBanner/index.astro +52 -0
  42. package/astro/ProfileNav/index.astro +34 -0
  43. package/astro/ProgressBar/index.astro +70 -0
  44. package/astro/Skeleton/index.astro +50 -0
  45. package/astro/SpecialButton/index.astro +331 -0
  46. package/astro/Spinner/index.astro +75 -0
  47. package/astro/SubHeader/index.astro +57 -0
  48. package/astro/Switch/Card/index.astro +69 -0
  49. package/astro/Switch/Pure/index.astro +47 -0
  50. package/astro/Tooltip/index.astro +38 -0
  51. package/astro/form/Checkbox/Field/index.astro +68 -0
  52. package/astro/form/Checkbox/Group/index.astro +79 -0
  53. package/astro/form/Datepicker/Calendar/index.astro +53 -0
  54. package/astro/form/Datepicker/CalendarHeader/index.astro +57 -0
  55. package/astro/form/Datepicker/Segment/index.astro +53 -0
  56. package/astro/form/Datepicker/index.astro +756 -0
  57. package/astro/form/EmailField/index.astro +237 -0
  58. package/astro/form/Field/ErrorMessage/index.astro +20 -0
  59. package/astro/form/Field/Input/index.astro +15 -0
  60. package/astro/form/Field/InputHolder/index.astro +28 -0
  61. package/astro/form/Field/Label/index.astro +80 -0
  62. package/astro/form/Field/Message/index.astro +29 -0
  63. package/astro/form/Field/Root/index.astro +37 -0
  64. package/astro/form/Field/TextArea/index.astro +69 -0
  65. package/astro/form/InputField/index.astro +96 -0
  66. package/astro/form/PasswordField/index.astro +123 -0
  67. package/astro/form/Radio/Field/index.astro +52 -0
  68. package/astro/form/Radio/Group/index.astro +78 -0
  69. package/astro/form/SelectField/index.astro +561 -0
  70. package/astro/form/TextareaField/index.astro +81 -0
  71. package/astro/form/TokenField/index.astro +211 -0
  72. package/astro/tokens/package.json +6 -0
  73. package/dist/{tokens-C4ypB4bY.js → astro/tokens/index.es.js} +101 -101
  74. package/dist/astro/tokens/index.es.js.map +1 -0
  75. package/dist/components/Alert/index.es.js +1 -1
  76. package/dist/components/BadgeInfo/index.es.js +1 -1
  77. package/dist/components/BadgeState/index.es.js +1 -1
  78. package/dist/components/Image/index.es.js +1 -1
  79. package/dist/components/Spinner/index.es.js +1 -1
  80. package/dist/components/form/Checkbox/Field/index.es.js +1 -1
  81. package/dist/components/form/Datepicker/Calendar/index.es.js +2 -2
  82. package/dist/components/form/Datepicker/CalendarHeader/index.es.js +2 -2
  83. package/dist/components/form/Datepicker/PortalHolder/index.es.js +1 -1
  84. package/dist/components/form/Datepicker/Segment/index.es.js +1 -1
  85. package/dist/components/form/Datepicker/index.es.js +3 -3
  86. package/dist/components/form/Field/Label/index.es.js +1 -1
  87. package/dist/components/form/SelectField/index.es.js +1 -1
  88. package/dist/{index-BcC9liUl.js → index-Bkl5OBIY.js} +3 -3
  89. package/dist/{index-BcC9liUl.js.map → index-Bkl5OBIY.js.map} +1 -1
  90. package/dist/{index-CFYmtkje.js → index-CZfMkAoF.js} +2 -2
  91. package/dist/{index-CFYmtkje.js.map → index-CZfMkAoF.js.map} +1 -1
  92. package/dist/{index-CeClo_WS.js → index-Cw1KtRQr.js} +2 -2
  93. package/dist/{index-CeClo_WS.js.map → index-Cw1KtRQr.js.map} +1 -1
  94. package/dist/main.es.js +101 -101
  95. package/package.json +7 -1
  96. 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>