@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,21 @@
1
+ import * as React from "react"
2
+ import "./MarkdownSection.scss"
3
+
4
+ export interface MarkdownSectionProps {
5
+ fullwidth?: boolean
6
+ children: React.ReactNode
7
+ }
8
+
9
+ export const MarkdownSection = (props: MarkdownSectionProps) => {
10
+ const contentWidth = props.fullwidth ? "markdown" : "markdown max-w-2xl"
11
+
12
+ return (
13
+ <div className="markdown-section">
14
+ <div className="markdown-section__inner">
15
+ <article className={contentWidth}>{props.children}</article>
16
+ </div>
17
+ </div>
18
+ )
19
+ }
20
+
21
+ export default MarkdownSection
@@ -0,0 +1,67 @@
1
+ import * as React from "react"
2
+ import { Desktop, Mobile } from "./ResponsiveWrappers"
3
+ import {
4
+ Accordion,
5
+ AccordionItem,
6
+ AccordionItemHeading,
7
+ AccordionItemButton,
8
+ AccordionItemPanel,
9
+ } from "react-accessible-accordion"
10
+
11
+ export interface ResponsiveContentProps {
12
+ children: React.ReactNode
13
+ desktopClass?: string
14
+ }
15
+
16
+ const ResponsiveContentList = (props: ResponsiveContentProps) => (
17
+ <>
18
+ <Mobile>
19
+ <Accordion allowZeroExpanded allowMultipleExpanded>
20
+ {props.children}
21
+ </Accordion>
22
+ </Mobile>
23
+ <Desktop>
24
+ <ul className="responsive-content-list">{props.children}</ul>
25
+ </Desktop>
26
+ </>
27
+ )
28
+
29
+ const ResponsiveContentItem = (props: ResponsiveContentProps) => (
30
+ <>
31
+ <Mobile>
32
+ <AccordionItem>{props.children}</AccordionItem>
33
+ </Mobile>
34
+ <Desktop>
35
+ <li className={"responsive-content-item " + (props.desktopClass ? props.desktopClass : "")}>
36
+ {props.children}
37
+ </li>
38
+ </Desktop>
39
+ </>
40
+ )
41
+
42
+ const ResponsiveContentItemHeader = (props: ResponsiveContentProps) => (
43
+ <>
44
+ <Mobile>
45
+ <AccordionItemHeading aria-level={2}>
46
+ <AccordionItemButton>{props.children}</AccordionItemButton>
47
+ </AccordionItemHeading>
48
+ </Mobile>
49
+ <Desktop>{props.children}</Desktop>
50
+ </>
51
+ )
52
+
53
+ const ResponsiveContentItemBody = (props: ResponsiveContentProps) => (
54
+ <>
55
+ <Mobile>
56
+ <AccordionItemPanel>{props.children}</AccordionItemPanel>
57
+ </Mobile>
58
+ <Desktop>{props.children}</Desktop>
59
+ </>
60
+ )
61
+
62
+ export {
63
+ ResponsiveContentList,
64
+ ResponsiveContentItem,
65
+ ResponsiveContentItemHeader,
66
+ ResponsiveContentItemBody,
67
+ }
@@ -0,0 +1,23 @@
1
+ import * as React from "react"
2
+ import Media from "react-media"
3
+ import * as tailwindConfig from "../../tailwind.config.js"
4
+
5
+ export interface ResponsiveWrapperProps {
6
+ children: React.ReactNode
7
+ }
8
+
9
+ const mdBreakpoint = parseInt(tailwindConfig.theme.screens.md.replace("px", ""))
10
+
11
+ const Desktop = (props: ResponsiveWrapperProps) => (
12
+ <Media render={() => props.children} query={{ minWidth: mdBreakpoint }} defaultMatches={false} />
13
+ )
14
+
15
+ const Mobile = (props: ResponsiveWrapperProps) => (
16
+ <Media
17
+ render={() => props.children}
18
+ query={{ maxWidth: mdBreakpoint - 1 }}
19
+ defaultMatches={true}
20
+ />
21
+ )
22
+
23
+ export { Desktop, Mobile }
@@ -0,0 +1,86 @@
1
+ import * as React from "react"
2
+ import { nanoid } from "nanoid"
3
+ import { Cell, StandardTableProps } from "./StandardTable"
4
+
5
+ export interface GroupedTableGroup {
6
+ header?: string | React.ReactNode
7
+ className?: string
8
+ data: Record<string, React.ReactNode>[]
9
+ }
10
+
11
+ export interface GroupedTableProps extends Omit<StandardTableProps, "data"> {
12
+ data: GroupedTableGroup[]
13
+ }
14
+
15
+ export const GroupedTable = (props: GroupedTableProps) => {
16
+ const { headers, data, cellClassName } = props
17
+
18
+ const headerLabels = Object.values(headers).map((col, index) => {
19
+ const uniqKey = process.env.NODE_ENV === "test" ? `header-${index}` : nanoid()
20
+ return <th key={uniqKey}>{col}</th>
21
+ })
22
+
23
+ const body: React.ReactNode[] = []
24
+
25
+ data.forEach((group: GroupedTableGroup, dataIndex) => {
26
+ const colSpan = Object.keys(headers).length
27
+
28
+ const groupHeader = group.header
29
+ const groupClassName = group.className
30
+ const groupData = group.data
31
+
32
+ if (groupHeader) {
33
+ body.push(
34
+ <tr key={process.env.NODE_ENV === "test" ? "data-header" : nanoid()}>
35
+ <Cell
36
+ key={process.env.NODE_ENV === "test" ? "cell-header" : nanoid()}
37
+ className={groupClassName}
38
+ colSpan={colSpan}
39
+ >
40
+ {groupHeader}
41
+ </Cell>
42
+ </tr>
43
+ )
44
+ }
45
+
46
+ groupData.forEach((row: Record<string, React.ReactNode>, groupDataIndex) => {
47
+ const rowKey = row["id"]
48
+ ? `row-${row["id"] as string}`
49
+ : process.env.NODE_ENV === "test"
50
+ ? `groupedrow-${dataIndex}-${groupDataIndex}`
51
+ : nanoid()
52
+ const cols = Object.keys(headers).map((colKey, colIndex) => {
53
+ const uniqKey = process.env.NODE_ENV === "test" ? `col-${colIndex}` : nanoid()
54
+ const header = headers[colKey]
55
+ const cell = row[colKey]
56
+ return (
57
+ <Cell key={uniqKey} headerLabel={header} className={cellClassName}>
58
+ {cell}
59
+ </Cell>
60
+ )
61
+ })
62
+
63
+ body.push(
64
+ <tr id={rowKey} key={rowKey} className={`group-${groupClassName}`}>
65
+ {cols}
66
+ </tr>
67
+ )
68
+ })
69
+ })
70
+
71
+ const tableClasses = ["w-full", "text-sm"]
72
+ if (props.responsiveCollapse) {
73
+ tableClasses.push("responsive-collapse")
74
+ }
75
+
76
+ return (
77
+ <div style={{ overflowX: "auto" }}>
78
+ <table className={tableClasses.join(" ")}>
79
+ <thead>
80
+ <tr>{headerLabels}</tr>
81
+ </thead>
82
+ <tbody>{body}</tbody>
83
+ </table>
84
+ </div>
85
+ )
86
+ }
@@ -0,0 +1,32 @@
1
+ import * as React from "react"
2
+ import { TableHeaders, StandardTable } from "./StandardTable"
3
+
4
+ interface MinimalTableProps {
5
+ draggable?: boolean
6
+ setData?: (data: unknown[]) => void
7
+ headers: TableHeaders
8
+ data: Record<string, React.ReactNode>[]
9
+ flushLeft?: boolean
10
+ flushRight?: boolean
11
+ responsiveCollapse?: boolean
12
+ className?: string
13
+ }
14
+
15
+ const MinimalTable = (props: MinimalTableProps) => {
16
+ const tableClasses = ["td-plain", "th-plain", props.className]
17
+ if (props.flushLeft) tableClasses.push("is-flush-left")
18
+ if (props.flushRight) tableClasses.push("is-flush-right")
19
+ return (
20
+ <StandardTable
21
+ draggable={props.draggable}
22
+ setData={props.setData}
23
+ headers={props.headers}
24
+ data={props.data}
25
+ tableClassName={tableClasses.join(" ")}
26
+ cellClassName="px-5 py-3"
27
+ responsiveCollapse={props.responsiveCollapse}
28
+ />
29
+ )
30
+ }
31
+
32
+ export { MinimalTable as default, MinimalTable }
@@ -0,0 +1,24 @@
1
+ import * as React from "react"
2
+ import { TableHeaders, StandardTable } from "./StandardTable"
3
+
4
+ interface ResponsiveTableProps {
5
+ headers: TableHeaders
6
+ data: Record<string, React.ReactNode>[]
7
+ className?: string
8
+ }
9
+
10
+ const ResponsiveTable = (props: ResponsiveTableProps) => {
11
+ const tableClasses = ["base", props.className]
12
+
13
+ return (
14
+ <StandardTable
15
+ headers={props.headers}
16
+ data={props.data}
17
+ tableClassName={tableClasses.join(" ")}
18
+ cellClassName="px-5 py-3"
19
+ responsiveCollapse
20
+ />
21
+ )
22
+ }
23
+
24
+ export { ResponsiveTable as default, ResponsiveTable }
@@ -0,0 +1,229 @@
1
+ import React, { useState, useEffect } from "react"
2
+ import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd"
3
+ import { nanoid } from "nanoid"
4
+ import { getTranslationWithArguments } from "../helpers/getTranslationWithArguments"
5
+ import { Icon } from "../icons/Icon"
6
+ import { t } from "../helpers/translator"
7
+
8
+ export interface TableHeadersOptions {
9
+ name: string
10
+ className: string
11
+ }
12
+ export interface TableHeaders {
13
+ [key: string]: string | TableHeadersOptions
14
+ }
15
+
16
+ export const Row = (props: { id?: string; className?: string; children: React.ReactNode }) => (
17
+ <tr id={props.id} className={props.className}>
18
+ {props.children}
19
+ </tr>
20
+ )
21
+
22
+ export const HeaderCell = (props: { children: React.ReactNode; className?: string }) => (
23
+ <th className={props.className}>{props.children}</th>
24
+ )
25
+
26
+ export const Cell = (props: {
27
+ headerLabel?: string | TableHeadersOptions
28
+ className?: string
29
+ colSpan?: number
30
+ children: React.ReactNode
31
+ }) => (
32
+ <td
33
+ data-label={props.headerLabel instanceof Object ? props.headerLabel?.name : props.headerLabel}
34
+ className={props.className || "p-5"}
35
+ colSpan={props.colSpan}
36
+ >
37
+ {props.children}
38
+ </td>
39
+ )
40
+
41
+ export const TableThumbnail = (props: { children: React.ReactNode }) => {
42
+ return <span className="table__thumbnail">{props.children}</span>
43
+ }
44
+
45
+ export interface StandardTableProps {
46
+ draggable?: boolean
47
+ setData?: (data: unknown[]) => void
48
+ headers: TableHeaders
49
+ data: StandardTableData
50
+ tableClassName?: string
51
+ cellClassName?: string
52
+ responsiveCollapse?: boolean
53
+ translateData?: boolean
54
+ }
55
+
56
+ export type StandardTableData = Record<string, React.ReactNode>[] | undefined
57
+
58
+ const headerName = (header: string | TableHeadersOptions) => {
59
+ if (typeof header === "string") {
60
+ return header
61
+ } else {
62
+ return header.name
63
+ }
64
+ }
65
+ const headerClassName = (header: string | TableHeadersOptions) => {
66
+ if (typeof header === "string") {
67
+ return ""
68
+ } else {
69
+ return header.className
70
+ }
71
+ }
72
+
73
+ export const StandardTable = (props: StandardTableProps) => {
74
+ const { headers = {}, cellClassName } = props
75
+
76
+ const [tableData, setTableData] = useState<StandardTableData>()
77
+
78
+ useEffect(() => {
79
+ setTableData(props.data)
80
+ }, [props.data])
81
+
82
+ const headerLabels = Object.values(headers)?.map((header, index) => {
83
+ const uniqKey = process.env.NODE_ENV === "test" ? `header-${index}` : nanoid()
84
+ return (
85
+ <HeaderCell key={uniqKey} className={headerClassName(header)}>
86
+ {getTranslationWithArguments(headerName(header))}
87
+ </HeaderCell>
88
+ )
89
+ })
90
+
91
+ useEffect(() => {
92
+ setTableData(props.data)
93
+ }, [props.data])
94
+
95
+ if (props.draggable) {
96
+ headerLabels.splice(
97
+ 0,
98
+ 0,
99
+ <th key={"header-draggable"} className={"table__draggable-cell pl-5"}>
100
+ {t("t.sort")}
101
+ </th>
102
+ )
103
+ }
104
+
105
+ const body = tableData?.map((row: Record<string, React.ReactNode>, dataIndex) => {
106
+ const rowKey = row["id"]
107
+ ? `row-${row["id"] as string}`
108
+ : process.env.NODE_ENV === "test"
109
+ ? `standardrow-${dataIndex}`
110
+ : nanoid()
111
+
112
+ const cols = Object.keys(headers)?.map((colKey, colIndex) => {
113
+ const uniqKey = process.env.NODE_ENV === "test" ? `standardcol-${colIndex}` : nanoid()
114
+ const cell = row[colKey]
115
+
116
+ const cellClass = [cellClassName, headerClassName(headers[colKey])].join(" ")
117
+
118
+ return (
119
+ <Cell
120
+ key={uniqKey}
121
+ headerLabel={getTranslationWithArguments(headerName(headers[colKey]))}
122
+ className={cellClass !== " " ? cellClass : undefined}
123
+ >
124
+ {props.translateData && typeof cell === "string"
125
+ ? getTranslationWithArguments(cell)
126
+ : cell}
127
+ </Cell>
128
+ )
129
+ })
130
+ if (props.draggable) {
131
+ cols.splice(
132
+ 0,
133
+ 0,
134
+ <Cell
135
+ key={`${dataIndex}-draggable`}
136
+ headerLabel={t("t.sort")}
137
+ className={`table__draggable-cell pl-5`}
138
+ >
139
+ <Icon symbol={"draggable"} size={"medium"} />
140
+ </Cell>
141
+ )
142
+ }
143
+ return (
144
+ <React.Fragment key={rowKey}>
145
+ {props.draggable ? (
146
+ <Draggable draggableId={rowKey} index={dataIndex} key={rowKey}>
147
+ {(provided, snapshot) => (
148
+ <tr
149
+ key={rowKey}
150
+ id={rowKey}
151
+ {...provided.draggableProps}
152
+ {...provided.dragHandleProps}
153
+ // eslint-disable-next-line @typescript-eslint/unbound-method
154
+ ref={provided.innerRef}
155
+ className={snapshot.isDragging ? "table__is-dragging" : ""}
156
+ >
157
+ {cols}
158
+ </tr>
159
+ )}
160
+ </Draggable>
161
+ ) : (
162
+ <tr id={rowKey} key={rowKey}>
163
+ {cols}
164
+ </tr>
165
+ )}
166
+ </React.Fragment>
167
+ )
168
+ })
169
+
170
+ const tableClasses = ["w-full", "text-sm"]
171
+ if (props.responsiveCollapse) {
172
+ tableClasses.push("responsive-collapse")
173
+ }
174
+ if (props.tableClassName) {
175
+ tableClasses.push(props.tableClassName)
176
+ }
177
+
178
+ const reorder = (
179
+ list: Record<string, React.ReactNode>[] | undefined,
180
+ startIndex: number,
181
+ endIndex: number
182
+ ) => {
183
+ if (!list) return
184
+
185
+ const result = Array.from(list)
186
+ const [removed] = result.splice(startIndex, 1)
187
+ result.splice(endIndex, 0, removed)
188
+ return result
189
+ }
190
+
191
+ const onDragEnd = (result: DropResult) => {
192
+ if (!result.destination) {
193
+ return
194
+ }
195
+ if (result.destination.index === result.source.index) {
196
+ return
197
+ }
198
+ const reorderedTableData = reorder(tableData, result.source.index, result.destination.index)
199
+ setTableData(reorderedTableData)
200
+ if (props.setData && reorderedTableData) {
201
+ props.setData(reorderedTableData)
202
+ }
203
+ }
204
+
205
+ return (
206
+ <div style={{ overflowX: "auto" }}>
207
+ <table className={tableClasses.join(" ")}>
208
+ <thead>
209
+ <tr>{headerLabels}</tr>
210
+ </thead>
211
+ {props.draggable ? (
212
+ <DragDropContext onDragEnd={onDragEnd}>
213
+ <Droppable droppableId="standard-table">
214
+ {(provided) => (
215
+ // eslint-disable-next-line @typescript-eslint/unbound-method
216
+ <tbody {...provided.droppableProps} ref={provided.innerRef}>
217
+ {body}
218
+ {provided.placeholder}
219
+ </tbody>
220
+ )}
221
+ </Droppable>
222
+ </DragDropContext>
223
+ ) : (
224
+ <tbody>{body}</tbody>
225
+ )}
226
+ </table>
227
+ </div>
228
+ )
229
+ }