@farming-labs/nuxt-theme 0.0.51 → 0.0.53

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/nuxt-theme",
3
- "version": "0.0.51",
3
+ "version": "0.0.53",
4
4
  "description": "Nuxt/Vue UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle",
5
5
  "keywords": [
6
6
  "docs",
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "sugar-high": "^0.9.5",
63
- "@farming-labs/docs": "0.0.51"
63
+ "@farming-labs/docs": "0.0.53"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "nuxt": ">=3.0.0",
@@ -20,6 +20,7 @@ const props = defineProps<{
20
20
  editOnGithub?: string;
21
21
  lastModified?: string;
22
22
  entry?: string;
23
+ slug?: string;
23
24
  locale?: string;
24
25
  };
25
26
  config?: Record<string, unknown> | null;
@@ -29,6 +30,7 @@ const route = useRoute();
29
30
  const openDropdownMenu = ref(false);
30
31
  const copyLabel = ref("Copy page");
31
32
  const copied = ref(false);
33
+ const selectedFeedback = ref<"positive" | "negative" | null>(null);
32
34
 
33
35
  const titleSuffix = computed(() =>
34
36
  props.config?.metadata?.titleTemplate
@@ -145,6 +147,35 @@ const showPageActions = computed(
145
147
  );
146
148
  const showActionsAbove = computed(() => pageActionsPosition.value === "above-title" && showPageActions.value);
147
149
  const showActionsBelow = computed(() => pageActionsPosition.value === "below-title" && showPageActions.value);
150
+ const feedbackConfig = computed(() => {
151
+ const defaults = {
152
+ enabled: false,
153
+ question: "How is this guide?",
154
+ positiveLabel: "Good",
155
+ negativeLabel: "Bad",
156
+ onFeedback: undefined as ((payload: Record<string, unknown>) => void) | undefined,
157
+ };
158
+
159
+ const feedback = props.config?.feedback as Record<string, unknown> | boolean | null | undefined;
160
+ if (feedback === undefined || feedback === false) return defaults;
161
+ if (feedback === true) return { ...defaults, enabled: true };
162
+ if (typeof feedback !== "object" || feedback === null) return defaults;
163
+
164
+ return {
165
+ enabled: feedback.enabled !== false,
166
+ question: String((feedback as { question?: string }).question ?? defaults.question),
167
+ positiveLabel: String(
168
+ feedback.positiveLabel ?? defaults.positiveLabel,
169
+ ),
170
+ negativeLabel: String(
171
+ feedback.negativeLabel ?? defaults.negativeLabel,
172
+ ),
173
+ onFeedback:
174
+ typeof feedback.onFeedback === "function"
175
+ ? (feedback.onFeedback as (payload: Record<string, unknown>) => void)
176
+ : undefined,
177
+ };
178
+ });
148
179
 
149
180
  useHead({
150
181
  title: () => `${props.data.title}${titleSuffix.value}`,
@@ -201,6 +232,43 @@ function openInProvider(provider: { name: string; urlTemplate: string }) {
201
232
  closeDropdown();
202
233
  }
203
234
 
235
+ function handleFeedback(value: "positive" | "negative") {
236
+ selectedFeedback.value = value;
237
+
238
+ const pathname =
239
+ typeof window !== "undefined"
240
+ ? window.location.pathname.replace(/\/$/, "") || "/"
241
+ : props.data.slug
242
+ ? `/${entry.value}/${props.data.slug}`
243
+ : `/${entry.value}`;
244
+
245
+ const payload = {
246
+ value,
247
+ title: props.data.title,
248
+ description: props.data.description,
249
+ url: typeof window !== "undefined" ? window.location.href : pathname,
250
+ pathname,
251
+ path: pathname,
252
+ entry: entry.value,
253
+ slug: props.data.slug ?? "",
254
+ locale: props.data.locale,
255
+ };
256
+
257
+ try {
258
+ feedbackConfig.value.onFeedback?.(payload);
259
+ } catch {}
260
+
261
+ try {
262
+ if (typeof window !== "undefined" && (window as any).__fdOnFeedback__) {
263
+ (window as any).__fdOnFeedback__(payload);
264
+ }
265
+ } catch {}
266
+
267
+ if (typeof window !== "undefined") {
268
+ window.dispatchEvent(new CustomEvent("fd:feedback", { detail: payload }));
269
+ }
270
+ }
271
+
204
272
  function handleClickOutside(e: MouseEvent) {
205
273
  const target = e.target as Node;
206
274
  if (openDropdownMenu.value && !(target as Element).closest?.(".fd-page-action-dropdown")) {
@@ -354,5 +422,49 @@ onUnmounted(() => {
354
422
  </template>
355
423
 
356
424
  <div v-html="htmlWithoutFirstH1" />
425
+
426
+ <section v-if="feedbackConfig.enabled" class="fd-feedback" aria-label="Page feedback">
427
+ <div class="fd-feedback-content">
428
+ <p class="fd-feedback-question">{{ feedbackConfig.question }}</p>
429
+ <div class="fd-feedback-actions" role="group" :aria-label="feedbackConfig.question">
430
+ <button
431
+ type="button"
432
+ class="fd-page-action-btn"
433
+ :aria-pressed="selectedFeedback === 'positive'"
434
+ :data-selected="selectedFeedback === 'positive' ? 'true' : undefined"
435
+ @click="handleFeedback('positive')"
436
+ >
437
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
438
+ <path
439
+ d="M7 21H5a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h2m0 11V10m0 11h9.28a2 2 0 0 0 1.97-1.66l1.2-7A2 2 0 0 0 17.48 10H13V6.5a2.5 2.5 0 0 0-2.5-2.5L7 10"
440
+ stroke="currentColor"
441
+ stroke-width="2"
442
+ stroke-linecap="round"
443
+ stroke-linejoin="round"
444
+ />
445
+ </svg>
446
+ <span>{{ feedbackConfig.positiveLabel }}</span>
447
+ </button>
448
+ <button
449
+ type="button"
450
+ class="fd-page-action-btn"
451
+ :aria-pressed="selectedFeedback === 'negative'"
452
+ :data-selected="selectedFeedback === 'negative' ? 'true' : undefined"
453
+ @click="handleFeedback('negative')"
454
+ >
455
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
456
+ <path
457
+ d="M17 3h2a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-2M17 3v11m0-11H7.72a2 2 0 0 0-1.97 1.66l-1.2 7A2 2 0 0 0 6.52 14H11v3.5a2.5 2.5 0 0 0 2.5 2.5L17 14"
458
+ stroke="currentColor"
459
+ stroke-width="2"
460
+ stroke-linecap="round"
461
+ stroke-linejoin="round"
462
+ />
463
+ </svg>
464
+ <span>{{ feedbackConfig.negativeLabel }}</span>
465
+ </button>
466
+ </div>
467
+ </div>
468
+ </section>
357
469
  </DocsPage>
358
470
  </template>
package/styles/docs.css CHANGED
@@ -649,10 +649,19 @@ samp {
649
649
  color: var(--color-fd-foreground);
650
650
  }
651
651
 
652
+ .fd-page-action-btn[data-selected="true"] {
653
+ color: var(--color-fd-accent-foreground);
654
+ background: var(--color-fd-accent);
655
+ }
656
+
652
657
  .fd-page-action-btn svg {
653
658
  flex-shrink: 0;
654
659
  }
655
660
 
661
+ .fd-page-action-btn[data-selected="true"] svg {
662
+ color: currentColor;
663
+ }
664
+
656
665
  .fd-page-action-dropdown {
657
666
  position: relative;
658
667
  }
@@ -720,6 +729,35 @@ samp {
720
729
  margin-bottom: 1rem;
721
730
  }
722
731
 
732
+ .fd-feedback {
733
+ margin-top: 2rem;
734
+ padding-top: 1.25rem;
735
+ border-top: 1px solid var(--color-fd-border);
736
+ }
737
+
738
+ .fd-feedback-content {
739
+ display: flex;
740
+ align-items: center;
741
+ justify-content: space-between;
742
+ gap: 1rem;
743
+ flex-wrap: wrap;
744
+ }
745
+
746
+ .fd-feedback-question {
747
+ margin: 0;
748
+ font-size: 0.9375rem;
749
+ font-weight: 600;
750
+ line-height: 1.5;
751
+ color: var(--color-fd-foreground);
752
+ }
753
+
754
+ .fd-feedback-actions {
755
+ display: inline-flex;
756
+ align-items: center;
757
+ gap: 0.5rem;
758
+ flex-wrap: wrap;
759
+ }
760
+
723
761
  /* ─── Breadcrumb ─────────────────────────────────────────────────────── */
724
762
 
725
763
  .fd-breadcrumb {