@bloom-housing/ui-components 4.4.1-alpha.9 → 5.0.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.
- package/CHANGELOG.md +930 -0
- package/index.ts +6 -5
- package/package.json +8 -3
- package/src/actions/Button.tsx +2 -2
- package/src/actions/ExpandableContent.tsx +9 -5
- package/src/blocks/ImageCard.tsx +3 -3
- package/src/blocks/StandardCard.docs.mdx +34 -0
- package/src/blocks/StandardCard.scss +33 -0
- package/src/blocks/StandardCard.tsx +37 -0
- package/src/config/index.ts +0 -1
- package/src/forms/FieldGroup.tsx +1 -1
- package/src/forms/HouseholdSizeField.tsx +2 -1
- package/src/global/tables.scss +7 -1
- package/src/helpers/formOptions.tsx +0 -9
- package/src/helpers/preferences.tsx +3 -314
- package/src/icons/Icon.tsx +22 -3
- package/src/locales/es.json +18 -0
- package/src/locales/general.json +23 -0
- package/src/{prototypes → navigation}/SideNav.scss +15 -9
- package/src/navigation/SideNav.tsx +39 -0
- package/src/notifications/ApplicationStatus.tsx +2 -2
- package/src/overlays/Drawer.tsx +1 -1
- package/src/overlays/Modal.scss +5 -0
- package/src/overlays/Modal.tsx +19 -3
- package/src/page_components/listing/ListingsGroup.tsx +2 -2
- package/src/page_components/listing/listing_sidebar/ExpandableSection.tsx +34 -0
- package/src/page_components/listing/listing_sidebar/QuantityRowSection.tsx +1 -0
- package/src/page_components/sign-in/FormSignInMFACode.tsx +7 -3
- package/src/page_components/sign-in/FormSignInMFAType.tsx +7 -8
- package/src/page_components/sign-in/FormTerms.tsx +9 -27
- package/src/tables/StandardTable.tsx +17 -4
- package/src/authentication/AuthContext.ts +0 -386
- package/src/authentication/RequireLogin.tsx +0 -89
- package/src/authentication/index.ts +0 -5
- package/src/authentication/timeout.tsx +0 -128
- package/src/authentication/token.ts +0 -17
- package/src/authentication/useRequireLoggedInUser.ts +0 -19
- package/src/config/ConfigContext.tsx +0 -36
- package/src/helpers/tableSummaries.tsx +0 -104
- package/src/notifications/index.ts +0 -4
- package/src/page_components/listing/UnitTables.tsx +0 -122
- package/src/page_components/listing/listing_sidebar/WhatToExpect.tsx +0 -22
- package/src/prototypes/SideNav.tsx +0 -14
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import React from "react"
|
|
2
|
-
import {
|
|
3
|
-
InputType,
|
|
4
|
-
ApplicationPreference,
|
|
5
|
-
FormMetadataOptions,
|
|
6
|
-
Preference,
|
|
7
|
-
ListingPreference,
|
|
8
|
-
} from "@bloom-housing/backend-core/types"
|
|
9
2
|
import { UseFormMethods } from "react-hook-form"
|
|
10
3
|
import {
|
|
11
4
|
t,
|
|
@@ -14,133 +7,23 @@ import {
|
|
|
14
7
|
GridCell,
|
|
15
8
|
Field,
|
|
16
9
|
Select,
|
|
17
|
-
SelectOption,
|
|
18
10
|
resolveObject,
|
|
19
11
|
} from "@bloom-housing/ui-components"
|
|
20
12
|
|
|
21
|
-
type ExtraFieldProps = {
|
|
22
|
-
metaKey: string
|
|
23
|
-
optionKey: string
|
|
24
|
-
extraKey: string
|
|
25
|
-
type: InputType
|
|
26
|
-
register: UseFormMethods["register"]
|
|
27
|
-
errors?: UseFormMethods["errors"]
|
|
28
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
-
hhMembersOptions?: SelectOption[]
|
|
30
|
-
stateKeys: string[]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
13
|
type FormAddressProps = {
|
|
34
14
|
subtitle: string
|
|
35
15
|
dataKey: string
|
|
36
|
-
|
|
16
|
+
enableMailCheckbox?: boolean
|
|
37
17
|
register: UseFormMethods["register"]
|
|
38
18
|
errors?: UseFormMethods["errors"]
|
|
39
19
|
required?: boolean
|
|
40
20
|
stateKeys: string[]
|
|
41
21
|
}
|
|
42
22
|
|
|
43
|
-
type AddressType =
|
|
44
|
-
| "residence"
|
|
45
|
-
| "residence-member"
|
|
46
|
-
| "work"
|
|
47
|
-
| "mailing"
|
|
48
|
-
| "alternate"
|
|
49
|
-
| "preference"
|
|
50
|
-
|
|
51
|
-
/*
|
|
52
|
-
Path to the preferences from listing object
|
|
53
|
-
*/
|
|
54
|
-
export const PREFERENCES_FORM_PATH = "application.preferences.options"
|
|
55
|
-
export const PREFERENCES_NONE_FORM_PATH = "application.preferences.none"
|
|
56
|
-
/*
|
|
57
|
-
It generates inner fields for preferences form
|
|
58
|
-
*/
|
|
59
|
-
export const ExtraField = ({
|
|
60
|
-
metaKey,
|
|
61
|
-
optionKey,
|
|
62
|
-
extraKey,
|
|
63
|
-
type,
|
|
64
|
-
register,
|
|
65
|
-
errors,
|
|
66
|
-
hhMembersOptions,
|
|
67
|
-
stateKeys,
|
|
68
|
-
}: ExtraFieldProps) => {
|
|
69
|
-
const FIELD_NAME = `${PREFERENCES_FORM_PATH}.${metaKey}.${optionKey}.${extraKey}`
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<div className="my-4" key={FIELD_NAME}>
|
|
73
|
-
{(() => {
|
|
74
|
-
if (type === InputType.text) {
|
|
75
|
-
return (
|
|
76
|
-
<Field
|
|
77
|
-
id={FIELD_NAME}
|
|
78
|
-
name={FIELD_NAME}
|
|
79
|
-
type="text"
|
|
80
|
-
label={t(`application.preferences.options.${extraKey}`)}
|
|
81
|
-
register={register}
|
|
82
|
-
validation={{ required: true }}
|
|
83
|
-
error={!!resolveObject(FIELD_NAME, errors)}
|
|
84
|
-
errorMessage={t("errors.requiredFieldError")}
|
|
85
|
-
/>
|
|
86
|
-
)
|
|
87
|
-
} else if (type === InputType.address) {
|
|
88
|
-
return (
|
|
89
|
-
<div className="pb-4">
|
|
90
|
-
<FormAddress
|
|
91
|
-
subtitle={t("application.preferences.options.address")}
|
|
92
|
-
dataKey={FIELD_NAME}
|
|
93
|
-
type="preference"
|
|
94
|
-
register={register}
|
|
95
|
-
errors={errors}
|
|
96
|
-
required={true}
|
|
97
|
-
stateKeys={stateKeys}
|
|
98
|
-
/>
|
|
99
|
-
</div>
|
|
100
|
-
)
|
|
101
|
-
} else if (type === InputType.hhMemberSelect) {
|
|
102
|
-
if (!hhMembersOptions)
|
|
103
|
-
return (
|
|
104
|
-
<Field
|
|
105
|
-
id={FIELD_NAME}
|
|
106
|
-
name={FIELD_NAME}
|
|
107
|
-
type="text"
|
|
108
|
-
label={t(`application.preferences.options.${extraKey}`)}
|
|
109
|
-
register={register}
|
|
110
|
-
validation={{ required: true }}
|
|
111
|
-
error={!!resolveObject(FIELD_NAME, errors)}
|
|
112
|
-
errorMessage={t("errors.requiredFieldError")}
|
|
113
|
-
/>
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
return (
|
|
117
|
-
<>
|
|
118
|
-
<Select
|
|
119
|
-
id={FIELD_NAME}
|
|
120
|
-
name={FIELD_NAME}
|
|
121
|
-
label={t(`application.preferences.options.${extraKey}`)}
|
|
122
|
-
register={register}
|
|
123
|
-
controlClassName="control"
|
|
124
|
-
placeholder={t("t.selectOne")}
|
|
125
|
-
options={hhMembersOptions}
|
|
126
|
-
validation={{ required: true }}
|
|
127
|
-
error={!!resolveObject(FIELD_NAME, errors)}
|
|
128
|
-
errorMessage={t("errors.requiredFieldError")}
|
|
129
|
-
/>
|
|
130
|
-
</>
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return <></>
|
|
135
|
-
})()}
|
|
136
|
-
</div>
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
23
|
export const FormAddress = ({
|
|
141
24
|
subtitle,
|
|
142
25
|
dataKey,
|
|
143
|
-
|
|
26
|
+
enableMailCheckbox = false,
|
|
144
27
|
register,
|
|
145
28
|
errors,
|
|
146
29
|
required,
|
|
@@ -225,7 +108,7 @@ export const FormAddress = ({
|
|
|
225
108
|
</ViewItem>
|
|
226
109
|
</GridCell>
|
|
227
110
|
|
|
228
|
-
{
|
|
111
|
+
{enableMailCheckbox && (
|
|
229
112
|
<GridCell span={2}>
|
|
230
113
|
<Field
|
|
231
114
|
id="application.sendMailToMailingAddress"
|
|
@@ -240,197 +123,3 @@ export const FormAddress = ({
|
|
|
240
123
|
</>
|
|
241
124
|
)
|
|
242
125
|
}
|
|
243
|
-
|
|
244
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
245
|
-
export const mapPreferencesToApi = (data: Record<string, any>) => {
|
|
246
|
-
if (!data.application?.preferences) return []
|
|
247
|
-
|
|
248
|
-
const CLAIMED_KEY = "claimed"
|
|
249
|
-
|
|
250
|
-
const preferencesFormData = data.application.preferences.options
|
|
251
|
-
|
|
252
|
-
const keys = Object.keys(preferencesFormData)
|
|
253
|
-
|
|
254
|
-
return keys.map((key) => {
|
|
255
|
-
const currentPreference = preferencesFormData[key]
|
|
256
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
257
|
-
const currentPreferenceValues = Object.values(currentPreference) as Record<string, any>
|
|
258
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
259
|
-
const claimed = currentPreferenceValues.map((c: { claimed: any }) => c.claimed).includes(true)
|
|
260
|
-
|
|
261
|
-
const options = Object.keys(currentPreference).map((option) => {
|
|
262
|
-
const currentOption = currentPreference[option]
|
|
263
|
-
|
|
264
|
-
// count keys except claimed
|
|
265
|
-
const extraKeys = Object.keys(currentOption).filter((item) => item !== CLAIMED_KEY)
|
|
266
|
-
|
|
267
|
-
const response = {
|
|
268
|
-
key: option,
|
|
269
|
-
checked: currentOption[CLAIMED_KEY],
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// assign extra data and detect data type
|
|
273
|
-
if (extraKeys.length) {
|
|
274
|
-
const extraData = extraKeys.map((extraKey) => {
|
|
275
|
-
const type = (() => {
|
|
276
|
-
if (typeof currentOption[extraKey] === "boolean") return InputType.boolean
|
|
277
|
-
// if object includes "city" property, it should be an address
|
|
278
|
-
if (Object.keys(currentOption[extraKey]).includes("city")) return InputType.address
|
|
279
|
-
|
|
280
|
-
return InputType.text
|
|
281
|
-
})()
|
|
282
|
-
|
|
283
|
-
return {
|
|
284
|
-
key: extraKey,
|
|
285
|
-
type,
|
|
286
|
-
value: currentOption[extraKey],
|
|
287
|
-
}
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
Object.assign(response, { extraData })
|
|
291
|
-
} else {
|
|
292
|
-
Object.assign(response, { extraData: [] })
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return response
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
key,
|
|
300
|
-
claimed,
|
|
301
|
-
options,
|
|
302
|
-
}
|
|
303
|
-
})
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
307
|
-
export const mapApiToPreferencesForm = (preferences: ApplicationPreference[]) => {
|
|
308
|
-
const preferencesFormData = {}
|
|
309
|
-
|
|
310
|
-
preferences.forEach((item) => {
|
|
311
|
-
const options = item.options.reduce((acc, curr) => {
|
|
312
|
-
// extraData which comes from the API is an array, in the form we expect an object
|
|
313
|
-
const extraData =
|
|
314
|
-
curr?.extraData?.reduce((extraAcc, extraCurr) => {
|
|
315
|
-
// value - it can be string or nested address object
|
|
316
|
-
const value = extraCurr.value
|
|
317
|
-
Object.assign(extraAcc, {
|
|
318
|
-
[extraCurr.key]: value,
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
return extraAcc
|
|
322
|
-
}, {}) || {}
|
|
323
|
-
|
|
324
|
-
// each form option has "claimed" property - it's "checked" property in the API
|
|
325
|
-
const claimed = curr.checked
|
|
326
|
-
|
|
327
|
-
Object.assign(acc, {
|
|
328
|
-
[curr.key]: {
|
|
329
|
-
claimed,
|
|
330
|
-
...extraData,
|
|
331
|
-
},
|
|
332
|
-
})
|
|
333
|
-
return acc
|
|
334
|
-
}, {})
|
|
335
|
-
|
|
336
|
-
Object.assign(preferencesFormData, {
|
|
337
|
-
[item.key]: options,
|
|
338
|
-
})
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
const noneValues = preferences.reduce((acc, item) => {
|
|
342
|
-
const isClaimed = item.claimed
|
|
343
|
-
|
|
344
|
-
Object.assign(acc, {
|
|
345
|
-
[`${item.key}-none`]: !isClaimed,
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
return acc
|
|
349
|
-
}, {})
|
|
350
|
-
|
|
351
|
-
return { options: preferencesFormData, none: noneValues }
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/*
|
|
355
|
-
It generates checkbox name in proper prefrences structure
|
|
356
|
-
*/
|
|
357
|
-
export const getPreferenceOptionName = (key: string, metaKey: string, noneOption?: boolean) => {
|
|
358
|
-
if (noneOption) return getExclusivePreferenceOptionName(key)
|
|
359
|
-
else return getNormalPreferenceOptionName(metaKey, key)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
export const getNormalPreferenceOptionName = (metaKey: string, key: string) => {
|
|
363
|
-
return `${PREFERENCES_FORM_PATH}.${metaKey}.${key}.claimed`
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export const getExclusivePreferenceOptionName = (key: string | undefined) => {
|
|
367
|
-
return `${PREFERENCES_NONE_FORM_PATH}.${key}-none`
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export type ExclusiveKey = {
|
|
371
|
-
optionKey: string
|
|
372
|
-
preferenceKey: string | undefined
|
|
373
|
-
}
|
|
374
|
-
/*
|
|
375
|
-
Create an array of all exclusive keys from a preference set
|
|
376
|
-
*/
|
|
377
|
-
export const getExclusiveKeys = (preferences: ListingPreference[]) => {
|
|
378
|
-
const exclusive: ExclusiveKey[] = []
|
|
379
|
-
preferences?.forEach((listingPreference) => {
|
|
380
|
-
listingPreference.preference?.formMetadata?.options.forEach((option: FormMetadataOptions) => {
|
|
381
|
-
if (option.exclusive)
|
|
382
|
-
exclusive.push({
|
|
383
|
-
optionKey: getPreferenceOptionName(
|
|
384
|
-
option.key,
|
|
385
|
-
listingPreference.preference?.formMetadata?.key ?? ""
|
|
386
|
-
),
|
|
387
|
-
preferenceKey: listingPreference.preference?.formMetadata?.key,
|
|
388
|
-
})
|
|
389
|
-
})
|
|
390
|
-
if (!listingPreference.preference?.formMetadata?.hideGenericDecline)
|
|
391
|
-
exclusive.push({
|
|
392
|
-
optionKey: getExclusivePreferenceOptionName(
|
|
393
|
-
listingPreference.preference?.formMetadata?.key
|
|
394
|
-
),
|
|
395
|
-
preferenceKey: listingPreference.preference?.formMetadata?.key,
|
|
396
|
-
})
|
|
397
|
-
})
|
|
398
|
-
return exclusive
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const uncheckPreference = (
|
|
402
|
-
metaKey: string,
|
|
403
|
-
options: FormMetadataOptions[] | undefined,
|
|
404
|
-
setValue: (key: string, value: boolean) => void
|
|
405
|
-
) => {
|
|
406
|
-
options?.forEach((option) => {
|
|
407
|
-
setValue(getPreferenceOptionName(option.key, metaKey), false)
|
|
408
|
-
})
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/*
|
|
412
|
-
Set the value of an exclusive checkbox, unchecking all the appropriate boxes in response to the value
|
|
413
|
-
*/
|
|
414
|
-
export const setExclusive = (
|
|
415
|
-
value: boolean,
|
|
416
|
-
setValue: (key: string, value: boolean) => void,
|
|
417
|
-
exclusiveKeys: ExclusiveKey[],
|
|
418
|
-
key: string,
|
|
419
|
-
preference: Preference
|
|
420
|
-
) => {
|
|
421
|
-
if (value) {
|
|
422
|
-
// Uncheck all other keys if setting an exclusive key to true
|
|
423
|
-
uncheckPreference(
|
|
424
|
-
preference?.formMetadata?.key ?? "",
|
|
425
|
-
preference?.formMetadata?.options,
|
|
426
|
-
setValue
|
|
427
|
-
)
|
|
428
|
-
setValue(key ?? "", true)
|
|
429
|
-
} else {
|
|
430
|
-
// Uncheck all exclusive keys if setting a normal key to true
|
|
431
|
-
exclusiveKeys.forEach((thisKey) => {
|
|
432
|
-
if (thisKey.preferenceKey === preference?.formMetadata?.key)
|
|
433
|
-
setValue(thisKey.optionKey, false)
|
|
434
|
-
})
|
|
435
|
-
}
|
|
436
|
-
}
|
package/src/icons/Icon.tsx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
+
import { IconDefinition } from "@fortawesome/fontawesome-svg-core"
|
|
3
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
|
2
4
|
import "./Icon.scss"
|
|
3
5
|
import {
|
|
4
6
|
Application,
|
|
@@ -130,19 +132,22 @@ const IconMap = {
|
|
|
130
132
|
|
|
131
133
|
export type IconTypes = keyof typeof IconMap
|
|
132
134
|
|
|
135
|
+
export type UniversalIconType = IconTypes | IconDefinition
|
|
136
|
+
|
|
133
137
|
export type IconFill = "white" | "primary"
|
|
134
138
|
|
|
135
139
|
export const IconFillColors = {
|
|
136
140
|
white: "#ffffff",
|
|
137
141
|
black: "#000000",
|
|
138
142
|
primary: "#0077DA",
|
|
143
|
+
alert: "#b91c1c",
|
|
139
144
|
}
|
|
140
145
|
|
|
141
146
|
export type IconSize = "tiny" | "small" | "base" | "medium" | "large" | "xlarge" | "2xl" | "3xl"
|
|
142
147
|
|
|
143
148
|
export interface IconProps {
|
|
144
149
|
size: IconSize
|
|
145
|
-
symbol:
|
|
150
|
+
symbol: UniversalIconType
|
|
146
151
|
className?: string
|
|
147
152
|
fill?: string
|
|
148
153
|
ariaHidden?: boolean
|
|
@@ -155,9 +160,14 @@ const Icon = (props: IconProps) => {
|
|
|
155
160
|
if (props.className) wrapperClasses.push(props.className)
|
|
156
161
|
if (props.symbol == "spinner") wrapperClasses.push("spinner-animation")
|
|
157
162
|
|
|
158
|
-
const SpecificIcon =
|
|
163
|
+
const SpecificIcon =
|
|
164
|
+
typeof props.symbol === "string" ? (
|
|
165
|
+
IconMap[props.symbol as string]
|
|
166
|
+
) : (
|
|
167
|
+
<FontAwesomeIcon icon={props.symbol} />
|
|
168
|
+
)
|
|
159
169
|
|
|
160
|
-
return (
|
|
170
|
+
return typeof props.symbol === "string" ? (
|
|
161
171
|
<span
|
|
162
172
|
className={wrapperClasses.join(" ")}
|
|
163
173
|
aria-hidden={props.ariaHidden}
|
|
@@ -165,6 +175,15 @@ const Icon = (props: IconProps) => {
|
|
|
165
175
|
>
|
|
166
176
|
<SpecificIcon fill={props.fill ? props.fill : undefined} />
|
|
167
177
|
</span>
|
|
178
|
+
) : (
|
|
179
|
+
<span
|
|
180
|
+
className={wrapperClasses.join(" ")}
|
|
181
|
+
aria-hidden={props.ariaHidden}
|
|
182
|
+
data-test-id={props.dataTestId ?? null}
|
|
183
|
+
style={{ color: props.fill }}
|
|
184
|
+
>
|
|
185
|
+
{SpecificIcon}
|
|
186
|
+
</span>
|
|
168
187
|
)
|
|
169
188
|
}
|
|
170
189
|
|
package/src/locales/es.json
CHANGED
|
@@ -394,6 +394,24 @@
|
|
|
394
394
|
"authentication.timeout.signOutMessage": "Su seguridad es importante para nosotros. Concluimos su sesión debido a inactividad. Sírvase iniciar sesión para continuar.",
|
|
395
395
|
"authentication.timeout.text": "Para proteger su identidad, su sesión concluirá en un minuto debido a inactividad. Si decide no responder, perderá toda la información que no haya guardado y concluirá su sesión.",
|
|
396
396
|
"config.routePrefix": "es",
|
|
397
|
+
"eligibility.accessibility.acInUnit": "CA en la unidad",
|
|
398
|
+
"eligibility.accessibility.accessibleParking": "Estacionamiento Accesible",
|
|
399
|
+
"eligibility.accessibility.barrierFreeEntrance": "Entrada sin barreras",
|
|
400
|
+
"eligibility.accessibility.description": "Algunas propiedades tienen características de accesibilidad que otras pueden no tener.",
|
|
401
|
+
"eligibility.accessibility.elevator": "Ascensor",
|
|
402
|
+
"eligibility.accessibility.grabBars": "Barras de apoyo",
|
|
403
|
+
"eligibility.accessibility.hearing": "Audiencia",
|
|
404
|
+
"eligibility.accessibility.heatingInUnit": "Calefacción en Unidad",
|
|
405
|
+
"eligibility.accessibility.inUnitWasherDryer": "Lavadora y secadora en el apartamento",
|
|
406
|
+
"eligibility.accessibility.laundryInBuilding": "Lavandería en el edificio",
|
|
407
|
+
"eligibility.accessibility.mobility": "Movilidad",
|
|
408
|
+
"eligibility.accessibility.parkingOnSite": "Estacionamiento en el lugar",
|
|
409
|
+
"eligibility.accessibility.prompt": "¿Necesita funciones de accesibilidad adicionales?",
|
|
410
|
+
"eligibility.accessibility.rollInShower": "Rollo en la ducha",
|
|
411
|
+
"eligibility.accessibility.serviceAnimalsAllowed": "Se admiten animales de servicio",
|
|
412
|
+
"eligibility.accessibility.title": "Funciones de accesibilidad",
|
|
413
|
+
"eligibility.accessibility.visual": "Visual",
|
|
414
|
+
"eligibility.accessibility.wheelchairRamp": "Rampa para silla de ruedas",
|
|
397
415
|
"errors.agreeError": "Debe estar de acuerdo con los términos para poder continuar",
|
|
398
416
|
"errors.alert.badRequest": "¡Oops! Parece que algo salió mal. Por favor, inténtelo de nuevo. Comuníquese con su departamento de vivienda si sigue teniendo problemas.",
|
|
399
417
|
"errors.alert.timeoutPleaseTryAgain": "¡Oops! Parece que algo salió mal. Por favor, inténtelo de nuevo.",
|
package/src/locales/general.json
CHANGED
|
@@ -515,6 +515,24 @@
|
|
|
515
515
|
"authentication.timeout.signOutMessage": "We care about your security. We logged you out due to inactivity. Please sign in to continue.",
|
|
516
516
|
"authentication.timeout.text": "To protect your identity, your session will expire in one minute due to inactivity. You will lose any unsaved information and be logged out if you choose not to respond.",
|
|
517
517
|
"config.routePrefix": "",
|
|
518
|
+
"eligibility.accessibility.acInUnit": "AC in Unit",
|
|
519
|
+
"eligibility.accessibility.accessibleParking": "Accessible Parking",
|
|
520
|
+
"eligibility.accessibility.barrierFreeEntrance": "Barrier Free Entrance",
|
|
521
|
+
"eligibility.accessibility.description": "Some properties have accessibility features that others may not have.",
|
|
522
|
+
"eligibility.accessibility.elevator": "Elevator",
|
|
523
|
+
"eligibility.accessibility.grabBars": "Grab Bars",
|
|
524
|
+
"eligibility.accessibility.hearing": "Hearing",
|
|
525
|
+
"eligibility.accessibility.heatingInUnit": "Heating in Unit",
|
|
526
|
+
"eligibility.accessibility.inUnitWasherDryer": "In Unit Washer Dryer",
|
|
527
|
+
"eligibility.accessibility.laundryInBuilding": "Laundry in Building",
|
|
528
|
+
"eligibility.accessibility.mobility": "Mobility",
|
|
529
|
+
"eligibility.accessibility.parkingOnSite": "Parking On Site",
|
|
530
|
+
"eligibility.accessibility.prompt": "Do you require additional accessibility features?",
|
|
531
|
+
"eligibility.accessibility.rollInShower": "Roll in Shower",
|
|
532
|
+
"eligibility.accessibility.serviceAnimalsAllowed": "Service Animals Allowed",
|
|
533
|
+
"eligibility.accessibility.title": "Accessibility Features",
|
|
534
|
+
"eligibility.accessibility.visual": "Visual",
|
|
535
|
+
"eligibility.accessibility.wheelchairRamp": "Wheelchair Ramp",
|
|
518
536
|
"errors.agreeError": "You must agree to the terms in order to continue",
|
|
519
537
|
"errors.alert.badRequest": "Looks like something went wrong. Please try again. \n\nContact your housing department if you're still experiencing issues.",
|
|
520
538
|
"errors.alert.timeoutPleaseTryAgain": "Oops! Looks like something went wrong. Please try again.",
|
|
@@ -592,8 +610,10 @@
|
|
|
592
610
|
"listings.apply.submitPaperNoDueDateNoPostMark": "%{developer} is not responsible for lost or delayed mail.",
|
|
593
611
|
"listings.apply.submitPaperNoDueDatePostMark": "Applications must be received by the deadline. If sending by U.S. Mail, the application must be received by mail no later than %{postmarkReceivedByDate}. Applications received after %{postmarkReceivedByDate} via mail will not be accepted. %{developer} is not responsible for lost or delayed mail.",
|
|
594
612
|
"listings.availableAndWaitlist": "Available Units & Open Waitlist",
|
|
613
|
+
"listings.availableUnitsDescription": "Applicants will be reviewed in order until all vacancies are filled.",
|
|
595
614
|
"listings.availableUnitsAndWaitlist": "Available units and waitlist",
|
|
596
615
|
"listings.availableUnitsAndWaitlistDesc": "Once applicants fill all available units, additional applicants will be placed on the waitlist for <span class='t-italic'>%{number} units</span>",
|
|
616
|
+
"listings.availableUnit": "Available Unit",
|
|
597
617
|
"listings.availableUnits": "Available Units",
|
|
598
618
|
"listings.bath": "bath",
|
|
599
619
|
"listings.browseListings": "Browse Listings",
|
|
@@ -681,6 +701,7 @@
|
|
|
681
701
|
"listings.sections.eligibilityTitle": "Eligibility",
|
|
682
702
|
"listings.sections.featuresSubtitle": "Amenities, unit details and additional fees",
|
|
683
703
|
"listings.sections.featuresTitle": "Features",
|
|
704
|
+
"listings.sections.accessibilityFeatures": "Accessibility Features",
|
|
684
705
|
"listings.sections.housingPreferencesSubtitle": "Preference holders will be given highest ranking.",
|
|
685
706
|
"listings.sections.housingPreferencesTitle": "Housing Preferences",
|
|
686
707
|
"listings.sections.neighborhoodSubtitle": "Location and transportation",
|
|
@@ -810,6 +831,7 @@
|
|
|
810
831
|
"states.WV": "West Virginia",
|
|
811
832
|
"states.WY": "Wyoming",
|
|
812
833
|
"t.accessibility": "Accessibility",
|
|
834
|
+
"t.additionalAccessibility": "Additional Accessibility",
|
|
813
835
|
"t.additionalPhone": "Additional Phone",
|
|
814
836
|
"t.am": "AM",
|
|
815
837
|
"t.areYouStillWorking": "Are you still working?",
|
|
@@ -861,6 +883,7 @@
|
|
|
861
883
|
"t.occupancy": "Occupancy",
|
|
862
884
|
"t.ok": "Ok",
|
|
863
885
|
"t.or": "or",
|
|
886
|
+
"t.order": "Order",
|
|
864
887
|
"t.people": "people",
|
|
865
888
|
"t.perMonth": "per month",
|
|
866
889
|
"t.perYear": "per year",
|
|
@@ -3,13 +3,26 @@
|
|
|
3
3
|
@apply rounded-lg;
|
|
4
4
|
|
|
5
5
|
li {
|
|
6
|
-
&:first-of-type
|
|
6
|
+
&:first-of-type {
|
|
7
7
|
@apply rounded-t-lg;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
&:last-of-type
|
|
10
|
+
&:last-of-type {
|
|
11
11
|
@apply rounded-b-lg;
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
&.is-current {
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
@apply text-gray-900;
|
|
17
|
+
box-shadow: inset 3px 0px 0px 0px $tailwind-primary;
|
|
18
|
+
@apply block;
|
|
19
|
+
@apply px-4;
|
|
20
|
+
@apply py-2;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&:not(:last-child) {
|
|
24
|
+
@apply border-b;
|
|
25
|
+
}
|
|
13
26
|
}
|
|
14
27
|
|
|
15
28
|
a {
|
|
@@ -17,16 +30,9 @@
|
|
|
17
30
|
@apply block;
|
|
18
31
|
@apply px-4;
|
|
19
32
|
@apply py-2;
|
|
20
|
-
@apply border-b;
|
|
21
|
-
|
|
22
33
|
&:hover {
|
|
23
34
|
@apply bg-primary-lighter;
|
|
24
35
|
@apply text-primary;
|
|
25
36
|
}
|
|
26
|
-
|
|
27
|
-
&.is-current {
|
|
28
|
-
@apply text-gray-900;
|
|
29
|
-
box-shadow: inset 3px 0px 0px 0px $tailwind-primary;
|
|
30
|
-
}
|
|
31
37
|
}
|
|
32
38
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { NavigationContext } from "../config/NavigationContext"
|
|
3
|
+
import "./SideNav.scss"
|
|
4
|
+
|
|
5
|
+
export interface SideNavItemProps {
|
|
6
|
+
current?: boolean
|
|
7
|
+
url: string
|
|
8
|
+
label: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SideNavProps {
|
|
12
|
+
navItems?: SideNavItemProps[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const SideNav = (props: SideNavProps) => {
|
|
16
|
+
const { LinkComponent } = React.useContext(NavigationContext)
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<nav className="side-nav" aria-label="Secondary navigation">
|
|
20
|
+
<ul>
|
|
21
|
+
{props.navItems?.map((navItem: SideNavItemProps, index: number) => {
|
|
22
|
+
if (navItem.current) {
|
|
23
|
+
return (
|
|
24
|
+
<li className="is-current" key={index} aria-current="page">
|
|
25
|
+
{navItem.label}
|
|
26
|
+
</li>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
return (
|
|
30
|
+
<li key={index}>
|
|
31
|
+
<LinkComponent href={navItem.url}>{navItem.label}</LinkComponent>
|
|
32
|
+
</li>
|
|
33
|
+
)
|
|
34
|
+
})}
|
|
35
|
+
</ul>
|
|
36
|
+
</nav>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
export { SideNav as default, SideNav }
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
-
import { Icon, IconFillColors,
|
|
2
|
+
import { Icon, IconFillColors, UniversalIconType } from "../icons/Icon"
|
|
3
3
|
import { ApplicationStatusType } from "../global/ApplicationStatusType"
|
|
4
4
|
import "./ApplicationStatus.scss"
|
|
5
5
|
|
|
6
6
|
export interface ApplicationStatusProps {
|
|
7
7
|
content: string
|
|
8
8
|
iconColor?: string
|
|
9
|
-
iconType?:
|
|
9
|
+
iconType?: UniversalIconType
|
|
10
10
|
status?: ApplicationStatusType
|
|
11
11
|
subContent?: string
|
|
12
12
|
vivid?: boolean
|
package/src/overlays/Drawer.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { Overlay, OverlayProps } from "./Overlay"
|
|
|
5
5
|
import { Tag } from "../text/Tag"
|
|
6
6
|
import { AppearanceStyleType, AppearanceSizeType } from "../global/AppearanceTypes"
|
|
7
7
|
import { AlertTypes } from "../notifications/alertTypes"
|
|
8
|
-
import { AlertBox } from "../notifications"
|
|
8
|
+
import { AlertBox } from "../notifications/AlertBox"
|
|
9
9
|
import { nanoid } from "nanoid"
|
|
10
10
|
|
|
11
11
|
export enum DrawerSide {
|
package/src/overlays/Modal.scss
CHANGED
package/src/overlays/Modal.tsx
CHANGED
|
@@ -11,6 +11,10 @@ export interface ModalProps extends Omit<OverlayProps, "children"> {
|
|
|
11
11
|
children?: React.ReactNode
|
|
12
12
|
slim?: boolean
|
|
13
13
|
role?: string
|
|
14
|
+
modalClassNames?: string
|
|
15
|
+
innerClassNames?: string
|
|
16
|
+
closeClassNames?: string
|
|
17
|
+
scrollable?: boolean
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
const ModalHeader = (props: { title: string; uniqueId?: string }) => (
|
|
@@ -35,6 +39,13 @@ const ModalFooter = (props: { actions: React.ReactNode[] }) => (
|
|
|
35
39
|
|
|
36
40
|
export const Modal = (props: ModalProps) => {
|
|
37
41
|
const uniqueIdRef = useRef(nanoid())
|
|
42
|
+
const modalClassNames = ["modal"]
|
|
43
|
+
const innerClassNames = ["modal__inner"]
|
|
44
|
+
const closeClassNames = ["modal__close"]
|
|
45
|
+
if (props.scrollable) innerClassNames.push("is-scrollable")
|
|
46
|
+
if (props.modalClassNames) modalClassNames.push(...props.modalClassNames.split(" "))
|
|
47
|
+
if (props.innerClassNames) innerClassNames.push(...props.innerClassNames.split(" "))
|
|
48
|
+
if (props.closeClassNames) closeClassNames.push(...props.closeClassNames.split(" "))
|
|
38
49
|
|
|
39
50
|
return (
|
|
40
51
|
<Overlay
|
|
@@ -46,10 +57,10 @@ export const Modal = (props: ModalProps) => {
|
|
|
46
57
|
slim={props.slim}
|
|
47
58
|
role={props.role ? props.role : "dialog"}
|
|
48
59
|
>
|
|
49
|
-
<div className="
|
|
60
|
+
<div className={modalClassNames.join(" ")}>
|
|
50
61
|
<ModalHeader title={props.title} uniqueId={uniqueIdRef.current} />
|
|
51
62
|
|
|
52
|
-
<section className="
|
|
63
|
+
<section className={innerClassNames.join(" ")}>
|
|
53
64
|
{typeof props.children === "string" ? (
|
|
54
65
|
<p className="c-steel">{props.children}</p>
|
|
55
66
|
) : (
|
|
@@ -60,7 +71,12 @@ export const Modal = (props: ModalProps) => {
|
|
|
60
71
|
{props.actions && <ModalFooter actions={props.actions} />}
|
|
61
72
|
|
|
62
73
|
{!props.hideCloseIcon && (
|
|
63
|
-
<button
|
|
74
|
+
<button
|
|
75
|
+
className={closeClassNames.join(" ")}
|
|
76
|
+
aria-label="Close"
|
|
77
|
+
onClick={props.onClose}
|
|
78
|
+
tabIndex={0}
|
|
79
|
+
>
|
|
64
80
|
<Icon size="medium" symbol="close" />
|
|
65
81
|
</button>
|
|
66
82
|
)}
|