@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,62 @@
1
+ import React, { FunctionComponent, useContext, useEffect } from "react"
2
+ import { setSiteAlertMessage } from "../notifications/SiteAlert"
3
+ import { NavigationContext } from "../config/NavigationContext"
4
+ import { AuthContext } from "./AuthContext"
5
+
6
+ // See https://github.com/Microsoft/TypeScript/issues/14094
7
+ type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never }
8
+ type XOR<T, U> = T | U extends Record<string, unknown>
9
+ ? (Without<T, U> & U) | (Without<U, T> & T)
10
+ : T | U
11
+
12
+ type RequireLoginProps = {
13
+ signInPath: string
14
+ signInMessage: string
15
+ } & XOR<{ requireForRoutes?: string[] }, { skipForRoutes: string[] }>
16
+
17
+ /**
18
+ * Require a login to render children. Will redirect to `signInPath` if not logged in.
19
+ *
20
+ * Props can be specified with either a "whitelist" (list of routes to skip check for) or a "blacklist" (list of
21
+ * routes to apply test on). If no list of routes is provided, then will always apply check.
22
+ */
23
+ const RequireLogin: FunctionComponent<RequireLoginProps> = ({
24
+ children,
25
+ signInPath,
26
+ signInMessage,
27
+ ...rest
28
+ }) => {
29
+ const { router } = useContext(NavigationContext)
30
+ const { profile, initialStateLoaded } = useContext(AuthContext)
31
+
32
+ // Parse just the pathname portion of the signInPath (in case we want to pass URL params)
33
+ const [signInPathname] = signInPath.split("?")
34
+
35
+ // Check if this route requires a login or not (can be specified as a whitelist or a blacklist).
36
+ const loginRequiredForPath =
37
+ // by definition, we shouldn't require login on the sign in page itself
38
+ router.pathname !== signInPathname &&
39
+ ("requireForRoutes" in rest
40
+ ? rest.requireForRoutes
41
+ ? rest.requireForRoutes.some((path) => new RegExp(path).exec(router.pathname))
42
+ : true
43
+ : rest.skipForRoutes
44
+ ? !rest.skipForRoutes.some((path) => new RegExp(path).exec(router.pathname))
45
+ : true)
46
+
47
+ useEffect(() => {
48
+ if (loginRequiredForPath && initialStateLoaded && !profile) {
49
+ setSiteAlertMessage(signInMessage, "notice")
50
+ void router.push(signInPath)
51
+ }
52
+ }, [loginRequiredForPath, initialStateLoaded, profile, router, signInPath, signInMessage])
53
+
54
+ if (loginRequiredForPath && !profile) {
55
+ return null
56
+ }
57
+
58
+ // Login either isn't required, or the user object is loaded successfully, continue rendering as normal.
59
+ return <>{children}</>
60
+ }
61
+
62
+ export { RequireLogin as default, RequireLogin }
@@ -0,0 +1,5 @@
1
+ export { AuthContext, AuthProvider } from "./AuthContext"
2
+ export { RequireLogin } from "./RequireLogin"
3
+ export { useRequireLoggedInUser } from "./useRequireLoggedInUser"
4
+ export { ACCESS_TOKEN_LOCAL_STORAGE_KEY } from "./token"
5
+ export { IdleTimeout, LoggedInUserIdleTimeout } from "./timeout"
@@ -0,0 +1,127 @@
1
+ import React, { createElement, FunctionComponent, useContext, useEffect, useState } from "react"
2
+ import { AuthContext } from "./AuthContext"
3
+ import { ConfigContext, NavigationContext } from "../config"
4
+ import { Button } from "../actions/Button"
5
+ import { Modal } from "../overlays/Modal"
6
+ import { setSiteAlertMessage } from "../notifications/SiteAlert"
7
+ import { AlertTypes } from "../notifications/alertTypes"
8
+ import { t } from "../helpers/translator"
9
+ import { AppearanceStyleType } from "../global/AppearanceTypes"
10
+
11
+ const PROMPT_TIMEOUT = 60000
12
+ const events = ["mousemove", "keypress", "scroll"]
13
+
14
+ function useIdleTimeout(timeoutMs: number, onTimeout: () => void) {
15
+ useEffect(() => {
16
+ let timer: number
17
+ const restartTimer = () => {
18
+ if (timer) {
19
+ clearTimeout(timer)
20
+ }
21
+ timer = (setTimeout(onTimeout, timeoutMs) as unknown) as number
22
+ }
23
+
24
+ // Listen for any activity events & reset the timer when they are found
25
+ if (typeof document !== "undefined") {
26
+ events.forEach((event) => document.addEventListener(event, restartTimer, false))
27
+ }
28
+
29
+ // Clean up our listeners & clear the timeout on unmounting/updating the effect
30
+ return () => {
31
+ if (timer) {
32
+ clearTimeout(timer)
33
+ }
34
+ events.forEach((event) => document.removeEventListener(event, restartTimer, false))
35
+ }
36
+ }, [timeoutMs, onTimeout])
37
+ }
38
+
39
+ type IdleTimeoutProps = {
40
+ promptTitle: string
41
+ promptText: string
42
+ promptAction: string
43
+ redirectPath: string
44
+ alertMessage: string
45
+ alertType?: AlertTypes
46
+ onTimeout: () => unknown
47
+ }
48
+
49
+ export const IdleTimeout: FunctionComponent<IdleTimeoutProps> = ({
50
+ promptTitle,
51
+ promptAction,
52
+ promptText,
53
+ redirectPath,
54
+ alertMessage,
55
+ alertType = "alert",
56
+ onTimeout,
57
+ }) => {
58
+ const { idleTimeout } = useContext(ConfigContext)
59
+ const [promptTimeout, setPromptTimeout] = useState<number | undefined>()
60
+ const { router } = useContext(NavigationContext)
61
+
62
+ useIdleTimeout(idleTimeout, () => {
63
+ // Clear any existing prompt timeouts
64
+ if (promptTimeout) {
65
+ clearTimeout(promptTimeout)
66
+ }
67
+
68
+ // Give the user 1 minute to respond to the prompt before the onTimeout action
69
+ setPromptTimeout(
70
+ (setTimeout(() => {
71
+ const timeoutAction = async () => {
72
+ setPromptTimeout(undefined)
73
+ await onTimeout()
74
+ setSiteAlertMessage(alertMessage, alertType)
75
+ void router.push(redirectPath)
76
+ }
77
+ void timeoutAction()
78
+ }, PROMPT_TIMEOUT) as unknown) as number
79
+ )
80
+ })
81
+
82
+ const modalActions = [
83
+ <Button
84
+ styleType={AppearanceStyleType.primary}
85
+ onClick={() => {
86
+ clearTimeout(promptTimeout)
87
+ setPromptTimeout(undefined)
88
+ }}
89
+ >
90
+ {promptAction}
91
+ </Button>,
92
+ ]
93
+
94
+ return (
95
+ <Modal
96
+ open={Boolean(promptTimeout)}
97
+ title={promptTitle}
98
+ ariaDescription={promptText}
99
+ actions={modalActions}
100
+ hideCloseIcon
101
+ >
102
+ {promptText}
103
+ </Modal>
104
+ )
105
+ }
106
+
107
+ export const LoggedInUserIdleTimeout = ({ onTimeout }: { onTimeout?: () => unknown }) => {
108
+ const { profile, signOut } = useContext(AuthContext)
109
+
110
+ const timeoutFxn = async () => {
111
+ onTimeout && (await onTimeout())
112
+ signOut && signOut()
113
+ }
114
+
115
+ // Only render the IdleTimeout component if the user is logged in
116
+ return profile && signOut
117
+ ? createElement(IdleTimeout, {
118
+ promptTitle: t("t.areYouStillWorking"),
119
+ promptText: t("authentication.timeout.text"),
120
+ promptAction: t("authentication.timeout.action"),
121
+ redirectPath: `/sign-in`,
122
+ alertMessage: t("authentication.timeout.signOutMessage"),
123
+ alertType: "notice",
124
+ onTimeout: timeoutFxn,
125
+ })
126
+ : null
127
+ }
@@ -0,0 +1,17 @@
1
+ import jwtDecode from "jwt-decode"
2
+
3
+ export const ACCESS_TOKEN_LOCAL_STORAGE_KEY = "@bht"
4
+
5
+ const getStorage = (type: string) => (type === "local" ? localStorage : sessionStorage)
6
+
7
+ export const setToken = (storageType: string, token: string) =>
8
+ getStorage(storageType).setItem(ACCESS_TOKEN_LOCAL_STORAGE_KEY, token)
9
+ export const getToken = (storageType: string) =>
10
+ getStorage(storageType).getItem(ACCESS_TOKEN_LOCAL_STORAGE_KEY)
11
+ export const clearToken = (storageType: string) =>
12
+ getStorage(storageType).removeItem(ACCESS_TOKEN_LOCAL_STORAGE_KEY)
13
+
14
+ export const getTokenTtl = (token: string) => {
15
+ const { exp = 0 } = jwtDecode(token)
16
+ return new Date(exp * 1000).valueOf() - new Date().valueOf()
17
+ }
@@ -0,0 +1,19 @@
1
+ import { useContext } from "react"
2
+ import { AuthContext } from "./AuthContext"
3
+ import { NavigationContext } from "../config/NavigationContext"
4
+
5
+ /**
6
+ * Require a logged in user. Waits on initial load, then initiates a redirect to `redirectPath` if user is not
7
+ * logged in.
8
+ */
9
+ function useRequireLoggedInUser(redirectPath: string) {
10
+ const { profile, initialStateLoaded } = useContext(AuthContext)
11
+ const { router } = useContext(NavigationContext)
12
+
13
+ if (initialStateLoaded && !profile) {
14
+ void router.push(redirectPath)
15
+ }
16
+ return profile
17
+ }
18
+
19
+ export { useRequireLoggedInUser as default, useRequireLoggedInUser }
@@ -0,0 +1,108 @@
1
+ .action-block {
2
+ display: flex;
3
+ @apply p-4;
4
+
5
+ &.primary-lighter {
6
+ @apply bg-primary-lighter;
7
+ }
8
+ &.primary-darker {
9
+ @apply bg-primary-darker;
10
+ @apply text-white;
11
+ .action-block__icon {
12
+ @apply border-white;
13
+ }
14
+ }
15
+ .action-block__actions {
16
+ @media (max-width: 640px) {
17
+ display: flex;
18
+ justify-items: center;
19
+ }
20
+ }
21
+ .action-block__actions button {
22
+ @apply m-2;
23
+ }
24
+ .action-block__subheader {
25
+ @apply pt-2;
26
+ @apply font-sans;
27
+ }
28
+ }
29
+
30
+ .action-block__block {
31
+ align-items: center;
32
+ flex-direction: column;
33
+ justify-content: center;
34
+ @apply pb-8;
35
+
36
+ .action-block__head {
37
+ display: flex;
38
+ align-items: center;
39
+ flex-direction: column;
40
+ justify-content: center;
41
+ }
42
+
43
+ .action-block__icon {
44
+ @apply p-4;
45
+ @apply border-solid;
46
+ @apply border-b-4;
47
+ @apply border-primary;
48
+ @apply mb-6;
49
+ }
50
+ .action-block__head {
51
+ @apply mb-6;
52
+ }
53
+ .action-block__actions {
54
+ @media (max-width: 640px) {
55
+ display: block;
56
+ text-align: center;
57
+ }
58
+ }
59
+ }
60
+
61
+ .action-block__inline {
62
+ align-items: baseline;
63
+ flex-direction: row;
64
+ justify-content: space-between;
65
+ @media (max-width: 640px) {
66
+ display: block;
67
+ text-align: center;
68
+ }
69
+
70
+ .action-block__actions {
71
+ display: flex;
72
+ justify-content: flex-end;
73
+
74
+ @media (max-width: 640px) {
75
+ display: block;
76
+ text-align: center;
77
+ }
78
+ }
79
+
80
+ .action-block__head {
81
+ display: flex;
82
+ align-items: center;
83
+ flex-direction: row;
84
+ @media (max-width: 640px) {
85
+ display: block;
86
+ }
87
+ }
88
+ .action-block__icon {
89
+ @apply p-4;
90
+ @apply border-solid;
91
+ @apply border-r-4;
92
+ @apply border-primary;
93
+
94
+ @media (max-width: 640px) {
95
+ display: inline-block;
96
+ @apply border-solid;
97
+ @apply border-r-0;
98
+ @apply border-b-4;
99
+ @apply mb-4;
100
+ }
101
+ }
102
+ .action-block__header {
103
+ @apply ml-6;
104
+ @media (max-width: 640px) {
105
+ @apply ml-0;
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,51 @@
1
+ import React from "react"
2
+ import "./ActionBlock.scss"
3
+
4
+ export enum ActionBlockLayout {
5
+ block = "block",
6
+ inline = "inline",
7
+ }
8
+
9
+ export enum ActionBlockBackground {
10
+ none = "none",
11
+ primaryLighter = "primary-lighter",
12
+ primaryDarker = "primary-darker",
13
+ }
14
+ interface ActionBlockProps {
15
+ actions: React.ReactNode[]
16
+ background?: string
17
+ header: string
18
+ icon?: React.ReactNode
19
+ layout?: ActionBlockLayout
20
+ subheader?: string
21
+ }
22
+ const ActionBlock = ({
23
+ actions,
24
+ background = ActionBlockBackground.none,
25
+ header,
26
+ icon,
27
+ layout = ActionBlockLayout.block,
28
+ subheader,
29
+ }: ActionBlockProps) => {
30
+ const actionBlockClasses = ["action-block"]
31
+ if (background) actionBlockClasses.push(background)
32
+ if (layout === "block") {
33
+ actionBlockClasses.push("action-block__block")
34
+ } else {
35
+ actionBlockClasses.push("action-block__inline")
36
+ }
37
+
38
+ return (
39
+ <div className={actionBlockClasses.join(" ")}>
40
+ <div className="action-block__head">
41
+ {icon && <div className="action-block__icon">{icon}</div>}
42
+ <h3 className="action-block__header">{header}</h3>
43
+ {subheader && layout === ActionBlockLayout.block && (
44
+ <p className="action-block__subheader">{subheader}</p>
45
+ )}
46
+ </div>
47
+ <div className="action-block__actions">{actions}</div>
48
+ </div>
49
+ )
50
+ }
51
+ export { ActionBlock as default, ActionBlock }
@@ -0,0 +1,140 @@
1
+ .status-item {
2
+ @apply relative;
3
+ @apply text-gray-800;
4
+ @apply border-b;
5
+ @apply border-solid;
6
+ @apply border-gray-450;
7
+
8
+ &:last-of-type {
9
+ @apply border-b-0;
10
+ }
11
+
12
+ &.is-editable {
13
+ @apply bg-primary-lighter;
14
+ }
15
+ }
16
+
17
+ .status-item__inner {
18
+ @apply p-4;
19
+ }
20
+
21
+ .status-item__header {
22
+ @apply pb-2;
23
+
24
+ @screen md {
25
+ @apply flex;
26
+ @apply mb-4;
27
+ @apply justify-between;
28
+ @apply border-b;
29
+ @apply border-solid;
30
+ @apply border-gray-450;
31
+ }
32
+ }
33
+
34
+ .status-item__title {
35
+ @apply text-xl;
36
+ @apply font-alt-sans;
37
+ @apply tracking-wider;
38
+ @apply mb-0;
39
+ @apply uppercase;
40
+ }
41
+
42
+ .status-item__due {
43
+ @screen md {
44
+ @apply text-right;
45
+ @apply self-center;
46
+ }
47
+ }
48
+
49
+ .status-item__content {
50
+ @screen md {
51
+ @apply flex;
52
+ @apply justify-between;
53
+ }
54
+ }
55
+
56
+ .status-confirmation__number {
57
+ @apply pt-4;
58
+ }
59
+
60
+ .status-item__details {
61
+ @apply text-center;
62
+ @screen md {
63
+ @apply text-left;
64
+ }
65
+ }
66
+
67
+ .status-item__action {
68
+ @apply text-center;
69
+ @apply pt-4;
70
+ @apply mb-4;
71
+
72
+ @screen md {
73
+ @apply pt-0;
74
+ @apply text-right;
75
+ }
76
+ }
77
+
78
+ .status-item__footer {
79
+ @apply text-center;
80
+
81
+ @screen md {
82
+ @apply flex;
83
+ @apply justify-between;
84
+ @apply text-left;
85
+ }
86
+ }
87
+
88
+ .status-item__links {
89
+ @apply pb-4;
90
+
91
+ @screen md {
92
+ @apply pb-0;
93
+ }
94
+ }
95
+
96
+ .status-item__meta {
97
+ @apply mt-4;
98
+
99
+ @screen md {
100
+ @apply text-right;
101
+ @apply mt-0;
102
+ }
103
+ }
104
+
105
+ .status-item__status {
106
+ @apply pb-4;
107
+ }
108
+
109
+ .status-item__label {
110
+ @apply relative;
111
+ }
112
+
113
+ .status-item__link {
114
+ @screen md {
115
+ @apply mr-4;
116
+ }
117
+ }
118
+
119
+ .status-item__date {
120
+ @apply text-sm;
121
+ }
122
+
123
+ .status-item__address {
124
+ @apply mb-4;
125
+ }
126
+
127
+ .status-item__confirm-text {
128
+ @apply text-tiny;
129
+ @apply mb-4;
130
+ }
131
+
132
+ .status-item__confirm-number {
133
+ @apply text-lg;
134
+ }
135
+
136
+ .status-item__address {
137
+ @screen md {
138
+ @apply text-left;
139
+ }
140
+ }