@opengovsg/confetti 0.0.1 → 0.0.3
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/build/confetti.css +10 -10
- package/build/index.d.ts +20 -8
- package/build/index.js +51 -15
- package/package.json +3 -3
package/build/confetti.css
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
:root {
|
|
2
|
+
--confetti-font-scale: 16px;
|
|
2
3
|
/* Define CSS Variables with defaults */
|
|
3
|
-
--confetti-font-family:
|
|
4
|
-
-apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', 'Roboto', Helvetica,
|
|
5
|
-
Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
|
6
4
|
--confetti-box-padding: 20px 24px;
|
|
7
5
|
--confetti-max-width: 300px;
|
|
8
6
|
--confetti-z-index: 2147483647;
|
|
@@ -39,6 +37,7 @@
|
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
.confetti-dialog {
|
|
40
|
+
width: 100%;
|
|
42
41
|
max-width: var(--confetti-max-width);
|
|
43
42
|
background: transparent;
|
|
44
43
|
position: relative;
|
|
@@ -79,6 +78,7 @@
|
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
.confetti {
|
|
81
|
+
font-family: var(--confetti-font-family);
|
|
82
82
|
overflow: hidden;
|
|
83
83
|
padding: 24px;
|
|
84
84
|
border-radius: var(--confetti-border-radius);
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
flex-direction: column;
|
|
88
88
|
gap: 16px;
|
|
89
89
|
color: var(--confetti-text-primary-color);
|
|
90
|
-
font-size:
|
|
90
|
+
font-size: var(--confetti-font-scale);
|
|
91
91
|
width: 100%;
|
|
92
92
|
background: var(--confetti-background-color);
|
|
93
93
|
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
border-radius: 8px;
|
|
102
102
|
cursor: pointer;
|
|
103
103
|
transition: transform 200ms;
|
|
104
|
-
font-size:
|
|
104
|
+
font-size: 0.875em;
|
|
105
105
|
font-weight: 500;
|
|
106
106
|
line-height: 20px;
|
|
107
107
|
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
.cf-choice {
|
|
157
|
-
font-size:
|
|
157
|
+
font-size: 1em;
|
|
158
158
|
cursor: pointer;
|
|
159
159
|
padding: 8px 10px;
|
|
160
160
|
border-radius: 6px;
|
|
@@ -336,7 +336,7 @@
|
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
.cf-description {
|
|
339
|
-
font-size:
|
|
339
|
+
font-size: 0.875em;
|
|
340
340
|
line-height: 20px;
|
|
341
341
|
font-weight: 400;
|
|
342
342
|
color: var(--confetti-text-subtle-color);
|
|
@@ -369,7 +369,7 @@
|
|
|
369
369
|
|
|
370
370
|
.cf-rating-emoji,
|
|
371
371
|
.cf-rating-star {
|
|
372
|
-
font-size:
|
|
372
|
+
font-size: 1em;
|
|
373
373
|
background-color: transparent;
|
|
374
374
|
border: none;
|
|
375
375
|
padding: 0;
|
|
@@ -405,10 +405,10 @@
|
|
|
405
405
|
.cf-rating-text {
|
|
406
406
|
display: flex;
|
|
407
407
|
justify-content: space-between;
|
|
408
|
-
font-size:
|
|
408
|
+
font-size: 0.75em;
|
|
409
409
|
color: var(--confetti-text-subtle-color);
|
|
410
410
|
font-weight: 500;
|
|
411
|
-
line-height:
|
|
411
|
+
line-height: 1;
|
|
412
412
|
|
|
413
413
|
.cf-rating-text-lower {
|
|
414
414
|
flex: 1;
|
package/build/index.d.ts
CHANGED
|
@@ -131,6 +131,11 @@ declare const publicGetSurveyOutputSchema: z.ZodMiniObject<{
|
|
|
131
131
|
}, z.core.$strip>], "type">;
|
|
132
132
|
}, z.core.$strip>], "type">;
|
|
133
133
|
}, z.core.$strip>>;
|
|
134
|
+
confirmation: z.ZodMiniObject<{
|
|
135
|
+
title: z.ZodMiniString<string>;
|
|
136
|
+
description: z.ZodMiniString<string>;
|
|
137
|
+
buttonText: z.ZodMiniString<string>;
|
|
138
|
+
}, z.core.$strip>;
|
|
134
139
|
}, z.core.$strip>;
|
|
135
140
|
type PublicGetSurveyOutput = z.infer<typeof publicGetSurveyOutputSchema>;
|
|
136
141
|
//#endregion
|
|
@@ -195,7 +200,7 @@ interface ConfettiProviderProps {
|
|
|
195
200
|
surveyId: string;
|
|
196
201
|
publishableKey: string;
|
|
197
202
|
respondent?: string;
|
|
198
|
-
metadata
|
|
203
|
+
metadata?: Record<string, string>;
|
|
199
204
|
apiBaseUrl?: string;
|
|
200
205
|
isSurveyVisible?: boolean | ((_: {
|
|
201
206
|
lastRespondedAt: Date | null;
|
|
@@ -216,6 +221,7 @@ declare const useConfetti: () => ConfettiContextValue;
|
|
|
216
221
|
interface ConfettiProps extends Omit<ConfettiProviderProps, 'children'>, Pick<ConfettiControllerProps, 'onFinished' | 'shouldAutoSubmit'> {}
|
|
217
222
|
declare const Confetti: ({
|
|
218
223
|
children,
|
|
224
|
+
onFinished,
|
|
219
225
|
shouldAutoSubmit,
|
|
220
226
|
...props
|
|
221
227
|
}: ConfettiProps & Pick<ConfettiControllerProps, "children" | "shouldAutoSubmit">) => react_jsx_runtime3.JSX.Element;
|
|
@@ -235,11 +241,11 @@ declare const ThankYouMessage: ({
|
|
|
235
241
|
title,
|
|
236
242
|
description,
|
|
237
243
|
classNames
|
|
238
|
-
}: ThankYouMessageProps) => react_jsx_runtime3.JSX.Element;
|
|
244
|
+
}: ThankYouMessageProps) => react_jsx_runtime3.JSX.Element | null;
|
|
239
245
|
//#endregion
|
|
240
246
|
//#region src/components/container/PopoverContainer.d.ts
|
|
241
247
|
interface PopoverContainerProps {
|
|
242
|
-
closeOnFinishMs?: number;
|
|
248
|
+
closeOnFinishMs?: number | null;
|
|
243
249
|
classNames?: {
|
|
244
250
|
dialog?: string;
|
|
245
251
|
closeButton?: string;
|
|
@@ -398,9 +404,7 @@ declare const MultiChoice: ({
|
|
|
398
404
|
}: MultiChoiceProps) => react_jsx_runtime3.JSX.Element;
|
|
399
405
|
//#endregion
|
|
400
406
|
//#region src/components/presentation/Flow.d.ts
|
|
401
|
-
type ChildField = ({
|
|
402
|
-
onChange
|
|
403
|
-
}: {
|
|
407
|
+
type ChildField = (_: {
|
|
404
408
|
onChange: (answer: Answer) => void;
|
|
405
409
|
question: Question$1;
|
|
406
410
|
error?: string;
|
|
@@ -409,11 +413,13 @@ interface FlowProps {
|
|
|
409
413
|
onSubmit: (answer: Answer) => void;
|
|
410
414
|
children: ChildField;
|
|
411
415
|
question: Question$1;
|
|
416
|
+
isLastQuestion: boolean;
|
|
412
417
|
}
|
|
413
418
|
declare const Flow: ({
|
|
414
419
|
onSubmit,
|
|
415
420
|
children,
|
|
416
|
-
question
|
|
421
|
+
question,
|
|
422
|
+
isLastQuestion
|
|
417
423
|
}: FlowProps) => react_jsx_runtime3.JSX.Element;
|
|
418
424
|
//#endregion
|
|
419
425
|
//#region src/components/presentation/Embed.d.ts
|
|
@@ -431,7 +437,13 @@ declare const EmbeddedConfetti: (props: ConfettiProps) => react_jsx_runtime3.JSX
|
|
|
431
437
|
//#endregion
|
|
432
438
|
//#region src/templates/PopoverConfetti.d.ts
|
|
433
439
|
interface PopoverConfettiProps extends ConfettiProps {
|
|
434
|
-
|
|
440
|
+
/**
|
|
441
|
+
* The popover will automatically close this many milliseconds
|
|
442
|
+
* after the survey is finished.
|
|
443
|
+
* Set to `null` to disable automatic closing.
|
|
444
|
+
* @default 3000
|
|
445
|
+
*/
|
|
446
|
+
closeOnFinishMs?: number | null;
|
|
435
447
|
}
|
|
436
448
|
declare const PopoverConfetti: ({
|
|
437
449
|
closeOnFinishMs,
|
package/build/index.js
CHANGED
|
@@ -123,6 +123,11 @@ const multiSelectAnswerSchemaFactory = ({ maxSelected, maxLength }, { required }
|
|
|
123
123
|
|
|
124
124
|
//#endregion
|
|
125
125
|
//#region ../validators/src/survey.ts
|
|
126
|
+
const surveyConfirmationSchema = z.object({
|
|
127
|
+
title: z.string().check(z.trim()),
|
|
128
|
+
description: z.string().check(z.trim()),
|
|
129
|
+
buttonText: z.string().check(z.trim())
|
|
130
|
+
});
|
|
126
131
|
const publicGetSurveyOutputSchema = z.object({
|
|
127
132
|
title: z.string(),
|
|
128
133
|
description: z.nullable(z.string()),
|
|
@@ -134,7 +139,8 @@ const publicGetSurveyOutputSchema = z.object({
|
|
|
134
139
|
required: z.boolean(),
|
|
135
140
|
properties: questionPropertiesSchema,
|
|
136
141
|
routingLogic: routingLogicSchema
|
|
137
|
-
}))
|
|
142
|
+
})),
|
|
143
|
+
confirmation: surveyConfirmationSchema
|
|
138
144
|
});
|
|
139
145
|
|
|
140
146
|
//#endregion
|
|
@@ -165,7 +171,7 @@ const client = ky.create({
|
|
|
165
171
|
//#endregion
|
|
166
172
|
//#region src/core/ConfettiProvider.tsx
|
|
167
173
|
const ConfettiContext = createContext(null);
|
|
168
|
-
const ConfettiProvider = ({ children, surveyId, publishableKey, respondent, metadata, isSurveyVisible: isSurveyVisibleInput, apiBaseUrl }) => {
|
|
174
|
+
const ConfettiProvider = ({ children, surveyId, publishableKey, respondent, metadata = {}, isSurveyVisible: isSurveyVisibleInput, apiBaseUrl }) => {
|
|
169
175
|
const [survey, setSurvey] = useState();
|
|
170
176
|
const [lastRespondedAt, setLastRespondedAt] = useState();
|
|
171
177
|
const responseId = useMemo(() => crypto.randomUUID(), []);
|
|
@@ -186,7 +192,7 @@ const ConfettiProvider = ({ children, surveyId, publishableKey, respondent, meta
|
|
|
186
192
|
setSurvey(parseResult.data);
|
|
187
193
|
});
|
|
188
194
|
if (!respondent && !internalRespondentId) return;
|
|
189
|
-
client$1.get(`v1/cfti/${surveyId}/respondent/${respondent
|
|
195
|
+
client$1.get(`v1/cfti/${surveyId}/respondent/${respondent || internalRespondentId}`).json().then((data) => {
|
|
190
196
|
const parseResult = publicGetRespondentOutputSchema.safeParse(data);
|
|
191
197
|
if (!parseResult.success) {
|
|
192
198
|
setLastRespondedAt(null);
|
|
@@ -203,7 +209,7 @@ const ConfettiProvider = ({ children, surveyId, publishableKey, respondent, meta
|
|
|
203
209
|
]);
|
|
204
210
|
const submit = useCallback(async ({ answers }) => {
|
|
205
211
|
await client$1.put(`v1/cfti/${surveyId}/response/${responseId}`, { json: {
|
|
206
|
-
respondent: respondent
|
|
212
|
+
respondent: respondent || internalRespondentId,
|
|
207
213
|
data: answers,
|
|
208
214
|
metadata
|
|
209
215
|
} });
|
|
@@ -351,11 +357,12 @@ const getNextQuestionState = ({ originalQuestions, answers, errors, pointer = -1
|
|
|
351
357
|
for (let i = 0; i < questions.length; i++) {
|
|
352
358
|
const question = questions[i];
|
|
353
359
|
if (!question) continue;
|
|
354
|
-
|
|
360
|
+
const visible = isVisible({
|
|
355
361
|
index: i,
|
|
356
362
|
questions,
|
|
357
363
|
answers
|
|
358
364
|
});
|
|
365
|
+
question.visible = pointer === i || visible;
|
|
359
366
|
}
|
|
360
367
|
let nextIndex = questions.findIndex((q, i) => i > pointer && q.visible && answers[q.id] === void 0);
|
|
361
368
|
if (nextIndex === -1) nextIndex = questions.findLastIndex((q) => !!q.visible);
|
|
@@ -466,13 +473,14 @@ const ConfettiController = ({ current: currentProp, shouldAutoSubmit, onFinished
|
|
|
466
473
|
|
|
467
474
|
//#endregion
|
|
468
475
|
//#region src/components/Confetti.tsx
|
|
469
|
-
const Confetti = ({ children, shouldAutoSubmit, ...props }) => {
|
|
476
|
+
const Confetti = ({ children, onFinished, shouldAutoSubmit, ...props }) => {
|
|
470
477
|
return /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
471
478
|
fallback: /* @__PURE__ */ jsx(Fragment, {}),
|
|
472
479
|
children: /* @__PURE__ */ jsx(ConfettiProvider, {
|
|
473
480
|
...props,
|
|
474
481
|
children: /* @__PURE__ */ jsx(ConfettiController, {
|
|
475
482
|
shouldAutoSubmit,
|
|
483
|
+
onFinished,
|
|
476
484
|
children
|
|
477
485
|
})
|
|
478
486
|
})
|
|
@@ -482,29 +490,36 @@ const Confetti = ({ children, shouldAutoSubmit, ...props }) => {
|
|
|
482
490
|
//#endregion
|
|
483
491
|
//#region src/components/ThankYouMessage.tsx
|
|
484
492
|
const ThankYouMessage = ({ title, description, classNames }) => {
|
|
485
|
-
|
|
493
|
+
const { survey, dismiss } = useConfetti();
|
|
494
|
+
if (!survey) return null;
|
|
495
|
+
const descriptionText = description || survey.confirmation.description;
|
|
496
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
486
497
|
className: clsx("cf-question", classNames?.question),
|
|
487
498
|
children: /* @__PURE__ */ jsxs("div", {
|
|
488
499
|
className: clsx("cf-header", classNames?.header),
|
|
489
500
|
children: [/* @__PURE__ */ jsx(Label, {
|
|
490
501
|
className: clsx("cf-title", classNames?.title),
|
|
491
|
-
children: title
|
|
492
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
502
|
+
children: title || survey.confirmation.title || "Feedback submitted!"
|
|
503
|
+
}), descriptionText && /* @__PURE__ */ jsx(Text, {
|
|
493
504
|
slot: "description",
|
|
494
505
|
className: clsx("cf-description", classNames?.description),
|
|
495
|
-
children:
|
|
506
|
+
children: descriptionText
|
|
496
507
|
})]
|
|
497
508
|
})
|
|
498
|
-
})
|
|
509
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
510
|
+
className: "cf-submit-button",
|
|
511
|
+
onPress: dismiss,
|
|
512
|
+
children: survey.confirmation.buttonText || "Close"
|
|
513
|
+
})] });
|
|
499
514
|
};
|
|
500
515
|
|
|
501
516
|
//#endregion
|
|
502
517
|
//#region src/components/container/PopoverContainer.tsx
|
|
503
518
|
const PopoverContainer = ({ children, classNames, containerProps, onDismiss, closeOnFinishMs = 3e3 }) => {
|
|
504
|
-
const { surveyId, dismiss } = useConfetti();
|
|
519
|
+
const { surveyId, survey, dismiss } = useConfetti();
|
|
505
520
|
const [isFinished, setIsFinished] = useState(false);
|
|
506
521
|
useTimeout(() => {
|
|
507
|
-
dismiss();
|
|
522
|
+
if (survey) dismiss();
|
|
508
523
|
}, isFinished ? closeOnFinishMs : null);
|
|
509
524
|
return /* @__PURE__ */ jsxs("div", {
|
|
510
525
|
className: clsx("confetti-dialog", classNames?.dialog),
|
|
@@ -1094,7 +1109,7 @@ const getInitialState = (type) => {
|
|
|
1094
1109
|
default: return "";
|
|
1095
1110
|
}
|
|
1096
1111
|
};
|
|
1097
|
-
const Flow = ({ onSubmit, children, question }) => {
|
|
1112
|
+
const Flow = ({ onSubmit, children, question, isLastQuestion }) => {
|
|
1098
1113
|
const [answer, setAnswer] = useState(getInitialState(question.properties.type));
|
|
1099
1114
|
const validator = useMemo(() => getAnswerValidator(question.properties, { required: question.required }), [question.properties, question.required]);
|
|
1100
1115
|
const error = useMemo(() => {
|
|
@@ -1102,13 +1117,30 @@ const Flow = ({ onSubmit, children, question }) => {
|
|
|
1102
1117
|
if (parseResult.success) return;
|
|
1103
1118
|
return prettifyError(parseResult.error);
|
|
1104
1119
|
}, [answer, validator]);
|
|
1120
|
+
const hasNextQuestion = useMemo(() => {
|
|
1121
|
+
const rule = question.routingLogic;
|
|
1122
|
+
if (rule.type !== "always" && (answer === "" || Array.isArray(answer) && answer.length === 0) && question.required) return true;
|
|
1123
|
+
const match = rule.type === "always" ? rule.target : rule.cases.find((condition) => evaluateCondition({
|
|
1124
|
+
answer,
|
|
1125
|
+
condition
|
|
1126
|
+
}))?.target ?? rule.default;
|
|
1127
|
+
if (match.type === "end") return false;
|
|
1128
|
+
if (match.type === "next") return !isLastQuestion;
|
|
1129
|
+
return true;
|
|
1130
|
+
}, [
|
|
1131
|
+
answer,
|
|
1132
|
+
isLastQuestion,
|
|
1133
|
+
question.required,
|
|
1134
|
+
question.routingLogic
|
|
1135
|
+
]);
|
|
1105
1136
|
return /* @__PURE__ */ jsxs(Fragment, { children: [children({
|
|
1106
1137
|
onChange: setAnswer,
|
|
1107
1138
|
question,
|
|
1108
1139
|
error
|
|
1109
1140
|
}), /* @__PURE__ */ jsx(SubmitButton, {
|
|
1110
1141
|
isDisabled: !!error,
|
|
1111
|
-
onPress: () => onSubmit(answer)
|
|
1142
|
+
onPress: () => onSubmit(answer),
|
|
1143
|
+
children: hasNextQuestion ? "Next" : "Submit"
|
|
1112
1144
|
})] });
|
|
1113
1145
|
};
|
|
1114
1146
|
|
|
@@ -1158,10 +1190,12 @@ const PopoverConfetti = ({ closeOnFinishMs, ...props }) => {
|
|
|
1158
1190
|
props.onFinished?.();
|
|
1159
1191
|
},
|
|
1160
1192
|
children: ({ current, questions, update, isSubmitted }) => {
|
|
1193
|
+
const isLastQuestion = current === questions.length - 1;
|
|
1161
1194
|
if (isSubmitted) return /* @__PURE__ */ jsx(ThankYouMessage, {});
|
|
1162
1195
|
const currentQuestion = questions[current];
|
|
1163
1196
|
if (!currentQuestion) return /* @__PURE__ */ jsx(Fragment, {});
|
|
1164
1197
|
return /* @__PURE__ */ jsx(Flow, {
|
|
1198
|
+
isLastQuestion,
|
|
1165
1199
|
question: currentQuestion,
|
|
1166
1200
|
onSubmit: (answer) => {
|
|
1167
1201
|
return update({
|
|
@@ -1184,10 +1218,12 @@ const StepperConfetti = (props) => {
|
|
|
1184
1218
|
...props,
|
|
1185
1219
|
shouldAutoSubmit: true,
|
|
1186
1220
|
children: ({ current, questions, update, isSubmitted }) => {
|
|
1221
|
+
const isLastQuestion = current === questions.length - 1;
|
|
1187
1222
|
if (isSubmitted) return /* @__PURE__ */ jsx(ThankYouMessage, {});
|
|
1188
1223
|
const currentQuestion = questions[current];
|
|
1189
1224
|
if (!currentQuestion) return /* @__PURE__ */ jsx(Fragment, {});
|
|
1190
1225
|
return /* @__PURE__ */ jsx(Flow, {
|
|
1226
|
+
isLastQuestion,
|
|
1191
1227
|
question: currentQuestion,
|
|
1192
1228
|
onSubmit: (answer) => {
|
|
1193
1229
|
return update({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengovsg/confetti",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"description": "Widgets for Confetti",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"typescript": "^5.9.3",
|
|
41
41
|
"@confetti/tsconfig": "0.0.0",
|
|
42
42
|
"@confetti/validators": "0.0.0",
|
|
43
|
-
"@confetti/
|
|
44
|
-
"@confetti/
|
|
43
|
+
"@confetti/eslint-config": "0.0.0",
|
|
44
|
+
"@confetti/prettier-config": "0.0.0"
|
|
45
45
|
},
|
|
46
46
|
"prettier": "@confetti/prettier-config",
|
|
47
47
|
"scripts": {
|