@bloom-housing/ui-components 4.2.2-alpha.9 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +206 -0
  2. package/README.md +10 -4
  3. package/index.ts +12 -10
  4. package/package.json +6 -4
  5. package/src/actions/Button.docs.mdx +76 -0
  6. package/src/actions/Button.scss +58 -61
  7. package/src/blocks/DashBlock.tsx +5 -3
  8. package/src/blocks/DashBlocks.scss +4 -1
  9. package/src/global/app-css.scss +7 -0
  10. package/src/global/mixins.scss +66 -49
  11. package/src/global/tables.scss +236 -58
  12. package/src/global/text.scss +11 -3
  13. package/src/global/tokens/borders.scss +15 -0
  14. package/src/global/tokens/colors.scss +64 -0
  15. package/src/global/tokens/fonts.scss +45 -0
  16. package/src/global/tokens/screens.scss +6 -0
  17. package/src/global/tokens/sizes.scss +48 -0
  18. package/src/headers/Heading.tsx +2 -0
  19. package/src/headers/PageHeader.docs.mdx +45 -0
  20. package/src/headers/PageHeader.scss +30 -17
  21. package/src/headers/PageHeader.tsx +2 -10
  22. package/src/headers/SiteHeader.tsx +3 -0
  23. package/src/helpers/MultiLineAddress.tsx +37 -0
  24. package/src/helpers/OneLineAddress.tsx +21 -0
  25. package/src/helpers/tableSummaries.tsx +34 -23
  26. package/src/locales/general.json +4 -1
  27. package/src/navigation/FooterNav.scss +8 -3
  28. package/src/page_components/forgot-password/FormForgotPassword.tsx +5 -4
  29. package/src/page_components/listing/AdditionalFees.tsx +38 -31
  30. package/src/page_components/listing/ListingCard.scss +12 -0
  31. package/src/page_components/listing/ListingCard.tsx +5 -3
  32. package/src/page_components/listing/ListingMap.tsx +7 -2
  33. package/src/page_components/listing/UnitTables.tsx +19 -18
  34. package/src/page_components/listing/listing_sidebar/Contact.tsx +110 -0
  35. package/src/page_components/listing/listing_sidebar/ContactAddress.tsx +41 -0
  36. package/src/page_components/listing/listing_sidebar/GetApplication.tsx +35 -15
  37. package/src/page_components/listing/listing_sidebar/QuantityRowSection.tsx +46 -0
  38. package/src/page_components/listing/listing_sidebar/SubmitApplication.tsx +52 -57
  39. package/src/page_components/listing/listing_sidebar/events/EventSection.tsx +3 -2
  40. package/src/page_components/sign-in/FormSignIn.tsx +1 -1
  41. package/src/page_components/sign-in/ResendConfirmationModal.tsx +1 -3
  42. package/src/prototypes/Swatch.tsx +10 -0
  43. package/src/tables/CategoryTable.tsx +33 -0
  44. package/src/tables/GroupedTable.tsx +5 -5
  45. package/src/tables/MinimalTable.tsx +12 -2
  46. package/src/tables/StackedTable.tsx +38 -26
  47. package/src/tables/StandardTable.tsx +26 -10
  48. package/tailwind.config.js +76 -81
  49. package/tailwind.tosass.js +2 -1
  50. package/src/helpers/address.tsx +0 -46
  51. package/src/page_components/listing/listing_sidebar/LeasingAgent.tsx +0 -72
  52. package/src/page_components/listing/listing_sidebar/SidebarAddress.tsx +0 -56
  53. package/src/page_components/listing/listing_sidebar/Waitlist.tsx +0 -49
@@ -1,11 +1,10 @@
1
1
  import React from "react"
2
- import { nanoid } from "nanoid"
3
2
  import { MinMax, UnitSummary, Unit } from "@bloom-housing/backend-core/types"
4
3
 
5
- import { StandardTable } from "../../tables/StandardTable"
4
+ import { StandardTable, StandardTableData } from "../../tables/StandardTable"
6
5
  import { t } from "../../helpers/translator"
7
6
  import { numberOrdinal } from "../../helpers/numberOrdinal"
8
- import ContentAccordion from "./ContentAccordion"
7
+ import { ContentAccordion } from "./ContentAccordion"
9
8
 
10
9
  const formatRange = (range: MinMax, ordinalize?: boolean) => {
11
10
  let min: string | number = range.min
@@ -50,24 +49,26 @@ const UnitTables = (props: UnitTablesProps) => {
50
49
  const units = props.units.filter(
51
50
  (unit: Unit) => unit.unitType?.name == unitSummary.unitType.name
52
51
  )
53
- const unitsFormatted = [] as Array<Record<string, React.ReactNode>>
52
+ const unitsFormatted = [] as StandardTableData
54
53
  let floorSection: React.ReactNode
55
54
  units.forEach((unit: Unit) => {
56
55
  unitsFormatted.push({
57
- number: unit.number,
58
- sqFeet: (
59
- <>
60
- {unit.sqFeet ? (
61
- <>
62
- <strong>{parseInt(unit.sqFeet)}</strong> {t("t.sqFeet")}
63
- </>
64
- ) : (
65
- <></>
66
- )}
67
- </>
68
- ),
69
- numBathrooms: <strong>{unit.numBathrooms}</strong>,
70
- floor: <strong>{unit.floor}</strong>,
56
+ number: { content: unit.number },
57
+ sqFeet: {
58
+ content: (
59
+ <>
60
+ {unit.sqFeet ? (
61
+ <>
62
+ <strong>{parseInt(unit.sqFeet)}</strong> {t("t.sqFeet")}
63
+ </>
64
+ ) : (
65
+ <></>
66
+ )}
67
+ </>
68
+ ),
69
+ },
70
+ numBathrooms: { content: <strong>{unit.numBathrooms}</strong> },
71
+ floor: { content: <strong>{unit.floor}</strong> },
71
72
  })
72
73
  })
73
74
 
@@ -0,0 +1,110 @@
1
+ import * as React from "react"
2
+ import { ContactAddress } from "./ContactAddress"
3
+ import { Icon, IconFillColors } from "../../../icons/Icon"
4
+ import { Address } from "../../../helpers/MultiLineAddress"
5
+ import { Heading } from "../../../headers/Heading"
6
+
7
+ export interface ContactProps {
8
+ /** Any number of text sections rendered after the contact information */
9
+ additionalInformation?: { title: string; content: string | React.ReactNode }[]
10
+ /** The contact's address */
11
+ contactAddress?: Address
12
+ /** The contact's company and website */
13
+ contactCompany?: { name: string; website: string }
14
+ /** The contact's email */
15
+ contactEmail?: string
16
+ /** The contact's name */
17
+ contactName?: string
18
+ /** The contact's phone number */
19
+ contactPhoneNumber?: string
20
+ /** Additional information pertaining to the contact's phone number */
21
+ contactPhoneNumberNote?: string
22
+ /** The contact's title */
23
+ contactTitle?: string
24
+ /** The text for the section's header */
25
+ sectionTitle: string
26
+ strings: { email?: string; getDirections: string; website?: string }
27
+ }
28
+
29
+ /** Displays information about a contact, including their address, contact information, and notes */
30
+ const Contact = ({
31
+ additionalInformation,
32
+ contactAddress,
33
+ contactCompany,
34
+ contactEmail,
35
+ contactName,
36
+ contactPhoneNumber,
37
+ contactPhoneNumberNote,
38
+ contactTitle,
39
+ sectionTitle,
40
+ strings,
41
+ }: ContactProps) => {
42
+ const formattedPhoneLink = contactPhoneNumber
43
+ ? `tel:${contactPhoneNumber.replace(/[-()]/g, "")}`
44
+ : undefined
45
+ const formattedCompanyWebsite =
46
+ contactCompany?.website && !contactCompany?.website.startsWith("http")
47
+ ? `http://${contactCompany?.website}`
48
+ : contactCompany?.website
49
+
50
+ return (
51
+ <section className="aside-block">
52
+ <Heading priority={4} style={"sidebarHeader"}>
53
+ {sectionTitle}
54
+ </Heading>
55
+
56
+ {contactName && <p className="text-xl">{contactName}</p>}
57
+ {contactTitle && <p className="text-gray-700">{contactTitle}</p>}
58
+ {contactCompany?.name && <p className="text-gray-700">{contactCompany.name}</p>}
59
+
60
+ {contactPhoneNumber && (
61
+ <>
62
+ <p className="mt-3">
63
+ <a href={formattedPhoneLink}>
64
+ <Icon symbol="phone" size="medium" fill={IconFillColors.primary} className={"pr-2"} />
65
+ {contactPhoneNumber}
66
+ </a>
67
+ </p>
68
+ {contactPhoneNumberNote && (
69
+ <p className="text-sm text-gray-700">{contactPhoneNumberNote}</p>
70
+ )}
71
+ </>
72
+ )}
73
+
74
+ {contactEmail && (
75
+ <p className="my-3">
76
+ <a href={`mailto:${contactEmail}`}>
77
+ <Icon symbol="mail" size="medium" fill={IconFillColors.primary} className={"pr-2"} />
78
+ {strings?.email && strings?.email}
79
+ </a>
80
+ </p>
81
+ )}
82
+
83
+ {formattedCompanyWebsite && (
84
+ <p className="my-3">
85
+ <a href={formattedCompanyWebsite} target="_blank" rel="noreferrer noopener">
86
+ <Icon symbol="globe" size="medium" fill={IconFillColors.primary} className={"pr-2"} />
87
+ {strings?.website && strings?.website}
88
+ </a>
89
+ </p>
90
+ )}
91
+
92
+ {contactAddress && (
93
+ <ContactAddress address={contactAddress} mapString={strings.getDirections} />
94
+ )}
95
+
96
+ {additionalInformation?.map((info) => {
97
+ return (
98
+ <>
99
+ <Heading priority={3} style={"sidebarSubHeader"}>
100
+ {info.title}
101
+ </Heading>
102
+ <div className="text-gray-800 text-tiny markdown">{info.content}</div>
103
+ </>
104
+ )
105
+ })}
106
+ </section>
107
+ )
108
+ }
109
+
110
+ export { Contact as default, Contact }
@@ -0,0 +1,41 @@
1
+ import * as React from "react"
2
+ import ReactDOMServer from "react-dom/server"
3
+ import { Icon, IconFillColors } from "../../../icons/Icon"
4
+ import { MultiLineAddress, Address } from "../../../helpers/MultiLineAddress"
5
+ import { OneLineAddress } from "../../../helpers/OneLineAddress"
6
+
7
+ export interface ContactAddressProps {
8
+ /** An address */
9
+ address: Address
10
+ /** A string for the map link */
11
+ mapString: string
12
+ }
13
+
14
+ /** Renders an address followed by a Google Maps link to the address */
15
+ const ContactAddress = ({ address, mapString }: ContactAddressProps) => {
16
+ if (!address?.street) return null
17
+
18
+ const oneLineAddress = <OneLineAddress address={address} />
19
+ const mainAddress = <MultiLineAddress address={address} />
20
+ const googleMapsHref =
21
+ "https://www.google.com/maps/place/" + ReactDOMServer.renderToStaticMarkup(oneLineAddress)
22
+
23
+ return (
24
+ <>
25
+ <p className="text-gray-700 mb-1">{mainAddress}</p>
26
+ <p className="mb-4">
27
+ <a
28
+ href={googleMapsHref}
29
+ className="inline-block pt-1"
30
+ target="_blank"
31
+ rel="noreferrer noopener"
32
+ >
33
+ <Icon symbol="map" size="medium" fill={IconFillColors.primary} className={"pr-2"} />
34
+ {mapString}
35
+ </a>
36
+ </p>
37
+ </>
38
+ )
39
+ }
40
+
41
+ export { ContactAddress as default, ContactAddress }
@@ -3,10 +3,11 @@ import { t } from "../../../helpers/translator"
3
3
  import { Button } from "../../../actions/Button"
4
4
  import { LinkButton } from "../../../actions/LinkButton"
5
5
  import { AppearanceStyleType } from "../../../global/AppearanceTypes"
6
- import { Address } from "../../../helpers/address"
7
- import { SidebarAddress } from "./SidebarAddress"
6
+ import { Address } from "../../../helpers/MultiLineAddress"
7
+ import { ContactAddress } from "./ContactAddress"
8
8
  import { OrDivider } from "./OrDivider"
9
- import { ListingStatus } from "@bloom-housing/backend-core/types"
9
+ import { Heading } from "../../../headers/Heading"
10
+ import Markdown from "markdown-to-jsx"
10
11
 
11
12
  export interface PaperApplication {
12
13
  fileURL: string
@@ -14,26 +15,30 @@ export interface PaperApplication {
14
15
  }
15
16
 
16
17
  export interface ApplicationsProps {
17
- onlineApplicationURL?: string
18
+ /** The pickup address for the application */
19
+ applicationPickUpAddress?: Address
20
+ /** Office hours for the agent at the pickup address */
21
+ applicationPickUpAddressOfficeHours?: string
22
+ /** Whether or not applications are currently open */
18
23
  applicationsOpen: boolean
24
+ /** The date applications open */
19
25
  applicationsOpenDate?: string
26
+ /** The URL for an online applications */
27
+ onlineApplicationURL?: string
28
+ /** Any number of paper application objects, including their URL and language */
20
29
  paperApplications?: PaperApplication[]
30
+ /** Whether or not there is a paper application method */
21
31
  paperMethod?: boolean
32
+ /** The date mailed applications must be received by */
22
33
  postmarkedApplicationsReceivedByDate?: string
23
- applicationPickUpAddressOfficeHours?: string
24
- applicationPickUpAddress?: Address
34
+ /** Whether or not to hide actionable application buttons */
25
35
  preview?: boolean
26
- listingStatus?: ListingStatus
27
36
  }
28
-
37
+ /** Displays information regarding how to apply, including an online application link button, paper application downloads, and a paper application pickup address */
29
38
  const GetApplication = (props: ApplicationsProps) => {
30
39
  const [showDownload, setShowDownload] = useState(false)
31
40
  const toggleDownload = () => setShowDownload(!showDownload)
32
41
 
33
- if (props.listingStatus === ListingStatus.closed) {
34
- return null
35
- }
36
-
37
42
  return (
38
43
  <section className="aside-block">
39
44
  <h2 className="text-caps-underline">{t("listings.apply.howToApply")}</h2>
@@ -95,11 +100,26 @@ const GetApplication = (props: ApplicationsProps) => {
95
100
  {props.applicationsOpen && (props.onlineApplicationURL || props.paperMethod) && (
96
101
  <OrDivider bgColor="white" />
97
102
  )}
98
- <h3 className="text-caps-tiny">{t("listings.apply.pickUpAnApplication")}</h3>
99
- <SidebarAddress
103
+ <Heading priority={3} style={"sidebarSubHeader"}>
104
+ {t("listings.apply.pickUpAnApplication")}
105
+ </Heading>
106
+ <ContactAddress
100
107
  address={props.applicationPickUpAddress}
101
- officeHours={props.applicationPickUpAddressOfficeHours}
108
+ mapString={t("t.getDirections")}
102
109
  />
110
+ {props.applicationPickUpAddressOfficeHours && (
111
+ <>
112
+ <Heading priority={3} style={"sidebarSubHeader"}>
113
+ {t("leasingAgent.officeHours")}
114
+ </Heading>
115
+ <p className="text-gray-800 text-tiny markdown">
116
+ <Markdown
117
+ children={props.applicationPickUpAddressOfficeHours}
118
+ options={{ disableParsingRawHTML: true }}
119
+ />
120
+ </p>
121
+ </>
122
+ )}
103
123
  </>
104
124
  )}
105
125
  </section>
@@ -0,0 +1,46 @@
1
+ import * as React from "react"
2
+
3
+ export interface QuantityRow {
4
+ amount: number | null
5
+ text: string
6
+ emphasized?: boolean
7
+ }
8
+
9
+ export interface QuantityRowSectionProps {
10
+ /** Any amount of number/text combinations, rendered in a list */
11
+ quantityRows: QuantityRow[]
12
+ strings: {
13
+ sectionTitle: string
14
+ description?: string | React.ReactNode
15
+ }
16
+ }
17
+
18
+ const QuantityRowSection = ({ quantityRows, strings }: QuantityRowSectionProps) => {
19
+ const getRow = (row: QuantityRow) => {
20
+ return (
21
+ <li
22
+ key={row.text}
23
+ className={`uppercase text-gray-800 ${
24
+ row.emphasized ? "font-bold" : "font-normal"
25
+ } font-alt-sans leading-7`}
26
+ >
27
+ <span className="text-right w-12 inline-block pr-2.5 text-base">{row.amount}</span>
28
+ <span className={"text-sm"}>{row.text}</span>
29
+ </li>
30
+ )
31
+ }
32
+
33
+ return (
34
+ <section className="aside-block is-tinted">
35
+ <h4 className="text-caps-tiny">{strings.sectionTitle}</h4>
36
+ <div>
37
+ {strings.description && (
38
+ <p className="text-tiny text-gray-800 pb-3">{strings.description}</p>
39
+ )}
40
+ {quantityRows.length && <ul>{quantityRows.map((row) => getRow(row))}</ul>}
41
+ </div>
42
+ </section>
43
+ )
44
+ }
45
+
46
+ export { QuantityRowSection as default, QuantityRowSection }
@@ -1,78 +1,73 @@
1
1
  import * as React from "react"
2
- import { t } from "../../../helpers/translator"
3
- import { Address } from "../../../helpers/address"
4
- import { SidebarAddress } from "./SidebarAddress"
2
+ import Markdown from "markdown-to-jsx"
3
+ import { Address } from "../../../helpers/MultiLineAddress"
4
+ import { ContactAddress } from "./ContactAddress"
5
5
  import { OrDivider } from "./OrDivider"
6
- import { ListingStatus } from "@bloom-housing/backend-core/types"
7
-
8
- export interface PostmarkedApplication {
9
- postmarkedApplicationsReceivedByDate: string | null
10
- developer: string
11
- applicationsDueDate: string | null
12
- }
6
+ import { Heading } from "../../../headers/Heading"
13
7
 
14
8
  export interface ApplicationAddressesProps {
15
- applicationMailingAddress?: Address
9
+ /** The dropoff address for paper applications */
16
10
  applicationDropOffAddress?: Address
11
+ /** Office hours for developers at the dropoff address for paper applications */
17
12
  applicationDropOffAddressOfficeHours?: string
13
+ /** The mailing address for paper applications */
14
+ applicationMailingAddress?: Address
15
+ /** The application organization, often the developer */
18
16
  applicationOrganization?: string
19
- postmarkedApplicationData?: PostmarkedApplication
20
- listingStatus?: ListingStatus
21
- }
22
-
23
- const SubmitApplication = (props: ApplicationAddressesProps) => {
24
- if (
25
- props.listingStatus === ListingStatus.closed ||
26
- !(props.applicationMailingAddress || props.applicationDropOffAddress)
27
- ) {
28
- return null
29
- }
30
-
31
- const getPostmarkString = () => {
32
- const applicationDueDate = props.postmarkedApplicationData?.applicationsDueDate
33
- const postmarkReceivedByDate =
34
- props.postmarkedApplicationData?.postmarkedApplicationsReceivedByDate
35
- const developer = props.postmarkedApplicationData?.developer
36
- if (applicationDueDate) {
37
- return postmarkReceivedByDate
38
- ? t("listings.apply.submitPaperDueDatePostMark", {
39
- applicationDueDate,
40
- postmarkReceivedByDate,
41
- developer,
42
- })
43
- : t("listings.apply.submitPaperDueDateNoPostMark", {
44
- applicationDueDate,
45
- developer,
46
- })
47
- } else {
48
- return postmarkReceivedByDate
49
- ? t("listings.apply.submitPaperNoDueDatePostMark", { postmarkReceivedByDate, developer })
50
- : t("listings.apply.submitPaperNoDueDateNoPostMark", { developer })
51
- }
17
+ strings: {
18
+ postmark?: string
19
+ mailHeader?: string
20
+ sectionHeader?: string
21
+ dropOffHeader?: string
22
+ officeHoursHeader?: string
23
+ mapString: string
52
24
  }
25
+ }
53
26
 
27
+ /** Displays information regarding paper applications, including two sections: (1) how to mail in applications and (2) how to drop off applications */
28
+ const SubmitApplication = ({
29
+ applicationDropOffAddress,
30
+ applicationDropOffAddressOfficeHours,
31
+ applicationMailingAddress,
32
+ applicationOrganization,
33
+ strings,
34
+ }: ApplicationAddressesProps) => {
54
35
  return (
55
36
  <>
56
37
  <section className="aside-block is-tinted bg-gray-100">
57
- <div className="text-serif-lg">{t("listings.apply.submitAPaperApplication")}</div>
58
- {props.applicationMailingAddress && (
38
+ <div className="text-serif-lg">{strings.sectionHeader}</div>
39
+ {applicationMailingAddress && (
59
40
  <>
60
- <h3 className="text-caps-tiny">{t("listings.apply.sendByUsMail")}</h3>
41
+ <Heading priority={3} style={"sidebarSubHeader"}>
42
+ {strings.mailHeader}
43
+ </Heading>
61
44
  <>
62
- <p className="text-gray-700">{props.applicationOrganization}</p>
63
- <SidebarAddress address={props.applicationMailingAddress} />
45
+ <p className="text-gray-700">{applicationOrganization}</p>
46
+ <ContactAddress address={applicationMailingAddress} mapString={strings.mapString} />
64
47
  </>
65
- <p className="mt-4 text-tiny text-gray-750">{getPostmarkString()}</p>
48
+ {strings.postmark && <p className="mt-4 text-tiny text-gray-750">{strings.postmark}</p>}
66
49
  </>
67
50
  )}
68
- {props.applicationDropOffAddress && (
51
+ {applicationDropOffAddress && (
69
52
  <>
70
- {props.applicationMailingAddress && <OrDivider bgColor="gray-100" />}
71
- <h3 className="text-caps-tiny">{t("listings.apply.dropOffApplication")}</h3>
72
- <SidebarAddress
73
- address={props.applicationDropOffAddress}
74
- officeHours={props.applicationDropOffAddressOfficeHours}
75
- />
53
+ {applicationMailingAddress && <OrDivider bgColor="gray-100" />}
54
+ <Heading priority={3} style={"sidebarSubHeader"}>
55
+ {strings.dropOffHeader}
56
+ </Heading>
57
+ <ContactAddress address={applicationDropOffAddress} mapString={strings.mapString} />
58
+ {applicationDropOffAddressOfficeHours && (
59
+ <>
60
+ <Heading priority={3} style={"sidebarSubHeader"}>
61
+ {strings.officeHoursHeader}
62
+ </Heading>
63
+ <p className="mt-4 text-tiny text-gray-750">
64
+ <Markdown
65
+ children={applicationDropOffAddressOfficeHours}
66
+ options={{ disableParsingRawHTML: true }}
67
+ />
68
+ </p>
69
+ </>
70
+ )}
76
71
  </>
77
72
  )}
78
73
  </section>
@@ -1,4 +1,5 @@
1
1
  import * as React from "react"
2
+ import Heading from "../../../../headers/Heading"
2
3
 
3
4
  export type EventType = {
4
5
  timeString?: string
@@ -19,9 +20,9 @@ const EventSection = (props: EventSectionProps) => {
19
20
  return (
20
21
  <section className="aside-block">
21
22
  {props.headerText && (
22
- <h4 className={props.sectionHeader ? "text-caps-underline" : "text-caps-tiny"}>
23
+ <Heading priority={4} style={props.sectionHeader ? "sidebarHeader" : "sidebarSubHeader"}>
23
24
  {props.headerText}
24
- </h4>
25
+ </Heading>
25
26
  )}
26
27
  {props.events.map((event, index) => (
27
28
  <div key={`events-${index}`} className={`${index !== props.events.length - 1 && "pb-3"}`}>
@@ -112,7 +112,7 @@ const FormSignIn = ({
112
112
  </Form>
113
113
  </div>
114
114
  {showRegisterBtn && (
115
- <div className="form-card__group text-center">
115
+ <div className="form-card__group text-center border-t">
116
116
  <h2 className="mb-6">{t("authentication.createAccount.noAccount")}</h2>
117
117
 
118
118
  <LinkButton href="/create-account">{t("account.createAccount")}</LinkButton>
@@ -6,9 +6,8 @@ import {
6
6
  Form,
7
7
  Field,
8
8
  emailRegex,
9
- NavigationContext,
10
9
  } from "@bloom-housing/ui-components"
11
- import React, { useEffect, useMemo, useContext } from "react"
10
+ import React, { useEffect, useMemo } from "react"
12
11
  import { useForm } from "react-hook-form"
13
12
 
14
13
  export type ResendConfirmationModalProps = {
@@ -30,7 +29,6 @@ const ResendConfirmationModal = ({
30
29
  onClose,
31
30
  onSubmit,
32
31
  }: ResendConfirmationModalProps) => {
33
- const { router } = useContext(NavigationContext)
34
32
  // eslint-disable-next-line @typescript-eslint/unbound-method
35
33
  const { register, errors, reset, getValues, trigger } = useForm({
36
34
  defaultValues: useMemo(() => {
@@ -0,0 +1,10 @@
1
+ import * as React from "react"
2
+
3
+ export const Swatch = (props: { colorVar: string }) => (
4
+ <span
5
+ style={{ backgroundColor: `var(${props.colorVar})` }}
6
+ className="w-6 border border-gray-500 inline-block"
7
+ >
8
+ &nbsp;
9
+ </span>
10
+ )
@@ -0,0 +1,33 @@
1
+ import * as React from "react"
2
+ import { StackedTable, StackedTableProps } from "./StackedTable"
3
+ import { Heading } from "../headers/Heading"
4
+
5
+ export interface CategoryTableSection {
6
+ /** The header text for a category */
7
+ header: string
8
+ /** The table data for a category */
9
+ tableData: StackedTableProps
10
+ }
11
+
12
+ export interface CategoryTableProps {
13
+ /** The table data passed as category section strings associated with a table data set */
14
+ categoryData: CategoryTableSection[]
15
+ }
16
+
17
+ const CategoryTable = (props: CategoryTableProps) => {
18
+ const tables = props.categoryData.map((category, index) => {
19
+ return (
20
+ <div key={index}>
21
+ <Heading priority={3} style={"categoryHeader"}>
22
+ {category.header}
23
+ </Heading>
24
+ <hr className={"my-2"} />
25
+ <StackedTable {...category.tableData} className={"category-table mb-2 md:mb-6"} />
26
+ </div>
27
+ )
28
+ })
29
+
30
+ return <>{tables}</>
31
+ }
32
+
33
+ export { CategoryTable as default, CategoryTable }
@@ -1,11 +1,11 @@
1
1
  import * as React from "react"
2
2
  import { nanoid } from "nanoid"
3
- import { Cell, StandardTableProps } from "./StandardTable"
3
+ import { Cell, StandardTableData, StandardTableProps } from "./StandardTable"
4
4
 
5
5
  export interface GroupedTableGroup {
6
6
  header?: string | React.ReactNode
7
7
  className?: string
8
- data: Record<string, React.ReactNode>[]
8
+ data: StandardTableData
9
9
  }
10
10
 
11
11
  export interface GroupedTableProps extends Omit<StandardTableProps, "data"> {
@@ -43,16 +43,16 @@ export const GroupedTable = (props: GroupedTableProps) => {
43
43
  )
44
44
  }
45
45
 
46
- groupData.forEach((row: Record<string, React.ReactNode>, groupDataIndex) => {
46
+ groupData.forEach((row, groupDataIndex) => {
47
47
  const rowKey = row["id"]
48
- ? `row-${row["id"] as string}`
48
+ ? `row-${row["id"].content as string}`
49
49
  : process.env.NODE_ENV === "test"
50
50
  ? `groupedrow-${dataIndex}-${groupDataIndex}`
51
51
  : nanoid()
52
52
  const cols = Object.keys(headers).map((colKey, colIndex) => {
53
53
  const uniqKey = process.env.NODE_ENV === "test" ? `col-${colIndex}` : nanoid()
54
54
  const header = headers[colKey]
55
- const cell = row[colKey]
55
+ const cell = row[colKey]?.content
56
56
  return (
57
57
  <Cell key={uniqKey} headerLabel={header} className={cellClassName}>
58
58
  {cell}
@@ -1,16 +1,26 @@
1
1
  import * as React from "react"
2
- import { TableHeaders, StandardTable } from "./StandardTable"
2
+ import { TableHeaders, StandardTable, StandardTableData } from "./StandardTable"
3
3
 
4
4
  export interface MinimalTableProps {
5
+ /** If the table should be sortable through dragging */
5
6
  draggable?: boolean
7
+ /** A set state function tied to the table's data, used if the table is draggable */
6
8
  setData?: (data: unknown[]) => void
9
+ /** The headers for the table passed as text content with optional settings */
7
10
  headers: TableHeaders
8
- data?: Record<string, React.ReactNode>[]
11
+ /** The table data passed as records of column name to cell data with optional settings */
12
+ data?: StandardTableData
13
+ /** Removes cell margins on the left of every row's first cell */
9
14
  flushLeft?: boolean
15
+ /** Removes cell margins on the right of every row's last cell */
10
16
  flushRight?: boolean
17
+ /** If the table should collapse on mobile views to show repeating columns on the left for every row */
11
18
  responsiveCollapse?: boolean
19
+ /** A class name applied to the root of the table */
12
20
  className?: string
21
+ /** A class name applied to the cells of the table */
13
22
  cellClassName?: string
23
+ /** An id applied to the table */
14
24
  id?: string
15
25
  }
16
26