@bloom-housing/ui-components 4.2.2-alpha.2 → 4.2.2-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +173 -0
- package/README.md +10 -4
- package/index.ts +3 -0
- package/package.json +5 -3
- package/src/actions/Button.docs.mdx +76 -0
- package/src/actions/Button.scss +58 -61
- package/src/authentication/timeout.tsx +1 -0
- package/src/blocks/DashBlock.tsx +5 -3
- package/src/blocks/DashBlocks.scss +4 -1
- package/src/forms/FieldGroup.tsx +18 -17
- package/src/global/app-css.scss +7 -0
- package/src/global/markdown.scss +20 -0
- package/src/global/mixins.scss +66 -49
- package/src/global/tables.scss +236 -58
- package/src/global/text.scss +9 -1
- package/src/global/tokens/borders.scss +15 -0
- package/src/global/tokens/colors.scss +64 -0
- package/src/global/tokens/fonts.scss +45 -0
- package/src/global/tokens/screens.scss +6 -0
- package/src/global/tokens/sizes.scss +48 -0
- package/src/headers/Heading.tsx +1 -0
- package/src/headers/PageHeader.docs.mdx +45 -0
- package/src/headers/PageHeader.scss +30 -17
- package/src/headers/PageHeader.tsx +2 -10
- package/src/headers/SiteHeader.tsx +4 -1
- package/src/helpers/address.tsx +5 -4
- package/src/helpers/tableSummaries.tsx +34 -23
- package/src/locales/general.json +9 -2
- package/src/navigation/FooterNav.scss +2 -1
- package/src/overlays/Drawer.tsx +11 -3
- package/src/overlays/Modal.tsx +16 -7
- package/src/overlays/Overlay.tsx +4 -3
- package/src/page_components/ApplicationTimeline.scss +36 -0
- package/src/page_components/ApplicationTimeline.tsx +33 -0
- package/src/page_components/forgot-password/FormForgotPassword.tsx +5 -4
- package/src/page_components/listing/AdditionalFees.tsx +38 -31
- package/src/page_components/listing/ListingCard.scss +12 -0
- package/src/page_components/listing/ListingCard.tsx +5 -3
- package/src/page_components/listing/UnitTables.tsx +19 -18
- package/src/page_components/sign-in/FormSignIn.tsx +2 -1
- package/src/page_components/sign-in/ResendConfirmationModal.tsx +108 -0
- package/src/prototypes/Swatch.tsx +10 -0
- package/src/tables/CategoryTable.tsx +33 -0
- package/src/tables/GroupedTable.tsx +5 -5
- package/src/tables/MinimalTable.tsx +12 -2
- package/src/tables/StackedTable.tsx +38 -26
- package/src/tables/StandardTable.tsx +26 -10
- package/tailwind.config.js +76 -81
- package/tailwind.tosass.js +2 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import Markdown from "markdown-to-jsx"
|
|
3
|
+
import { Icon } from "../icons/Icon"
|
|
4
|
+
import { t } from "../helpers/translator"
|
|
5
|
+
import "./ApplicationTimeline.scss"
|
|
6
|
+
|
|
7
|
+
const ApplicationTimeline = () => (
|
|
8
|
+
<ul
|
|
9
|
+
className="progress-nav application-timeline"
|
|
10
|
+
aria-label="Steps of processing your application"
|
|
11
|
+
>
|
|
12
|
+
<li className="progress-nav__item is-active" aria-current="step">
|
|
13
|
+
<span className="text-white absolute">
|
|
14
|
+
<Icon symbol="check" size="base" />
|
|
15
|
+
</span>
|
|
16
|
+
<Markdown className="font-bold" options={{ disableParsingRawHTML: true }}>
|
|
17
|
+
{t("application.review.confirmation.applicationReceived")}
|
|
18
|
+
</Markdown>
|
|
19
|
+
</li>
|
|
20
|
+
<li className="progress-nav__item is-disabled">
|
|
21
|
+
<Markdown options={{ disableParsingRawHTML: true }}>
|
|
22
|
+
{t("application.review.confirmation.applicationsClosed")}
|
|
23
|
+
</Markdown>
|
|
24
|
+
</li>
|
|
25
|
+
<li className="progress-nav__item is-disabled">
|
|
26
|
+
<Markdown options={{ disableParsingRawHTML: true }}>
|
|
27
|
+
{t("application.review.confirmation.applicationsRanked")}
|
|
28
|
+
</Markdown>
|
|
29
|
+
</li>
|
|
30
|
+
</ul>
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
export { ApplicationTimeline }
|
|
@@ -62,8 +62,8 @@ const FormForgotPassword = ({
|
|
|
62
62
|
</AlertBox>
|
|
63
63
|
)}
|
|
64
64
|
|
|
65
|
-
{!!networkError.error && Object.entries(errors).length === 0 && (
|
|
66
|
-
<ErrorMessage id={"
|
|
65
|
+
{!!networkError.error?.error && Object.entries(errors).length === 0 && (
|
|
66
|
+
<ErrorMessage id={"forgotpasswordemail-error"} error={!!networkError.error}>
|
|
67
67
|
<AlertBox type="alert" inverted onClose={() => networkError.reset()}>
|
|
68
68
|
{networkError.error.title}
|
|
69
69
|
</AlertBox>
|
|
@@ -76,7 +76,7 @@ const FormForgotPassword = ({
|
|
|
76
76
|
|
|
77
77
|
<SiteAlert type="notice" dismissable />
|
|
78
78
|
|
|
79
|
-
<div className="form-card__group pt-0
|
|
79
|
+
<div className="form-card__group pt-0">
|
|
80
80
|
<Form id="sign-in" className="mt-10" onSubmit={handleSubmit(onSubmit, onError)}>
|
|
81
81
|
<Field
|
|
82
82
|
caps={true}
|
|
@@ -86,8 +86,9 @@ const FormForgotPassword = ({
|
|
|
86
86
|
error={errors.email}
|
|
87
87
|
errorMessage={errors.email ? t("authentication.signIn.loginError") : undefined}
|
|
88
88
|
register={register}
|
|
89
|
+
onChange={() => networkError.reset()}
|
|
89
90
|
/>
|
|
90
|
-
<section
|
|
91
|
+
<section>
|
|
91
92
|
<div className="text-center mt-6">
|
|
92
93
|
<Button styleType={AppearanceStyleType.primary}>
|
|
93
94
|
{t("authentication.forgotPassword.sendEmail")}
|
|
@@ -1,49 +1,56 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
-
import { t } from "../../helpers/translator"
|
|
3
2
|
|
|
4
3
|
export interface AdditionalFeesProps {
|
|
5
|
-
|
|
6
|
-
depositMax?: string
|
|
4
|
+
/** The application fee for the property, rendered in the first block */
|
|
7
5
|
applicationFee?: string
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
/** Costs not included in the deposit or application fee, rendered below both blocks */
|
|
7
|
+
costsNotIncluded?: string | React.ReactNode
|
|
8
|
+
/** The deposit amount for the property, rendered in the second block */
|
|
9
|
+
deposit?: string
|
|
10
|
+
strings: {
|
|
11
|
+
sectionHeader: string
|
|
12
|
+
deposit?: string
|
|
13
|
+
depositSubtext?: string[]
|
|
14
|
+
applicationFee?: string
|
|
15
|
+
applicationFeeSubtext?: string[]
|
|
15
16
|
}
|
|
17
|
+
}
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
else return `$${max}`
|
|
24
|
-
}
|
|
19
|
+
const AdditionalFees = ({
|
|
20
|
+
deposit,
|
|
21
|
+
applicationFee,
|
|
22
|
+
costsNotIncluded,
|
|
23
|
+
strings,
|
|
24
|
+
}: AdditionalFeesProps) => {
|
|
25
25
|
return (
|
|
26
26
|
<div className="info-card bg-gray-100 border-0">
|
|
27
|
-
<p className="info-card__title">{
|
|
27
|
+
<p className="info-card__title mb-2">{strings.sectionHeader}</p>
|
|
28
28
|
<div className="info-card__columns text-sm">
|
|
29
|
-
{
|
|
30
|
-
<div className=
|
|
31
|
-
<div className="text-base">{
|
|
32
|
-
<div className="text-xl font-bold"
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
{applicationFee && (
|
|
30
|
+
<div className={`info-card__column ${deposit && "mr-2"}`}>
|
|
31
|
+
<div className="text-base">{strings.applicationFee}</div>
|
|
32
|
+
<div className="text-xl font-bold">{applicationFee}</div>
|
|
33
|
+
{strings.applicationFeeSubtext?.map((appFeeSubtext, index) => (
|
|
34
|
+
<div key={index}>{appFeeSubtext}</div>
|
|
35
|
+
))}
|
|
35
36
|
</div>
|
|
36
37
|
)}
|
|
37
|
-
{
|
|
38
|
-
<div className=
|
|
39
|
-
<div className="text-base">{
|
|
40
|
-
<div className="text-xl font-bold">{
|
|
41
|
-
{
|
|
38
|
+
{deposit && (
|
|
39
|
+
<div className={`info-card__column ${applicationFee && "ml-2"}`}>
|
|
40
|
+
<div className="text-base">{strings.deposit}</div>
|
|
41
|
+
<div className="text-xl font-bold">{deposit}</div>
|
|
42
|
+
{strings.depositSubtext?.map((depositSubtext, index) => (
|
|
43
|
+
<div key={index}>{depositSubtext}</div>
|
|
44
|
+
))}
|
|
42
45
|
</div>
|
|
43
46
|
)}
|
|
44
47
|
</div>
|
|
45
48
|
|
|
46
|
-
{
|
|
49
|
+
{costsNotIncluded && (
|
|
50
|
+
<p className={`text-sm mt-2 ${(applicationFee || deposit) && `mt-6`}`}>
|
|
51
|
+
{costsNotIncluded}
|
|
52
|
+
</p>
|
|
53
|
+
)}
|
|
47
54
|
</div>
|
|
48
55
|
)
|
|
49
56
|
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
.listings-row_figure {
|
|
20
20
|
@apply w-full;
|
|
21
21
|
@apply p-3;
|
|
22
|
+
@apply pb-0;
|
|
22
23
|
|
|
23
24
|
@screen lg {
|
|
24
25
|
@apply w-6/12;
|
|
@@ -32,6 +33,17 @@
|
|
|
32
33
|
@screen lg {
|
|
33
34
|
@apply w-6/12;
|
|
34
35
|
}
|
|
36
|
+
|
|
37
|
+
.listings-row_headers {
|
|
38
|
+
@apply flex;
|
|
39
|
+
@apply flex-col;
|
|
40
|
+
@apply items-center;
|
|
41
|
+
@apply text-center;
|
|
42
|
+
@screen md {
|
|
43
|
+
@apply items-start;
|
|
44
|
+
@apply text-left;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
.listings-row_table {
|
|
@@ -122,8 +122,10 @@ const ListingCard = (props: ListingCardProps) => {
|
|
|
122
122
|
(contentProps.contentHeader?.text || contentProps?.contentSubheader?.text) && (
|
|
123
123
|
<hr className={"mb-2"} />
|
|
124
124
|
)}
|
|
125
|
-
{
|
|
126
|
-
|
|
125
|
+
<div className={"listings-row_headers"}>
|
|
126
|
+
{getHeader(contentProps?.tableHeader, 4, "tableHeader")}
|
|
127
|
+
{getHeader(contentProps?.tableSubheader, 5, "tableSubheader")}
|
|
128
|
+
</div>
|
|
127
129
|
{children && children}
|
|
128
130
|
{tableProps && (tableProps.data || tableProps.stackedData) && (
|
|
129
131
|
<>
|
|
@@ -159,7 +161,7 @@ const ListingCard = (props: ListingCardProps) => {
|
|
|
159
161
|
<ImageCard {...imageCardProps} />
|
|
160
162
|
</div>
|
|
161
163
|
<div className="listings-row_content">
|
|
162
|
-
<div>{getContentHeader()}</div>
|
|
164
|
+
<div className={"listings-row_headers"}>{getContentHeader()}</div>
|
|
163
165
|
{getContent()}
|
|
164
166
|
</div>
|
|
165
167
|
</article>
|
|
@@ -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
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
|
@@ -43,6 +43,7 @@ export type FormSignInControl = {
|
|
|
43
43
|
errors: UseFormMethods["errors"]
|
|
44
44
|
handleSubmit: UseFormMethods["handleSubmit"]
|
|
45
45
|
register: UseFormMethods["register"]
|
|
46
|
+
watch: UseFormMethods["watch"]
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export type FormSignInValues = {
|
|
@@ -111,7 +112,7 @@ const FormSignIn = ({
|
|
|
111
112
|
</Form>
|
|
112
113
|
</div>
|
|
113
114
|
{showRegisterBtn && (
|
|
114
|
-
<div className="form-card__group text-center">
|
|
115
|
+
<div className="form-card__group text-center border-t">
|
|
115
116
|
<h2 className="mb-6">{t("authentication.createAccount.noAccount")}</h2>
|
|
116
117
|
|
|
117
118
|
<LinkButton href="/create-account">{t("account.createAccount")}</LinkButton>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppearanceStyleType,
|
|
3
|
+
Button,
|
|
4
|
+
Modal,
|
|
5
|
+
t,
|
|
6
|
+
Form,
|
|
7
|
+
Field,
|
|
8
|
+
emailRegex,
|
|
9
|
+
NavigationContext,
|
|
10
|
+
} from "@bloom-housing/ui-components"
|
|
11
|
+
import React, { useEffect, useMemo, useContext } from "react"
|
|
12
|
+
import { useForm } from "react-hook-form"
|
|
13
|
+
|
|
14
|
+
export type ResendConfirmationModalProps = {
|
|
15
|
+
isOpen: boolean
|
|
16
|
+
initialEmailValue: string
|
|
17
|
+
onClose: () => void
|
|
18
|
+
onSubmit: (email: string) => void
|
|
19
|
+
loading: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ResendConfirmationModalForm = {
|
|
23
|
+
onSubmit: (email: string) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ResendConfirmationModal = ({
|
|
27
|
+
isOpen,
|
|
28
|
+
initialEmailValue,
|
|
29
|
+
loading,
|
|
30
|
+
onClose,
|
|
31
|
+
onSubmit,
|
|
32
|
+
}: ResendConfirmationModalProps) => {
|
|
33
|
+
const { router } = useContext(NavigationContext)
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
35
|
+
const { register, errors, reset, getValues, trigger } = useForm({
|
|
36
|
+
defaultValues: useMemo(() => {
|
|
37
|
+
return {
|
|
38
|
+
emailResend: initialEmailValue,
|
|
39
|
+
}
|
|
40
|
+
}, [initialEmailValue]),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
reset({
|
|
45
|
+
emailResend: initialEmailValue,
|
|
46
|
+
})
|
|
47
|
+
}, [initialEmailValue, reset])
|
|
48
|
+
|
|
49
|
+
const onFormSubmit = async () => {
|
|
50
|
+
const isValid = await trigger()
|
|
51
|
+
if (!isValid) return
|
|
52
|
+
|
|
53
|
+
const { emailResend } = getValues()
|
|
54
|
+
onSubmit(emailResend)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Modal
|
|
59
|
+
open={isOpen}
|
|
60
|
+
title={t("authentication.signIn.yourAccountIsNotConfirmed")}
|
|
61
|
+
ariaDescription={t("authentication.createAccount.linkExpired")}
|
|
62
|
+
onClose={() => {
|
|
63
|
+
onClose()
|
|
64
|
+
window.scrollTo(0, 0)
|
|
65
|
+
}}
|
|
66
|
+
actions={[
|
|
67
|
+
<Button
|
|
68
|
+
type="button"
|
|
69
|
+
styleType={AppearanceStyleType.primary}
|
|
70
|
+
onClick={() => onFormSubmit()}
|
|
71
|
+
loading={loading}
|
|
72
|
+
>
|
|
73
|
+
{t("authentication.createAccount.resendTheEmail")}
|
|
74
|
+
</Button>,
|
|
75
|
+
<Button
|
|
76
|
+
type="button"
|
|
77
|
+
styleType={AppearanceStyleType.alert}
|
|
78
|
+
onClick={() => {
|
|
79
|
+
onClose()
|
|
80
|
+
window.scrollTo(0, 0)
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
{t("t.cancel")}
|
|
84
|
+
</Button>,
|
|
85
|
+
]}
|
|
86
|
+
>
|
|
87
|
+
<>
|
|
88
|
+
<Form>
|
|
89
|
+
<Field
|
|
90
|
+
caps={true}
|
|
91
|
+
type="email"
|
|
92
|
+
name="emailResend"
|
|
93
|
+
label={t("authentication.createAccount.resendAnEmailTo")}
|
|
94
|
+
placeholder="example@web.com"
|
|
95
|
+
validation={{ required: true, pattern: emailRegex }}
|
|
96
|
+
error={!!errors.emailResend}
|
|
97
|
+
errorMessage={t("authentication.signIn.loginError")}
|
|
98
|
+
register={register}
|
|
99
|
+
/>
|
|
100
|
+
</Form>
|
|
101
|
+
|
|
102
|
+
<p className="pt-4">{t("authentication.createAccount.resendEmailInfo")}</p>
|
|
103
|
+
</>
|
|
104
|
+
</Modal>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { ResendConfirmationModal as default, ResendConfirmationModal }
|
|
@@ -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:
|
|
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
|
|
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
|
|
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
|
|
|
@@ -1,43 +1,54 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
-
import { TableHeaders } from "./StandardTable"
|
|
2
|
+
import { StandardTableData, TableHeaders } from "./StandardTable"
|
|
3
3
|
import { MinimalTable } from "./MinimalTable"
|
|
4
4
|
|
|
5
5
|
export interface StackedTableRow {
|
|
6
|
+
/** The main text content of the cell */
|
|
6
7
|
cellText: string
|
|
8
|
+
/** The subtext of the cell, displayed beneath the main text */
|
|
7
9
|
cellSubText?: string
|
|
8
|
-
|
|
10
|
+
/** Hides this cell's subtext on mobile views */
|
|
11
|
+
hideSubTextMobile?: boolean
|
|
12
|
+
/** Text content that will replace this cell's header on mobile views */
|
|
13
|
+
mobileReplacement?: string
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
export interface StackedTableProps {
|
|
17
|
+
/** The headers for the table passed as text content with optional settings */
|
|
12
18
|
headers: TableHeaders
|
|
19
|
+
/** Headers hidden on desktop views */
|
|
13
20
|
headersHiddenDesktop?: string[]
|
|
21
|
+
/** The table data passed as records of column name to cell data */
|
|
14
22
|
stackedData?: Record<string, StackedTableRow>[]
|
|
23
|
+
/** A class name applied to the root of the table */
|
|
15
24
|
className?: string
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
const StackedTable = (props: StackedTableProps) => {
|
|
19
|
-
const tableClasses = ["base", props.className]
|
|
20
|
-
const modifiedData:
|
|
21
|
-
|
|
22
|
-
const cellSubtextClass = "text-sm text-gray-700"
|
|
28
|
+
const tableClasses = ["base", "stacked-table", props.className]
|
|
29
|
+
const modifiedData: StandardTableData = []
|
|
30
|
+
|
|
23
31
|
props.stackedData?.forEach((dataRow) => {
|
|
24
32
|
const dataCell = Object.keys(dataRow).reduce((acc, item) => {
|
|
25
|
-
acc[item] =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<span className={`${cellTextClass}`}>{dataRow[item].cellText}</span>
|
|
32
|
-
<span
|
|
33
|
-
className={`pl-1 md:pl-0 ${
|
|
34
|
-
dataRow[item].hideMobile && "hidden md:block"
|
|
35
|
-
} ${cellSubtextClass}`}
|
|
33
|
+
acc[item] = {
|
|
34
|
+
content: (
|
|
35
|
+
<div
|
|
36
|
+
className={`stacked-table-cell-container ${
|
|
37
|
+
props.headersHiddenDesktop?.includes(item) && "md:hidden"
|
|
38
|
+
}`}
|
|
36
39
|
>
|
|
37
|
-
{dataRow[item].
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
<span className={"stacked-table-cell"}>{dataRow[item].cellText}</span>
|
|
41
|
+
<span
|
|
42
|
+
className={`stacked-table-subtext ${
|
|
43
|
+
dataRow[item].hideSubTextMobile && "hidden md:block"
|
|
44
|
+
} `}
|
|
45
|
+
>
|
|
46
|
+
{dataRow[item].cellSubText}
|
|
47
|
+
</span>
|
|
48
|
+
</div>
|
|
49
|
+
),
|
|
50
|
+
mobileReplacement: dataRow[item].cellText,
|
|
51
|
+
}
|
|
41
52
|
return acc
|
|
42
53
|
}, {})
|
|
43
54
|
modifiedData.push(dataCell)
|
|
@@ -48,14 +59,15 @@ const StackedTable = (props: StackedTableProps) => {
|
|
|
48
59
|
if (props.headersHiddenDesktop?.includes(headerKey)) {
|
|
49
60
|
let headerClasses = "md:hidden"
|
|
50
61
|
headerClasses = `${tempHeader["className"] && tempHeader["className"]} ${headerClasses}`
|
|
51
|
-
tempHeader = {
|
|
62
|
+
tempHeader = {
|
|
63
|
+
name: tempHeader["name"] ?? tempHeader,
|
|
64
|
+
className: `stacked-table-header ${headerClasses}`,
|
|
65
|
+
}
|
|
52
66
|
} else {
|
|
53
67
|
acc[headerKey] = props.headers[headerKey]
|
|
54
68
|
tempHeader = {
|
|
55
69
|
name: tempHeader["name"] ?? tempHeader,
|
|
56
|
-
className: `${
|
|
57
|
-
tempHeader["className"] && tempHeader["className"]
|
|
58
|
-
} px-0 text-base text-gray-700 border-b`,
|
|
70
|
+
className: `${tempHeader["className"] && tempHeader["className"]} stacked-table-header`,
|
|
59
71
|
}
|
|
60
72
|
}
|
|
61
73
|
acc[headerKey] = tempHeader
|
|
@@ -68,7 +80,7 @@ const StackedTable = (props: StackedTableProps) => {
|
|
|
68
80
|
data={modifiedData}
|
|
69
81
|
className={tableClasses.join(" ")}
|
|
70
82
|
responsiveCollapse={true}
|
|
71
|
-
cellClassName={"
|
|
83
|
+
cellClassName={"b-0"}
|
|
72
84
|
/>
|
|
73
85
|
)
|
|
74
86
|
}
|