@farming-labs/astro-theme 0.0.53 → 0.0.54

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/astro-theme",
3
- "version": "0.0.53",
3
+ "version": "0.0.54",
4
4
  "description": "Astro UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle",
5
5
  "keywords": [
6
6
  "astro",
@@ -79,8 +79,8 @@
79
79
  },
80
80
  "dependencies": {
81
81
  "sugar-high": "^0.9.5",
82
- "@farming-labs/docs": "0.0.53",
83
- "@farming-labs/astro": "0.0.53"
82
+ "@farming-labs/docs": "0.0.54",
83
+ "@farming-labs/astro": "0.0.54"
84
84
  },
85
85
  "peerDependencies": {
86
86
  "astro": ">=4.0.0"
@@ -101,8 +101,10 @@ const feedbackConfig = (() => {
101
101
  const defaults = {
102
102
  enabled: false,
103
103
  question: "How is this guide?",
104
+ placeholder: "Leave your feedback...",
104
105
  positiveLabel: "Good",
105
106
  negativeLabel: "Bad",
107
+ submitLabel: "Submit",
106
108
  };
107
109
  const feedback = config?.feedback;
108
110
  if (feedback === undefined || feedback === false) return defaults;
@@ -111,8 +113,10 @@ const feedbackConfig = (() => {
111
113
  return {
112
114
  enabled: feedback.enabled !== false,
113
115
  question: feedback.question ?? defaults.question,
116
+ placeholder: feedback.placeholder ?? defaults.placeholder,
114
117
  positiveLabel: feedback.positiveLabel ?? defaults.positiveLabel,
115
118
  negativeLabel: feedback.negativeLabel ?? defaults.negativeLabel,
119
+ submitLabel: feedback.submitLabel ?? defaults.submitLabel,
116
120
  };
117
121
  })();
118
122
  const feedbackEntry = data.entry ?? config?.entry ?? "docs";
@@ -247,7 +251,7 @@ const htmlWithoutFirstH1 = (data.html || "").replace(/<h1[^>]*>[\s\S]*?<\/h1>\s*
247
251
  <div class="fd-feedback-actions" role="group" aria-label={feedbackConfig.question}>
248
252
  <button
249
253
  type="button"
250
- class="fd-page-action-btn"
254
+ class="fd-page-action-btn fd-feedback-choice"
251
255
  aria-pressed="false"
252
256
  data-feedback-button
253
257
  data-feedback-value="positive"
@@ -265,7 +269,7 @@ const htmlWithoutFirstH1 = (data.html || "").replace(/<h1[^>]*>[\s\S]*?<\/h1>\s*
265
269
  </button>
266
270
  <button
267
271
  type="button"
268
- class="fd-page-action-btn"
272
+ class="fd-page-action-btn fd-feedback-choice"
269
273
  aria-pressed="false"
270
274
  data-feedback-button
271
275
  data-feedback-value="negative"
@@ -283,6 +287,62 @@ const htmlWithoutFirstH1 = (data.html || "").replace(/<h1[^>]*>[\s\S]*?<\/h1>\s*
283
287
  </button>
284
288
  </div>
285
289
  </div>
290
+ <div class="fd-feedback-form" hidden data-feedback-form>
291
+ <textarea
292
+ class="fd-feedback-input"
293
+ aria-label="Additional feedback"
294
+ placeholder={feedbackConfig.placeholder}
295
+ data-feedback-input
296
+ ></textarea>
297
+ <div class="fd-feedback-submit-row">
298
+ <button
299
+ type="button"
300
+ class="fd-page-action-btn fd-feedback-submit"
301
+ data-feedback-submit
302
+ disabled
303
+ >
304
+ <span class="fd-feedback-spinner" aria-hidden="true" hidden data-feedback-spinner></span>
305
+ <svg
306
+ width="14"
307
+ height="14"
308
+ viewBox="0 0 24 24"
309
+ fill="none"
310
+ aria-hidden="true"
311
+ hidden
312
+ data-feedback-check
313
+ >
314
+ <path
315
+ d="M20 6 9 17l-5-5"
316
+ stroke="currentColor"
317
+ stroke-width="2"
318
+ stroke-linecap="round"
319
+ stroke-linejoin="round"
320
+ />
321
+ </svg>
322
+ <span data-feedback-submit-label>{feedbackConfig.submitLabel}</span>
323
+ </button>
324
+ <p
325
+ class="fd-feedback-status"
326
+ data-status="success"
327
+ role="status"
328
+ aria-live="polite"
329
+ hidden
330
+ data-feedback-success
331
+ >
332
+ Thanks for the feedback.
333
+ </p>
334
+ </div>
335
+ <p
336
+ class="fd-feedback-status"
337
+ data-status="error"
338
+ role="status"
339
+ aria-live="polite"
340
+ hidden
341
+ data-feedback-error
342
+ >
343
+ Could not send feedback. Please try again.
344
+ </p>
345
+ </div>
286
346
  </section>
287
347
  )}
288
348
  </DocsPage>
@@ -310,6 +370,26 @@ const htmlWithoutFirstH1 = (data.html || "").replace(/<h1[^>]*>[\s\S]*?<\/h1>\s*
310
370
  var feedbackTitleVal = typeof feedbackTitle === "string" ? feedbackTitle : undefined;
311
371
  var feedbackDescriptionVal = typeof feedbackDescription === "string" ? feedbackDescription : undefined;
312
372
 
373
+ function emitFeedback(data) {
374
+ var firstError = null;
375
+
376
+ return Promise.resolve()
377
+ .then(function () {
378
+ if (typeof window !== "undefined" && typeof window.__fdOnFeedback__ === "function") {
379
+ return window.__fdOnFeedback__(data);
380
+ }
381
+ })
382
+ .catch(function (error) {
383
+ firstError = firstError || error;
384
+ })
385
+ .then(function () {
386
+ if (typeof window !== "undefined") {
387
+ window.dispatchEvent(new CustomEvent("fd:feedback", { detail: data }));
388
+ }
389
+ if (firstError) throw firstError;
390
+ });
391
+ }
392
+
313
393
  function init() {
314
394
  document.querySelectorAll("[data-copy-page]").forEach(function (btn) {
315
395
  btn.addEventListener("click", function () {
@@ -380,42 +460,130 @@ const htmlWithoutFirstH1 = (data.html || "").replace(/<h1[^>]*>[\s\S]*?<\/h1>\s*
380
460
  });
381
461
  });
382
462
 
383
- document.querySelectorAll("[data-feedback-button]").forEach(function (btn) {
384
- btn.addEventListener("click", function () {
385
- var value = btn.getAttribute("data-feedback-value");
386
- if (value !== "positive" && value !== "negative") return;
387
-
388
- document.querySelectorAll("[data-feedback-button]").forEach(function (item) {
389
- if (!(item instanceof HTMLElement)) return;
390
- var selected = item.getAttribute("data-feedback-value") === value;
391
- if (selected) item.setAttribute("data-selected", "true");
392
- else item.removeAttribute("data-selected");
393
- item.setAttribute("aria-pressed", String(selected));
394
- });
463
+ document.querySelectorAll(".fd-feedback").forEach(function (section) {
464
+ if (!(section instanceof HTMLElement)) return;
465
+ if (section.dataset.feedbackInitialized === "true") return;
466
+ section.dataset.feedbackInitialized = "true";
395
467
 
396
- var pathnameCurrent = window.location.pathname.replace(/\/$/, "") || "/";
397
- var data = {
398
- value: value,
399
- title: feedbackTitleVal,
400
- description: feedbackDescriptionVal,
401
- url: window.location.href,
402
- pathname: pathnameCurrent,
403
- path: pathnameCurrent,
404
- entry: feedbackEntryVal,
405
- slug: feedbackSlugVal,
406
- locale: feedbackLocaleVal,
407
- };
408
-
409
- try {
410
- if (typeof window !== "undefined" && window.__fdOnFeedback__) {
411
- window.__fdOnFeedback__(data);
468
+ var selectedValue = null;
469
+ var status = "idle";
470
+ var buttons = Array.prototype.slice.call(
471
+ section.querySelectorAll("[data-feedback-button]")
472
+ );
473
+ var form = section.querySelector("[data-feedback-form]");
474
+ var input = section.querySelector("[data-feedback-input]");
475
+ var submitButton = section.querySelector("[data-feedback-submit]");
476
+ var submitLabel = section.querySelector("[data-feedback-submit-label]");
477
+ var spinner = section.querySelector("[data-feedback-spinner]");
478
+ var check = section.querySelector("[data-feedback-check]");
479
+ var success = section.querySelector("[data-feedback-success]");
480
+ var error = section.querySelector("[data-feedback-error]");
481
+
482
+ function setStatus(nextStatus) {
483
+ status = nextStatus;
484
+
485
+ if (spinner) spinner.hidden = status !== "submitting";
486
+ if (check) check.hidden = status !== "submitted";
487
+ if (success) success.hidden = status !== "submitted";
488
+ if (error) error.hidden = status !== "error";
489
+ if (submitLabel) {
490
+ submitLabel.textContent =
491
+ status === "submitted"
492
+ ? "Submitted"
493
+ : submitLabel.getAttribute("data-default-label") || "Submit";
494
+ }
495
+
496
+ if (submitButton instanceof HTMLButtonElement) {
497
+ submitButton.disabled =
498
+ !selectedValue || status === "submitting" || status === "submitted";
499
+ }
500
+
501
+ buttons.forEach(function (button) {
502
+ if (button instanceof HTMLButtonElement) {
503
+ button.disabled = status === "submitting";
412
504
  }
413
- } catch (_) {}
505
+ });
414
506
 
415
- if (typeof window !== "undefined") {
416
- window.dispatchEvent(new CustomEvent("fd:feedback", { detail: data }));
507
+ if (input instanceof HTMLTextAreaElement) {
508
+ input.disabled = status === "submitting";
509
+ }
510
+ }
511
+
512
+ if (submitLabel instanceof HTMLElement) {
513
+ submitLabel.setAttribute("data-default-label", submitLabel.textContent || "Submit");
514
+ }
515
+
516
+ if (form instanceof HTMLElement) {
517
+ form.hidden = true;
518
+ }
519
+ setStatus("idle");
520
+
521
+ function selectValue(value) {
522
+ selectedValue = value;
523
+ buttons.forEach(function (button) {
524
+ if (!(button instanceof HTMLElement)) return;
525
+ var selected = button.getAttribute("data-feedback-value") === value;
526
+ if (selected) button.setAttribute("data-selected", "true");
527
+ else button.removeAttribute("data-selected");
528
+ button.setAttribute("aria-pressed", String(selected));
529
+ });
530
+ if (form instanceof HTMLElement) {
531
+ form.hidden = false;
532
+ }
533
+ if (status !== "idle") {
534
+ setStatus("idle");
535
+ } else if (submitButton instanceof HTMLButtonElement) {
536
+ submitButton.disabled = false;
417
537
  }
538
+ }
539
+
540
+ buttons.forEach(function (btn) {
541
+ btn.addEventListener("click", function () {
542
+ var value = btn.getAttribute("data-feedback-value");
543
+ if (value !== "positive" && value !== "negative") return;
544
+ selectValue(value);
545
+ });
418
546
  });
547
+
548
+ if (input instanceof HTMLTextAreaElement) {
549
+ input.addEventListener("input", function () {
550
+ if (status !== "idle") setStatus("idle");
551
+ });
552
+ }
553
+
554
+ if (submitButton instanceof HTMLButtonElement) {
555
+ submitButton.addEventListener("click", function () {
556
+ if (!selectedValue || status === "submitting" || status === "submitted") return;
557
+
558
+ setStatus("submitting");
559
+
560
+ var pathnameCurrent = window.location.pathname.replace(/\/$/, "") || "/";
561
+ var data = {
562
+ value: selectedValue,
563
+ comment:
564
+ input instanceof HTMLTextAreaElement && input.value.trim().length > 0
565
+ ? input.value.trim()
566
+ : undefined,
567
+ title: feedbackTitleVal,
568
+ description: feedbackDescriptionVal,
569
+ url: window.location.href,
570
+ pathname: pathnameCurrent,
571
+ path: pathnameCurrent,
572
+ entry: feedbackEntryVal,
573
+ slug: feedbackSlugVal,
574
+ locale: feedbackLocaleVal,
575
+ };
576
+
577
+ emitFeedback(data).then(
578
+ function () {
579
+ setStatus("submitted");
580
+ },
581
+ function () {
582
+ setStatus("error");
583
+ }
584
+ );
585
+ });
586
+ }
419
587
  });
420
588
  }
421
589
 
@@ -318,3 +318,18 @@
318
318
  .fd-ai-floating-trigger .ask-ai-trigger:hover {
319
319
  box-shadow: 0 2px 30px rgba(180, 140, 20, 0.4);
320
320
  }
321
+
322
+ /* ─── Feedback (colorful theme) ──────────────────────────────────── */
323
+
324
+ .fd-feedback-input,
325
+ .fd-feedback-submit {
326
+ border-radius: 0.5rem;
327
+ }
328
+
329
+ .fd-feedback-choice[data-selected="true"] {
330
+ background: color-mix(in srgb, var(--color-fd-primary) 12%, var(--color-fd-secondary));
331
+ }
332
+
333
+ .fd-feedback-status[data-status="success"] {
334
+ color: var(--color-fd-primary);
335
+ }
@@ -222,3 +222,22 @@ code:not(pre code) {
222
222
  .fd-ai-fm-input-bar .fd-ai-floating-trigger .ask-ai-trigger:hover {
223
223
  transform: none;
224
224
  }
225
+
226
+ /* ─── Feedback (darksharp theme) ─────────────────────────────────── */
227
+
228
+ .fd-feedback-input,
229
+ .fd-feedback-submit {
230
+ border-radius: 0.2rem !important;
231
+ }
232
+
233
+ .dark .fd-feedback-input {
234
+ background: hsl(0 0% 4%);
235
+ }
236
+
237
+ .dark .fd-feedback-choice[data-selected="true"] {
238
+ background: hsl(0 0% 8%);
239
+ }
240
+
241
+ .dark .fd-feedback-status[data-status="success"] {
242
+ color: hsl(0 0% 90%);
243
+ }
package/styles/docs.css CHANGED
@@ -1622,6 +1622,7 @@ html.dark pre.shiki {
1622
1622
 
1623
1623
  .fd-feedback {
1624
1624
  margin-top: 2rem;
1625
+ margin-bottom: 1.25rem;
1625
1626
  padding-top: 1.25rem;
1626
1627
  border-top: 1px solid var(--color-fd-border);
1627
1628
  }
@@ -1649,7 +1650,118 @@ html.dark pre.shiki {
1649
1650
  flex-wrap: wrap;
1650
1651
  }
1651
1652
 
1653
+ .fd-feedback-form {
1654
+ display: flex;
1655
+ flex-direction: column;
1656
+ gap: 0.75rem;
1657
+ margin-top: 0.875rem;
1658
+ }
1659
+
1660
+ .fd-feedback-form[hidden],
1661
+ .fd-feedback-spinner[hidden],
1662
+ .fd-feedback-status[hidden],
1663
+ .fd-feedback-submit svg[hidden] {
1664
+ display: none !important;
1665
+ }
1666
+
1667
+ .fd-feedback-choice[data-selected="true"] {
1668
+ background: var(--color-fd-accent);
1669
+ color: var(--color-fd-accent-foreground);
1670
+ border-color: color-mix(in srgb, var(--color-fd-primary, currentColor) 65%, transparent);
1671
+ }
1672
+
1673
+ .fd-feedback-input {
1674
+ width: 100%;
1675
+ min-height: 4.75rem;
1676
+ resize: vertical;
1677
+ padding: 0.875rem 1rem;
1678
+ border: 1px solid
1679
+ color-mix(in srgb, var(--color-fd-border) 78%, var(--color-fd-foreground) 22%);
1680
+ border-radius: 0.5rem;
1681
+ background: var(--color-fd-card, transparent);
1682
+ color: var(--color-fd-foreground);
1683
+ font: inherit;
1684
+ line-height: 1.55;
1685
+ outline: none;
1686
+ transition:
1687
+ border-color 150ms ease,
1688
+ box-shadow 150ms ease,
1689
+ background-color 150ms ease;
1690
+ }
1691
+
1692
+ .fd-feedback-input::placeholder {
1693
+ color: var(--color-fd-muted-foreground);
1694
+ }
1695
+
1696
+ .fd-feedback-input:focus {
1697
+ border-color: var(--color-fd-ring, var(--color-fd-primary, currentColor));
1698
+ box-shadow: 0 0 0 1px var(--color-fd-ring, var(--color-fd-primary, currentColor));
1699
+ }
1700
+
1701
+ .fd-feedback-submit-row {
1702
+ display: flex;
1703
+ align-items: center;
1704
+ gap: 0.75rem;
1705
+ flex-wrap: nowrap;
1706
+ min-height: 2rem;
1707
+ }
1708
+
1709
+ .fd-feedback-submit {
1710
+ min-width: 7rem;
1711
+ padding-inline: 0.875rem;
1712
+ justify-content: center;
1713
+ }
1714
+
1715
+ .fd-feedback-submit:disabled {
1716
+ cursor: not-allowed;
1717
+ opacity: 0.65;
1718
+ }
1719
+
1720
+ .fd-feedback-spinner {
1721
+ width: 0.875rem;
1722
+ height: 0.875rem;
1723
+ border-radius: 9999px;
1724
+ border: 2px solid currentColor;
1725
+ border-right-color: transparent;
1726
+ animation: fd-feedback-spin 0.8s linear infinite;
1727
+ }
1728
+
1729
+ .fd-feedback-status {
1730
+ margin: 0 !important;
1731
+ font-size: 0.8125rem;
1732
+ line-height: 1.35;
1733
+ color: var(--color-fd-muted-foreground);
1734
+ }
1735
+
1736
+ .fd-feedback-status[data-status="success"] {
1737
+ display: inline-flex;
1738
+ align-self: center;
1739
+ align-items: center;
1740
+ gap: 0.4rem;
1741
+ min-height: 2rem;
1742
+ color: color-mix(in srgb, var(--color-fd-primary) 85%, var(--color-fd-foreground));
1743
+ }
1744
+
1745
+ .fd-feedback-status[data-status="error"] {
1746
+ color: var(--color-fd-foreground);
1747
+ }
1748
+
1749
+ @keyframes fd-feedback-spin {
1750
+ to {
1751
+ transform: rotate(360deg);
1752
+ }
1753
+ }
1754
+
1652
1755
  @media (max-width: 640px) {
1756
+ .fd-feedback-content {
1757
+ flex-direction: column;
1758
+ align-items: flex-start;
1759
+ }
1760
+
1761
+ .fd-feedback-submit-row {
1762
+ flex-wrap: wrap;
1763
+ }
1764
+
1653
1765
  .fd-last-modified {
1654
1766
  width: 100%;
1655
1767
  }
@@ -831,3 +831,18 @@ details > :not(summary) {
831
831
  .fd-sidebar::-webkit-scrollbar-track {
832
832
  background: transparent;
833
833
  }
834
+
835
+ /* ─── Feedback (greentree theme) ─────────────────────────────────── */
836
+
837
+ .fd-feedback-input,
838
+ .fd-feedback-submit {
839
+ border-radius: 0.75rem;
840
+ }
841
+
842
+ .fd-feedback-choice[data-selected="true"] {
843
+ background: color-mix(in srgb, var(--color-fd-primary) 12%, var(--color-fd-secondary));
844
+ }
845
+
846
+ .fd-feedback-status[data-status="success"] {
847
+ color: var(--color-fd-primary);
848
+ }
@@ -765,3 +765,20 @@ code:not(pre code) {
765
765
  :root:not(.dark) .fd-ai-code-block code {
766
766
  color: #1f2937;
767
767
  }
768
+
769
+ /* ─── Feedback (pixel-border theme) ──────────────────────────────── */
770
+
771
+ .fd-feedback-input,
772
+ .fd-feedback-submit {
773
+ border-radius: 0 !important;
774
+ box-shadow: 3px 3px 0 0 var(--color-fd-border);
775
+ }
776
+
777
+ .fd-feedback-choice[data-selected="true"] {
778
+ background: var(--color-fd-secondary);
779
+ }
780
+
781
+ .fd-feedback-status[data-status="success"] {
782
+ font-family: var(--fd-font-mono, ui-monospace, monospace);
783
+ text-transform: uppercase;
784
+ }