gotcha-feedback 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gotcha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # gotcha-feedback
2
+
3
+ A developer-first contextual feedback SDK for React. Add a feedback button to any component with a single line of code.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install gotcha-feedback
9
+ # or
10
+ yarn add gotcha-feedback
11
+ # or
12
+ pnpm add gotcha-feedback
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```tsx
18
+ import { GotchaProvider, Gotcha } from 'gotcha-feedback';
19
+
20
+ function App() {
21
+ return (
22
+ <GotchaProvider apiKey="your-api-key">
23
+ <YourApp />
24
+ </GotchaProvider>
25
+ );
26
+ }
27
+
28
+ function FeatureCard() {
29
+ return (
30
+ <div style={{ position: 'relative' }}>
31
+ <h2>New Feature</h2>
32
+ <p>Check out this awesome new feature!</p>
33
+ <Gotcha elementId="feature-card" />
34
+ </div>
35
+ );
36
+ }
37
+ ```
38
+
39
+ ## Features
40
+
41
+ - **Feedback Mode** - Star rating + text input
42
+ - **Vote Mode** - Thumbs up/down
43
+ - **Customizable** - Themes, sizes, positions
44
+ - **Accessible** - Full keyboard navigation and screen reader support
45
+ - **Animated** - Smooth enter/exit animations with Framer Motion
46
+ - **Lightweight** - ~15KB minified
47
+
48
+ ## Props
49
+
50
+ ### GotchaProvider
51
+
52
+ | Prop | Type | Default | Description |
53
+ |------|------|---------|-------------|
54
+ | `apiKey` | `string` | Required | Your Gotcha API key |
55
+ | `baseUrl` | `string` | Production URL | Override API endpoint |
56
+ | `debug` | `boolean` | `false` | Enable debug logging |
57
+ | `disabled` | `boolean` | `false` | Disable all Gotcha buttons |
58
+ | `defaultUser` | `object` | `{}` | Default user metadata |
59
+
60
+ ### Gotcha
61
+
62
+ | Prop | Type | Default | Description |
63
+ |------|------|---------|-------------|
64
+ | `elementId` | `string` | Required | Unique identifier for this element |
65
+ | `mode` | `'feedback' \| 'vote'` | `'feedback'` | Feedback mode |
66
+ | `position` | `'top-right' \| 'top-left' \| 'bottom-right' \| 'bottom-left' \| 'inline'` | `'top-right'` | Button position |
67
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
68
+ | `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Color theme |
69
+ | `showOnHover` | `boolean` | `true` | Only show on hover |
70
+ | `promptText` | `string` | Mode-specific | Custom prompt text |
71
+ | `onSubmit` | `function` | - | Callback after submission |
72
+ | `onOpen` | `function` | - | Callback when modal opens |
73
+ | `onClose` | `function` | - | Callback when modal closes |
74
+
75
+ ## Examples
76
+
77
+ ### Inline Position
78
+
79
+ ```tsx
80
+ <h2>
81
+ Feature Title
82
+ <Gotcha elementId="title" position="inline" size="sm" showOnHover={false} />
83
+ </h2>
84
+ ```
85
+
86
+ ### Vote Mode
87
+
88
+ ```tsx
89
+ <Gotcha
90
+ elementId="pricing-feedback"
91
+ mode="vote"
92
+ promptText="Is this pricing fair?"
93
+ />
94
+ ```
95
+
96
+ ### Dark Theme
97
+
98
+ ```tsx
99
+ <Gotcha
100
+ elementId="dark-card"
101
+ theme="dark"
102
+ />
103
+ ```
104
+
105
+ ### With User Data
106
+
107
+ ```tsx
108
+ <GotchaProvider
109
+ apiKey="your-api-key"
110
+ defaultUser={{ plan: 'pro', role: 'admin' }}
111
+ >
112
+ <App />
113
+ </GotchaProvider>
114
+ ```
115
+
116
+ ### Custom Callbacks
117
+
118
+ ```tsx
119
+ <Gotcha
120
+ elementId="feature"
121
+ onSubmit={(response) => {
122
+ console.log('Feedback submitted:', response);
123
+ analytics.track('feedback_submitted', { elementId: 'feature' });
124
+ }}
125
+ onError={(error) => {
126
+ console.error('Feedback error:', error);
127
+ }}
128
+ />
129
+ ```
130
+
131
+ ## Hooks
132
+
133
+ ### useGotcha
134
+
135
+ Access the Gotcha context for programmatic control:
136
+
137
+ ```tsx
138
+ import { useGotcha } from 'gotcha-feedback';
139
+
140
+ function MyComponent() {
141
+ const { submitFeedback, disabled } = useGotcha();
142
+
143
+ const handleCustomSubmit = async () => {
144
+ await submitFeedback({
145
+ elementId: 'custom',
146
+ mode: 'feedback',
147
+ rating: 5,
148
+ content: 'Great feature!',
149
+ });
150
+ };
151
+
152
+ return <button onClick={handleCustomSubmit}>Submit Feedback</button>;
153
+ }
154
+ ```
155
+
156
+ ## TypeScript
157
+
158
+ The package includes full TypeScript definitions:
159
+
160
+ ```tsx
161
+ import type { GotchaProps, GotchaProviderProps } from 'gotcha-feedback';
162
+ ```
163
+
164
+ ## Requirements
165
+
166
+ - React 18+
167
+ - Framer Motion (peer dependency)
168
+
169
+ ## License
170
+
171
+ MIT
@@ -0,0 +1,139 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React$1 from 'react';
3
+
4
+ type ResponseMode = 'feedback' | 'vote' | 'poll' | 'feature-request' | 'ab';
5
+ type VoteType = 'up' | 'down';
6
+ interface GotchaUser {
7
+ id?: string;
8
+ [key: string]: string | number | boolean | null | undefined;
9
+ }
10
+ type Position = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'inline';
11
+ type Size = 'sm' | 'md' | 'lg';
12
+ type Theme = 'light' | 'dark' | 'auto' | 'custom';
13
+ type TouchBehavior = 'always-visible' | 'tap-to-reveal';
14
+ interface GotchaStyles {
15
+ button?: React.CSSProperties;
16
+ modal?: React.CSSProperties;
17
+ input?: React.CSSProperties;
18
+ submitButton?: React.CSSProperties;
19
+ }
20
+ interface SubmitResponsePayload {
21
+ elementId: string;
22
+ mode: ResponseMode;
23
+ content?: string;
24
+ title?: string;
25
+ rating?: number;
26
+ vote?: VoteType;
27
+ pollOptions?: string[];
28
+ pollSelected?: string[];
29
+ experimentId?: string;
30
+ variant?: string;
31
+ user?: GotchaUser;
32
+ context?: {
33
+ url?: string;
34
+ userAgent?: string;
35
+ };
36
+ }
37
+ interface GotchaResponse {
38
+ id: string;
39
+ status: 'created' | 'duplicate';
40
+ createdAt: string;
41
+ results?: PollResults;
42
+ }
43
+ interface PollResults {
44
+ [option: string]: number;
45
+ }
46
+ interface GotchaError {
47
+ code: ErrorCode;
48
+ message: string;
49
+ status: number;
50
+ }
51
+ type ErrorCode = 'INVALID_API_KEY' | 'ORIGIN_NOT_ALLOWED' | 'RATE_LIMITED' | 'QUOTA_EXCEEDED' | 'INVALID_REQUEST' | 'USER_NOT_FOUND' | 'INTERNAL_ERROR';
52
+
53
+ interface GotchaProviderProps {
54
+ /** Your Gotcha API key */
55
+ apiKey: string;
56
+ /** React children */
57
+ children: React$1.ReactNode;
58
+ /** Override the API base URL (for testing/staging) */
59
+ baseUrl?: string;
60
+ /** Enable debug logging */
61
+ debug?: boolean;
62
+ /** Disable all Gotcha buttons globally */
63
+ disabled?: boolean;
64
+ /** Default user metadata applied to all submissions */
65
+ defaultUser?: GotchaUser;
66
+ }
67
+ declare function GotchaProvider({ apiKey, children, baseUrl, debug, disabled, defaultUser, }: GotchaProviderProps): react_jsx_runtime.JSX.Element;
68
+
69
+ interface GotchaProps {
70
+ /** Unique identifier for this element */
71
+ elementId: string;
72
+ /** User metadata for segmentation */
73
+ user?: GotchaUser;
74
+ /** Feedback mode */
75
+ mode?: ResponseMode;
76
+ /** Required if mode is 'ab' */
77
+ experimentId?: string;
78
+ /** Current A/B variant shown to user */
79
+ variant?: string;
80
+ /** Required if mode is 'poll' (2-6 options) */
81
+ options?: string[];
82
+ /** Allow selecting multiple options */
83
+ allowMultiple?: boolean;
84
+ /** Show results after voting */
85
+ showResults?: boolean;
86
+ /** Button position relative to parent */
87
+ position?: Position;
88
+ /** Button size */
89
+ size?: Size;
90
+ /** Color theme */
91
+ theme?: Theme;
92
+ /** Custom style overrides */
93
+ customStyles?: GotchaStyles;
94
+ /** Control visibility programmatically */
95
+ visible?: boolean;
96
+ /** Only show when parent is hovered (default: true) */
97
+ showOnHover?: boolean;
98
+ /** Mobile behavior (default: 'always-visible') */
99
+ touchBehavior?: TouchBehavior;
100
+ /** Custom prompt text */
101
+ promptText?: string;
102
+ /** Input placeholder text */
103
+ placeholder?: string;
104
+ /** Submit button text */
105
+ submitText?: string;
106
+ /** Post-submission message */
107
+ thankYouMessage?: string;
108
+ /** Called after successful submission */
109
+ onSubmit?: (response: GotchaResponse) => void;
110
+ /** Called when modal opens */
111
+ onOpen?: () => void;
112
+ /** Called when modal closes */
113
+ onClose?: () => void;
114
+ /** Called on error */
115
+ onError?: (error: GotchaError) => void;
116
+ }
117
+ declare function Gotcha({ elementId, user, mode, experimentId, variant, options, allowMultiple, showResults, position, size, theme, customStyles, visible, showOnHover, touchBehavior, promptText, placeholder, submitText, thankYouMessage, onSubmit, onOpen, onClose, onError, }: GotchaProps): react_jsx_runtime.JSX.Element | null;
118
+
119
+ /**
120
+ * Hook to access Gotcha context
121
+ * Must be used within a GotchaProvider
122
+ */
123
+ declare function useGotcha(): {
124
+ /** The API client for manual submissions */
125
+ client: {
126
+ submitResponse(payload: Omit<SubmitResponsePayload, "context">): Promise<GotchaResponse>;
127
+ getBaseUrl(): string;
128
+ };
129
+ /** Whether Gotcha is globally disabled */
130
+ disabled: boolean;
131
+ /** Default user metadata */
132
+ defaultUser: GotchaUser;
133
+ /** Whether debug mode is enabled */
134
+ debug: boolean;
135
+ /** Submit feedback programmatically */
136
+ submitFeedback: (payload: Omit<SubmitResponsePayload, "context">) => Promise<GotchaResponse>;
137
+ };
138
+
139
+ export { Gotcha, type GotchaError, type GotchaProps, GotchaProvider, type GotchaProviderProps, type GotchaResponse, type GotchaStyles, type GotchaUser, type Position, type ResponseMode, type Size, type Theme, type TouchBehavior, type VoteType, useGotcha };
@@ -0,0 +1,139 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React$1 from 'react';
3
+
4
+ type ResponseMode = 'feedback' | 'vote' | 'poll' | 'feature-request' | 'ab';
5
+ type VoteType = 'up' | 'down';
6
+ interface GotchaUser {
7
+ id?: string;
8
+ [key: string]: string | number | boolean | null | undefined;
9
+ }
10
+ type Position = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'inline';
11
+ type Size = 'sm' | 'md' | 'lg';
12
+ type Theme = 'light' | 'dark' | 'auto' | 'custom';
13
+ type TouchBehavior = 'always-visible' | 'tap-to-reveal';
14
+ interface GotchaStyles {
15
+ button?: React.CSSProperties;
16
+ modal?: React.CSSProperties;
17
+ input?: React.CSSProperties;
18
+ submitButton?: React.CSSProperties;
19
+ }
20
+ interface SubmitResponsePayload {
21
+ elementId: string;
22
+ mode: ResponseMode;
23
+ content?: string;
24
+ title?: string;
25
+ rating?: number;
26
+ vote?: VoteType;
27
+ pollOptions?: string[];
28
+ pollSelected?: string[];
29
+ experimentId?: string;
30
+ variant?: string;
31
+ user?: GotchaUser;
32
+ context?: {
33
+ url?: string;
34
+ userAgent?: string;
35
+ };
36
+ }
37
+ interface GotchaResponse {
38
+ id: string;
39
+ status: 'created' | 'duplicate';
40
+ createdAt: string;
41
+ results?: PollResults;
42
+ }
43
+ interface PollResults {
44
+ [option: string]: number;
45
+ }
46
+ interface GotchaError {
47
+ code: ErrorCode;
48
+ message: string;
49
+ status: number;
50
+ }
51
+ type ErrorCode = 'INVALID_API_KEY' | 'ORIGIN_NOT_ALLOWED' | 'RATE_LIMITED' | 'QUOTA_EXCEEDED' | 'INVALID_REQUEST' | 'USER_NOT_FOUND' | 'INTERNAL_ERROR';
52
+
53
+ interface GotchaProviderProps {
54
+ /** Your Gotcha API key */
55
+ apiKey: string;
56
+ /** React children */
57
+ children: React$1.ReactNode;
58
+ /** Override the API base URL (for testing/staging) */
59
+ baseUrl?: string;
60
+ /** Enable debug logging */
61
+ debug?: boolean;
62
+ /** Disable all Gotcha buttons globally */
63
+ disabled?: boolean;
64
+ /** Default user metadata applied to all submissions */
65
+ defaultUser?: GotchaUser;
66
+ }
67
+ declare function GotchaProvider({ apiKey, children, baseUrl, debug, disabled, defaultUser, }: GotchaProviderProps): react_jsx_runtime.JSX.Element;
68
+
69
+ interface GotchaProps {
70
+ /** Unique identifier for this element */
71
+ elementId: string;
72
+ /** User metadata for segmentation */
73
+ user?: GotchaUser;
74
+ /** Feedback mode */
75
+ mode?: ResponseMode;
76
+ /** Required if mode is 'ab' */
77
+ experimentId?: string;
78
+ /** Current A/B variant shown to user */
79
+ variant?: string;
80
+ /** Required if mode is 'poll' (2-6 options) */
81
+ options?: string[];
82
+ /** Allow selecting multiple options */
83
+ allowMultiple?: boolean;
84
+ /** Show results after voting */
85
+ showResults?: boolean;
86
+ /** Button position relative to parent */
87
+ position?: Position;
88
+ /** Button size */
89
+ size?: Size;
90
+ /** Color theme */
91
+ theme?: Theme;
92
+ /** Custom style overrides */
93
+ customStyles?: GotchaStyles;
94
+ /** Control visibility programmatically */
95
+ visible?: boolean;
96
+ /** Only show when parent is hovered (default: true) */
97
+ showOnHover?: boolean;
98
+ /** Mobile behavior (default: 'always-visible') */
99
+ touchBehavior?: TouchBehavior;
100
+ /** Custom prompt text */
101
+ promptText?: string;
102
+ /** Input placeholder text */
103
+ placeholder?: string;
104
+ /** Submit button text */
105
+ submitText?: string;
106
+ /** Post-submission message */
107
+ thankYouMessage?: string;
108
+ /** Called after successful submission */
109
+ onSubmit?: (response: GotchaResponse) => void;
110
+ /** Called when modal opens */
111
+ onOpen?: () => void;
112
+ /** Called when modal closes */
113
+ onClose?: () => void;
114
+ /** Called on error */
115
+ onError?: (error: GotchaError) => void;
116
+ }
117
+ declare function Gotcha({ elementId, user, mode, experimentId, variant, options, allowMultiple, showResults, position, size, theme, customStyles, visible, showOnHover, touchBehavior, promptText, placeholder, submitText, thankYouMessage, onSubmit, onOpen, onClose, onError, }: GotchaProps): react_jsx_runtime.JSX.Element | null;
118
+
119
+ /**
120
+ * Hook to access Gotcha context
121
+ * Must be used within a GotchaProvider
122
+ */
123
+ declare function useGotcha(): {
124
+ /** The API client for manual submissions */
125
+ client: {
126
+ submitResponse(payload: Omit<SubmitResponsePayload, "context">): Promise<GotchaResponse>;
127
+ getBaseUrl(): string;
128
+ };
129
+ /** Whether Gotcha is globally disabled */
130
+ disabled: boolean;
131
+ /** Default user metadata */
132
+ defaultUser: GotchaUser;
133
+ /** Whether debug mode is enabled */
134
+ debug: boolean;
135
+ /** Submit feedback programmatically */
136
+ submitFeedback: (payload: Omit<SubmitResponsePayload, "context">) => Promise<GotchaResponse>;
137
+ };
138
+
139
+ export { Gotcha, type GotchaError, type GotchaProps, GotchaProvider, type GotchaProviderProps, type GotchaResponse, type GotchaStyles, type GotchaUser, type Position, type ResponseMode, type Size, type Theme, type TouchBehavior, type VoteType, useGotcha };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var j="https://api.gotcha.cx/v1";var V={ANONYMOUS_ID:"gotcha_anonymous_id"},y={POSITION:"top-right",SIZE:"md",THEME:"auto",SHOW_ON_HOVER:true,TOUCH_BEHAVIOR:"always-visible",SUBMIT_TEXT:"Submit",THANK_YOU_MESSAGE:"Thanks for your feedback!"};var P={MAX_RETRIES:2,BASE_DELAY_MS:500,MAX_DELAY_MS:5e3};function J(){if(typeof window>"u")return `anon_${crypto.randomUUID()}`;let e=localStorage.getItem(V.ANONYMOUS_ID);if(e)return e;let s=`anon_${crypto.randomUUID()}`;return localStorage.setItem(V.ANONYMOUS_ID,s),s}var ee={maxRetries:P.MAX_RETRIES,baseDelayMs:P.BASE_DELAY_MS,maxDelayMs:P.MAX_DELAY_MS};async function we(e,s,o=ee,n=false){let i=null;for(let a=0;a<=o.maxRetries;a++){try{n&&a>0&&console.log(`[Gotcha] Retry attempt ${a}/${o.maxRetries}`);let t=await fetch(e,s);if(t.status>=400&&t.status<500&&t.status!==429||t.ok)return t;i=new Error(`HTTP ${t.status}`);}catch(t){i=t,n&&console.log(`[Gotcha] Network error: ${i.message}`);}if(a<o.maxRetries){let t=Math.min(o.baseDelayMs*Math.pow(2,a),o.maxDelayMs);await new Promise(u=>setTimeout(u,t));}}throw i}function te(e){let{apiKey:s,baseUrl:o=j,debug:n=false}=e,i={"Content-Type":"application/json",Authorization:`Bearer ${s}`};async function a(t,u,r){let l=`${o}${u}`,c=crypto.randomUUID();n&&console.log(`[Gotcha] ${t} ${u}`,r);let p=await we(l,{method:t,headers:{...i,"Idempotency-Key":c},body:r?JSON.stringify(r):void 0},ee,n),d=await p.json();if(!p.ok){let m=d.error;throw n&&console.error(`[Gotcha] Error: ${m.code} - ${m.message}`),m}return n&&console.log("[Gotcha] Response:",d),d}return {async submitResponse(t){let u=t.user||{};u.id||(u.id=J());let r={...t,user:u,context:{url:typeof window<"u"?window.location.href:void 0,userAgent:typeof navigator<"u"?navigator.userAgent:void 0}};return a("POST","/responses",r)},getBaseUrl(){return o}}}var re=react.createContext(null);function Me({apiKey:e,children:s,baseUrl:o,debug:n=false,disabled:i=false,defaultUser:a={}}){let[t,u]=react.useState(null),r=react.useMemo(()=>te({apiKey:e,baseUrl:o,debug:n}),[e,o,n]),l=react.useCallback(d=>{u(d);},[]),c=react.useCallback(()=>{u(null);},[]),p=react.useMemo(()=>({client:r,disabled:i,defaultUser:a,debug:n,activeModalId:t,openModal:l,closeModal:c}),[r,i,a,n,t,l,c]);return jsxRuntime.jsx(re.Provider,{value:p,children:s})}function T(){let e=react.useContext(re);if(!e)throw new Error("useGotchaContext must be used within a GotchaProvider");return e}function ae(e){let{client:s,defaultUser:o}=T(),[n,i]=react.useState(false),[a,t]=react.useState(null);return {submit:react.useCallback(async r=>{i(true),t(null);try{let l=await s.submitResponse({elementId:e.elementId,mode:e.mode,content:r.content,title:r.title,rating:r.rating,vote:r.vote,pollOptions:e.pollOptions,pollSelected:r.pollSelected,experimentId:e.experimentId,variant:e.variant,user:{...o,...e.user}});return e.onSuccess?.(l),l}catch(l){let c=l instanceof Error?l.message:"Something went wrong";throw t(c),e.onError?.(l instanceof Error?l:new Error(c)),l}finally{i(false);}},[s,o,e]),isLoading:n,error:a,clearError:()=>t(null)}}function D(...e){return e.filter(Boolean).join(" ")}var ie=()=>typeof window>"u"?false:"ontouchstart"in window||navigator.maxTouchPoints>0,le=(e,s)=>{let o={sm:{desktop:24,mobile:44},md:{desktop:32,mobile:44},lg:{desktop:40,mobile:48}};return s?o[e].mobile:o[e].desktop};function ue({size:e,theme:s,customStyles:o,showOnHover:n,touchBehavior:i,onClick:a,isOpen:t,isParentHovered:u=false}){let[r,l]=react.useState(false),[c,p]=react.useState(false);react.useEffect(()=>{l(ie());},[]);let d=t?true:!r&&n?u:r&&i==="tap-to-reveal"?c:true,m=()=>{if(r&&i==="tap-to-reveal"&&!c){p(true);return}a();},f=le(e,r),E=s==="auto"?typeof window<"u"&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":s,k={width:f,height:f,borderRadius:"50%",border:"none",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:E==="dark"?"#374151":"#c7d2dc",color:E==="dark"?"#e5e7eb":"#4b5563",boxShadow:"0 1px 3px rgba(0, 0, 0, 0.1)",transition:"opacity 0.2s ease-out, transform 0.2s ease-out",opacity:d?1:0,transform:d?"scale(1)":"scale(0.6)",pointerEvents:d?"auto":"none",...o?.button};return jsxRuntime.jsx("button",{type:"button",onClick:m,style:k,className:D("gotcha-button",t&&"gotcha-button--open"),"aria-label":"Give feedback on this feature","aria-expanded":t,"aria-haspopup":"dialog",children:jsxRuntime.jsx(_e,{size:f*.75})})}function _e({size:e}){return jsxRuntime.jsx("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:jsxRuntime.jsx("text",{x:"50%",y:"50%",dominantBaseline:"central",textAnchor:"middle",fontSize:"16",fontWeight:"bold",fill:"currentColor",fontFamily:"system-ui, -apple-system, sans-serif",children:"G"})})}function de({theme:e,placeholder:s,submitText:o,isLoading:n,onSubmit:i,customStyles:a}){let[t,u]=react.useState(""),[r,l]=react.useState(null),c=e==="dark",p=f=>{f.preventDefault(),!(!t.trim()&&r===null)&&i({content:t.trim()||void 0,rating:r??void 0});},d={width:"100%",padding:"10px 12px",border:`1px solid ${c?"#374151":"#d1d5db"}`,borderRadius:6,backgroundColor:c?"#374151":"#ffffff",color:c?"#f9fafb":"#111827",fontSize:14,resize:"vertical",minHeight:80,fontFamily:"inherit",...a?.input},m={width:"100%",padding:"10px 16px",border:"none",borderRadius:6,backgroundColor:n?c?"#4b5563":"#9ca3af":"#6366f1",color:"#ffffff",fontSize:14,fontWeight:500,cursor:n?"not-allowed":"pointer",transition:"background-color 150ms ease",...a?.submitButton};return jsxRuntime.jsxs("form",{onSubmit:p,children:[jsxRuntime.jsx("div",{style:{marginBottom:12},children:jsxRuntime.jsx(Oe,{value:r,onChange:l,isDark:c})}),jsxRuntime.jsx("textarea",{value:t,onChange:f=>u(f.target.value),placeholder:s||"Share your thoughts...",style:d,disabled:n,"aria-label":"Your feedback"}),jsxRuntime.jsx("button",{type:"submit",disabled:n||!t.trim()&&r===null,style:{...m,marginTop:12,opacity:!t.trim()&&r===null?.5:1},children:n?"Submitting...":o})]})}function Oe({value:e,onChange:s,isDark:o}){let[n,i]=react.useState(null);return jsxRuntime.jsx("div",{style:{display:"flex",gap:4},role:"group","aria-label":"Rating",children:[1,2,3,4,5].map(a=>{let t=(n??e??0)>=a;return jsxRuntime.jsx("button",{type:"button",onClick:()=>s(a),onMouseEnter:()=>i(a),onMouseLeave:()=>i(null),"aria-label":`Rate ${a} out of 5`,"aria-pressed":e===a,style:{background:"none",border:"none",cursor:"pointer",padding:2,color:t?"#f59e0b":o?"#4b5563":"#d1d5db",transition:"color 150ms ease"},children:jsxRuntime.jsx("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"currentColor",children:jsxRuntime.jsx("path",{d:"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"})})},a)})})}function pe({theme:e,isLoading:s,onSubmit:o}){let n=e==="dark",i={flex:1,padding:"12px 16px",border:`1px solid ${n?"#374151":"#e5e7eb"}`,borderRadius:8,backgroundColor:n?"#374151":"#f9fafb",color:n?"#f9fafb":"#111827",fontSize:24,cursor:s?"not-allowed":"pointer",transition:"all 150ms ease",display:"flex",alignItems:"center",justifyContent:"center",gap:8};return jsxRuntime.jsxs("div",{style:{display:"flex",gap:12},role:"group","aria-label":"Vote",children:[jsxRuntime.jsxs("button",{type:"button",onClick:()=>o({vote:"up"}),disabled:s,style:i,"aria-label":"Vote up - I like this",children:[jsxRuntime.jsx(De,{}),jsxRuntime.jsx("span",{style:{fontSize:14,fontWeight:500},children:"Like"})]}),jsxRuntime.jsxs("button",{type:"button",onClick:()=>o({vote:"down"}),disabled:s,style:i,"aria-label":"Vote down - I don't like this",children:[jsxRuntime.jsx(Le,{}),jsxRuntime.jsx("span",{style:{fontSize:14,fontWeight:500},children:"Dislike"})]})]})}function De(){return jsxRuntime.jsx("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",children:jsxRuntime.jsx("path",{d:"M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"})})}function Le(){return jsxRuntime.jsx("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",children:jsxRuntime.jsx("path",{d:"M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"})})}function he({mode:e,theme:s,customStyles:o,promptText:n,placeholder:i,submitText:a,thankYouMessage:t,isLoading:u,isSubmitted:r,error:l,onSubmit:c,onClose:p,anchorRect:d}){let m=react.useRef(null),f=react.useRef(null),[E,k]=react.useState(false);react.useEffect(()=>{let x=requestAnimationFrame(()=>k(true));return ()=>cancelAnimationFrame(x)},[]);let G=s==="auto"?typeof window<"u"&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":s,h=G==="dark",M=(d?window.innerHeight-d.bottom:window.innerHeight/2)<280+20;react.useEffect(()=>{let x=m.current;if(!x)return;f.current?.focus();let I=b=>{if(b.key==="Escape"){p();return}if(b.key==="Tab"){let C=x.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),w=C[0],_=C[C.length-1];b.shiftKey&&document.activeElement===w?(b.preventDefault(),_?.focus()):!b.shiftKey&&document.activeElement===_&&(b.preventDefault(),w?.focus());}};return document.addEventListener("keydown",I),()=>document.removeEventListener("keydown",I)},[p]);let B=e==="vote"?"What do you think?":"What do you think of this feature?",H={position:"absolute",left:"50%",width:320,padding:16,borderRadius:8,backgroundColor:h?"#1f2937":"#ffffff",color:h?"#f9fafb":"#111827",boxShadow:"0 10px 25px rgba(0, 0, 0, 0.15)",border:`1px solid ${h?"#374151":"#e5e7eb"}`,zIndex:9999,...M?{bottom:"100%",marginBottom:8}:{top:"100%",marginTop:8},transition:"opacity 0.2s ease-out, transform 0.2s ease-out",opacity:E?1:0,transform:E?"translateX(-50%) scale(1) translateY(0)":`translateX(-50%) scale(0.95) translateY(${M?"10px":"-10px"})`,...o?.modal};return jsxRuntime.jsxs("div",{ref:m,role:"dialog","aria-modal":"true","aria-labelledby":"gotcha-modal-title",style:H,className:D("gotcha-modal",h&&"gotcha-modal--dark"),children:[jsxRuntime.jsx("button",{ref:f,type:"button",onClick:p,"aria-label":"Close feedback form",style:{position:"absolute",top:8,right:8,width:24,height:24,border:"none",background:"none",cursor:"pointer",color:h?"#9ca3af":"#6b7280",display:"flex",alignItems:"center",justifyContent:"center",borderRadius:4},children:jsxRuntime.jsx("svg",{width:"14",height:"14",viewBox:"0 0 14 14",fill:"none",children:jsxRuntime.jsx("path",{d:"M1 1L13 13M1 13L13 1",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round"})})}),jsxRuntime.jsx("h2",{id:"gotcha-modal-title",style:{margin:"0 0 12px 0",fontSize:14,fontWeight:500,paddingRight:24},children:n||B}),r&&jsxRuntime.jsxs("div",{style:{textAlign:"center",padding:"20px 0",color:h?"#10b981":"#059669"},children:[jsxRuntime.jsx("svg",{width:"32",height:"32",viewBox:"0 0 24 24",fill:"none",style:{margin:"0 auto 8px"},children:jsxRuntime.jsx("path",{d:"M20 6L9 17L4 12",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})}),jsxRuntime.jsx("p",{style:{margin:0,fontSize:14},children:t})]}),l&&!r&&jsxRuntime.jsx("div",{style:{padding:8,marginBottom:12,borderRadius:4,backgroundColor:h?"#7f1d1d":"#fef2f2",color:h?"#fecaca":"#dc2626",fontSize:13},children:l}),!r&&jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[e==="feedback"&&jsxRuntime.jsx(de,{theme:G,placeholder:i,submitText:a,isLoading:u,onSubmit:c,customStyles:o}),e==="vote"&&jsxRuntime.jsx(pe,{theme:G,isLoading:u,onSubmit:c})]}),jsxRuntime.jsxs("div",{"aria-live":"polite",className:"sr-only",style:{position:"absolute",left:-9999},children:[r&&"Thank you! Your feedback has been submitted.",l&&`Error: ${l}`]})]})}function ze({elementId:e,user:s,mode:o="feedback",experimentId:n,variant:i,options:a,allowMultiple:t=false,showResults:u=true,position:r=y.POSITION,size:l=y.SIZE,theme:c=y.THEME,customStyles:p,visible:d=true,showOnHover:m=y.SHOW_ON_HOVER,touchBehavior:f=y.TOUCH_BEHAVIOR,promptText:E,placeholder:k,submitText:G=y.SUBMIT_TEXT,thankYouMessage:h=y.THANK_YOU_MESSAGE,onSubmit:K,onOpen:N,onClose:M,onError:B}){let{disabled:H,activeModalId:x,openModal:I,closeModal:b}=T(),[C,w]=react.useState(false),[_,X]=react.useState(false),[ge,ve]=react.useState(null),O=react.useRef(null),z=x===e;react.useEffect(()=>{if(!m)return;let v=O.current;if(!v)return;let A=v.parentElement;if(!A)return;let Q=()=>X(true),Z=()=>X(false);return A.addEventListener("mouseenter",Q),A.addEventListener("mouseleave",Z),()=>{A.removeEventListener("mouseenter",Q),A.removeEventListener("mouseleave",Z);}},[m]);let{submit:q,isLoading:ye,error:Se}=ae({elementId:e,mode:o,experimentId:n,variant:i,pollOptions:a,user:s,onSuccess:v=>{w(true),K?.(v),setTimeout(()=>{b(),w(false);},2500);},onError:v=>{B?.(v);}}),Re=react.useCallback(()=>{O.current&&ve(O.current.getBoundingClientRect()),I(e),N?.();},[e,I,N]),Ee=react.useCallback(()=>{b(),w(false),M?.();},[b,M]),xe=react.useCallback(v=>{q(v);},[q]);return H||!d?null:jsxRuntime.jsxs("div",{ref:O,style:{...{"top-right":{position:"absolute",top:0,right:0,transform:"translate(50%, -50%)"},"top-left":{position:"absolute",top:0,left:0,transform:"translate(-50%, -50%)"},"bottom-right":{position:"absolute",bottom:0,right:0,transform:"translate(50%, 50%)"},"bottom-left":{position:"absolute",bottom:0,left:0,transform:"translate(-50%, 50%)"},inline:{position:"relative",display:"inline-flex"}}[r],zIndex:z?1e4:50},className:"gotcha-container","data-gotcha-element":e,children:[jsxRuntime.jsx(ue,{size:l,theme:c,customStyles:p,showOnHover:m,touchBehavior:f,onClick:Re,isOpen:z,isParentHovered:_}),z&&jsxRuntime.jsx(he,{mode:o,theme:c,customStyles:p,promptText:E,placeholder:k,submitText:G,thankYouMessage:h,isLoading:ye,isSubmitted:C,error:Se,onSubmit:xe,onClose:Ee,anchorRect:ge||void 0})]})}function Ye(){let{client:e,disabled:s,defaultUser:o,debug:n}=T();return {client:e,disabled:s,defaultUser:o,debug:n,submitFeedback:e.submitResponse.bind(e)}}
2
+ exports.Gotcha=ze;exports.GotchaProvider=Me;exports.useGotcha=Ye;//# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/utils/anonymous.ts","../src/api/client.ts","../src/components/GotchaProvider.tsx","../src/hooks/useSubmit.ts","../src/utils/cn.ts","../src/utils/device.ts","../src/components/GotchaButton.tsx","../src/components/modes/FeedbackMode.tsx","../src/components/modes/VoteMode.tsx","../src/components/GotchaModal.tsx","../src/components/Gotcha.tsx","../src/hooks/useGotcha.ts"],"names":["API_BASE_URL","STORAGE_KEYS","DEFAULTS","RETRY_CONFIG","getAnonymousId","stored","id","DEFAULT_RETRY_CONFIG","fetchWithRetry","url","options","config","debug","lastError","attempt","response","error","delay","resolve","createApiClient","apiKey","baseUrl","headers","request","method","endpoint","body","idempotencyKey","data","payload","user","fullPayload","GotchaContext","createContext","GotchaProvider","children","disabled","defaultUser","activeModalId","setActiveModalId","useState","client","useMemo","openModal","useCallback","elementId","closeModal","value","jsx","useGotchaContext","context","useContext","useSubmit","isLoading","setIsLoading","setError","err","errorMessage","cn","classes","isTouchDevice","getResponsiveSize","size","isTouch","sizes","GotchaButton","theme","customStyles","showOnHover","touchBehavior","onClick","isOpen","isParentHovered","setIsTouch","tapRevealed","setTapRevealed","useEffect","shouldShow","handleClick","buttonSize","resolvedTheme","baseStyles","GotchaIcon","FeedbackMode","placeholder","submitText","onSubmit","content","setContent","rating","setRating","isDark","handleSubmit","e","inputStyles","buttonStyles","jsxs","StarRating","onChange","hovered","setHovered","star","isFilled","VoteMode","buttonBase","ThumbsUpIcon","ThumbsDownIcon","GotchaModal","mode","promptText","thankYouMessage","isSubmitted","onClose","anchorRect","modalRef","useRef","firstFocusableRef","isVisible","setIsVisible","timer","showAbove","modal","handleKeyDown","focusableElements","firstElement","lastElement","defaultPrompt","modalStyles","Fragment","Gotcha","experimentId","variant","allowMultiple","showResults","position","visible","onOpen","onError","setIsSubmitted","setIsParentHovered","setAnchorRect","containerRef","container","parent","handleMouseEnter","handleMouseLeave","submit","handleOpen","handleClose","useGotcha"],"mappings":"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","file":"index.js","sourcesContent":["// API URLs\nexport const API_BASE_URL = 'https://api.gotcha.cx/v1';\nexport const API_STAGING_URL = 'https://api.staging.gotcha.cx/v1';\n\n// Error codes\nexport const ERROR_CODES = {\n INVALID_API_KEY: 'INVALID_API_KEY',\n ORIGIN_NOT_ALLOWED: 'ORIGIN_NOT_ALLOWED',\n RATE_LIMITED: 'RATE_LIMITED',\n QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',\n INVALID_REQUEST: 'INVALID_REQUEST',\n USER_NOT_FOUND: 'USER_NOT_FOUND',\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n} as const;\n\n// Local storage keys\nexport const STORAGE_KEYS = {\n ANONYMOUS_ID: 'gotcha_anonymous_id',\n OFFLINE_QUEUE: 'gotcha_offline_queue',\n} as const;\n\n// Default values\nexport const DEFAULTS = {\n POSITION: 'top-right' as const,\n SIZE: 'md' as const,\n THEME: 'auto' as const,\n SHOW_ON_HOVER: true,\n TOUCH_BEHAVIOR: 'always-visible' as const,\n SUBMIT_TEXT: 'Submit',\n THANK_YOU_MESSAGE: 'Thanks for your feedback!',\n} as const;\n\n// Size mappings (desktop/mobile in pixels)\nexport const SIZE_MAP = {\n sm: { desktop: 24, mobile: 44 },\n md: { desktop: 32, mobile: 44 },\n lg: { desktop: 40, mobile: 48 },\n} as const;\n\n// Retry config\nexport const RETRY_CONFIG = {\n MAX_RETRIES: 2,\n BASE_DELAY_MS: 500,\n MAX_DELAY_MS: 5000,\n} as const;\n","import { STORAGE_KEYS } from '../constants';\n\n/**\n * Get or create an anonymous user ID\n * Stored in localStorage for consistency across sessions\n */\nexport function getAnonymousId(): string {\n if (typeof window === 'undefined') {\n // SSR fallback - generate but don't persist\n return `anon_${crypto.randomUUID()}`;\n }\n\n const stored = localStorage.getItem(STORAGE_KEYS.ANONYMOUS_ID);\n if (stored) return stored;\n\n const id = `anon_${crypto.randomUUID()}`;\n localStorage.setItem(STORAGE_KEYS.ANONYMOUS_ID, id);\n return id;\n}\n\n/**\n * Clear the anonymous ID (useful for testing)\n */\nexport function clearAnonymousId(): void {\n if (typeof window !== 'undefined') {\n localStorage.removeItem(STORAGE_KEYS.ANONYMOUS_ID);\n }\n}\n","import { API_BASE_URL, RETRY_CONFIG } from '../constants';\nimport { SubmitResponsePayload, GotchaResponse, GotchaError } from '../types';\nimport { getAnonymousId } from '../utils/anonymous';\n\ninterface ApiClientConfig {\n apiKey: string;\n baseUrl?: string;\n debug?: boolean;\n}\n\ninterface RetryConfig {\n maxRetries: number;\n baseDelayMs: number;\n maxDelayMs: number;\n}\n\nconst DEFAULT_RETRY_CONFIG: RetryConfig = {\n maxRetries: RETRY_CONFIG.MAX_RETRIES,\n baseDelayMs: RETRY_CONFIG.BASE_DELAY_MS,\n maxDelayMs: RETRY_CONFIG.MAX_DELAY_MS,\n};\n\n/**\n * Fetch with automatic retry and exponential backoff\n */\nasync function fetchWithRetry(\n url: string,\n options: RequestInit,\n config: RetryConfig = DEFAULT_RETRY_CONFIG,\n debug: boolean = false\n): Promise<Response> {\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n if (debug && attempt > 0) {\n console.log(`[Gotcha] Retry attempt ${attempt}/${config.maxRetries}`);\n }\n\n const response = await fetch(url, options);\n\n // Don't retry client errors (4xx) except 429 (rate limit)\n if (response.status >= 400 && response.status < 500 && response.status !== 429) {\n return response;\n }\n\n // Success - return immediately\n if (response.ok) {\n return response;\n }\n\n lastError = new Error(`HTTP ${response.status}`);\n } catch (error) {\n // Network error - retry\n lastError = error as Error;\n if (debug) {\n console.log(`[Gotcha] Network error: ${lastError.message}`);\n }\n }\n\n // Don't delay after last attempt\n if (attempt < config.maxRetries) {\n const delay = Math.min(\n config.baseDelayMs * Math.pow(2, attempt),\n config.maxDelayMs\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw lastError;\n}\n\nexport function createApiClient(config: ApiClientConfig) {\n const { apiKey, baseUrl = API_BASE_URL, debug = false } = config;\n\n const headers = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n };\n\n async function request<T>(\n method: string,\n endpoint: string,\n body?: unknown\n ): Promise<T> {\n const url = `${baseUrl}${endpoint}`;\n const idempotencyKey = crypto.randomUUID();\n\n if (debug) {\n console.log(`[Gotcha] ${method} ${endpoint}`, body);\n }\n\n const response = await fetchWithRetry(\n url,\n {\n method,\n headers: {\n ...headers,\n 'Idempotency-Key': idempotencyKey,\n },\n body: body ? JSON.stringify(body) : undefined,\n },\n DEFAULT_RETRY_CONFIG,\n debug\n );\n\n const data = await response.json();\n\n if (!response.ok) {\n const error = data.error as GotchaError;\n if (debug) {\n console.error(`[Gotcha] Error: ${error.code} - ${error.message}`);\n }\n throw error;\n }\n\n if (debug) {\n console.log(`[Gotcha] Response:`, data);\n }\n\n return data as T;\n }\n\n return {\n /**\n * Submit a response (feedback, vote, etc.)\n */\n async submitResponse(\n payload: Omit<SubmitResponsePayload, 'context'>\n ): Promise<GotchaResponse> {\n // Ensure user has an ID (anonymous if not provided)\n const user = payload.user || {};\n if (!user.id) {\n user.id = getAnonymousId();\n }\n\n const fullPayload: SubmitResponsePayload = {\n ...payload,\n user,\n context: {\n url: typeof window !== 'undefined' ? window.location.href : undefined,\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n },\n };\n\n return request<GotchaResponse>('POST', '/responses', fullPayload);\n },\n\n /**\n * Get the base URL (for debugging)\n */\n getBaseUrl(): string {\n return baseUrl;\n },\n };\n}\n\nexport type ApiClient = ReturnType<typeof createApiClient>;\n","import React, { createContext, useContext, useMemo, useState, useCallback } from 'react';\nimport { createApiClient, ApiClient } from '../api/client';\nimport { GotchaUser } from '../types';\n\nexport interface GotchaProviderProps {\n /** Your Gotcha API key */\n apiKey: string;\n /** React children */\n children: React.ReactNode;\n /** Override the API base URL (for testing/staging) */\n baseUrl?: string;\n /** Enable debug logging */\n debug?: boolean;\n /** Disable all Gotcha buttons globally */\n disabled?: boolean;\n /** Default user metadata applied to all submissions */\n defaultUser?: GotchaUser;\n}\n\nexport interface GotchaContextValue {\n client: ApiClient;\n disabled: boolean;\n defaultUser: GotchaUser;\n debug: boolean;\n // Modal management - only one open at a time\n activeModalId: string | null;\n openModal: (elementId: string) => void;\n closeModal: () => void;\n}\n\nconst GotchaContext = createContext<GotchaContextValue | null>(null);\n\nexport function GotchaProvider({\n apiKey,\n children,\n baseUrl,\n debug = false,\n disabled = false,\n defaultUser = {},\n}: GotchaProviderProps) {\n const [activeModalId, setActiveModalId] = useState<string | null>(null);\n\n const client = useMemo(\n () => createApiClient({ apiKey, baseUrl, debug }),\n [apiKey, baseUrl, debug]\n );\n\n const openModal = useCallback((elementId: string) => {\n setActiveModalId(elementId);\n }, []);\n\n const closeModal = useCallback(() => {\n setActiveModalId(null);\n }, []);\n\n const value: GotchaContextValue = useMemo(\n () => ({\n client,\n disabled,\n defaultUser,\n debug,\n activeModalId,\n openModal,\n closeModal,\n }),\n [client, disabled, defaultUser, debug, activeModalId, openModal, closeModal]\n );\n\n return (\n <GotchaContext.Provider value={value}>{children}</GotchaContext.Provider>\n );\n}\n\nexport function useGotchaContext(): GotchaContextValue {\n const context = useContext(GotchaContext);\n if (!context) {\n throw new Error('useGotchaContext must be used within a GotchaProvider');\n }\n return context;\n}\n","import { useState, useCallback } from 'react';\nimport { useGotchaContext } from '../components/GotchaProvider';\nimport { ResponseMode, GotchaUser, GotchaResponse, VoteType } from '../types';\n\ninterface UseSubmitOptions {\n elementId: string;\n mode: ResponseMode;\n experimentId?: string;\n variant?: string;\n pollOptions?: string[];\n user?: GotchaUser;\n onSuccess?: (response: GotchaResponse) => void;\n onError?: (error: Error) => void;\n}\n\ninterface SubmitData {\n content?: string;\n title?: string;\n rating?: number;\n vote?: VoteType;\n pollSelected?: string[];\n}\n\nexport function useSubmit(options: UseSubmitOptions) {\n const { client, defaultUser } = useGotchaContext();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const submit = useCallback(\n async (data: SubmitData) => {\n setIsLoading(true);\n setError(null);\n\n try {\n const response = await client.submitResponse({\n elementId: options.elementId,\n mode: options.mode,\n content: data.content,\n title: data.title,\n rating: data.rating,\n vote: data.vote,\n pollOptions: options.pollOptions,\n pollSelected: data.pollSelected,\n experimentId: options.experimentId,\n variant: options.variant,\n user: { ...defaultUser, ...options.user },\n });\n\n options.onSuccess?.(response);\n return response;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Something went wrong';\n setError(errorMessage);\n options.onError?.(err instanceof Error ? err : new Error(errorMessage));\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [client, defaultUser, options]\n );\n\n return {\n submit,\n isLoading,\n error,\n clearError: () => setError(null),\n };\n}\n","/**\n * Simple class name utility\n * Combines class names, filtering out falsy values\n */\nexport function cn(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(' ');\n}\n","/**\n * Detect if the device supports touch\n */\nexport const isTouchDevice = (): boolean => {\n if (typeof window === 'undefined') return false;\n return 'ontouchstart' in window || navigator.maxTouchPoints > 0;\n};\n\n/**\n * Get the appropriate size based on device type\n */\nexport const getResponsiveSize = (\n size: 'sm' | 'md' | 'lg',\n isTouch: boolean\n): number => {\n const sizes = {\n sm: { desktop: 24, mobile: 44 },\n md: { desktop: 32, mobile: 44 },\n lg: { desktop: 40, mobile: 48 },\n };\n\n return isTouch ? sizes[size].mobile : sizes[size].desktop;\n};\n","import React, { useState, useEffect } from 'react';\nimport { Size, Theme, GotchaStyles } from '../types';\nimport { cn } from '../utils/cn';\nimport { isTouchDevice, getResponsiveSize } from '../utils/device';\n\nexport interface GotchaButtonProps {\n size: Size;\n theme: Theme;\n customStyles?: GotchaStyles;\n showOnHover: boolean;\n touchBehavior: 'always-visible' | 'tap-to-reveal';\n onClick: () => void;\n isOpen: boolean;\n isParentHovered?: boolean;\n}\n\nexport function GotchaButton({\n size,\n theme,\n customStyles,\n showOnHover,\n touchBehavior,\n onClick,\n isOpen,\n isParentHovered = false,\n}: GotchaButtonProps) {\n const [isTouch, setIsTouch] = useState(false);\n const [tapRevealed, setTapRevealed] = useState(false);\n\n useEffect(() => {\n setIsTouch(isTouchDevice());\n }, []);\n\n // Determine visibility\n const shouldShow = (() => {\n // Always show if modal is open\n if (isOpen) return true;\n\n // Desktop with showOnHover: only show when parent is hovered\n if (!isTouch && showOnHover) {\n return isParentHovered;\n }\n\n // Touch device with tap-to-reveal: show only after first tap\n if (isTouch && touchBehavior === 'tap-to-reveal') {\n return tapRevealed;\n }\n\n // All other cases: always visible\n return true;\n })();\n\n const handleClick = () => {\n // For tap-to-reveal on touch: first tap reveals, second tap opens\n if (isTouch && touchBehavior === 'tap-to-reveal' && !tapRevealed) {\n setTapRevealed(true);\n return;\n }\n onClick();\n };\n\n const buttonSize = getResponsiveSize(size, isTouch);\n\n // Determine theme colors\n const resolvedTheme = theme === 'auto'\n ? (typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')\n : theme;\n\n const baseStyles: React.CSSProperties = {\n width: buttonSize,\n height: buttonSize,\n borderRadius: '50%',\n border: 'none',\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: resolvedTheme === 'dark' ? '#374151' : '#c7d2dc',\n color: resolvedTheme === 'dark' ? '#e5e7eb' : '#4b5563',\n boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',\n // CSS transition for animations\n transition: 'opacity 0.2s ease-out, transform 0.2s ease-out',\n opacity: shouldShow ? 1 : 0,\n transform: shouldShow ? 'scale(1)' : 'scale(0.6)',\n pointerEvents: shouldShow ? 'auto' : 'none',\n ...customStyles?.button,\n };\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n style={baseStyles}\n className={cn('gotcha-button', isOpen && 'gotcha-button--open')}\n aria-label=\"Give feedback on this feature\"\n aria-expanded={isOpen}\n aria-haspopup=\"dialog\"\n >\n <GotchaIcon size={buttonSize * 0.75} />\n </button>\n );\n}\n\nfunction GotchaIcon({ size }: { size: number }) {\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <text\n x=\"50%\"\n y=\"50%\"\n dominantBaseline=\"central\"\n textAnchor=\"middle\"\n fontSize=\"16\"\n fontWeight=\"bold\"\n fill=\"currentColor\"\n fontFamily=\"system-ui, -apple-system, sans-serif\"\n >\n G\n </text>\n </svg>\n );\n}\n","import React, { useState } from 'react';\nimport { GotchaStyles } from '../../types';\n\ninterface FeedbackModeProps {\n theme: 'light' | 'dark' | 'custom';\n placeholder?: string;\n submitText: string;\n isLoading: boolean;\n onSubmit: (data: { content?: string; rating?: number }) => void;\n customStyles?: GotchaStyles;\n}\n\nexport function FeedbackMode({\n theme,\n placeholder,\n submitText,\n isLoading,\n onSubmit,\n customStyles,\n}: FeedbackModeProps) {\n const [content, setContent] = useState('');\n const [rating, setRating] = useState<number | null>(null);\n\n const isDark = theme === 'dark';\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!content.trim() && rating === null) return;\n onSubmit({ content: content.trim() || undefined, rating: rating ?? undefined });\n };\n\n const inputStyles: React.CSSProperties = {\n width: '100%',\n padding: '10px 12px',\n border: `1px solid ${isDark ? '#374151' : '#d1d5db'}`,\n borderRadius: 6,\n backgroundColor: isDark ? '#374151' : '#ffffff',\n color: isDark ? '#f9fafb' : '#111827',\n fontSize: 14,\n resize: 'vertical',\n minHeight: 80,\n fontFamily: 'inherit',\n ...customStyles?.input,\n };\n\n const buttonStyles: React.CSSProperties = {\n width: '100%',\n padding: '10px 16px',\n border: 'none',\n borderRadius: 6,\n backgroundColor: isLoading ? (isDark ? '#4b5563' : '#9ca3af') : '#6366f1',\n color: '#ffffff',\n fontSize: 14,\n fontWeight: 500,\n cursor: isLoading ? 'not-allowed' : 'pointer',\n transition: 'background-color 150ms ease',\n ...customStyles?.submitButton,\n };\n\n return (\n <form onSubmit={handleSubmit}>\n {/* Rating (optional) */}\n <div style={{ marginBottom: 12 }}>\n <StarRating value={rating} onChange={setRating} isDark={isDark} />\n </div>\n\n {/* Text input */}\n <textarea\n value={content}\n onChange={(e) => setContent(e.target.value)}\n placeholder={placeholder || 'Share your thoughts...'}\n style={inputStyles}\n disabled={isLoading}\n aria-label=\"Your feedback\"\n />\n\n {/* Submit button */}\n <button\n type=\"submit\"\n disabled={isLoading || (!content.trim() && rating === null)}\n style={{\n ...buttonStyles,\n marginTop: 12,\n opacity: (!content.trim() && rating === null) ? 0.5 : 1,\n }}\n >\n {isLoading ? 'Submitting...' : submitText}\n </button>\n </form>\n );\n}\n\ninterface StarRatingProps {\n value: number | null;\n onChange: (rating: number) => void;\n isDark: boolean;\n}\n\nfunction StarRating({ value, onChange, isDark }: StarRatingProps) {\n const [hovered, setHovered] = useState<number | null>(null);\n\n return (\n <div\n style={{\n display: 'flex',\n gap: 4,\n }}\n role=\"group\"\n aria-label=\"Rating\"\n >\n {[1, 2, 3, 4, 5].map((star) => {\n const isFilled = (hovered ?? value ?? 0) >= star;\n return (\n <button\n key={star}\n type=\"button\"\n onClick={() => onChange(star)}\n onMouseEnter={() => setHovered(star)}\n onMouseLeave={() => setHovered(null)}\n aria-label={`Rate ${star} out of 5`}\n aria-pressed={value === star}\n style={{\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n padding: 2,\n color: isFilled ? '#f59e0b' : (isDark ? '#4b5563' : '#d1d5db'),\n transition: 'color 150ms ease',\n }}\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z\" />\n </svg>\n </button>\n );\n })}\n </div>\n );\n}\n","import React from 'react';\n\ninterface VoteModeProps {\n theme: 'light' | 'dark' | 'custom';\n isLoading: boolean;\n onSubmit: (data: { vote: 'up' | 'down' }) => void;\n}\n\nexport function VoteMode({ theme, isLoading, onSubmit }: VoteModeProps) {\n const isDark = theme === 'dark';\n\n const buttonBase: React.CSSProperties = {\n flex: 1,\n padding: '12px 16px',\n border: `1px solid ${isDark ? '#374151' : '#e5e7eb'}`,\n borderRadius: 8,\n backgroundColor: isDark ? '#374151' : '#f9fafb',\n color: isDark ? '#f9fafb' : '#111827',\n fontSize: 24,\n cursor: isLoading ? 'not-allowed' : 'pointer',\n transition: 'all 150ms ease',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 8,\n };\n\n return (\n <div\n style={{\n display: 'flex',\n gap: 12,\n }}\n role=\"group\"\n aria-label=\"Vote\"\n >\n <button\n type=\"button\"\n onClick={() => onSubmit({ vote: 'up' })}\n disabled={isLoading}\n style={buttonBase}\n aria-label=\"Vote up - I like this\"\n >\n <ThumbsUpIcon />\n <span style={{ fontSize: 14, fontWeight: 500 }}>Like</span>\n </button>\n\n <button\n type=\"button\"\n onClick={() => onSubmit({ vote: 'down' })}\n disabled={isLoading}\n style={buttonBase}\n aria-label=\"Vote down - I don't like this\"\n >\n <ThumbsDownIcon />\n <span style={{ fontSize: 14, fontWeight: 500 }}>Dislike</span>\n </button>\n </div>\n );\n}\n\nfunction ThumbsUpIcon() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3\" />\n </svg>\n );\n}\n\nfunction ThumbsDownIcon() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17\" />\n </svg>\n );\n}\n","import React, { useRef, useEffect, useState } from 'react';\nimport { Theme, GotchaStyles, ResponseMode } from '../types';\nimport { cn } from '../utils/cn';\nimport { FeedbackMode } from './modes/FeedbackMode';\nimport { VoteMode } from './modes/VoteMode';\n\nexport interface GotchaModalProps {\n mode: ResponseMode;\n theme: Theme;\n customStyles?: GotchaStyles;\n promptText?: string;\n placeholder?: string;\n submitText: string;\n thankYouMessage: string;\n // State\n isLoading: boolean;\n isSubmitted: boolean;\n error: string | null;\n // Handlers\n onSubmit: (data: { content?: string; rating?: number; vote?: 'up' | 'down' }) => void;\n onClose: () => void;\n // Position info from parent\n anchorRect?: DOMRect;\n}\n\nexport function GotchaModal({\n mode,\n theme,\n customStyles,\n promptText,\n placeholder,\n submitText,\n thankYouMessage,\n isLoading,\n isSubmitted,\n error,\n onSubmit,\n onClose,\n anchorRect,\n}: GotchaModalProps) {\n const modalRef = useRef<HTMLDivElement>(null);\n const firstFocusableRef = useRef<HTMLButtonElement>(null);\n const [isVisible, setIsVisible] = useState(false);\n\n // Trigger animation after mount\n useEffect(() => {\n // Small delay to ensure CSS transition works\n const timer = requestAnimationFrame(() => setIsVisible(true));\n return () => cancelAnimationFrame(timer);\n }, []);\n\n // Resolve theme\n const resolvedTheme = theme === 'auto'\n ? (typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')\n : theme;\n\n const isDark = resolvedTheme === 'dark';\n\n // Determine if modal should appear above or below\n const modalHeight = 280; // approximate modal height\n const spaceBelow = anchorRect\n ? window.innerHeight - anchorRect.bottom\n : window.innerHeight / 2;\n const showAbove = spaceBelow < modalHeight + 20;\n\n // Focus trap\n useEffect(() => {\n const modal = modalRef.current;\n if (!modal) return;\n\n // Focus first element\n firstFocusableRef.current?.focus();\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onClose();\n return;\n }\n\n if (e.key === 'Tab') {\n const focusableElements = modal.querySelectorAll<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n );\n const firstElement = focusableElements[0];\n const lastElement = focusableElements[focusableElements.length - 1];\n\n if (e.shiftKey && document.activeElement === firstElement) {\n e.preventDefault();\n lastElement?.focus();\n } else if (!e.shiftKey && document.activeElement === lastElement) {\n e.preventDefault();\n firstElement?.focus();\n }\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [onClose]);\n\n const defaultPrompt = mode === 'vote'\n ? 'What do you think?'\n : 'What do you think of this feature?';\n\n const modalStyles: React.CSSProperties = {\n position: 'absolute',\n left: '50%',\n width: 320,\n padding: 16,\n borderRadius: 8,\n backgroundColor: isDark ? '#1f2937' : '#ffffff',\n color: isDark ? '#f9fafb' : '#111827',\n boxShadow: '0 10px 25px rgba(0, 0, 0, 0.15)',\n border: `1px solid ${isDark ? '#374151' : '#e5e7eb'}`,\n zIndex: 9999,\n ...(showAbove\n ? { bottom: '100%', marginBottom: 8 }\n : { top: '100%', marginTop: 8 }),\n // CSS transition for animations\n transition: 'opacity 0.2s ease-out, transform 0.2s ease-out',\n opacity: isVisible ? 1 : 0,\n transform: isVisible\n ? 'translateX(-50%) scale(1) translateY(0)'\n : `translateX(-50%) scale(0.95) translateY(${showAbove ? '10px' : '-10px'})`,\n ...customStyles?.modal,\n };\n\n return (\n <div\n ref={modalRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"gotcha-modal-title\"\n style={modalStyles}\n className={cn('gotcha-modal', isDark && 'gotcha-modal--dark')}\n >\n {/* Close button */}\n <button\n ref={firstFocusableRef}\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close feedback form\"\n style={{\n position: 'absolute',\n top: 8,\n right: 8,\n width: 24,\n height: 24,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: isDark ? '#9ca3af' : '#6b7280',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 4,\n }}\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n <path\n d=\"M1 1L13 13M1 13L13 1\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n\n {/* Title */}\n <h2\n id=\"gotcha-modal-title\"\n style={{\n margin: '0 0 12px 0',\n fontSize: 14,\n fontWeight: 500,\n paddingRight: 24,\n }}\n >\n {promptText || defaultPrompt}\n </h2>\n\n {/* Success state */}\n {isSubmitted && (\n <div\n style={{\n textAlign: 'center',\n padding: '20px 0',\n color: isDark ? '#10b981' : '#059669',\n }}\n >\n <svg\n width=\"32\"\n height=\"32\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n style={{ margin: '0 auto 8px' }}\n >\n <path\n d=\"M20 6L9 17L4 12\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n <p style={{ margin: 0, fontSize: 14 }}>{thankYouMessage}</p>\n </div>\n )}\n\n {/* Error state */}\n {error && !isSubmitted && (\n <div\n style={{\n padding: 8,\n marginBottom: 12,\n borderRadius: 4,\n backgroundColor: isDark ? '#7f1d1d' : '#fef2f2',\n color: isDark ? '#fecaca' : '#dc2626',\n fontSize: 13,\n }}\n >\n {error}\n </div>\n )}\n\n {/* Form content based on mode */}\n {!isSubmitted && (\n <>\n {mode === 'feedback' && (\n <FeedbackMode\n theme={resolvedTheme}\n placeholder={placeholder}\n submitText={submitText}\n isLoading={isLoading}\n onSubmit={onSubmit}\n customStyles={customStyles}\n />\n )}\n {mode === 'vote' && (\n <VoteMode\n theme={resolvedTheme}\n isLoading={isLoading}\n onSubmit={onSubmit}\n />\n )}\n </>\n )}\n\n {/* Screen reader announcement */}\n <div aria-live=\"polite\" className=\"sr-only\" style={{ position: 'absolute', left: -9999 }}>\n {isSubmitted && 'Thank you! Your feedback has been submitted.'}\n {error && `Error: ${error}`}\n </div>\n </div>\n );\n}\n","import React, { useState, useCallback, useEffect, useRef } from 'react';\nimport {\n ResponseMode,\n GotchaUser,\n Position,\n Size,\n Theme,\n TouchBehavior,\n GotchaStyles,\n GotchaResponse,\n GotchaError,\n} from '../types';\nimport { DEFAULTS } from '../constants';\nimport { useGotchaContext } from './GotchaProvider';\nimport { useSubmit } from '../hooks/useSubmit';\nimport { GotchaButton } from './GotchaButton';\nimport { GotchaModal } from './GotchaModal';\n\nexport interface GotchaProps {\n /** Unique identifier for this element */\n elementId: string;\n\n // User data\n /** User metadata for segmentation */\n user?: GotchaUser;\n\n // Behavior\n /** Feedback mode */\n mode?: ResponseMode;\n /** Required if mode is 'ab' */\n experimentId?: string;\n /** Current A/B variant shown to user */\n variant?: string;\n\n // Poll mode specific (Phase 2)\n /** Required if mode is 'poll' (2-6 options) */\n options?: string[];\n /** Allow selecting multiple options */\n allowMultiple?: boolean;\n /** Show results after voting */\n showResults?: boolean;\n\n // Appearance\n /** Button position relative to parent */\n position?: Position;\n /** Button size */\n size?: Size;\n /** Color theme */\n theme?: Theme;\n /** Custom style overrides */\n customStyles?: GotchaStyles;\n /** Control visibility programmatically */\n visible?: boolean;\n /** Only show when parent is hovered (default: true) */\n showOnHover?: boolean;\n /** Mobile behavior (default: 'always-visible') */\n touchBehavior?: TouchBehavior;\n\n // Content\n /** Custom prompt text */\n promptText?: string;\n /** Input placeholder text */\n placeholder?: string;\n /** Submit button text */\n submitText?: string;\n /** Post-submission message */\n thankYouMessage?: string;\n\n // Callbacks\n /** Called after successful submission */\n onSubmit?: (response: GotchaResponse) => void;\n /** Called when modal opens */\n onOpen?: () => void;\n /** Called when modal closes */\n onClose?: () => void;\n /** Called on error */\n onError?: (error: GotchaError) => void;\n}\n\nexport function Gotcha({\n elementId,\n user,\n mode = 'feedback',\n experimentId,\n variant,\n options,\n allowMultiple = false,\n showResults = true,\n position = DEFAULTS.POSITION,\n size = DEFAULTS.SIZE,\n theme = DEFAULTS.THEME,\n customStyles,\n visible = true,\n showOnHover = DEFAULTS.SHOW_ON_HOVER,\n touchBehavior = DEFAULTS.TOUCH_BEHAVIOR,\n promptText,\n placeholder,\n submitText = DEFAULTS.SUBMIT_TEXT,\n thankYouMessage = DEFAULTS.THANK_YOU_MESSAGE,\n onSubmit,\n onOpen,\n onClose,\n onError,\n}: GotchaProps) {\n const { disabled, activeModalId, openModal, closeModal } = useGotchaContext();\n const [isSubmitted, setIsSubmitted] = useState(false);\n const [isParentHovered, setIsParentHovered] = useState(false);\n const [anchorRect, setAnchorRect] = useState<DOMRect | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n\n // This instance's modal is open if activeModalId matches our elementId\n const isOpen = activeModalId === elementId;\n\n // Attach hover listeners to the parent element (not the button container)\n useEffect(() => {\n if (!showOnHover) return;\n\n const container = containerRef.current;\n if (!container) return;\n\n // Find the parent element with position: relative\n const parent = container.parentElement;\n if (!parent) return;\n\n const handleMouseEnter = () => setIsParentHovered(true);\n const handleMouseLeave = () => setIsParentHovered(false);\n\n parent.addEventListener('mouseenter', handleMouseEnter);\n parent.addEventListener('mouseleave', handleMouseLeave);\n\n return () => {\n parent.removeEventListener('mouseenter', handleMouseEnter);\n parent.removeEventListener('mouseleave', handleMouseLeave);\n };\n }, [showOnHover]);\n\n const { submit, isLoading, error } = useSubmit({\n elementId,\n mode,\n experimentId,\n variant,\n pollOptions: options,\n user,\n onSuccess: (response) => {\n setIsSubmitted(true);\n onSubmit?.(response);\n // Auto-close after 2.5 seconds\n setTimeout(() => {\n closeModal();\n setIsSubmitted(false);\n }, 2500);\n },\n onError: (err) => {\n onError?.(err as unknown as GotchaError);\n },\n });\n\n const handleOpen = useCallback(() => {\n if (containerRef.current) {\n setAnchorRect(containerRef.current.getBoundingClientRect());\n }\n openModal(elementId);\n onOpen?.();\n }, [elementId, openModal, onOpen]);\n\n const handleClose = useCallback(() => {\n closeModal();\n setIsSubmitted(false);\n onClose?.();\n }, [closeModal, onClose]);\n\n const handleSubmit = useCallback(\n (data: { content?: string; rating?: number; vote?: 'up' | 'down' }) => {\n submit(data);\n },\n [submit]\n );\n\n // Don't render if disabled or not visible\n if (disabled || !visible) return null;\n\n // Position styles\n const positionStyles: Record<Position, React.CSSProperties> = {\n 'top-right': { position: 'absolute', top: 0, right: 0, transform: 'translate(50%, -50%)' },\n 'top-left': { position: 'absolute', top: 0, left: 0, transform: 'translate(-50%, -50%)' },\n 'bottom-right': { position: 'absolute', bottom: 0, right: 0, transform: 'translate(50%, 50%)' },\n 'bottom-left': { position: 'absolute', bottom: 0, left: 0, transform: 'translate(-50%, 50%)' },\n 'inline': { position: 'relative', display: 'inline-flex' },\n };\n\n return (\n <div\n ref={containerRef}\n style={{\n ...positionStyles[position],\n zIndex: isOpen ? 10000 : 50,\n }}\n className=\"gotcha-container\"\n data-gotcha-element={elementId}\n >\n <GotchaButton\n size={size}\n theme={theme}\n customStyles={customStyles}\n showOnHover={showOnHover}\n touchBehavior={touchBehavior}\n onClick={handleOpen}\n isOpen={isOpen}\n isParentHovered={isParentHovered}\n />\n\n {isOpen && (\n <GotchaModal\n mode={mode}\n theme={theme}\n customStyles={customStyles}\n promptText={promptText}\n placeholder={placeholder}\n submitText={submitText}\n thankYouMessage={thankYouMessage}\n isLoading={isLoading}\n isSubmitted={isSubmitted}\n error={error}\n onSubmit={handleSubmit}\n onClose={handleClose}\n anchorRect={anchorRect || undefined}\n />\n )}\n </div>\n );\n}\n","import { useGotchaContext } from '../components/GotchaProvider';\n\n/**\n * Hook to access Gotcha context\n * Must be used within a GotchaProvider\n */\nexport function useGotcha() {\n const { client, disabled, defaultUser, debug } = useGotchaContext();\n\n return {\n /** The API client for manual submissions */\n client,\n /** Whether Gotcha is globally disabled */\n disabled,\n /** Default user metadata */\n defaultUser,\n /** Whether debug mode is enabled */\n debug,\n /** Submit feedback programmatically */\n submitFeedback: client.submitResponse.bind(client),\n };\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import {createContext,useState,useMemo,useCallback,useRef,useEffect,useContext}from'react';import {jsx,jsxs,Fragment}from'react/jsx-runtime';var j="https://api.gotcha.cx/v1";var V={ANONYMOUS_ID:"gotcha_anonymous_id"},y={POSITION:"top-right",SIZE:"md",THEME:"auto",SHOW_ON_HOVER:true,TOUCH_BEHAVIOR:"always-visible",SUBMIT_TEXT:"Submit",THANK_YOU_MESSAGE:"Thanks for your feedback!"};var P={MAX_RETRIES:2,BASE_DELAY_MS:500,MAX_DELAY_MS:5e3};function J(){if(typeof window>"u")return `anon_${crypto.randomUUID()}`;let e=localStorage.getItem(V.ANONYMOUS_ID);if(e)return e;let s=`anon_${crypto.randomUUID()}`;return localStorage.setItem(V.ANONYMOUS_ID,s),s}var ee={maxRetries:P.MAX_RETRIES,baseDelayMs:P.BASE_DELAY_MS,maxDelayMs:P.MAX_DELAY_MS};async function we(e,s,o=ee,n=false){let i=null;for(let a=0;a<=o.maxRetries;a++){try{n&&a>0&&console.log(`[Gotcha] Retry attempt ${a}/${o.maxRetries}`);let t=await fetch(e,s);if(t.status>=400&&t.status<500&&t.status!==429||t.ok)return t;i=new Error(`HTTP ${t.status}`);}catch(t){i=t,n&&console.log(`[Gotcha] Network error: ${i.message}`);}if(a<o.maxRetries){let t=Math.min(o.baseDelayMs*Math.pow(2,a),o.maxDelayMs);await new Promise(u=>setTimeout(u,t));}}throw i}function te(e){let{apiKey:s,baseUrl:o=j,debug:n=false}=e,i={"Content-Type":"application/json",Authorization:`Bearer ${s}`};async function a(t,u,r){let l=`${o}${u}`,c=crypto.randomUUID();n&&console.log(`[Gotcha] ${t} ${u}`,r);let p=await we(l,{method:t,headers:{...i,"Idempotency-Key":c},body:r?JSON.stringify(r):void 0},ee,n),d=await p.json();if(!p.ok){let m=d.error;throw n&&console.error(`[Gotcha] Error: ${m.code} - ${m.message}`),m}return n&&console.log("[Gotcha] Response:",d),d}return {async submitResponse(t){let u=t.user||{};u.id||(u.id=J());let r={...t,user:u,context:{url:typeof window<"u"?window.location.href:void 0,userAgent:typeof navigator<"u"?navigator.userAgent:void 0}};return a("POST","/responses",r)},getBaseUrl(){return o}}}var re=createContext(null);function Me({apiKey:e,children:s,baseUrl:o,debug:n=false,disabled:i=false,defaultUser:a={}}){let[t,u]=useState(null),r=useMemo(()=>te({apiKey:e,baseUrl:o,debug:n}),[e,o,n]),l=useCallback(d=>{u(d);},[]),c=useCallback(()=>{u(null);},[]),p=useMemo(()=>({client:r,disabled:i,defaultUser:a,debug:n,activeModalId:t,openModal:l,closeModal:c}),[r,i,a,n,t,l,c]);return jsx(re.Provider,{value:p,children:s})}function T(){let e=useContext(re);if(!e)throw new Error("useGotchaContext must be used within a GotchaProvider");return e}function ae(e){let{client:s,defaultUser:o}=T(),[n,i]=useState(false),[a,t]=useState(null);return {submit:useCallback(async r=>{i(true),t(null);try{let l=await s.submitResponse({elementId:e.elementId,mode:e.mode,content:r.content,title:r.title,rating:r.rating,vote:r.vote,pollOptions:e.pollOptions,pollSelected:r.pollSelected,experimentId:e.experimentId,variant:e.variant,user:{...o,...e.user}});return e.onSuccess?.(l),l}catch(l){let c=l instanceof Error?l.message:"Something went wrong";throw t(c),e.onError?.(l instanceof Error?l:new Error(c)),l}finally{i(false);}},[s,o,e]),isLoading:n,error:a,clearError:()=>t(null)}}function D(...e){return e.filter(Boolean).join(" ")}var ie=()=>typeof window>"u"?false:"ontouchstart"in window||navigator.maxTouchPoints>0,le=(e,s)=>{let o={sm:{desktop:24,mobile:44},md:{desktop:32,mobile:44},lg:{desktop:40,mobile:48}};return s?o[e].mobile:o[e].desktop};function ue({size:e,theme:s,customStyles:o,showOnHover:n,touchBehavior:i,onClick:a,isOpen:t,isParentHovered:u=false}){let[r,l]=useState(false),[c,p]=useState(false);useEffect(()=>{l(ie());},[]);let d=t?true:!r&&n?u:r&&i==="tap-to-reveal"?c:true,m=()=>{if(r&&i==="tap-to-reveal"&&!c){p(true);return}a();},f=le(e,r),E=s==="auto"?typeof window<"u"&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":s,k={width:f,height:f,borderRadius:"50%",border:"none",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:E==="dark"?"#374151":"#c7d2dc",color:E==="dark"?"#e5e7eb":"#4b5563",boxShadow:"0 1px 3px rgba(0, 0, 0, 0.1)",transition:"opacity 0.2s ease-out, transform 0.2s ease-out",opacity:d?1:0,transform:d?"scale(1)":"scale(0.6)",pointerEvents:d?"auto":"none",...o?.button};return jsx("button",{type:"button",onClick:m,style:k,className:D("gotcha-button",t&&"gotcha-button--open"),"aria-label":"Give feedback on this feature","aria-expanded":t,"aria-haspopup":"dialog",children:jsx(_e,{size:f*.75})})}function _e({size:e}){return jsx("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:jsx("text",{x:"50%",y:"50%",dominantBaseline:"central",textAnchor:"middle",fontSize:"16",fontWeight:"bold",fill:"currentColor",fontFamily:"system-ui, -apple-system, sans-serif",children:"G"})})}function de({theme:e,placeholder:s,submitText:o,isLoading:n,onSubmit:i,customStyles:a}){let[t,u]=useState(""),[r,l]=useState(null),c=e==="dark",p=f=>{f.preventDefault(),!(!t.trim()&&r===null)&&i({content:t.trim()||void 0,rating:r??void 0});},d={width:"100%",padding:"10px 12px",border:`1px solid ${c?"#374151":"#d1d5db"}`,borderRadius:6,backgroundColor:c?"#374151":"#ffffff",color:c?"#f9fafb":"#111827",fontSize:14,resize:"vertical",minHeight:80,fontFamily:"inherit",...a?.input},m={width:"100%",padding:"10px 16px",border:"none",borderRadius:6,backgroundColor:n?c?"#4b5563":"#9ca3af":"#6366f1",color:"#ffffff",fontSize:14,fontWeight:500,cursor:n?"not-allowed":"pointer",transition:"background-color 150ms ease",...a?.submitButton};return jsxs("form",{onSubmit:p,children:[jsx("div",{style:{marginBottom:12},children:jsx(Oe,{value:r,onChange:l,isDark:c})}),jsx("textarea",{value:t,onChange:f=>u(f.target.value),placeholder:s||"Share your thoughts...",style:d,disabled:n,"aria-label":"Your feedback"}),jsx("button",{type:"submit",disabled:n||!t.trim()&&r===null,style:{...m,marginTop:12,opacity:!t.trim()&&r===null?.5:1},children:n?"Submitting...":o})]})}function Oe({value:e,onChange:s,isDark:o}){let[n,i]=useState(null);return jsx("div",{style:{display:"flex",gap:4},role:"group","aria-label":"Rating",children:[1,2,3,4,5].map(a=>{let t=(n??e??0)>=a;return jsx("button",{type:"button",onClick:()=>s(a),onMouseEnter:()=>i(a),onMouseLeave:()=>i(null),"aria-label":`Rate ${a} out of 5`,"aria-pressed":e===a,style:{background:"none",border:"none",cursor:"pointer",padding:2,color:t?"#f59e0b":o?"#4b5563":"#d1d5db",transition:"color 150ms ease"},children:jsx("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"currentColor",children:jsx("path",{d:"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"})})},a)})})}function pe({theme:e,isLoading:s,onSubmit:o}){let n=e==="dark",i={flex:1,padding:"12px 16px",border:`1px solid ${n?"#374151":"#e5e7eb"}`,borderRadius:8,backgroundColor:n?"#374151":"#f9fafb",color:n?"#f9fafb":"#111827",fontSize:24,cursor:s?"not-allowed":"pointer",transition:"all 150ms ease",display:"flex",alignItems:"center",justifyContent:"center",gap:8};return jsxs("div",{style:{display:"flex",gap:12},role:"group","aria-label":"Vote",children:[jsxs("button",{type:"button",onClick:()=>o({vote:"up"}),disabled:s,style:i,"aria-label":"Vote up - I like this",children:[jsx(De,{}),jsx("span",{style:{fontSize:14,fontWeight:500},children:"Like"})]}),jsxs("button",{type:"button",onClick:()=>o({vote:"down"}),disabled:s,style:i,"aria-label":"Vote down - I don't like this",children:[jsx(Le,{}),jsx("span",{style:{fontSize:14,fontWeight:500},children:"Dislike"})]})]})}function De(){return jsx("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",children:jsx("path",{d:"M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"})})}function Le(){return jsx("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",children:jsx("path",{d:"M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"})})}function he({mode:e,theme:s,customStyles:o,promptText:n,placeholder:i,submitText:a,thankYouMessage:t,isLoading:u,isSubmitted:r,error:l,onSubmit:c,onClose:p,anchorRect:d}){let m=useRef(null),f=useRef(null),[E,k]=useState(false);useEffect(()=>{let x=requestAnimationFrame(()=>k(true));return ()=>cancelAnimationFrame(x)},[]);let G=s==="auto"?typeof window<"u"&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":s,h=G==="dark",M=(d?window.innerHeight-d.bottom:window.innerHeight/2)<280+20;useEffect(()=>{let x=m.current;if(!x)return;f.current?.focus();let I=b=>{if(b.key==="Escape"){p();return}if(b.key==="Tab"){let C=x.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),w=C[0],_=C[C.length-1];b.shiftKey&&document.activeElement===w?(b.preventDefault(),_?.focus()):!b.shiftKey&&document.activeElement===_&&(b.preventDefault(),w?.focus());}};return document.addEventListener("keydown",I),()=>document.removeEventListener("keydown",I)},[p]);let B=e==="vote"?"What do you think?":"What do you think of this feature?",H={position:"absolute",left:"50%",width:320,padding:16,borderRadius:8,backgroundColor:h?"#1f2937":"#ffffff",color:h?"#f9fafb":"#111827",boxShadow:"0 10px 25px rgba(0, 0, 0, 0.15)",border:`1px solid ${h?"#374151":"#e5e7eb"}`,zIndex:9999,...M?{bottom:"100%",marginBottom:8}:{top:"100%",marginTop:8},transition:"opacity 0.2s ease-out, transform 0.2s ease-out",opacity:E?1:0,transform:E?"translateX(-50%) scale(1) translateY(0)":`translateX(-50%) scale(0.95) translateY(${M?"10px":"-10px"})`,...o?.modal};return jsxs("div",{ref:m,role:"dialog","aria-modal":"true","aria-labelledby":"gotcha-modal-title",style:H,className:D("gotcha-modal",h&&"gotcha-modal--dark"),children:[jsx("button",{ref:f,type:"button",onClick:p,"aria-label":"Close feedback form",style:{position:"absolute",top:8,right:8,width:24,height:24,border:"none",background:"none",cursor:"pointer",color:h?"#9ca3af":"#6b7280",display:"flex",alignItems:"center",justifyContent:"center",borderRadius:4},children:jsx("svg",{width:"14",height:"14",viewBox:"0 0 14 14",fill:"none",children:jsx("path",{d:"M1 1L13 13M1 13L13 1",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round"})})}),jsx("h2",{id:"gotcha-modal-title",style:{margin:"0 0 12px 0",fontSize:14,fontWeight:500,paddingRight:24},children:n||B}),r&&jsxs("div",{style:{textAlign:"center",padding:"20px 0",color:h?"#10b981":"#059669"},children:[jsx("svg",{width:"32",height:"32",viewBox:"0 0 24 24",fill:"none",style:{margin:"0 auto 8px"},children:jsx("path",{d:"M20 6L9 17L4 12",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})}),jsx("p",{style:{margin:0,fontSize:14},children:t})]}),l&&!r&&jsx("div",{style:{padding:8,marginBottom:12,borderRadius:4,backgroundColor:h?"#7f1d1d":"#fef2f2",color:h?"#fecaca":"#dc2626",fontSize:13},children:l}),!r&&jsxs(Fragment,{children:[e==="feedback"&&jsx(de,{theme:G,placeholder:i,submitText:a,isLoading:u,onSubmit:c,customStyles:o}),e==="vote"&&jsx(pe,{theme:G,isLoading:u,onSubmit:c})]}),jsxs("div",{"aria-live":"polite",className:"sr-only",style:{position:"absolute",left:-9999},children:[r&&"Thank you! Your feedback has been submitted.",l&&`Error: ${l}`]})]})}function ze({elementId:e,user:s,mode:o="feedback",experimentId:n,variant:i,options:a,allowMultiple:t=false,showResults:u=true,position:r=y.POSITION,size:l=y.SIZE,theme:c=y.THEME,customStyles:p,visible:d=true,showOnHover:m=y.SHOW_ON_HOVER,touchBehavior:f=y.TOUCH_BEHAVIOR,promptText:E,placeholder:k,submitText:G=y.SUBMIT_TEXT,thankYouMessage:h=y.THANK_YOU_MESSAGE,onSubmit:K,onOpen:N,onClose:M,onError:B}){let{disabled:H,activeModalId:x,openModal:I,closeModal:b}=T(),[C,w]=useState(false),[_,X]=useState(false),[ge,ve]=useState(null),O=useRef(null),z=x===e;useEffect(()=>{if(!m)return;let v=O.current;if(!v)return;let A=v.parentElement;if(!A)return;let Q=()=>X(true),Z=()=>X(false);return A.addEventListener("mouseenter",Q),A.addEventListener("mouseleave",Z),()=>{A.removeEventListener("mouseenter",Q),A.removeEventListener("mouseleave",Z);}},[m]);let{submit:q,isLoading:ye,error:Se}=ae({elementId:e,mode:o,experimentId:n,variant:i,pollOptions:a,user:s,onSuccess:v=>{w(true),K?.(v),setTimeout(()=>{b(),w(false);},2500);},onError:v=>{B?.(v);}}),Re=useCallback(()=>{O.current&&ve(O.current.getBoundingClientRect()),I(e),N?.();},[e,I,N]),Ee=useCallback(()=>{b(),w(false),M?.();},[b,M]),xe=useCallback(v=>{q(v);},[q]);return H||!d?null:jsxs("div",{ref:O,style:{...{"top-right":{position:"absolute",top:0,right:0,transform:"translate(50%, -50%)"},"top-left":{position:"absolute",top:0,left:0,transform:"translate(-50%, -50%)"},"bottom-right":{position:"absolute",bottom:0,right:0,transform:"translate(50%, 50%)"},"bottom-left":{position:"absolute",bottom:0,left:0,transform:"translate(-50%, 50%)"},inline:{position:"relative",display:"inline-flex"}}[r],zIndex:z?1e4:50},className:"gotcha-container","data-gotcha-element":e,children:[jsx(ue,{size:l,theme:c,customStyles:p,showOnHover:m,touchBehavior:f,onClick:Re,isOpen:z,isParentHovered:_}),z&&jsx(he,{mode:o,theme:c,customStyles:p,promptText:E,placeholder:k,submitText:G,thankYouMessage:h,isLoading:ye,isSubmitted:C,error:Se,onSubmit:xe,onClose:Ee,anchorRect:ge||void 0})]})}function Ye(){let{client:e,disabled:s,defaultUser:o,debug:n}=T();return {client:e,disabled:s,defaultUser:o,debug:n,submitFeedback:e.submitResponse.bind(e)}}
2
+ export{ze as Gotcha,Me as GotchaProvider,Ye as useGotcha};//# sourceMappingURL=index.mjs.map
3
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/utils/anonymous.ts","../src/api/client.ts","../src/components/GotchaProvider.tsx","../src/hooks/useSubmit.ts","../src/utils/cn.ts","../src/utils/device.ts","../src/components/GotchaButton.tsx","../src/components/modes/FeedbackMode.tsx","../src/components/modes/VoteMode.tsx","../src/components/GotchaModal.tsx","../src/components/Gotcha.tsx","../src/hooks/useGotcha.ts"],"names":["API_BASE_URL","STORAGE_KEYS","DEFAULTS","RETRY_CONFIG","getAnonymousId","stored","id","DEFAULT_RETRY_CONFIG","fetchWithRetry","url","options","config","debug","lastError","attempt","response","error","delay","resolve","createApiClient","apiKey","baseUrl","headers","request","method","endpoint","body","idempotencyKey","data","payload","user","fullPayload","GotchaContext","createContext","GotchaProvider","children","disabled","defaultUser","activeModalId","setActiveModalId","useState","client","useMemo","openModal","useCallback","elementId","closeModal","value","jsx","useGotchaContext","context","useContext","useSubmit","isLoading","setIsLoading","setError","err","errorMessage","cn","classes","isTouchDevice","getResponsiveSize","size","isTouch","sizes","GotchaButton","theme","customStyles","showOnHover","touchBehavior","onClick","isOpen","isParentHovered","setIsTouch","tapRevealed","setTapRevealed","useEffect","shouldShow","handleClick","buttonSize","resolvedTheme","baseStyles","GotchaIcon","FeedbackMode","placeholder","submitText","onSubmit","content","setContent","rating","setRating","isDark","handleSubmit","e","inputStyles","buttonStyles","jsxs","StarRating","onChange","hovered","setHovered","star","isFilled","VoteMode","buttonBase","ThumbsUpIcon","ThumbsDownIcon","GotchaModal","mode","promptText","thankYouMessage","isSubmitted","onClose","anchorRect","modalRef","useRef","firstFocusableRef","isVisible","setIsVisible","timer","showAbove","modal","handleKeyDown","focusableElements","firstElement","lastElement","defaultPrompt","modalStyles","Fragment","Gotcha","experimentId","variant","allowMultiple","showResults","position","visible","onOpen","onError","setIsSubmitted","setIsParentHovered","setAnchorRect","containerRef","container","parent","handleMouseEnter","handleMouseLeave","submit","handleOpen","handleClose","useGotcha"],"mappings":"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","file":"index.mjs","sourcesContent":["// API URLs\nexport const API_BASE_URL = 'https://api.gotcha.cx/v1';\nexport const API_STAGING_URL = 'https://api.staging.gotcha.cx/v1';\n\n// Error codes\nexport const ERROR_CODES = {\n INVALID_API_KEY: 'INVALID_API_KEY',\n ORIGIN_NOT_ALLOWED: 'ORIGIN_NOT_ALLOWED',\n RATE_LIMITED: 'RATE_LIMITED',\n QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',\n INVALID_REQUEST: 'INVALID_REQUEST',\n USER_NOT_FOUND: 'USER_NOT_FOUND',\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n} as const;\n\n// Local storage keys\nexport const STORAGE_KEYS = {\n ANONYMOUS_ID: 'gotcha_anonymous_id',\n OFFLINE_QUEUE: 'gotcha_offline_queue',\n} as const;\n\n// Default values\nexport const DEFAULTS = {\n POSITION: 'top-right' as const,\n SIZE: 'md' as const,\n THEME: 'auto' as const,\n SHOW_ON_HOVER: true,\n TOUCH_BEHAVIOR: 'always-visible' as const,\n SUBMIT_TEXT: 'Submit',\n THANK_YOU_MESSAGE: 'Thanks for your feedback!',\n} as const;\n\n// Size mappings (desktop/mobile in pixels)\nexport const SIZE_MAP = {\n sm: { desktop: 24, mobile: 44 },\n md: { desktop: 32, mobile: 44 },\n lg: { desktop: 40, mobile: 48 },\n} as const;\n\n// Retry config\nexport const RETRY_CONFIG = {\n MAX_RETRIES: 2,\n BASE_DELAY_MS: 500,\n MAX_DELAY_MS: 5000,\n} as const;\n","import { STORAGE_KEYS } from '../constants';\n\n/**\n * Get or create an anonymous user ID\n * Stored in localStorage for consistency across sessions\n */\nexport function getAnonymousId(): string {\n if (typeof window === 'undefined') {\n // SSR fallback - generate but don't persist\n return `anon_${crypto.randomUUID()}`;\n }\n\n const stored = localStorage.getItem(STORAGE_KEYS.ANONYMOUS_ID);\n if (stored) return stored;\n\n const id = `anon_${crypto.randomUUID()}`;\n localStorage.setItem(STORAGE_KEYS.ANONYMOUS_ID, id);\n return id;\n}\n\n/**\n * Clear the anonymous ID (useful for testing)\n */\nexport function clearAnonymousId(): void {\n if (typeof window !== 'undefined') {\n localStorage.removeItem(STORAGE_KEYS.ANONYMOUS_ID);\n }\n}\n","import { API_BASE_URL, RETRY_CONFIG } from '../constants';\nimport { SubmitResponsePayload, GotchaResponse, GotchaError } from '../types';\nimport { getAnonymousId } from '../utils/anonymous';\n\ninterface ApiClientConfig {\n apiKey: string;\n baseUrl?: string;\n debug?: boolean;\n}\n\ninterface RetryConfig {\n maxRetries: number;\n baseDelayMs: number;\n maxDelayMs: number;\n}\n\nconst DEFAULT_RETRY_CONFIG: RetryConfig = {\n maxRetries: RETRY_CONFIG.MAX_RETRIES,\n baseDelayMs: RETRY_CONFIG.BASE_DELAY_MS,\n maxDelayMs: RETRY_CONFIG.MAX_DELAY_MS,\n};\n\n/**\n * Fetch with automatic retry and exponential backoff\n */\nasync function fetchWithRetry(\n url: string,\n options: RequestInit,\n config: RetryConfig = DEFAULT_RETRY_CONFIG,\n debug: boolean = false\n): Promise<Response> {\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n if (debug && attempt > 0) {\n console.log(`[Gotcha] Retry attempt ${attempt}/${config.maxRetries}`);\n }\n\n const response = await fetch(url, options);\n\n // Don't retry client errors (4xx) except 429 (rate limit)\n if (response.status >= 400 && response.status < 500 && response.status !== 429) {\n return response;\n }\n\n // Success - return immediately\n if (response.ok) {\n return response;\n }\n\n lastError = new Error(`HTTP ${response.status}`);\n } catch (error) {\n // Network error - retry\n lastError = error as Error;\n if (debug) {\n console.log(`[Gotcha] Network error: ${lastError.message}`);\n }\n }\n\n // Don't delay after last attempt\n if (attempt < config.maxRetries) {\n const delay = Math.min(\n config.baseDelayMs * Math.pow(2, attempt),\n config.maxDelayMs\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw lastError;\n}\n\nexport function createApiClient(config: ApiClientConfig) {\n const { apiKey, baseUrl = API_BASE_URL, debug = false } = config;\n\n const headers = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n };\n\n async function request<T>(\n method: string,\n endpoint: string,\n body?: unknown\n ): Promise<T> {\n const url = `${baseUrl}${endpoint}`;\n const idempotencyKey = crypto.randomUUID();\n\n if (debug) {\n console.log(`[Gotcha] ${method} ${endpoint}`, body);\n }\n\n const response = await fetchWithRetry(\n url,\n {\n method,\n headers: {\n ...headers,\n 'Idempotency-Key': idempotencyKey,\n },\n body: body ? JSON.stringify(body) : undefined,\n },\n DEFAULT_RETRY_CONFIG,\n debug\n );\n\n const data = await response.json();\n\n if (!response.ok) {\n const error = data.error as GotchaError;\n if (debug) {\n console.error(`[Gotcha] Error: ${error.code} - ${error.message}`);\n }\n throw error;\n }\n\n if (debug) {\n console.log(`[Gotcha] Response:`, data);\n }\n\n return data as T;\n }\n\n return {\n /**\n * Submit a response (feedback, vote, etc.)\n */\n async submitResponse(\n payload: Omit<SubmitResponsePayload, 'context'>\n ): Promise<GotchaResponse> {\n // Ensure user has an ID (anonymous if not provided)\n const user = payload.user || {};\n if (!user.id) {\n user.id = getAnonymousId();\n }\n\n const fullPayload: SubmitResponsePayload = {\n ...payload,\n user,\n context: {\n url: typeof window !== 'undefined' ? window.location.href : undefined,\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n },\n };\n\n return request<GotchaResponse>('POST', '/responses', fullPayload);\n },\n\n /**\n * Get the base URL (for debugging)\n */\n getBaseUrl(): string {\n return baseUrl;\n },\n };\n}\n\nexport type ApiClient = ReturnType<typeof createApiClient>;\n","import React, { createContext, useContext, useMemo, useState, useCallback } from 'react';\nimport { createApiClient, ApiClient } from '../api/client';\nimport { GotchaUser } from '../types';\n\nexport interface GotchaProviderProps {\n /** Your Gotcha API key */\n apiKey: string;\n /** React children */\n children: React.ReactNode;\n /** Override the API base URL (for testing/staging) */\n baseUrl?: string;\n /** Enable debug logging */\n debug?: boolean;\n /** Disable all Gotcha buttons globally */\n disabled?: boolean;\n /** Default user metadata applied to all submissions */\n defaultUser?: GotchaUser;\n}\n\nexport interface GotchaContextValue {\n client: ApiClient;\n disabled: boolean;\n defaultUser: GotchaUser;\n debug: boolean;\n // Modal management - only one open at a time\n activeModalId: string | null;\n openModal: (elementId: string) => void;\n closeModal: () => void;\n}\n\nconst GotchaContext = createContext<GotchaContextValue | null>(null);\n\nexport function GotchaProvider({\n apiKey,\n children,\n baseUrl,\n debug = false,\n disabled = false,\n defaultUser = {},\n}: GotchaProviderProps) {\n const [activeModalId, setActiveModalId] = useState<string | null>(null);\n\n const client = useMemo(\n () => createApiClient({ apiKey, baseUrl, debug }),\n [apiKey, baseUrl, debug]\n );\n\n const openModal = useCallback((elementId: string) => {\n setActiveModalId(elementId);\n }, []);\n\n const closeModal = useCallback(() => {\n setActiveModalId(null);\n }, []);\n\n const value: GotchaContextValue = useMemo(\n () => ({\n client,\n disabled,\n defaultUser,\n debug,\n activeModalId,\n openModal,\n closeModal,\n }),\n [client, disabled, defaultUser, debug, activeModalId, openModal, closeModal]\n );\n\n return (\n <GotchaContext.Provider value={value}>{children}</GotchaContext.Provider>\n );\n}\n\nexport function useGotchaContext(): GotchaContextValue {\n const context = useContext(GotchaContext);\n if (!context) {\n throw new Error('useGotchaContext must be used within a GotchaProvider');\n }\n return context;\n}\n","import { useState, useCallback } from 'react';\nimport { useGotchaContext } from '../components/GotchaProvider';\nimport { ResponseMode, GotchaUser, GotchaResponse, VoteType } from '../types';\n\ninterface UseSubmitOptions {\n elementId: string;\n mode: ResponseMode;\n experimentId?: string;\n variant?: string;\n pollOptions?: string[];\n user?: GotchaUser;\n onSuccess?: (response: GotchaResponse) => void;\n onError?: (error: Error) => void;\n}\n\ninterface SubmitData {\n content?: string;\n title?: string;\n rating?: number;\n vote?: VoteType;\n pollSelected?: string[];\n}\n\nexport function useSubmit(options: UseSubmitOptions) {\n const { client, defaultUser } = useGotchaContext();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const submit = useCallback(\n async (data: SubmitData) => {\n setIsLoading(true);\n setError(null);\n\n try {\n const response = await client.submitResponse({\n elementId: options.elementId,\n mode: options.mode,\n content: data.content,\n title: data.title,\n rating: data.rating,\n vote: data.vote,\n pollOptions: options.pollOptions,\n pollSelected: data.pollSelected,\n experimentId: options.experimentId,\n variant: options.variant,\n user: { ...defaultUser, ...options.user },\n });\n\n options.onSuccess?.(response);\n return response;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Something went wrong';\n setError(errorMessage);\n options.onError?.(err instanceof Error ? err : new Error(errorMessage));\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [client, defaultUser, options]\n );\n\n return {\n submit,\n isLoading,\n error,\n clearError: () => setError(null),\n };\n}\n","/**\n * Simple class name utility\n * Combines class names, filtering out falsy values\n */\nexport function cn(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(' ');\n}\n","/**\n * Detect if the device supports touch\n */\nexport const isTouchDevice = (): boolean => {\n if (typeof window === 'undefined') return false;\n return 'ontouchstart' in window || navigator.maxTouchPoints > 0;\n};\n\n/**\n * Get the appropriate size based on device type\n */\nexport const getResponsiveSize = (\n size: 'sm' | 'md' | 'lg',\n isTouch: boolean\n): number => {\n const sizes = {\n sm: { desktop: 24, mobile: 44 },\n md: { desktop: 32, mobile: 44 },\n lg: { desktop: 40, mobile: 48 },\n };\n\n return isTouch ? sizes[size].mobile : sizes[size].desktop;\n};\n","import React, { useState, useEffect } from 'react';\nimport { Size, Theme, GotchaStyles } from '../types';\nimport { cn } from '../utils/cn';\nimport { isTouchDevice, getResponsiveSize } from '../utils/device';\n\nexport interface GotchaButtonProps {\n size: Size;\n theme: Theme;\n customStyles?: GotchaStyles;\n showOnHover: boolean;\n touchBehavior: 'always-visible' | 'tap-to-reveal';\n onClick: () => void;\n isOpen: boolean;\n isParentHovered?: boolean;\n}\n\nexport function GotchaButton({\n size,\n theme,\n customStyles,\n showOnHover,\n touchBehavior,\n onClick,\n isOpen,\n isParentHovered = false,\n}: GotchaButtonProps) {\n const [isTouch, setIsTouch] = useState(false);\n const [tapRevealed, setTapRevealed] = useState(false);\n\n useEffect(() => {\n setIsTouch(isTouchDevice());\n }, []);\n\n // Determine visibility\n const shouldShow = (() => {\n // Always show if modal is open\n if (isOpen) return true;\n\n // Desktop with showOnHover: only show when parent is hovered\n if (!isTouch && showOnHover) {\n return isParentHovered;\n }\n\n // Touch device with tap-to-reveal: show only after first tap\n if (isTouch && touchBehavior === 'tap-to-reveal') {\n return tapRevealed;\n }\n\n // All other cases: always visible\n return true;\n })();\n\n const handleClick = () => {\n // For tap-to-reveal on touch: first tap reveals, second tap opens\n if (isTouch && touchBehavior === 'tap-to-reveal' && !tapRevealed) {\n setTapRevealed(true);\n return;\n }\n onClick();\n };\n\n const buttonSize = getResponsiveSize(size, isTouch);\n\n // Determine theme colors\n const resolvedTheme = theme === 'auto'\n ? (typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')\n : theme;\n\n const baseStyles: React.CSSProperties = {\n width: buttonSize,\n height: buttonSize,\n borderRadius: '50%',\n border: 'none',\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: resolvedTheme === 'dark' ? '#374151' : '#c7d2dc',\n color: resolvedTheme === 'dark' ? '#e5e7eb' : '#4b5563',\n boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',\n // CSS transition for animations\n transition: 'opacity 0.2s ease-out, transform 0.2s ease-out',\n opacity: shouldShow ? 1 : 0,\n transform: shouldShow ? 'scale(1)' : 'scale(0.6)',\n pointerEvents: shouldShow ? 'auto' : 'none',\n ...customStyles?.button,\n };\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n style={baseStyles}\n className={cn('gotcha-button', isOpen && 'gotcha-button--open')}\n aria-label=\"Give feedback on this feature\"\n aria-expanded={isOpen}\n aria-haspopup=\"dialog\"\n >\n <GotchaIcon size={buttonSize * 0.75} />\n </button>\n );\n}\n\nfunction GotchaIcon({ size }: { size: number }) {\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <text\n x=\"50%\"\n y=\"50%\"\n dominantBaseline=\"central\"\n textAnchor=\"middle\"\n fontSize=\"16\"\n fontWeight=\"bold\"\n fill=\"currentColor\"\n fontFamily=\"system-ui, -apple-system, sans-serif\"\n >\n G\n </text>\n </svg>\n );\n}\n","import React, { useState } from 'react';\nimport { GotchaStyles } from '../../types';\n\ninterface FeedbackModeProps {\n theme: 'light' | 'dark' | 'custom';\n placeholder?: string;\n submitText: string;\n isLoading: boolean;\n onSubmit: (data: { content?: string; rating?: number }) => void;\n customStyles?: GotchaStyles;\n}\n\nexport function FeedbackMode({\n theme,\n placeholder,\n submitText,\n isLoading,\n onSubmit,\n customStyles,\n}: FeedbackModeProps) {\n const [content, setContent] = useState('');\n const [rating, setRating] = useState<number | null>(null);\n\n const isDark = theme === 'dark';\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!content.trim() && rating === null) return;\n onSubmit({ content: content.trim() || undefined, rating: rating ?? undefined });\n };\n\n const inputStyles: React.CSSProperties = {\n width: '100%',\n padding: '10px 12px',\n border: `1px solid ${isDark ? '#374151' : '#d1d5db'}`,\n borderRadius: 6,\n backgroundColor: isDark ? '#374151' : '#ffffff',\n color: isDark ? '#f9fafb' : '#111827',\n fontSize: 14,\n resize: 'vertical',\n minHeight: 80,\n fontFamily: 'inherit',\n ...customStyles?.input,\n };\n\n const buttonStyles: React.CSSProperties = {\n width: '100%',\n padding: '10px 16px',\n border: 'none',\n borderRadius: 6,\n backgroundColor: isLoading ? (isDark ? '#4b5563' : '#9ca3af') : '#6366f1',\n color: '#ffffff',\n fontSize: 14,\n fontWeight: 500,\n cursor: isLoading ? 'not-allowed' : 'pointer',\n transition: 'background-color 150ms ease',\n ...customStyles?.submitButton,\n };\n\n return (\n <form onSubmit={handleSubmit}>\n {/* Rating (optional) */}\n <div style={{ marginBottom: 12 }}>\n <StarRating value={rating} onChange={setRating} isDark={isDark} />\n </div>\n\n {/* Text input */}\n <textarea\n value={content}\n onChange={(e) => setContent(e.target.value)}\n placeholder={placeholder || 'Share your thoughts...'}\n style={inputStyles}\n disabled={isLoading}\n aria-label=\"Your feedback\"\n />\n\n {/* Submit button */}\n <button\n type=\"submit\"\n disabled={isLoading || (!content.trim() && rating === null)}\n style={{\n ...buttonStyles,\n marginTop: 12,\n opacity: (!content.trim() && rating === null) ? 0.5 : 1,\n }}\n >\n {isLoading ? 'Submitting...' : submitText}\n </button>\n </form>\n );\n}\n\ninterface StarRatingProps {\n value: number | null;\n onChange: (rating: number) => void;\n isDark: boolean;\n}\n\nfunction StarRating({ value, onChange, isDark }: StarRatingProps) {\n const [hovered, setHovered] = useState<number | null>(null);\n\n return (\n <div\n style={{\n display: 'flex',\n gap: 4,\n }}\n role=\"group\"\n aria-label=\"Rating\"\n >\n {[1, 2, 3, 4, 5].map((star) => {\n const isFilled = (hovered ?? value ?? 0) >= star;\n return (\n <button\n key={star}\n type=\"button\"\n onClick={() => onChange(star)}\n onMouseEnter={() => setHovered(star)}\n onMouseLeave={() => setHovered(null)}\n aria-label={`Rate ${star} out of 5`}\n aria-pressed={value === star}\n style={{\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n padding: 2,\n color: isFilled ? '#f59e0b' : (isDark ? '#4b5563' : '#d1d5db'),\n transition: 'color 150ms ease',\n }}\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z\" />\n </svg>\n </button>\n );\n })}\n </div>\n );\n}\n","import React from 'react';\n\ninterface VoteModeProps {\n theme: 'light' | 'dark' | 'custom';\n isLoading: boolean;\n onSubmit: (data: { vote: 'up' | 'down' }) => void;\n}\n\nexport function VoteMode({ theme, isLoading, onSubmit }: VoteModeProps) {\n const isDark = theme === 'dark';\n\n const buttonBase: React.CSSProperties = {\n flex: 1,\n padding: '12px 16px',\n border: `1px solid ${isDark ? '#374151' : '#e5e7eb'}`,\n borderRadius: 8,\n backgroundColor: isDark ? '#374151' : '#f9fafb',\n color: isDark ? '#f9fafb' : '#111827',\n fontSize: 24,\n cursor: isLoading ? 'not-allowed' : 'pointer',\n transition: 'all 150ms ease',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 8,\n };\n\n return (\n <div\n style={{\n display: 'flex',\n gap: 12,\n }}\n role=\"group\"\n aria-label=\"Vote\"\n >\n <button\n type=\"button\"\n onClick={() => onSubmit({ vote: 'up' })}\n disabled={isLoading}\n style={buttonBase}\n aria-label=\"Vote up - I like this\"\n >\n <ThumbsUpIcon />\n <span style={{ fontSize: 14, fontWeight: 500 }}>Like</span>\n </button>\n\n <button\n type=\"button\"\n onClick={() => onSubmit({ vote: 'down' })}\n disabled={isLoading}\n style={buttonBase}\n aria-label=\"Vote down - I don't like this\"\n >\n <ThumbsDownIcon />\n <span style={{ fontSize: 14, fontWeight: 500 }}>Dislike</span>\n </button>\n </div>\n );\n}\n\nfunction ThumbsUpIcon() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3\" />\n </svg>\n );\n}\n\nfunction ThumbsDownIcon() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17\" />\n </svg>\n );\n}\n","import React, { useRef, useEffect, useState } from 'react';\nimport { Theme, GotchaStyles, ResponseMode } from '../types';\nimport { cn } from '../utils/cn';\nimport { FeedbackMode } from './modes/FeedbackMode';\nimport { VoteMode } from './modes/VoteMode';\n\nexport interface GotchaModalProps {\n mode: ResponseMode;\n theme: Theme;\n customStyles?: GotchaStyles;\n promptText?: string;\n placeholder?: string;\n submitText: string;\n thankYouMessage: string;\n // State\n isLoading: boolean;\n isSubmitted: boolean;\n error: string | null;\n // Handlers\n onSubmit: (data: { content?: string; rating?: number; vote?: 'up' | 'down' }) => void;\n onClose: () => void;\n // Position info from parent\n anchorRect?: DOMRect;\n}\n\nexport function GotchaModal({\n mode,\n theme,\n customStyles,\n promptText,\n placeholder,\n submitText,\n thankYouMessage,\n isLoading,\n isSubmitted,\n error,\n onSubmit,\n onClose,\n anchorRect,\n}: GotchaModalProps) {\n const modalRef = useRef<HTMLDivElement>(null);\n const firstFocusableRef = useRef<HTMLButtonElement>(null);\n const [isVisible, setIsVisible] = useState(false);\n\n // Trigger animation after mount\n useEffect(() => {\n // Small delay to ensure CSS transition works\n const timer = requestAnimationFrame(() => setIsVisible(true));\n return () => cancelAnimationFrame(timer);\n }, []);\n\n // Resolve theme\n const resolvedTheme = theme === 'auto'\n ? (typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')\n : theme;\n\n const isDark = resolvedTheme === 'dark';\n\n // Determine if modal should appear above or below\n const modalHeight = 280; // approximate modal height\n const spaceBelow = anchorRect\n ? window.innerHeight - anchorRect.bottom\n : window.innerHeight / 2;\n const showAbove = spaceBelow < modalHeight + 20;\n\n // Focus trap\n useEffect(() => {\n const modal = modalRef.current;\n if (!modal) return;\n\n // Focus first element\n firstFocusableRef.current?.focus();\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onClose();\n return;\n }\n\n if (e.key === 'Tab') {\n const focusableElements = modal.querySelectorAll<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n );\n const firstElement = focusableElements[0];\n const lastElement = focusableElements[focusableElements.length - 1];\n\n if (e.shiftKey && document.activeElement === firstElement) {\n e.preventDefault();\n lastElement?.focus();\n } else if (!e.shiftKey && document.activeElement === lastElement) {\n e.preventDefault();\n firstElement?.focus();\n }\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [onClose]);\n\n const defaultPrompt = mode === 'vote'\n ? 'What do you think?'\n : 'What do you think of this feature?';\n\n const modalStyles: React.CSSProperties = {\n position: 'absolute',\n left: '50%',\n width: 320,\n padding: 16,\n borderRadius: 8,\n backgroundColor: isDark ? '#1f2937' : '#ffffff',\n color: isDark ? '#f9fafb' : '#111827',\n boxShadow: '0 10px 25px rgba(0, 0, 0, 0.15)',\n border: `1px solid ${isDark ? '#374151' : '#e5e7eb'}`,\n zIndex: 9999,\n ...(showAbove\n ? { bottom: '100%', marginBottom: 8 }\n : { top: '100%', marginTop: 8 }),\n // CSS transition for animations\n transition: 'opacity 0.2s ease-out, transform 0.2s ease-out',\n opacity: isVisible ? 1 : 0,\n transform: isVisible\n ? 'translateX(-50%) scale(1) translateY(0)'\n : `translateX(-50%) scale(0.95) translateY(${showAbove ? '10px' : '-10px'})`,\n ...customStyles?.modal,\n };\n\n return (\n <div\n ref={modalRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"gotcha-modal-title\"\n style={modalStyles}\n className={cn('gotcha-modal', isDark && 'gotcha-modal--dark')}\n >\n {/* Close button */}\n <button\n ref={firstFocusableRef}\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close feedback form\"\n style={{\n position: 'absolute',\n top: 8,\n right: 8,\n width: 24,\n height: 24,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: isDark ? '#9ca3af' : '#6b7280',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 4,\n }}\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n <path\n d=\"M1 1L13 13M1 13L13 1\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n\n {/* Title */}\n <h2\n id=\"gotcha-modal-title\"\n style={{\n margin: '0 0 12px 0',\n fontSize: 14,\n fontWeight: 500,\n paddingRight: 24,\n }}\n >\n {promptText || defaultPrompt}\n </h2>\n\n {/* Success state */}\n {isSubmitted && (\n <div\n style={{\n textAlign: 'center',\n padding: '20px 0',\n color: isDark ? '#10b981' : '#059669',\n }}\n >\n <svg\n width=\"32\"\n height=\"32\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n style={{ margin: '0 auto 8px' }}\n >\n <path\n d=\"M20 6L9 17L4 12\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n <p style={{ margin: 0, fontSize: 14 }}>{thankYouMessage}</p>\n </div>\n )}\n\n {/* Error state */}\n {error && !isSubmitted && (\n <div\n style={{\n padding: 8,\n marginBottom: 12,\n borderRadius: 4,\n backgroundColor: isDark ? '#7f1d1d' : '#fef2f2',\n color: isDark ? '#fecaca' : '#dc2626',\n fontSize: 13,\n }}\n >\n {error}\n </div>\n )}\n\n {/* Form content based on mode */}\n {!isSubmitted && (\n <>\n {mode === 'feedback' && (\n <FeedbackMode\n theme={resolvedTheme}\n placeholder={placeholder}\n submitText={submitText}\n isLoading={isLoading}\n onSubmit={onSubmit}\n customStyles={customStyles}\n />\n )}\n {mode === 'vote' && (\n <VoteMode\n theme={resolvedTheme}\n isLoading={isLoading}\n onSubmit={onSubmit}\n />\n )}\n </>\n )}\n\n {/* Screen reader announcement */}\n <div aria-live=\"polite\" className=\"sr-only\" style={{ position: 'absolute', left: -9999 }}>\n {isSubmitted && 'Thank you! Your feedback has been submitted.'}\n {error && `Error: ${error}`}\n </div>\n </div>\n );\n}\n","import React, { useState, useCallback, useEffect, useRef } from 'react';\nimport {\n ResponseMode,\n GotchaUser,\n Position,\n Size,\n Theme,\n TouchBehavior,\n GotchaStyles,\n GotchaResponse,\n GotchaError,\n} from '../types';\nimport { DEFAULTS } from '../constants';\nimport { useGotchaContext } from './GotchaProvider';\nimport { useSubmit } from '../hooks/useSubmit';\nimport { GotchaButton } from './GotchaButton';\nimport { GotchaModal } from './GotchaModal';\n\nexport interface GotchaProps {\n /** Unique identifier for this element */\n elementId: string;\n\n // User data\n /** User metadata for segmentation */\n user?: GotchaUser;\n\n // Behavior\n /** Feedback mode */\n mode?: ResponseMode;\n /** Required if mode is 'ab' */\n experimentId?: string;\n /** Current A/B variant shown to user */\n variant?: string;\n\n // Poll mode specific (Phase 2)\n /** Required if mode is 'poll' (2-6 options) */\n options?: string[];\n /** Allow selecting multiple options */\n allowMultiple?: boolean;\n /** Show results after voting */\n showResults?: boolean;\n\n // Appearance\n /** Button position relative to parent */\n position?: Position;\n /** Button size */\n size?: Size;\n /** Color theme */\n theme?: Theme;\n /** Custom style overrides */\n customStyles?: GotchaStyles;\n /** Control visibility programmatically */\n visible?: boolean;\n /** Only show when parent is hovered (default: true) */\n showOnHover?: boolean;\n /** Mobile behavior (default: 'always-visible') */\n touchBehavior?: TouchBehavior;\n\n // Content\n /** Custom prompt text */\n promptText?: string;\n /** Input placeholder text */\n placeholder?: string;\n /** Submit button text */\n submitText?: string;\n /** Post-submission message */\n thankYouMessage?: string;\n\n // Callbacks\n /** Called after successful submission */\n onSubmit?: (response: GotchaResponse) => void;\n /** Called when modal opens */\n onOpen?: () => void;\n /** Called when modal closes */\n onClose?: () => void;\n /** Called on error */\n onError?: (error: GotchaError) => void;\n}\n\nexport function Gotcha({\n elementId,\n user,\n mode = 'feedback',\n experimentId,\n variant,\n options,\n allowMultiple = false,\n showResults = true,\n position = DEFAULTS.POSITION,\n size = DEFAULTS.SIZE,\n theme = DEFAULTS.THEME,\n customStyles,\n visible = true,\n showOnHover = DEFAULTS.SHOW_ON_HOVER,\n touchBehavior = DEFAULTS.TOUCH_BEHAVIOR,\n promptText,\n placeholder,\n submitText = DEFAULTS.SUBMIT_TEXT,\n thankYouMessage = DEFAULTS.THANK_YOU_MESSAGE,\n onSubmit,\n onOpen,\n onClose,\n onError,\n}: GotchaProps) {\n const { disabled, activeModalId, openModal, closeModal } = useGotchaContext();\n const [isSubmitted, setIsSubmitted] = useState(false);\n const [isParentHovered, setIsParentHovered] = useState(false);\n const [anchorRect, setAnchorRect] = useState<DOMRect | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n\n // This instance's modal is open if activeModalId matches our elementId\n const isOpen = activeModalId === elementId;\n\n // Attach hover listeners to the parent element (not the button container)\n useEffect(() => {\n if (!showOnHover) return;\n\n const container = containerRef.current;\n if (!container) return;\n\n // Find the parent element with position: relative\n const parent = container.parentElement;\n if (!parent) return;\n\n const handleMouseEnter = () => setIsParentHovered(true);\n const handleMouseLeave = () => setIsParentHovered(false);\n\n parent.addEventListener('mouseenter', handleMouseEnter);\n parent.addEventListener('mouseleave', handleMouseLeave);\n\n return () => {\n parent.removeEventListener('mouseenter', handleMouseEnter);\n parent.removeEventListener('mouseleave', handleMouseLeave);\n };\n }, [showOnHover]);\n\n const { submit, isLoading, error } = useSubmit({\n elementId,\n mode,\n experimentId,\n variant,\n pollOptions: options,\n user,\n onSuccess: (response) => {\n setIsSubmitted(true);\n onSubmit?.(response);\n // Auto-close after 2.5 seconds\n setTimeout(() => {\n closeModal();\n setIsSubmitted(false);\n }, 2500);\n },\n onError: (err) => {\n onError?.(err as unknown as GotchaError);\n },\n });\n\n const handleOpen = useCallback(() => {\n if (containerRef.current) {\n setAnchorRect(containerRef.current.getBoundingClientRect());\n }\n openModal(elementId);\n onOpen?.();\n }, [elementId, openModal, onOpen]);\n\n const handleClose = useCallback(() => {\n closeModal();\n setIsSubmitted(false);\n onClose?.();\n }, [closeModal, onClose]);\n\n const handleSubmit = useCallback(\n (data: { content?: string; rating?: number; vote?: 'up' | 'down' }) => {\n submit(data);\n },\n [submit]\n );\n\n // Don't render if disabled or not visible\n if (disabled || !visible) return null;\n\n // Position styles\n const positionStyles: Record<Position, React.CSSProperties> = {\n 'top-right': { position: 'absolute', top: 0, right: 0, transform: 'translate(50%, -50%)' },\n 'top-left': { position: 'absolute', top: 0, left: 0, transform: 'translate(-50%, -50%)' },\n 'bottom-right': { position: 'absolute', bottom: 0, right: 0, transform: 'translate(50%, 50%)' },\n 'bottom-left': { position: 'absolute', bottom: 0, left: 0, transform: 'translate(-50%, 50%)' },\n 'inline': { position: 'relative', display: 'inline-flex' },\n };\n\n return (\n <div\n ref={containerRef}\n style={{\n ...positionStyles[position],\n zIndex: isOpen ? 10000 : 50,\n }}\n className=\"gotcha-container\"\n data-gotcha-element={elementId}\n >\n <GotchaButton\n size={size}\n theme={theme}\n customStyles={customStyles}\n showOnHover={showOnHover}\n touchBehavior={touchBehavior}\n onClick={handleOpen}\n isOpen={isOpen}\n isParentHovered={isParentHovered}\n />\n\n {isOpen && (\n <GotchaModal\n mode={mode}\n theme={theme}\n customStyles={customStyles}\n promptText={promptText}\n placeholder={placeholder}\n submitText={submitText}\n thankYouMessage={thankYouMessage}\n isLoading={isLoading}\n isSubmitted={isSubmitted}\n error={error}\n onSubmit={handleSubmit}\n onClose={handleClose}\n anchorRect={anchorRect || undefined}\n />\n )}\n </div>\n );\n}\n","import { useGotchaContext } from '../components/GotchaProvider';\n\n/**\n * Hook to access Gotcha context\n * Must be used within a GotchaProvider\n */\nexport function useGotcha() {\n const { client, disabled, defaultUser, debug } = useGotchaContext();\n\n return {\n /** The API client for manual submissions */\n client,\n /** Whether Gotcha is globally disabled */\n disabled,\n /** Default user metadata */\n defaultUser,\n /** Whether debug mode is enabled */\n debug,\n /** Submit feedback programmatically */\n submitFeedback: client.submitResponse.bind(client),\n };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "gotcha-feedback",
3
+ "version": "1.0.0",
4
+ "description": "Developer-first contextual feedback SDK",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "sideEffects": [
19
+ "*.css"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "typecheck": "tsc --noEmit",
25
+ "clean": "rm -rf dist"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=18.0.0",
29
+ "react-dom": ">=18.0.0"
30
+ },
31
+ "dependencies": {},
32
+ "devDependencies": {
33
+ "@types/react": "^18.2.0",
34
+ "@types/react-dom": "^18.2.0",
35
+ "react": "^18.2.0",
36
+ "react-dom": "^18.2.0",
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.3.0"
39
+ },
40
+ "keywords": [
41
+ "feedback",
42
+ "react",
43
+ "component",
44
+ "user-feedback",
45
+ "product-feedback"
46
+ ],
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/gotcha/gotcha-feedback"
51
+ }
52
+ }