@bloom-housing/ui-components 5.1.1-alpha.9 → 6.0.1-alpha.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/CHANGELOG.md +259 -0
- package/index.ts +1 -0
- package/package.json +3 -3
- package/src/actions/Button.tsx +1 -0
- package/src/actions/ButtonGroup.docs.mdx +30 -0
- package/src/actions/ButtonGroup.scss +61 -0
- package/src/actions/ButtonGroup.tsx +42 -0
- package/src/actions/LinkButton.tsx +1 -0
- package/src/blocks/FormCard.scss +2 -8
- package/src/blocks/HousingCounselor.tsx +8 -3
- package/src/blocks/ImageCard.tsx +24 -13
- package/src/blocks/MediaCard.docs.mdx +37 -0
- package/src/blocks/MediaCard.scss +10 -11
- package/src/blocks/MediaCard.tsx +4 -4
- package/src/blocks/StandardCard.tsx +1 -1
- package/src/blocks/StatusItem.tsx +17 -6
- package/src/forms/DOBField.tsx +20 -8
- package/src/forms/DateField.tsx +16 -7
- package/src/forms/Dropzone.scss +7 -0
- package/src/forms/Dropzone.tsx +18 -5
- package/src/forms/Field.tsx +5 -0
- package/src/forms/FieldGroup.tsx +14 -3
- package/src/forms/HouseholdMemberForm.tsx +4 -1
- package/src/forms/HouseholdSizeField.tsx +16 -6
- package/src/forms/TimeField.tsx +15 -6
- package/src/global/custom_counter.scss +1 -1
- package/src/global/forms.scss +38 -5
- package/src/global/headers.scss +1 -1
- package/src/global/markdown.scss +2 -2
- package/src/headers/Hero.tsx +8 -1
- package/src/headers/PageHeader.scss +1 -1
- package/src/headers/PageHeader.tsx +5 -1
- package/src/headers/SiteHeader.tsx +14 -7
- package/src/helpers/formOptions.tsx +4 -1
- package/src/helpers/formatYesNoLabel.ts +8 -6
- package/src/locales/es.json +1 -1
- package/src/locales/general.json +11 -4
- package/src/locales/tl.json +1 -1
- package/src/locales/vi.json +1 -1
- package/src/locales/zh.json +1 -1
- package/src/navigation/Breadcrumbs.tsx +1 -1
- package/src/navigation/FooterNav.tsx +5 -1
- package/src/navigation/LanguageNav.tsx +1 -1
- package/src/navigation/ProgressNav.docs.mdx +47 -0
- package/src/navigation/ProgressNav.scss +101 -55
- package/src/navigation/ProgressNav.tsx +45 -15
- package/src/navigation/TabNav.scss +1 -1
- package/src/navigation/TabNav.tsx +1 -1
- package/src/notifications/AlertBox.docs.mdx +41 -0
- package/src/notifications/AlertBox.scss +78 -41
- package/src/notifications/AlertBox.tsx +20 -14
- package/src/notifications/SiteAlert.tsx +3 -0
- package/src/notifications/StatusMessage.tsx +8 -2
- package/src/notifications/alertTypes.ts +1 -0
- package/src/overlays/Drawer.scss +7 -0
- package/src/overlays/Modal.scss +2 -1
- package/src/page_components/ApplicationTimeline.scss +6 -6
- package/src/page_components/ApplicationTimeline.tsx +17 -7
- package/src/page_components/forgot-password/FormForgotPassword.tsx +1 -1
- package/src/page_components/listing/AdditionalFees.tsx +1 -1
- package/src/page_components/listing/ListingCard.scss +4 -0
- package/src/page_components/listing/ListingCard.tsx +18 -3
- package/src/page_components/listing/listing_sidebar/Contact.tsx +2 -2
- package/src/page_components/listing/listing_sidebar/GetApplication.tsx +31 -16
- package/src/page_components/listing/listing_sidebar/ListingUpdated.tsx +5 -1
- package/src/page_components/listing/listing_sidebar/OrDivider.tsx +4 -2
- package/src/page_components/listing/listing_sidebar/ReferralApplication.tsx +7 -4
- package/src/page_components/listing/listing_sidebar/events/DownloadLotteryResults.tsx +6 -1
- package/src/page_components/listing/listing_sidebar/events/EventSection.tsx +1 -1
- package/src/page_components/sign-in/FormSignIn.tsx +1 -1
- package/src/page_components/sign-in/FormSignInErrorBox.tsx +1 -1
- package/src/prototypes/Swatch.tsx +1 -0
- package/src/sections/InfoCardGrid.scss +1 -1
- package/src/sections/InfoCardGrid.tsx +4 -1
- package/src/sections/ListSection.tsx +1 -1
- package/src/tables/AgTable.tsx +10 -4
- package/src/tables/StandardTable.tsx +19 -7
- package/src/text/Tag.scss +7 -0
- package/src/text/Tag.tsx +2 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.page-header {
|
|
2
2
|
/* Component Variables */
|
|
3
|
-
--background-color: var(--bloom-color-
|
|
3
|
+
--background-color: var(--bloom-color-gray-300);
|
|
4
4
|
--border-color: var(--bloom-color-gray-450);
|
|
5
5
|
--text-color: inherit;
|
|
6
6
|
--text-font-family: var(--bloom-font-serif);
|
|
@@ -19,7 +19,11 @@ const PageHeader = (props: PageHeaderProps) => {
|
|
|
19
19
|
return (
|
|
20
20
|
<header className={classNames.join(" ")}>
|
|
21
21
|
<hgroup className="page-header__group">
|
|
22
|
-
{props?.breadcrumbs &&
|
|
22
|
+
{props?.breadcrumbs && (
|
|
23
|
+
<nav className="page-header__breadcrumbs" aria-label={"Page Header"}>
|
|
24
|
+
{props?.breadcrumbs}
|
|
25
|
+
</nav>
|
|
26
|
+
)}
|
|
23
27
|
|
|
24
28
|
{props.title && (
|
|
25
29
|
<h1 data-test-id="page-header" className="page-header__title">
|
|
@@ -38,6 +38,11 @@ export interface SiteHeaderProps {
|
|
|
38
38
|
noticeMobile?: boolean
|
|
39
39
|
siteHeaderWidth?: SiteHeaderWidth
|
|
40
40
|
title?: string
|
|
41
|
+
strings?: {
|
|
42
|
+
close?: string
|
|
43
|
+
logoAriaLable?: string
|
|
44
|
+
menu?: string
|
|
45
|
+
}
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
const SiteHeader = (props: SiteHeaderProps) => {
|
|
@@ -147,7 +152,7 @@ const SiteHeader = (props: SiteHeaderProps) => {
|
|
|
147
152
|
}
|
|
148
153
|
dropdownOptionKeyDown(event, index)
|
|
149
154
|
}}
|
|
150
|
-
data-test-id={`${option.title}
|
|
155
|
+
data-test-id={`${option.title}`}
|
|
151
156
|
>
|
|
152
157
|
{dropdownOptionContent(option)}
|
|
153
158
|
</button>
|
|
@@ -240,7 +245,7 @@ const SiteHeader = (props: SiteHeaderProps) => {
|
|
|
240
245
|
setMobileDrawer(false)
|
|
241
246
|
}
|
|
242
247
|
}}
|
|
243
|
-
aria-label={t("t.close")}
|
|
248
|
+
aria-label={props.strings?.close ?? t("t.close")}
|
|
244
249
|
>
|
|
245
250
|
<Icon size="small" symbol="arrowForward" fill={"#ffffff"} className={"pl-2"} />
|
|
246
251
|
</button>
|
|
@@ -301,7 +306,7 @@ const SiteHeader = (props: SiteHeaderProps) => {
|
|
|
301
306
|
className={`navbar-link ${props.menuItemClassName && props.menuItemClassName}`}
|
|
302
307
|
href={menuLink.href}
|
|
303
308
|
key={`${menuLink.title}-${index}`}
|
|
304
|
-
data-test-id={`${menuLink.title}
|
|
309
|
+
data-test-id={`${menuLink.title}`}
|
|
305
310
|
>
|
|
306
311
|
{menuContent}
|
|
307
312
|
</LinkComponent>
|
|
@@ -340,8 +345,8 @@ const SiteHeader = (props: SiteHeaderProps) => {
|
|
|
340
345
|
}}
|
|
341
346
|
onMouseEnter={() => changeMenuShow(menuLink.title, activeMenus, setActiveMenus)}
|
|
342
347
|
onMouseLeave={() => changeMenuShow(menuLink.title, activeMenus, setActiveMenus)}
|
|
343
|
-
data-test-id={`${menuLink.title}-${index}`}
|
|
344
348
|
role={"button"}
|
|
349
|
+
data-test-id={`${menuLink.title}`}
|
|
345
350
|
>
|
|
346
351
|
{menuContent}
|
|
347
352
|
</span>
|
|
@@ -377,7 +382,9 @@ const SiteHeader = (props: SiteHeaderProps) => {
|
|
|
377
382
|
}
|
|
378
383
|
}}
|
|
379
384
|
>
|
|
380
|
-
<div className={"pr-2 text-tiny text-primary uppercase"}>
|
|
385
|
+
<div className={"pr-2 text-tiny text-primary uppercase"}>
|
|
386
|
+
{props.strings?.menu ?? t("t.menu")}
|
|
387
|
+
</div>
|
|
381
388
|
<Icon
|
|
382
389
|
symbol={mobileMenu ? "closeSmall" : "hamburger"}
|
|
383
390
|
size={"base"}
|
|
@@ -400,7 +407,7 @@ const SiteHeader = (props: SiteHeaderProps) => {
|
|
|
400
407
|
className={"navbar-mobile-menu-button"}
|
|
401
408
|
unstyled
|
|
402
409
|
>
|
|
403
|
-
{mobileMenu ? t("t.close") : t("t.menu")}
|
|
410
|
+
{mobileMenu ? props.strings?.close ?? t("t.close") : props.strings?.menu ?? t("t.menu")}
|
|
404
411
|
</Button>
|
|
405
412
|
)}
|
|
406
413
|
</>
|
|
@@ -415,7 +422,7 @@ const SiteHeader = (props: SiteHeaderProps) => {
|
|
|
415
422
|
props.logoWidth && "navbar-custom-width"
|
|
416
423
|
}`}
|
|
417
424
|
href={props.homeURL}
|
|
418
|
-
aria-label={t("t.homePage")}
|
|
425
|
+
aria-label={props.strings?.logoAriaLable ?? t("t.homePage")}
|
|
419
426
|
>
|
|
420
427
|
<div className={`logo-content ${props.imageOnly && "navbar-image-only-container"}`}>
|
|
421
428
|
<img
|
|
@@ -5,6 +5,9 @@ import { SelectOption } from "../forms/Select"
|
|
|
5
5
|
export interface FormOptionsProps {
|
|
6
6
|
options: (string | SelectOption)[]
|
|
7
7
|
keyPrefix?: string
|
|
8
|
+
strings?: {
|
|
9
|
+
selectOne?: string
|
|
10
|
+
}
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export const numberOptions = (end: number, start = 1): SelectOption[] => {
|
|
@@ -21,7 +24,7 @@ export const FormOptions = (props: FormOptionsProps) => {
|
|
|
21
24
|
if (option == "" || option["value"] == "") {
|
|
22
25
|
return (
|
|
23
26
|
<option value="" key="select-one">
|
|
24
|
-
{t("t.selectOne")}
|
|
27
|
+
{props.strings?.selectOne ?? t("t.selectOne")}
|
|
25
28
|
</option>
|
|
26
29
|
)
|
|
27
30
|
} else {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { t } from "./translator"
|
|
2
2
|
|
|
3
|
-
export const formatYesNoLabel = (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
export const formatYesNoLabel = (
|
|
4
|
+
value: boolean | null,
|
|
5
|
+
strings?: { yesString?: string; noString?: string; notApplicableString?: string }
|
|
6
|
+
) => {
|
|
7
|
+
if (value === null || typeof value == "undefined")
|
|
8
|
+
return strings?.notApplicableString ?? t("t.n/a")
|
|
9
|
+
if (value) return strings?.yesString ?? t("t.yes")
|
|
10
|
+
return strings?.noString ?? t("t.no")
|
|
9
11
|
}
|
package/src/locales/es.json
CHANGED
|
@@ -371,7 +371,7 @@
|
|
|
371
371
|
"authentication.forgotPassword.sendEmail": "Enviar correo electrónico",
|
|
372
372
|
"authentication.forgotPassword.success": "Le hemos enviado un correo electrónico con un enlace para restablecer tu contraseña.",
|
|
373
373
|
"authentication.signIn.accountHasBeenLocked": "Por razones de seguridad_ esta cuenta ha sido bloqueada.",
|
|
374
|
-
"authentication.signIn.afterFailedAttempts": "Por razones de seguridad_ después de
|
|
374
|
+
"authentication.signIn.afterFailedAttempts": "Por razones de seguridad_ después de %{count} intentos fallidos_ deberá esperar 30 minutos antes de volver a intentarlo.",
|
|
375
375
|
"authentication.signIn.changeYourPassword": "Puede cambiar su contraseña",
|
|
376
376
|
"authentication.signIn.enterLoginEmail": "Por favor_ escriba su correo electrónico de inicio de sesión",
|
|
377
377
|
"authentication.signIn.enterLoginPassword": "Por favor_ escriba su contraseña de inicio de sesión",
|
package/src/locales/general.json
CHANGED
|
@@ -345,6 +345,7 @@
|
|
|
345
345
|
"application.programs.rentBasedOnIncome.flatRent.description": "I would like to apply for an affordable flat rent apartment, which has a set monthly rent amount that is below market rate. Note - applicants with Section 8 Mobile Housing Choice Vouchers (HCV) are welcome to apply.",
|
|
346
346
|
"application.programs.rentBasedOnIncome.flatRent.label": "Affordable apartment with flat rent",
|
|
347
347
|
"application.programs.rentBasedOnIncome.summary": "Flat Rent & Rent Based on Income",
|
|
348
|
+
"application.programs.selectBelow": "Please select all that apply:",
|
|
348
349
|
"application.programs.servedInMilitary.doNotConsider.label": "No",
|
|
349
350
|
"application.programs.servedInMilitary.servedInMilitary.label": "Yes",
|
|
350
351
|
"application.programs.servedInMilitary.summary": "Veteran of the US Military",
|
|
@@ -362,7 +363,7 @@
|
|
|
362
363
|
"application.review.confirmation.createAccountParagraph": "Creating an account will save your information for future applications, and you can check the status of this application anytime.",
|
|
363
364
|
"application.review.confirmation.createAccountTitle": "Would you like to create an account?",
|
|
364
365
|
"application.review.confirmation.doNotSubmitTitle": "Do not submit another application for this listing.",
|
|
365
|
-
"application.review.confirmation.eligibleApplicants.FCFS": "
|
|
366
|
+
"application.review.confirmation.eligibleApplicants.FCFS": "Eligible applicants will be contacted by on a **first come first serve** basis until vacancies are filled.",
|
|
366
367
|
"application.review.confirmation.eligibleApplicants.lottery": "Eligible applicants will be contacted by the agent in lottery rank order until vacancies are filled.",
|
|
367
368
|
"application.review.confirmation.eligibleApplicants.lotteryDate": "The lottery will be held on **%{lotteryDate}**.",
|
|
368
369
|
"application.review.confirmation.imdone": "No thanks, I'm done.",
|
|
@@ -494,7 +495,7 @@
|
|
|
494
495
|
"authentication.forgotPassword.sendEmail": "Send email",
|
|
495
496
|
"authentication.forgotPassword.success": "We've sent you an email. You'll receive an email with a link to reset your password.",
|
|
496
497
|
"authentication.signIn.accountHasBeenLocked": "For security reasons, this account has been locked.",
|
|
497
|
-
"authentication.signIn.afterFailedAttempts": "For security reasons, after
|
|
498
|
+
"authentication.signIn.afterFailedAttempts": "For security reasons, after %{count} failed attempts, you’ll have to wait 30 minutes before trying again.",
|
|
498
499
|
"authentication.signIn.changeYourPassword": "You can change your password",
|
|
499
500
|
"authentication.signIn.enterLoginEmail": "Please enter your login email",
|
|
500
501
|
"authentication.signIn.enterLoginPassword": "Please enter your login password",
|
|
@@ -613,9 +614,12 @@
|
|
|
613
614
|
"listings.apply.submitPaperNoDueDateNoPostMark": "%{developer} is not responsible for lost or delayed mail.",
|
|
614
615
|
"listings.apply.submitPaperNoDueDatePostMark": "Applications must be received by the deadline. If sending by U.S. Mail, the application must be received by mail no later than %{postmarkReceivedByDate}. Applications received after %{postmarkReceivedByDate} via mail will not be accepted. %{developer} is not responsible for lost or delayed mail.",
|
|
615
616
|
"listings.availableAndWaitlist": "Available Units & Open Waitlist",
|
|
616
|
-
"listings.availableUnitsDescription": "Applicants will be reviewed in order until all vacancies are filled.",
|
|
617
|
+
"listings.availableUnitsDescription": "Applicants will be reviewed in lottery rank order until all vacancies are filled.",
|
|
617
618
|
"listings.availableUnitsAndWaitlist": "Available units and waitlist",
|
|
618
619
|
"listings.availableUnitsAndWaitlistDesc": "Once applicants fill all available units, additional applicants will be placed on the waitlist for <span class='t-italic'>%{number} units</span>",
|
|
620
|
+
"listings.vacantUnit": "Vacant Unit",
|
|
621
|
+
"listings.vacantUnits": "Vacant Units",
|
|
622
|
+
"listings.vacantUnitsAvailable": "Vacant Units Available",
|
|
619
623
|
"listings.availableUnit": "Available Unit",
|
|
620
624
|
"listings.availableUnits": "Available Units",
|
|
621
625
|
"listings.bath": "bath",
|
|
@@ -788,7 +792,8 @@
|
|
|
788
792
|
"pageTitle.welcomeEnglish": "Welcome",
|
|
789
793
|
"pageTitle.welcomeSpanish": "Bienvenido",
|
|
790
794
|
"pageTitle.welcomeVietnamese": "Tiếng Việt",
|
|
791
|
-
"progressNav.
|
|
795
|
+
"progressNav.completed": "completed",
|
|
796
|
+
"progressNav.notCompleted": "not completed",
|
|
792
797
|
"progressNav.srHeading": "Progress",
|
|
793
798
|
"region.name": "Local Region",
|
|
794
799
|
"states.AK": "Alaska",
|
|
@@ -866,6 +871,7 @@
|
|
|
866
871
|
"t.edit": "Edit",
|
|
867
872
|
"t.email": "Email",
|
|
868
873
|
"t.emailAddressPlaceholder": "you@myemail.com",
|
|
874
|
+
"t.filter": "Filter",
|
|
869
875
|
"t.floor": "floor",
|
|
870
876
|
"t.floors": "floors",
|
|
871
877
|
"t.getDirections": "Get Directions",
|
|
@@ -909,6 +915,7 @@
|
|
|
909
915
|
"t.preferNotToSay": "Prefer not to say",
|
|
910
916
|
"t.preferences": "Preferences",
|
|
911
917
|
"t.previous": "Previous",
|
|
918
|
+
"t.programs": "Programs",
|
|
912
919
|
"t.propertyAmenities": "Property Amenities",
|
|
913
920
|
"t.range": "%{from} to %{to}",
|
|
914
921
|
"t.readLess": "read less",
|
package/src/locales/tl.json
CHANGED
|
@@ -324,7 +324,7 @@
|
|
|
324
324
|
"authentication.forgotPassword.sendEmail": "Magpadala ng email",
|
|
325
325
|
"authentication.forgotPassword.success": "Nagpadala kami sa iyo ng email. Makakatanggap ka ng email na may link para i-reset ang iyong password.",
|
|
326
326
|
"authentication.signIn.accountHasBeenLocked": "Para sa mga kadahilanang pangseguridad_ ang account na ito isinara na.",
|
|
327
|
-
"authentication.signIn.afterFailedAttempts": "Para sa mga kadahilanang pangseguridad_ pagkatapos ng
|
|
327
|
+
"authentication.signIn.afterFailedAttempts": "Para sa mga kadahilanang pangseguridad_ pagkatapos ng %{count} nabigong pagtatangka_ kailangan mong maghintay ng 30 minuto bago subukang muli.",
|
|
328
328
|
"authentication.signIn.changeYourPassword": "Maaari mong palitan ang iyong password",
|
|
329
329
|
"authentication.signIn.enterLoginEmail": "Pakilagay ang iyong email sa pag-login",
|
|
330
330
|
"authentication.signIn.enterLoginPassword": "Pakilagay ang iyong password sa pag-log in",
|
package/src/locales/vi.json
CHANGED
|
@@ -371,7 +371,7 @@
|
|
|
371
371
|
"authentication.forgotPassword.sendEmail": "Gửi email",
|
|
372
372
|
"authentication.forgotPassword.success": "Chúng tôi đã gửi email cho quý vị. Quý vị sẽ nhận được email có liên kết để đặt lại mật khẩu.",
|
|
373
373
|
"authentication.signIn.accountHasBeenLocked": "Vì lý do bảo mật_ tài khoản này đã bị khóa.",
|
|
374
|
-
"authentication.signIn.afterFailedAttempts": "Vì lý do bảo mật_ quý vị sẽ phải chờ 30 phút trước khi thử lại sau
|
|
374
|
+
"authentication.signIn.afterFailedAttempts": "Vì lý do bảo mật_ quý vị sẽ phải chờ 30 phút trước khi thử lại sau %{count} lần thử không thành công.",
|
|
375
375
|
"authentication.signIn.changeYourPassword": "Quý vị có thể đổi mật khẩu",
|
|
376
376
|
"authentication.signIn.enterLoginEmail": "Vui lòng nhập email đăng nhập của quý vị",
|
|
377
377
|
"authentication.signIn.enterLoginPassword": "Vui lòng nhập mật khẩu đăng nhập của quý vị",
|
package/src/locales/zh.json
CHANGED
|
@@ -371,7 +371,7 @@
|
|
|
371
371
|
"authentication.forgotPassword.sendEmail": "傳送電子郵件",
|
|
372
372
|
"authentication.forgotPassword.success": "我們已向您傳送電子郵件。您會收到具有重設密碼連結的電子郵件。",
|
|
373
373
|
"authentication.signIn.accountHasBeenLocked": "基於安全原因,此帳戶已遭到鎖定。",
|
|
374
|
-
"authentication.signIn.afterFailedAttempts": "基於安全原因,只要失敗嘗試達
|
|
374
|
+
"authentication.signIn.afterFailedAttempts": "基於安全原因,只要失敗嘗試達 %{count} 次,您就必須等待 30 分鐘才能再試一次。",
|
|
375
375
|
"authentication.signIn.changeYourPassword": "您可以變更密碼",
|
|
376
376
|
"authentication.signIn.enterLoginEmail": "請輸入您的登入電子郵件",
|
|
377
377
|
"authentication.signIn.enterLoginPassword": "請輸入您的登入密碼",
|
|
@@ -9,7 +9,7 @@ export interface BreadcrumbsProps {
|
|
|
9
9
|
const BreadcrumbLink = (props: { href: string; children: React.ReactNode; current?: boolean }) => (
|
|
10
10
|
<li>
|
|
11
11
|
<LocalizedLink
|
|
12
|
-
className={props.current ? "is-active" :
|
|
12
|
+
className={props.current ? "is-active text-gray-850" : "text-blue-700"}
|
|
13
13
|
aria-current={props.current ? "page" : undefined}
|
|
14
14
|
href={props.href}
|
|
15
15
|
>
|
|
@@ -10,7 +10,11 @@ const FooterNav = (props: FooterNavProps) => (
|
|
|
10
10
|
<section className="footer-sock">
|
|
11
11
|
<div className="footer-sock__inner">
|
|
12
12
|
<p className="footer-copyright">{props.copyright}</p>
|
|
13
|
-
{props.children &&
|
|
13
|
+
{props.children && (
|
|
14
|
+
<nav className="footer-nav" aria-label={"Footer"}>
|
|
15
|
+
{props.children}
|
|
16
|
+
</nav>
|
|
17
|
+
)}
|
|
14
18
|
</div>
|
|
15
19
|
</section>
|
|
16
20
|
)
|
|
@@ -16,7 +16,7 @@ const LanguageNav = ({ ariaLabel, languages }: LanguageNavProps) => {
|
|
|
16
16
|
return (
|
|
17
17
|
<div className="language-bar">
|
|
18
18
|
<div className="language-bar__inner">
|
|
19
|
-
<nav {...
|
|
19
|
+
<nav {...{ "aria-label": ariaLabel ?? "Language" }} className="language-nav">
|
|
20
20
|
<ul className="language-nav__list">
|
|
21
21
|
{languages.map((item) => (
|
|
22
22
|
<li key={item.label}>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Canvas, Story, ArgsTable } from "@storybook/addon-docs"
|
|
2
|
+
import { ProgressNav } from "./ProgressNav"
|
|
3
|
+
|
|
4
|
+
# Progress Nav
|
|
5
|
+
|
|
6
|
+
The progress nav component is used to illustrate progress in a multi-step process. It displays the step labels, and uses color and layout to indicate which steps are completed, in-progress, and unstarted.
|
|
7
|
+
|
|
8
|
+
## Dot Style
|
|
9
|
+
|
|
10
|
+
<Canvas>
|
|
11
|
+
<Story id="navigation-progress-nav--default" />
|
|
12
|
+
</Canvas>
|
|
13
|
+
|
|
14
|
+
## Bar Style
|
|
15
|
+
|
|
16
|
+
<Canvas>
|
|
17
|
+
<Story id="navigation-progress-nav--bar-style" />
|
|
18
|
+
</Canvas>
|
|
19
|
+
|
|
20
|
+
You can apply CSS variables to the `.progrss-nav` selector to customize the appearance of the component.
|
|
21
|
+
|
|
22
|
+
| Name | Type | Description | Default |
|
|
23
|
+
| ----------------------------- | ----- | ------------------------------------------- | ------------------------------ |
|
|
24
|
+
| `--completed-step-color` | Color | The color of completed step | `--bloom-color-primary-darker` |
|
|
25
|
+
| `--completed-step-font-color` | Color | The color of completed step label | `--bloom-color-primary-darker` |
|
|
26
|
+
| `--active-step-color` | Color | The color of active step | `--bloom-color-primary` |
|
|
27
|
+
| `--active-step-font-color` | Color | The color of active step label | `--bloom-color-primary-darker` |
|
|
28
|
+
| `--future-step-color` | Color | The color of future step | `--bloom-color-gray-450` |
|
|
29
|
+
| `--future-step-font-color` | Color | The color of future step label | `--bloom-color-gray-750` |
|
|
30
|
+
| | | | |
|
|
31
|
+
| `--dot-size` | Size | The diameter of each dot step | `--bloom-s3` |
|
|
32
|
+
| `--dot-padding-left-mobile` | Size | The padding-left of each dot step on mobile | `--bloom-s2` |
|
|
33
|
+
| `--dot-label-padding-top` | Size | The padding-top of each dot step label | `--bloom-s4` |
|
|
34
|
+
| `--dot-label-padding-left` | Size | The padding-left of each dot step label | `--bloom-s1` |
|
|
35
|
+
| `--dot-font-size-desktop` | Size | The font size of dot step labels on desktop | `--bloom-font-size-base` |
|
|
36
|
+
| `--dot-font-size-mobile` | Size | The font size of dot step labels on mobile | `--bloom-font-size-2xs` |
|
|
37
|
+
| `--dot-line-color` | Color | The color of the dot connecting line | `--bloom-color-gray-450` |
|
|
38
|
+
| `--dot-active-font-weight` | Size | The font weight of active dot step label | `600` |
|
|
39
|
+
| `--dot-text-transform` | Size | The capitalization of dot step label | `capitalize` |
|
|
40
|
+
| | | | |
|
|
41
|
+
| `--bar-height` | Size | The height of bar step | `--bloom-s4` |
|
|
42
|
+
| `--bar-spacing` | Size | The spacing between each bar step | `--bloom-s0_5` |
|
|
43
|
+
| `--bar-label-padding-top` | Size | The padding-top of each bar step label | `--bloom-s2` |
|
|
44
|
+
| `--bar-label-padding-left` | Size | The padding-left of each bar step label | `--bloom-s0_5` |
|
|
45
|
+
| `--bar-font-size` | Size | The font size of bar step labels on desktop | `--bloom-font-size-base` |
|
|
46
|
+
| `--bar-active-font-weight` | Size | The font weight of active bar step label | `600` |
|
|
47
|
+
| `--bar-text-transform` | Size | The capitalization of dot step label | `capitalize` |
|
|
@@ -1,39 +1,54 @@
|
|
|
1
1
|
.progress-nav {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
2
|
+
--completed-step-color: var(--bloom-color-gray-850);
|
|
3
|
+
--completed-step-font-color: var(--bloom-color-gray-850);
|
|
4
|
+
--active-step-color: var(--bloom-color-primary);
|
|
5
|
+
--active-step-font-color: var(--bloom-color-gray-900);
|
|
6
|
+
--future-step-color: var(--bloom-color-gray-450);
|
|
7
|
+
--future-step-font-color: var(--bloom-color-gray-750);
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
--dot-size: var(--bloom-s3);
|
|
10
|
+
--dot-padding-left-mobile: var(--bloom-s2);
|
|
11
|
+
--dot-label-padding-top: var(--bloom-s4);
|
|
12
|
+
--dot-label-padding-left: var(--bloom-s1);
|
|
13
|
+
--dot-font-size-desktop: var(--bloom-font-size-base);
|
|
14
|
+
--dot-font-size-mobile: var(--bloom-font-size-2xs);
|
|
15
|
+
--dot-line-color: var(--bloom-color-gray-450);
|
|
16
|
+
--dot-active-font-weight: bold;
|
|
17
|
+
--dot-text-transform: capitalize;
|
|
18
|
+
|
|
19
|
+
--bar-height: var(--bloom-s4);
|
|
20
|
+
--bar-spacing: var(--bloom-s0_5);
|
|
21
|
+
--bar-label-padding-top: var(--bloom-s2);
|
|
22
|
+
--bar-label-padding-left: var(--bloom-s0_5);
|
|
23
|
+
--bar-font-size: var(--bloom-font-size-base);
|
|
24
|
+
--bar-active-font-weight: bold;
|
|
25
|
+
--bar-text-transform: capitalize;
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
display: table;
|
|
28
|
+
width: 100%;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.progress-nav__dot-item {
|
|
32
|
+
font-size: var(--dot-font-size-mobile);
|
|
33
|
+
padding: var(--dot-size) var(--dot-padding-left-mobile);
|
|
34
|
+
position: relative;
|
|
35
|
+
text-align: center;
|
|
36
|
+
text-transform: var(--dot-text-transform);
|
|
37
|
+
display: table-cell;
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
float: none;
|
|
40
|
+
@media (min-width: $screen-md) {
|
|
41
|
+
font-size: var(--dot-font-size-desktop);
|
|
42
|
+
padding: 0rem;
|
|
25
43
|
}
|
|
26
44
|
|
|
27
45
|
&:before {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@apply border-white;
|
|
35
|
-
@apply rounded-full;
|
|
36
|
-
@apply top-0;
|
|
46
|
+
position: absolute;
|
|
47
|
+
height: var(--dot-size);
|
|
48
|
+
width: var(--dot-size);
|
|
49
|
+
background-color: var(--completed-step-color);
|
|
50
|
+
border-radius: 50%;
|
|
51
|
+
top: 0;
|
|
37
52
|
left: 50%;
|
|
38
53
|
content: "";
|
|
39
54
|
transform: translateX(-50%);
|
|
@@ -41,10 +56,10 @@
|
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
&:after {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
position: absolute;
|
|
60
|
+
background-color: var(--dot-line-color);
|
|
61
|
+
width: 100%;
|
|
62
|
+
left: 0;
|
|
48
63
|
top: 0.4rem;
|
|
49
64
|
content: "";
|
|
50
65
|
height: 1px;
|
|
@@ -58,45 +73,76 @@
|
|
|
58
73
|
|
|
59
74
|
&:last-of-type {
|
|
60
75
|
&:after {
|
|
61
|
-
|
|
76
|
+
left: auto;
|
|
62
77
|
right: 50%;
|
|
63
78
|
}
|
|
64
79
|
}
|
|
80
|
+
.progress-nav__item-container {
|
|
81
|
+
padding-top: var(--dot-label-padding-top);
|
|
82
|
+
padding-left: var(--dot-label-padding-left);
|
|
83
|
+
color: var(--completed-step-font-color);
|
|
84
|
+
display: block;
|
|
85
|
+
position: relative;
|
|
86
|
+
pointer-events: none;
|
|
87
|
+
cursor: default;
|
|
88
|
+
z-index: 3;
|
|
89
|
+
}
|
|
65
90
|
|
|
66
91
|
&.is-active {
|
|
67
92
|
&:before {
|
|
68
|
-
|
|
69
|
-
@apply w-3;
|
|
70
|
-
@apply bg-primary;
|
|
71
|
-
@apply top-0;
|
|
93
|
+
background-color: var(--active-step-color);
|
|
72
94
|
}
|
|
73
|
-
|
|
74
95
|
.progress-nav__item-container {
|
|
75
|
-
|
|
76
|
-
|
|
96
|
+
color: var(--active-step-font-color);
|
|
97
|
+
font-weight: var(--dot-active-font-weight);
|
|
77
98
|
}
|
|
78
99
|
}
|
|
79
100
|
|
|
80
101
|
&.is-disabled {
|
|
81
102
|
&:before {
|
|
82
|
-
|
|
83
|
-
@apply w-3;
|
|
84
|
-
@apply bg-gray-450;
|
|
85
|
-
@apply top-0;
|
|
103
|
+
background-color: var(--future-step-color);
|
|
86
104
|
}
|
|
87
|
-
|
|
88
105
|
.progress-nav__item-container {
|
|
89
|
-
|
|
90
|
-
@apply cursor-default;
|
|
106
|
+
color: var(--future-step-font-color);
|
|
91
107
|
}
|
|
92
108
|
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.progress-nav__bar {
|
|
112
|
+
border-spacing: var(--bar-spacing);
|
|
113
|
+
table-layout: fixed;
|
|
114
|
+
}
|
|
93
115
|
|
|
116
|
+
.progress-nav__bar-item {
|
|
117
|
+
// drop the labels on mobile view
|
|
118
|
+
font-size: 0;
|
|
119
|
+
padding-top: var(--bar-label-padding-top);
|
|
120
|
+
padding-left: var(--bar-label-padding-left);
|
|
121
|
+
position: relative;
|
|
122
|
+
display: table-cell;
|
|
123
|
+
white-space: nowrap;
|
|
124
|
+
float: none;
|
|
125
|
+
max-width: 20%;
|
|
126
|
+
border-top-width: var(--bar-height);
|
|
127
|
+
border-top-color: var(--completed-step-color);
|
|
128
|
+
text-transform: var(--bar-text-transform);
|
|
129
|
+
@media (min-width: $screen-md) {
|
|
130
|
+
font-size: var(--bar-font-size);
|
|
131
|
+
}
|
|
132
|
+
&.is-active {
|
|
133
|
+
border-top-color: var(--active-step-color);
|
|
134
|
+
.progress-nav__item-container {
|
|
135
|
+
font-weight: var(--bar-active-font-weight);
|
|
136
|
+
color: var(--active-step-font-color);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
&.is-disabled {
|
|
140
|
+
border-top-color: var(--future-step-color);
|
|
141
|
+
.progress-nav__item-container {
|
|
142
|
+
color: var(--future-step-font-color);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
94
145
|
.progress-nav__item-container {
|
|
95
|
-
|
|
96
|
-
@apply pl-1;
|
|
97
|
-
@apply text-gray-700;
|
|
98
|
-
@apply block;
|
|
99
|
-
@apply relative;
|
|
100
|
-
z-index: 3;
|
|
146
|
+
color: var(--completed-step-font-color);
|
|
101
147
|
}
|
|
102
148
|
}
|
|
@@ -2,12 +2,20 @@ import React from "react"
|
|
|
2
2
|
import "./ProgressNav.scss"
|
|
3
3
|
import { t } from "../helpers/translator"
|
|
4
4
|
|
|
5
|
+
type ProgressNavStyle = "bar" | "dot"
|
|
6
|
+
|
|
5
7
|
const ProgressNavItem = (props: {
|
|
6
8
|
section: number
|
|
7
9
|
currentPageSection: number
|
|
8
10
|
completedSections: number
|
|
9
11
|
label: string
|
|
10
12
|
mounted: boolean
|
|
13
|
+
style: ProgressNavStyle
|
|
14
|
+
strings?: {
|
|
15
|
+
screenReaderCompleted?: string
|
|
16
|
+
screenReaderNotCompleted?: string
|
|
17
|
+
screenReaderTitle?: string
|
|
18
|
+
}
|
|
11
19
|
}) => {
|
|
12
20
|
let bgColor = "is-disabled"
|
|
13
21
|
if (props.mounted) {
|
|
@@ -18,18 +26,32 @@ const ProgressNavItem = (props: {
|
|
|
18
26
|
}
|
|
19
27
|
}
|
|
20
28
|
|
|
21
|
-
const
|
|
22
|
-
props.section
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
const srTextBuilder = (): string | React.ReactFragment => {
|
|
30
|
+
if (props.section < props.currentPageSection) {
|
|
31
|
+
return (
|
|
32
|
+
<span className="sr-only">
|
|
33
|
+
{props.strings?.screenReaderCompleted ?? t("progressNav.completed")}
|
|
34
|
+
</span>
|
|
35
|
+
)
|
|
36
|
+
} else if (props.section > props.currentPageSection) {
|
|
37
|
+
return (
|
|
38
|
+
<span className="sr-only">
|
|
39
|
+
{props.strings?.screenReaderNotCompleted ?? t("progressNav.notCompleted")}
|
|
40
|
+
</span>
|
|
41
|
+
)
|
|
42
|
+
} else {
|
|
43
|
+
return ""
|
|
44
|
+
}
|
|
45
|
+
}
|
|
27
46
|
|
|
28
47
|
return (
|
|
29
|
-
<li className={`progress-
|
|
30
|
-
<span
|
|
31
|
-
{
|
|
32
|
-
{
|
|
48
|
+
<li className={`progress-nav__${props.style}-item ${bgColor}`}>
|
|
49
|
+
<span
|
|
50
|
+
aria-disabled={bgColor === "is-disabled"}
|
|
51
|
+
aria-current={bgColor === "is-active"}
|
|
52
|
+
className={"progress-nav__item-container"}
|
|
53
|
+
>
|
|
54
|
+
{props.label} {srTextBuilder()}
|
|
33
55
|
</span>
|
|
34
56
|
</li>
|
|
35
57
|
)
|
|
@@ -40,23 +62,31 @@ const ProgressNav = (props: {
|
|
|
40
62
|
completedSections: number
|
|
41
63
|
labels: string[]
|
|
42
64
|
mounted: boolean
|
|
65
|
+
style?: ProgressNavStyle
|
|
66
|
+
strings?: {
|
|
67
|
+
screenReaderHeading?: string
|
|
68
|
+
}
|
|
43
69
|
}) => {
|
|
70
|
+
let navClasses = "progress-nav"
|
|
71
|
+
if (props.style === "bar") navClasses += " progress-nav__bar"
|
|
44
72
|
return (
|
|
45
|
-
<div>
|
|
46
|
-
<h2 className="sr-only">
|
|
47
|
-
|
|
73
|
+
<div aria-label="progress">
|
|
74
|
+
<h2 className="sr-only">
|
|
75
|
+
{props.strings?.screenReaderHeading ?? t("progressNav.srHeading")}
|
|
76
|
+
</h2>
|
|
77
|
+
<ol className={!props.mounted ? "invisible" : navClasses}>
|
|
48
78
|
{props.labels.map((label, i) => (
|
|
49
79
|
<ProgressNavItem
|
|
50
80
|
key={label}
|
|
51
|
-
// Sections are 1-indexed
|
|
52
81
|
section={i + 1}
|
|
53
82
|
currentPageSection={props.currentPageSection}
|
|
54
83
|
completedSections={props.completedSections}
|
|
55
84
|
label={label}
|
|
56
85
|
mounted={props.mounted}
|
|
86
|
+
style={props.style ?? "dot"}
|
|
57
87
|
/>
|
|
58
88
|
))}
|
|
59
|
-
</
|
|
89
|
+
</ol>
|
|
60
90
|
</div>
|
|
61
91
|
)
|
|
62
92
|
}
|
|
@@ -58,7 +58,7 @@ const TabNav = (props: { children: React.ReactNode; className?: string }) => {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
return (
|
|
61
|
-
<nav className={classes.join(" ")}>
|
|
61
|
+
<nav className={classes.join(" ")} aria-label={"Secondary navigation"}>
|
|
62
62
|
<ul role="tablist" aria-label="Secondary navigation">
|
|
63
63
|
{props.children}
|
|
64
64
|
</ul>
|