react-linear-feedback 0.3.0 → 0.4.0

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.
@@ -195,6 +195,8 @@ var CSS2 = `
195
195
 
196
196
  .lfb-rect { position: absolute; border: 2px solid var(--lfb-rect); background: rgba(255,0,85,0.12); border-radius: 3px; pointer-events: none; }
197
197
 
198
+ .lfb-shield { position: fixed; inset: 0; pointer-events: auto; cursor: default; }
199
+
198
200
  .lfb-anchor { position: absolute; pointer-events: auto; }
199
201
 
200
202
  .lfb-card {
@@ -209,6 +211,7 @@ var CSS2 = `
209
211
  line-height: 1.4;
210
212
  }
211
213
  .lfb-composer { position: absolute; top: 100%; left: 0; margin-top: 8px; width: 320px; max-width: calc(100vw - 32px); }
214
+ .lfb-composer--above { top: auto; bottom: 100%; margin-top: 0; margin-bottom: 8px; }
212
215
 
213
216
  .lfb-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
214
217
  .lfb-eyebrow { font-size: 12px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--lfb-fg-tertiary); }
@@ -292,6 +295,8 @@ function ensureStyles() {
292
295
  }
293
296
  var MIN_DRAG = 12;
294
297
  var DEFAULT_BOX = { width: 220, height: 130 };
298
+ var COMPOSER_WIDTH = 320;
299
+ var COMPOSER_EST_HEIGHT = 320;
295
300
  var DEFAULT_TYPES = [
296
301
  { id: "bug", label: "Bug", color: "#ef4444", icon: "bug" },
297
302
  { id: "improvement", label: "Improvement", color: "#22c55e", icon: "improvement" }
@@ -320,6 +325,11 @@ function FeedbackWidget({
320
325
  const [drag, setDrag] = react.useState(null);
321
326
  const textareaRef = react.useRef(null);
322
327
  const nameInputRef = react.useRef(null);
328
+ const nameEditRef = react.useRef(null);
329
+ const submittingRef = react.useRef(false);
330
+ submittingRef.current = submitting;
331
+ const editingNameRef = react.useRef(false);
332
+ editingNameRef.current = editingName;
323
333
  react.useEffect(() => {
324
334
  ensureStyles();
325
335
  try {
@@ -329,12 +339,19 @@ function FeedbackWidget({
329
339
  }
330
340
  }, [nameStorageKey]);
331
341
  react.useEffect(() => {
332
- if (mode.kind !== "drawing" && mode.kind !== "naming") return;
342
+ if (mode.kind === "idle") return;
333
343
  const onKey = (e) => {
334
- if (e.key === "Escape") {
335
- setMode({ kind: "idle" });
336
- setDrag(null);
344
+ if (e.key !== "Escape" || submittingRef.current) return;
345
+ if (editingNameRef.current) {
346
+ setEditingName(false);
347
+ return;
337
348
  }
349
+ setMode({ kind: "idle" });
350
+ setDrag(null);
351
+ setText("");
352
+ setError(null);
353
+ setSubmitting(false);
354
+ setEditingName(false);
338
355
  };
339
356
  document.addEventListener("keydown", onKey);
340
357
  return () => document.removeEventListener("keydown", onKey);
@@ -349,6 +366,22 @@ function FeedbackWidget({
349
366
  return () => window.clearTimeout(t);
350
367
  }
351
368
  }, [mode.kind]);
369
+ react.useEffect(() => {
370
+ if (editingName) nameEditRef.current?.focus();
371
+ }, [editingName]);
372
+ react.useEffect(() => {
373
+ if (mode.kind !== "drawing" && mode.kind !== "composing") return;
374
+ const el = document.documentElement;
375
+ const prevOverflow = el.style.overflow;
376
+ const prevPaddingRight = el.style.paddingRight;
377
+ const scrollbar = window.innerWidth - el.clientWidth;
378
+ el.style.overflow = "hidden";
379
+ if (scrollbar > 0) el.style.paddingRight = `${scrollbar}px`;
380
+ return () => {
381
+ el.style.overflow = prevOverflow;
382
+ el.style.paddingRight = prevPaddingRight;
383
+ };
384
+ }, [mode.kind]);
352
385
  const persistName = (value) => {
353
386
  try {
354
387
  window.localStorage.setItem(nameStorageKey, value);
@@ -402,12 +435,17 @@ function FeedbackWidget({
402
435
  vy = end.y - vh / 2;
403
436
  }
404
437
  const rect = { x: Math.max(0, vx + window.scrollX), y: Math.max(0, vy + window.scrollY), width: vw, height: vh };
438
+ const fitsBelow = vy + vh + COMPOSER_EST_HEIGHT <= window.innerHeight;
439
+ const fitsAbove = vy >= COMPOSER_EST_HEIGHT;
440
+ const placement = !fitsBelow && fitsAbove ? "above" : "below";
441
+ const maxAnchorX = window.scrollX + Math.max(16, window.innerWidth - COMPOSER_WIDTH - 16);
442
+ const anchorX = Math.min(rect.x, maxAnchorX);
405
443
  setDrag(null);
406
444
  setText("");
407
445
  setIssueType(types[0]?.id ?? "bug");
408
446
  setError(null);
409
447
  setEditingName(false);
410
- setMode({ kind: "composing", rect });
448
+ setMode({ kind: "composing", rect, placement, anchorX });
411
449
  };
412
450
  const cancelComposer = () => {
413
451
  setMode({ kind: "idle" });
@@ -454,93 +492,102 @@ function FeedbackWidget({
454
492
  } : null;
455
493
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lfb-root", style: rootStyle, children: [
456
494
  /* @__PURE__ */ jsxRuntime.jsx("div", { ...overlayProps, className: "lfb-doc-layer", children: mode.kind === "composing" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
495
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lfb-shield", "aria-hidden": "true" }),
457
496
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lfb-rect", style: { left: mode.rect.x, top: mode.rect.y, width: mode.rect.width, height: mode.rect.height } }),
458
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lfb-anchor", style: { left: mode.rect.x, top: mode.rect.y + mode.rect.height }, children: /* @__PURE__ */ jsxRuntime.jsxs("form", { className: "lfb-card lfb-composer", onSubmit: handleSubmit, children: [
459
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lfb-row", children: [
460
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lfb-eyebrow", children: "New feedback" }),
461
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "lfb-iconbtn", "aria-label": "Cancel", onClick: cancelComposer, children: /* @__PURE__ */ jsxRuntime.jsx(XIcon, {}) })
462
- ] }),
463
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 12 }, children: [
464
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lfb-field-label", children: "Issue type" }),
465
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lfb-types", children: types.map((t) => {
466
- const Icon = TYPE_ICONS[t.icon ?? "dot"];
467
- return /* @__PURE__ */ jsxRuntime.jsxs(
468
- "button",
497
+ /* @__PURE__ */ jsxRuntime.jsx(
498
+ "div",
499
+ {
500
+ className: "lfb-anchor",
501
+ style: { left: mode.anchorX, top: mode.placement === "below" ? mode.rect.y + mode.rect.height : mode.rect.y },
502
+ children: /* @__PURE__ */ jsxRuntime.jsxs("form", { className: `lfb-card lfb-composer${mode.placement === "above" ? " lfb-composer--above" : ""}`, onSubmit: handleSubmit, children: [
503
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lfb-row", children: [
504
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lfb-eyebrow", children: "New feedback" }),
505
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "lfb-iconbtn", "aria-label": "Cancel", onClick: cancelComposer, children: /* @__PURE__ */ jsxRuntime.jsx(XIcon, {}) })
506
+ ] }),
507
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 12 }, children: [
508
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lfb-field-label", children: "Issue type" }),
509
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lfb-types", children: types.map((t) => {
510
+ const Icon = TYPE_ICONS[t.icon ?? "dot"];
511
+ return /* @__PURE__ */ jsxRuntime.jsxs(
512
+ "button",
513
+ {
514
+ type: "button",
515
+ className: "lfb-type",
516
+ "aria-pressed": issueType === t.id,
517
+ onClick: () => setIssueType(t.id),
518
+ children: [
519
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lfb-swatch", style: { background: t.color }, children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 14 }) }),
520
+ t.label
521
+ ]
522
+ },
523
+ t.id
524
+ );
525
+ }) })
526
+ ] }),
527
+ /* @__PURE__ */ jsxRuntime.jsx(
528
+ "textarea",
469
529
  {
470
- type: "button",
471
- className: "lfb-type",
472
- "aria-pressed": issueType === t.id,
473
- onClick: () => setIssueType(t.id),
474
- children: [
475
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lfb-swatch", style: { background: t.color }, children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 14 }) }),
476
- t.label
477
- ]
478
- },
479
- t.id
480
- );
481
- }) })
482
- ] }),
483
- /* @__PURE__ */ jsxRuntime.jsx(
484
- "textarea",
485
- {
486
- ref: textareaRef,
487
- className: "lfb-textarea",
488
- placeholder: "What's on your mind?",
489
- value: text,
490
- onChange: (e) => setText(e.target.value),
491
- maxLength: 5e3,
492
- required: true,
493
- disabled: submitting,
494
- rows: 3,
495
- onKeyDown: (e) => {
496
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
497
- e.preventDefault();
498
- handleSubmit(e);
530
+ ref: textareaRef,
531
+ className: "lfb-textarea",
532
+ placeholder: "What's on your mind?",
533
+ value: text,
534
+ onChange: (e) => setText(e.target.value),
535
+ maxLength: 5e3,
536
+ required: true,
537
+ disabled: submitting,
538
+ rows: 3,
539
+ onKeyDown: (e) => {
540
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
541
+ e.preventDefault();
542
+ handleSubmit(e);
543
+ }
544
+ }
499
545
  }
500
- }
501
- }
502
- ),
503
- editingName ? /* @__PURE__ */ jsxRuntime.jsx(
504
- "input",
505
- {
506
- className: "lfb-input",
507
- type: "text",
508
- value: name,
509
- autoFocus: true,
510
- maxLength: 80,
511
- onChange: (e) => setName(e.target.value),
512
- onBlur: () => {
513
- const t = name.trim();
514
- if (t) persistName(t);
515
- setEditingName(false);
516
- },
517
- onKeyDown: (e) => {
518
- if (e.key === "Enter") {
519
- e.preventDefault();
520
- e.target.blur();
521
- }
522
- if (e.key === "Escape") {
523
- e.preventDefault();
524
- setEditingName(false);
546
+ ),
547
+ editingName ? /* @__PURE__ */ jsxRuntime.jsx(
548
+ "input",
549
+ {
550
+ ref: nameEditRef,
551
+ className: "lfb-input",
552
+ type: "text",
553
+ value: name,
554
+ maxLength: 80,
555
+ onChange: (e) => setName(e.target.value),
556
+ onBlur: () => {
557
+ const t = name.trim();
558
+ if (t) persistName(t);
559
+ setEditingName(false);
560
+ },
561
+ onKeyDown: (e) => {
562
+ if (e.key === "Enter") {
563
+ e.preventDefault();
564
+ e.target.blur();
565
+ }
566
+ if (e.key === "Escape") {
567
+ e.preventDefault();
568
+ e.stopPropagation();
569
+ setEditingName(false);
570
+ }
571
+ }
525
572
  }
526
- }
527
- }
528
- ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lfb-namerow", children: [
529
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
530
- "Posting as ",
531
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lfb-name", children: name || "anonymous" })
532
- ] }),
533
- /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "lfb-link", onClick: () => setEditingName(true), children: [
534
- /* @__PURE__ */ jsxRuntime.jsx(EditIcon, { size: 12 }),
535
- "change"
573
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lfb-namerow", children: [
574
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
575
+ "Posting as ",
576
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lfb-name", children: name || "anonymous" })
577
+ ] }),
578
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "lfb-link", onClick: () => setEditingName(true), children: [
579
+ /* @__PURE__ */ jsxRuntime.jsx(EditIcon, { size: 12 }),
580
+ "change"
581
+ ] })
582
+ ] }),
583
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lfb-error", children: error }),
584
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lfb-actions", children: [
585
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "lfb-btn lfb-btn-ghost", onClick: cancelComposer, disabled: submitting, children: "Cancel" }),
586
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "lfb-btn lfb-btn-primary", disabled: submitting || !text.trim(), children: submitting ? "Sending\u2026" : "Send to Linear" })
587
+ ] })
536
588
  ] })
537
- ] }),
538
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lfb-error", children: error }),
539
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lfb-actions", children: [
540
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "lfb-btn lfb-btn-ghost", onClick: cancelComposer, disabled: submitting, children: "Cancel" }),
541
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "lfb-btn lfb-btn-primary", disabled: submitting || !text.trim(), children: submitting ? "Sending\u2026" : "Send to Linear" })
542
- ] })
543
- ] }) })
589
+ }
590
+ )
544
591
  ] }) }),
545
592
  /* @__PURE__ */ jsxRuntime.jsxs("div", { ...overlayProps, className: "lfb-fixed-layer", children: [
546
593
  mode.kind === "drawing" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lfb-draw", role: "presentation", onMouseDown: onDrawMouseDown, onMouseMove: onDrawMouseMove, onMouseUp: onDrawMouseUp, children: [
@@ -193,6 +193,8 @@ var CSS2 = `
193
193
 
194
194
  .lfb-rect { position: absolute; border: 2px solid var(--lfb-rect); background: rgba(255,0,85,0.12); border-radius: 3px; pointer-events: none; }
195
195
 
196
+ .lfb-shield { position: fixed; inset: 0; pointer-events: auto; cursor: default; }
197
+
196
198
  .lfb-anchor { position: absolute; pointer-events: auto; }
197
199
 
198
200
  .lfb-card {
@@ -207,6 +209,7 @@ var CSS2 = `
207
209
  line-height: 1.4;
208
210
  }
209
211
  .lfb-composer { position: absolute; top: 100%; left: 0; margin-top: 8px; width: 320px; max-width: calc(100vw - 32px); }
212
+ .lfb-composer--above { top: auto; bottom: 100%; margin-top: 0; margin-bottom: 8px; }
210
213
 
211
214
  .lfb-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
212
215
  .lfb-eyebrow { font-size: 12px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--lfb-fg-tertiary); }
@@ -290,6 +293,8 @@ function ensureStyles() {
290
293
  }
291
294
  var MIN_DRAG = 12;
292
295
  var DEFAULT_BOX = { width: 220, height: 130 };
296
+ var COMPOSER_WIDTH = 320;
297
+ var COMPOSER_EST_HEIGHT = 320;
293
298
  var DEFAULT_TYPES = [
294
299
  { id: "bug", label: "Bug", color: "#ef4444", icon: "bug" },
295
300
  { id: "improvement", label: "Improvement", color: "#22c55e", icon: "improvement" }
@@ -318,6 +323,11 @@ function FeedbackWidget({
318
323
  const [drag, setDrag] = useState(null);
319
324
  const textareaRef = useRef(null);
320
325
  const nameInputRef = useRef(null);
326
+ const nameEditRef = useRef(null);
327
+ const submittingRef = useRef(false);
328
+ submittingRef.current = submitting;
329
+ const editingNameRef = useRef(false);
330
+ editingNameRef.current = editingName;
321
331
  useEffect(() => {
322
332
  ensureStyles();
323
333
  try {
@@ -327,12 +337,19 @@ function FeedbackWidget({
327
337
  }
328
338
  }, [nameStorageKey]);
329
339
  useEffect(() => {
330
- if (mode.kind !== "drawing" && mode.kind !== "naming") return;
340
+ if (mode.kind === "idle") return;
331
341
  const onKey = (e) => {
332
- if (e.key === "Escape") {
333
- setMode({ kind: "idle" });
334
- setDrag(null);
342
+ if (e.key !== "Escape" || submittingRef.current) return;
343
+ if (editingNameRef.current) {
344
+ setEditingName(false);
345
+ return;
335
346
  }
347
+ setMode({ kind: "idle" });
348
+ setDrag(null);
349
+ setText("");
350
+ setError(null);
351
+ setSubmitting(false);
352
+ setEditingName(false);
336
353
  };
337
354
  document.addEventListener("keydown", onKey);
338
355
  return () => document.removeEventListener("keydown", onKey);
@@ -347,6 +364,22 @@ function FeedbackWidget({
347
364
  return () => window.clearTimeout(t);
348
365
  }
349
366
  }, [mode.kind]);
367
+ useEffect(() => {
368
+ if (editingName) nameEditRef.current?.focus();
369
+ }, [editingName]);
370
+ useEffect(() => {
371
+ if (mode.kind !== "drawing" && mode.kind !== "composing") return;
372
+ const el = document.documentElement;
373
+ const prevOverflow = el.style.overflow;
374
+ const prevPaddingRight = el.style.paddingRight;
375
+ const scrollbar = window.innerWidth - el.clientWidth;
376
+ el.style.overflow = "hidden";
377
+ if (scrollbar > 0) el.style.paddingRight = `${scrollbar}px`;
378
+ return () => {
379
+ el.style.overflow = prevOverflow;
380
+ el.style.paddingRight = prevPaddingRight;
381
+ };
382
+ }, [mode.kind]);
350
383
  const persistName = (value) => {
351
384
  try {
352
385
  window.localStorage.setItem(nameStorageKey, value);
@@ -400,12 +433,17 @@ function FeedbackWidget({
400
433
  vy = end.y - vh / 2;
401
434
  }
402
435
  const rect = { x: Math.max(0, vx + window.scrollX), y: Math.max(0, vy + window.scrollY), width: vw, height: vh };
436
+ const fitsBelow = vy + vh + COMPOSER_EST_HEIGHT <= window.innerHeight;
437
+ const fitsAbove = vy >= COMPOSER_EST_HEIGHT;
438
+ const placement = !fitsBelow && fitsAbove ? "above" : "below";
439
+ const maxAnchorX = window.scrollX + Math.max(16, window.innerWidth - COMPOSER_WIDTH - 16);
440
+ const anchorX = Math.min(rect.x, maxAnchorX);
403
441
  setDrag(null);
404
442
  setText("");
405
443
  setIssueType(types[0]?.id ?? "bug");
406
444
  setError(null);
407
445
  setEditingName(false);
408
- setMode({ kind: "composing", rect });
446
+ setMode({ kind: "composing", rect, placement, anchorX });
409
447
  };
410
448
  const cancelComposer = () => {
411
449
  setMode({ kind: "idle" });
@@ -452,93 +490,102 @@ function FeedbackWidget({
452
490
  } : null;
453
491
  return /* @__PURE__ */ jsxs("div", { className: "lfb-root", style: rootStyle, children: [
454
492
  /* @__PURE__ */ jsx("div", { ...overlayProps, className: "lfb-doc-layer", children: mode.kind === "composing" && /* @__PURE__ */ jsxs(Fragment, { children: [
493
+ /* @__PURE__ */ jsx("div", { className: "lfb-shield", "aria-hidden": "true" }),
455
494
  /* @__PURE__ */ jsx("div", { className: "lfb-rect", style: { left: mode.rect.x, top: mode.rect.y, width: mode.rect.width, height: mode.rect.height } }),
456
- /* @__PURE__ */ jsx("div", { className: "lfb-anchor", style: { left: mode.rect.x, top: mode.rect.y + mode.rect.height }, children: /* @__PURE__ */ jsxs("form", { className: "lfb-card lfb-composer", onSubmit: handleSubmit, children: [
457
- /* @__PURE__ */ jsxs("div", { className: "lfb-row", children: [
458
- /* @__PURE__ */ jsx("span", { className: "lfb-eyebrow", children: "New feedback" }),
459
- /* @__PURE__ */ jsx("button", { type: "button", className: "lfb-iconbtn", "aria-label": "Cancel", onClick: cancelComposer, children: /* @__PURE__ */ jsx(XIcon, {}) })
460
- ] }),
461
- /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
462
- /* @__PURE__ */ jsx("span", { className: "lfb-field-label", children: "Issue type" }),
463
- /* @__PURE__ */ jsx("div", { className: "lfb-types", children: types.map((t) => {
464
- const Icon = TYPE_ICONS[t.icon ?? "dot"];
465
- return /* @__PURE__ */ jsxs(
466
- "button",
495
+ /* @__PURE__ */ jsx(
496
+ "div",
497
+ {
498
+ className: "lfb-anchor",
499
+ style: { left: mode.anchorX, top: mode.placement === "below" ? mode.rect.y + mode.rect.height : mode.rect.y },
500
+ children: /* @__PURE__ */ jsxs("form", { className: `lfb-card lfb-composer${mode.placement === "above" ? " lfb-composer--above" : ""}`, onSubmit: handleSubmit, children: [
501
+ /* @__PURE__ */ jsxs("div", { className: "lfb-row", children: [
502
+ /* @__PURE__ */ jsx("span", { className: "lfb-eyebrow", children: "New feedback" }),
503
+ /* @__PURE__ */ jsx("button", { type: "button", className: "lfb-iconbtn", "aria-label": "Cancel", onClick: cancelComposer, children: /* @__PURE__ */ jsx(XIcon, {}) })
504
+ ] }),
505
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
506
+ /* @__PURE__ */ jsx("span", { className: "lfb-field-label", children: "Issue type" }),
507
+ /* @__PURE__ */ jsx("div", { className: "lfb-types", children: types.map((t) => {
508
+ const Icon = TYPE_ICONS[t.icon ?? "dot"];
509
+ return /* @__PURE__ */ jsxs(
510
+ "button",
511
+ {
512
+ type: "button",
513
+ className: "lfb-type",
514
+ "aria-pressed": issueType === t.id,
515
+ onClick: () => setIssueType(t.id),
516
+ children: [
517
+ /* @__PURE__ */ jsx("span", { className: "lfb-swatch", style: { background: t.color }, children: /* @__PURE__ */ jsx(Icon, { size: 14 }) }),
518
+ t.label
519
+ ]
520
+ },
521
+ t.id
522
+ );
523
+ }) })
524
+ ] }),
525
+ /* @__PURE__ */ jsx(
526
+ "textarea",
467
527
  {
468
- type: "button",
469
- className: "lfb-type",
470
- "aria-pressed": issueType === t.id,
471
- onClick: () => setIssueType(t.id),
472
- children: [
473
- /* @__PURE__ */ jsx("span", { className: "lfb-swatch", style: { background: t.color }, children: /* @__PURE__ */ jsx(Icon, { size: 14 }) }),
474
- t.label
475
- ]
476
- },
477
- t.id
478
- );
479
- }) })
480
- ] }),
481
- /* @__PURE__ */ jsx(
482
- "textarea",
483
- {
484
- ref: textareaRef,
485
- className: "lfb-textarea",
486
- placeholder: "What's on your mind?",
487
- value: text,
488
- onChange: (e) => setText(e.target.value),
489
- maxLength: 5e3,
490
- required: true,
491
- disabled: submitting,
492
- rows: 3,
493
- onKeyDown: (e) => {
494
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
495
- e.preventDefault();
496
- handleSubmit(e);
528
+ ref: textareaRef,
529
+ className: "lfb-textarea",
530
+ placeholder: "What's on your mind?",
531
+ value: text,
532
+ onChange: (e) => setText(e.target.value),
533
+ maxLength: 5e3,
534
+ required: true,
535
+ disabled: submitting,
536
+ rows: 3,
537
+ onKeyDown: (e) => {
538
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
539
+ e.preventDefault();
540
+ handleSubmit(e);
541
+ }
542
+ }
497
543
  }
498
- }
499
- }
500
- ),
501
- editingName ? /* @__PURE__ */ jsx(
502
- "input",
503
- {
504
- className: "lfb-input",
505
- type: "text",
506
- value: name,
507
- autoFocus: true,
508
- maxLength: 80,
509
- onChange: (e) => setName(e.target.value),
510
- onBlur: () => {
511
- const t = name.trim();
512
- if (t) persistName(t);
513
- setEditingName(false);
514
- },
515
- onKeyDown: (e) => {
516
- if (e.key === "Enter") {
517
- e.preventDefault();
518
- e.target.blur();
519
- }
520
- if (e.key === "Escape") {
521
- e.preventDefault();
522
- setEditingName(false);
544
+ ),
545
+ editingName ? /* @__PURE__ */ jsx(
546
+ "input",
547
+ {
548
+ ref: nameEditRef,
549
+ className: "lfb-input",
550
+ type: "text",
551
+ value: name,
552
+ maxLength: 80,
553
+ onChange: (e) => setName(e.target.value),
554
+ onBlur: () => {
555
+ const t = name.trim();
556
+ if (t) persistName(t);
557
+ setEditingName(false);
558
+ },
559
+ onKeyDown: (e) => {
560
+ if (e.key === "Enter") {
561
+ e.preventDefault();
562
+ e.target.blur();
563
+ }
564
+ if (e.key === "Escape") {
565
+ e.preventDefault();
566
+ e.stopPropagation();
567
+ setEditingName(false);
568
+ }
569
+ }
523
570
  }
524
- }
525
- }
526
- ) : /* @__PURE__ */ jsxs("div", { className: "lfb-namerow", children: [
527
- /* @__PURE__ */ jsxs("span", { children: [
528
- "Posting as ",
529
- /* @__PURE__ */ jsx("span", { className: "lfb-name", children: name || "anonymous" })
530
- ] }),
531
- /* @__PURE__ */ jsxs("button", { type: "button", className: "lfb-link", onClick: () => setEditingName(true), children: [
532
- /* @__PURE__ */ jsx(EditIcon, { size: 12 }),
533
- "change"
571
+ ) : /* @__PURE__ */ jsxs("div", { className: "lfb-namerow", children: [
572
+ /* @__PURE__ */ jsxs("span", { children: [
573
+ "Posting as ",
574
+ /* @__PURE__ */ jsx("span", { className: "lfb-name", children: name || "anonymous" })
575
+ ] }),
576
+ /* @__PURE__ */ jsxs("button", { type: "button", className: "lfb-link", onClick: () => setEditingName(true), children: [
577
+ /* @__PURE__ */ jsx(EditIcon, { size: 12 }),
578
+ "change"
579
+ ] })
580
+ ] }),
581
+ error && /* @__PURE__ */ jsx("p", { className: "lfb-error", children: error }),
582
+ /* @__PURE__ */ jsxs("div", { className: "lfb-actions", children: [
583
+ /* @__PURE__ */ jsx("button", { type: "button", className: "lfb-btn lfb-btn-ghost", onClick: cancelComposer, disabled: submitting, children: "Cancel" }),
584
+ /* @__PURE__ */ jsx("button", { type: "submit", className: "lfb-btn lfb-btn-primary", disabled: submitting || !text.trim(), children: submitting ? "Sending\u2026" : "Send to Linear" })
585
+ ] })
534
586
  ] })
535
- ] }),
536
- error && /* @__PURE__ */ jsx("p", { className: "lfb-error", children: error }),
537
- /* @__PURE__ */ jsxs("div", { className: "lfb-actions", children: [
538
- /* @__PURE__ */ jsx("button", { type: "button", className: "lfb-btn lfb-btn-ghost", onClick: cancelComposer, disabled: submitting, children: "Cancel" }),
539
- /* @__PURE__ */ jsx("button", { type: "submit", className: "lfb-btn lfb-btn-primary", disabled: submitting || !text.trim(), children: submitting ? "Sending\u2026" : "Send to Linear" })
540
- ] })
541
- ] }) })
587
+ }
588
+ )
542
589
  ] }) }),
543
590
  /* @__PURE__ */ jsxs("div", { ...overlayProps, className: "lfb-fixed-layer", children: [
544
591
  mode.kind === "drawing" && /* @__PURE__ */ jsxs("div", { className: "lfb-draw", role: "presentation", onMouseDown: onDrawMouseDown, onMouseMove: onDrawMouseMove, onMouseUp: onDrawMouseUp, children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-linear-feedback",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Drop-in feedback widget for React apps or any site via a script tag: draw a box, write a note, and it captures a screenshot and opens a Linear issue. Self-contained styles, zero design-system dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Oliver Odgaard",