Skip to content

Commit a5bba89

Browse files
Merge pull request #129 from uw-ssec/theme-customization
feat: Add custom SurveyJS theme with brand colors
2 parents cf07bf9 + aa61391 commit a5bba89

File tree

3 files changed

+144
-66
lines changed

3 files changed

+144
-66
lines changed

client/src/pages/Survey/utils/SurveyJson.tsx

Lines changed: 30 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const generateSurveyJson = (locations: Choice[]) => {
1919
preScreenPage,
2020
consentPage,
2121
surveyValidationPage,
22+
giftCardPage,
2223
personalLivingSituationPage,
2324
networkPage1,
2425
networkPage2,
@@ -29,7 +30,6 @@ export const generateSurveyJson = (locations: Choice[]) => {
2930
householdPage,
3031
specialQuestionsPage,
3132
surveyDeduplicationPage,
32-
giftCardPage,
3333
outroPage
3434
],
3535
triggers: [
@@ -134,27 +134,12 @@ const consentPage = {
134134
{
135135
type: 'html',
136136
name: 'consent-note',
137-
html: `<div style="background-color: #fff3cd; padding: 10px; margin: 10px 0;">
138-
<strong>Instructions:</strong> Please read the following consent information out loud to the respondent and have them orally give their consent to you.
139-
</div>
140-
<br />
141-
<p>
142-
As part of work with the <strong>University of Washington</strong> in preparation for
143-
<strong>King County Regional Homelessness Authority (KCRHA) 2026 PIT Count</strong>,
144-
I'd like to ask you some questions we're required to ask and collect for <strong>KCRHA's</strong>
145-
funders about unsheltered homelessness in our region.
146-
</p>
147-
<p>
148-
Your participation is voluntary and will not affect any services you or your family are seeking or currently receiving.
149-
We are surveying many people and will put all responses together, so it will not be possible to identify you
150-
from the information you provide here.
151-
</p>
152-
<p>
153-
As a token of appreciation for your time, we will give you a <strong>$20 pre-loaded debit card</strong>.
154-
</p>
155-
<p>
156-
Would you be willing to talk with me for about <strong>30 minutes</strong>?
157-
</p>`
137+
html: `<div><strong>Please read the following consent information out loud to the respondent and have them orally give their consent to you:</strong></div>
138+
<br />
139+
<p>As part of work with the <strong>University of Washington</strong> in preparation for <strong>King County Regional Homelessness Authority (KCRHA) 2026 PIT Count</strong>, I'd like to ask you some questions we're required to ask and collect for <strong>KCRHA's</strong> funders about unsheltered homelessness in our region.</p>
140+
<p>Your participation is voluntary and will not affect any services you or your family are seeking or currently receiving. We are surveying many people and will put all responses together, so it will not be possible to identify you from the information you provide here.</p>
141+
<p>As a token of appreciation for your time, we will give you a <strong>$20 pre-loaded debit card</strong>.</p>
142+
<p>Would you be willing to talk with me for about <strong>30 minutes</strong>?</p>`
158143
},
159144
{
160145
type: 'radiogroup',
@@ -218,20 +203,6 @@ const giftCardPage = {
218203
name: 'giftCards',
219204
title: 'Gift Cards',
220205
elements: [
221-
{
222-
type: 'html',
223-
name: 'referral_instruction',
224-
html: `<div style="background-color: #fff3cd; padding: 10px; margin: 10px 0;">
225-
<strong>Instructions:</strong> Please read the following information out loud to the respondent.
226-
</div>
227-
<br />
228-
<p>
229-
At the end of this survey, we will give you <strong>referral coupons</strong>.
230-
Please give them to other people experiencing homelessness.
231-
If they come and take our survey using your coupon, we can send you <strong>$5 gift cards</strong> for each completed referral.
232-
To receive these gift cards, we need either a <strong>phone number or email address</strong>.
233-
</p>`
234-
},
235206
{
236207
type: 'checkbox',
237208
name: 'email_phone_consent',
@@ -262,7 +233,7 @@ const giftCardPage = {
262233
title: "What's your phone number?",
263234
maskType: 'pattern',
264235
maskSettings: {
265-
pattern: '+1(999)-999-99-99'
236+
pattern: '+9(999)-999-99-99'
266237
}
267238
}
268239
]
@@ -312,7 +283,7 @@ const personalLivingSituationPage = {
312283
],
313284
showNoneItem: true,
314285
isRequired: false,
315-
visibleIf: "{sleeping_situation} notempty and !({sleeping_situation} anyof ['small_vehicle', 'large_vehicle', 'choose_not_to_answer', 'do_not_know'])"
286+
visibleIf: "{sleeping_situation} notempty and !{sleeping_situation} anyof ['small_vehicle', 'large_vehicle']"
316287
}
317288
]
318289
};
@@ -322,13 +293,6 @@ const networkPage1 = {
322293
name: 'network_questions_p1',
323294
title: 'Network Module',
324295
elements: [
325-
{
326-
type: 'html',
327-
name: 'network_size_instruction',
328-
html: `<div style="background-color: #fff3cd; padding: 10px; margin: 10px 0;">
329-
<strong>Instructions:</strong> If the respondent says "hundreds" or "too many" emphasize the "personally know" part of the question. They would need to both recognize each other and know some information about each other.
330-
</div>`
331-
},
332296
{
333297
type: 'text',
334298
name: 'non_family_network_size',
@@ -487,40 +451,40 @@ const shelterServicesPage = {
487451
cellType: 'dropdown',
488452
choices: [
489453
{
490-
value: '1',
491-
text: 'Street Outreach'
454+
value: '1 ',
455+
text: ' Street Outreach'
492456
},
493457
{
494-
value: '2',
495-
text: 'Diversion'
458+
value: '2 ',
459+
text: ' Diversion'
496460
},
497461
{
498-
value: '3',
499-
text: 'Emergency Shelter'
462+
value: '3 ',
463+
text: ' Emergency Shelter'
500464
},
501465
{
502-
value: '4',
503-
text: 'Temporary Housing'
466+
value: '4 ',
467+
text: ' Temporary Housing'
504468
},
505469
{
506-
value: '5',
507-
text: 'Coordinated Entry'
470+
value: '5 ',
471+
text: ' Coordinated Entry'
508472
},
509473
{
510-
value: '6',
511-
text: 'Severe Weather Shelter'
474+
value: '6 ',
475+
text: ' Severe Weather Shelter'
512476
},
513477
{
514-
value: '7',
515-
text: 'Day Center'
478+
value: '7 ',
479+
text: ' Day Center'
516480
},
517481
{
518-
value: '8',
519-
text: 'Food bank'
482+
value: '8 ',
483+
text: ' Food bank'
520484
},
521485
{
522-
value: '9',
523-
text: 'Case Management'
486+
value: '9 ',
487+
text: ' Case Management'
524488
}
525489
],
526490
showOtherItem: true
@@ -1353,11 +1317,11 @@ const surveyDeduplicationPage = {
13531317
title: 'Survey Deduplication',
13541318
elements: [
13551319
{
1356-
type: 'radiogroup',
1320+
type: 'dropdown',
13571321
name: 'deduplication',
13581322
title: "After completing this survey, do you believe you have completed this before?",
13591323
choices: ['Yes', 'No', 'Do not know'],
1360-
isRequired: false
1324+
isRequired: true
13611325
}
13621326
]
13631327
};
@@ -1372,4 +1336,4 @@ const outroPage = {
13721336
html: "<strong>That completes the questions for today's survey. Thank you so much for your responses and your time. We will now issue the gift card and prepare your coupons to pass out to others in your network who are unsheltered.</strong>"
13731337
}
13741338
]
1375-
};
1339+
};
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
export const themeJson = {
2+
themeName: 'default',
3+
colorPalette: 'light',
4+
isPanelless: false,
5+
backgroundImageFit: 'cover' as const,
6+
backgroundImageAttachment: 'scroll' as const,
7+
backgroundOpacity: 1,
8+
cssVariables: {
9+
'--sjs-font-family': 'Open Sans',
10+
'--sjs-font-size': '16px',
11+
'--sjs-corner-radius': '4px',
12+
'--sjs-base-unit': '8px',
13+
'--sjs-shadow-small': '0px 1px 2px 0px rgba(0, 0, 0, 0.15)',
14+
'--sjs-shadow-inner': 'inset 0px 1px 2px 0px rgba(0, 0, 0, 0.15)',
15+
'--sjs-border-default': 'rgba(0, 0, 0, 0.16)',
16+
'--sjs-border-light': 'rgba(0, 0, 0, 0.09)',
17+
'--sjs-general-backcolor': 'rgba(255, 255, 255, 1)',
18+
'--sjs-general-backcolor-dark': 'rgba(248, 248, 248, 1)',
19+
'--sjs-general-backcolor-dim-light': 'rgba(249, 249, 249, 1)',
20+
'--sjs-general-backcolor-dim-dark': 'rgba(243, 243, 243, 1)',
21+
'--sjs-general-forecolor': 'rgba(0, 0, 0, 0.91)',
22+
'--sjs-general-forecolor-light': 'rgba(0, 0, 0, 0.45)',
23+
'--sjs-general-dim-forecolor': 'rgba(0, 0, 0, 0.91)',
24+
'--sjs-general-dim-forecolor-light': 'rgba(0, 0, 0, 0.45)',
25+
'--sjs-secondary-backcolor': 'rgba(255, 152, 20, 1)',
26+
'--sjs-secondary-backcolor-light': 'rgba(255, 152, 20, 0.1)',
27+
'--sjs-secondary-backcolor-semi-light': 'rgba(255, 152, 20, 0.25)',
28+
'--sjs-secondary-forecolor': 'rgba(255, 255, 255, 1)',
29+
'--sjs-secondary-forecolor-light': 'rgba(255, 255, 255, 0.25)',
30+
'--sjs-shadow-small-reset': '0px 0px 0px 0px rgba(0, 0, 0, 0.15)',
31+
'--sjs-shadow-medium': '0px 2px 6px 0px rgba(0, 0, 0, 0.1)',
32+
'--sjs-shadow-large': '0px 8px 16px 0px rgba(0, 0, 0, 0.1)',
33+
'--sjs-shadow-inner-reset': 'inset 0px 0px 0px 0px rgba(0, 0, 0, 0.15)',
34+
'--sjs-border-inside': 'rgba(0, 0, 0, 0.16)',
35+
'--sjs-special-red-forecolor': 'rgba(255, 255, 255, 1)',
36+
'--sjs-special-green': 'rgba(25, 179, 148, 1)',
37+
'--sjs-special-green-light': 'rgba(25, 179, 148, 0.1)',
38+
'--sjs-special-green-forecolor': 'rgba(255, 255, 255, 1)',
39+
'--sjs-special-blue': 'rgba(67, 127, 217, 1)',
40+
'--sjs-special-blue-light': 'rgba(67, 127, 217, 0.1)',
41+
'--sjs-special-blue-forecolor': 'rgba(255, 255, 255, 1)',
42+
'--sjs-special-yellow': 'rgba(255, 152, 20, 1)',
43+
'--sjs-special-yellow-light': 'rgba(255, 152, 20, 0.1)',
44+
'--sjs-special-yellow-forecolor': 'rgba(255, 255, 255, 1)',
45+
'--sjs-article-font-xx-large-textDecoration': 'none',
46+
'--sjs-article-font-xx-large-fontWeight': '700',
47+
'--sjs-article-font-xx-large-fontStyle': 'normal',
48+
'--sjs-article-font-xx-large-fontStretch': 'normal',
49+
'--sjs-article-font-xx-large-letterSpacing': '0',
50+
'--sjs-article-font-xx-large-lineHeight': '64px',
51+
'--sjs-article-font-xx-large-paragraphIndent': '0px',
52+
'--sjs-article-font-xx-large-textCase': 'none',
53+
'--sjs-article-font-x-large-textDecoration': 'none',
54+
'--sjs-article-font-x-large-fontWeight': '700',
55+
'--sjs-article-font-x-large-fontStyle': 'normal',
56+
'--sjs-article-font-x-large-fontStretch': 'normal',
57+
'--sjs-article-font-x-large-letterSpacing': '0',
58+
'--sjs-article-font-x-large-lineHeight': '56px',
59+
'--sjs-article-font-x-large-paragraphIndent': '0px',
60+
'--sjs-article-font-x-large-textCase': 'none',
61+
'--sjs-article-font-large-textDecoration': 'none',
62+
'--sjs-article-font-large-fontWeight': '700',
63+
'--sjs-article-font-large-fontStyle': 'normal',
64+
'--sjs-article-font-large-fontStretch': 'normal',
65+
'--sjs-article-font-large-letterSpacing': '0',
66+
'--sjs-article-font-large-lineHeight': '40px',
67+
'--sjs-article-font-large-paragraphIndent': '0px',
68+
'--sjs-article-font-large-textCase': 'none',
69+
'--sjs-article-font-medium-textDecoration': 'none',
70+
'--sjs-article-font-medium-fontWeight': '700',
71+
'--sjs-article-font-medium-fontStyle': 'normal',
72+
'--sjs-article-font-medium-fontStretch': 'normal',
73+
'--sjs-article-font-medium-letterSpacing': '0',
74+
'--sjs-article-font-medium-lineHeight': '32px',
75+
'--sjs-article-font-medium-paragraphIndent': '0px',
76+
'--sjs-article-font-medium-textCase': 'none',
77+
'--sjs-article-font-default-textDecoration': 'none',
78+
'--sjs-article-font-default-fontWeight': '400',
79+
'--sjs-article-font-default-fontStyle': 'normal',
80+
'--sjs-article-font-default-fontStretch': 'normal',
81+
'--sjs-article-font-default-letterSpacing': '0',
82+
'--sjs-article-font-default-lineHeight': '28px',
83+
'--sjs-article-font-default-paragraphIndent': '0px',
84+
'--sjs-article-font-default-textCase': 'none',
85+
'--sjs-general-backcolor-dim': '',
86+
'--sjs-primary-backcolor': '#32006e',
87+
'--sjs-primary-backcolor-dark': 'rgba(43, 0, 95, 1)',
88+
'--sjs-primary-backcolor-light': 'rgba(50, 0, 110, 0.1)',
89+
'--sjs-primary-forecolor': 'rgba(255, 255, 255, 1)',
90+
'--sjs-primary-forecolor-light': 'rgba(255, 255, 255, 0.25)',
91+
'--sjs-special-red': 'rgba(229, 10, 62, 1)',
92+
'--sjs-special-red-light': 'rgba(229, 10, 62, 0.1)'
93+
},
94+
header: {
95+
height: 0,
96+
mobileHeight: 0,
97+
inheritWidthFrom: 'survey' as const,
98+
textAreaWidth: 0,
99+
backgroundImageFit: 'cover' as const,
100+
backgroundImageOpacity: 100,
101+
overlapEnabled: false,
102+
logoPositionX: 'left' as const,
103+
logoPositionY: 'top' as const,
104+
titlePositionX: 'left' as const,
105+
titlePositionY: 'bottom' as const,
106+
descriptionPositionX: 'left' as const,
107+
descriptionPositionY: 'bottom' as const
108+
},
109+
headerView: 'basic' as const
110+
};

client/src/pages/Survey/utils/surveyUtils.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { LocationDocument } from '@/types/Locations';
44
import { SurveyDocument } from '@/types/Survey';
55

66
import { generateEditSurveyJson, generateSurveyJson } from './SurveyJson';
7+
import { themeJson } from './surveyTheme';
78

89
// Helper function to initialize survey with or without existing data
910
export const initializeSurvey = (
@@ -22,6 +23,9 @@ export const initializeSurvey = (
2223
? generateEditSurveyJson(locationChoices)
2324
: generateSurveyJson(locationChoices);
2425
const survey = new Model(surveyJson);
26+
27+
// Apply custom theme
28+
survey.applyTheme(themeJson);
2529

2630
// Populate with existing data from objectId if found
2731
if (surveyByObjectId) {

0 commit comments

Comments
 (0)