@queuezero/react 0.1.7 → 0.1.8

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.mts CHANGED
@@ -74,6 +74,12 @@ interface WaitlistFormProps {
74
74
  showLabel?: boolean;
75
75
  /** Label text */
76
76
  labelText?: string;
77
+ /** Display mode: 'inline' renders form directly, 'modal' renders trigger button */
78
+ displayMode?: 'inline' | 'modal';
79
+ /** Text for modal trigger button (only used when displayMode is 'modal') */
80
+ triggerText?: string;
81
+ /** Modal size (only used when displayMode is 'modal') */
82
+ modalSize?: 'sm' | 'md' | 'lg';
77
83
  /** Children for custom rendering */
78
84
  children?: React.ReactNode;
79
85
  }
@@ -193,30 +199,11 @@ declare function useWaitlist(campaign: string, config?: WaitlistProviderProps['c
193
199
  * // Basic usage
194
200
  * <WaitlistForm onSuccess={(res) => console.log('Joined!', res)} />
195
201
  *
196
- * // With referral code from URL
197
- * const referrerCode = new URLSearchParams(window.location.search).get('ref');
198
- * <WaitlistForm referrerCode={referrerCode || undefined} />
199
- *
200
- * // With custom styling
201
- * <WaitlistForm
202
- * className="my-form"
203
- * placeholder="Enter your work email"
204
- * buttonText="Get Early Access"
205
- * />
206
- *
207
- * // With custom rendering
208
- * <WaitlistForm>
209
- * {({ email, setEmail, submit, loading, error }) => (
210
- * <div>
211
- * <input value={email} onChange={(e) => setEmail(e.target.value)} />
212
- * <button onClick={submit} disabled={loading}>Join</button>
213
- * {error && <span>{error.message}</span>}
214
- * </div>
215
- * )}
216
- * </WaitlistForm>
202
+ * // Modal mode
203
+ * <WaitlistForm displayMode="modal" triggerText="Join Our Waitlist" />
217
204
  * ```
218
205
  */
219
- declare function WaitlistForm({ onSuccess, onError, referrerCode, metadata, className, placeholder, buttonText, loadingText, showLabel, labelText, children, }: WaitlistFormProps): any;
206
+ declare function WaitlistForm({ onSuccess, onError, referrerCode, metadata, className, placeholder, buttonText, loadingText, showLabel, labelText, displayMode, triggerText, modalSize, children, }: WaitlistFormProps): any;
220
207
 
221
208
  /**
222
209
  * WaitlistStatus - Shows current position, score, and referral count
package/dist/index.d.ts CHANGED
@@ -74,6 +74,12 @@ interface WaitlistFormProps {
74
74
  showLabel?: boolean;
75
75
  /** Label text */
76
76
  labelText?: string;
77
+ /** Display mode: 'inline' renders form directly, 'modal' renders trigger button */
78
+ displayMode?: 'inline' | 'modal';
79
+ /** Text for modal trigger button (only used when displayMode is 'modal') */
80
+ triggerText?: string;
81
+ /** Modal size (only used when displayMode is 'modal') */
82
+ modalSize?: 'sm' | 'md' | 'lg';
77
83
  /** Children for custom rendering */
78
84
  children?: React.ReactNode;
79
85
  }
@@ -193,30 +199,11 @@ declare function useWaitlist(campaign: string, config?: WaitlistProviderProps['c
193
199
  * // Basic usage
194
200
  * <WaitlistForm onSuccess={(res) => console.log('Joined!', res)} />
195
201
  *
196
- * // With referral code from URL
197
- * const referrerCode = new URLSearchParams(window.location.search).get('ref');
198
- * <WaitlistForm referrerCode={referrerCode || undefined} />
199
- *
200
- * // With custom styling
201
- * <WaitlistForm
202
- * className="my-form"
203
- * placeholder="Enter your work email"
204
- * buttonText="Get Early Access"
205
- * />
206
- *
207
- * // With custom rendering
208
- * <WaitlistForm>
209
- * {({ email, setEmail, submit, loading, error }) => (
210
- * <div>
211
- * <input value={email} onChange={(e) => setEmail(e.target.value)} />
212
- * <button onClick={submit} disabled={loading}>Join</button>
213
- * {error && <span>{error.message}</span>}
214
- * </div>
215
- * )}
216
- * </WaitlistForm>
202
+ * // Modal mode
203
+ * <WaitlistForm displayMode="modal" triggerText="Join Our Waitlist" />
217
204
  * ```
218
205
  */
219
- declare function WaitlistForm({ onSuccess, onError, referrerCode, metadata, className, placeholder, buttonText, loadingText, showLabel, labelText, children, }: WaitlistFormProps): any;
206
+ declare function WaitlistForm({ onSuccess, onError, referrerCode, metadata, className, placeholder, buttonText, loadingText, showLabel, labelText, displayMode, triggerText, modalSize, children, }: WaitlistFormProps): any;
220
207
 
221
208
  /**
222
209
  * WaitlistStatus - Shows current position, score, and referral count
package/dist/index.js CHANGED
@@ -173,12 +173,33 @@ function WaitlistForm({
173
173
  loadingText = "Joining...",
174
174
  showLabel = false,
175
175
  labelText = "Email",
176
+ displayMode = "inline",
177
+ triggerText = "Join Waitlist",
178
+ modalSize = "md",
176
179
  children
177
180
  }) {
178
181
  const { join, loading, error, isJoined, config, configLoading } = useWaitlistContext();
179
182
  const [email, setEmail] = (0, import_react2.useState)("");
180
183
  const [formData, setFormData] = (0, import_react2.useState)({});
181
184
  const [localError, setLocalError] = (0, import_react2.useState)(null);
185
+ const [isModalOpen, setIsModalOpen] = (0, import_react2.useState)(false);
186
+ (0, import_react2.useEffect)(() => {
187
+ const handleEsc = (e) => {
188
+ if (e.key === "Escape" && isModalOpen) setIsModalOpen(false);
189
+ };
190
+ document.addEventListener("keydown", handleEsc);
191
+ return () => document.removeEventListener("keydown", handleEsc);
192
+ }, [isModalOpen]);
193
+ (0, import_react2.useEffect)(() => {
194
+ if (isModalOpen) {
195
+ document.body.style.overflow = "hidden";
196
+ } else {
197
+ document.body.style.overflow = "";
198
+ }
199
+ return () => {
200
+ document.body.style.overflow = "";
201
+ };
202
+ }, [isModalOpen]);
182
203
  const controller = (0, import_react2.useMemo)(
183
204
  () => new import_queuezero2.FormController(join, config?.formFields || [], referrerCode),
184
205
  [join, config, referrerCode]
@@ -205,6 +226,13 @@ function WaitlistForm({
205
226
  [email, formData, controller, metadata, onSuccess, onError, error]
206
227
  );
207
228
  const displayError = localError || error;
229
+ const branding = config?.branding || {};
230
+ const themeColor = branding.themeColor || "#10B981";
231
+ const sizeClasses = {
232
+ sm: "max-w-sm",
233
+ md: "max-w-md",
234
+ lg: "max-w-lg"
235
+ };
208
236
  if (isJoined) {
209
237
  return null;
210
238
  }
@@ -217,7 +245,7 @@ function WaitlistForm({
217
245
  error: displayError
218
246
  });
219
247
  }
220
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, className: `qz-form ${className}`.trim(), children: [
248
+ const formContent = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, className: `qz-form ${displayMode === "inline" ? className : ""}`.trim(), children: [
221
249
  showLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "qz-email", className: "qz-form-label", children: labelText }),
222
250
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "qz-form-row", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
223
251
  "input",
@@ -307,8 +335,49 @@ function WaitlistForm({
307
335
  ] }, field.key);
308
336
  }),
309
337
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "qz-form-row", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "submit", disabled: loading, className: "qz-form-button", children: loading ? loadingText : buttonText }) }),
310
- displayError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "qz-form-error", role: "alert", children: displayError.message }),
311
- children
338
+ displayError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "qz-form-error", role: "alert", children: displayError.message })
339
+ ] });
340
+ if (displayMode === "inline") {
341
+ return formContent;
342
+ }
343
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
344
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
345
+ "button",
346
+ {
347
+ type: "button",
348
+ onClick: () => setIsModalOpen(true),
349
+ className: `qz-modal-trigger ${className}`.trim(),
350
+ style: { "--qz-theme-color": themeColor },
351
+ children: triggerText
352
+ }
353
+ ),
354
+ isModalOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
355
+ "div",
356
+ {
357
+ className: "qz-modal-overlay",
358
+ onClick: (e) => e.target === e.currentTarget && setIsModalOpen(false),
359
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `qz-modal ${sizeClasses[modalSize]}`, style: { "--qz-theme-color": themeColor }, children: [
360
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "qz-modal-header", children: [
361
+ branding.logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: branding.logoUrl, alt: "Logo", className: "qz-modal-logo" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", {}),
362
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
363
+ "button",
364
+ {
365
+ type: "button",
366
+ className: "qz-modal-close",
367
+ onClick: () => setIsModalOpen(false),
368
+ "aria-label": "Close",
369
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 6L6 18M6 6l12 12" }) })
370
+ }
371
+ )
372
+ ] }),
373
+ branding.coverImageUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: branding.coverImageUrl, alt: "", className: "qz-modal-cover" }),
374
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "qz-modal-body", children: [
375
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "qz-modal-title", children: config?.name || "Join Waitlist" }),
376
+ formContent
377
+ ] })
378
+ ] })
379
+ }
380
+ )
312
381
  ] });
313
382
  }
314
383
 
package/dist/index.mjs CHANGED
@@ -125,9 +125,9 @@ function useWaitlist(campaign, config) {
125
125
  }
126
126
 
127
127
  // src/WaitlistForm.tsx
128
- import { useState as useState2, useCallback as useCallback2, useMemo as useMemo2 } from "react";
128
+ import { useState as useState2, useCallback as useCallback2, useMemo as useMemo2, useEffect as useEffect2 } from "react";
129
129
  import { FormController } from "queuezero";
130
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
130
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
131
131
  function WaitlistForm({
132
132
  onSuccess,
133
133
  onError,
@@ -139,12 +139,33 @@ function WaitlistForm({
139
139
  loadingText = "Joining...",
140
140
  showLabel = false,
141
141
  labelText = "Email",
142
+ displayMode = "inline",
143
+ triggerText = "Join Waitlist",
144
+ modalSize = "md",
142
145
  children
143
146
  }) {
144
147
  const { join, loading, error, isJoined, config, configLoading } = useWaitlistContext();
145
148
  const [email, setEmail] = useState2("");
146
149
  const [formData, setFormData] = useState2({});
147
150
  const [localError, setLocalError] = useState2(null);
151
+ const [isModalOpen, setIsModalOpen] = useState2(false);
152
+ useEffect2(() => {
153
+ const handleEsc = (e) => {
154
+ if (e.key === "Escape" && isModalOpen) setIsModalOpen(false);
155
+ };
156
+ document.addEventListener("keydown", handleEsc);
157
+ return () => document.removeEventListener("keydown", handleEsc);
158
+ }, [isModalOpen]);
159
+ useEffect2(() => {
160
+ if (isModalOpen) {
161
+ document.body.style.overflow = "hidden";
162
+ } else {
163
+ document.body.style.overflow = "";
164
+ }
165
+ return () => {
166
+ document.body.style.overflow = "";
167
+ };
168
+ }, [isModalOpen]);
148
169
  const controller = useMemo2(
149
170
  () => new FormController(join, config?.formFields || [], referrerCode),
150
171
  [join, config, referrerCode]
@@ -171,6 +192,13 @@ function WaitlistForm({
171
192
  [email, formData, controller, metadata, onSuccess, onError, error]
172
193
  );
173
194
  const displayError = localError || error;
195
+ const branding = config?.branding || {};
196
+ const themeColor = branding.themeColor || "#10B981";
197
+ const sizeClasses = {
198
+ sm: "max-w-sm",
199
+ md: "max-w-md",
200
+ lg: "max-w-lg"
201
+ };
174
202
  if (isJoined) {
175
203
  return null;
176
204
  }
@@ -183,7 +211,7 @@ function WaitlistForm({
183
211
  error: displayError
184
212
  });
185
213
  }
186
- return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: `qz-form ${className}`.trim(), children: [
214
+ const formContent = /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: `qz-form ${displayMode === "inline" ? className : ""}`.trim(), children: [
187
215
  showLabel && /* @__PURE__ */ jsx2("label", { htmlFor: "qz-email", className: "qz-form-label", children: labelText }),
188
216
  /* @__PURE__ */ jsx2("div", { className: "qz-form-row", children: /* @__PURE__ */ jsx2(
189
217
  "input",
@@ -273,13 +301,54 @@ function WaitlistForm({
273
301
  ] }, field.key);
274
302
  }),
275
303
  /* @__PURE__ */ jsx2("div", { className: "qz-form-row", children: /* @__PURE__ */ jsx2("button", { type: "submit", disabled: loading, className: "qz-form-button", children: loading ? loadingText : buttonText }) }),
276
- displayError && /* @__PURE__ */ jsx2("div", { className: "qz-form-error", role: "alert", children: displayError.message }),
277
- children
304
+ displayError && /* @__PURE__ */ jsx2("div", { className: "qz-form-error", role: "alert", children: displayError.message })
305
+ ] });
306
+ if (displayMode === "inline") {
307
+ return formContent;
308
+ }
309
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
310
+ /* @__PURE__ */ jsx2(
311
+ "button",
312
+ {
313
+ type: "button",
314
+ onClick: () => setIsModalOpen(true),
315
+ className: `qz-modal-trigger ${className}`.trim(),
316
+ style: { "--qz-theme-color": themeColor },
317
+ children: triggerText
318
+ }
319
+ ),
320
+ isModalOpen && /* @__PURE__ */ jsx2(
321
+ "div",
322
+ {
323
+ className: "qz-modal-overlay",
324
+ onClick: (e) => e.target === e.currentTarget && setIsModalOpen(false),
325
+ children: /* @__PURE__ */ jsxs("div", { className: `qz-modal ${sizeClasses[modalSize]}`, style: { "--qz-theme-color": themeColor }, children: [
326
+ /* @__PURE__ */ jsxs("div", { className: "qz-modal-header", children: [
327
+ branding.logoUrl ? /* @__PURE__ */ jsx2("img", { src: branding.logoUrl, alt: "Logo", className: "qz-modal-logo" }) : /* @__PURE__ */ jsx2("span", {}),
328
+ /* @__PURE__ */ jsx2(
329
+ "button",
330
+ {
331
+ type: "button",
332
+ className: "qz-modal-close",
333
+ onClick: () => setIsModalOpen(false),
334
+ "aria-label": "Close",
335
+ children: /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx2("path", { d: "M18 6L6 18M6 6l12 12" }) })
336
+ }
337
+ )
338
+ ] }),
339
+ branding.coverImageUrl && /* @__PURE__ */ jsx2("img", { src: branding.coverImageUrl, alt: "", className: "qz-modal-cover" }),
340
+ /* @__PURE__ */ jsxs("div", { className: "qz-modal-body", children: [
341
+ /* @__PURE__ */ jsx2("h2", { className: "qz-modal-title", children: config?.name || "Join Waitlist" }),
342
+ formContent
343
+ ] })
344
+ ] })
345
+ }
346
+ )
278
347
  ] });
279
348
  }
280
349
 
281
350
  // src/WaitlistStatus.tsx
282
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
351
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
283
352
  function WaitlistStatus({
284
353
  className = "",
285
354
  showReferrals = true,
@@ -297,7 +366,7 @@ function WaitlistStatus({
297
366
  return /* @__PURE__ */ jsx3("div", { className: `qz-status qz-status-loading ${className}`.trim(), children: "Loading..." });
298
367
  }
299
368
  if (typeof children === "function") {
300
- return /* @__PURE__ */ jsx3(Fragment, { children: children(status) });
369
+ return /* @__PURE__ */ jsx3(Fragment2, { children: children(status) });
301
370
  }
302
371
  return /* @__PURE__ */ jsxs2("div", { className: `qz-status ${className}`.trim(), children: [
303
372
  /* @__PURE__ */ jsxs2("div", { className: "qz-status-item qz-status-position", children: [
@@ -320,7 +389,7 @@ function WaitlistStatus({
320
389
 
321
390
  // src/ReferralShare.tsx
322
391
  import { useState as useState3, useCallback as useCallback3 } from "react";
323
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
392
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
324
393
  function ReferralShare({
325
394
  className = "",
326
395
  label = "Share your referral link:",
@@ -381,7 +450,7 @@ ${link}`);
381
450
  return null;
382
451
  }
383
452
  if (typeof children === "function") {
384
- return /* @__PURE__ */ jsx4(Fragment2, { children: children({ link, code, copy: handleCopy, copied }) });
453
+ return /* @__PURE__ */ jsx4(Fragment3, { children: children({ link, code, copy: handleCopy, copied }) });
385
454
  }
386
455
  return /* @__PURE__ */ jsxs3("div", { className: `qz-share ${className}`.trim(), children: [
387
456
  label && /* @__PURE__ */ jsx4("div", { className: "qz-share-label", children: label }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@queuezero/react",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "React components and hooks for QueueZero viral waitlists",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -35,7 +35,7 @@
35
35
  "react": ">=17.0.0"
36
36
  },
37
37
  "dependencies": {
38
- "queuezero": "^0.1.5"
38
+ "queuezero": "^0.1.6"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^22.13.11",