@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
+ .loading-overlay {
2
+ @apply relative;
3
+ @apply pointer-events-none;
4
+
5
+ &:before {
6
+ content: "";
7
+ @apply absolute;
8
+ @apply top-0;
9
+ @apply left-0;
10
+ @apply w-full;
11
+ @apply h-full;
12
+ @apply z-10;
13
+ background-color: rgba(215, 215, 215, 0.5);
14
+ }
15
+
16
+ .loading-overlay__spinner {
17
+ @apply absolute;
18
+ left: 50%;
19
+ top: 50%;
20
+ @apply text-white;
21
+ @apply -mt-8;
22
+ @apply -ml-8;
23
+ @apply z-10;
24
+ }
25
+ }
@@ -0,0 +1,29 @@
1
+ import React, { useMemo } from "react"
2
+ import { Icon } from "../icons/Icon"
3
+ import "./LoadingOverlay.scss"
4
+
5
+ type LoadingOverlayProps = {
6
+ isLoading: boolean
7
+ children: React.ReactChild
8
+ }
9
+
10
+ const LoadingOverlay = ({ isLoading, children }: LoadingOverlayProps) => {
11
+ const content = useMemo(() => {
12
+ if (!isLoading) return children
13
+
14
+ return (
15
+ <div className="loading-overlay">
16
+ <Icon size="3xl" symbol="spinner" className="loading-overlay__spinner" />
17
+ {children}
18
+ </div>
19
+ )
20
+ }, [isLoading, children])
21
+
22
+ return (
23
+ <div role="alert" aria-live="assertive">
24
+ {content}
25
+ </div>
26
+ )
27
+ }
28
+
29
+ export { LoadingOverlay as default, LoadingOverlay }
@@ -0,0 +1,55 @@
1
+ .modal {
2
+ @apply relative;
3
+ @apply max-w-5xl;
4
+ @apply m-auto;
5
+ @apply border;
6
+ @apply border-solid;
7
+ @apply border-gray-400;
8
+ @apply bg-white;
9
+ @apply rounded-md;
10
+ @apply shadow-md;
11
+ min-width: 32rem;
12
+ }
13
+
14
+ // Content containers
15
+ .modal__header,
16
+ .modal__inner,
17
+ .modal__footer {
18
+ @apply py-4;
19
+ @apply px-8;
20
+ }
21
+
22
+ .modal__header {
23
+ @apply pt-8;
24
+ }
25
+
26
+ .modal__title {
27
+ @apply text-xl;
28
+ }
29
+
30
+ .modal__inner {
31
+ &:last-of-type {
32
+ @apply rounded-b;
33
+ }
34
+ }
35
+
36
+ .modal__footer {
37
+ @apply rounded-b;
38
+ }
39
+
40
+ .modal__close {
41
+ @apply absolute;
42
+ top: 1.5rem;
43
+ right: 2rem;
44
+ cursor: pointer;
45
+ }
46
+
47
+ // Loading container for modal
48
+ .modal__loading {
49
+ @apply w-full;
50
+ @apply flex;
51
+ @apply text-center;
52
+ @apply items-center;
53
+ @apply justify-center;
54
+ min-height: 12rem;
55
+ }
@@ -0,0 +1,61 @@
1
+ import React from "react"
2
+ import "./Modal.scss"
3
+ import { Icon } from "../icons/Icon"
4
+ import { GridCell, GridSection } from "../sections/GridSection"
5
+ import { Overlay, OverlayProps } from "./Overlay"
6
+
7
+ export interface ModalProps extends Omit<OverlayProps, "children"> {
8
+ title: string
9
+ actions?: React.ReactNode[]
10
+ hideCloseIcon?: boolean
11
+ children?: React.ReactNode
12
+ }
13
+
14
+ const ModalHeader = (props: { title: string }) => (
15
+ <header className="modal__inner">
16
+ <h1 className="modal__title">{props.title}</h1>
17
+ </header>
18
+ )
19
+
20
+ const ModalFooter = (props: { actions: React.ReactNode[] }) => (
21
+ <footer className="modal__footer bg-primary-lighter" data-testid="footer">
22
+ <GridSection columns={4} reverse={true} tightSpacing={true}>
23
+ {props.actions &&
24
+ props.actions.map((action: React.ReactNode, index: number) => (
25
+ <GridCell key={index}>{action}</GridCell>
26
+ ))}
27
+ </GridSection>
28
+ </footer>
29
+ )
30
+
31
+ export const Modal = (props: ModalProps) => {
32
+ return (
33
+ <Overlay
34
+ ariaLabel={props.ariaLabel || props.title}
35
+ ariaDescription={props.ariaDescription}
36
+ open={props.open}
37
+ onClose={props.onClose}
38
+ backdrop={props.backdrop}
39
+ >
40
+ <div className="modal">
41
+ <ModalHeader title={props.title} />
42
+
43
+ <section className="modal__inner">
44
+ {typeof props.children === "string" ? (
45
+ <p className="c-steel">{props.children}</p>
46
+ ) : (
47
+ props.children
48
+ )}
49
+ </section>
50
+
51
+ {props.actions && <ModalFooter actions={props.actions} />}
52
+
53
+ {!props.hideCloseIcon && (
54
+ <button className="modal__close" aria-label="Close" onClick={props.onClose} tabIndex={0}>
55
+ <Icon size="medium" symbol="close" />
56
+ </button>
57
+ )}
58
+ </div>
59
+ </Overlay>
60
+ )
61
+ }
@@ -0,0 +1,50 @@
1
+ @import "../global/mixins.scss";
2
+
3
+ .fixed-overlay {
4
+ @apply top-0;
5
+ @apply left-0;
6
+ @apply fixed;
7
+ @apply w-screen;
8
+ @apply h-screen;
9
+ @apply z-50;
10
+ @apply flex;
11
+
12
+ &.is-backdrop:before {
13
+ content: "";
14
+ @apply absolute;
15
+ @apply w-full;
16
+ @apply h-full;
17
+ @apply bg-gray-900;
18
+ @apply bg-opacity-50;
19
+ transition-property: background-color;
20
+
21
+ @include transition-timing;
22
+ }
23
+
24
+ &.overlay-effect-enter,
25
+ &.overlay-effect-exit-active {
26
+ .fixed-overlay__inner {
27
+ opacity: 0;
28
+ transform: translate(0px, 14px);
29
+ }
30
+ &.is-backdrop:before {
31
+ @apply bg-opacity-0;
32
+ }
33
+ }
34
+ &.overlay-effect-enter-active {
35
+ .fixed-overlay__inner {
36
+ opacity: 1;
37
+ transform: translate(0px, 0px);
38
+ }
39
+ &.is-backdrop:before {
40
+ @apply bg-opacity-50;
41
+ }
42
+ }
43
+ }
44
+
45
+ .fixed-overlay__inner {
46
+ transition-property: transform, opacity;
47
+ margin: auto;
48
+
49
+ @include transition-timing;
50
+ }
@@ -0,0 +1,100 @@
1
+ import React, { useState, useEffect, createRef } from "react"
2
+ import "./Overlay.scss"
3
+ import useKeyPress from "../helpers/useKeyPress"
4
+ import { useOutsideClick } from "../helpers/useOutsideClick"
5
+ import { createPortal } from "react-dom"
6
+ import FocusLock from "react-focus-lock"
7
+ import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock"
8
+ import { CSSTransition } from "react-transition-group"
9
+
10
+ export type OverlayProps = {
11
+ open?: boolean
12
+ ariaLabel?: string
13
+ ariaDescription?: string
14
+ className?: string
15
+ backdrop?: boolean
16
+ onClose?: () => void
17
+ children: React.ReactNode
18
+ }
19
+
20
+ const OverlayInner = (props: OverlayProps) => {
21
+ // close overlay on click outside overlay content
22
+ const overlayInnerRef = createRef<HTMLDivElement>()
23
+ useOutsideClick({
24
+ ref: overlayInnerRef,
25
+ callback: () => {
26
+ if (props.onClose) props.onClose()
27
+ },
28
+ })
29
+
30
+ useKeyPress("Escape", () => {
31
+ if (props.onClose) props.onClose()
32
+ })
33
+
34
+ const classNames = ["fixed-overlay"]
35
+ if (typeof props.backdrop === "undefined" || props.backdrop) classNames.push("is-backdrop")
36
+ if (props.className) classNames.push(props.className)
37
+
38
+ return (
39
+ <div
40
+ className={classNames.join(" ")}
41
+ role="dialog"
42
+ aria-labelledby={props.ariaLabel}
43
+ aria-describedby={props.ariaDescription}
44
+ >
45
+ <div className="fixed-overlay__inner" ref={overlayInnerRef}>
46
+ <FocusLock>{props.children}</FocusLock>
47
+ </div>
48
+ </div>
49
+ )
50
+ }
51
+
52
+ export const Overlay = (props: OverlayProps) => {
53
+ const documentAvailable = typeof document !== "undefined"
54
+ const overlayRoot = useState<Element | null>(
55
+ documentAvailable ? document.querySelector("#__next") : null
56
+ )[0]
57
+ const elForPortal = useState<Element | null>(
58
+ documentAvailable ? document.createElement("div") : null
59
+ )[0]
60
+
61
+ // append overlay to the root of app
62
+ useEffect(() => {
63
+ if (!(overlayRoot && elForPortal)) return
64
+
65
+ overlayRoot.appendChild(elForPortal)
66
+
67
+ return () => {
68
+ overlayRoot.removeChild(elForPortal)
69
+ }
70
+ }, [elForPortal, overlayRoot])
71
+
72
+ // disable body scrolling when the overlay is open
73
+ useEffect(() => {
74
+ if (!(overlayRoot && elForPortal)) return
75
+
76
+ props.open ? disableBodyScroll(elForPortal) : enableBodyScroll(elForPortal)
77
+
78
+ return () => {
79
+ if (!props.open) enableBodyScroll(elForPortal)
80
+ }
81
+ }, [elForPortal, overlayRoot, props.open])
82
+
83
+ return (
84
+ elForPortal &&
85
+ createPortal(
86
+ <CSSTransition
87
+ in={props.open}
88
+ timeout={250}
89
+ classNames="overlay-effect"
90
+ mountOnEnter
91
+ unmountOnExit
92
+ >
93
+ <OverlayInner {...props}>{props.children}</OverlayInner>
94
+ </CSSTransition>,
95
+ elForPortal
96
+ )
97
+ )
98
+ }
99
+
100
+ export default Overlay
@@ -0,0 +1,56 @@
1
+ import * as React from "react"
2
+ import { Listing } from "@bloom-housing/backend-core/types"
3
+ import { t } from "../../helpers/translator"
4
+
5
+ export interface AdditionalFeesProps {
6
+ listing: Listing
7
+ }
8
+
9
+ const AdditionalFees = (props: AdditionalFeesProps) => {
10
+ // If none of the relevant fields are provided, return an empty div.
11
+ if (
12
+ !props.listing.depositMin &&
13
+ !props.listing.depositMax &&
14
+ !props.listing.applicationFee &&
15
+ !props.listing.costsNotIncluded
16
+ ) {
17
+ return <></>
18
+ }
19
+
20
+ const getDeposit = () => {
21
+ const min = props.listing.depositMin
22
+ const max = props.listing.depositMax
23
+ if (min && max && min !== max) {
24
+ return `$${min} – $${max}`
25
+ } else if (min) return `$${min}`
26
+ else return `$${max}`
27
+ }
28
+ return (
29
+ <div className="info-card bg-gray-100 border-0">
30
+ <p className="info-card__title">{t("listings.sections.additionalFees")}</p>
31
+ <div className="info-card__columns text-sm">
32
+ {props.listing.applicationFee && (
33
+ <div className="info-card__column">
34
+ <div className="text-base">{t("listings.applicationFee")}</div>
35
+ <div className="text-xl font-bold">${props.listing.applicationFee}</div>
36
+ <div>{t("listings.applicationPerApplicantAgeDescription")}</div>
37
+ <div>{t("listings.applicationFeeDueAt")}</div>
38
+ </div>
39
+ )}
40
+ {(props.listing.depositMin || props.listing.depositMax) && (
41
+ <div className="info-card__column">
42
+ <div className="text-base">{t("t.deposit")}</div>
43
+ <div className="text-xl font-bold">{getDeposit()}</div>
44
+ <div>{t("listings.depositOrMonthsRent")}</div>
45
+ </div>
46
+ )}
47
+ </div>
48
+
49
+ {props.listing.costsNotIncluded && (
50
+ <p className="text-sm mt-6">{props.listing.costsNotIncluded}</p>
51
+ )}
52
+ </div>
53
+ )
54
+ }
55
+
56
+ export { AdditionalFees as default, AdditionalFees }
@@ -0,0 +1,47 @@
1
+ .listings-row {
2
+ @apply flex;
3
+ @apply flex-row;
4
+ @apply flex-wrap;
5
+ @apply max-w-5xl;
6
+ @apply m-auto;
7
+ @apply mb-12;
8
+ max-width: 48rem;
9
+
10
+ &:first-of-type {
11
+ @apply mt-12;
12
+ }
13
+
14
+ @screen lg {
15
+ @apply max-w-5xl;
16
+ }
17
+ }
18
+
19
+ .listings-row_figure {
20
+ @apply w-full;
21
+ @apply p-3;
22
+
23
+ @screen lg {
24
+ @apply w-6/12;
25
+ }
26
+ }
27
+
28
+ .listings-row_content {
29
+ @apply w-full;
30
+ @apply p-3;
31
+
32
+ @screen lg {
33
+ @apply w-6/12;
34
+ }
35
+ }
36
+
37
+ .listings-row_title {
38
+ @apply font-alt-sans;
39
+ @apply font-semibold;
40
+ @apply text-gray-900;
41
+ @apply text-base;
42
+ @apply mb-2;
43
+ }
44
+
45
+ .listings-row_table {
46
+ @apply mb-4;
47
+ }
@@ -0,0 +1,34 @@
1
+ import * as React from "react"
2
+ import { ImageCard, ImageCardProps } from "../../blocks/ImageCard"
3
+ import { LinkButton } from "../../actions/LinkButton"
4
+ import { GroupedTable, GroupedTableProps } from "../../tables/GroupedTable"
5
+ import { t } from "../../helpers/translator"
6
+ import "./ListingCard.scss"
7
+
8
+ export interface ListingCardProps {
9
+ imageCardProps: ImageCardProps
10
+ seeDetailsLink: string
11
+ tableHeader: string
12
+ tableProps: GroupedTableProps
13
+ }
14
+
15
+ const ListingCard = (props: ListingCardProps) => {
16
+ const { imageCardProps, tableProps } = props
17
+
18
+ return (
19
+ <article className="listings-row">
20
+ <div className="listings-row_figure">
21
+ <ImageCard {...imageCardProps} />
22
+ </div>
23
+ <div className="listings-row_content">
24
+ {props.tableHeader && <h4 className="listings-row_title">{props.tableHeader}</h4>}
25
+ <div className="listings-row_table">
26
+ {tableProps.data && <GroupedTable {...tableProps} />}
27
+ </div>
28
+ <LinkButton href={props.seeDetailsLink}>{t("t.seeDetails")}</LinkButton>
29
+ </div>
30
+ </article>
31
+ )
32
+ }
33
+
34
+ export { ListingCard as default, ListingCard }