@burdenoff/website-sdk 2026.520.2 → 2026.520.4

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.
@@ -36,6 +36,39 @@ interface ContactOption {
36
36
  buttonText: string;
37
37
  buttonLink: string;
38
38
  }
39
+ /**
40
+ * One entry in the contact-form Category dropdown. Each product website
41
+ * supplies its own allow-list via the `categories` prop so the
42
+ * vocabulary stays product-appropriate (e.g. VibeControls uses
43
+ * `plugin-developer`, a manufacturing site uses `dealer`). The backend
44
+ * accepts whatever `value` string is sent and persists it directly.
45
+ *
46
+ * `value` flows to the backend; `label` is what the user sees.
47
+ */
48
+ interface ContactCategory {
49
+ /** Stored on the server. Short, kebab-case is conventional. */
50
+ value: string;
51
+ /** Human-readable label shown in the dropdown. */
52
+ label: string;
53
+ }
54
+ /**
55
+ * Conservative starter category list — Burdenoff-generic vocabulary
56
+ * (General / Sales / Support / Partnership / Press / Careers / Security).
57
+ *
58
+ * **Not** applied by default: pass it explicitly to opt in:
59
+ *
60
+ * ```tsx
61
+ * <ContactPage
62
+ * …
63
+ * categories={DEFAULT_CONTACT_CATEGORIES}
64
+ * />
65
+ * ```
66
+ *
67
+ * The component default for `categories` is `[]` (no dropdown) so this
68
+ * SDK release is backward-compatible with existing consumers that
69
+ * don't yet know about the field — see the prop docs.
70
+ */
71
+ declare const DEFAULT_CONTACT_CATEGORIES: ContactCategory[];
39
72
  interface ContactPageProps {
40
73
  /** Product name. Falls back to SDK config productId if not provided. */
41
74
  productName?: string;
@@ -55,6 +88,40 @@ interface ContactPageProps {
55
88
  heroDescription?: string;
56
89
  /** Additional contact options */
57
90
  additionalOptions?: ContactOption[];
91
+ /**
92
+ * Inquiry-type dropdown allow-list. **Defaults to `[]`** — the
93
+ * dropdown does not render unless the consumer explicitly opts in.
94
+ *
95
+ * Two ways to opt in:
96
+ *
97
+ * ```tsx
98
+ * // 1. Provide a product-specific list
99
+ * <ContactPage categories={[{ value: 'sales', label: 'Sales' }, …]} />
100
+ *
101
+ * // 2. Or use the conservative SDK starter list
102
+ * <ContactPage categories={DEFAULT_CONTACT_CATEGORIES} />
103
+ * ```
104
+ *
105
+ * Default is intentionally empty (not `DEFAULT_CONTACT_CATEGORIES`)
106
+ * to keep this SDK release backward-compatible with existing
107
+ * consumers that don't yet wire `categories` — they get zero
108
+ * behavior change.
109
+ *
110
+ * Typed `readonly` because the component never mutates the array;
111
+ * lets product sites pass an `as const`-frozen config without an
112
+ * extra `[...spread]` at the call site.
113
+ */
114
+ categories?: readonly ContactCategory[];
115
+ /** Label shown above the category dropdown. */
116
+ categoryLabel?: string;
117
+ /** Placeholder text for the unselected state of the dropdown. */
118
+ categoryPlaceholder?: string;
119
+ /**
120
+ * Whether selecting a category is required to submit. Defaults to
121
+ * `true` — the whole point of the dropdown is routing, so users
122
+ * self-triage instead of pushing that work onto support.
123
+ */
124
+ categoryRequired?: boolean;
58
125
  /** SEO metadata */
59
126
  seo?: {
60
127
  title?: string;
@@ -64,6 +131,6 @@ interface ContactPageProps {
64
131
  url?: string;
65
132
  };
66
133
  }
67
- declare function ContactPage({ productName, recaptchaSiteKey, contactEmail, contactPhone, officeAddress, responseTime, heroTitle, heroDescription, additionalOptions, seo, }: ContactPageProps): react_jsx_runtime.JSX.Element;
134
+ declare function ContactPage({ productName, recaptchaSiteKey, contactEmail, contactPhone, officeAddress, responseTime, heroTitle, heroDescription, categories, categoryLabel, categoryPlaceholder, categoryRequired, additionalOptions, seo, }: ContactPageProps): react_jsx_runtime.JSX.Element;
68
135
 
69
- export { ContactPage, type ContactPageProps };
136
+ export { type ContactCategory, ContactPage, type ContactPageProps, DEFAULT_CONTACT_CATEGORIES };
@@ -36,6 +36,39 @@ interface ContactOption {
36
36
  buttonText: string;
37
37
  buttonLink: string;
38
38
  }
39
+ /**
40
+ * One entry in the contact-form Category dropdown. Each product website
41
+ * supplies its own allow-list via the `categories` prop so the
42
+ * vocabulary stays product-appropriate (e.g. VibeControls uses
43
+ * `plugin-developer`, a manufacturing site uses `dealer`). The backend
44
+ * accepts whatever `value` string is sent and persists it directly.
45
+ *
46
+ * `value` flows to the backend; `label` is what the user sees.
47
+ */
48
+ interface ContactCategory {
49
+ /** Stored on the server. Short, kebab-case is conventional. */
50
+ value: string;
51
+ /** Human-readable label shown in the dropdown. */
52
+ label: string;
53
+ }
54
+ /**
55
+ * Conservative starter category list — Burdenoff-generic vocabulary
56
+ * (General / Sales / Support / Partnership / Press / Careers / Security).
57
+ *
58
+ * **Not** applied by default: pass it explicitly to opt in:
59
+ *
60
+ * ```tsx
61
+ * <ContactPage
62
+ * …
63
+ * categories={DEFAULT_CONTACT_CATEGORIES}
64
+ * />
65
+ * ```
66
+ *
67
+ * The component default for `categories` is `[]` (no dropdown) so this
68
+ * SDK release is backward-compatible with existing consumers that
69
+ * don't yet know about the field — see the prop docs.
70
+ */
71
+ declare const DEFAULT_CONTACT_CATEGORIES: ContactCategory[];
39
72
  interface ContactPageProps {
40
73
  /** Product name. Falls back to SDK config productId if not provided. */
41
74
  productName?: string;
@@ -55,6 +88,40 @@ interface ContactPageProps {
55
88
  heroDescription?: string;
56
89
  /** Additional contact options */
57
90
  additionalOptions?: ContactOption[];
91
+ /**
92
+ * Inquiry-type dropdown allow-list. **Defaults to `[]`** — the
93
+ * dropdown does not render unless the consumer explicitly opts in.
94
+ *
95
+ * Two ways to opt in:
96
+ *
97
+ * ```tsx
98
+ * // 1. Provide a product-specific list
99
+ * <ContactPage categories={[{ value: 'sales', label: 'Sales' }, …]} />
100
+ *
101
+ * // 2. Or use the conservative SDK starter list
102
+ * <ContactPage categories={DEFAULT_CONTACT_CATEGORIES} />
103
+ * ```
104
+ *
105
+ * Default is intentionally empty (not `DEFAULT_CONTACT_CATEGORIES`)
106
+ * to keep this SDK release backward-compatible with existing
107
+ * consumers that don't yet wire `categories` — they get zero
108
+ * behavior change.
109
+ *
110
+ * Typed `readonly` because the component never mutates the array;
111
+ * lets product sites pass an `as const`-frozen config without an
112
+ * extra `[...spread]` at the call site.
113
+ */
114
+ categories?: readonly ContactCategory[];
115
+ /** Label shown above the category dropdown. */
116
+ categoryLabel?: string;
117
+ /** Placeholder text for the unselected state of the dropdown. */
118
+ categoryPlaceholder?: string;
119
+ /**
120
+ * Whether selecting a category is required to submit. Defaults to
121
+ * `true` — the whole point of the dropdown is routing, so users
122
+ * self-triage instead of pushing that work onto support.
123
+ */
124
+ categoryRequired?: boolean;
58
125
  /** SEO metadata */
59
126
  seo?: {
60
127
  title?: string;
@@ -64,6 +131,6 @@ interface ContactPageProps {
64
131
  url?: string;
65
132
  };
66
133
  }
67
- declare function ContactPage({ productName, recaptchaSiteKey, contactEmail, contactPhone, officeAddress, responseTime, heroTitle, heroDescription, additionalOptions, seo, }: ContactPageProps): react_jsx_runtime.JSX.Element;
134
+ declare function ContactPage({ productName, recaptchaSiteKey, contactEmail, contactPhone, officeAddress, responseTime, heroTitle, heroDescription, categories, categoryLabel, categoryPlaceholder, categoryRequired, additionalOptions, seo, }: ContactPageProps): react_jsx_runtime.JSX.Element;
68
135
 
69
- export { ContactPage, type ContactPageProps };
136
+ export { type ContactCategory, ContactPage, type ContactPageProps, DEFAULT_CONTACT_CATEGORIES };
@@ -322,6 +322,28 @@ var Label = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__
322
322
  }
323
323
  ));
324
324
  Label.displayName = LabelPrimitive__namespace.Root.displayName;
325
+ var Select = React__namespace.forwardRef(({ className, children, ...props }, ref) => {
326
+ return /* @__PURE__ */ jsxRuntime.jsx(
327
+ "select",
328
+ {
329
+ className: cn(
330
+ "flex h-9 w-full appearance-none rounded-md border border-input bg-transparent bg-[length:1rem_1rem] bg-[right_0.75rem_center] bg-no-repeat px-3 py-1 pr-8 text-base shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
331
+ // Inline SVG chevron: SVG inside a `background-image: url(data:…)`
332
+ // does NOT inherit `currentColor` from the host element (the SVG is
333
+ // its own document with no parent CSS context), so the stroke is
334
+ // hardcoded to a muted slate-400 (#94a3b8). Matches the muted
335
+ // foreground token visually in both light and dark themes well
336
+ // enough for a passive chevron — addresses #9 review feedback.
337
+ `bg-[url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>")]`,
338
+ className
339
+ ),
340
+ ref,
341
+ ...props,
342
+ children
343
+ }
344
+ );
345
+ });
346
+ Select.displayName = "Select";
325
347
  var Textarea = React__namespace.forwardRef(({ className, ...props }, ref) => {
326
348
  return /* @__PURE__ */ jsxRuntime.jsx(
327
349
  "textarea",
@@ -336,6 +358,15 @@ var Textarea = React__namespace.forwardRef(({ className, ...props }, ref) => {
336
358
  );
337
359
  });
338
360
  Textarea.displayName = "Textarea";
361
+ var DEFAULT_CONTACT_CATEGORIES = [
362
+ { value: "general", label: "General enquiry" },
363
+ { value: "sales", label: "Sales" },
364
+ { value: "support", label: "Support" },
365
+ { value: "partnership", label: "Partnership" },
366
+ { value: "press", label: "Press / media" },
367
+ { value: "careers", label: "Careers" },
368
+ { value: "security", label: "Security disclosure" }
369
+ ];
339
370
  var SUBMIT_CONTACT_INQUIRY_MUTATION = (
340
371
  /* GraphQL */
341
372
  `
@@ -357,6 +388,12 @@ async function submitContactForm(client, data, productId) {
357
388
  subject: data.subject,
358
389
  message: data.message,
359
390
  productId,
391
+ // Send `category` only when the user actually picked one. Older
392
+ // backend deployments without the `category` field in the
393
+ // `SubmitContactInquiryInput` schema would reject any unknown
394
+ // field; spreading conditionally keeps the SDK forward-
395
+ // compatible.
396
+ ...data.category ? { category: data.category } : {},
360
397
  ...data.recaptchaToken ? { recaptchaToken: data.recaptchaToken } : {}
361
398
  }
362
399
  });
@@ -395,6 +432,16 @@ function ContactPage({
395
432
  responseTime = "We typically respond within 24 hours",
396
433
  heroTitle = "Get in Touch",
397
434
  heroDescription = "Have questions about {productName}? We're here to help. Reach out to our team and we'll get back to you as soon as possible.",
435
+ // Backward-compatibility: default to `[]` so existing consumers
436
+ // (fluidgrids/burdenoff/algoshred websites) that don't pass
437
+ // `categories` continue to render with NO dropdown — same behavior
438
+ // they had before this SDK version. To opt in, pass an explicit
439
+ // `categories={…}` array (or the exported `DEFAULT_CONTACT_CATEGORIES`
440
+ // constant for the sane Burdenoff-generic vocabulary).
441
+ categories = [],
442
+ categoryLabel = "Category",
443
+ categoryPlaceholder = "Select a category",
444
+ categoryRequired = true,
398
445
  additionalOptions = [
399
446
  {
400
447
  title: "Sales Inquiries",
@@ -438,11 +485,19 @@ function ContactPage({
438
485
  const form = e.currentTarget;
439
486
  try {
440
487
  const formData = new FormData(form);
488
+ const showCategory = categories.length > 0;
489
+ const rawCategory = showCategory ? (formData.get("category") ?? "").trim() : "";
490
+ if (showCategory && categoryRequired && !rawCategory) {
491
+ sonner.toast.error("Please select a category");
492
+ setIsSubmitting(false);
493
+ return;
494
+ }
441
495
  const contactData = {
442
496
  firstName: formData.get("firstName"),
443
497
  lastName: formData.get("lastName"),
444
498
  email: formData.get("email"),
445
499
  company: formData.get("company"),
500
+ category: rawCategory || void 0,
446
501
  subject: formData.get("subject"),
447
502
  message: formData.get("message"),
448
503
  recaptchaToken
@@ -611,6 +666,33 @@ function ContactPage({
611
666
  }
612
667
  )
613
668
  ] }),
669
+ categories.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
670
+ /* @__PURE__ */ jsxRuntime.jsxs(
671
+ Label,
672
+ {
673
+ htmlFor: "category",
674
+ className: "text-sm sm:text-base",
675
+ children: [
676
+ categoryLabel,
677
+ categoryRequired ? " *" : ""
678
+ ]
679
+ }
680
+ ),
681
+ /* @__PURE__ */ jsxRuntime.jsxs(
682
+ Select,
683
+ {
684
+ id: "category",
685
+ name: "category",
686
+ required: categoryRequired,
687
+ defaultValue: "",
688
+ className: "text-sm sm:text-base",
689
+ children: [
690
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: categoryRequired, children: categoryPlaceholder }),
691
+ categories.map((c) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: c.value, children: c.label }, c.value))
692
+ ]
693
+ }
694
+ )
695
+ ] }),
614
696
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
615
697
  /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "subject", className: "text-sm sm:text-base", children: "Subject *" }),
616
698
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -712,5 +794,6 @@ function ContactPage({
712
794
  }
713
795
 
714
796
  exports.ContactPage = ContactPage;
797
+ exports.DEFAULT_CONTACT_CATEGORIES = DEFAULT_CONTACT_CATEGORIES;
715
798
  //# sourceMappingURL=contact.js.map
716
799
  //# sourceMappingURL=contact.js.map