@bloom-housing/ui-components 2.0.0-pre-tailwind

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 (223) hide show
  1. package/.jest/setup-tests.js +24 -0
  2. package/CHANGELOG.md +20 -0
  3. package/README.md +195 -0
  4. package/index.ts +148 -0
  5. package/jest.config.js +41 -0
  6. package/package.json +98 -0
  7. package/public/images/alameda-logo-white.svg +1 -0
  8. package/public/images/arrow-down.png +0 -0
  9. package/public/images/arrow-down.svg +1 -0
  10. package/public/images/check.png +0 -0
  11. package/public/images/check.svg +11 -0
  12. package/public/images/eho-logo-white.svg +1 -0
  13. package/public/images/eho-logo.svg +1 -0
  14. package/public/images/logo_glyph.svg +11 -0
  15. package/src/actions/Button.scss +157 -0
  16. package/src/actions/Button.tsx +80 -0
  17. package/src/actions/ExpandableContent.tsx +29 -0
  18. package/src/actions/ExpandableText.scss +18 -0
  19. package/src/actions/ExpandableText.tsx +52 -0
  20. package/src/actions/LinkButton.tsx +30 -0
  21. package/src/actions/LocalizedLink.tsx +11 -0
  22. package/src/authentication/AuthContext.ts +327 -0
  23. package/src/authentication/RequireLogin.tsx +62 -0
  24. package/src/authentication/index.ts +5 -0
  25. package/src/authentication/timeout.tsx +127 -0
  26. package/src/authentication/token.ts +17 -0
  27. package/src/authentication/useRequireLoggedInUser.ts +19 -0
  28. package/src/blocks/ActionBlock.scss +108 -0
  29. package/src/blocks/ActionBlock.tsx +51 -0
  30. package/src/blocks/AppStatusItem.scss +140 -0
  31. package/src/blocks/AppStatusItem.tsx +75 -0
  32. package/src/blocks/DashBlock.tsx +42 -0
  33. package/src/blocks/DashBlocks.scss +56 -0
  34. package/src/blocks/DashBlocks.tsx +7 -0
  35. package/src/blocks/FormCard.scss +201 -0
  36. package/src/blocks/FormCard.tsx +29 -0
  37. package/src/blocks/HousingCounselor.tsx +51 -0
  38. package/src/blocks/ImageCard.scss +91 -0
  39. package/src/blocks/ImageCard.tsx +77 -0
  40. package/src/blocks/InfoCard.scss +42 -0
  41. package/src/blocks/InfoCard.tsx +44 -0
  42. package/src/blocks/StatusBar.scss +30 -0
  43. package/src/blocks/StatusBar.tsx +31 -0
  44. package/src/blocks/ViewItem.scss +59 -0
  45. package/src/blocks/ViewItem.tsx +32 -0
  46. package/src/config/ConfigContext.tsx +36 -0
  47. package/src/config/NavigationContext.tsx +54 -0
  48. package/src/config/index.ts +2 -0
  49. package/src/footers/ExygyFooter.tsx +12 -0
  50. package/src/footers/SiteFooter.scss +28 -0
  51. package/src/footers/SiteFooter.tsx +10 -0
  52. package/src/forms/CloudinaryUpload.ts +50 -0
  53. package/src/forms/DOBField.tsx +132 -0
  54. package/src/forms/DateField.tsx +120 -0
  55. package/src/forms/Dropzone.scss +17 -0
  56. package/src/forms/Dropzone.tsx +67 -0
  57. package/src/forms/Field.tsx +115 -0
  58. package/src/forms/FieldGroup.tsx +82 -0
  59. package/src/forms/Form.tsx +22 -0
  60. package/src/forms/HouseholdMemberForm.tsx +41 -0
  61. package/src/forms/HouseholdSizeField.tsx +74 -0
  62. package/src/forms/PhoneField.tsx +69 -0
  63. package/src/forms/PhoneMask.tsx +24 -0
  64. package/src/forms/Select.tsx +80 -0
  65. package/src/forms/Textarea.scss +40 -0
  66. package/src/forms/Textarea.tsx +64 -0
  67. package/src/forms/TimeField.tsx +176 -0
  68. package/src/global/AppearanceTypes.ts +46 -0
  69. package/src/global/ApplicationStatusType.ts +6 -0
  70. package/src/global/accordion.scss +4 -0
  71. package/src/global/blocks.scss +137 -0
  72. package/src/global/custom_counter.scss +50 -0
  73. package/src/global/forms.scss +362 -0
  74. package/src/global/headers.scss +89 -0
  75. package/src/global/homepage.scss +8 -0
  76. package/src/global/index.scss +72 -0
  77. package/src/global/lists.scss +21 -0
  78. package/src/global/markdown.scss +33 -0
  79. package/src/global/mixins.scss +175 -0
  80. package/src/global/navbar.scss +280 -0
  81. package/src/global/print.scss +59 -0
  82. package/src/global/tables.scss +197 -0
  83. package/src/global/text.scss +141 -0
  84. package/src/global/vendor/AgPagination.tsx +133 -0
  85. package/src/global/vendor/_setup_bulma.scss +31 -0
  86. package/src/global/vendor/ag_grid.scss +140 -0
  87. package/src/headers/Hero.scss +56 -0
  88. package/src/headers/Hero.tsx +76 -0
  89. package/src/headers/PageHeader.scss +31 -0
  90. package/src/headers/PageHeader.tsx +39 -0
  91. package/src/headers/SiteHeader.tsx +136 -0
  92. package/src/helpers/address.tsx +46 -0
  93. package/src/helpers/blankApplication.ts +108 -0
  94. package/src/helpers/capitalize.tsx +7 -0
  95. package/src/helpers/dateToString.ts +11 -0
  96. package/src/helpers/debounce.ts +12 -0
  97. package/src/helpers/formOptions.tsx +229 -0
  98. package/src/helpers/formatYesNoLabel.ts +9 -0
  99. package/src/helpers/getTranslationWithArguments.ts +14 -0
  100. package/src/helpers/links.ts +7 -0
  101. package/src/helpers/localeRoute.tsx +13 -0
  102. package/src/helpers/mergeDeep.ts +12 -0
  103. package/src/helpers/nextjs.ts +7 -0
  104. package/src/helpers/numberOrdinal.ts +17 -0
  105. package/src/helpers/occupancyFormatting.tsx +46 -0
  106. package/src/helpers/pdfs.ts +19 -0
  107. package/src/helpers/photos.ts +19 -0
  108. package/src/helpers/preferences.tsx +426 -0
  109. package/src/helpers/resolveObject.ts +5 -0
  110. package/src/helpers/state.tsx +7 -0
  111. package/src/helpers/tableSummaries.tsx +80 -0
  112. package/src/helpers/translator.tsx +37 -0
  113. package/src/helpers/useKeyPress.ts +17 -0
  114. package/src/helpers/useMutate.ts +40 -0
  115. package/src/helpers/useOutsideClick.ts +25 -0
  116. package/src/helpers/validators.ts +3 -0
  117. package/src/icons/HeaderBadge.scss +29 -0
  118. package/src/icons/HeaderBadge.tsx +38 -0
  119. package/src/icons/Icon.scss +76 -0
  120. package/src/icons/Icon.tsx +145 -0
  121. package/src/icons/Icons.tsx +556 -0
  122. package/src/lists/PreferencesList.scss +72 -0
  123. package/src/lists/PreferencesList.tsx +60 -0
  124. package/src/locales/es.json +745 -0
  125. package/src/locales/general.json +1307 -0
  126. package/src/locales/general_OLD.json +868 -0
  127. package/src/locales/vi.json +745 -0
  128. package/src/locales/zh.json +745 -0
  129. package/src/navigation/Breadcrumbs.scss +25 -0
  130. package/src/navigation/Breadcrumbs.tsx +27 -0
  131. package/src/navigation/FooterNav.scss +47 -0
  132. package/src/navigation/FooterNav.tsx +19 -0
  133. package/src/navigation/LanguageNav.scss +32 -0
  134. package/src/navigation/LanguageNav.tsx +53 -0
  135. package/src/navigation/ProgressNav.scss +102 -0
  136. package/src/navigation/ProgressNav.tsx +50 -0
  137. package/src/navigation/TabNav.scss +38 -0
  138. package/src/navigation/TabNav.tsx +69 -0
  139. package/src/navigation/Tabs.scss +65 -0
  140. package/src/navigation/Tabs.tsx +93 -0
  141. package/src/navigation/UserNav.tsx +37 -0
  142. package/src/notifications/AlertBox.scss +78 -0
  143. package/src/notifications/AlertBox.tsx +79 -0
  144. package/src/notifications/AlertNotice.scss +58 -0
  145. package/src/notifications/AlertNotice.tsx +37 -0
  146. package/src/notifications/ApplicationStatus.scss +10 -0
  147. package/src/notifications/ApplicationStatus.tsx +64 -0
  148. package/src/notifications/ErrorMessage.tsx +15 -0
  149. package/src/notifications/SiteAlert.tsx +54 -0
  150. package/src/notifications/StatusAside.scss +11 -0
  151. package/src/notifications/StatusAside.tsx +25 -0
  152. package/src/notifications/StatusMessage.scss +25 -0
  153. package/src/notifications/StatusMessage.tsx +59 -0
  154. package/src/notifications/alertTypes.ts +7 -0
  155. package/src/notifications/index.ts +4 -0
  156. package/src/overlays/Drawer.scss +105 -0
  157. package/src/overlays/Drawer.tsx +51 -0
  158. package/src/overlays/LoadingOverlay.scss +25 -0
  159. package/src/overlays/LoadingOverlay.tsx +29 -0
  160. package/src/overlays/Modal.scss +55 -0
  161. package/src/overlays/Modal.tsx +61 -0
  162. package/src/overlays/Overlay.scss +50 -0
  163. package/src/overlays/Overlay.tsx +100 -0
  164. package/src/page_components/listing/AdditionalFees.tsx +56 -0
  165. package/src/page_components/listing/ListingCard.scss +47 -0
  166. package/src/page_components/listing/ListingCard.tsx +34 -0
  167. package/src/page_components/listing/ListingDetailHeader.tsx +25 -0
  168. package/src/page_components/listing/ListingDetails.tsx +29 -0
  169. package/src/page_components/listing/ListingMap.scss +36 -0
  170. package/src/page_components/listing/ListingMap.tsx +138 -0
  171. package/src/page_components/listing/ListingsGroup.scss +65 -0
  172. package/src/page_components/listing/ListingsGroup.tsx +49 -0
  173. package/src/page_components/listing/UnitTables.tsx +111 -0
  174. package/src/page_components/listing/listing_sidebar/ApplicationSection.tsx +49 -0
  175. package/src/page_components/listing/listing_sidebar/Apply.tsx +225 -0
  176. package/src/page_components/listing/listing_sidebar/LeasingAgent.tsx +77 -0
  177. package/src/page_components/listing/listing_sidebar/ListingUpdated.tsx +20 -0
  178. package/src/page_components/listing/listing_sidebar/ReferralApplication.tsx +28 -0
  179. package/src/page_components/listing/listing_sidebar/SidebarAddress.tsx +56 -0
  180. package/src/page_components/listing/listing_sidebar/Waitlist.tsx +94 -0
  181. package/src/page_components/listing/listing_sidebar/WhatToExpect.tsx +22 -0
  182. package/src/page_components/listing/listing_sidebar/events/DownloadLotteryResults.tsx +34 -0
  183. package/src/page_components/listing/listing_sidebar/events/EventDateSection.tsx +24 -0
  184. package/src/page_components/listing/listing_sidebar/events/LotteryResultsEvent.tsx +26 -0
  185. package/src/page_components/listing/listing_sidebar/events/OpenHouseEvent.tsx +27 -0
  186. package/src/page_components/listing/listing_sidebar/events/PublicLotteryEvent.tsx +22 -0
  187. package/src/prototypes/AppCard.scss +64 -0
  188. package/src/prototypes/Back.scss +19 -0
  189. package/src/prototypes/ButtonGroup.scss +6 -0
  190. package/src/prototypes/ButtonPager.scss +22 -0
  191. package/src/prototypes/FieldSection.scss +35 -0
  192. package/src/prototypes/FieldSection.tsx +31 -0
  193. package/src/prototypes/GridItem.tsx +15 -0
  194. package/src/prototypes/SideNav.scss +32 -0
  195. package/src/prototypes/SideNav.tsx +14 -0
  196. package/src/prototypes/SummaryCard.scss +34 -0
  197. package/src/sections/ContentSection.scss +15 -0
  198. package/src/sections/ContentSection.tsx +25 -0
  199. package/src/sections/FooterSection.scss +6 -0
  200. package/src/sections/FooterSection.tsx +16 -0
  201. package/src/sections/GridSection.scss +72 -0
  202. package/src/sections/GridSection.tsx +82 -0
  203. package/src/sections/InfoCardGrid.scss +45 -0
  204. package/src/sections/InfoCardGrid.tsx +20 -0
  205. package/src/sections/ListSection.scss +7 -0
  206. package/src/sections/ListSection.tsx +23 -0
  207. package/src/sections/MarkdownSection.scss +13 -0
  208. package/src/sections/MarkdownSection.tsx +21 -0
  209. package/src/sections/ResponsiveContentList.tsx +67 -0
  210. package/src/sections/ResponsiveWrappers.tsx +23 -0
  211. package/src/tables/GroupedTable.tsx +86 -0
  212. package/src/tables/MinimalTable.tsx +32 -0
  213. package/src/tables/ResponsiveTable.tsx +24 -0
  214. package/src/tables/StandardTable.tsx +229 -0
  215. package/src/text/Description.scss +52 -0
  216. package/src/text/Description.tsx +24 -0
  217. package/src/text/Message.scss +16 -0
  218. package/src/text/Message.tsx +16 -0
  219. package/src/text/Tag.scss +94 -0
  220. package/src/text/Tag.tsx +22 -0
  221. package/tailwind.config.js +128 -0
  222. package/tailwind.tosass.js +29 -0
  223. package/tsconfig.json +31 -0
@@ -0,0 +1,25 @@
1
+ .breadcrumbs {
2
+ ol {
3
+ @apply flex;
4
+ @apply items-center;
5
+ line-height: 1.5rem;
6
+ }
7
+
8
+ li {
9
+ @apply text-sm;
10
+ @apply flex;
11
+ @apply tracking-wider;
12
+ @apply self-center;
13
+
14
+ &:not(:last-child)::after {
15
+ content: "\203A";
16
+ @apply text-lg;
17
+ @apply px-2;
18
+ line-height: 1.4rem;
19
+ }
20
+ }
21
+
22
+ a.is-active {
23
+ color: inherit;
24
+ }
25
+ }
@@ -0,0 +1,27 @@
1
+ import React from "react"
2
+ import { LocalizedLink } from "../actions/LocalizedLink"
3
+ import "./Breadcrumbs.scss"
4
+
5
+ export interface BreadcrumbsProps {
6
+ children: React.ReactNode
7
+ }
8
+
9
+ const BreadcrumbLink = (props: { href: string; children: React.ReactNode; current?: boolean }) => (
10
+ <li>
11
+ <LocalizedLink
12
+ className={props.current ? "is-active" : undefined}
13
+ aria-current={props.current ? "page" : undefined}
14
+ href={props.href}
15
+ >
16
+ {props.children}
17
+ </LocalizedLink>
18
+ </li>
19
+ )
20
+
21
+ const Breadcrumbs = (props: BreadcrumbsProps) => (
22
+ <nav className="breadcrumbs" aria-label="Breadcrumb">
23
+ <ol>{props.children}</ol>
24
+ </nav>
25
+ )
26
+
27
+ export { Breadcrumbs as default, Breadcrumbs, BreadcrumbLink }
@@ -0,0 +1,47 @@
1
+ .footer-sock {
2
+ @apply bg-gray-950;
3
+ @apply py-8;
4
+ @apply px-2;
5
+
6
+ p {
7
+ margin-bottom: 0 !important;
8
+ }
9
+
10
+ a {
11
+ @apply text-white;
12
+ @apply block;
13
+ @apply mt-2;
14
+
15
+ @screen lg {
16
+ @apply inline;
17
+ @apply mt-0;
18
+ @apply ml-5;
19
+ }
20
+ }
21
+
22
+ .footer-sock__inner {
23
+ @apply max-w-5xl;
24
+ @apply m-auto;
25
+
26
+ @screen lg {
27
+ @apply flex;
28
+ }
29
+ }
30
+
31
+ .footer-copyright {
32
+ @screen lg {
33
+ @apply w-6/12;
34
+ @apply text-left;
35
+ }
36
+ }
37
+
38
+ .footer-nav {
39
+ @apply mt-5;
40
+
41
+ @screen lg {
42
+ @apply mt-0;
43
+ @apply w-6/12;
44
+ @apply text-right;
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from "react"
2
+ import "./FooterNav.scss"
3
+
4
+ export interface FooterNavProps {
5
+ children: React.ReactNode
6
+ copyright: string
7
+ }
8
+
9
+ const FooterNav = (props: FooterNavProps) => (
10
+ <section className="footer-sock">
11
+ <div className="footer-sock__inner">
12
+ <p className="footer-copyright">{props.copyright}</p>
13
+
14
+ <nav className="footer-nav">{props.children}</nav>
15
+ </div>
16
+ </section>
17
+ )
18
+
19
+ export { FooterNav as default, FooterNav }
@@ -0,0 +1,32 @@
1
+ .language-bar {
2
+ @apply bg-gray-800;
3
+ }
4
+
5
+ .language-bar__inner {
6
+ @apply max-w-5xl;
7
+ @apply m-auto;
8
+ @apply flex;
9
+ @apply justify-end;
10
+ }
11
+
12
+ .language-nav__list {
13
+ @apply flex;
14
+ }
15
+
16
+ .language-nav__list-button {
17
+ @apply py-2;
18
+ @apply px-2;
19
+ @apply text-gray-500;
20
+ @apply font-semibold;
21
+ @apply cursor-pointer;
22
+ @apply bg-none;
23
+
24
+ &:focus {
25
+ outline: none;
26
+ box-shadow: 0 0 0 2px #fff, 0 0 3px 4px $tailwind-accent-cool;
27
+ }
28
+
29
+ &.is-active {
30
+ @apply text-white;
31
+ }
32
+ }
@@ -0,0 +1,53 @@
1
+ import React, { useContext } from "react"
2
+ import { NavigationContext } from "../config/NavigationContext"
3
+ import "./LanguageNav.scss"
4
+ import { t } from "../helpers/translator"
5
+
6
+ export type LangItem = {
7
+ prefix: string
8
+ label: string
9
+ }
10
+
11
+ export interface LanguageNavLang {
12
+ list: LangItem[]
13
+ codes: string[]
14
+ }
15
+
16
+ export interface LanguageNavProps {
17
+ language: LanguageNavLang
18
+ }
19
+
20
+ const LanguageNav = ({ language }: LanguageNavProps) => {
21
+ const routePrefix = t("config.routePrefix")
22
+ const { router } = useContext(NavigationContext)
23
+
24
+ return (
25
+ <div className="language-bar">
26
+ <div className="language-bar__inner">
27
+ <nav className="language-nav">
28
+ <ul className="language-nav__list">
29
+ {language.list?.map((item) => (
30
+ <li key={item.prefix}>
31
+ <button
32
+ onClick={() => {
33
+ void router.push(router.asPath, router.asPath, { locale: item.prefix || "en" })
34
+ }}
35
+ className={
36
+ routePrefix === item.prefix
37
+ ? "language-nav__list-button is-active"
38
+ : "language-nav__list-button"
39
+ }
40
+ type="button"
41
+ >
42
+ {item.label}
43
+ </button>
44
+ </li>
45
+ ))}
46
+ </ul>
47
+ </nav>
48
+ </div>
49
+ </div>
50
+ )
51
+ }
52
+
53
+ export { LanguageNav as default, LanguageNav }
@@ -0,0 +1,102 @@
1
+ .progress-nav {
2
+ // @include clearfix;
3
+ @apply list-none;
4
+ @apply table;
5
+ @apply m-0;
6
+ @apply p-0;
7
+ @apply w-full;
8
+ }
9
+
10
+ .progress-nav__item {
11
+ @apply text-2xs;
12
+ @apply px-2;
13
+ @apply py-3;
14
+ @apply relative;
15
+ @apply text-center;
16
+ @apply uppercase;
17
+ @apply table-cell;
18
+ @apply whitespace-no-wrap;
19
+ @apply float-none;
20
+ max-width: 20%;
21
+
22
+ @screen md {
23
+ @apply text-sm;
24
+ @apply p-0;
25
+ }
26
+
27
+ &:before {
28
+ @apply absolute;
29
+ @apply h-3;
30
+ @apply w-3;
31
+ @apply bg-lush;
32
+ @apply border-solid;
33
+ @apply border;
34
+ @apply border-white;
35
+ @apply rounded-full;
36
+ @apply top-0;
37
+ left: 50%;
38
+ content: "";
39
+ transform: translateX(-50%);
40
+ z-index: 2;
41
+ }
42
+
43
+ &:after {
44
+ @apply absolute;
45
+ @apply bg-gray-450;
46
+ @apply w-full;
47
+ @apply left-0;
48
+ top: 0.4rem;
49
+ content: "";
50
+ height: 1px;
51
+ }
52
+
53
+ &:first-of-type {
54
+ &:after {
55
+ left: 50%;
56
+ }
57
+ }
58
+
59
+ &:last-of-type {
60
+ &:after {
61
+ @apply left-auto;
62
+ right: 50%;
63
+ }
64
+ }
65
+
66
+ &.is-active {
67
+ &:before {
68
+ @apply h-3;
69
+ @apply w-3;
70
+ @apply bg-primary;
71
+ @apply top-0;
72
+ }
73
+
74
+ a {
75
+ @apply text-black;
76
+ @apply font-bold;
77
+ }
78
+ }
79
+
80
+ &.is-disabled {
81
+ &:before {
82
+ @apply h-3;
83
+ @apply w-3;
84
+ @apply bg-gray-450;
85
+ @apply top-0;
86
+ }
87
+
88
+ a {
89
+ @apply pointer-events-none;
90
+ @apply cursor-default;
91
+ }
92
+ }
93
+
94
+ a {
95
+ @apply pt-4;
96
+ @apply pl-1;
97
+ @apply text-gray-700;
98
+ @apply block;
99
+ @apply relative;
100
+ z-index: 3;
101
+ }
102
+ }
@@ -0,0 +1,50 @@
1
+ import React from "react"
2
+ import { OnClientSide } from "../helpers/nextjs"
3
+ import "./ProgressNav.scss"
4
+
5
+ const ProgressNavItem = (props: {
6
+ section: number
7
+ currentPageSection: number
8
+ completedSections: number
9
+ label: string
10
+ }) => {
11
+ let bgColor = "is-disabled"
12
+ if (OnClientSide()) {
13
+ if (props.section === props.currentPageSection) {
14
+ bgColor = "is-active"
15
+ } else if (props.completedSections >= props.section) {
16
+ bgColor = ""
17
+ }
18
+ }
19
+
20
+ return (
21
+ <li className={`progress-nav__item ${bgColor}`}>
22
+ <a aria-disabled={bgColor === "is-disabled"} href={"#"}>
23
+ {props.label}
24
+ </a>
25
+ </li>
26
+ )
27
+ }
28
+
29
+ const ProgressNav = (props: {
30
+ currentPageSection: number
31
+ completedSections: number
32
+ labels: string[]
33
+ }) => {
34
+ return (
35
+ <ul className={!OnClientSide() ? "invisible" : "progress-nav"}>
36
+ {props.labels.map((label, i) => (
37
+ <ProgressNavItem
38
+ key={label}
39
+ // Sections are 1-indexed
40
+ section={i + 1}
41
+ currentPageSection={props.currentPageSection}
42
+ completedSections={props.completedSections}
43
+ label={label}
44
+ />
45
+ ))}
46
+ </ul>
47
+ )
48
+ }
49
+
50
+ export { ProgressNav as default, ProgressNav }
@@ -0,0 +1,38 @@
1
+ .tab-nav ul {
2
+ @apply flex;
3
+ @apply items-center;
4
+ @apply font-alt-sans;
5
+ @apply uppercase;
6
+ @apply text-sm;
7
+ @apply font-semibold;
8
+ @apply tracking-wider;
9
+ }
10
+
11
+ .tab-nav__tab {
12
+ a {
13
+ @apply block;
14
+ @apply px-6;
15
+ @apply py-3;
16
+ @apply border-b-2;
17
+ @apply text-gray-700;
18
+ @apply items-center;
19
+ border-color: transparent;
20
+ line-height: 1.5rem;
21
+
22
+ &:hover {
23
+ @apply text-gray-900;
24
+ @apply border-gray-450;
25
+ }
26
+
27
+ &.is-active {
28
+ @apply border-primary;
29
+ @apply text-gray-900;
30
+ }
31
+
32
+ .tag {
33
+ @apply ml-2;
34
+ @apply text-xs;
35
+ line-height: 1rem;
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,69 @@
1
+ import React from "react"
2
+ import { LocalizedLink } from "../actions/LocalizedLink"
3
+ import { AppearanceSizeType } from "../global/AppearanceTypes"
4
+ import { Tag } from "../text/Tag"
5
+ import "./TabNav.scss"
6
+
7
+ export interface TabNavItemProps {
8
+ href: string
9
+ current?: boolean
10
+ tagContent?: React.ReactNode
11
+ tagSize?: AppearanceSizeType
12
+ children: React.ReactNode
13
+ }
14
+
15
+ const TabNavItem = (props: TabNavItemProps) => {
16
+ const tabRef = React.useRef<HTMLLIElement>(null)
17
+
18
+ const handleKeyboard = (event: React.KeyboardEvent) => {
19
+ if (event.key == "ArrowLeft") {
20
+ const previousTab = tabRef.current?.previousSibling as HTMLElement
21
+ previousTab?.querySelector("a")?.focus()
22
+ } else if (event.key == "ArrowRight") {
23
+ const nextTab = tabRef.current?.nextSibling as HTMLElement
24
+ nextTab?.querySelector("a")?.focus()
25
+ }
26
+ }
27
+
28
+ return (
29
+ <li
30
+ role="tab"
31
+ aria-selected={props.current ? "true" : undefined}
32
+ ref={tabRef}
33
+ onKeyUp={handleKeyboard}
34
+ className="tab-nav__tab"
35
+ >
36
+ <LocalizedLink
37
+ className={props.current ? "is-active" : undefined}
38
+ aria-current={props.current ? "page" : undefined}
39
+ href={props.href}
40
+ tabIndex={props.current ? 0 : -1}
41
+ >
42
+ {props.children}
43
+ {props.tagContent && (
44
+ <Tag pillStyle={true} size={props.tagSize ? props.tagSize : AppearanceSizeType.normal}>
45
+ {props.tagContent}
46
+ </Tag>
47
+ )}
48
+ </LocalizedLink>
49
+ </li>
50
+ )
51
+ }
52
+
53
+ const TabNav = (props: { children: React.ReactNode; className?: string }) => {
54
+ const classes = ["tab-nav"]
55
+
56
+ if (props.className) {
57
+ classes.push(props.className)
58
+ }
59
+
60
+ return (
61
+ <nav className={classes.join(" ")}>
62
+ <ul role="tablist" aria-label="Secondary navigation">
63
+ {props.children}
64
+ </ul>
65
+ </nav>
66
+ )
67
+ }
68
+
69
+ export { TabNav as default, TabNav, TabNavItem }