@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,213 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+ import SubHeader from '../SubHeader/index.astro'
4
+ import IconX from '../icons/default/IconX.astro'
5
+ import '../../dist/components/Modal/styles.css'
6
+
7
+ type ModalLayout = 'default' | 'centralized'
8
+ type ModalLayoutMobile = 'default' | 'centralized' | 'full-screen'
9
+
10
+ /**
11
+ * Differences from React:
12
+ * - No `isOpen`/`onClose`. Any element carrying
13
+ * `data-au-modal-toggle="<id>"` toggles this modal, from anywhere on the
14
+ * page. `closeButton` stands for React's `onClose`: it renders the close
15
+ * button (and the SubHeader back button), which close the modal, and lets
16
+ * `closeOnBackdropClick` work. The root emits `au:modalopen` /
17
+ * `au:modalclose` with `{ id }`.
18
+ * - Like React, which renders nothing while closed, the controller takes the
19
+ * closed modal out of the page and puts it back when it opens.
20
+ * - `headerContent` and `content` are the `header` slot and the default slot.
21
+ * - `ariaLabel` is the native `aria-label`.
22
+ * - `handleHelpInfo` becomes `helpButton`: the SubHeader help button carries
23
+ * `data-au-sub-header="help"` for the page's own `<script>`.
24
+ * - React picks the mobile SubHeader with `isMobile()` (max-width 767px) each
25
+ * time it renders. With `layoutMobile="full-screen"`, the SubHeader rides in
26
+ * a `<template>` and the controller makes the same choice on every open.
27
+ * - `portal` moves the modal to `document.body` when the controller runs, the
28
+ * counterpart of a React `createPortal(<Modal />, document.body)`.
29
+ * - Besides the toggles, a `CustomEvent('au:modalcontrol', { detail: { id,
30
+ * open? } })` dispatched on `document` opens (`open: true`), closes
31
+ * (`open: false`) or toggles (no `open`) the modal with that `id`, for
32
+ * components that drive a modal from their own controller.
33
+ */
34
+ export type Props = HTMLAttributes<'div'> & {
35
+ id: string
36
+ /** Renders the modal already open. */
37
+ open?: boolean
38
+ /**
39
+ * Calls the close (React `onClose`) when the backdrop (area outside the
40
+ * container) is clicked. Needs `closeButton`, as React needs `onClose`.
41
+ */
42
+ closeOnBackdropClick?: boolean
43
+ /**
44
+ * Renders the close button (and the SubHeader back button). React renders
45
+ * them only when `onClose` is passed.
46
+ */
47
+ closeButton?: boolean
48
+ layoutMobile?: ModalLayoutMobile
49
+ layoutDesktop?: ModalLayout
50
+ mobileModalTitle?: string
51
+ /** Renders the help button in the mobile full-screen SubHeader. */
52
+ helpButton?: boolean
53
+ /** Moves the modal to `document.body` once the controller runs. */
54
+ portal?: boolean
55
+ }
56
+
57
+ const {
58
+ id,
59
+ open = false,
60
+ closeOnBackdropClick = false,
61
+ closeButton = true,
62
+ layoutMobile = 'default',
63
+ layoutDesktop = 'default',
64
+ mobileModalTitle = 'Título',
65
+ helpButton = false,
66
+ portal = false,
67
+ 'aria-label': ariaLabel,
68
+ class: className,
69
+ ...rest
70
+ } = Astro.props
71
+
72
+ const isFullScreenMobile = layoutMobile === 'full-screen'
73
+ ---
74
+
75
+ <div
76
+ id={id}
77
+ class:list={[
78
+ 'au-modal',
79
+ open && 'au-modal--is-open',
80
+ isFullScreenMobile && 'au-modal--mobile-full-screen',
81
+ layoutMobile === 'centralized' && 'au-modal--mobile-centralized',
82
+ layoutDesktop === 'centralized' && 'au-modal--desktop-centralized',
83
+ className,
84
+ ]}
85
+ role="dialog"
86
+ aria-modal="true"
87
+ aria-label={ariaLabel ?? mobileModalTitle}
88
+ data-element="au-modal"
89
+ data-au-modal-close-on-backdrop={closeButton && closeOnBackdropClick ? 'true' : undefined}
90
+ data-au-modal-portal={portal ? '' : undefined}
91
+ {...rest}>
92
+ <div class="au-modal__container">
93
+ <div class="au-modal__header">
94
+ {
95
+ closeButton && (
96
+ <button class="au-modal__header-close" aria-label="Fechar">
97
+ <IconX class="au-modal__header-close-icon" />
98
+ </button>
99
+ )
100
+ }
101
+ <slot name="header" />
102
+ {
103
+ isFullScreenMobile && (
104
+ <template data-au-modal-mobile-header>
105
+ {closeButton && (
106
+ <SubHeader title={mobileModalTitle} showHelp={helpButton} />
107
+ )}
108
+ </template>
109
+ )
110
+ }
111
+ </div>
112
+ <div class="au-modal__content">
113
+ <slot />
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ <script>
119
+ import { elementController } from '@consumidor-positivo/aurora/astro/runtime'
120
+
121
+ const OPEN_CLASS = 'au-modal--is-open'
122
+ // Same rule as `isMobile()` (lib/core/utils/isMobile.ts).
123
+ const MOBILE_QUERY = 'screen and (max-width:767px)'
124
+
125
+ elementController('au-modal', ({ root, on, emit }) => {
126
+ const toggleSelector = `[data-au-modal-toggle="${root.id}"]`
127
+ const closesOnBackdrop = root.dataset.auModalCloseOnBackdrop === 'true'
128
+ root.removeAttribute('data-au-modal-close-on-backdrop')
129
+
130
+ const header = root.querySelector<HTMLElement>(
131
+ ':scope > .au-modal__container > .au-modal__header',
132
+ )
133
+ const template = header?.querySelector<HTMLTemplateElement>(
134
+ ':scope > template[data-au-modal-mobile-header]',
135
+ )
136
+ // Full-screen mobile: React renders either the close button + header
137
+ // content or the SubHeader, never both.
138
+ const desktopNodes = header
139
+ ? Array.from(header.childNodes).filter((node) => node !== template)
140
+ : []
141
+ const mobileNodes = template ? Array.from(template.content.childNodes) : []
142
+ template?.remove()
143
+
144
+ function pickHeader() {
145
+ if (!header || !template) return
146
+ const isMobile = window.matchMedia(MOBILE_QUERY).matches
147
+ const [show, hide] = isMobile
148
+ ? [mobileNodes, desktopNodes]
149
+ : [desktopNodes, mobileNodes]
150
+ hide.forEach((node) => node.parentNode === header && header.removeChild(node))
151
+ show.forEach((node) => header.appendChild(node))
152
+ }
153
+
154
+ // React renders nothing while closed: the closed modal leaves the page
155
+ // and this marker keeps its place.
156
+ const placeholder = document.createComment('au-modal')
157
+ const isOpen = () => root.classList.contains(OPEN_CLASS)
158
+
159
+ function setOpen(open: boolean, silent = false) {
160
+ document
161
+ .querySelectorAll(toggleSelector)
162
+ .forEach((trigger) => trigger.setAttribute('aria-expanded', String(open)))
163
+
164
+ if (open) {
165
+ pickHeader()
166
+ root.classList.add(OPEN_CLASS)
167
+ if (placeholder.parentNode) placeholder.replaceWith(root)
168
+ if (!silent) emit('au:modalopen', { id: root.id })
169
+ return
170
+ }
171
+
172
+ root.classList.remove(OPEN_CLASS)
173
+ // Emitted while still in the page, so the event reaches `document`.
174
+ if (!silent) emit('au:modalclose', { id: root.id })
175
+ if (root.parentNode) root.replaceWith(placeholder)
176
+ }
177
+
178
+ document.querySelectorAll(toggleSelector).forEach((trigger) => {
179
+ trigger.setAttribute('aria-controls', root.id)
180
+ })
181
+ // Controller setting only; React's root carries no such attribute.
182
+ if (root.hasAttribute('data-au-modal-portal')) {
183
+ root.removeAttribute('data-au-modal-portal')
184
+ document.body.appendChild(root)
185
+ }
186
+ setOpen(isOpen(), true)
187
+
188
+ on('click', (event) => {
189
+ const target = event.target as Element | null
190
+ // The backdrop is the root itself; the container swallows its own clicks.
191
+ if (target === root) {
192
+ if (closesOnBackdrop) setOpen(false)
193
+ return
194
+ }
195
+ const close = target?.closest(
196
+ '.au-modal__header-close, .au-sub-header [data-au-sub-header="return"]',
197
+ )
198
+ if (close && header?.contains(close)) setOpen(false)
199
+ })
200
+
201
+ on('global:click', (event) => {
202
+ const trigger = (event.target as Element | null)?.closest(toggleSelector)
203
+ if (!trigger) return
204
+ setOpen(!isOpen())
205
+ })
206
+
207
+ on('global:au:modalcontrol', (event) => {
208
+ const detail = (event as CustomEvent<{ id?: string; open?: boolean }>).detail
209
+ if (!detail || detail.id !== root.id) return
210
+ setOpen(detail.open ?? !isOpen())
211
+ })
212
+ })
213
+ </script>
@@ -0,0 +1,70 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+ import Text from '../../Text/index.astro'
4
+ import '../../../dist/components/NotificationsBar/styles.css'
5
+
6
+ export type NotificationsBarLinkItem = {
7
+ title: string
8
+ createdAt?: string
9
+ isUnread?: boolean
10
+ /** Renders the "Excluir" button (React renders it when `onDelete` is passed). */
11
+ deletable?: boolean
12
+ }
13
+
14
+ /**
15
+ * `NotificationsBar.Link` in React. No `onClick`/`onDelete`: the item button
16
+ * carries `data-au-notifications-bar="link"` and the delete button
17
+ * `data-au-notifications-bar="delete"`, so the page's own `<script>` can wire
18
+ * them. `Icon` is the `icon` slot.
19
+ */
20
+ export type Props = Omit<HTMLAttributes<'div'>, 'title'> & NotificationsBarLinkItem
21
+
22
+ const {
23
+ title,
24
+ createdAt,
25
+ isUnread = false,
26
+ deletable = false,
27
+ class: className,
28
+ ...rest
29
+ } = Astro.props
30
+
31
+ const timestampId = `au-notifications-bar-${crypto.randomUUID().slice(0, 8)}`
32
+ ---
33
+
34
+ <div class:list={['au-notifications-bar__item', className]} {...rest}>
35
+ <button
36
+ type="button"
37
+ class="au-notifications-bar__link"
38
+ data-au-notifications-bar="link"
39
+ aria-describedby={createdAt ? timestampId : undefined}>
40
+ {
41
+ Astro.slots.has('icon') && (
42
+ <span aria-hidden="true">
43
+ <slot name="icon" />
44
+ </span>
45
+ )
46
+ }
47
+ <Text as="span" variant="body-medium" weight="regular">
48
+ {isUnread && <span class="au-sr-only">não lida — </span>}
49
+ {title}
50
+ </Text>
51
+ </button>
52
+ <div class="au-notifications-bar__actions">
53
+ <Text as="span" id={timestampId} variant="body-small">
54
+ {createdAt}
55
+ </Text>
56
+ {
57
+ deletable && (
58
+ <button
59
+ type="button"
60
+ class="au-notifications-bar__delete-btn"
61
+ data-au-notifications-bar="delete"
62
+ aria-label={`Excluir: ${title}`}>
63
+ <Text as="span" variant="body-small" weight="semibold">
64
+ Excluir
65
+ </Text>
66
+ </button>
67
+ )
68
+ }
69
+ </div>
70
+ </div>
@@ -0,0 +1,38 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+ import Text from '../../Text/index.astro'
4
+ import NotificationsBarLink from '../Link/index.astro'
5
+ import type { NotificationsBarLinkItem } from '../Link/index.astro'
6
+ import '../../../dist/components/NotificationsBar/styles.css'
7
+
8
+ /**
9
+ * `NotificationsBar.List` in React. `dataSource` + `renderItem` become `data`:
10
+ * the list renders a `NotificationsBar/Link` per item. For custom items (an
11
+ * icon, other markup), put `NotificationsBar/Link`s in the default slot,
12
+ * rendered after `data`.
13
+ */
14
+ export type Props = Omit<HTMLAttributes<'div'>, 'title'> & {
15
+ title: string
16
+ data?: NotificationsBarLinkItem[]
17
+ }
18
+
19
+ const {
20
+ title,
21
+ data = [],
22
+ class: className,
23
+ // Astro hands a component its own `slot="recents|olds"` as a prop; spread
24
+ // into `...rest` it would render as a stray `slot` attribute.
25
+ slot: _slot,
26
+ ...rest
27
+ } = Astro.props
28
+ ---
29
+
30
+ <div class:list={['au-notifications-bar__section', className]} {...rest}>
31
+ <Text as="h3" variant="body-medium" weight="bold">
32
+ {title}
33
+ </Text>
34
+ <div class="au-notifications-bar__list">
35
+ {data.map((item) => <NotificationsBarLink {...item} />)}
36
+ <slot />
37
+ </div>
38
+ </div>
@@ -0,0 +1,24 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+ import '../../dist/components/NotificationsBar/styles.css'
4
+
5
+ /**
6
+ * `NotificationsBar.Root` in React. `renderRecents` and `renderOlds` become
7
+ * the `recents` and `olds` slots, rendered in that order (the stylesheet
8
+ * highlights the first section as the unread one). The default slot comes
9
+ * last, for anything else.
10
+ */
11
+ export type Props = HTMLAttributes<'div'>
12
+
13
+ const { class: className, ...rest } = Astro.props
14
+ ---
15
+
16
+ <div
17
+ class:list={['au-notifications-bar', className]}
18
+ role="region"
19
+ aria-label="Notificações"
20
+ {...rest}>
21
+ <slot name="recents" />
22
+ <slot name="olds" />
23
+ <slot />
24
+ </div>
@@ -0,0 +1,52 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+ import Text from '../Text/index.astro'
4
+ import '../../dist/components/PartnerBanner/styles.css'
5
+
6
+ /**
7
+ * The React `icon` prop is the `icon` slot (and still adds
8
+ * `au-partner-banner--with-icon`); `ariaLabel` is the plain `aria-label`
9
+ * attribute and `className` the plain `class`. No `onButtonClick`: the button
10
+ * carries `data-au-partner-banner="button"` for the page's own `<script>`.
11
+ * Every HTML attribute is forwarded to the `<aside>`.
12
+ */
13
+ export type Props = HTMLAttributes<'aside'> & {
14
+ text: string
15
+ btnText: string
16
+ }
17
+
18
+ const { text, btnText, class: className, ...rest } = Astro.props
19
+
20
+ const hasIcon = Astro.slots.has('icon')
21
+ ---
22
+
23
+ <aside
24
+ class:list={[
25
+ 'au-partner-banner',
26
+ hasIcon && 'au-partner-banner--with-icon',
27
+ className,
28
+ ]}
29
+ {...rest}>
30
+ {
31
+ hasIcon && (
32
+ <div class="au-partner-banner__icon" aria-hidden="true">
33
+ <slot name="icon" />
34
+ </div>
35
+ )
36
+ }
37
+ <div class="au-partner-banner__content">
38
+ <Text
39
+ as="p"
40
+ variant="body-small"
41
+ color="secondary"
42
+ class="au-partner-banner__text">
43
+ {text}
44
+ </Text>
45
+ <button
46
+ type="button"
47
+ class="au-partner-banner__button"
48
+ data-au-partner-banner="button">
49
+ <Text as="span" variant="body-small" weight="semibold">{btnText}</Text>
50
+ </button>
51
+ </div>
52
+ </aside>
@@ -0,0 +1,34 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+ import Text from '../Text/index.astro'
4
+ import '../../dist/components/ProfileNav/styles.css'
5
+
6
+ /** Same props as React; every HTML attribute is forwarded to the root `div`. */
7
+ export type Props = HTMLAttributes<'div'> & {
8
+ /** Shown in the greeting ("Olá, {name}"). */
9
+ name: string
10
+ /** Source of the two initials in the badge. */
11
+ fullName: string
12
+ }
13
+
14
+ const { name, fullName, class: className, ...rest } = Astro.props
15
+
16
+ // Same rule as `lib/core/utils/getInitialLetters.ts`, inlined: a `.astro`
17
+ // ships as source and `lib/core` is not published, so importing it would break
18
+ // in the consumer.
19
+ const initialLetters = fullName
20
+ ?.split(' ')
21
+ .map((part) => part[0])
22
+ .slice(0, 2)
23
+ .join('')
24
+ ---
25
+
26
+ <div class:list={['au-profile-nav', className]} {...rest}>
27
+ <div class="au-profile-nav__badget">
28
+ <Text variant="heading-small" weight="medium">{initialLetters}</Text>
29
+ </div>
30
+ <div class="div">
31
+ <Text as="h3" variant="heading-micro" weight="bold">Olá, {name}</Text>
32
+ <Text color="secondary">Tudo bem?</Text>
33
+ </div>
34
+ </div>
@@ -0,0 +1,70 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+ import Text from '../Text/index.astro'
4
+ import '../../dist/components/ProgressBar/styles.css'
5
+
6
+ /** Same props as React; every HTML attribute is forwarded to the root `div`. */
7
+ export type Props = HTMLAttributes<'div'> & {
8
+ /** Shows a continuous bar with a percentage instead of step segments. */
9
+ percentageMode?: boolean
10
+ stepName: string
11
+ currentStep: number
12
+ totalSteps: number
13
+ }
14
+
15
+ const {
16
+ percentageMode = false,
17
+ stepName,
18
+ currentStep,
19
+ totalSteps,
20
+ class: className,
21
+ ...rest
22
+ } = Astro.props
23
+
24
+ const safeCurrentStep = Math.max(1, Math.min(currentStep, totalSteps))
25
+ const percentageWidth = (safeCurrentStep / totalSteps) * 100
26
+ const roundedPercentage = Math.round(percentageWidth)
27
+
28
+ const progressText = percentageMode
29
+ ? `${roundedPercentage}%`
30
+ : `${safeCurrentStep} de ${totalSteps}`
31
+ ---
32
+
33
+ <div
34
+ class:list={['au-progress-bar', className]}
35
+ role="progressbar"
36
+ aria-valuenow={percentageMode ? roundedPercentage : safeCurrentStep}
37
+ aria-valuemin={percentageMode ? 0 : 1}
38
+ aria-valuemax={percentageMode ? 100 : totalSteps}
39
+ aria-label={stepName}
40
+ {...rest}>
41
+ {
42
+ percentageMode ? (
43
+ <div class="au-progress-bar__bar">
44
+ <div
45
+ style={`width: ${percentageWidth}%`}
46
+ class="au-progress-bar__bar-filled"
47
+ />
48
+ </div>
49
+ ) : (
50
+ <div class="au-progress-bar__steps">
51
+ {Array.from({ length: totalSteps }).map((_, index) => (
52
+ <div class="au-progress-bar__step-container">
53
+ <div
54
+ class:list={[
55
+ 'au-progress-bar__step-filled',
56
+ index < safeCurrentStep &&
57
+ 'au-progress-bar__step-filled--is-active',
58
+ ]}
59
+ />
60
+ </div>
61
+ ))}
62
+ </div>
63
+ )
64
+ }
65
+
66
+ <div class="au-progress-bar__text">
67
+ <Text as="span" variant="body-medium">{stepName}</Text>
68
+ <Text as="span" variant="body-medium">{progressText}</Text>
69
+ </div>
70
+ </div>
@@ -0,0 +1,50 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+ import '../../dist/components/Skeleton/styles.css'
4
+
5
+ /**
6
+ * Same props as React; every HTML attribute is forwarded to the `div`. The
7
+ * size is an inline `style`, so a `style` passed here is ignored.
8
+ */
9
+ export type Props = Omit<HTMLAttributes<'div'>, 'color' | 'style'> & {
10
+ active?: boolean
11
+ shape?: 'default' | 'square' | 'circle'
12
+ color?: 'primary' | 'secondary'
13
+ /** Fills the container width when no `width` is given. */
14
+ block?: boolean
15
+ /** Width in px. Defaults to 80px (or 100% with `block`). */
16
+ width?: number
17
+ /** Height in px. Defaults to 28px. */
18
+ height?: number
19
+ }
20
+
21
+ const {
22
+ active = true,
23
+ shape = 'default',
24
+ color = 'primary',
25
+ block = false,
26
+ width,
27
+ height,
28
+ class: className,
29
+ ...rest
30
+ } = Astro.props
31
+
32
+ const style = {
33
+ height: height ? `${height}px` : '28px',
34
+ width: width ? `${width}px` : block ? '100%' : '80px',
35
+ }
36
+ ---
37
+
38
+ <div
39
+ class:list={[
40
+ 'au-skeleton',
41
+ active && 'au-skeleton--active',
42
+ shape === 'square' && 'au-skeleton--shape-square',
43
+ shape === 'circle' && 'au-skeleton--shape-circle',
44
+ color === 'secondary' && 'au-skeleton--color-secondary',
45
+ className,
46
+ ]}
47
+ aria-hidden="true"
48
+ {...rest}
49
+ style={style}>
50
+ </div>