@planetaexo/design-system 0.4.6 → 0.4.7

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/dist/index.d.cts CHANGED
@@ -549,6 +549,7 @@ interface RegistrationFormLabels {
549
549
  nationalityLabel?: string;
550
550
  selectPlaceholder?: string;
551
551
  optionalLabel?: string;
552
+ requiredFieldError?: string;
552
553
  }
553
554
  interface RegistrationFormProps {
554
555
  /** Logo shown above the title. Defaults to "/logo-planetaexo.png" (matching Offer). Pass null to hide. */
@@ -656,9 +657,10 @@ interface BirthDateFieldProps {
656
657
  required?: boolean;
657
658
  value: Date | undefined;
658
659
  onChange: (date: Date | undefined) => void;
660
+ error?: string;
659
661
  className?: string;
660
662
  }
661
- declare function BirthDateField({ label, required, value, onChange, className, }: BirthDateFieldProps): react_jsx_runtime.JSX.Element;
663
+ declare function BirthDateField({ label, required, value, onChange, error, className, }: BirthDateFieldProps): react_jsx_runtime.JSX.Element;
662
664
 
663
665
  interface FilterItem {
664
666
  id: string;
package/dist/index.d.ts CHANGED
@@ -549,6 +549,7 @@ interface RegistrationFormLabels {
549
549
  nationalityLabel?: string;
550
550
  selectPlaceholder?: string;
551
551
  optionalLabel?: string;
552
+ requiredFieldError?: string;
552
553
  }
553
554
  interface RegistrationFormProps {
554
555
  /** Logo shown above the title. Defaults to "/logo-planetaexo.png" (matching Offer). Pass null to hide. */
@@ -656,9 +657,10 @@ interface BirthDateFieldProps {
656
657
  required?: boolean;
657
658
  value: Date | undefined;
658
659
  onChange: (date: Date | undefined) => void;
660
+ error?: string;
659
661
  className?: string;
660
662
  }
661
- declare function BirthDateField({ label, required, value, onChange, className, }: BirthDateFieldProps): react_jsx_runtime.JSX.Element;
663
+ declare function BirthDateField({ label, required, value, onChange, error, className, }: BirthDateFieldProps): react_jsx_runtime.JSX.Element;
662
664
 
663
665
  interface FilterItem {
664
666
  id: string;
package/dist/index.js CHANGED
@@ -320,14 +320,16 @@ var FloatingInput = React21.forwardRef(
320
320
  FloatingInput.displayName = "FloatingInput";
321
321
  var FloatingSelect = React21.forwardRef(
322
322
  (_a, ref) => {
323
- var _b = _a, { label, error, id, className, required, children } = _b, props = __objRest(_b, ["label", "error", "id", "className", "required", "children"]);
323
+ var _b = _a, { label, error, id, className, required, children, value } = _b, props = __objRest(_b, ["label", "error", "id", "className", "required", "children", "value"]);
324
324
  const inputId = id != null ? id : React21.useId();
325
+ const hasValue = typeof value === "string" ? value !== "" : value !== void 0 && value !== null;
325
326
  return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
326
327
  /* @__PURE__ */ jsx(
327
328
  "select",
328
329
  __spreadProps(__spreadValues({
329
330
  id: inputId,
330
331
  ref,
332
+ value,
331
333
  className: cn(
332
334
  "peer block w-full h-14 appearance-none rounded-lg border border-border bg-background",
333
335
  "px-3 pt-6 pb-2 text-base text-foreground font-ui",
@@ -361,7 +363,7 @@ var FloatingSelect = React21.forwardRef(
361
363
  "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2",
362
364
  "text-base text-muted-foreground font-ui transition-all duration-150",
363
365
  "peer-focus:top-2 peer-focus:translate-y-0 peer-focus:text-xs peer-focus:text-primary",
364
- "peer-not-placeholder-shown:top-2 peer-not-placeholder-shown:translate-y-0 peer-not-placeholder-shown:text-xs"
366
+ hasValue && "top-2 translate-y-0 text-xs"
365
367
  ),
366
368
  children: [
367
369
  label,
@@ -889,6 +891,7 @@ function BirthDateField({
889
891
  required,
890
892
  value,
891
893
  onChange,
894
+ error,
892
895
  className
893
896
  }) {
894
897
  const [open, setOpen] = React21.useState(false);
@@ -931,7 +934,7 @@ function BirthDateField({
931
934
  {
932
935
  className: cn(
933
936
  "flex items-center rounded-lg border border-border bg-background h-14 transition-colors",
934
- open ? "border-primary ring-1 ring-primary" : "focus-within:border-primary focus-within:ring-1 focus-within:ring-primary"
937
+ error ? "border-destructive focus-within:border-destructive focus-within:ring-destructive" : open ? "border-primary ring-1 ring-primary" : "focus-within:border-primary focus-within:ring-1 focus-within:ring-primary"
935
938
  ),
936
939
  children: [
937
940
  /* @__PURE__ */ jsxs("div", { className: "relative flex-1 h-full", children: [
@@ -3963,7 +3966,7 @@ function BookingForm({
3963
3966
  );
3964
3967
  }
3965
3968
  var DEFAULT_LABELS3 = {
3966
- formSubtitle: "Free enquiry \u2013 no commitment",
3969
+ formSubtitle: "To confirm your participation, please complete this short form. It's required for all travellers and helps us coordinate logistics with our local partners and tailor the experience to you.",
3967
3970
  detailsSectionTitle: "Your details",
3968
3971
  tripInfoSectionTitle: "Trip info",
3969
3972
  termsSectionTitle: "Terms & conditions",
@@ -3983,7 +3986,8 @@ var DEFAULT_LABELS3 = {
3983
3986
  emergencyContactPhoneLabel: "Contact phone",
3984
3987
  nationalityLabel: "Nationality",
3985
3988
  selectPlaceholder: "Select an option\u2026",
3986
- optionalLabel: "(optional)"
3989
+ optionalLabel: "(optional)",
3990
+ requiredFieldError: "Required"
3987
3991
  };
3988
3992
  var TERMS_ACCEPT_KEY = "__registrationTermsAccepted";
3989
3993
  function emptyName() {
@@ -4086,7 +4090,34 @@ function FormSection2({
4086
4090
  children
4087
4091
  ] });
4088
4092
  }
4089
- function PhoneInput({ id, label, required, value, onChange }) {
4093
+ function isFieldEmpty(field, value) {
4094
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
4095
+ if (value === void 0 || value === null) return true;
4096
+ switch (field.type) {
4097
+ case "phone": {
4098
+ const p = value;
4099
+ return !((_a = p == null ? void 0 : p.nationalNumber) == null ? void 0 : _a.trim());
4100
+ }
4101
+ case "name": {
4102
+ const n = value;
4103
+ return !((_b = n == null ? void 0 : n.firstName) == null ? void 0 : _b.trim()) && !((_c = n == null ? void 0 : n.lastName) == null ? void 0 : _c.trim());
4104
+ }
4105
+ case "emergencyContact": {
4106
+ const ec = value;
4107
+ return !((_e = (_d = ec == null ? void 0 : ec.contactName) == null ? void 0 : _d.firstName) == null ? void 0 : _e.trim()) && !((_g = (_f = ec == null ? void 0 : ec.contactName) == null ? void 0 : _f.lastName) == null ? void 0 : _g.trim()) && !((_i = (_h = ec == null ? void 0 : ec.phone) == null ? void 0 : _h.nationalNumber) == null ? void 0 : _i.trim());
4108
+ }
4109
+ case "checkbox":
4110
+ if (Array.isArray(value)) return value.length === 0;
4111
+ return value === false;
4112
+ case "date":
4113
+ case "birthDate":
4114
+ if (value instanceof Date) return false;
4115
+ return typeof value !== "string" || !value.trim();
4116
+ default:
4117
+ return typeof value === "string" ? !value.trim() : false;
4118
+ }
4119
+ }
4120
+ function PhoneInput({ id, label, required, value, onChange, error }) {
4090
4121
  return /* @__PURE__ */ jsxs("div", { className: "flex w-full min-w-0", children: [
4091
4122
  /* @__PURE__ */ jsx(
4092
4123
  PhoneCountrySelect,
@@ -4110,7 +4141,8 @@ function PhoneInput({ id, label, required, value, onChange }) {
4110
4141
  "peer block h-14 w-full rounded-r-lg border border-border bg-background",
4111
4142
  "px-3 pt-5 pb-2 text-base text-foreground font-ui",
4112
4143
  "transition-colors placeholder-transparent",
4113
- "focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary"
4144
+ "focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary",
4145
+ error && "border-destructive focus:border-destructive focus:ring-destructive"
4114
4146
  )
4115
4147
  }
4116
4148
  ),
@@ -4139,7 +4171,8 @@ function FloatingTextarea({
4139
4171
  required,
4140
4172
  optionalHint,
4141
4173
  value,
4142
- onChange
4174
+ onChange,
4175
+ error
4143
4176
  }) {
4144
4177
  return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4145
4178
  /* @__PURE__ */ jsx(
@@ -4155,7 +4188,8 @@ function FloatingTextarea({
4155
4188
  "peer block w-full resize-none rounded-lg border border-border bg-background",
4156
4189
  "px-3 pt-6 pb-3 text-base text-foreground font-ui",
4157
4190
  "transition-colors placeholder-transparent",
4158
- "focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary"
4191
+ "focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary",
4192
+ error && "border-destructive focus:border-destructive focus:ring-destructive"
4159
4193
  )
4160
4194
  }
4161
4195
  ),
@@ -4182,7 +4216,8 @@ function FieldRenderer({
4182
4216
  value,
4183
4217
  onChange,
4184
4218
  defaultPhoneCountry,
4185
- labels
4219
+ labels,
4220
+ error
4186
4221
  }) {
4187
4222
  var _a, _b, _c;
4188
4223
  const fieldId = `rf-${field.id}`;
@@ -4200,7 +4235,8 @@ function FieldRenderer({
4200
4235
  label: labels.firstNameLabel,
4201
4236
  required: field.required,
4202
4237
  value: v.firstName,
4203
- onChange: (e) => onChange(__spreadProps(__spreadValues({}, v), { firstName: e.target.value }))
4238
+ onChange: (e) => onChange(__spreadProps(__spreadValues({}, v), { firstName: e.target.value })),
4239
+ error: error && !v.firstName.trim() ? error : void 0
4204
4240
  }
4205
4241
  ),
4206
4242
  /* @__PURE__ */ jsx(
@@ -4209,7 +4245,8 @@ function FieldRenderer({
4209
4245
  label: labels.lastNameLabel,
4210
4246
  required: field.required,
4211
4247
  value: v.lastName,
4212
- onChange: (e) => onChange(__spreadProps(__spreadValues({}, v), { lastName: e.target.value }))
4248
+ onChange: (e) => onChange(__spreadProps(__spreadValues({}, v), { lastName: e.target.value })),
4249
+ error: error && !v.lastName.trim() ? error : void 0
4213
4250
  }
4214
4251
  )
4215
4252
  ] })
@@ -4224,7 +4261,8 @@ function FieldRenderer({
4224
4261
  label: field.label,
4225
4262
  required: field.required,
4226
4263
  value: v,
4227
- onChange
4264
+ onChange,
4265
+ error
4228
4266
  }
4229
4267
  );
4230
4268
  }
@@ -4247,7 +4285,8 @@ function FieldRenderer({
4247
4285
  contactName: __spreadProps(__spreadValues({}, v.contactName), {
4248
4286
  firstName: e.target.value
4249
4287
  })
4250
- }))
4288
+ })),
4289
+ error: error && !v.contactName.firstName.trim() ? error : void 0
4251
4290
  }
4252
4291
  ),
4253
4292
  /* @__PURE__ */ jsx(
@@ -4260,7 +4299,8 @@ function FieldRenderer({
4260
4299
  contactName: __spreadProps(__spreadValues({}, v.contactName), {
4261
4300
  lastName: e.target.value
4262
4301
  })
4263
- }))
4302
+ })),
4303
+ error: error && !v.contactName.lastName.trim() ? error : void 0
4264
4304
  }
4265
4305
  )
4266
4306
  ] }),
@@ -4271,7 +4311,8 @@ function FieldRenderer({
4271
4311
  label: labels.emergencyContactPhoneLabel,
4272
4312
  required: field.required,
4273
4313
  value: v.phone,
4274
- onChange: (phone) => onChange(__spreadProps(__spreadValues({}, v), { phone }))
4314
+ onChange: (phone) => onChange(__spreadProps(__spreadValues({}, v), { phone })),
4315
+ error: error && !v.phone.nationalNumber.trim() ? error : void 0
4275
4316
  }
4276
4317
  )
4277
4318
  ] })
@@ -4286,7 +4327,8 @@ function FieldRenderer({
4286
4327
  required: field.required,
4287
4328
  optionalHint: !field.required ? labels.optionalLabel : void 0,
4288
4329
  value: typeof value === "string" ? value : "",
4289
- onChange
4330
+ onChange,
4331
+ error
4290
4332
  }
4291
4333
  );
4292
4334
  }
@@ -4299,6 +4341,7 @@ function FieldRenderer({
4299
4341
  required: field.required,
4300
4342
  value: typeof value === "string" ? value : "",
4301
4343
  onChange: (e) => onChange(e.target.value),
4344
+ error,
4302
4345
  children: [
4303
4346
  /* @__PURE__ */ jsx("option", { value: "", disabled: true, hidden: true }),
4304
4347
  options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
@@ -4404,25 +4447,15 @@ function FieldRenderer({
4404
4447
  }
4405
4448
  );
4406
4449
  }
4407
- if (field.type === "date") {
4408
- return /* @__PURE__ */ jsx(
4409
- DatePickerField,
4410
- {
4411
- label: field.label,
4412
- required: field.required,
4413
- value: asDate(value),
4414
- onChange: (d) => onChange(d)
4415
- }
4416
- );
4417
- }
4418
- if (field.type === "birthDate") {
4450
+ if (field.type === "date" || field.type === "birthDate") {
4419
4451
  return /* @__PURE__ */ jsx(
4420
4452
  BirthDateField,
4421
4453
  {
4422
4454
  label: field.label,
4423
4455
  required: field.required,
4424
4456
  value: asDate(value),
4425
- onChange: (d) => onChange(d)
4457
+ onChange: (d) => onChange(d),
4458
+ error
4426
4459
  }
4427
4460
  );
4428
4461
  }
@@ -4434,7 +4467,8 @@ function FieldRenderer({
4434
4467
  required: field.required,
4435
4468
  type: "number",
4436
4469
  value: value == null ? "" : String(value),
4437
- onChange: (e) => onChange(e.target.value)
4470
+ onChange: (e) => onChange(e.target.value),
4471
+ error
4438
4472
  }
4439
4473
  );
4440
4474
  }
@@ -4446,7 +4480,8 @@ function FieldRenderer({
4446
4480
  required: field.required,
4447
4481
  type: htmlType,
4448
4482
  value: typeof value === "string" ? value : "",
4449
- onChange: (e) => onChange(e.target.value)
4483
+ onChange: (e) => onChange(e.target.value),
4484
+ error
4450
4485
  }
4451
4486
  );
4452
4487
  }
@@ -4495,6 +4530,7 @@ function RegistrationForm({
4495
4530
  includeTerms
4496
4531
  )
4497
4532
  );
4533
+ const [submitAttempted, setSubmitAttempted] = React21.useState(false);
4498
4534
  React21.useEffect(() => {
4499
4535
  if (isControlled) return;
4500
4536
  setInternal((prev) => {
@@ -4516,15 +4552,28 @@ function RegistrationForm({
4516
4552
  if (!isControlled) setInternal(next);
4517
4553
  onChange == null ? void 0 : onChange(next);
4518
4554
  };
4555
+ const termsAccepted = current[TERMS_ACCEPT_KEY] === true;
4556
+ const termsEnabled = includeTerms && !!terms;
4557
+ const acceptControl = (_a = terms == null ? void 0 : terms.acceptControl) != null ? _a : "checkbox";
4519
4558
  const handleSubmit = (e) => {
4520
4559
  e.preventDefault();
4560
+ setSubmitAttempted(true);
4561
+ const hasRequiredEmpty = sortedFields.some(
4562
+ (f) => f.required && isFieldEmpty(f, current[f.id])
4563
+ );
4564
+ if (hasRequiredEmpty || termsEnabled && !termsAccepted) return;
4521
4565
  onSubmit == null ? void 0 : onSubmit(current);
4522
4566
  };
4523
- const termsAccepted = current[TERMS_ACCEPT_KEY] === true;
4524
- const termsEnabled = includeTerms && !!terms;
4525
- const acceptControl = (_a = terms == null ? void 0 : terms.acceptControl) != null ? _a : "checkbox";
4526
4567
  const dateRange = formatDateRange(adventure, dateFormatter);
4527
4568
  const hasTripInfo = !!adventure || !!booking || !!traveller;
4569
+ const fieldErrors = {};
4570
+ if (submitAttempted) {
4571
+ for (const field of sortedFields) {
4572
+ if (field.required && isFieldEmpty(field, current[field.id])) {
4573
+ fieldErrors[field.id] = L.requiredFieldError;
4574
+ }
4575
+ }
4576
+ }
4528
4577
  return /* @__PURE__ */ jsxs(
4529
4578
  "form",
4530
4579
  {
@@ -4608,7 +4657,8 @@ function RegistrationForm({
4608
4657
  value: current[field.id],
4609
4658
  onChange: (v) => setField(field.id, v),
4610
4659
  defaultPhoneCountry,
4611
- labels: L
4660
+ labels: L,
4661
+ error: fieldErrors[field.id]
4612
4662
  }
4613
4663
  ),
4614
4664
  ((_a2 = field.helpText) == null ? void 0 : _a2.trim()) && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground font-ui leading-relaxed", children: field.helpText.trim() })