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.
- package/dist/module.d.mts +5 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +240 -1
- package/dist/runtime/components/EventCalendar.vue +2 -2
- package/dist/runtime/components/FeedbackWidget.d.vue.ts +118 -0
- package/dist/runtime/components/FeedbackWidget.vue +141 -0
- package/dist/runtime/components/FeedbackWidget.vue.d.ts +118 -0
- package/dist/runtime/components/GanttChart.d.vue.ts +138 -0
- package/dist/runtime/components/GanttChart.vue +206 -0
- package/dist/runtime/components/GanttChart.vue.d.ts +138 -0
- package/dist/runtime/components/event-calendar/EventCalendarTimeGrid.vue +23 -3
- package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.d.vue.ts +22 -0
- package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.vue +27 -0
- package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.vue.d.ts +22 -0
- package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.d.vue.ts +42 -0
- package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.vue +288 -0
- package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.vue.d.ts +42 -0
- package/dist/runtime/components/gantt-chart/GanttChartDependencies.d.vue.ts +16 -0
- package/dist/runtime/components/gantt-chart/GanttChartDependencies.vue +70 -0
- package/dist/runtime/components/gantt-chart/GanttChartDependencies.vue.d.ts +16 -0
- package/dist/runtime/components/gantt-chart/GanttChartHeader.d.vue.ts +41 -0
- package/dist/runtime/components/gantt-chart/GanttChartHeader.vue +56 -0
- package/dist/runtime/components/gantt-chart/GanttChartHeader.vue.d.ts +41 -0
- package/dist/runtime/components/gantt-chart/GanttChartTimeline.d.vue.ts +34 -0
- package/dist/runtime/components/gantt-chart/GanttChartTimeline.vue +193 -0
- package/dist/runtime/components/gantt-chart/GanttChartTimeline.vue.d.ts +34 -0
- package/dist/runtime/composables/useEventCalendar.d.ts +2 -0
- package/dist/runtime/composables/useEventCalendar.js +8 -6
- package/dist/runtime/composables/useEventCalendarContext.d.ts +2 -7
- package/dist/runtime/composables/useEventCalendarContext.js +4 -11
- package/dist/runtime/composables/useFeedbackWidget.d.ts +46 -0
- package/dist/runtime/composables/useFeedbackWidget.js +137 -0
- package/dist/runtime/composables/useFeedbackWidgetContext.d.ts +3 -0
- package/dist/runtime/composables/useFeedbackWidgetContext.js +4 -0
- package/dist/runtime/composables/useGanttChart.d.ts +52 -0
- package/dist/runtime/composables/useGanttChart.js +224 -0
- package/dist/runtime/composables/useGanttChartContext.d.ts +3 -0
- package/dist/runtime/composables/useGanttChartContext.js +4 -0
- package/dist/runtime/composables/useGanttChartDragDrop.d.ts +28 -0
- package/dist/runtime/composables/useGanttChartDragDrop.js +68 -0
- package/dist/runtime/composables/useGanttChartKeyboard.d.ts +14 -0
- package/dist/runtime/composables/useGanttChartKeyboard.js +92 -0
- package/dist/runtime/composables/useGanttChartResize.d.ts +26 -0
- package/dist/runtime/composables/useGanttChartResize.js +89 -0
- package/dist/runtime/composables/useOrgChartContext.d.ts +2 -7
- package/dist/runtime/composables/useOrgChartContext.js +4 -11
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/server/api/_feedback.post.d.ts +3 -0
- package/dist/runtime/server/api/_feedback.post.js +115 -0
- package/dist/runtime/server/nodemailer.d.ts +29 -0
- package/dist/runtime/server/utils/feedback-captcha.d.ts +1 -0
- package/dist/runtime/server/utils/feedback-captcha.js +27 -0
- package/dist/runtime/server/utils/feedback-rate-limit.d.ts +4 -0
- package/dist/runtime/server/utils/feedback-rate-limit.js +26 -0
- package/dist/runtime/types/event-calendar.d.ts +10 -4
- package/dist/runtime/types/feedback-widget.d.ts +110 -0
- package/dist/runtime/types/feedback-widget.js +0 -0
- package/dist/runtime/types/gantt-chart.d.ts +223 -0
- package/dist/runtime/types/gantt-chart.js +0 -0
- package/dist/runtime/types/index.d.ts +4 -0
- package/dist/runtime/types/index.js +4 -0
- package/dist/runtime/utils/createComponentContext.d.ts +15 -0
- package/dist/runtime/utils/createComponentContext.js +17 -0
- package/dist/runtime/utils/date.d.ts +10 -0
- package/dist/runtime/utils/date.js +9 -0
- package/dist/runtime/utils/event-calendar.d.ts +1 -9
- package/dist/runtime/utils/event-calendar.js +2 -9
- package/dist/runtime/utils/gantt-chart.d.ts +85 -0
- package/dist/runtime/utils/gantt-chart.js +549 -0
- package/dist/runtime/utils/recurrence.js +2 -1
- 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
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 ??
|
|
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 ??
|
|
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
|
+
};
|