nuxt-ui-elements-pro 0.1.9 → 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 (77) hide show
  1. package/dist/module.d.mts +5 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +243 -3
  4. package/dist/runtime/components/EventCalendar.d.vue.ts +4 -0
  5. package/dist/runtime/components/EventCalendar.vue +5 -2
  6. package/dist/runtime/components/EventCalendar.vue.d.ts +4 -0
  7. package/dist/runtime/components/FeedbackWidget.d.vue.ts +118 -0
  8. package/dist/runtime/components/FeedbackWidget.vue +141 -0
  9. package/dist/runtime/components/FeedbackWidget.vue.d.ts +118 -0
  10. package/dist/runtime/components/GanttChart.d.vue.ts +138 -0
  11. package/dist/runtime/components/GanttChart.vue +206 -0
  12. package/dist/runtime/components/GanttChart.vue.d.ts +138 -0
  13. package/dist/runtime/components/event-calendar/EventCalendarMonthView.d.vue.ts +3 -0
  14. package/dist/runtime/components/event-calendar/EventCalendarMonthView.vue +15 -6
  15. package/dist/runtime/components/event-calendar/EventCalendarMonthView.vue.d.ts +3 -0
  16. package/dist/runtime/components/event-calendar/EventCalendarTimeGrid.vue +23 -3
  17. package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.d.vue.ts +22 -0
  18. package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.vue +27 -0
  19. package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.vue.d.ts +22 -0
  20. package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.d.vue.ts +42 -0
  21. package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.vue +288 -0
  22. package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.vue.d.ts +42 -0
  23. package/dist/runtime/components/gantt-chart/GanttChartDependencies.d.vue.ts +16 -0
  24. package/dist/runtime/components/gantt-chart/GanttChartDependencies.vue +70 -0
  25. package/dist/runtime/components/gantt-chart/GanttChartDependencies.vue.d.ts +16 -0
  26. package/dist/runtime/components/gantt-chart/GanttChartHeader.d.vue.ts +41 -0
  27. package/dist/runtime/components/gantt-chart/GanttChartHeader.vue +56 -0
  28. package/dist/runtime/components/gantt-chart/GanttChartHeader.vue.d.ts +41 -0
  29. package/dist/runtime/components/gantt-chart/GanttChartTimeline.d.vue.ts +34 -0
  30. package/dist/runtime/components/gantt-chart/GanttChartTimeline.vue +193 -0
  31. package/dist/runtime/components/gantt-chart/GanttChartTimeline.vue.d.ts +34 -0
  32. package/dist/runtime/composables/useEventCalendar.d.ts +2 -0
  33. package/dist/runtime/composables/useEventCalendar.js +8 -6
  34. package/dist/runtime/composables/useEventCalendarContext.d.ts +2 -7
  35. package/dist/runtime/composables/useEventCalendarContext.js +4 -11
  36. package/dist/runtime/composables/useFeedbackWidget.d.ts +46 -0
  37. package/dist/runtime/composables/useFeedbackWidget.js +137 -0
  38. package/dist/runtime/composables/useFeedbackWidgetContext.d.ts +3 -0
  39. package/dist/runtime/composables/useFeedbackWidgetContext.js +4 -0
  40. package/dist/runtime/composables/useGanttChart.d.ts +52 -0
  41. package/dist/runtime/composables/useGanttChart.js +224 -0
  42. package/dist/runtime/composables/useGanttChartContext.d.ts +3 -0
  43. package/dist/runtime/composables/useGanttChartContext.js +4 -0
  44. package/dist/runtime/composables/useGanttChartDragDrop.d.ts +28 -0
  45. package/dist/runtime/composables/useGanttChartDragDrop.js +68 -0
  46. package/dist/runtime/composables/useGanttChartKeyboard.d.ts +14 -0
  47. package/dist/runtime/composables/useGanttChartKeyboard.js +92 -0
  48. package/dist/runtime/composables/useGanttChartResize.d.ts +26 -0
  49. package/dist/runtime/composables/useGanttChartResize.js +89 -0
  50. package/dist/runtime/composables/useOrgChartContext.d.ts +2 -7
  51. package/dist/runtime/composables/useOrgChartContext.js +4 -11
  52. package/dist/runtime/index.d.ts +1 -0
  53. package/dist/runtime/index.js +1 -0
  54. package/dist/runtime/server/api/_feedback.post.d.ts +3 -0
  55. package/dist/runtime/server/api/_feedback.post.js +115 -0
  56. package/dist/runtime/server/nodemailer.d.ts +29 -0
  57. package/dist/runtime/server/utils/feedback-captcha.d.ts +1 -0
  58. package/dist/runtime/server/utils/feedback-captcha.js +27 -0
  59. package/dist/runtime/server/utils/feedback-rate-limit.d.ts +4 -0
  60. package/dist/runtime/server/utils/feedback-rate-limit.js +26 -0
  61. package/dist/runtime/types/event-calendar.d.ts +10 -4
  62. package/dist/runtime/types/feedback-widget.d.ts +110 -0
  63. package/dist/runtime/types/feedback-widget.js +0 -0
  64. package/dist/runtime/types/gantt-chart.d.ts +223 -0
  65. package/dist/runtime/types/gantt-chart.js +0 -0
  66. package/dist/runtime/types/index.d.ts +4 -0
  67. package/dist/runtime/types/index.js +4 -0
  68. package/dist/runtime/utils/createComponentContext.d.ts +15 -0
  69. package/dist/runtime/utils/createComponentContext.js +17 -0
  70. package/dist/runtime/utils/date.d.ts +10 -0
  71. package/dist/runtime/utils/date.js +9 -0
  72. package/dist/runtime/utils/event-calendar.d.ts +1 -9
  73. package/dist/runtime/utils/event-calendar.js +2 -9
  74. package/dist/runtime/utils/gantt-chart.d.ts +85 -0
  75. package/dist/runtime/utils/gantt-chart.js +549 -0
  76. package/dist/runtime/utils/recurrence.js +2 -1
  77. 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.9",
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
 
@@ -15,9 +15,10 @@ const eventCalendar = (options) => ({
15
15
  weekdayCell: "py-2 text-center text-xs font-medium text-muted uppercase tracking-wider",
16
16
  monthBody: "flex flex-col select-none",
17
17
  monthWeekRow: "relative",
18
- dayCell: "border-b border-r border-default hover:bg-elevated/50 cursor-pointer",
18
+ dayCell: "relative border-b border-r border-default hover:bg-elevated/50 cursor-pointer",
19
19
  dayCellOutside: "bg-muted/20",
20
- monthDayNumber: "relative z-[1] p-1",
20
+ monthDayCell: "absolute inset-0 pointer-events-none",
21
+ monthDayNumber: "relative z-[1] p-1 pointer-events-none",
21
22
  dayNumber: "text-xs font-medium p-1 leading-none",
22
23
  dayNumberToday: "inline-flex items-center justify-center size-6 rounded-full text-inverted font-semibold",
23
24
  dayNumberOutside: "text-muted",
@@ -50,6 +51,7 @@ const eventCalendar = (options) => ({
50
51
  "relative z-[2] flex items-center text-xs px-1.5 truncate cursor-pointer",
51
52
  "text-[var(--event-color)]"
52
53
  ],
54
+ timeGridWrapper: "relative flex-1 min-h-0 flex flex-col",
53
55
  timeGrid: "relative flex items-start flex-1 min-h-0 overflow-auto select-none",
54
56
  timeGutter: "sticky left-0 z-10 w-16 shrink-0 bg-default border-r border-default",
55
57
  timeGutterSlot: "relative border-b border-default/50",
@@ -215,9 +217,220 @@ const orgChart = (options) => ({
215
217
  }
216
218
  });
217
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
+
218
429
  const theme = {
219
430
  __proto__: null,
220
431
  eventCalendar: eventCalendar,
432
+ feedbackWidget: feedbackWidget,
433
+ ganttChart: ganttChart,
221
434
  orgChart: orgChart
222
435
  };
223
436
 
@@ -350,6 +563,33 @@ Add it to your \`.env\` file to build for production.`
350
563
  prefix: options.prefix,
351
564
  pattern: "*.vue"
352
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
+ }
353
593
  }
354
594
  });
355
595
 
@@ -108,6 +108,10 @@ export interface EventCalendarSlots {
108
108
  "month-day-header"?: (props: {
109
109
  day: CalendarDay;
110
110
  }) => any;
111
+ /** Full day-cell overlay for custom interactive content (month view). Container has pointer-events: none; use pointer-events-auto on your content. */
112
+ "month-day-cell"?: (props: {
113
+ day: CalendarDay;
114
+ }) => any;
111
115
  /** Customize "+N more" indicator (month view) */
112
116
  "month-more-events"?: (props: {
113
117
  events: CalendarEvent[];
@@ -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
  });
@@ -229,6 +229,9 @@ defineExpose({
229
229
  <template v-if="slots['month-day-header']" #month-day-header="dayProps">
230
230
  <slot name="month-day-header" v-bind="dayProps" />
231
231
  </template>
232
+ <template v-if="slots['month-day-cell']" #month-day-cell="cellProps">
233
+ <slot name="month-day-cell" v-bind="cellProps" />
234
+ </template>
232
235
  <template v-if="slots['month-more-events']" #month-more-events="moreProps">
233
236
  <slot name="month-more-events" v-bind="moreProps" />
234
237
  </template>
@@ -108,6 +108,10 @@ export interface EventCalendarSlots {
108
108
  "month-day-header"?: (props: {
109
109
  day: CalendarDay;
110
110
  }) => any;
111
+ /** Full day-cell overlay for custom interactive content (month view). Container has pointer-events: none; use pointer-events-auto on your content. */
112
+ "month-day-cell"?: (props: {
113
+ day: CalendarDay;
114
+ }) => any;
111
115
  /** Customize "+N more" indicator (month view) */
112
116
  "month-more-events"?: (props: {
113
117
  events: CalendarEvent[];
@@ -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>