@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.
@@ -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: 16px;
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: 14px;
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: 16px;
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: 14px;
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: 16px;
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: 12px;
408
+ font-size: 0.75em;
409
409
  color: var(--confetti-text-subtle-color);
410
410
  font-weight: 500;
411
- line-height: 16px;
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: Record<string, string>;
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
- closeOnFinishMs?: number;
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 ?? internalRespondentId}`).json().then((data) => {
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 ?? internalRespondentId,
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
- question.visible = isVisible({
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
- return /* @__PURE__ */ jsx("div", {
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 ?? "Feedback submitted!"
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: description ?? "Thank you for your feedback!"
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.1",
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/prettier-config": "0.0.0",
44
- "@confetti/eslint-config": "0.0.0"
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": {