nuxt-ui-elements-pro 0.1.10 → 0.1.11

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.
Files changed (72) hide show
  1. package/dist/module.d.mts +5 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +240 -1
  4. package/dist/runtime/components/EventCalendar.vue +2 -2
  5. package/dist/runtime/components/FeedbackWidget.d.vue.ts +118 -0
  6. package/dist/runtime/components/FeedbackWidget.vue +141 -0
  7. package/dist/runtime/components/FeedbackWidget.vue.d.ts +118 -0
  8. package/dist/runtime/components/GanttChart.d.vue.ts +138 -0
  9. package/dist/runtime/components/GanttChart.vue +206 -0
  10. package/dist/runtime/components/GanttChart.vue.d.ts +138 -0
  11. package/dist/runtime/components/event-calendar/EventCalendarTimeGrid.vue +23 -3
  12. package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.d.vue.ts +22 -0
  13. package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.vue +27 -0
  14. package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.vue.d.ts +22 -0
  15. package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.d.vue.ts +42 -0
  16. package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.vue +288 -0
  17. package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.vue.d.ts +42 -0
  18. package/dist/runtime/components/gantt-chart/GanttChartDependencies.d.vue.ts +16 -0
  19. package/dist/runtime/components/gantt-chart/GanttChartDependencies.vue +70 -0
  20. package/dist/runtime/components/gantt-chart/GanttChartDependencies.vue.d.ts +16 -0
  21. package/dist/runtime/components/gantt-chart/GanttChartHeader.d.vue.ts +41 -0
  22. package/dist/runtime/components/gantt-chart/GanttChartHeader.vue +56 -0
  23. package/dist/runtime/components/gantt-chart/GanttChartHeader.vue.d.ts +41 -0
  24. package/dist/runtime/components/gantt-chart/GanttChartTimeline.d.vue.ts +34 -0
  25. package/dist/runtime/components/gantt-chart/GanttChartTimeline.vue +193 -0
  26. package/dist/runtime/components/gantt-chart/GanttChartTimeline.vue.d.ts +34 -0
  27. package/dist/runtime/composables/useEventCalendar.d.ts +2 -0
  28. package/dist/runtime/composables/useEventCalendar.js +8 -6
  29. package/dist/runtime/composables/useEventCalendarContext.d.ts +2 -7
  30. package/dist/runtime/composables/useEventCalendarContext.js +4 -11
  31. package/dist/runtime/composables/useFeedbackWidget.d.ts +46 -0
  32. package/dist/runtime/composables/useFeedbackWidget.js +137 -0
  33. package/dist/runtime/composables/useFeedbackWidgetContext.d.ts +3 -0
  34. package/dist/runtime/composables/useFeedbackWidgetContext.js +4 -0
  35. package/dist/runtime/composables/useGanttChart.d.ts +52 -0
  36. package/dist/runtime/composables/useGanttChart.js +224 -0
  37. package/dist/runtime/composables/useGanttChartContext.d.ts +3 -0
  38. package/dist/runtime/composables/useGanttChartContext.js +4 -0
  39. package/dist/runtime/composables/useGanttChartDragDrop.d.ts +28 -0
  40. package/dist/runtime/composables/useGanttChartDragDrop.js +68 -0
  41. package/dist/runtime/composables/useGanttChartKeyboard.d.ts +14 -0
  42. package/dist/runtime/composables/useGanttChartKeyboard.js +92 -0
  43. package/dist/runtime/composables/useGanttChartResize.d.ts +26 -0
  44. package/dist/runtime/composables/useGanttChartResize.js +89 -0
  45. package/dist/runtime/composables/useOrgChartContext.d.ts +2 -7
  46. package/dist/runtime/composables/useOrgChartContext.js +4 -11
  47. package/dist/runtime/index.d.ts +1 -0
  48. package/dist/runtime/index.js +1 -0
  49. package/dist/runtime/server/api/_feedback.post.d.ts +3 -0
  50. package/dist/runtime/server/api/_feedback.post.js +115 -0
  51. package/dist/runtime/server/nodemailer.d.ts +29 -0
  52. package/dist/runtime/server/utils/feedback-captcha.d.ts +1 -0
  53. package/dist/runtime/server/utils/feedback-captcha.js +27 -0
  54. package/dist/runtime/server/utils/feedback-rate-limit.d.ts +4 -0
  55. package/dist/runtime/server/utils/feedback-rate-limit.js +26 -0
  56. package/dist/runtime/types/event-calendar.d.ts +10 -4
  57. package/dist/runtime/types/feedback-widget.d.ts +110 -0
  58. package/dist/runtime/types/feedback-widget.js +0 -0
  59. package/dist/runtime/types/gantt-chart.d.ts +223 -0
  60. package/dist/runtime/types/gantt-chart.js +0 -0
  61. package/dist/runtime/types/index.d.ts +4 -0
  62. package/dist/runtime/types/index.js +4 -0
  63. package/dist/runtime/utils/createComponentContext.d.ts +15 -0
  64. package/dist/runtime/utils/createComponentContext.js +17 -0
  65. package/dist/runtime/utils/date.d.ts +10 -0
  66. package/dist/runtime/utils/date.js +9 -0
  67. package/dist/runtime/utils/event-calendar.d.ts +1 -9
  68. package/dist/runtime/utils/event-calendar.js +2 -9
  69. package/dist/runtime/utils/gantt-chart.d.ts +85 -0
  70. package/dist/runtime/utils/gantt-chart.js +549 -0
  71. package/dist/runtime/utils/recurrence.js +2 -1
  72. package/package.json +17 -8
package/dist/module.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
+ import { FeedbackModuleConfig } from '../dist/runtime/types/feedback-widget.js';
2
3
  export * from '../dist/runtime/types/index.js';
3
4
 
4
5
  interface ModuleOptions {
@@ -12,6 +13,10 @@ interface ModuleOptions {
12
13
  * Can also be set via NUXT_UI_ELEMENTS_PRO_KEY env variable
13
14
  */
14
15
  license?: string;
16
+ /**
17
+ * Feedback widget server-side configuration
18
+ */
19
+ feedback?: FeedbackModuleConfig;
15
20
  }
16
21
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
17
22
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-ui-elements-pro",
3
3
  "configKey": "uiElementsPro",
4
- "version": "0.1.10",
4
+ "version": "0.1.11",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { addTemplate, useLogger, defineNuxtModule, createResolver, addImportsDir, addComponentsDir } from '@nuxt/kit';
1
+ import { addTemplate, useLogger, defineNuxtModule, createResolver, addImportsDir, addComponentsDir, addServerHandler } from '@nuxt/kit';
2
2
  import { kebabCase } from 'scule';
3
3
  import { ofetch } from 'ofetch';
4
4
 
@@ -51,6 +51,7 @@ const eventCalendar = (options) => ({
51
51
  "relative z-[2] flex items-center text-xs px-1.5 truncate cursor-pointer",
52
52
  "text-[var(--event-color)]"
53
53
  ],
54
+ timeGridWrapper: "relative flex-1 min-h-0 flex flex-col",
54
55
  timeGrid: "relative flex items-start flex-1 min-h-0 overflow-auto select-none",
55
56
  timeGutter: "sticky left-0 z-10 w-16 shrink-0 bg-default border-r border-default",
56
57
  timeGutterSlot: "relative border-b border-default/50",
@@ -216,9 +217,220 @@ const orgChart = (options) => ({
216
217
  }
217
218
  });
218
219
 
220
+ const feedbackWidget = (options) => ({
221
+ slots: {
222
+ // Root wrapper (positioned fixed)
223
+ root: "fixed z-50",
224
+ // Floating trigger button
225
+ button: [
226
+ "flex items-center justify-center rounded-full shadow-lg cursor-pointer",
227
+ "size-12 transition-all duration-200 hover:scale-105 hover:shadow-xl"
228
+ ],
229
+ buttonIcon: "size-5 text-inverted",
230
+ // Panel container
231
+ panel: [
232
+ "flex flex-col rounded-xl border border-default bg-default shadow-2xl",
233
+ "w-[380px] max-h-[520px] overflow-hidden",
234
+ "transition-all duration-200"
235
+ ],
236
+ panelHidden: "scale-95 opacity-0 pointer-events-none",
237
+ panelVisible: "scale-100 opacity-100",
238
+ // Panel header
239
+ panelHeader: "flex items-center justify-between px-4 py-3 border-b border-default",
240
+ panelTitle: "text-sm font-semibold text-highlighted",
241
+ panelCloseButton: "text-muted hover:text-highlighted cursor-pointer",
242
+ // Type selector
243
+ typeSelector: "grid gap-2 p-4",
244
+ typeOption: [
245
+ "flex items-center gap-3 rounded-lg border border-default p-3 cursor-pointer",
246
+ "hover:bg-elevated/50 transition-colors"
247
+ ],
248
+ typeOptionSelected: "ring-2",
249
+ typeOptionIcon: "size-5 shrink-0",
250
+ typeOptionLabel: "text-sm font-medium text-highlighted",
251
+ typeOptionDescription: "text-xs text-muted",
252
+ // Back button
253
+ backButton: "w-fit",
254
+ // Form
255
+ form: "flex flex-col gap-3 p-4 overflow-y-auto",
256
+ formField: "flex flex-col gap-1",
257
+ formLabel: "text-xs font-medium text-muted",
258
+ formInput: "",
259
+ formTextarea: "",
260
+ // Screenshot
261
+ screenshotButton: "flex items-center gap-1.5 text-xs text-muted hover:text-highlighted cursor-pointer",
262
+ screenshotPreview: "relative rounded-md border border-default overflow-hidden",
263
+ screenshotImage: "w-full h-20 object-cover",
264
+ screenshotRemove: "absolute top-1 right-1 size-5 flex items-center justify-center rounded-full bg-default/80 cursor-pointer",
265
+ // Honeypot (visually hidden)
266
+ honeypot: "absolute -left-[9999px] opacity-0 h-0 w-0 overflow-hidden",
267
+ // CAPTCHA container
268
+ captchaContainer: "flex justify-center py-2",
269
+ // Submit area
270
+ submitArea: "flex items-center justify-between px-4 py-3 border-t border-default",
271
+ submitButton: "",
272
+ // Success state
273
+ successState: "flex flex-col items-center justify-center gap-3 p-8 text-center",
274
+ successIcon: "size-12",
275
+ successTitle: "text-sm font-semibold text-highlighted",
276
+ successMessage: "text-xs text-muted",
277
+ // Error message
278
+ errorMessage: "text-xs text-error px-4 pb-2"
279
+ },
280
+ variants: {
281
+ color: {
282
+ ...Object.fromEntries((options.theme.colors || []).map((color) => [color, ""])),
283
+ neutral: ""
284
+ },
285
+ position: {
286
+ "bottom-right": "",
287
+ "bottom-left": "",
288
+ "top-right": "",
289
+ "top-left": ""
290
+ }
291
+ },
292
+ compoundVariants: [
293
+ ...(options.theme.colors || []).map((color) => ({
294
+ color,
295
+ class: {
296
+ button: `bg-${color} hover:bg-${color}/90`,
297
+ typeOptionSelected: `ring-${color} bg-${color}/5`,
298
+ successIcon: `text-${color}`
299
+ }
300
+ })),
301
+ {
302
+ color: "neutral",
303
+ class: {
304
+ button: "bg-inverted hover:bg-inverted/90",
305
+ typeOptionSelected: "ring-inverted bg-inverted/5",
306
+ successIcon: "text-inverted"
307
+ }
308
+ },
309
+ {
310
+ position: "bottom-right",
311
+ class: {
312
+ root: "bottom-4 right-4",
313
+ panel: "absolute bottom-16 right-0 origin-bottom-right"
314
+ }
315
+ },
316
+ {
317
+ position: "bottom-left",
318
+ class: {
319
+ root: "bottom-4 left-4",
320
+ panel: "absolute bottom-16 left-0 origin-bottom-left"
321
+ }
322
+ },
323
+ {
324
+ position: "top-right",
325
+ class: {
326
+ root: "top-4 right-4",
327
+ panel: "absolute top-16 right-0 origin-top-right"
328
+ }
329
+ },
330
+ {
331
+ position: "top-left",
332
+ class: {
333
+ root: "top-4 left-4",
334
+ panel: "absolute top-16 left-0 origin-top-left"
335
+ }
336
+ }
337
+ ],
338
+ defaultVariants: {
339
+ color: "primary",
340
+ position: "bottom-right"
341
+ }
342
+ });
343
+
344
+ const ganttChart = (options) => ({
345
+ slots: {
346
+ // Root container
347
+ root: "relative w-full border border-default rounded-lg bg-default flex flex-col overflow-hidden",
348
+ // Header bar
349
+ header: "flex items-center justify-between px-4 py-3 border-b border-default",
350
+ headerTitle: "text-base font-semibold text-highlighted",
351
+ headerActions: "flex items-center gap-2",
352
+ headerNav: "flex items-center gap-1",
353
+ zoomSwitcher: "flex items-center gap-0.5 bg-elevated rounded-lg p-0.5",
354
+ // Body (scrollable container)
355
+ body: "flex-1 overflow-auto relative",
356
+ // Timeline
357
+ timeline: "relative min-w-full",
358
+ timelineHeader: "sticky top-0 z-10 bg-default border-b border-default",
359
+ headerGroupRow: "flex border-b border-default",
360
+ headerGroup: "flex items-center justify-center text-xs font-medium text-muted border-r border-default py-1.5 shrink-0",
361
+ headerColumnRow: "flex",
362
+ headerColumn: "flex items-center justify-center text-xs text-muted border-r border-default py-1 shrink-0",
363
+ headerColumnWeekend: "bg-muted/10",
364
+ headerColumnToday: "font-semibold text-highlighted",
365
+ // Grid
366
+ grid: "relative",
367
+ gridLine: "absolute top-0 bottom-0 border-r border-default/20 shrink-0",
368
+ weekendColumn: "bg-muted/5",
369
+ row: "absolute left-0 right-0 border-b border-default/10",
370
+ rowEven: "bg-elevated/20",
371
+ // Today marker
372
+ todayMarker: "absolute top-0 bottom-0 w-0.5 z-[5]",
373
+ // Task bar — per-task color via --task-color CSS variable set by getTaskStyle()
374
+ taskBar: [
375
+ "absolute flex items-center rounded cursor-pointer group select-none z-[2]",
376
+ "text-xs overflow-hidden",
377
+ "hover:shadow-md transition-shadow"
378
+ ],
379
+ taskBarInner: "absolute inset-0 rounded opacity-20 bg-[var(--task-color)]",
380
+ taskBarProgress: "absolute inset-y-0 left-0 rounded-l opacity-30 bg-[var(--task-color)]",
381
+ taskBarTitle: "relative z-[1] px-2 truncate font-medium text-[var(--task-color)]",
382
+ // Group bar
383
+ groupBar: "absolute flex items-center cursor-pointer select-none z-[2]",
384
+ groupBarShape: "absolute bottom-1 left-0 right-0 h-1.5 rounded-full opacity-60 bg-[var(--task-color)]",
385
+ groupBarEndCap: "absolute bottom-1 w-1.5 h-3 rounded-sm opacity-60 bg-[var(--task-color)]",
386
+ groupToggle: "size-4 shrink-0 flex items-center justify-center cursor-pointer text-muted hover:text-highlighted",
387
+ groupTitle: "text-xs font-semibold text-highlighted truncate ml-1",
388
+ // Milestone
389
+ milestone: "absolute flex items-center justify-center z-[2]",
390
+ milestoneDiamond: "size-3.5 rotate-45 rounded-sm bg-[var(--task-color)]",
391
+ // Resize handles
392
+ resizeHandleLeft: "absolute left-0 top-0 bottom-0 w-1.5 cursor-w-resize opacity-0 group-hover:opacity-100 transition-opacity rounded-l z-[2] bg-[var(--task-color)]",
393
+ resizeHandleRight: "absolute right-0 top-0 bottom-0 w-1.5 cursor-e-resize opacity-0 group-hover:opacity-100 transition-opacity rounded-r z-[2] bg-[var(--task-color)]",
394
+ // Dependency layer
395
+ dependencyLayer: "absolute inset-0 pointer-events-none overflow-visible z-[1]",
396
+ dependencyArrow: "fill-none text-muted stroke-current",
397
+ // Loading
398
+ loadingOverlay: "absolute inset-0 z-30 flex items-center justify-center bg-default/60",
399
+ // Empty state
400
+ emptyState: "flex flex-col items-center justify-center gap-2 py-14 text-center",
401
+ emptyStateText: "text-sm text-muted"
402
+ },
403
+ variants: {
404
+ color: {
405
+ ...Object.fromEntries((options.theme.colors || []).map((color) => [color, ""])),
406
+ neutral: ""
407
+ }
408
+ },
409
+ compoundVariants: [
410
+ // todayMarker uses the component-level color variant (not per-task)
411
+ ...(options.theme.colors || []).map((color) => ({
412
+ color,
413
+ class: {
414
+ todayMarker: `bg-${color}`
415
+ }
416
+ })),
417
+ {
418
+ color: "neutral",
419
+ class: {
420
+ todayMarker: "bg-inverted"
421
+ }
422
+ }
423
+ ],
424
+ defaultVariants: {
425
+ color: "primary"
426
+ }
427
+ });
428
+
219
429
  const theme = {
220
430
  __proto__: null,
221
431
  eventCalendar: eventCalendar,
432
+ feedbackWidget: feedbackWidget,
433
+ ganttChart: ganttChart,
222
434
  orgChart: orgChart
223
435
  };
224
436
 
@@ -351,6 +563,33 @@ Add it to your \`.env\` file to build for production.`
351
563
  prefix: options.prefix,
352
564
  pattern: "*.vue"
353
565
  });
566
+ if (options.feedback) {
567
+ const fb = options.feedback;
568
+ nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
569
+ nuxt.options.runtimeConfig.public.uiElementsProFeedback = {
570
+ captchaProvider: fb.captcha?.provider,
571
+ captchaSiteKey: fb.captcha?.siteKey
572
+ };
573
+ nuxt.options.runtimeConfig.uiElementsProFeedback = {
574
+ webhook: fb.webhook,
575
+ emailTo: fb.email?.to,
576
+ emailFrom: fb.email?.from,
577
+ smtpHost: fb.email?.smtp?.host,
578
+ smtpPort: fb.email?.smtp?.port,
579
+ smtpSecure: fb.email?.smtp?.secure,
580
+ smtpUser: fb.email?.smtp?.auth?.user,
581
+ smtpPass: fb.email?.smtp?.auth?.pass,
582
+ captchaSecretKey: fb.captcha?.secretKey,
583
+ captchaProvider: fb.captcha?.provider,
584
+ rateLimitMax: fb.rateLimit?.max,
585
+ rateLimitWindow: fb.rateLimit?.window
586
+ };
587
+ addServerHandler({
588
+ route: "/api/_feedback",
589
+ method: "post",
590
+ handler: resolver.resolve("./runtime/server/api/_feedback.post")
591
+ });
592
+ }
354
593
  }
355
594
  });
356
595
 
@@ -102,11 +102,11 @@ const selection = useEventCalendarSelect({
102
102
  },
103
103
  startHour: () => {
104
104
  const v = internalView.value;
105
- return v === "day" ? props.dayOptions?.startHour ?? 7 : props.weekOptions?.startHour ?? 7;
105
+ return v === "day" ? props.dayOptions?.startHour ?? 0 : props.weekOptions?.startHour ?? 0;
106
106
  },
107
107
  endHour: () => {
108
108
  const v = internalView.value;
109
- return v === "day" ? props.dayOptions?.endHour ?? 22 : props.weekOptions?.endHour ?? 22;
109
+ return v === "day" ? props.dayOptions?.endHour ?? 24 : props.weekOptions?.endHour ?? 24;
110
110
  },
111
111
  onSelect: (payload) => emit("select", payload)
112
112
  });
@@ -0,0 +1,118 @@
1
+ import type { AppConfig } from "@nuxt/schema";
2
+ import type { ComponentConfig } from "nuxt-ui-elements";
3
+ import type { FeedbackType, FeedbackTypeId, FeedbackSubmission, FeedbackSubmitResult, FeedbackWidgetPosition, FeedbackWidgetContext } from "../types/feedback-widget.js";
4
+ import { type Component } from "vue";
5
+ import theme from "#build/ui-elements-pro/feedback-widget";
6
+ type FeedbackWidgetTheme = ComponentConfig<typeof theme, AppConfig, "feedbackWidget">;
7
+ export interface FeedbackWidgetProps {
8
+ /** Rendered element type @defaultValue 'div' */
9
+ as?: string | Component;
10
+ /** Configurable feedback type options @defaultValue bug/feature/general */
11
+ feedbackTypes?: FeedbackType[];
12
+ /** Widget position @defaultValue 'bottom-right' */
13
+ position?: FeedbackWidgetPosition;
14
+ /** Theme color @defaultValue 'primary' */
15
+ color?: FeedbackWidgetTheme["variants"]["color"];
16
+ /** Show screenshot capture button @defaultValue false */
17
+ showScreenshot?: boolean;
18
+ /** Custom button icon @defaultValue 'i-lucide-message-square-plus' */
19
+ buttonIcon?: string;
20
+ /** Button label (screen reader) @defaultValue 'Send feedback' */
21
+ buttonLabel?: string;
22
+ /** Panel title @defaultValue 'Send Feedback' */
23
+ title?: string;
24
+ /** Email field placeholder @defaultValue 'your@email.com' */
25
+ emailPlaceholder?: string;
26
+ /** Message field placeholder @defaultValue 'Tell us what you think...' */
27
+ messagePlaceholder?: string;
28
+ /** Custom submit handler. If provided, bypasses built-in server route. */
29
+ onSubmit?: (submission: FeedbackSubmission) => Promise<FeedbackSubmitResult> | FeedbackSubmitResult;
30
+ /** Built-in endpoint path @defaultValue '/api/_feedback' */
31
+ submitEndpoint?: string;
32
+ /** Slot class overrides */
33
+ ui?: FeedbackWidgetTheme["slots"];
34
+ }
35
+ export interface FeedbackWidgetEmits {
36
+ /** Fires when the panel opens */
37
+ open: [];
38
+ /** Fires when the panel closes */
39
+ close: [];
40
+ /** Fires after successful submission */
41
+ success: [submission: FeedbackSubmission];
42
+ /** Fires on submission error */
43
+ error: [message: string];
44
+ }
45
+ export interface FeedbackWidgetSlots {
46
+ /** Compound mode: full rendering control */
47
+ default?: (props: FeedbackWidgetContext) => any;
48
+ /** Custom trigger button */
49
+ button?: (props: {
50
+ open: () => void;
51
+ close: () => void;
52
+ toggle: () => void;
53
+ isOpen: boolean;
54
+ }) => any;
55
+ /** Custom panel header */
56
+ "panel-header"?: (props: {
57
+ title: string;
58
+ close: () => void;
59
+ }) => any;
60
+ /** Custom type selector */
61
+ "type-selector"?: (props: {
62
+ types: FeedbackType[];
63
+ selected: FeedbackTypeId | null;
64
+ select: (id: FeedbackTypeId) => void;
65
+ }) => any;
66
+ /** Custom form fields */
67
+ form?: (props: {
68
+ email: string;
69
+ message: string;
70
+ screenshot: string | null;
71
+ }) => any;
72
+ /** Custom success state */
73
+ success?: (props: {
74
+ reset: () => void;
75
+ close: () => void;
76
+ }) => any;
77
+ /** Custom submit button area */
78
+ "submit-area"?: (props: {
79
+ submit: () => void;
80
+ isValid: boolean;
81
+ isSubmitting: boolean;
82
+ }) => any;
83
+ }
84
+ declare const _default: typeof __VLS_export;
85
+ export default _default;
86
+ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<FeedbackWidgetProps, {
87
+ open: () => void;
88
+ close: () => void;
89
+ toggle: () => void;
90
+ reset: () => void;
91
+ submit: () => Promise<void>;
92
+ isOpen: import("vue").Ref<boolean, boolean>;
93
+ isSubmitted: import("vue").Ref<boolean, boolean>;
94
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
95
+ success: (submission: FeedbackSubmission) => any;
96
+ error: (message: string) => any;
97
+ close: () => any;
98
+ open: () => any;
99
+ }, string, import("vue").PublicProps, Readonly<FeedbackWidgetProps> & Readonly<{
100
+ onSuccess?: ((submission: FeedbackSubmission) => any) | undefined;
101
+ onError?: ((message: string) => any) | undefined;
102
+ onClose?: (() => any) | undefined;
103
+ onOpen?: (() => any) | undefined;
104
+ }>, {
105
+ title: string;
106
+ position: FeedbackWidgetPosition;
107
+ buttonIcon: string;
108
+ showScreenshot: boolean;
109
+ buttonLabel: string;
110
+ emailPlaceholder: string;
111
+ messagePlaceholder: string;
112
+ submitEndpoint: string;
113
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, FeedbackWidgetSlots>;
114
+ type __VLS_WithSlots<T, S> = T & {
115
+ new (): {
116
+ $slots: S;
117
+ };
118
+ };
@@ -0,0 +1,141 @@
1
+ <script>
2
+ import { Primitive } from "reka-ui";
3
+ import { computed, provide, useSlots } from "vue";
4
+ import { tv } from "../utils/tv";
5
+ import { useFeedbackWidget } from "../composables/useFeedbackWidget";
6
+ import { feedbackWidgetContextKey } from "../composables/useFeedbackWidgetContext";
7
+ import FeedbackWidgetButton from "./feedback-widget/FeedbackWidgetButton.vue";
8
+ import FeedbackWidgetPanel from "./feedback-widget/FeedbackWidgetPanel.vue";
9
+ import { useRuntimeConfig } from "#imports";
10
+ import theme from "#build/ui-elements-pro/feedback-widget";
11
+ const DEFAULT_TYPES = [
12
+ { id: "bug", label: "Bug Report", icon: "i-lucide-bug", description: "Something isn't working" },
13
+ { id: "feature", label: "Feature Request", icon: "i-lucide-lightbulb", description: "Suggest an improvement" },
14
+ { id: "general", label: "General Feedback", icon: "i-lucide-message-circle", description: "Share your thoughts" }
15
+ ];
16
+ </script>
17
+
18
+ <script setup>
19
+ const props = defineProps({
20
+ as: { type: null, required: false },
21
+ feedbackTypes: { type: Array, required: false },
22
+ position: { type: String, required: false, default: "bottom-right" },
23
+ color: { type: null, required: false },
24
+ showScreenshot: { type: Boolean, required: false, default: false },
25
+ buttonIcon: { type: String, required: false, default: "i-lucide-message-square-plus" },
26
+ buttonLabel: { type: String, required: false, default: "Send feedback" },
27
+ title: { type: String, required: false, default: "Send Feedback" },
28
+ emailPlaceholder: { type: String, required: false, default: "your@email.com" },
29
+ messagePlaceholder: { type: String, required: false, default: "Tell us what you think..." },
30
+ onSubmit: { type: Function, required: false },
31
+ submitEndpoint: { type: String, required: false, default: "/api/_feedback" },
32
+ ui: { type: Object, required: false }
33
+ });
34
+ const emit = defineEmits(["open", "close", "success", "error"]);
35
+ defineSlots();
36
+ const slots = useSlots();
37
+ const runtimeConfig = useRuntimeConfig();
38
+ const captchaPublic = computed(() => runtimeConfig.public?.uiElementsProFeedback);
39
+ const ui = computed(
40
+ () => tv({
41
+ extend: tv(theme)
42
+ })({
43
+ color: props.color ?? "primary",
44
+ position: props.position
45
+ })
46
+ );
47
+ const widget = useFeedbackWidget({
48
+ feedbackTypes: () => props.feedbackTypes ?? DEFAULT_TYPES,
49
+ position: () => props.position,
50
+ showScreenshot: () => props.showScreenshot,
51
+ emailPlaceholder: () => props.emailPlaceholder,
52
+ messagePlaceholder: () => props.messagePlaceholder,
53
+ captchaEnabled: () => !!captchaPublic.value?.captchaSiteKey,
54
+ captchaProvider: () => captchaPublic.value?.captchaProvider ?? null,
55
+ captchaSiteKey: () => captchaPublic.value?.captchaSiteKey ?? null,
56
+ submitEndpoint: () => props.submitEndpoint,
57
+ onSubmit: () => props.onSubmit,
58
+ onOpen: () => emit("open"),
59
+ onClose: () => emit("close"),
60
+ onSuccess: (submission) => emit("success", submission),
61
+ onError: (message) => emit("error", message)
62
+ });
63
+ const ctx = {
64
+ isOpen: widget.isOpen,
65
+ isSubmitting: widget.isSubmitting,
66
+ isSubmitted: widget.isSubmitted,
67
+ submitError: widget.submitError,
68
+ selectedType: widget.selectedType,
69
+ email: widget.email,
70
+ message: widget.message,
71
+ screenshot: widget.screenshot,
72
+ honeypot: widget.honeypot,
73
+ feedbackTypes: widget.feedbackTypes,
74
+ position: widget.position,
75
+ showScreenshot: widget.showScreenshot,
76
+ emailPlaceholder: widget.emailPlaceholder,
77
+ messagePlaceholder: widget.messagePlaceholder,
78
+ captchaEnabled: widget.captchaEnabled,
79
+ captchaProvider: widget.captchaProvider,
80
+ captchaSiteKey: widget.captchaSiteKey,
81
+ open: widget.open,
82
+ close: widget.close,
83
+ toggle: widget.toggle,
84
+ reset: widget.reset,
85
+ submit: widget.submit,
86
+ captureScreenshot: widget.captureScreenshot,
87
+ removeScreenshot: widget.removeScreenshot,
88
+ setCaptchaToken: widget.setCaptchaToken,
89
+ isValid: widget.isValid,
90
+ color: computed(() => props.color ?? "primary"),
91
+ ui,
92
+ propUi: computed(() => props.ui)
93
+ };
94
+ provide(feedbackWidgetContextKey, ctx);
95
+ defineExpose({
96
+ open: widget.open,
97
+ close: widget.close,
98
+ toggle: widget.toggle,
99
+ reset: widget.reset,
100
+ submit: widget.submit,
101
+ isOpen: widget.isOpen,
102
+ isSubmitted: widget.isSubmitted
103
+ });
104
+ </script>
105
+
106
+ <template>
107
+ <Primitive :as="props.as ?? 'div'" data-slot="root" :class="ui.root({ class: props.ui?.root })">
108
+ <!-- Compound mode: user provides #default and owns rendering -->
109
+ <slot v-if="slots.default" v-bind="ctx" />
110
+
111
+ <!-- Auto mode: render everything with slot forwarding -->
112
+ <template v-else>
113
+ <FeedbackWidgetButton
114
+ :icon="props.buttonIcon"
115
+ :label="props.buttonLabel"
116
+ >
117
+ <template v-if="slots.button" #default="btnProps">
118
+ <slot name="button" v-bind="btnProps" />
119
+ </template>
120
+ </FeedbackWidgetButton>
121
+
122
+ <FeedbackWidgetPanel :title="props.title">
123
+ <template v-if="slots['panel-header']" #header="headerProps">
124
+ <slot name="panel-header" v-bind="headerProps" />
125
+ </template>
126
+ <template v-if="slots['type-selector']" #type-selector="typeProps">
127
+ <slot name="type-selector" v-bind="typeProps" />
128
+ </template>
129
+ <template v-if="slots.form" #form="formProps">
130
+ <slot name="form" v-bind="formProps" />
131
+ </template>
132
+ <template v-if="slots.success" #success="successProps">
133
+ <slot name="success" v-bind="successProps" />
134
+ </template>
135
+ <template v-if="slots['submit-area']" #submit-area="submitProps">
136
+ <slot name="submit-area" v-bind="submitProps" />
137
+ </template>
138
+ </FeedbackWidgetPanel>
139
+ </template>
140
+ </Primitive>
141
+ </template>
@@ -0,0 +1,118 @@
1
+ import type { AppConfig } from "@nuxt/schema";
2
+ import type { ComponentConfig } from "nuxt-ui-elements";
3
+ import type { FeedbackType, FeedbackTypeId, FeedbackSubmission, FeedbackSubmitResult, FeedbackWidgetPosition, FeedbackWidgetContext } from "../types/feedback-widget.js";
4
+ import { type Component } from "vue";
5
+ import theme from "#build/ui-elements-pro/feedback-widget";
6
+ type FeedbackWidgetTheme = ComponentConfig<typeof theme, AppConfig, "feedbackWidget">;
7
+ export interface FeedbackWidgetProps {
8
+ /** Rendered element type @defaultValue 'div' */
9
+ as?: string | Component;
10
+ /** Configurable feedback type options @defaultValue bug/feature/general */
11
+ feedbackTypes?: FeedbackType[];
12
+ /** Widget position @defaultValue 'bottom-right' */
13
+ position?: FeedbackWidgetPosition;
14
+ /** Theme color @defaultValue 'primary' */
15
+ color?: FeedbackWidgetTheme["variants"]["color"];
16
+ /** Show screenshot capture button @defaultValue false */
17
+ showScreenshot?: boolean;
18
+ /** Custom button icon @defaultValue 'i-lucide-message-square-plus' */
19
+ buttonIcon?: string;
20
+ /** Button label (screen reader) @defaultValue 'Send feedback' */
21
+ buttonLabel?: string;
22
+ /** Panel title @defaultValue 'Send Feedback' */
23
+ title?: string;
24
+ /** Email field placeholder @defaultValue 'your@email.com' */
25
+ emailPlaceholder?: string;
26
+ /** Message field placeholder @defaultValue 'Tell us what you think...' */
27
+ messagePlaceholder?: string;
28
+ /** Custom submit handler. If provided, bypasses built-in server route. */
29
+ onSubmit?: (submission: FeedbackSubmission) => Promise<FeedbackSubmitResult> | FeedbackSubmitResult;
30
+ /** Built-in endpoint path @defaultValue '/api/_feedback' */
31
+ submitEndpoint?: string;
32
+ /** Slot class overrides */
33
+ ui?: FeedbackWidgetTheme["slots"];
34
+ }
35
+ export interface FeedbackWidgetEmits {
36
+ /** Fires when the panel opens */
37
+ open: [];
38
+ /** Fires when the panel closes */
39
+ close: [];
40
+ /** Fires after successful submission */
41
+ success: [submission: FeedbackSubmission];
42
+ /** Fires on submission error */
43
+ error: [message: string];
44
+ }
45
+ export interface FeedbackWidgetSlots {
46
+ /** Compound mode: full rendering control */
47
+ default?: (props: FeedbackWidgetContext) => any;
48
+ /** Custom trigger button */
49
+ button?: (props: {
50
+ open: () => void;
51
+ close: () => void;
52
+ toggle: () => void;
53
+ isOpen: boolean;
54
+ }) => any;
55
+ /** Custom panel header */
56
+ "panel-header"?: (props: {
57
+ title: string;
58
+ close: () => void;
59
+ }) => any;
60
+ /** Custom type selector */
61
+ "type-selector"?: (props: {
62
+ types: FeedbackType[];
63
+ selected: FeedbackTypeId | null;
64
+ select: (id: FeedbackTypeId) => void;
65
+ }) => any;
66
+ /** Custom form fields */
67
+ form?: (props: {
68
+ email: string;
69
+ message: string;
70
+ screenshot: string | null;
71
+ }) => any;
72
+ /** Custom success state */
73
+ success?: (props: {
74
+ reset: () => void;
75
+ close: () => void;
76
+ }) => any;
77
+ /** Custom submit button area */
78
+ "submit-area"?: (props: {
79
+ submit: () => void;
80
+ isValid: boolean;
81
+ isSubmitting: boolean;
82
+ }) => any;
83
+ }
84
+ declare const _default: typeof __VLS_export;
85
+ export default _default;
86
+ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<FeedbackWidgetProps, {
87
+ open: () => void;
88
+ close: () => void;
89
+ toggle: () => void;
90
+ reset: () => void;
91
+ submit: () => Promise<void>;
92
+ isOpen: import("vue").Ref<boolean, boolean>;
93
+ isSubmitted: import("vue").Ref<boolean, boolean>;
94
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
95
+ success: (submission: FeedbackSubmission) => any;
96
+ error: (message: string) => any;
97
+ close: () => any;
98
+ open: () => any;
99
+ }, string, import("vue").PublicProps, Readonly<FeedbackWidgetProps> & Readonly<{
100
+ onSuccess?: ((submission: FeedbackSubmission) => any) | undefined;
101
+ onError?: ((message: string) => any) | undefined;
102
+ onClose?: (() => any) | undefined;
103
+ onOpen?: (() => any) | undefined;
104
+ }>, {
105
+ title: string;
106
+ position: FeedbackWidgetPosition;
107
+ buttonIcon: string;
108
+ showScreenshot: boolean;
109
+ buttonLabel: string;
110
+ emailPlaceholder: string;
111
+ messagePlaceholder: string;
112
+ submitEndpoint: string;
113
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, FeedbackWidgetSlots>;
114
+ type __VLS_WithSlots<T, S> = T & {
115
+ new (): {
116
+ $slots: S;
117
+ };
118
+ };