@skorm11x/dev-feedback-banner 0.1.1 → 0.1.2
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/README.md +20 -13
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -23,22 +23,29 @@ npm i @skorm11x/dev-feedback-banner
|
|
|
23
23
|
```
|
|
24
24
|
import { FeedbackProvider, type FeedbackConfig } from '@skorm11x/dev-feedback-banner';
|
|
25
25
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
26
|
+
const emailConfig: FeedbackConfig = {
|
|
27
|
+
position: 'top-left',
|
|
28
|
+
primaryColor: '#10b981',
|
|
29
|
+
submissionEndpoint: 'mailto:feedback@yourcompany.com',
|
|
30
|
+
fields: [
|
|
31
|
+
{ id: 'type', label: 'Issue Type', type: 'select', options: ['Bug', 'UI', 'Feature'], required: true },
|
|
32
|
+
{ id: 'msg', label: 'Message', type: 'textarea', required: true }
|
|
33
|
+
],
|
|
34
|
+
onSubmit: async (payload: FeedbackPayload) => {
|
|
35
|
+
const subject = encodeURIComponent(`[Feedback] ${payload.formData.type}`);
|
|
36
|
+
const body = encodeURIComponent([
|
|
37
|
+
`Type: ${payload.formData.type}`,
|
|
38
|
+
`Message: ${payload.formData.msg}`,
|
|
39
|
+
].join('\n'));
|
|
40
|
+
|
|
41
|
+
window.location.href = `mailto:feedback@yourcompany.com?subject=${subject}&body=${body}`;
|
|
42
|
+
return new Promise(res => setTimeout(res, 500));
|
|
43
|
+
}
|
|
44
|
+
};
|
|
38
45
|
|
|
39
46
|
function App() {
|
|
40
47
|
return (
|
|
41
|
-
<FeedbackProvider config={
|
|
48
|
+
<FeedbackProvider config={emailConfig}>
|
|
42
49
|
<div>Your app content</div>
|
|
43
50
|
</FeedbackProvider>
|
|
44
51
|
);
|
package/dist/index.d.mts
CHANGED
|
@@ -21,7 +21,8 @@ interface CanvasProps {
|
|
|
21
21
|
interface FeedbackConfig {
|
|
22
22
|
primaryColor?: string;
|
|
23
23
|
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
|
|
24
|
-
|
|
24
|
+
ribbonLabel?: string;
|
|
25
|
+
modalTitle?: string;
|
|
25
26
|
enableAnnotation?: boolean;
|
|
26
27
|
enableKeyboard?: boolean;
|
|
27
28
|
keyboardShortcut?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,8 @@ interface CanvasProps {
|
|
|
21
21
|
interface FeedbackConfig {
|
|
22
22
|
primaryColor?: string;
|
|
23
23
|
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
|
|
24
|
-
|
|
24
|
+
ribbonLabel?: string;
|
|
25
|
+
modalTitle?: string;
|
|
25
26
|
enableAnnotation?: boolean;
|
|
26
27
|
enableKeyboard?: boolean;
|
|
27
28
|
keyboardShortcut?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var W=Object.create;var v=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var z=Object.getPrototypeOf,V=Object.prototype.hasOwnProperty;var Y=(e,t)=>{for(var a in t)v(e,a,{get:t[a],enumerable:!0})},T=(e,t,a,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of H(t))!V.call(e,s)&&s!==a&&v(e,s,{get:()=>t[s],enumerable:!(n=q(t,s))||n.enumerable});return e};var B=(e,t,a)=>(a=e!=null?W(z(e)):{},T(t||!e||!e.__esModule?v(a,"default",{value:e,enumerable:!0}):a,e)),X=e=>T(v({},"__esModule",{value:!0}),e);var Z={};Y(Z,{FeedbackProvider:()=>U,useFeedback:()=>f});module.exports=X(Z);var
|
|
1
|
+
"use strict";var W=Object.create;var v=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var z=Object.getPrototypeOf,V=Object.prototype.hasOwnProperty;var Y=(e,t)=>{for(var a in t)v(e,a,{get:t[a],enumerable:!0})},T=(e,t,a,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of H(t))!V.call(e,s)&&s!==a&&v(e,s,{get:()=>t[s],enumerable:!(n=q(t,s))||n.enumerable});return e};var B=(e,t,a)=>(a=e!=null?W(z(e)):{},T(t||!e||!e.__esModule?v(a,"default",{value:e,enumerable:!0}):a,e)),X=e=>T(v({},"__esModule",{value:!0}),e);var Z={};Y(Z,{FeedbackProvider:()=>U,useFeedback:()=>f});module.exports=X(Z);var b=require("react");var A=require("react");var M=require("react");var f=()=>{let e=(0,M.useContext)(w);if(!e)throw new Error("useFeedback must be used within a FeedbackProvider. Check if you wrapped your root component.");return e};var P=require("react/jsx-runtime"),N=()=>{let{config:e,triggerFeedback:t}=f(),a=()=>{let n={position:"fixed",padding:"8px 40px",cursor:"pointer",color:"white",fontWeight:"bold",fontSize:"14px",zIndex:999999,boxShadow:"0 4px 12px rgba(0,0,0,0.15)",userSelect:"none",transition:"filter 0.2s",backgroundColor:e.primaryColor||"#dc2626"};switch(e.position){case"top-left":return{...n,top:"25px",left:"-35px",transform:"rotate(-45deg)"};case"bottom-right":return{...n,bottom:"25px",right:"-35px",transform:"rotate(-45deg)"};case"bottom-left":return{...n,bottom:"25px",left:"-35px",transform:"rotate(45deg)"};default:return{...n,top:"25px",right:"-35px",transform:"rotate(45deg)"}}};return(0,A.useEffect)(()=>{let n=s=>{let i=(e.keyboardShortcut||"ctrl+shift+f").toLowerCase().split("+"),p=i.includes("ctrl"),y=i.includes("shift"),o=i.includes("alt"),r=i[i.length-1];s.ctrlKey===p&&s.shiftKey===y&&s.altKey===o&&s.key.toLowerCase()===r&&(s.preventDefault(),t())};return e.enableKeyboard!==!1&&window.addEventListener("keydown",n),()=>window.removeEventListener("keydown",n)},[e,t]),(0,P.jsx)("div",{"data-html2canvas-ignore":"true",onClick:t,style:a(),onMouseEnter:n=>n.currentTarget.style.filter="brightness(1.1)",onMouseLeave:n=>n.currentTarget.style.filter="brightness(1.0)",children:e.ribbonLabel||"FEEDBACK"})};var m=require("@mui/material"),E=B(require("@mui/icons-material/Close"));var g=require("react/jsx-runtime"),J={position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",width:"90%",maxWidth:900,maxHeight:"90vh",bgcolor:"background.paper",borderRadius:4,boxShadow:24,display:"flex",flexDirection:"column",outline:"none",overflow:"hidden"},O=({title:e,children:t})=>{let{isOpen:a,setIsOpen:n,setStage:s}=f(),l=()=>{n(!1),s("IDLE")};return(0,g.jsx)(m.Modal,{open:a,onClose:l,"aria-labelledby":"feedback-modal-title",closeAfterTransition:!0,slotProps:{backdrop:{sx:{backdropFilter:"blur(4px)",backgroundColor:"rgba(0,0,0,0.6)"}}},children:(0,g.jsxs)(m.Box,{sx:J,children:[(0,g.jsxs)(m.Box,{sx:{p:2,px:3,display:"flex",alignItems:"center",justifyContent:"space-between"},children:[(0,g.jsx)(m.Typography,{id:"feedback-modal-title",variant:"h6",component:"h2",fontWeight:"bold",children:e}),(0,g.jsx)(m.IconButton,{onClick:l,size:"small","aria-label":"close",children:(0,g.jsx)(E.default,{})})]}),(0,g.jsx)(m.Divider,{}),(0,g.jsx)(m.Box,{sx:{p:3,overflowY:"auto",flex:1},children:t})]})})};var F=require("react"),C=require("@mui/material");var k=require("react/jsx-runtime"),D=()=>{let e=(0,F.useRef)(null),[t,a]=(0,F.useState)(!1),{screenshot:n,setScreenshot:s,setStage:l,config:i}=f();(0,F.useEffect)(()=>{let o=e.current;if(!o||!n)return;let r=o.getContext("2d");if(!r)return;let c=new Image;c.onload=()=>{o.width=c.width,o.height=c.height,r.drawImage(c,0,0),r.strokeStyle=i.primaryColor||"#ff0000",r.lineWidth=5,r.lineCap="round"},c.src=n},[n,i.primaryColor]);let p=o=>{let r=e.current;if(!r)return{x:0,y:0};let c=r.getBoundingClientRect();return{x:(o.clientX-c.left)*(r.width/c.width),y:(o.clientY-c.top)*(r.height/c.height)}};return(0,k.jsxs)(C.Box,{sx:{width:"100%",position:"relative"},children:[(0,k.jsx)(C.Box,{sx:{width:"100%",bgcolor:"grey.100",borderRadius:2,overflow:"hidden",border:"1px solid",borderColor:"divider"},children:(0,k.jsx)("canvas",{ref:e,onMouseDown:o=>{let r=e.current?.getContext("2d"),{x:c,y:h}=p(o);r?.beginPath(),r?.moveTo(c,h),a(!0)},onMouseMove:o=>{if(!t)return;let r=e.current?.getContext("2d"),{x:c,y:h}=p(o);r?.lineTo(c,h),r?.stroke()},onMouseUp:()=>a(!1),style:{display:"block",maxWidth:"100%",cursor:"crosshair"}})}),(0,k.jsx)(C.Box,{sx:{mt:2,display:"flex",justifyContent:"flex-end"},children:(0,k.jsx)(C.Button,{variant:"contained",onClick:()=>{let o=e.current?.toDataURL("image/png");o&&s(o),l("FORM")},sx:{bgcolor:i.primaryColor},children:"Confirm Annotations"})})]})};var I=require("react"),d=require("@mui/material");var x=require("react/jsx-runtime"),L=()=>{let{config:e,setStage:t,setIsOpen:a,screenshot:n}=f(),[s,l]=(0,I.useState)(!1),[i,p]=(0,I.useState)({});return(0,x.jsx)(d.Box,{component:"form",onSubmit:async o=>{o.preventDefault(),l(!0);try{let r={formData:i,screenshot:n};await e.onSubmit(r),a(!1)}catch(r){console.error("Submission failed",r)}finally{l(!1)}},children:(0,x.jsxs)(d.Stack,{spacing:3,children:[e.fields.map(o=>(0,x.jsx)(d.TextField,{label:o.label,required:o.required,select:o.type==="select",multiline:o.type==="textarea",rows:o.type==="textarea"?4:1,fullWidth:!0,variant:"outlined",onChange:r=>p({...i,[o.id]:r.target.value}),children:o.type==="select"&&o.options?.map(r=>(0,x.jsx)(d.MenuItem,{value:r,children:r},r))},o.id)),(0,x.jsxs)(d.Box,{sx:{display:"flex",justifyContent:"flex-end",gap:2,mt:2},children:[(0,x.jsx)(d.Button,{variant:"text",onClick:()=>{e.enableAnnotation?t("ANNOTATING"):(t("IDLE"),a(!1))},disabled:s,children:"Back"}),(0,x.jsx)(d.Button,{type:"submit",variant:"contained",disabled:s,sx:{bgcolor:e.primaryColor,"&:hover":{opacity:.9}},startIcon:s&&(0,x.jsx)(d.CircularProgress,{size:20,color:"inherit"}),children:s?"Sending...":"Submit Feedback"})]})]})})};var S=require("@mui/material");var R=require("react"),K=B(require("html2canvas")),G=()=>{let[e,t]=(0,R.useState)(null),[a,n]=(0,R.useState)(!1);return{capture:async(l=document.body)=>{n(!0);try{let p=(await(0,K.default)(l,{useCORS:!0,logging:!1,allowTaint:!0,backgroundColor:null})).toDataURL("image/png");return t(p),p}catch(i){console.error("Failed to capture screenshot:",i)}finally{n(!1)}},image:e,isCapturing:a,setImage:t}};var u=require("react/jsx-runtime"),w=(0,b.createContext)(null),Q=()=>{let{stage:e,config:t}=f();return t.modalTitle?t.modalTitle:(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(N,{}),(0,u.jsxs)(O,{title:(()=>{switch(e){case"CAPTURING":return"Taking Screenshot...";case"ANNOTATING":return t.enableAnnotation?"Highlight Issues":"Send Feedback";case"FORM":return"Send Feedback";default:return"Feedback"}})(),children:[e==="CAPTURING"&&(0,u.jsx)(S.Box,{sx:{display:"flex",justifyContent:"center",p:4},children:(0,u.jsx)(S.CircularProgress,{})}),e==="ANNOTATING"&&t.enableAnnotation&&(0,u.jsx)(D,{}),(e==="FORM"||e==="ANNOTATING"&&!t.enableAnnotation)&&(0,u.jsx)(L,{})]})]})},U=({children:e,config:t})=>{let[a,n]=(0,b.useState)(!1),[s,l]=(0,b.useState)("IDLE"),[i,p]=(0,b.useState)(null),{capture:y}=G(),o=(0,b.useMemo)(()=>({position:"top-right",primaryColor:"#dc2626",ribbonLabel:"Feedback",modalTitle:void 0,enableKeyboard:!0,enableAnnotation:!1,keyboardShortcut:"ctrl+shift+f",...t}),[t]),r=(0,b.useCallback)(async()=>{if(n(!0),o.enableAnnotation){l("CAPTURING");try{let h=await y();if(h){p(h),l("ANNOTATING");return}}catch(h){console.warn("Screenshot failed, skipping to form:",h)}}l("FORM")},[y,o.enableAnnotation]),c=(0,b.useMemo)(()=>({isOpen:a,setIsOpen:n,stage:s,setStage:l,screenshot:i,setScreenshot:p,config:o,triggerFeedback:r}),[a,s,i,o,r]);return(0,u.jsxs)(w.Provider,{value:c,children:[e,(0,u.jsx)(Q,{})]})};0&&(module.exports={FeedbackProvider,useFeedback});
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/context/FeedbackProvider.tsx","../src/components/FeedbackRibbon.tsx","../src/hooks/useFeedback.tsx","../src/components/FeedbackModal.tsx","../src/components/FeedbackCanvas.tsx","../src/components/FeedbackForm.tsx","../src/hooks/useScreenshot.tsx"],"sourcesContent":["export { FeedbackProvider } from './context/FeedbackProvider';\nexport { useFeedback } from './hooks/useFeedback';\nexport * from './types';","import { createContext, useState, ReactNode, useMemo, useCallback } from 'react';\nimport { FeedbackRibbon } from '../components/FeedbackRibbon';\nimport { FeedbackModal } from '../components/FeedbackModal';\nimport { FeedbackCanvas } from '../components/FeedbackCanvas';\nimport { FeedbackForm } from \"../components/FeedbackForm\";\nimport { Box, CircularProgress } from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\nimport { useScreenshot } from '../hooks/useScreenshot';\nimport { Base64Image, FeedbackConfig, FeedbackStage } from '../types';\n\nexport interface FeedbackContextValue {\n isOpen: boolean;\n setIsOpen: (open: boolean) => void;\n stage: FeedbackStage;\n setStage: (stage: FeedbackStage) => void;\n screenshot: Base64Image;\n setScreenshot: (img: string | null) => void;\n config: FeedbackConfig;\n triggerFeedback: () => Promise<void>;\n}\n\nexport const FeedbackContext = createContext<FeedbackContextValue | null>(null);\n\nexport const FeedbackUI = () => {\n const { stage, config } = useFeedback();\n\n const getTitle = () => {\n switch (stage) {\n case 'CAPTURING': return 'Taking Screenshot...';\n case 'ANNOTATING': return !config.enableAnnotation ? 'Highlight Issues' : 'Send Feedback';\n case 'FORM': return 'Send Feedback';\n default: return 'Feedback';\n }\n };\n\n return (\n <>\n <FeedbackRibbon />\n <FeedbackModal title={getTitle()}>\n {stage === 'CAPTURING' && (\n <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>\n <CircularProgress />\n </Box>\n )}\n {stage === 'ANNOTATING' && !!config.enableAnnotation && (\n <FeedbackCanvas />\n )}\n {(stage === 'FORM' || (stage === 'ANNOTATING' && !config.enableAnnotation)) && (\n <FeedbackForm />\n )}\n </FeedbackModal>\n </>\n );\n};\n\nexport const FeedbackProvider = ({ children, config }: { children: ReactNode, config: FeedbackConfig }) => {\n const [isOpen, setIsOpen] = useState(false);\n const [stage, setStage] = useState<FeedbackStage>('IDLE');\n const [screenshot, setScreenshot] = useState<string | null>(null);\n\n const { capture } = useScreenshot();\n\n const finalConfig = useMemo((): FeedbackConfig => ({\n position: 'top-right',\n primaryColor: '#dc2626',\n labelText: 'FEEDBACK',\n enableKeyboard: true,\n enableAnnotation: false,\n keyboardShortcut: 'ctrl+shift+f',\n ...config\n }), [config]);\n\n const handleStartCapture = useCallback(async () => {\n setIsOpen(true);\n setStage('CAPTURING');\n const img = await capture();\n if (img) {\n setScreenshot(img);\n setStage('ANNOTATING');\n }\n }, [capture]);\n\n const value: FeedbackContextValue = useMemo(() => ({\n isOpen,\n setIsOpen,\n stage,\n setStage,\n screenshot,\n setScreenshot,\n config: finalConfig,\n triggerFeedback: handleStartCapture\n }), [isOpen, stage, screenshot, finalConfig, handleStartCapture]);\n\n return (\n <FeedbackContext.Provider value={value}>\n {children}\n <FeedbackUI />\n </FeedbackContext.Provider>\n );\n};","import React, { useEffect } from \"react\";\nimport { useFeedback } from \"../hooks/useFeedback\";\n\nexport const FeedbackRibbon = () => {\n const { config, triggerFeedback } = useFeedback();\n\n const getPositionStyles = (): React.CSSProperties => {\n const base: React.CSSProperties = {\n position: 'fixed',\n padding: '8px 40px',\n cursor: 'pointer',\n color: 'white',\n fontWeight: 'bold',\n fontSize: '14px',\n zIndex: 999999,\n boxShadow: '0 4px 12px rgba(0,0,0,0.15)',\n userSelect: 'none',\n transition: 'filter 0.2s',\n backgroundColor: config.primaryColor || '#dc2626',\n };\n\n switch (config.position) {\n case 'top-left':\n return { ...base, top: '25px', left: '-35px', transform: 'rotate(-45deg)' };\n case 'bottom-right':\n return { ...base, bottom: '25px', right: '-35px', transform: 'rotate(-45deg)' };\n case 'bottom-left':\n return { ...base, bottom: '25px', left: '-35px', transform: 'rotate(45deg)' };\n case 'top-right':\n default:\n return { ...base, top: '25px', right: '-35px', transform: 'rotate(45deg)' };\n }\n };\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const shortcut = (config.keyboardShortcut || 'ctrl+shift+f').toLowerCase();\n const keys = shortcut.split('+');\n \n const ctrlReq = keys.includes('ctrl');\n const shiftReq = keys.includes('shift');\n const altReq = keys.includes('alt');\n const targetKey = keys[keys.length - 1];\n\n if (\n e.ctrlKey === ctrlReq &&\n e.shiftKey === shiftReq &&\n e.altKey === altReq &&\n e.key.toLowerCase() === targetKey\n ) {\n e.preventDefault();\n triggerFeedback();\n }\n };\n\n if (config.enableKeyboard !== false) {\n window.addEventListener('keydown', handleKeyDown);\n }\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [config, triggerFeedback]);\n\n return (\n <div\n data-html2canvas-ignore=\"true\" \n onClick={triggerFeedback}\n style={getPositionStyles()}\n onMouseEnter={(e) => (e.currentTarget.style.filter = 'brightness(1.1)')}\n onMouseLeave={(e) => (e.currentTarget.style.filter = 'brightness(1.0)')}\n >\n {config.labelText || 'FEEDBACK'}\n </div>\n );\n};","import { useContext } from 'react';\nimport { FeedbackContext } from '../context/FeedbackProvider';\n\nexport const useFeedback = () => {\n const context = useContext(FeedbackContext);\n \n if (!context) {\n throw new Error(\n 'useFeedback must be used within a FeedbackProvider. ' +\n 'Check if you wrapped your root component.'\n );\n }\n \n return context;\n};","import React from 'react';\nimport { Modal, Box, Typography, IconButton, Divider } from '@mui/material';\nimport CloseIcon from '@mui/icons-material/Close';\nimport { useFeedback } from '../hooks/useFeedback';\n\ninterface ModalProps {\n title: string;\n children: React.ReactNode;\n}\n\nconst style = {\n position: 'absolute' as const,\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n width: '90%',\n maxWidth: 900,\n maxHeight: '90vh',\n bgcolor: 'background.paper',\n borderRadius: 4,\n boxShadow: 24,\n display: 'flex',\n flexDirection: 'column',\n outline: 'none',\n overflow: 'hidden'\n};\n\nexport const FeedbackModal = ({ title, children }: ModalProps) => {\n const { isOpen, setIsOpen, setStage } = useFeedback();\n\n const handleClose = () => {\n setIsOpen(false);\n setStage('IDLE');\n };\n\n return (\n <Modal\n open={isOpen}\n onClose={handleClose}\n aria-labelledby=\"feedback-modal-title\"\n closeAfterTransition\n slotProps={{\n backdrop: {\n sx: { backdropFilter: 'blur(4px)', backgroundColor: 'rgba(0,0,0,0.6)' }\n }\n }}\n >\n <Box sx={style}>\n <Box sx={{ p: 2, px: 3, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <Typography id=\"feedback-modal-title\" variant=\"h6\" component=\"h2\" fontWeight=\"bold\">\n {title}\n </Typography>\n <IconButton onClick={handleClose} size=\"small\" aria-label=\"close\">\n <CloseIcon />\n </IconButton>\n </Box>\n\n <Divider />\n <Box sx={{ p: 3, overflowY: 'auto', flex: 1 }}>\n {children}\n </Box>\n </Box>\n </Modal>\n );\n};","import React, { useRef, useState, useEffect } from 'react';\nimport { Box, Button } from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\n\nexport const FeedbackCanvas = () => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [isDrawing, setIsDrawing] = useState(false);\n const { screenshot, setScreenshot, setStage, config } = useFeedback();\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || !screenshot) return;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const img = new Image() as HTMLImageElement;\n img.onload = () => {\n canvas.width = img.width;\n canvas.height = img.height;\n ctx.drawImage(img, 0, 0);\n ctx.strokeStyle = config.primaryColor || '#ff0000';\n ctx.lineWidth = 5;\n ctx.lineCap = 'round';\n };\n img.src = screenshot;\n }, [screenshot, config.primaryColor]);\n\n const getCoordinates = (e: React.MouseEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return { x: 0, y: 0 };\n const rect = canvas.getBoundingClientRect();\n return {\n x: (e.clientX - rect.left) * (canvas.width / rect.width),\n y: (e.clientY - rect.top) * (canvas.height / rect.height)\n };\n };\n\n const handleConfirm = () => {\n const dataUrl = canvasRef.current?.toDataURL('image/png');\n if (dataUrl) setScreenshot(dataUrl);\n setStage('FORM');\n };\n\n return (\n <Box sx={{ width: '100%', position: 'relative' }}>\n <Box sx={{ \n width: '100%', \n bgcolor: 'grey.100', \n borderRadius: 2, \n overflow: 'hidden',\n border: '1px solid',\n borderColor: 'divider'\n }}>\n <canvas\n ref={canvasRef}\n onMouseDown={(e) => {\n const ctx = canvasRef.current?.getContext('2d');\n const { x, y } = getCoordinates(e);\n ctx?.beginPath();\n ctx?.moveTo(x, y);\n setIsDrawing(true);\n }}\n onMouseMove={(e) => {\n if (!isDrawing) return;\n const ctx = canvasRef.current?.getContext('2d');\n const { x, y } = getCoordinates(e);\n ctx?.lineTo(x, y);\n ctx?.stroke();\n }}\n onMouseUp={() => setIsDrawing(false)}\n style={{ display: 'block', maxWidth: '100%', cursor: 'crosshair' }}\n />\n </Box>\n \n <Box sx={{ mt: 2, display: 'flex', justifyContent: 'flex-end' }}>\n <Button \n variant=\"contained\" \n onClick={handleConfirm}\n sx={{ bgcolor: config.primaryColor }}\n >\n Confirm Annotations\n </Button>\n </Box>\n </Box>\n );\n};","import React, { useState } from 'react';\nimport { \n Box, \n TextField, \n MenuItem, \n Button, \n CircularProgress,\n Stack \n} from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\nimport { FeedbackField } from '../types';\n\nexport const FeedbackForm = () => {\n const { config, setStage, setIsOpen, screenshot } = useFeedback();\n const [loading, setLoading] = useState(false);\n const [formData, setFormData] = useState<Record<string, any>>({});\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setLoading(true);\n \n try {\n const payload = { formData, screenshot };\n await config.onSubmit(payload);\n setIsOpen(false);\n } catch (error) {\n console.error(\"Submission failed\", error);\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <Box component=\"form\" onSubmit={handleSubmit}>\n <Stack spacing={3}>\n {config.fields.map((field: FeedbackField) => (\n <TextField\n key={field.id}\n label={field.label}\n required={field.required}\n select={field.type === 'select'}\n multiline={field.type === 'textarea'}\n rows={field.type === 'textarea' ? 4 : 1}\n fullWidth\n variant=\"outlined\"\n onChange={(e) => setFormData({ ...formData, [field.id]: e.target.value })}\n >\n {field.type === 'select' && field.options?.map((opt) => (\n <MenuItem key={opt} value={opt}>{opt}</MenuItem>\n ))}\n </TextField>\n ))}\n\n <Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mt: 2 }}>\n <Button \n variant=\"text\" \n onClick={() => {\n if (config.enableAnnotation) {\n setStage('ANNOTATING');\n } else {\n setStage('IDLE');\n setIsOpen(false);\n }\n }}\n disabled={loading}\n >\n Back\n </Button>\n <Button \n type=\"submit\" \n variant=\"contained\" \n disabled={loading}\n sx={{ bgcolor: config.primaryColor, '&:hover': { opacity: 0.9 } }}\n startIcon={loading && <CircularProgress size={20} color=\"inherit\" />}\n >\n {loading ? 'Sending...' : 'Submit Feedback'}\n </Button>\n </Box>\n </Stack>\n </Box>\n );\n};","import { useState } from 'react';\nimport html2canvas from 'html2canvas';\nimport { Base64Image } from '../types';\n\nexport const useScreenshot = () => {\n const [image, setImage] = useState<Base64Image | null>(null);\n const [isCapturing, setIsCapturing] = useState(false);\n\n const capture = async (element: HTMLElement = document.body) => {\n setIsCapturing(true);\n try {\n const canvas = await html2canvas(element, {\n useCORS: true,\n logging: false,\n allowTaint: true,\n backgroundColor: null,\n });\n const base64 = canvas.toDataURL('image/png');\n setImage(base64);\n return base64;\n } catch (err) {\n console.error(\"Failed to capture screenshot:\", err);\n } finally {\n setIsCapturing(false);\n }\n };\n\n return { capture, image, isCapturing, setImage };\n};"],"mappings":"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","names":["index_exports","__export","FeedbackProvider","useFeedback","__toCommonJS","import_react","import_react","import_react","useFeedback","context","FeedbackContext","import_jsx_runtime","FeedbackRibbon","config","triggerFeedback","useFeedback","getPositionStyles","base","handleKeyDown","e","keys","ctrlReq","shiftReq","altReq","targetKey","import_material","import_Close","import_jsx_runtime","style","FeedbackModal","title","children","isOpen","setIsOpen","setStage","useFeedback","handleClose","CloseIcon","import_react","import_material","import_jsx_runtime","FeedbackCanvas","canvasRef","isDrawing","setIsDrawing","screenshot","setScreenshot","setStage","config","useFeedback","canvas","ctx","img","getCoordinates","e","rect","x","dataUrl","import_react","import_material","import_jsx_runtime","FeedbackForm","config","setStage","setIsOpen","screenshot","useFeedback","loading","setLoading","formData","setFormData","e","payload","error","field","opt","import_material","import_react","import_html2canvas","useScreenshot","image","setImage","isCapturing","setIsCapturing","element","base64","html2canvas","err","import_jsx_runtime","FeedbackContext","FeedbackUI","stage","config","useFeedback","FeedbackRibbon","FeedbackModal","FeedbackCanvas","FeedbackForm","FeedbackProvider","children","isOpen","setIsOpen","setStage","screenshot","setScreenshot","capture","useScreenshot","finalConfig","handleStartCapture","img","value"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/context/FeedbackProvider.tsx","../src/components/FeedbackRibbon.tsx","../src/hooks/useFeedback.tsx","../src/components/FeedbackModal.tsx","../src/components/FeedbackCanvas.tsx","../src/components/FeedbackForm.tsx","../src/hooks/useScreenshot.tsx"],"sourcesContent":["export { FeedbackProvider } from './context/FeedbackProvider';\nexport { useFeedback } from './hooks/useFeedback';\nexport * from './types';","import { createContext, useState, ReactNode, useMemo, useCallback } from 'react';\nimport { FeedbackRibbon } from '../components/FeedbackRibbon';\nimport { FeedbackModal } from '../components/FeedbackModal';\nimport { FeedbackCanvas } from '../components/FeedbackCanvas';\nimport { FeedbackForm } from \"../components/FeedbackForm\";\nimport { Box, CircularProgress } from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\nimport { useScreenshot } from '../hooks/useScreenshot';\nimport { Base64Image, FeedbackConfig, FeedbackStage } from '../types';\n\nexport interface FeedbackContextValue {\n isOpen: boolean;\n setIsOpen: (open: boolean) => void;\n stage: FeedbackStage;\n setStage: (stage: FeedbackStage) => void;\n screenshot: Base64Image;\n setScreenshot: (img: string | null) => void;\n config: FeedbackConfig;\n triggerFeedback: () => Promise<void>;\n}\n\nexport const FeedbackContext = createContext<FeedbackContextValue | null>(null);\n\nexport const FeedbackUI = () => {\n const { stage, config } = useFeedback();\n\n if (config.modalTitle) return config.modalTitle;\n\n const getTitle = () => {\n switch (stage) {\n case 'CAPTURING': return 'Taking Screenshot...';\n case 'ANNOTATING': return config.enableAnnotation ? 'Highlight Issues' : 'Send Feedback';\n case 'FORM': return 'Send Feedback';\n default: return 'Feedback';\n }\n };\n\n return (\n <>\n <FeedbackRibbon />\n <FeedbackModal title={getTitle()}>\n {stage === 'CAPTURING' && (\n <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>\n <CircularProgress />\n </Box>\n )}\n {stage === 'ANNOTATING' && config.enableAnnotation && <FeedbackCanvas />}\n {(stage === 'FORM' || (stage === 'ANNOTATING' && !config.enableAnnotation)) && (\n <FeedbackForm />\n )}\n </FeedbackModal>\n </>\n );\n};\n\nexport const FeedbackProvider = ({ children, config }: { children: ReactNode, config: FeedbackConfig }) => {\n const [isOpen, setIsOpen] = useState(false);\n const [stage, setStage] = useState<FeedbackStage>('IDLE');\n const [screenshot, setScreenshot] = useState<string | null>(null);\n\n const { capture } = useScreenshot();\n\n const finalConfig = useMemo((): FeedbackConfig => ({\n position: 'top-right',\n primaryColor: '#dc2626',\n ribbonLabel: 'Feedback',\n modalTitle: undefined,\n enableKeyboard: true,\n enableAnnotation: false,\n keyboardShortcut: 'ctrl+shift+f',\n ...config\n }), [config]);\n\n const handleStartCapture = useCallback(async () => {\n setIsOpen(true);\n \n if (finalConfig.enableAnnotation) {\n setStage('CAPTURING');\n try {\n const img = await capture();\n if (img) {\n setScreenshot(img);\n setStage('ANNOTATING');\n return;\n }\n } catch (error) {\n console.warn('Screenshot failed, skipping to form:', error);\n }\n }\n setStage('FORM');\n }, [capture, finalConfig.enableAnnotation]);\n\n const value: FeedbackContextValue = useMemo(() => ({\n isOpen,\n setIsOpen,\n stage,\n setStage,\n screenshot,\n setScreenshot,\n config: finalConfig,\n triggerFeedback: handleStartCapture\n }), [isOpen, stage, screenshot, finalConfig, handleStartCapture]);\n\n return (\n <FeedbackContext.Provider value={value}>\n {children}\n <FeedbackUI />\n </FeedbackContext.Provider>\n );\n};","import React, { useEffect } from \"react\";\nimport { useFeedback } from \"../hooks/useFeedback\";\n\nexport const FeedbackRibbon = () => {\n const { config, triggerFeedback } = useFeedback();\n\n const getPositionStyles = (): React.CSSProperties => {\n const base: React.CSSProperties = {\n position: 'fixed',\n padding: '8px 40px',\n cursor: 'pointer',\n color: 'white',\n fontWeight: 'bold',\n fontSize: '14px',\n zIndex: 999999,\n boxShadow: '0 4px 12px rgba(0,0,0,0.15)',\n userSelect: 'none',\n transition: 'filter 0.2s',\n backgroundColor: config.primaryColor || '#dc2626',\n };\n\n switch (config.position) {\n case 'top-left':\n return { ...base, top: '25px', left: '-35px', transform: 'rotate(-45deg)' };\n case 'bottom-right':\n return { ...base, bottom: '25px', right: '-35px', transform: 'rotate(-45deg)' };\n case 'bottom-left':\n return { ...base, bottom: '25px', left: '-35px', transform: 'rotate(45deg)' };\n case 'top-right':\n default:\n return { ...base, top: '25px', right: '-35px', transform: 'rotate(45deg)' };\n }\n };\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const shortcut = (config.keyboardShortcut || 'ctrl+shift+f').toLowerCase();\n const keys = shortcut.split('+');\n \n const ctrlReq = keys.includes('ctrl');\n const shiftReq = keys.includes('shift');\n const altReq = keys.includes('alt');\n const targetKey = keys[keys.length - 1];\n\n if (\n e.ctrlKey === ctrlReq &&\n e.shiftKey === shiftReq &&\n e.altKey === altReq &&\n e.key.toLowerCase() === targetKey\n ) {\n e.preventDefault();\n triggerFeedback();\n }\n };\n\n if (config.enableKeyboard !== false) {\n window.addEventListener('keydown', handleKeyDown);\n }\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [config, triggerFeedback]);\n\n return (\n <div\n data-html2canvas-ignore=\"true\" \n onClick={triggerFeedback}\n style={getPositionStyles()}\n onMouseEnter={(e) => (e.currentTarget.style.filter = 'brightness(1.1)')}\n onMouseLeave={(e) => (e.currentTarget.style.filter = 'brightness(1.0)')}\n >\n {config.ribbonLabel || 'FEEDBACK'}\n </div>\n );\n};","import { useContext } from 'react';\nimport { FeedbackContext } from '../context/FeedbackProvider';\n\nexport const useFeedback = () => {\n const context = useContext(FeedbackContext);\n \n if (!context) {\n throw new Error(\n 'useFeedback must be used within a FeedbackProvider. ' +\n 'Check if you wrapped your root component.'\n );\n }\n \n return context;\n};","import React from 'react';\nimport { Modal, Box, Typography, IconButton, Divider } from '@mui/material';\nimport CloseIcon from '@mui/icons-material/Close';\nimport { useFeedback } from '../hooks/useFeedback';\n\ninterface ModalProps {\n title: string;\n children: React.ReactNode;\n}\n\nconst style = {\n position: 'absolute' as const,\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n width: '90%',\n maxWidth: 900,\n maxHeight: '90vh',\n bgcolor: 'background.paper',\n borderRadius: 4,\n boxShadow: 24,\n display: 'flex',\n flexDirection: 'column',\n outline: 'none',\n overflow: 'hidden'\n};\n\nexport const FeedbackModal = ({ title, children }: ModalProps) => {\n const { isOpen, setIsOpen, setStage } = useFeedback();\n\n const handleClose = () => {\n setIsOpen(false);\n setStage('IDLE');\n };\n\n return (\n <Modal\n open={isOpen}\n onClose={handleClose}\n aria-labelledby=\"feedback-modal-title\"\n closeAfterTransition\n slotProps={{\n backdrop: {\n sx: { backdropFilter: 'blur(4px)', backgroundColor: 'rgba(0,0,0,0.6)' }\n }\n }}\n >\n <Box sx={style}>\n <Box sx={{ p: 2, px: 3, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <Typography id=\"feedback-modal-title\" variant=\"h6\" component=\"h2\" fontWeight=\"bold\">\n {title}\n </Typography>\n <IconButton onClick={handleClose} size=\"small\" aria-label=\"close\">\n <CloseIcon />\n </IconButton>\n </Box>\n\n <Divider />\n <Box sx={{ p: 3, overflowY: 'auto', flex: 1 }}>\n {children}\n </Box>\n </Box>\n </Modal>\n );\n};","import React, { useRef, useState, useEffect } from 'react';\nimport { Box, Button } from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\n\nexport const FeedbackCanvas = () => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [isDrawing, setIsDrawing] = useState(false);\n const { screenshot, setScreenshot, setStage, config } = useFeedback();\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || !screenshot) return;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const img = new Image() as HTMLImageElement;\n img.onload = () => {\n canvas.width = img.width;\n canvas.height = img.height;\n ctx.drawImage(img, 0, 0);\n ctx.strokeStyle = config.primaryColor || '#ff0000';\n ctx.lineWidth = 5;\n ctx.lineCap = 'round';\n };\n img.src = screenshot;\n }, [screenshot, config.primaryColor]);\n\n const getCoordinates = (e: React.MouseEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return { x: 0, y: 0 };\n const rect = canvas.getBoundingClientRect();\n return {\n x: (e.clientX - rect.left) * (canvas.width / rect.width),\n y: (e.clientY - rect.top) * (canvas.height / rect.height)\n };\n };\n\n const handleConfirm = () => {\n const dataUrl = canvasRef.current?.toDataURL('image/png');\n if (dataUrl) setScreenshot(dataUrl);\n setStage('FORM');\n };\n\n return (\n <Box sx={{ width: '100%', position: 'relative' }}>\n <Box sx={{ \n width: '100%', \n bgcolor: 'grey.100', \n borderRadius: 2, \n overflow: 'hidden',\n border: '1px solid',\n borderColor: 'divider'\n }}>\n <canvas\n ref={canvasRef}\n onMouseDown={(e) => {\n const ctx = canvasRef.current?.getContext('2d');\n const { x, y } = getCoordinates(e);\n ctx?.beginPath();\n ctx?.moveTo(x, y);\n setIsDrawing(true);\n }}\n onMouseMove={(e) => {\n if (!isDrawing) return;\n const ctx = canvasRef.current?.getContext('2d');\n const { x, y } = getCoordinates(e);\n ctx?.lineTo(x, y);\n ctx?.stroke();\n }}\n onMouseUp={() => setIsDrawing(false)}\n style={{ display: 'block', maxWidth: '100%', cursor: 'crosshair' }}\n />\n </Box>\n \n <Box sx={{ mt: 2, display: 'flex', justifyContent: 'flex-end' }}>\n <Button \n variant=\"contained\" \n onClick={handleConfirm}\n sx={{ bgcolor: config.primaryColor }}\n >\n Confirm Annotations\n </Button>\n </Box>\n </Box>\n );\n};","import React, { useState } from 'react';\nimport { \n Box, \n TextField, \n MenuItem, \n Button, \n CircularProgress,\n Stack \n} from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\nimport { FeedbackField } from '../types';\n\nexport const FeedbackForm = () => {\n const { config, setStage, setIsOpen, screenshot } = useFeedback();\n const [loading, setLoading] = useState(false);\n const [formData, setFormData] = useState<Record<string, any>>({});\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setLoading(true);\n \n try {\n const payload = { formData, screenshot };\n await config.onSubmit(payload);\n setIsOpen(false);\n } catch (error) {\n console.error(\"Submission failed\", error);\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <Box component=\"form\" onSubmit={handleSubmit}>\n <Stack spacing={3}>\n {config.fields.map((field: FeedbackField) => (\n <TextField\n key={field.id}\n label={field.label}\n required={field.required}\n select={field.type === 'select'}\n multiline={field.type === 'textarea'}\n rows={field.type === 'textarea' ? 4 : 1}\n fullWidth\n variant=\"outlined\"\n onChange={(e) => setFormData({ ...formData, [field.id]: e.target.value })}\n >\n {field.type === 'select' && field.options?.map((opt) => (\n <MenuItem key={opt} value={opt}>{opt}</MenuItem>\n ))}\n </TextField>\n ))}\n\n <Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mt: 2 }}>\n <Button \n variant=\"text\" \n onClick={() => {\n if (config.enableAnnotation) {\n setStage('ANNOTATING');\n } else {\n setStage('IDLE');\n setIsOpen(false);\n }\n }}\n disabled={loading}\n >\n Back\n </Button>\n <Button \n type=\"submit\" \n variant=\"contained\" \n disabled={loading}\n sx={{ bgcolor: config.primaryColor, '&:hover': { opacity: 0.9 } }}\n startIcon={loading && <CircularProgress size={20} color=\"inherit\" />}\n >\n {loading ? 'Sending...' : 'Submit Feedback'}\n </Button>\n </Box>\n </Stack>\n </Box>\n );\n};","import { useState } from 'react';\nimport html2canvas from 'html2canvas';\nimport { Base64Image } from '../types';\n\nexport const useScreenshot = () => {\n const [image, setImage] = useState<Base64Image | null>(null);\n const [isCapturing, setIsCapturing] = useState(false);\n\n const capture = async (element: HTMLElement = document.body) => {\n setIsCapturing(true);\n try {\n const canvas = await html2canvas(element, {\n useCORS: true,\n logging: false,\n allowTaint: true,\n backgroundColor: null,\n });\n const base64 = canvas.toDataURL('image/png');\n setImage(base64);\n return base64;\n } catch (err) {\n console.error(\"Failed to capture screenshot:\", err);\n } finally {\n setIsCapturing(false);\n }\n };\n\n return { capture, image, isCapturing, setImage };\n};"],"mappings":"0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,sBAAAE,EAAA,gBAAAC,IAAA,eAAAC,EAAAJ,GCAA,IAAAK,EAAyE,iBCAzE,IAAAC,EAAiC,iBCAjC,IAAAC,EAA2B,iBAGpB,IAAMC,EAAc,IAAM,CAC/B,IAAMC,KAAU,cAAWC,CAAe,EAE1C,GAAI,CAACD,EACH,MAAM,IAAI,MACR,+FAEF,EAGF,OAAOA,CACT,EDgDI,IAAAE,EAAA,6BA3DSC,EAAiB,IAAM,CAClC,GAAM,CAAE,OAAAC,EAAQ,gBAAAC,CAAgB,EAAIC,EAAY,EAE1CC,EAAoB,IAA2B,CACnD,IAAMC,EAA4B,CAChC,SAAU,QACV,QAAS,WACT,OAAQ,UACR,MAAO,QACP,WAAY,OACZ,SAAU,OACV,OAAQ,OACR,UAAW,8BACX,WAAY,OACZ,WAAY,cACZ,gBAAiBJ,EAAO,cAAgB,SAC1C,EAEA,OAAQA,EAAO,SAAU,CACvB,IAAK,WACH,MAAO,CAAE,GAAGI,EAAM,IAAK,OAAQ,KAAM,QAAS,UAAW,gBAAiB,EAC5E,IAAK,eACH,MAAO,CAAE,GAAGA,EAAM,OAAQ,OAAQ,MAAO,QAAS,UAAW,gBAAiB,EAChF,IAAK,cACH,MAAO,CAAE,GAAGA,EAAM,OAAQ,OAAQ,KAAM,QAAS,UAAW,eAAgB,EAE9E,QACE,MAAO,CAAE,GAAGA,EAAM,IAAK,OAAQ,MAAO,QAAS,UAAW,eAAgB,CAC9E,CACF,EAEA,sBAAU,IAAM,CACd,IAAMC,EAAiBC,GAAqB,CAE1C,IAAMC,GADYP,EAAO,kBAAoB,gBAAgB,YAAY,EACnD,MAAM,GAAG,EAEzBQ,EAAUD,EAAK,SAAS,MAAM,EAC9BE,EAAWF,EAAK,SAAS,OAAO,EAChCG,EAASH,EAAK,SAAS,KAAK,EAC5BI,EAAYJ,EAAKA,EAAK,OAAS,CAAC,EAGpCD,EAAE,UAAYE,GACdF,EAAE,WAAaG,GACfH,EAAE,SAAWI,GACbJ,EAAE,IAAI,YAAY,IAAMK,IAExBL,EAAE,eAAe,EACjBL,EAAgB,EAEpB,EAEA,OAAID,EAAO,iBAAmB,IAC5B,OAAO,iBAAiB,UAAWK,CAAa,EAE3C,IAAM,OAAO,oBAAoB,UAAWA,CAAa,CAClE,EAAG,CAACL,EAAQC,CAAe,CAAC,KAG1B,OAAC,OACC,0BAAwB,OACxB,QAASA,EACT,MAAOE,EAAkB,EACzB,aAAeG,GAAOA,EAAE,cAAc,MAAM,OAAS,kBACrD,aAAeA,GAAOA,EAAE,cAAc,MAAM,OAAS,kBAEpD,SAAAN,EAAO,aAAe,WACzB,CAEJ,EEvEA,IAAAY,EAA4D,yBAC5DC,EAAsB,wCA8Cd,IAAAC,EAAA,6BAtCFC,EAAQ,CACZ,SAAU,WACV,IAAK,MACL,KAAM,MACN,UAAW,wBACX,MAAO,MACP,SAAU,IACV,UAAW,OACX,QAAS,mBACT,aAAc,EACd,UAAW,GACX,QAAS,OACT,cAAe,SACf,QAAS,OACT,SAAU,QACZ,EAEaC,EAAgB,CAAC,CAAE,MAAAC,EAAO,SAAAC,CAAS,IAAkB,CAChE,GAAM,CAAE,OAAAC,EAAQ,UAAAC,EAAW,SAAAC,CAAS,EAAIC,EAAY,EAE9CC,EAAc,IAAM,CACxBH,EAAU,EAAK,EACfC,EAAS,MAAM,CACjB,EAEA,SACE,OAAC,SACC,KAAMF,EACN,QAASI,EACT,kBAAgB,uBAChB,qBAAoB,GACpB,UAAW,CACT,SAAU,CACR,GAAI,CAAE,eAAgB,YAAa,gBAAiB,iBAAkB,CACxE,CACF,EAEA,oBAAC,OAAI,GAAIR,EACP,qBAAC,OAAI,GAAI,CAAE,EAAG,EAAG,GAAI,EAAG,QAAS,OAAQ,WAAY,SAAU,eAAgB,eAAgB,EAC7F,oBAAC,cAAW,GAAG,uBAAuB,QAAQ,KAAK,UAAU,KAAK,WAAW,OAC1E,SAAAE,EACH,KACA,OAAC,cAAW,QAASM,EAAa,KAAK,QAAQ,aAAW,QACxD,mBAAC,EAAAC,QAAA,EAAU,EACb,GACF,KAEA,OAAC,YAAQ,KACT,OAAC,OAAI,GAAI,CAAE,EAAG,EAAG,UAAW,OAAQ,KAAM,CAAE,EACzC,SAAAN,EACH,GACF,EACF,CAEJ,EChEA,IAAAO,EAAmD,iBACnDC,EAA4B,yBA2CxB,IAAAC,EAAA,6BAxCSC,EAAiB,IAAM,CAClC,IAAMC,KAAY,UAA0B,IAAI,EAC1C,CAACC,EAAWC,CAAY,KAAI,YAAS,EAAK,EAC1C,CAAE,WAAAC,EAAY,cAAAC,EAAe,SAAAC,EAAU,OAAAC,CAAO,EAAIC,EAAY,KAEpE,aAAU,IAAM,CACd,IAAMC,EAASR,EAAU,QACzB,GAAI,CAACQ,GAAU,CAACL,EAAY,OAC5B,IAAMM,EAAMD,EAAO,WAAW,IAAI,EAClC,GAAI,CAACC,EAAK,OAEV,IAAMC,EAAM,IAAI,MACdA,EAAI,OAAS,IAAM,CACjBF,EAAO,MAAQE,EAAI,MACnBF,EAAO,OAASE,EAAI,OACpBD,EAAI,UAAUC,EAAK,EAAG,CAAC,EACvBD,EAAI,YAAcH,EAAO,cAAgB,UACzCG,EAAI,UAAY,EAChBA,EAAI,QAAU,OAChB,EACAC,EAAI,IAAMP,CACZ,EAAG,CAACA,EAAYG,EAAO,YAAY,CAAC,EAEtC,IAAMK,EAAkBC,GAAwB,CAC9C,IAAMJ,EAASR,EAAU,QACzB,GAAI,CAACQ,EAAQ,MAAO,CAAE,EAAG,EAAG,EAAG,CAAE,EACjC,IAAMK,EAAOL,EAAO,sBAAsB,EAC1C,MAAO,CACL,GAAII,EAAE,QAAUC,EAAK,OAASL,EAAO,MAAQK,EAAK,OAClD,GAAID,EAAE,QAAUC,EAAK,MAAQL,EAAO,OAASK,EAAK,OACpD,CACF,EAQA,SACE,QAAC,OAAI,GAAI,CAAE,MAAO,OAAQ,SAAU,UAAW,EAC7C,oBAAC,OAAI,GAAI,CACP,MAAO,OACP,QAAS,WACT,aAAc,EACd,SAAU,SACV,OAAQ,YACR,YAAa,SACf,EACE,mBAAC,UACC,IAAKb,EACL,YAAcY,GAAM,CAClB,IAAMH,EAAMT,EAAU,SAAS,WAAW,IAAI,EACxC,CAAE,EAAAc,EAAG,EAAAC,CAAE,EAAIJ,EAAeC,CAAC,EACjCH,GAAK,UAAU,EACfA,GAAK,OAAOK,EAAGC,CAAC,EAChBb,EAAa,EAAI,CACnB,EACA,YAAcU,GAAM,CAClB,GAAI,CAACX,EAAW,OAChB,IAAMQ,EAAMT,EAAU,SAAS,WAAW,IAAI,EACxC,CAAE,EAAAc,EAAG,EAAAC,CAAE,EAAIJ,EAAeC,CAAC,EACjCH,GAAK,OAAOK,EAAGC,CAAC,EAChBN,GAAK,OAAO,CACd,EACA,UAAW,IAAMP,EAAa,EAAK,EACnC,MAAO,CAAE,QAAS,QAAS,SAAU,OAAQ,OAAQ,WAAY,EACnE,EACF,KAEA,OAAC,OAAI,GAAI,CAAE,GAAI,EAAG,QAAS,OAAQ,eAAgB,UAAW,EAC5D,mBAAC,UACC,QAAQ,YACR,QAxCc,IAAM,CAC1B,IAAMc,EAAUhB,EAAU,SAAS,UAAU,WAAW,EACpDgB,GAASZ,EAAcY,CAAO,EAClCX,EAAS,MAAM,CACjB,EAqCQ,GAAI,CAAE,QAASC,EAAO,YAAa,EACpC,+BAED,EACF,GACF,CAEJ,ECrFA,IAAAW,EAAgC,iBAChCC,EAOO,yBAwCO,IAAAC,EAAA,6BApCDC,EAAe,IAAM,CAChC,GAAM,CAAE,OAAAC,EAAQ,SAAAC,EAAU,UAAAC,EAAW,WAAAC,CAAW,EAAIC,EAAY,EAC1D,CAACC,EAASC,CAAU,KAAI,YAAS,EAAK,EACtC,CAACC,EAAUC,CAAW,KAAI,YAA8B,CAAC,CAAC,EAiBhE,SACE,OAAC,OAAI,UAAU,OAAO,SAhBH,MAAOC,GAAuB,CACjDA,EAAE,eAAe,EACjBH,EAAW,EAAI,EAEf,GAAI,CACF,IAAMI,EAAU,CAAE,SAAAH,EAAU,WAAAJ,CAAW,EACvC,MAAMH,EAAO,SAASU,CAAO,EAC7BR,EAAU,EAAK,CACjB,OAASS,EAAO,CACd,QAAQ,MAAM,oBAAqBA,CAAK,CAC1C,QAAE,CACAL,EAAW,EAAK,CAClB,CACF,EAII,oBAAC,SAAM,QAAS,EACb,UAAAN,EAAO,OAAO,IAAKY,MAClB,OAAC,aAEC,MAAOA,EAAM,MACb,SAAUA,EAAM,SAChB,OAAQA,EAAM,OAAS,SACvB,UAAWA,EAAM,OAAS,WAC1B,KAAMA,EAAM,OAAS,WAAa,EAAI,EACtC,UAAS,GACT,QAAQ,WACR,SAAWH,GAAMD,EAAY,CAAE,GAAGD,EAAU,CAACK,EAAM,EAAE,EAAGH,EAAE,OAAO,KAAM,CAAC,EAEvE,SAAAG,EAAM,OAAS,UAAYA,EAAM,SAAS,IAAKC,MAC9C,OAAC,YAAmB,MAAOA,EAAM,SAAAA,GAAlBA,CAAsB,CACtC,GAZID,EAAM,EAab,CACD,KAED,QAAC,OAAI,GAAI,CAAE,QAAS,OAAQ,eAAgB,WAAY,IAAK,EAAG,GAAI,CAAE,EACpE,oBAAC,UACC,QAAQ,OACR,QAAS,IAAM,CACTZ,EAAO,iBACTC,EAAS,YAAY,GAErBA,EAAS,MAAM,EACfC,EAAU,EAAK,EAEnB,EACA,SAAUG,EACX,gBAEC,KACF,OAAC,UACC,KAAK,SACL,QAAQ,YACR,SAAUA,EACV,GAAI,CAAE,QAASL,EAAO,aAAc,UAAW,CAAE,QAAS,EAAI,CAAE,EAChE,UAAWK,MAAW,OAAC,oBAAiB,KAAM,GAAI,MAAM,UAAU,EAEjE,SAAAA,EAAU,aAAe,kBAC5B,GACF,GACF,EACF,CAEJ,EL5EA,IAAAS,EAAsC,yBMLtC,IAAAC,EAAyB,iBACzBC,EAAwB,0BAGXC,EAAgB,IAAM,CACjC,GAAM,CAACC,EAAOC,CAAQ,KAAI,YAA6B,IAAI,EACrD,CAACC,EAAaC,CAAc,KAAI,YAAS,EAAK,EAqBpD,MAAO,CAAE,QAnBO,MAAOC,EAAuB,SAAS,OAAS,CAC9DD,EAAe,EAAI,EACnB,GAAI,CAOF,IAAME,GANS,QAAM,EAAAC,SAAYF,EAAS,CACxC,QAAS,GACT,QAAS,GACT,WAAY,GACZ,gBAAiB,IACnB,CAAC,GACqB,UAAU,WAAW,EAC3C,OAAAH,EAASI,CAAM,EACRA,CACT,OAASE,EAAK,CACZ,QAAQ,MAAM,gCAAiCA,CAAG,CACpD,QAAE,CACAJ,EAAe,EAAK,CACtB,CACF,EAEkB,MAAAH,EAAO,YAAAE,EAAa,SAAAD,CAAS,CACjD,ENUI,IAAAO,EAAA,6BAjBSC,KAAkB,iBAA2C,IAAI,EAEjEC,EAAa,IAAM,CAC9B,GAAM,CAAE,MAAAC,EAAO,OAAAC,CAAO,EAAIC,EAAY,EAEtC,OAAID,EAAO,WAAmBA,EAAO,cAYnC,oBACE,oBAACE,EAAA,EAAe,KAChB,QAACC,EAAA,CAAc,OAZF,IAAM,CACrB,OAAQJ,EAAO,CACb,IAAK,YAAa,MAAO,uBACzB,IAAK,aAAc,OAAOC,EAAO,iBAAmB,mBAAqB,gBACzE,IAAK,OAAQ,MAAO,gBACpB,QAAS,MAAO,UAClB,CACF,GAKmC,EAC5B,UAAAD,IAAU,gBACT,OAAC,OAAI,GAAI,CAAE,QAAS,OAAQ,eAAgB,SAAU,EAAG,CAAE,EACzD,mBAAC,qBAAiB,EACpB,EAEDA,IAAU,cAAgBC,EAAO,qBAAoB,OAACI,EAAA,EAAe,GACpEL,IAAU,QAAWA,IAAU,cAAgB,CAACC,EAAO,sBACvD,OAACK,EAAA,EAAa,GAElB,GACF,CAEJ,EAEaC,EAAmB,CAAC,CAAE,SAAAC,EAAU,OAAAP,CAAO,IAAuD,CACzG,GAAM,CAACQ,EAAQC,CAAS,KAAI,YAAS,EAAK,EACpC,CAACV,EAAOW,CAAQ,KAAI,YAAwB,MAAM,EAClD,CAACC,EAAYC,CAAa,KAAI,YAAwB,IAAI,EAE1D,CAAE,QAAAC,CAAQ,EAAIC,EAAc,EAE5BC,KAAc,WAAQ,KAAuB,CACjD,SAAU,YACV,aAAc,UACd,YAAa,WACb,WAAY,OACZ,eAAgB,GAChB,iBAAkB,GAClB,iBAAkB,eAClB,GAAGf,CACL,GAAI,CAACA,CAAM,CAAC,EAENgB,KAAqB,eAAY,SAAY,CAGjD,GAFAP,EAAU,EAAI,EAEVM,EAAY,iBAAkB,CAChCL,EAAS,WAAW,EACpB,GAAI,CACF,IAAMO,EAAM,MAAMJ,EAAQ,EAC1B,GAAII,EAAK,CACPL,EAAcK,CAAG,EACjBP,EAAS,YAAY,EACrB,MACF,CACF,OAASQ,EAAO,CACd,QAAQ,KAAK,uCAAwCA,CAAK,CAC5D,CACF,CACAR,EAAS,MAAM,CACjB,EAAG,CAACG,EAASE,EAAY,gBAAgB,CAAC,EAEpCI,KAA8B,WAAQ,KAAO,CACjD,OAAAX,EACA,UAAAC,EACA,MAAAV,EACA,SAAAW,EACA,WAAAC,EACA,cAAAC,EACA,OAAQG,EACR,gBAAiBC,CACnB,GAAI,CAACR,EAAQT,EAAOY,EAAYI,EAAaC,CAAkB,CAAC,EAEhE,SACE,QAACnB,EAAgB,SAAhB,CAAyB,MAAOsB,EAC9B,UAAAZ,KACD,OAACT,EAAA,EAAW,GACd,CAEJ","names":["index_exports","__export","FeedbackProvider","useFeedback","__toCommonJS","import_react","import_react","import_react","useFeedback","context","FeedbackContext","import_jsx_runtime","FeedbackRibbon","config","triggerFeedback","useFeedback","getPositionStyles","base","handleKeyDown","e","keys","ctrlReq","shiftReq","altReq","targetKey","import_material","import_Close","import_jsx_runtime","style","FeedbackModal","title","children","isOpen","setIsOpen","setStage","useFeedback","handleClose","CloseIcon","import_react","import_material","import_jsx_runtime","FeedbackCanvas","canvasRef","isDrawing","setIsDrawing","screenshot","setScreenshot","setStage","config","useFeedback","canvas","ctx","img","getCoordinates","e","rect","x","y","dataUrl","import_react","import_material","import_jsx_runtime","FeedbackForm","config","setStage","setIsOpen","screenshot","useFeedback","loading","setLoading","formData","setFormData","e","payload","error","field","opt","import_material","import_react","import_html2canvas","useScreenshot","image","setImage","isCapturing","setIsCapturing","element","base64","html2canvas","err","import_jsx_runtime","FeedbackContext","FeedbackUI","stage","config","useFeedback","FeedbackRibbon","FeedbackModal","FeedbackCanvas","FeedbackForm","FeedbackProvider","children","isOpen","setIsOpen","setStage","screenshot","setScreenshot","capture","useScreenshot","finalConfig","handleStartCapture","img","error","value"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createContext as ee,useState as C,useMemo as
|
|
1
|
+
import{createContext as ee,useState as C,useMemo as E,useCallback as te}from"react";import{useEffect as D}from"react";import{useContext as O}from"react";var u=()=>{let e=O(h);if(!e)throw new Error("useFeedback must be used within a FeedbackProvider. Check if you wrapped your root component.");return e};import{jsx as L}from"react/jsx-runtime";var v=()=>{let{config:e,triggerFeedback:n}=u(),c=()=>{let r={position:"fixed",padding:"8px 40px",cursor:"pointer",color:"white",fontWeight:"bold",fontSize:"14px",zIndex:999999,boxShadow:"0 4px 12px rgba(0,0,0,0.15)",userSelect:"none",transition:"filter 0.2s",backgroundColor:e.primaryColor||"#dc2626"};switch(e.position){case"top-left":return{...r,top:"25px",left:"-35px",transform:"rotate(-45deg)"};case"bottom-right":return{...r,bottom:"25px",right:"-35px",transform:"rotate(-45deg)"};case"bottom-left":return{...r,bottom:"25px",left:"-35px",transform:"rotate(45deg)"};default:return{...r,top:"25px",right:"-35px",transform:"rotate(45deg)"}}};return D(()=>{let r=a=>{let s=(e.keyboardShortcut||"ctrl+shift+f").toLowerCase().split("+"),d=s.includes("ctrl"),f=s.includes("shift"),t=s.includes("alt"),o=s[s.length-1];a.ctrlKey===d&&a.shiftKey===f&&a.altKey===t&&a.key.toLowerCase()===o&&(a.preventDefault(),n())};return e.enableKeyboard!==!1&&window.addEventListener("keydown",r),()=>window.removeEventListener("keydown",r)},[e,n]),L("div",{"data-html2canvas-ignore":"true",onClick:n,style:c(),onMouseEnter:r=>r.currentTarget.style.filter="brightness(1.1)",onMouseLeave:r=>r.currentTarget.style.filter="brightness(1.0)",children:e.ribbonLabel||"FEEDBACK"})};import{Modal as K,Box as k,Typography as G,IconButton as U,Divider as W}from"@mui/material";import q from"@mui/icons-material/Close";import{jsx as m,jsxs as S}from"react/jsx-runtime";var H={position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",width:"90%",maxWidth:900,maxHeight:"90vh",bgcolor:"background.paper",borderRadius:4,boxShadow:24,display:"flex",flexDirection:"column",outline:"none",overflow:"hidden"},w=({title:e,children:n})=>{let{isOpen:c,setIsOpen:r,setStage:a}=u(),l=()=>{r(!1),a("IDLE")};return m(K,{open:c,onClose:l,"aria-labelledby":"feedback-modal-title",closeAfterTransition:!0,slotProps:{backdrop:{sx:{backdropFilter:"blur(4px)",backgroundColor:"rgba(0,0,0,0.6)"}}},children:S(k,{sx:H,children:[S(k,{sx:{p:2,px:3,display:"flex",alignItems:"center",justifyContent:"space-between"},children:[m(G,{id:"feedback-modal-title",variant:"h6",component:"h2",fontWeight:"bold",children:e}),m(U,{onClick:l,size:"small","aria-label":"close",children:m(q,{})})]}),m(W,{}),m(k,{sx:{p:3,overflowY:"auto",flex:1},children:n})]})})};import{useRef as z,useState as V,useEffect as Y}from"react";import{Box as y,Button as X}from"@mui/material";import{jsx as x,jsxs as J}from"react/jsx-runtime";var I=()=>{let e=z(null),[n,c]=V(!1),{screenshot:r,setScreenshot:a,setStage:l,config:s}=u();Y(()=>{let t=e.current;if(!t||!r)return;let o=t.getContext("2d");if(!o)return;let i=new Image;i.onload=()=>{t.width=i.width,t.height=i.height,o.drawImage(i,0,0),o.strokeStyle=s.primaryColor||"#ff0000",o.lineWidth=5,o.lineCap="round"},i.src=r},[r,s.primaryColor]);let d=t=>{let o=e.current;if(!o)return{x:0,y:0};let i=o.getBoundingClientRect();return{x:(t.clientX-i.left)*(o.width/i.width),y:(t.clientY-i.top)*(o.height/i.height)}};return J(y,{sx:{width:"100%",position:"relative"},children:[x(y,{sx:{width:"100%",bgcolor:"grey.100",borderRadius:2,overflow:"hidden",border:"1px solid",borderColor:"divider"},children:x("canvas",{ref:e,onMouseDown:t=>{let o=e.current?.getContext("2d"),{x:i,y:p}=d(t);o?.beginPath(),o?.moveTo(i,p),c(!0)},onMouseMove:t=>{if(!n)return;let o=e.current?.getContext("2d"),{x:i,y:p}=d(t);o?.lineTo(i,p),o?.stroke()},onMouseUp:()=>c(!1),style:{display:"block",maxWidth:"100%",cursor:"crosshair"}})}),x(y,{sx:{mt:2,display:"flex",justifyContent:"flex-end"},children:x(X,{variant:"contained",onClick:()=>{let t=e.current?.toDataURL("image/png");t&&a(t),l("FORM")},sx:{bgcolor:s.primaryColor},children:"Confirm Annotations"})})]})};import{useState as R}from"react";import{Box as T,TextField as Q,MenuItem as Z,Button as B,CircularProgress as _,Stack as $}from"@mui/material";import{jsx as b,jsxs as M}from"react/jsx-runtime";var A=()=>{let{config:e,setStage:n,setIsOpen:c,screenshot:r}=u(),[a,l]=R(!1),[s,d]=R({});return b(T,{component:"form",onSubmit:async t=>{t.preventDefault(),l(!0);try{let o={formData:s,screenshot:r};await e.onSubmit(o),c(!1)}catch(o){console.error("Submission failed",o)}finally{l(!1)}},children:M($,{spacing:3,children:[e.fields.map(t=>b(Q,{label:t.label,required:t.required,select:t.type==="select",multiline:t.type==="textarea",rows:t.type==="textarea"?4:1,fullWidth:!0,variant:"outlined",onChange:o=>d({...s,[t.id]:o.target.value}),children:t.type==="select"&&t.options?.map(o=>b(Z,{value:o,children:o},o))},t.id)),M(T,{sx:{display:"flex",justifyContent:"flex-end",gap:2,mt:2},children:[b(B,{variant:"text",onClick:()=>{e.enableAnnotation?n("ANNOTATING"):(n("IDLE"),c(!1))},disabled:a,children:"Back"}),b(B,{type:"submit",variant:"contained",disabled:a,sx:{bgcolor:e.primaryColor,"&:hover":{opacity:.9}},startIcon:a&&b(_,{size:20,color:"inherit"}),children:a?"Sending...":"Submit Feedback"})]})]})})};import{Box as oe,CircularProgress as re}from"@mui/material";import{useState as N}from"react";import j from"html2canvas";var P=()=>{let[e,n]=N(null),[c,r]=N(!1);return{capture:async(l=document.body)=>{r(!0);try{let d=(await j(l,{useCORS:!0,logging:!1,allowTaint:!0,backgroundColor:null})).toDataURL("image/png");return n(d),d}catch(s){console.error("Failed to capture screenshot:",s)}finally{r(!1)}},image:e,isCapturing:c,setImage:n}};import{Fragment as se,jsx as g,jsxs as F}from"react/jsx-runtime";var h=ee(null),ne=()=>{let{stage:e,config:n}=u();return n.modalTitle?n.modalTitle:F(se,{children:[g(v,{}),F(w,{title:(()=>{switch(e){case"CAPTURING":return"Taking Screenshot...";case"ANNOTATING":return n.enableAnnotation?"Highlight Issues":"Send Feedback";case"FORM":return"Send Feedback";default:return"Feedback"}})(),children:[e==="CAPTURING"&&g(oe,{sx:{display:"flex",justifyContent:"center",p:4},children:g(re,{})}),e==="ANNOTATING"&&n.enableAnnotation&&g(I,{}),(e==="FORM"||e==="ANNOTATING"&&!n.enableAnnotation)&&g(A,{})]})]})},ae=({children:e,config:n})=>{let[c,r]=C(!1),[a,l]=C("IDLE"),[s,d]=C(null),{capture:f}=P(),t=E(()=>({position:"top-right",primaryColor:"#dc2626",ribbonLabel:"Feedback",modalTitle:void 0,enableKeyboard:!0,enableAnnotation:!1,keyboardShortcut:"ctrl+shift+f",...n}),[n]),o=te(async()=>{if(r(!0),t.enableAnnotation){l("CAPTURING");try{let p=await f();if(p){d(p),l("ANNOTATING");return}}catch(p){console.warn("Screenshot failed, skipping to form:",p)}}l("FORM")},[f,t.enableAnnotation]),i=E(()=>({isOpen:c,setIsOpen:r,stage:a,setStage:l,screenshot:s,setScreenshot:d,config:t,triggerFeedback:o}),[c,a,s,t,o]);return F(h.Provider,{value:i,children:[e,g(ne,{})]})};export{ae as FeedbackProvider,u as useFeedback};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context/FeedbackProvider.tsx","../src/components/FeedbackRibbon.tsx","../src/hooks/useFeedback.tsx","../src/components/FeedbackModal.tsx","../src/components/FeedbackCanvas.tsx","../src/components/FeedbackForm.tsx","../src/hooks/useScreenshot.tsx"],"sourcesContent":["import { createContext, useState, ReactNode, useMemo, useCallback } from 'react';\nimport { FeedbackRibbon } from '../components/FeedbackRibbon';\nimport { FeedbackModal } from '../components/FeedbackModal';\nimport { FeedbackCanvas } from '../components/FeedbackCanvas';\nimport { FeedbackForm } from \"../components/FeedbackForm\";\nimport { Box, CircularProgress } from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\nimport { useScreenshot } from '../hooks/useScreenshot';\nimport { Base64Image, FeedbackConfig, FeedbackStage } from '../types';\n\nexport interface FeedbackContextValue {\n isOpen: boolean;\n setIsOpen: (open: boolean) => void;\n stage: FeedbackStage;\n setStage: (stage: FeedbackStage) => void;\n screenshot: Base64Image;\n setScreenshot: (img: string | null) => void;\n config: FeedbackConfig;\n triggerFeedback: () => Promise<void>;\n}\n\nexport const FeedbackContext = createContext<FeedbackContextValue | null>(null);\n\nexport const FeedbackUI = () => {\n const { stage, config } = useFeedback();\n\n const getTitle = () => {\n switch (stage) {\n case 'CAPTURING': return 'Taking Screenshot...';\n case 'ANNOTATING': return !config.enableAnnotation ? 'Highlight Issues' : 'Send Feedback';\n case 'FORM': return 'Send Feedback';\n default: return 'Feedback';\n }\n };\n\n return (\n <>\n <FeedbackRibbon />\n <FeedbackModal title={getTitle()}>\n {stage === 'CAPTURING' && (\n <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>\n <CircularProgress />\n </Box>\n )}\n {stage === 'ANNOTATING' && !!config.enableAnnotation && (\n <FeedbackCanvas />\n )}\n {(stage === 'FORM' || (stage === 'ANNOTATING' && !config.enableAnnotation)) && (\n <FeedbackForm />\n )}\n </FeedbackModal>\n </>\n );\n};\n\nexport const FeedbackProvider = ({ children, config }: { children: ReactNode, config: FeedbackConfig }) => {\n const [isOpen, setIsOpen] = useState(false);\n const [stage, setStage] = useState<FeedbackStage>('IDLE');\n const [screenshot, setScreenshot] = useState<string | null>(null);\n\n const { capture } = useScreenshot();\n\n const finalConfig = useMemo((): FeedbackConfig => ({\n position: 'top-right',\n primaryColor: '#dc2626',\n labelText: 'FEEDBACK',\n enableKeyboard: true,\n enableAnnotation: false,\n keyboardShortcut: 'ctrl+shift+f',\n ...config\n }), [config]);\n\n const handleStartCapture = useCallback(async () => {\n setIsOpen(true);\n setStage('CAPTURING');\n const img = await capture();\n if (img) {\n setScreenshot(img);\n setStage('ANNOTATING');\n }\n }, [capture]);\n\n const value: FeedbackContextValue = useMemo(() => ({\n isOpen,\n setIsOpen,\n stage,\n setStage,\n screenshot,\n setScreenshot,\n config: finalConfig,\n triggerFeedback: handleStartCapture\n }), [isOpen, stage, screenshot, finalConfig, handleStartCapture]);\n\n return (\n <FeedbackContext.Provider value={value}>\n {children}\n <FeedbackUI />\n </FeedbackContext.Provider>\n );\n};","import React, { useEffect } from \"react\";\nimport { useFeedback } from \"../hooks/useFeedback\";\n\nexport const FeedbackRibbon = () => {\n const { config, triggerFeedback } = useFeedback();\n\n const getPositionStyles = (): React.CSSProperties => {\n const base: React.CSSProperties = {\n position: 'fixed',\n padding: '8px 40px',\n cursor: 'pointer',\n color: 'white',\n fontWeight: 'bold',\n fontSize: '14px',\n zIndex: 999999,\n boxShadow: '0 4px 12px rgba(0,0,0,0.15)',\n userSelect: 'none',\n transition: 'filter 0.2s',\n backgroundColor: config.primaryColor || '#dc2626',\n };\n\n switch (config.position) {\n case 'top-left':\n return { ...base, top: '25px', left: '-35px', transform: 'rotate(-45deg)' };\n case 'bottom-right':\n return { ...base, bottom: '25px', right: '-35px', transform: 'rotate(-45deg)' };\n case 'bottom-left':\n return { ...base, bottom: '25px', left: '-35px', transform: 'rotate(45deg)' };\n case 'top-right':\n default:\n return { ...base, top: '25px', right: '-35px', transform: 'rotate(45deg)' };\n }\n };\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const shortcut = (config.keyboardShortcut || 'ctrl+shift+f').toLowerCase();\n const keys = shortcut.split('+');\n \n const ctrlReq = keys.includes('ctrl');\n const shiftReq = keys.includes('shift');\n const altReq = keys.includes('alt');\n const targetKey = keys[keys.length - 1];\n\n if (\n e.ctrlKey === ctrlReq &&\n e.shiftKey === shiftReq &&\n e.altKey === altReq &&\n e.key.toLowerCase() === targetKey\n ) {\n e.preventDefault();\n triggerFeedback();\n }\n };\n\n if (config.enableKeyboard !== false) {\n window.addEventListener('keydown', handleKeyDown);\n }\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [config, triggerFeedback]);\n\n return (\n <div\n data-html2canvas-ignore=\"true\" \n onClick={triggerFeedback}\n style={getPositionStyles()}\n onMouseEnter={(e) => (e.currentTarget.style.filter = 'brightness(1.1)')}\n onMouseLeave={(e) => (e.currentTarget.style.filter = 'brightness(1.0)')}\n >\n {config.labelText || 'FEEDBACK'}\n </div>\n );\n};","import { useContext } from 'react';\nimport { FeedbackContext } from '../context/FeedbackProvider';\n\nexport const useFeedback = () => {\n const context = useContext(FeedbackContext);\n \n if (!context) {\n throw new Error(\n 'useFeedback must be used within a FeedbackProvider. ' +\n 'Check if you wrapped your root component.'\n );\n }\n \n return context;\n};","import React from 'react';\nimport { Modal, Box, Typography, IconButton, Divider } from '@mui/material';\nimport CloseIcon from '@mui/icons-material/Close';\nimport { useFeedback } from '../hooks/useFeedback';\n\ninterface ModalProps {\n title: string;\n children: React.ReactNode;\n}\n\nconst style = {\n position: 'absolute' as const,\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n width: '90%',\n maxWidth: 900,\n maxHeight: '90vh',\n bgcolor: 'background.paper',\n borderRadius: 4,\n boxShadow: 24,\n display: 'flex',\n flexDirection: 'column',\n outline: 'none',\n overflow: 'hidden'\n};\n\nexport const FeedbackModal = ({ title, children }: ModalProps) => {\n const { isOpen, setIsOpen, setStage } = useFeedback();\n\n const handleClose = () => {\n setIsOpen(false);\n setStage('IDLE');\n };\n\n return (\n <Modal\n open={isOpen}\n onClose={handleClose}\n aria-labelledby=\"feedback-modal-title\"\n closeAfterTransition\n slotProps={{\n backdrop: {\n sx: { backdropFilter: 'blur(4px)', backgroundColor: 'rgba(0,0,0,0.6)' }\n }\n }}\n >\n <Box sx={style}>\n <Box sx={{ p: 2, px: 3, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <Typography id=\"feedback-modal-title\" variant=\"h6\" component=\"h2\" fontWeight=\"bold\">\n {title}\n </Typography>\n <IconButton onClick={handleClose} size=\"small\" aria-label=\"close\">\n <CloseIcon />\n </IconButton>\n </Box>\n\n <Divider />\n <Box sx={{ p: 3, overflowY: 'auto', flex: 1 }}>\n {children}\n </Box>\n </Box>\n </Modal>\n );\n};","import React, { useRef, useState, useEffect } from 'react';\nimport { Box, Button } from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\n\nexport const FeedbackCanvas = () => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [isDrawing, setIsDrawing] = useState(false);\n const { screenshot, setScreenshot, setStage, config } = useFeedback();\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || !screenshot) return;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const img = new Image() as HTMLImageElement;\n img.onload = () => {\n canvas.width = img.width;\n canvas.height = img.height;\n ctx.drawImage(img, 0, 0);\n ctx.strokeStyle = config.primaryColor || '#ff0000';\n ctx.lineWidth = 5;\n ctx.lineCap = 'round';\n };\n img.src = screenshot;\n }, [screenshot, config.primaryColor]);\n\n const getCoordinates = (e: React.MouseEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return { x: 0, y: 0 };\n const rect = canvas.getBoundingClientRect();\n return {\n x: (e.clientX - rect.left) * (canvas.width / rect.width),\n y: (e.clientY - rect.top) * (canvas.height / rect.height)\n };\n };\n\n const handleConfirm = () => {\n const dataUrl = canvasRef.current?.toDataURL('image/png');\n if (dataUrl) setScreenshot(dataUrl);\n setStage('FORM');\n };\n\n return (\n <Box sx={{ width: '100%', position: 'relative' }}>\n <Box sx={{ \n width: '100%', \n bgcolor: 'grey.100', \n borderRadius: 2, \n overflow: 'hidden',\n border: '1px solid',\n borderColor: 'divider'\n }}>\n <canvas\n ref={canvasRef}\n onMouseDown={(e) => {\n const ctx = canvasRef.current?.getContext('2d');\n const { x, y } = getCoordinates(e);\n ctx?.beginPath();\n ctx?.moveTo(x, y);\n setIsDrawing(true);\n }}\n onMouseMove={(e) => {\n if (!isDrawing) return;\n const ctx = canvasRef.current?.getContext('2d');\n const { x, y } = getCoordinates(e);\n ctx?.lineTo(x, y);\n ctx?.stroke();\n }}\n onMouseUp={() => setIsDrawing(false)}\n style={{ display: 'block', maxWidth: '100%', cursor: 'crosshair' }}\n />\n </Box>\n \n <Box sx={{ mt: 2, display: 'flex', justifyContent: 'flex-end' }}>\n <Button \n variant=\"contained\" \n onClick={handleConfirm}\n sx={{ bgcolor: config.primaryColor }}\n >\n Confirm Annotations\n </Button>\n </Box>\n </Box>\n );\n};","import React, { useState } from 'react';\nimport { \n Box, \n TextField, \n MenuItem, \n Button, \n CircularProgress,\n Stack \n} from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\nimport { FeedbackField } from '../types';\n\nexport const FeedbackForm = () => {\n const { config, setStage, setIsOpen, screenshot } = useFeedback();\n const [loading, setLoading] = useState(false);\n const [formData, setFormData] = useState<Record<string, any>>({});\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setLoading(true);\n \n try {\n const payload = { formData, screenshot };\n await config.onSubmit(payload);\n setIsOpen(false);\n } catch (error) {\n console.error(\"Submission failed\", error);\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <Box component=\"form\" onSubmit={handleSubmit}>\n <Stack spacing={3}>\n {config.fields.map((field: FeedbackField) => (\n <TextField\n key={field.id}\n label={field.label}\n required={field.required}\n select={field.type === 'select'}\n multiline={field.type === 'textarea'}\n rows={field.type === 'textarea' ? 4 : 1}\n fullWidth\n variant=\"outlined\"\n onChange={(e) => setFormData({ ...formData, [field.id]: e.target.value })}\n >\n {field.type === 'select' && field.options?.map((opt) => (\n <MenuItem key={opt} value={opt}>{opt}</MenuItem>\n ))}\n </TextField>\n ))}\n\n <Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mt: 2 }}>\n <Button \n variant=\"text\" \n onClick={() => {\n if (config.enableAnnotation) {\n setStage('ANNOTATING');\n } else {\n setStage('IDLE');\n setIsOpen(false);\n }\n }}\n disabled={loading}\n >\n Back\n </Button>\n <Button \n type=\"submit\" \n variant=\"contained\" \n disabled={loading}\n sx={{ bgcolor: config.primaryColor, '&:hover': { opacity: 0.9 } }}\n startIcon={loading && <CircularProgress size={20} color=\"inherit\" />}\n >\n {loading ? 'Sending...' : 'Submit Feedback'}\n </Button>\n </Box>\n </Stack>\n </Box>\n );\n};","import { useState } from 'react';\nimport html2canvas from 'html2canvas';\nimport { Base64Image } from '../types';\n\nexport const useScreenshot = () => {\n const [image, setImage] = useState<Base64Image | null>(null);\n const [isCapturing, setIsCapturing] = useState(false);\n\n const capture = async (element: HTMLElement = document.body) => {\n setIsCapturing(true);\n try {\n const canvas = await html2canvas(element, {\n useCORS: true,\n logging: false,\n allowTaint: true,\n backgroundColor: null,\n });\n const base64 = canvas.toDataURL('image/png');\n setImage(base64);\n return base64;\n } catch (err) {\n console.error(\"Failed to capture screenshot:\", err);\n } finally {\n setIsCapturing(false);\n }\n };\n\n return { capture, image, isCapturing, setImage };\n};"],"mappings":"AAAA,OAAS,iBAAAA,GAAe,YAAAC,EAAqB,WAAAC,EAAS,eAAAC,OAAmB,QCAzE,OAAgB,aAAAC,MAAiB,QCAjC,OAAS,cAAAC,MAAkB,QAGpB,IAAMC,EAAc,IAAM,CAC/B,IAAMC,EAAUC,EAAWC,CAAe,EAE1C,GAAI,CAACF,EACH,MAAM,IAAI,MACR,+FAEF,EAGF,OAAOA,CACT,EDgDI,cAAAG,MAAA,oBA3DG,IAAMC,EAAiB,IAAM,CAClC,GAAM,CAAE,OAAAC,EAAQ,gBAAAC,CAAgB,EAAIC,EAAY,EAE1CC,EAAoB,IAA2B,CACnD,IAAMC,EAA4B,CAChC,SAAU,QACV,QAAS,WACT,OAAQ,UACR,MAAO,QACP,WAAY,OACZ,SAAU,OACV,OAAQ,OACR,UAAW,8BACX,WAAY,OACZ,WAAY,cACZ,gBAAiBJ,EAAO,cAAgB,SAC1C,EAEA,OAAQA,EAAO,SAAU,CACvB,IAAK,WACH,MAAO,CAAE,GAAGI,EAAM,IAAK,OAAQ,KAAM,QAAS,UAAW,gBAAiB,EAC5E,IAAK,eACH,MAAO,CAAE,GAAGA,EAAM,OAAQ,OAAQ,MAAO,QAAS,UAAW,gBAAiB,EAChF,IAAK,cACH,MAAO,CAAE,GAAGA,EAAM,OAAQ,OAAQ,KAAM,QAAS,UAAW,eAAgB,EAE9E,QACE,MAAO,CAAE,GAAGA,EAAM,IAAK,OAAQ,MAAO,QAAS,UAAW,eAAgB,CAC9E,CACF,EAEA,OAAAC,EAAU,IAAM,CACd,IAAMC,EAAiBC,GAAqB,CAE1C,IAAMC,GADYR,EAAO,kBAAoB,gBAAgB,YAAY,EACnD,MAAM,GAAG,EAEzBS,EAAUD,EAAK,SAAS,MAAM,EAC9BE,EAAWF,EAAK,SAAS,OAAO,EAChCG,EAASH,EAAK,SAAS,KAAK,EAC5BI,EAAYJ,EAAKA,EAAK,OAAS,CAAC,EAGpCD,EAAE,UAAYE,GACdF,EAAE,WAAaG,GACfH,EAAE,SAAWI,GACbJ,EAAE,IAAI,YAAY,IAAMK,IAExBL,EAAE,eAAe,EACjBN,EAAgB,EAEpB,EAEA,OAAID,EAAO,iBAAmB,IAC5B,OAAO,iBAAiB,UAAWM,CAAa,EAE3C,IAAM,OAAO,oBAAoB,UAAWA,CAAa,CAClE,EAAG,CAACN,EAAQC,CAAe,CAAC,EAG1BH,EAAC,OACC,0BAAwB,OACxB,QAASG,EACT,MAAOE,EAAkB,EACzB,aAAeI,GAAOA,EAAE,cAAc,MAAM,OAAS,kBACrD,aAAeA,GAAOA,EAAE,cAAc,MAAM,OAAS,kBAEpD,SAAAP,EAAO,WAAa,WACvB,CAEJ,EEvEA,OAAS,SAAAa,EAAO,OAAAC,EAAK,cAAAC,EAAY,cAAAC,EAAY,WAAAC,MAAe,gBAC5D,OAAOC,MAAe,4BA8Cd,OACE,OAAAC,EADF,QAAAC,MAAA,oBAtCR,IAAMC,EAAQ,CACZ,SAAU,WACV,IAAK,MACL,KAAM,MACN,UAAW,wBACX,MAAO,MACP,SAAU,IACV,UAAW,OACX,QAAS,mBACT,aAAc,EACd,UAAW,GACX,QAAS,OACT,cAAe,SACf,QAAS,OACT,SAAU,QACZ,EAEaC,EAAgB,CAAC,CAAE,MAAAC,EAAO,SAAAC,CAAS,IAAkB,CAChE,GAAM,CAAE,OAAAC,EAAQ,UAAAC,EAAW,SAAAC,CAAS,EAAIC,EAAY,EAE9CC,EAAc,IAAM,CACxBH,EAAU,EAAK,EACfC,EAAS,MAAM,CACjB,EAEA,OACER,EAACW,EAAA,CACC,KAAML,EACN,QAASI,EACT,kBAAgB,uBAChB,qBAAoB,GACpB,UAAW,CACT,SAAU,CACR,GAAI,CAAE,eAAgB,YAAa,gBAAiB,iBAAkB,CACxE,CACF,EAEA,SAAAT,EAACW,EAAA,CAAI,GAAIV,EACP,UAAAD,EAACW,EAAA,CAAI,GAAI,CAAE,EAAG,EAAG,GAAI,EAAG,QAAS,OAAQ,WAAY,SAAU,eAAgB,eAAgB,EAC7F,UAAAZ,EAACa,EAAA,CAAW,GAAG,uBAAuB,QAAQ,KAAK,UAAU,KAAK,WAAW,OAC1E,SAAAT,EACH,EACAJ,EAACc,EAAA,CAAW,QAASJ,EAAa,KAAK,QAAQ,aAAW,QACxD,SAAAV,EAACe,EAAA,EAAU,EACb,GACF,EAEAf,EAACgB,EAAA,EAAQ,EACThB,EAACY,EAAA,CAAI,GAAI,CAAE,EAAG,EAAG,UAAW,OAAQ,KAAM,CAAE,EACzC,SAAAP,EACH,GACF,EACF,CAEJ,EChEA,OAAgB,UAAAY,EAAQ,YAAAC,EAAU,aAAAC,MAAiB,QACnD,OAAS,OAAAC,EAAK,UAAAC,MAAc,gBA2CxB,OASI,OAAAC,EATJ,QAAAC,MAAA,oBAxCG,IAAMC,EAAiB,IAAM,CAClC,IAAMC,EAAYC,EAA0B,IAAI,EAC1C,CAACC,EAAWC,CAAY,EAAIC,EAAS,EAAK,EAC1C,CAAE,WAAAC,EAAY,cAAAC,EAAe,SAAAC,EAAU,OAAAC,CAAO,EAAIC,EAAY,EAEpEC,EAAU,IAAM,CACd,IAAMC,EAASX,EAAU,QACzB,GAAI,CAACW,GAAU,CAACN,EAAY,OAC5B,IAAMO,EAAMD,EAAO,WAAW,IAAI,EAClC,GAAI,CAACC,EAAK,OAEV,IAAMC,EAAM,IAAI,MACdA,EAAI,OAAS,IAAM,CACjBF,EAAO,MAAQE,EAAI,MACnBF,EAAO,OAASE,EAAI,OACpBD,EAAI,UAAUC,EAAK,EAAG,CAAC,EACvBD,EAAI,YAAcJ,EAAO,cAAgB,UACzCI,EAAI,UAAY,EAChBA,EAAI,QAAU,OAChB,EACAC,EAAI,IAAMR,CACZ,EAAG,CAACA,EAAYG,EAAO,YAAY,CAAC,EAEtC,IAAMM,EAAkBC,GAAwB,CAC9C,IAAMJ,EAASX,EAAU,QACzB,GAAI,CAACW,EAAQ,MAAO,CAAE,EAAG,EAAG,EAAG,CAAE,EACjC,IAAMK,EAAOL,EAAO,sBAAsB,EAC1C,MAAO,CACL,GAAII,EAAE,QAAUC,EAAK,OAASL,EAAO,MAAQK,EAAK,OAClD,GAAID,EAAE,QAAUC,EAAK,MAAQL,EAAO,OAASK,EAAK,OACpD,CACF,EAQA,OACElB,EAACmB,EAAA,CAAI,GAAI,CAAE,MAAO,OAAQ,SAAU,UAAW,EAC7C,UAAApB,EAACoB,EAAA,CAAI,GAAI,CACP,MAAO,OACP,QAAS,WACT,aAAc,EACd,SAAU,SACV,OAAQ,YACR,YAAa,SACf,EACE,SAAApB,EAAC,UACC,IAAKG,EACL,YAAce,GAAM,CAClB,IAAMH,EAAMZ,EAAU,SAAS,WAAW,IAAI,EACxC,CAAE,EAAAkB,EAAG,EAAAC,CAAE,EAAIL,EAAeC,CAAC,EACjCH,GAAK,UAAU,EACfA,GAAK,OAAOM,EAAGC,CAAC,EAChBhB,EAAa,EAAI,CACnB,EACA,YAAcY,GAAM,CAClB,GAAI,CAACb,EAAW,OAChB,IAAMU,EAAMZ,EAAU,SAAS,WAAW,IAAI,EACxC,CAAE,EAAAkB,EAAG,EAAAC,CAAE,EAAIL,EAAeC,CAAC,EACjCH,GAAK,OAAOM,EAAGC,CAAC,EAChBP,GAAK,OAAO,CACd,EACA,UAAW,IAAMT,EAAa,EAAK,EACnC,MAAO,CAAE,QAAS,QAAS,SAAU,OAAQ,OAAQ,WAAY,EACnE,EACF,EAEAN,EAACoB,EAAA,CAAI,GAAI,CAAE,GAAI,EAAG,QAAS,OAAQ,eAAgB,UAAW,EAC5D,SAAApB,EAACuB,EAAA,CACC,QAAQ,YACR,QAxCc,IAAM,CAC1B,IAAMC,EAAUrB,EAAU,SAAS,UAAU,WAAW,EACpDqB,GAASf,EAAce,CAAO,EAClCd,EAAS,MAAM,CACjB,EAqCQ,GAAI,CAAE,QAASC,EAAO,YAAa,EACpC,+BAED,EACF,GACF,CAEJ,ECrFA,OAAgB,YAAAc,MAAgB,QAChC,OACE,OAAAC,EACA,aAAAC,EACA,YAAAC,EACA,UAAAC,EACA,oBAAAC,EACA,SAAAC,MACK,gBAwCO,cAAAC,EAKN,QAAAC,MALM,oBApCP,IAAMC,EAAe,IAAM,CAChC,GAAM,CAAE,OAAAC,EAAQ,SAAAC,EAAU,UAAAC,EAAW,WAAAC,CAAW,EAAIC,EAAY,EAC1D,CAACC,EAASC,CAAU,EAAIC,EAAS,EAAK,EACtC,CAACC,EAAUC,CAAW,EAAIF,EAA8B,CAAC,CAAC,EAiBhE,OACEV,EAACa,EAAA,CAAI,UAAU,OAAO,SAhBH,MAAOC,GAAuB,CACjDA,EAAE,eAAe,EACjBL,EAAW,EAAI,EAEf,GAAI,CACF,IAAMM,EAAU,CAAE,SAAAJ,EAAU,WAAAL,CAAW,EACvC,MAAMH,EAAO,SAASY,CAAO,EAC7BV,EAAU,EAAK,CACjB,OAASW,EAAO,CACd,QAAQ,MAAM,oBAAqBA,CAAK,CAC1C,QAAE,CACAP,EAAW,EAAK,CAClB,CACF,EAII,SAAAR,EAACgB,EAAA,CAAM,QAAS,EACb,UAAAd,EAAO,OAAO,IAAKe,GAClBlB,EAACmB,EAAA,CAEC,MAAOD,EAAM,MACb,SAAUA,EAAM,SAChB,OAAQA,EAAM,OAAS,SACvB,UAAWA,EAAM,OAAS,WAC1B,KAAMA,EAAM,OAAS,WAAa,EAAI,EACtC,UAAS,GACT,QAAQ,WACR,SAAWJ,GAAMF,EAAY,CAAE,GAAGD,EAAU,CAACO,EAAM,EAAE,EAAGJ,EAAE,OAAO,KAAM,CAAC,EAEvE,SAAAI,EAAM,OAAS,UAAYA,EAAM,SAAS,IAAKE,GAC9CpB,EAACqB,EAAA,CAAmB,MAAOD,EAAM,SAAAA,GAAlBA,CAAsB,CACtC,GAZIF,EAAM,EAab,CACD,EAEDjB,EAACY,EAAA,CAAI,GAAI,CAAE,QAAS,OAAQ,eAAgB,WAAY,IAAK,EAAG,GAAI,CAAE,EACpE,UAAAb,EAACsB,EAAA,CACC,QAAQ,OACR,QAAS,IAAM,CACTnB,EAAO,iBACTC,EAAS,YAAY,GAErBA,EAAS,MAAM,EACfC,EAAU,EAAK,EAEnB,EACA,SAAUG,EACX,gBAEC,EACFR,EAACsB,EAAA,CACC,KAAK,SACL,QAAQ,YACR,SAAUd,EACV,GAAI,CAAE,QAASL,EAAO,aAAc,UAAW,CAAE,QAAS,EAAI,CAAE,EAChE,UAAWK,GAAWR,EAACuB,EAAA,CAAiB,KAAM,GAAI,MAAM,UAAU,EAEjE,SAAAf,EAAU,aAAe,kBAC5B,GACF,GACF,EACF,CAEJ,EL5EA,OAAS,OAAAgB,GAAK,oBAAAC,OAAwB,gBMLtC,OAAS,YAAAC,MAAgB,QACzB,OAAOC,MAAiB,cAGjB,IAAMC,EAAgB,IAAM,CACjC,GAAM,CAACC,EAAOC,CAAQ,EAAIJ,EAA6B,IAAI,EACrD,CAACK,EAAaC,CAAc,EAAIN,EAAS,EAAK,EAqBpD,MAAO,CAAE,QAnBO,MAAOO,EAAuB,SAAS,OAAS,CAC9DD,EAAe,EAAI,EACnB,GAAI,CAOF,IAAME,GANS,MAAMP,EAAYM,EAAS,CACxC,QAAS,GACT,QAAS,GACT,WAAY,GACZ,gBAAiB,IACnB,CAAC,GACqB,UAAU,WAAW,EAC3C,OAAAH,EAASI,CAAM,EACRA,CACT,OAASC,EAAK,CACZ,QAAQ,MAAM,gCAAiCA,CAAG,CACpD,QAAE,CACAH,EAAe,EAAK,CACtB,CACF,EAEkB,MAAAH,EAAO,YAAAE,EAAa,SAAAD,CAAS,CACjD,ENQI,mBAAAM,GACE,OAAAC,EACA,QAAAC,MAFF,oBAfG,IAAMC,EAAkBC,GAA2C,IAAI,EAEjEC,GAAa,IAAM,CAC9B,GAAM,CAAE,MAAAC,EAAO,OAAAC,CAAO,EAAIC,EAAY,EAWtC,OACEN,EAAAF,GAAA,CACE,UAAAC,EAACQ,EAAA,EAAe,EAChBP,EAACQ,EAAA,CAAc,OAZF,IAAM,CACrB,OAAQJ,EAAO,CACb,IAAK,YAAa,MAAO,uBACzB,IAAK,aAAc,OAAQC,EAAO,iBAAwC,gBAArB,mBACrD,IAAK,OAAQ,MAAO,gBACpB,QAAS,MAAO,UAClB,CACF,GAKmC,EAC5B,UAAAD,IAAU,aACTL,EAACU,GAAA,CAAI,GAAI,CAAE,QAAS,OAAQ,eAAgB,SAAU,EAAG,CAAE,EACzD,SAAAV,EAACW,GAAA,EAAiB,EACpB,EAEDN,IAAU,cAAgB,CAAC,CAACC,EAAO,kBAClCN,EAACY,EAAA,EAAe,GAEhBP,IAAU,QAAWA,IAAU,cAAgB,CAACC,EAAO,mBACvDN,EAACa,EAAA,EAAa,GAElB,GACF,CAEJ,EAEaC,GAAmB,CAAC,CAAE,SAAAC,EAAU,OAAAT,CAAO,IAAuD,CACzG,GAAM,CAACU,EAAQC,CAAS,EAAIC,EAAS,EAAK,EACpC,CAACb,EAAOc,CAAQ,EAAID,EAAwB,MAAM,EAClD,CAACE,EAAYC,CAAa,EAAIH,EAAwB,IAAI,EAE1D,CAAE,QAAAI,CAAQ,EAAIC,EAAc,EAE5BC,EAAcC,EAAQ,KAAuB,CACjD,SAAU,YACV,aAAc,UACd,UAAW,WACX,eAAgB,GAChB,iBAAkB,GAClB,iBAAkB,eAClB,GAAGnB,CACL,GAAI,CAACA,CAAM,CAAC,EAENoB,EAAqBC,GAAY,SAAY,CACjDV,EAAU,EAAI,EACdE,EAAS,WAAW,EACpB,IAAMS,EAAM,MAAMN,EAAQ,EACtBM,IACFP,EAAcO,CAAG,EACjBT,EAAS,YAAY,EAEzB,EAAG,CAACG,CAAO,CAAC,EAEJO,EAA8BJ,EAAQ,KAAO,CACjD,OAAAT,EACA,UAAAC,EACA,MAAAZ,EACA,SAAAc,EACA,WAAAC,EACA,cAAAC,EACA,OAAQG,EACR,gBAAiBE,CACnB,GAAI,CAACV,EAAQX,EAAOe,EAAYI,EAAaE,CAAkB,CAAC,EAElE,OACEzB,EAACC,EAAgB,SAAhB,CAAyB,MAAO2B,EAC9B,UAAAd,EACDf,EAACI,GAAA,EAAW,GACd,CAEJ","names":["createContext","useState","useMemo","useCallback","useEffect","useContext","useFeedback","context","useContext","FeedbackContext","jsx","FeedbackRibbon","config","triggerFeedback","useFeedback","getPositionStyles","base","useEffect","handleKeyDown","e","keys","ctrlReq","shiftReq","altReq","targetKey","Modal","Box","Typography","IconButton","Divider","CloseIcon","jsx","jsxs","style","FeedbackModal","title","children","isOpen","setIsOpen","setStage","useFeedback","handleClose","Modal","Box","Typography","IconButton","CloseIcon","Divider","useRef","useState","useEffect","Box","Button","jsx","jsxs","FeedbackCanvas","canvasRef","useRef","isDrawing","setIsDrawing","useState","screenshot","setScreenshot","setStage","config","useFeedback","useEffect","canvas","ctx","img","getCoordinates","e","rect","Box","x","y","Button","dataUrl","useState","Box","TextField","MenuItem","Button","CircularProgress","Stack","jsx","jsxs","FeedbackForm","config","setStage","setIsOpen","screenshot","useFeedback","loading","setLoading","useState","formData","setFormData","Box","e","payload","error","Stack","field","TextField","opt","MenuItem","Button","CircularProgress","Box","CircularProgress","useState","html2canvas","useScreenshot","image","setImage","isCapturing","setIsCapturing","element","base64","err","Fragment","jsx","jsxs","FeedbackContext","createContext","FeedbackUI","stage","config","useFeedback","FeedbackRibbon","FeedbackModal","Box","CircularProgress","FeedbackCanvas","FeedbackForm","FeedbackProvider","children","isOpen","setIsOpen","useState","setStage","screenshot","setScreenshot","capture","useScreenshot","finalConfig","useMemo","handleStartCapture","useCallback","img","value"]}
|
|
1
|
+
{"version":3,"sources":["../src/context/FeedbackProvider.tsx","../src/components/FeedbackRibbon.tsx","../src/hooks/useFeedback.tsx","../src/components/FeedbackModal.tsx","../src/components/FeedbackCanvas.tsx","../src/components/FeedbackForm.tsx","../src/hooks/useScreenshot.tsx"],"sourcesContent":["import { createContext, useState, ReactNode, useMemo, useCallback } from 'react';\nimport { FeedbackRibbon } from '../components/FeedbackRibbon';\nimport { FeedbackModal } from '../components/FeedbackModal';\nimport { FeedbackCanvas } from '../components/FeedbackCanvas';\nimport { FeedbackForm } from \"../components/FeedbackForm\";\nimport { Box, CircularProgress } from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\nimport { useScreenshot } from '../hooks/useScreenshot';\nimport { Base64Image, FeedbackConfig, FeedbackStage } from '../types';\n\nexport interface FeedbackContextValue {\n isOpen: boolean;\n setIsOpen: (open: boolean) => void;\n stage: FeedbackStage;\n setStage: (stage: FeedbackStage) => void;\n screenshot: Base64Image;\n setScreenshot: (img: string | null) => void;\n config: FeedbackConfig;\n triggerFeedback: () => Promise<void>;\n}\n\nexport const FeedbackContext = createContext<FeedbackContextValue | null>(null);\n\nexport const FeedbackUI = () => {\n const { stage, config } = useFeedback();\n\n if (config.modalTitle) return config.modalTitle;\n\n const getTitle = () => {\n switch (stage) {\n case 'CAPTURING': return 'Taking Screenshot...';\n case 'ANNOTATING': return config.enableAnnotation ? 'Highlight Issues' : 'Send Feedback';\n case 'FORM': return 'Send Feedback';\n default: return 'Feedback';\n }\n };\n\n return (\n <>\n <FeedbackRibbon />\n <FeedbackModal title={getTitle()}>\n {stage === 'CAPTURING' && (\n <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>\n <CircularProgress />\n </Box>\n )}\n {stage === 'ANNOTATING' && config.enableAnnotation && <FeedbackCanvas />}\n {(stage === 'FORM' || (stage === 'ANNOTATING' && !config.enableAnnotation)) && (\n <FeedbackForm />\n )}\n </FeedbackModal>\n </>\n );\n};\n\nexport const FeedbackProvider = ({ children, config }: { children: ReactNode, config: FeedbackConfig }) => {\n const [isOpen, setIsOpen] = useState(false);\n const [stage, setStage] = useState<FeedbackStage>('IDLE');\n const [screenshot, setScreenshot] = useState<string | null>(null);\n\n const { capture } = useScreenshot();\n\n const finalConfig = useMemo((): FeedbackConfig => ({\n position: 'top-right',\n primaryColor: '#dc2626',\n ribbonLabel: 'Feedback',\n modalTitle: undefined,\n enableKeyboard: true,\n enableAnnotation: false,\n keyboardShortcut: 'ctrl+shift+f',\n ...config\n }), [config]);\n\n const handleStartCapture = useCallback(async () => {\n setIsOpen(true);\n \n if (finalConfig.enableAnnotation) {\n setStage('CAPTURING');\n try {\n const img = await capture();\n if (img) {\n setScreenshot(img);\n setStage('ANNOTATING');\n return;\n }\n } catch (error) {\n console.warn('Screenshot failed, skipping to form:', error);\n }\n }\n setStage('FORM');\n }, [capture, finalConfig.enableAnnotation]);\n\n const value: FeedbackContextValue = useMemo(() => ({\n isOpen,\n setIsOpen,\n stage,\n setStage,\n screenshot,\n setScreenshot,\n config: finalConfig,\n triggerFeedback: handleStartCapture\n }), [isOpen, stage, screenshot, finalConfig, handleStartCapture]);\n\n return (\n <FeedbackContext.Provider value={value}>\n {children}\n <FeedbackUI />\n </FeedbackContext.Provider>\n );\n};","import React, { useEffect } from \"react\";\nimport { useFeedback } from \"../hooks/useFeedback\";\n\nexport const FeedbackRibbon = () => {\n const { config, triggerFeedback } = useFeedback();\n\n const getPositionStyles = (): React.CSSProperties => {\n const base: React.CSSProperties = {\n position: 'fixed',\n padding: '8px 40px',\n cursor: 'pointer',\n color: 'white',\n fontWeight: 'bold',\n fontSize: '14px',\n zIndex: 999999,\n boxShadow: '0 4px 12px rgba(0,0,0,0.15)',\n userSelect: 'none',\n transition: 'filter 0.2s',\n backgroundColor: config.primaryColor || '#dc2626',\n };\n\n switch (config.position) {\n case 'top-left':\n return { ...base, top: '25px', left: '-35px', transform: 'rotate(-45deg)' };\n case 'bottom-right':\n return { ...base, bottom: '25px', right: '-35px', transform: 'rotate(-45deg)' };\n case 'bottom-left':\n return { ...base, bottom: '25px', left: '-35px', transform: 'rotate(45deg)' };\n case 'top-right':\n default:\n return { ...base, top: '25px', right: '-35px', transform: 'rotate(45deg)' };\n }\n };\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const shortcut = (config.keyboardShortcut || 'ctrl+shift+f').toLowerCase();\n const keys = shortcut.split('+');\n \n const ctrlReq = keys.includes('ctrl');\n const shiftReq = keys.includes('shift');\n const altReq = keys.includes('alt');\n const targetKey = keys[keys.length - 1];\n\n if (\n e.ctrlKey === ctrlReq &&\n e.shiftKey === shiftReq &&\n e.altKey === altReq &&\n e.key.toLowerCase() === targetKey\n ) {\n e.preventDefault();\n triggerFeedback();\n }\n };\n\n if (config.enableKeyboard !== false) {\n window.addEventListener('keydown', handleKeyDown);\n }\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [config, triggerFeedback]);\n\n return (\n <div\n data-html2canvas-ignore=\"true\" \n onClick={triggerFeedback}\n style={getPositionStyles()}\n onMouseEnter={(e) => (e.currentTarget.style.filter = 'brightness(1.1)')}\n onMouseLeave={(e) => (e.currentTarget.style.filter = 'brightness(1.0)')}\n >\n {config.ribbonLabel || 'FEEDBACK'}\n </div>\n );\n};","import { useContext } from 'react';\nimport { FeedbackContext } from '../context/FeedbackProvider';\n\nexport const useFeedback = () => {\n const context = useContext(FeedbackContext);\n \n if (!context) {\n throw new Error(\n 'useFeedback must be used within a FeedbackProvider. ' +\n 'Check if you wrapped your root component.'\n );\n }\n \n return context;\n};","import React from 'react';\nimport { Modal, Box, Typography, IconButton, Divider } from '@mui/material';\nimport CloseIcon from '@mui/icons-material/Close';\nimport { useFeedback } from '../hooks/useFeedback';\n\ninterface ModalProps {\n title: string;\n children: React.ReactNode;\n}\n\nconst style = {\n position: 'absolute' as const,\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n width: '90%',\n maxWidth: 900,\n maxHeight: '90vh',\n bgcolor: 'background.paper',\n borderRadius: 4,\n boxShadow: 24,\n display: 'flex',\n flexDirection: 'column',\n outline: 'none',\n overflow: 'hidden'\n};\n\nexport const FeedbackModal = ({ title, children }: ModalProps) => {\n const { isOpen, setIsOpen, setStage } = useFeedback();\n\n const handleClose = () => {\n setIsOpen(false);\n setStage('IDLE');\n };\n\n return (\n <Modal\n open={isOpen}\n onClose={handleClose}\n aria-labelledby=\"feedback-modal-title\"\n closeAfterTransition\n slotProps={{\n backdrop: {\n sx: { backdropFilter: 'blur(4px)', backgroundColor: 'rgba(0,0,0,0.6)' }\n }\n }}\n >\n <Box sx={style}>\n <Box sx={{ p: 2, px: 3, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <Typography id=\"feedback-modal-title\" variant=\"h6\" component=\"h2\" fontWeight=\"bold\">\n {title}\n </Typography>\n <IconButton onClick={handleClose} size=\"small\" aria-label=\"close\">\n <CloseIcon />\n </IconButton>\n </Box>\n\n <Divider />\n <Box sx={{ p: 3, overflowY: 'auto', flex: 1 }}>\n {children}\n </Box>\n </Box>\n </Modal>\n );\n};","import React, { useRef, useState, useEffect } from 'react';\nimport { Box, Button } from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\n\nexport const FeedbackCanvas = () => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [isDrawing, setIsDrawing] = useState(false);\n const { screenshot, setScreenshot, setStage, config } = useFeedback();\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || !screenshot) return;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const img = new Image() as HTMLImageElement;\n img.onload = () => {\n canvas.width = img.width;\n canvas.height = img.height;\n ctx.drawImage(img, 0, 0);\n ctx.strokeStyle = config.primaryColor || '#ff0000';\n ctx.lineWidth = 5;\n ctx.lineCap = 'round';\n };\n img.src = screenshot;\n }, [screenshot, config.primaryColor]);\n\n const getCoordinates = (e: React.MouseEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return { x: 0, y: 0 };\n const rect = canvas.getBoundingClientRect();\n return {\n x: (e.clientX - rect.left) * (canvas.width / rect.width),\n y: (e.clientY - rect.top) * (canvas.height / rect.height)\n };\n };\n\n const handleConfirm = () => {\n const dataUrl = canvasRef.current?.toDataURL('image/png');\n if (dataUrl) setScreenshot(dataUrl);\n setStage('FORM');\n };\n\n return (\n <Box sx={{ width: '100%', position: 'relative' }}>\n <Box sx={{ \n width: '100%', \n bgcolor: 'grey.100', \n borderRadius: 2, \n overflow: 'hidden',\n border: '1px solid',\n borderColor: 'divider'\n }}>\n <canvas\n ref={canvasRef}\n onMouseDown={(e) => {\n const ctx = canvasRef.current?.getContext('2d');\n const { x, y } = getCoordinates(e);\n ctx?.beginPath();\n ctx?.moveTo(x, y);\n setIsDrawing(true);\n }}\n onMouseMove={(e) => {\n if (!isDrawing) return;\n const ctx = canvasRef.current?.getContext('2d');\n const { x, y } = getCoordinates(e);\n ctx?.lineTo(x, y);\n ctx?.stroke();\n }}\n onMouseUp={() => setIsDrawing(false)}\n style={{ display: 'block', maxWidth: '100%', cursor: 'crosshair' }}\n />\n </Box>\n \n <Box sx={{ mt: 2, display: 'flex', justifyContent: 'flex-end' }}>\n <Button \n variant=\"contained\" \n onClick={handleConfirm}\n sx={{ bgcolor: config.primaryColor }}\n >\n Confirm Annotations\n </Button>\n </Box>\n </Box>\n );\n};","import React, { useState } from 'react';\nimport { \n Box, \n TextField, \n MenuItem, \n Button, \n CircularProgress,\n Stack \n} from '@mui/material';\nimport { useFeedback } from '../hooks/useFeedback';\nimport { FeedbackField } from '../types';\n\nexport const FeedbackForm = () => {\n const { config, setStage, setIsOpen, screenshot } = useFeedback();\n const [loading, setLoading] = useState(false);\n const [formData, setFormData] = useState<Record<string, any>>({});\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setLoading(true);\n \n try {\n const payload = { formData, screenshot };\n await config.onSubmit(payload);\n setIsOpen(false);\n } catch (error) {\n console.error(\"Submission failed\", error);\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <Box component=\"form\" onSubmit={handleSubmit}>\n <Stack spacing={3}>\n {config.fields.map((field: FeedbackField) => (\n <TextField\n key={field.id}\n label={field.label}\n required={field.required}\n select={field.type === 'select'}\n multiline={field.type === 'textarea'}\n rows={field.type === 'textarea' ? 4 : 1}\n fullWidth\n variant=\"outlined\"\n onChange={(e) => setFormData({ ...formData, [field.id]: e.target.value })}\n >\n {field.type === 'select' && field.options?.map((opt) => (\n <MenuItem key={opt} value={opt}>{opt}</MenuItem>\n ))}\n </TextField>\n ))}\n\n <Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mt: 2 }}>\n <Button \n variant=\"text\" \n onClick={() => {\n if (config.enableAnnotation) {\n setStage('ANNOTATING');\n } else {\n setStage('IDLE');\n setIsOpen(false);\n }\n }}\n disabled={loading}\n >\n Back\n </Button>\n <Button \n type=\"submit\" \n variant=\"contained\" \n disabled={loading}\n sx={{ bgcolor: config.primaryColor, '&:hover': { opacity: 0.9 } }}\n startIcon={loading && <CircularProgress size={20} color=\"inherit\" />}\n >\n {loading ? 'Sending...' : 'Submit Feedback'}\n </Button>\n </Box>\n </Stack>\n </Box>\n );\n};","import { useState } from 'react';\nimport html2canvas from 'html2canvas';\nimport { Base64Image } from '../types';\n\nexport const useScreenshot = () => {\n const [image, setImage] = useState<Base64Image | null>(null);\n const [isCapturing, setIsCapturing] = useState(false);\n\n const capture = async (element: HTMLElement = document.body) => {\n setIsCapturing(true);\n try {\n const canvas = await html2canvas(element, {\n useCORS: true,\n logging: false,\n allowTaint: true,\n backgroundColor: null,\n });\n const base64 = canvas.toDataURL('image/png');\n setImage(base64);\n return base64;\n } catch (err) {\n console.error(\"Failed to capture screenshot:\", err);\n } finally {\n setIsCapturing(false);\n }\n };\n\n return { capture, image, isCapturing, setImage };\n};"],"mappings":"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","names":["createContext","useState","useMemo","useCallback","useEffect","useContext","useFeedback","context","useContext","FeedbackContext","jsx","FeedbackRibbon","config","triggerFeedback","useFeedback","getPositionStyles","base","useEffect","handleKeyDown","e","keys","ctrlReq","shiftReq","altReq","targetKey","Modal","Box","Typography","IconButton","Divider","CloseIcon","jsx","jsxs","style","FeedbackModal","title","children","isOpen","setIsOpen","setStage","useFeedback","handleClose","Modal","Box","Typography","IconButton","CloseIcon","Divider","useRef","useState","useEffect","Box","Button","jsx","jsxs","FeedbackCanvas","canvasRef","useRef","isDrawing","setIsDrawing","useState","screenshot","setScreenshot","setStage","config","useFeedback","useEffect","canvas","ctx","img","getCoordinates","e","rect","Box","x","y","Button","dataUrl","useState","Box","TextField","MenuItem","Button","CircularProgress","Stack","jsx","jsxs","FeedbackForm","config","setStage","setIsOpen","screenshot","useFeedback","loading","setLoading","useState","formData","setFormData","Box","e","payload","error","Stack","field","TextField","opt","MenuItem","Button","CircularProgress","Box","CircularProgress","useState","html2canvas","useScreenshot","image","setImage","isCapturing","setIsCapturing","element","base64","err","Fragment","jsx","jsxs","FeedbackContext","createContext","FeedbackUI","stage","config","useFeedback","FeedbackRibbon","FeedbackModal","Box","CircularProgress","FeedbackCanvas","FeedbackForm","FeedbackProvider","children","isOpen","setIsOpen","useState","setStage","screenshot","setScreenshot","capture","useScreenshot","finalConfig","useMemo","handleStartCapture","useCallback","img","error","value"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skorm11x/dev-feedback-banner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A simple React feedback widget in the form of a banner with screenshot annotation, form submission, and more.",
|
|
5
5
|
"author": "skorm11x",
|
|
6
6
|
"repository": "https://github.com/skorm11x/dev-feedback-helper",
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
"build": "tsup src/index.ts --format cjs,esm --dts --minify --clean",
|
|
26
26
|
"dev": "tsup src/index.ts --format cjs,esm --watch --dts",
|
|
27
27
|
"lint": "eslint src",
|
|
28
|
+
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
29
|
+
"type-check": "tsc --noEmit",
|
|
30
|
+
"format": "prettier --write .",
|
|
31
|
+
"format:check": "prettier --check .",
|
|
32
|
+
"format:fix": "prettier --write .",
|
|
28
33
|
"clean": "rm -rf dist node_modules && rm package-lock.json"
|
|
29
34
|
},
|
|
30
35
|
"peerDependencies": {
|
|
@@ -45,6 +50,9 @@
|
|
|
45
50
|
"@types/react-dom": "^18.3.7",
|
|
46
51
|
"@typescript-eslint/eslint-plugin": "^8.56.0",
|
|
47
52
|
"eslint": "^10.0.0",
|
|
53
|
+
"eslint-config-prettier": "^10.1.8",
|
|
54
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
55
|
+
"prettier": "^3.8.1",
|
|
48
56
|
"tailwindcss": "^3.4.0",
|
|
49
57
|
"tsup": "^8.0.0",
|
|
50
58
|
"typescript": "^5.0.0"
|