@explorer02/cfm-survey-sdk 0.2.8 → 0.3.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/dist/cli/index.js +1 -1
- package/dist/cli/index.mjs +1 -1
- package/package.json +1 -1
- package/templates/docs/00-integration/ui-customization-wizard.md +31 -0
- package/templates/docs/00-integration/wizard-chrome-contract.md +1 -0
- package/templates/docs/00-integration/wizard-config-handoff.md +10 -0
- package/templates/docs/00-integration/wizard-question-type-styling.md +4 -3
- package/templates/docs/templates/CsatMatrixScale.tsx +29 -20
- package/templates/docs/templates/CustomSliderTrack.tsx +2 -2
- package/templates/docs/templates/FileUploadScale.tsx +4 -4
- package/templates/docs/templates/Header.tsx +1 -1
- package/templates/docs/templates/HeatmapScale.tsx +4 -4
- package/templates/docs/templates/LikertMatrixScale.tsx +7 -4
- package/templates/docs/templates/Question.tsx +2 -1
- package/templates/docs/templates/RankOrderScale.tsx +10 -3
- package/templates/preview-harness/preview-bridge.inline.js +17 -4
- package/templates/preview-harness/previewPages.js +23 -15
- package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +22 -9
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +5 -2
- package/templates/previewBridge.ts +13 -0
- package/templates/wizard-dist/assets/{PreviewMock-tTMvwuEy.js → PreviewMock-Bax7oRAL.js} +1 -1
- package/templates/wizard-dist/assets/TypePanel-D2t4FPSd.js +1 -0
- package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
- package/templates/wizard-dist/assets/index-c5lka74l.js +34 -0
- package/templates/wizard-dist/index.html +2 -2
- package/templates/wizard-dist/assets/TypePanel-B9Ls7BSq.js +0 -1
- package/templates/wizard-dist/assets/index-CjkIlOUI.css +0 -1
- package/templates/wizard-dist/assets/index-CoCrnnxt.js +0 -34
package/dist/cli/index.js
CHANGED
|
@@ -126,7 +126,7 @@ ${n}`,u.writeFileSync(e,t)}function it(e){return e.replace(/_/g," ")}async funct
|
|
|
126
126
|
</body>
|
|
127
127
|
</html>`),u.writeFileSync(p.join(a,"src","entries",`${f}.tsx`),ot(f));u.writeFileSync(p.join(a,"tsconfig.json"),JSON.stringify({compilerOptions:{target:"ES2020",lib:["ES2020","DOM","DOM.Iterable"],module:"ESNext",moduleResolution:"bundler",jsx:"react-jsx",strict:!0,skipLibCheck:!0,paths:{"@/*":["./agent/src/*"]}},include:["src","agent/src","stubs"]},null,2)),tt(a,o,i,n);let m=Zo(i,o),C=Oe(o).join(p.delimiter);(0,ho.execSync)(`node "${m}" build --config "${p.join(a,"vite.config.mjs")}"`,{cwd:a,stdio:"inherit",env:{...process.env,NODE_PATH:C}});let S=p.join(t,"previews");u.mkdirSync(S,{recursive:!0});let v=p.join(a,"dist"),R=u.readFileSync(p.join(i,"templates","preview-harness","preview-bridge.inline.js"),"utf8");for(let f of n){let T=p.join(v,f),$=p.join(S,f);if(!u.existsSync(p.join(T,"index.html")))throw new Error(`Vite build did not produce ${f}/index.html`);u.existsSync($)&&u.rmSync($,{recursive:!0,force:!0}),K(T,$),nt(p.join($,"index.html"),R)}let x=p.join(v,"assets");u.existsSync(x)&&K(x,p.join(S,"assets"));let d=p.join(a,"agent","public");if(u.existsSync(d))for(let f of u.readdirSync(d)){let T=p.join(d,f);u.statSync(T).isFile()&&u.copyFileSync(T,p.join(S,f))}u.rmSync(a,{recursive:!0,force:!0})}var Le=k(require("fs")),Me=k(require("path"));function Pe(e){if(e)return e;let o=process.cwd();if(Le.existsSync(Me.join(o,"src","components","Question.tsx")))return o;let t=process.env.INIT_CWD;return t&&Le.existsSync(Me.join(t,"src","components","Question.tsx"))?t:o}var O=c;function rt(){return I.join(__dirname,"..","..","templates")}function st(e){let o=j.readFileSync(e,"utf8"),t=new Set,n=["MCQ","TEXTFIELD","NPS_SCALE","CFM_MATRIX","CSAT_MATRIX","RATING_MATRIX","SLIDER_MATRIX","FILE_UPLOAD","TEXT_AND_MEDIA","HEATMAP","RANK_ORDER"];for(let i of n)o.includes(i)&&t.add(i);return t.size?[...t]:n}function ct(e,o){let t=["header","footer","chrome","survey_page","intro","end"],n={MCQ:["MCQ_single","MCQ_multiple"],TEXTFIELD:["TEXTFIELD_short","TEXTFIELD_long"],NPS_SCALE:["NPS_SCALE"],CFM_MATRIX:["CFM_likert","CFM_bipolar"],CSAT_MATRIX:["CSAT_emoji","CSAT_star","CSAT_numbered","CSAT_graphics","CSAT_carousel","CSAT_dropdown"],RATING_MATRIX:["RATING_star","RATING_numbered","RATING_radio","RATING_emoji","RATING_graphics","RATING_slider"],SLIDER_MATRIX:["SLIDER_matrix"],FILE_UPLOAD:["FILE_UPLOAD"],HEATMAP:["HEATMAP"],RANK_ORDER:["RANK_drag","RANK_dropdown"]},i=new Set(t);for(let s of o)for(let r of n[s]??[])i.add(r);return e.filter(s=>i.has(s))}function Ue(e,o,t){if(j.existsSync(e))for(let n of j.readdirSync(e)){let i=I.join(e,n),s=I.posix.join(o,n);if(j.statSync(i).isDirectory())Ue(i,s,t);else{let r=I.extname(n).toLowerCase(),a=r===".html"?"text/html":r===".css"?"text/css":r===".js"?"application/javascript":r===".json"?"application/json":r===".svg"?"image/svg+xml":r===".jpg"||r===".jpeg"?"image/jpeg":r===".png"?"image/png":"application/octet-stream";t.push({path:s,contentType:a,sizeBytes:j.statSync(i).size})}}}async function ce(e={}){let o=Pe(e.projectRoot),t=e.outputDir??I.join(o,"out"),n=rt(),i=I.join(n,"preview-harness");e.useTemplatesOnly&&console.log(`${O.dim} Using SDK template preview harness only (--use-templates-only)${O.reset}`);let s=I.join(i,"previewPages.js"),{PREVIEW_PAGE_DEFS:r,buildPreviewHtml:a,PREVIEW_KEYS:l}=require(s),y=(()=>{let d=ie(o);return d?(console.log(`${O.dim} Using project theme: ${I.relative(o,d)}${O.reset}`),j.readFileSync(d,"utf8")):j.readFileSync(I.join(n,"survey-theme.css"),"utf8")})(),m=j.readFileSync(I.join(i,"preview-bridge.inline.js"),"utf8"),C=e.surveyTypes;if(!C){let d=I.join(o,"implementation_plan.md");C=j.existsSync(d)?st(d):["MCQ","TEXTFIELD","NPS_SCALE","CFM_MATRIX","CSAT_MATRIX","RATING_MATRIX","SLIDER_MATRIX","FILE_UPLOAD","TEXT_AND_MEDIA","HEATMAP","RANK_ORDER"]}let S=ct(l,C),v=I.join(t,"previews");if(j.existsSync(v)&&(j.rmSync(v,{recursive:!0,force:!0}),console.log(`${O.dim} Cleared previous preview output${O.reset}`)),j.mkdirSync(v,{recursive:!0}),!e.useTemplatesOnly)try{console.log(`${O.dim} Building previews from agent components (Vite)\u2026${O.reset}`),await yo({projectRoot:o,outputDir:t,keys:S});let d={};for(let T of S)d[T]=`previews/${T}/index.html`;j.writeFileSync(I.join(t,"previews","manifest.json"),JSON.stringify(d,null,2));let f=[];return Ue(I.join(t,"previews"),"previews",f),console.log(`${O.green}\u2705 Exported ${S.length} agent component previews \u2192 ${t}${O.reset}`),{outputDir:t,manifestPath:I.join(t,"previews","manifest.json"),files:f,surveyTypes:C}}catch(d){console.warn(`${O.yellow}\u26A0 Agent Vite preview build failed; falling back to template harness.${O.reset}`),console.warn(`${O.dim} ${L(d)}${O.reset}`)}for(let d of r){if(!S.includes(d.key))continue;let f=I.join(t,"previews",d.key);j.mkdirSync(f,{recursive:!0}),j.writeFileSync(I.join(f,"index.html"),a(d,y,m))}let R={};for(let d of S)R[d]=`previews/${d}/index.html`;j.writeFileSync(I.join(t,"previews","manifest.json"),JSON.stringify(R,null,2));let x=[];return Ue(I.join(t,"previews"),"previews",x),console.log(`${O.green}\u2705 Exported ${S.length} preview pages \u2192 ${t}${O.reset}`),{outputDir:t,manifestPath:I.join(t,"previews","manifest.json"),files:x,surveyTypes:C}}var F=c,ye=["MCQ","TEXTFIELD","NPS_SCALE","CFM_MATRIX","CSAT_MATRIX","RATING_MATRIX","SLIDER_MATRIX","FILE_UPLOAD","TEXT_AND_MEDIA","HEATMAP","RANK_ORDER"];function bo(e){return new Promise(o=>{let t=we.createServer();t.listen(e,()=>{t.close(()=>o(e))}),t.on("error",()=>o(bo(e+1)))})}function wo(e){return new Promise((o,t)=>{let n="";e.on("data",i=>n+=i),e.on("end",()=>o(n)),e.on("error",t)})}function at(e){let{exec:o}=require("child_process"),t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;o(t)}function So(e){let o=N.extname(e).toLowerCase();return{".html":"text/html",".js":"application/javascript",".css":"text/css",".json":"application/json",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".woff2":"font/woff2",".ico":"image/x-icon"}[o]??"application/octet-stream"}function lt(e){let o=new Set,t,n=e.match(/\*\*instanceId:\*\*\s*(.+)/i);if(n){let s=n[1].replace(/[()[\]]/g,"").trim();s&&!s.startsWith("(")&&s!=="JWT"&&(t=s)}for(let s of ye)e.includes(s)&&o.add(s);let i=e.matchAll(/\|\s*`([A-Z_]+)`\s*\|/g);for(let s of i)ye.includes(s[1])&&o.add(s[1]);return{surveyTypes:o.size>0?[...o]:[...ye],instanceId:t}}async function ft(e){return new Promise(o=>{try{let t=we.get(e,n=>{o((n.statusCode??500)<500),n.resume()});t.on("error",()=>o(!1)),t.setTimeout(2e3,()=>{t.destroy(),o(!1)})}catch{o(!1)}})}function ze(e){let o=N.join(e,"out","previews");P.existsSync(o)&&(P.rmSync(o,{recursive:!0,force:!0}),console.log(`${F.dim} Removed local preview files: out/previews/${F.reset}`))}async function Co(){let e=process.env.INIT_CWD||process.cwd(),o=N.join(e,"public"),t=N.join(e,"survey-ui-config.json"),n=N.join(e,"implementation_plan.md"),i=N.join(__dirname,"..","..","templates"),s=N.join(i,"wizard-dist"),r=process.env.CFM_PREVIEW_URL??"http://localhost:3000",a={},l=N.join(e,"out","previews"),y=N.join(l,"manifest.json");P.existsSync(y)||(console.log(`${F.dim} Exporting component previews for local wizard\u2026${F.reset}`),await ce({projectRoot:e,surveyTypes:[...ye]})),P.existsSync(y)&&(a=JSON.parse(P.readFileSync(y,"utf8")));let m=await bo(4200),C=`http://localhost:${m}`;return new Promise((S,v)=>{let R=we.createServer(async(d,f)=>{try{let T=d.url??"/";if(d.method==="GET"&&T==="/api/inventory"){let $;if(P.existsSync(n)){let w=P.readFileSync(n,"utf8");$=lt(w).instanceId}if(P.existsSync(t))try{let w=JSON.parse(P.readFileSync(t,"utf8"));w.instanceId&&($=w.instanceId)}catch{}let h=[...ye],{config:E}=se({projectRoot:e,surveyTypes:h,instanceId:$});f.writeHead(200,{"Content-Type":"application/json"}),f.end(JSON.stringify({surveyTypes:h,instanceId:$,previewUrl:r,initialConfig:E,previewManifest:Object.fromEntries(Object.keys(a).map(w=>[w,`${C}/previews/${w}/index.html`]))}));return}if(d.method==="GET"&&T==="/api/preview-url"){let $=await ft(r);f.writeHead(200,{"Content-Type":"application/json"}),f.end(JSON.stringify({url:r,connected:$}));return}if(d.method==="POST"&&T.split("?")[0]==="/api/wizard-quit"){ze(e),f.writeHead(200,{"Content-Type":"application/json"}),f.end(JSON.stringify({ok:!0,deleted:"out/previews"})),setTimeout(()=>{R.close(()=>{v(new Error("Client quit the wizard without saving. Local preview garbage collection complete (out/previews removed)."))})},300);return}if(d.method==="POST"&&T==="/api/upload-logo"){let $=await wo(d),h=JSON.parse($);P.existsSync(o)||P.mkdirSync(o,{recursive:!0});let E=h.base64.replace(/^data:image\/\w+;base64,/,""),w=Buffer.from(E,"base64"),_=N.extname(h.filename),U=N.basename(h.filename,_).toLowerCase().replace(/[^a-z0-9_-]/g,""),oe=`logo_${Date.now()}_${U}${_||".png"}`;P.writeFileSync(N.join(o,oe),w),f.writeHead(200,{"Content-Type":"application/json"}),f.end(JSON.stringify({fileName:oe}));return}if(d.method==="POST"&&T==="/api/save-config"){let $=await wo(d),h=JSON.parse($);h.configuredAt=new Date().toISOString();let E=h.global?.logo;E?.url?.startsWith("data:")&&(E.url=null),P.writeFileSync(t,JSON.stringify(h,null,2),"utf8"),console.log(`
|
|
128
128
|
${F.green}\u2705 UI configuration saved to ${t}${F.reset}`),ze(e),console.log(`${F.dim} Local preview garbage collection complete (out/previews removed)${F.reset}`),f.writeHead(200,{"Content-Type":"application/json"}),f.end(JSON.stringify({ok:!0})),setTimeout(()=>{R.close(()=>S())},500);return}if(d.method==="GET"){let $=T.split("?")[0];if($==="/"&&($="/index.html"),$.startsWith("/previews/")&&P.existsSync(l)){let w=$.replace(/^\/previews\//,""),_=N.join(l,w),U=N.normalize(_);if(U.startsWith(l)&&P.existsSync(U)&&P.statSync(U).isFile()){let oe=P.readFileSync(U);f.writeHead(200,{"Content-Type":So(U)}),f.end(oe);return}}let h=P.existsSync(s),E=h?N.join(s,$):N.join(i,"wizard.html");if(h&&P.existsSync(E)&&P.statSync(E).isFile()){let w=P.readFileSync(E);f.writeHead(200,{"Content-Type":So(E)}),f.end(w);return}if(!h&&$==="/index.html"){let w=P.readFileSync(N.join(i,"wizard.html"),"utf8");f.writeHead(200,{"Content-Type":"text/html"}),f.end(w);return}}f.writeHead(404,{"Content-Type":"text/plain"}),f.end("Not found")}catch(T){f.writeHead(500,{"Content-Type":"text/plain"}),f.end(`Server error: ${L(T)}`)}});R.listen(m,()=>{let d=C;console.log(`
|
|
129
|
-
${F.bold}${F.cyan}\u{1F3A8} Survey UI Customization Wizard${F.reset}`),console.log(`${F.
|
|
129
|
+
${F.bold}${F.cyan}\u{1F3A8} Survey UI Customization Wizard${F.reset}`),console.log(`${F.bold}\u{1F517} Client wizard link \u2014 share this URL only:${F.reset}`),console.log(` ${F.green}${d}${F.reset}`),console.log(`${F.dim} Port ${m} (auto-selected if 4200 was in use)${F.reset}`),console.log(`${F.dim} Live previews: ${F.reset}${Object.keys(a).length} components`),console.log(`${F.dim} Preview target: ${F.reset}${r}`),console.log(`${F.dim} Default wizard \u2014 saves directly to survey-ui-config.json when client clicks Review & Build${F.reset}
|
|
130
130
|
`),at(d)}),R.on("error",v);let x=()=>{ze(e),R.close(()=>process.exit(130))};process.once("SIGINT",x),process.once("SIGTERM",x)})}var de=k(require("fs")),vo=k(require("path")),Ro=k(require("http")),Io=k(require("https"));z();$e();he();Re();je();var g=c;function ut(e){let{exec:o}=require("child_process"),t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;o(t)}function pt(e){return new Promise(o=>setTimeout(o,e))}async function Je(e,o={}){return new Promise((t,n)=>{let i=new URL(e),r=(i.protocol==="https:"?Io:Ro).request(i,{method:o.method??"GET",headers:o.headers??{}},a=>{let l="";a.on("data",y=>l+=y),a.on("end",()=>{if((a.statusCode??500)>=400){n(new Error(`HTTP ${a.statusCode}: ${l}`));return}try{t(JSON.parse(l))}catch{n(new Error(`Invalid JSON: ${l.slice(0,200)}`))}})});r.on("error",n),o.body&&r.write(o.body),r.end()})}async function mt(e,o,t,n){return new Promise((i,s)=>{let r=new URL(e),a=r.protocol==="https:"?Io:Ro,l={"Content-Type":t,"Content-Length":o.length};n&&(l["X-Wizard-Session"]=n.sessionSecret,l["X-Wizard-Session-Id"]=n.sessionId);let y=a.request(r,{method:"PUT",headers:l},m=>{if((m.statusCode??500)>=400){let C="";m.on("data",S=>C+=S),m.on("end",()=>s(new Error(`PUT failed ${m.statusCode}: ${C}`)));return}m.resume(),i()});y.on("error",s),y.write(o),y.end()})}function ht(e){try{let o=e.split(".")[1];return JSON.parse(Buffer.from(o,"base64url").toString("utf8")).surveyId??null}catch{return null}}async function jo(e={}){let o=e.wait!==!1,t=e.pollIntervalMs??Number(process.env.CFM_WIZARD_POLL_MS??3e3),n=e.timeoutMs??Number(process.env.CFM_WIZARD_TIMEOUT_MS??72e5),i=process.env.CFM_INSTANCE_ID,s=(process.env.CFM_DEPLOY_API_BASE??"http://localhost:3000").replace(/\/$/,"");if(!i)throw new Error("CFM_INSTANCE_ID (JWT) is required");let r=ht(i);if(!r)throw new Error("Could not parse surveyId from CFM_INSTANCE_ID JWT");let a=Pe(),l=Y(a),y=l.activeConfigPath,m=e.apply?!1:e.review!==!1;console.log(`
|
|
131
131
|
${g.bold}${g.cyan}\u{1F3A8} CFM UI Customization (AWS)${g.reset}
|
|
132
132
|
`),m&&(console.log(`${g.dim} Review mode: seed kept in survey-ui-config.json; final + diff written on complete.${g.reset}`),console.log(`${g.dim} Apply later with: npx cfm-sdk confirm-ui-config${g.reset}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -126,7 +126,7 @@ ${n}`,u.writeFileSync(e,t)}function et(e){return e.replace(/_/g," ")}async funct
|
|
|
126
126
|
</body>
|
|
127
127
|
</html>`),u.writeFileSync(p.join(a,"src","entries",`${f}.tsx`),Ko(f));u.writeFileSync(p.join(a,"tsconfig.json"),JSON.stringify({compilerOptions:{target:"ES2020",lib:["ES2020","DOM","DOM.Iterable"],module:"ESNext",moduleResolution:"bundler",jsx:"react-jsx",strict:!0,skipLibCheck:!0,paths:{"@/*":["./agent/src/*"]}},include:["src","agent/src","stubs"]},null,2)),Yo(a,o,i,n);let m=Qo(i,o),C=Ae(o).join(p.delimiter);Xo(`node "${m}" build --config "${p.join(a,"vite.config.mjs")}"`,{cwd:a,stdio:"inherit",env:{...process.env,NODE_PATH:C}});let S=p.join(t,"previews");u.mkdirSync(S,{recursive:!0});let E=p.join(a,"dist"),v=u.readFileSync(p.join(i,"templates","preview-harness","preview-bridge.inline.js"),"utf8");for(let f of n){let T=p.join(E,f),$=p.join(S,f);if(!u.existsSync(p.join(T,"index.html")))throw new Error(`Vite build did not produce ${f}/index.html`);u.existsSync($)&&u.rmSync($,{recursive:!0,force:!0}),G(T,$),Zo(p.join($,"index.html"),v)}let x=p.join(E,"assets");u.existsSync(x)&&G(x,p.join(S,"assets"));let d=p.join(a,"agent","public");if(u.existsSync(d))for(let f of u.readdirSync(d)){let T=p.join(d,f);u.statSync(T).isFile()&&u.copyFileSync(T,p.join(S,f))}u.rmSync(a,{recursive:!0,force:!0})}import*as _e from"fs";import*as De from"path";function Te(e){if(e)return e;let o=process.cwd();if(_e.existsSync(De.join(o,"src","components","Question.tsx")))return o;let t=process.env.INIT_CWD;return t&&_e.existsSync(De.join(t,"src","components","Question.tsx"))?t:o}var D=c;function ot(){return R.join(__dirname,"..","..","templates")}function tt(e){let o=I.readFileSync(e,"utf8"),t=new Set,n=["MCQ","TEXTFIELD","NPS_SCALE","CFM_MATRIX","CSAT_MATRIX","RATING_MATRIX","SLIDER_MATRIX","FILE_UPLOAD","TEXT_AND_MEDIA","HEATMAP","RANK_ORDER"];for(let i of n)o.includes(i)&&t.add(i);return t.size?[...t]:n}function nt(e,o){let t=["header","footer","chrome","survey_page","intro","end"],n={MCQ:["MCQ_single","MCQ_multiple"],TEXTFIELD:["TEXTFIELD_short","TEXTFIELD_long"],NPS_SCALE:["NPS_SCALE"],CFM_MATRIX:["CFM_likert","CFM_bipolar"],CSAT_MATRIX:["CSAT_emoji","CSAT_star","CSAT_numbered","CSAT_graphics","CSAT_carousel","CSAT_dropdown"],RATING_MATRIX:["RATING_star","RATING_numbered","RATING_radio","RATING_emoji","RATING_graphics","RATING_slider"],SLIDER_MATRIX:["SLIDER_matrix"],FILE_UPLOAD:["FILE_UPLOAD"],HEATMAP:["HEATMAP"],RANK_ORDER:["RANK_drag","RANK_dropdown"]},i=new Set(t);for(let s of o)for(let r of n[s]??[])i.add(r);return e.filter(s=>i.has(s))}function Oe(e,o,t){if(I.existsSync(e))for(let n of I.readdirSync(e)){let i=R.join(e,n),s=R.posix.join(o,n);if(I.statSync(i).isDirectory())Oe(i,s,t);else{let r=R.extname(n).toLowerCase(),a=r===".html"?"text/html":r===".css"?"text/css":r===".js"?"application/javascript":r===".json"?"application/json":r===".svg"?"image/svg+xml":r===".jpg"||r===".jpeg"?"image/jpeg":r===".png"?"image/png":"application/octet-stream";t.push({path:s,contentType:a,sizeBytes:I.statSync(i).size})}}}async function se(e={}){let o=Te(e.projectRoot),t=e.outputDir??R.join(o,"out"),n=ot(),i=R.join(n,"preview-harness");e.useTemplatesOnly&&console.log(`${D.dim} Using SDK template preview harness only (--use-templates-only)${D.reset}`);let s=R.join(i,"previewPages.js"),{PREVIEW_PAGE_DEFS:r,buildPreviewHtml:a,PREVIEW_KEYS:l}=we(s),y=(()=>{let d=ne(o);return d?(console.log(`${D.dim} Using project theme: ${R.relative(o,d)}${D.reset}`),I.readFileSync(d,"utf8")):I.readFileSync(R.join(n,"survey-theme.css"),"utf8")})(),m=I.readFileSync(R.join(i,"preview-bridge.inline.js"),"utf8"),C=e.surveyTypes;if(!C){let d=R.join(o,"implementation_plan.md");C=I.existsSync(d)?tt(d):["MCQ","TEXTFIELD","NPS_SCALE","CFM_MATRIX","CSAT_MATRIX","RATING_MATRIX","SLIDER_MATRIX","FILE_UPLOAD","TEXT_AND_MEDIA","HEATMAP","RANK_ORDER"]}let S=nt(l,C),E=R.join(t,"previews");if(I.existsSync(E)&&(I.rmSync(E,{recursive:!0,force:!0}),console.log(`${D.dim} Cleared previous preview output${D.reset}`)),I.mkdirSync(E,{recursive:!0}),!e.useTemplatesOnly)try{console.log(`${D.dim} Building previews from agent components (Vite)\u2026${D.reset}`),await go({projectRoot:o,outputDir:t,keys:S});let d={};for(let T of S)d[T]=`previews/${T}/index.html`;I.writeFileSync(R.join(t,"previews","manifest.json"),JSON.stringify(d,null,2));let f=[];return Oe(R.join(t,"previews"),"previews",f),console.log(`${D.green}\u2705 Exported ${S.length} agent component previews \u2192 ${t}${D.reset}`),{outputDir:t,manifestPath:R.join(t,"previews","manifest.json"),files:f,surveyTypes:C}}catch(d){console.warn(`${D.yellow}\u26A0 Agent Vite preview build failed; falling back to template harness.${D.reset}`),console.warn(`${D.dim} ${O(d)}${D.reset}`)}for(let d of r){if(!S.includes(d.key))continue;let f=R.join(t,"previews",d.key);I.mkdirSync(f,{recursive:!0}),I.writeFileSync(R.join(f,"index.html"),a(d,y,m))}let v={};for(let d of S)v[d]=`previews/${d}/index.html`;I.writeFileSync(R.join(t,"previews","manifest.json"),JSON.stringify(v,null,2));let x=[];return Oe(R.join(t,"previews"),"previews",x),console.log(`${D.green}\u2705 Exported ${S.length} preview pages \u2192 ${t}${D.reset}`),{outputDir:t,manifestPath:R.join(t,"previews","manifest.json"),files:x,surveyTypes:C}}var j=c,me=["MCQ","TEXTFIELD","NPS_SCALE","CFM_MATRIX","CSAT_MATRIX","RATING_MATRIX","SLIDER_MATRIX","FILE_UPLOAD","TEXT_AND_MEDIA","HEATMAP","RANK_ORDER"];function mo(e){return new Promise(o=>{let t=he.createServer();t.listen(e,()=>{t.close(()=>o(e))}),t.on("error",()=>o(mo(e+1)))})}function uo(e){return new Promise((o,t)=>{let n="";e.on("data",i=>n+=i),e.on("end",()=>o(n)),e.on("error",t)})}function it(e){let{exec:o}=we("child_process"),t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;o(t)}function po(e){let o=F.extname(e).toLowerCase();return{".html":"text/html",".js":"application/javascript",".css":"text/css",".json":"application/json",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".woff2":"font/woff2",".ico":"image/x-icon"}[o]??"application/octet-stream"}function rt(e){let o=new Set,t,n=e.match(/\*\*instanceId:\*\*\s*(.+)/i);if(n){let s=n[1].replace(/[()[\]]/g,"").trim();s&&!s.startsWith("(")&&s!=="JWT"&&(t=s)}for(let s of me)e.includes(s)&&o.add(s);let i=e.matchAll(/\|\s*`([A-Z_]+)`\s*\|/g);for(let s of i)me.includes(s[1])&&o.add(s[1]);return{surveyTypes:o.size>0?[...o]:[...me],instanceId:t}}async function st(e){return new Promise(o=>{try{let t=he.get(e,n=>{o((n.statusCode??500)<500),n.resume()});t.on("error",()=>o(!1)),t.setTimeout(2e3,()=>{t.destroy(),o(!1)})}catch{o(!1)}})}function Le(e){let o=F.join(e,"out","previews");k.existsSync(o)&&(k.rmSync(o,{recursive:!0,force:!0}),console.log(`${j.dim} Removed local preview files: out/previews/${j.reset}`))}async function ho(){let e=process.env.INIT_CWD||process.cwd(),o=F.join(e,"public"),t=F.join(e,"survey-ui-config.json"),n=F.join(e,"implementation_plan.md"),i=F.join(__dirname,"..","..","templates"),s=F.join(i,"wizard-dist"),r=process.env.CFM_PREVIEW_URL??"http://localhost:3000",a={},l=F.join(e,"out","previews"),y=F.join(l,"manifest.json");k.existsSync(y)||(console.log(`${j.dim} Exporting component previews for local wizard\u2026${j.reset}`),await se({projectRoot:e,surveyTypes:[...me]})),k.existsSync(y)&&(a=JSON.parse(k.readFileSync(y,"utf8")));let m=await mo(4200),C=`http://localhost:${m}`;return new Promise((S,E)=>{let v=he.createServer(async(d,f)=>{try{let T=d.url??"/";if(d.method==="GET"&&T==="/api/inventory"){let $;if(k.existsSync(n)){let w=k.readFileSync(n,"utf8");$=rt(w).instanceId}if(k.existsSync(t))try{let w=JSON.parse(k.readFileSync(t,"utf8"));w.instanceId&&($=w.instanceId)}catch{}let h=[...me],{config:P}=re({projectRoot:e,surveyTypes:h,instanceId:$});f.writeHead(200,{"Content-Type":"application/json"}),f.end(JSON.stringify({surveyTypes:h,instanceId:$,previewUrl:r,initialConfig:P,previewManifest:Object.fromEntries(Object.keys(a).map(w=>[w,`${C}/previews/${w}/index.html`]))}));return}if(d.method==="GET"&&T==="/api/preview-url"){let $=await st(r);f.writeHead(200,{"Content-Type":"application/json"}),f.end(JSON.stringify({url:r,connected:$}));return}if(d.method==="POST"&&T.split("?")[0]==="/api/wizard-quit"){Le(e),f.writeHead(200,{"Content-Type":"application/json"}),f.end(JSON.stringify({ok:!0,deleted:"out/previews"})),setTimeout(()=>{v.close(()=>{E(new Error("Client quit the wizard without saving. Local preview garbage collection complete (out/previews removed)."))})},300);return}if(d.method==="POST"&&T==="/api/upload-logo"){let $=await uo(d),h=JSON.parse($);k.existsSync(o)||k.mkdirSync(o,{recursive:!0});let P=h.base64.replace(/^data:image\/\w+;base64,/,""),w=Buffer.from(P,"base64"),A=F.extname(h.filename),M=F.basename(h.filename,A).toLowerCase().replace(/[^a-z0-9_-]/g,""),ee=`logo_${Date.now()}_${M}${A||".png"}`;k.writeFileSync(F.join(o,ee),w),f.writeHead(200,{"Content-Type":"application/json"}),f.end(JSON.stringify({fileName:ee}));return}if(d.method==="POST"&&T==="/api/save-config"){let $=await uo(d),h=JSON.parse($);h.configuredAt=new Date().toISOString();let P=h.global?.logo;P?.url?.startsWith("data:")&&(P.url=null),k.writeFileSync(t,JSON.stringify(h,null,2),"utf8"),console.log(`
|
|
128
128
|
${j.green}\u2705 UI configuration saved to ${t}${j.reset}`),Le(e),console.log(`${j.dim} Local preview garbage collection complete (out/previews removed)${j.reset}`),f.writeHead(200,{"Content-Type":"application/json"}),f.end(JSON.stringify({ok:!0})),setTimeout(()=>{v.close(()=>S())},500);return}if(d.method==="GET"){let $=T.split("?")[0];if($==="/"&&($="/index.html"),$.startsWith("/previews/")&&k.existsSync(l)){let w=$.replace(/^\/previews\//,""),A=F.join(l,w),M=F.normalize(A);if(M.startsWith(l)&&k.existsSync(M)&&k.statSync(M).isFile()){let ee=k.readFileSync(M);f.writeHead(200,{"Content-Type":po(M)}),f.end(ee);return}}let h=k.existsSync(s),P=h?F.join(s,$):F.join(i,"wizard.html");if(h&&k.existsSync(P)&&k.statSync(P).isFile()){let w=k.readFileSync(P);f.writeHead(200,{"Content-Type":po(P)}),f.end(w);return}if(!h&&$==="/index.html"){let w=k.readFileSync(F.join(i,"wizard.html"),"utf8");f.writeHead(200,{"Content-Type":"text/html"}),f.end(w);return}}f.writeHead(404,{"Content-Type":"text/plain"}),f.end("Not found")}catch(T){f.writeHead(500,{"Content-Type":"text/plain"}),f.end(`Server error: ${O(T)}`)}});v.listen(m,()=>{let d=C;console.log(`
|
|
129
|
-
${j.bold}${j.cyan}\u{1F3A8} Survey UI Customization Wizard${j.reset}`),console.log(`${j.
|
|
129
|
+
${j.bold}${j.cyan}\u{1F3A8} Survey UI Customization Wizard${j.reset}`),console.log(`${j.bold}\u{1F517} Client wizard link \u2014 share this URL only:${j.reset}`),console.log(` ${j.green}${d}${j.reset}`),console.log(`${j.dim} Port ${m} (auto-selected if 4200 was in use)${j.reset}`),console.log(`${j.dim} Live previews: ${j.reset}${Object.keys(a).length} components`),console.log(`${j.dim} Preview target: ${j.reset}${r}`),console.log(`${j.dim} Default wizard \u2014 saves directly to survey-ui-config.json when client clicks Review & Build${j.reset}
|
|
130
130
|
`),it(d)}),v.on("error",E);let x=()=>{Le(e),v.close(()=>process.exit(130))};process.once("SIGINT",x),process.once("SIGTERM",x)})}U();import*as fe from"fs";import*as xo from"path";import*as To from"http";import*as ko from"https";be();pe();Ee();Re();var g=c;function lt(e){let{exec:o}=we("child_process"),t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;o(t)}function ft(e){return new Promise(o=>setTimeout(o,e))}async function ze(e,o={}){return new Promise((t,n)=>{let i=new URL(e),r=(i.protocol==="https:"?ko:To).request(i,{method:o.method??"GET",headers:o.headers??{}},a=>{let l="";a.on("data",y=>l+=y),a.on("end",()=>{if((a.statusCode??500)>=400){n(new Error(`HTTP ${a.statusCode}: ${l}`));return}try{t(JSON.parse(l))}catch{n(new Error(`Invalid JSON: ${l.slice(0,200)}`))}})});r.on("error",n),o.body&&r.write(o.body),r.end()})}async function dt(e,o,t,n){return new Promise((i,s)=>{let r=new URL(e),a=r.protocol==="https:"?ko:To,l={"Content-Type":t,"Content-Length":o.length};n&&(l["X-Wizard-Session"]=n.sessionSecret,l["X-Wizard-Session-Id"]=n.sessionId);let y=a.request(r,{method:"PUT",headers:l},m=>{if((m.statusCode??500)>=400){let C="";m.on("data",S=>C+=S),m.on("end",()=>s(new Error(`PUT failed ${m.statusCode}: ${C}`)));return}m.resume(),i()});y.on("error",s),y.write(o),y.end()})}function gt(e){try{let o=e.split(".")[1];return JSON.parse(Buffer.from(o,"base64url").toString("utf8")).surveyId??null}catch{return null}}async function Po(e={}){let o=e.wait!==!1,t=e.pollIntervalMs??Number(process.env.CFM_WIZARD_POLL_MS??3e3),n=e.timeoutMs??Number(process.env.CFM_WIZARD_TIMEOUT_MS??72e5),i=process.env.CFM_INSTANCE_ID,s=(process.env.CFM_DEPLOY_API_BASE??"http://localhost:3000").replace(/\/$/,"");if(!i)throw new Error("CFM_INSTANCE_ID (JWT) is required");let r=gt(i);if(!r)throw new Error("Could not parse surveyId from CFM_INSTANCE_ID JWT");let a=Te(),l=K(a),y=l.activeConfigPath,m=e.apply?!1:e.review!==!1;console.log(`
|
|
131
131
|
${g.bold}${g.cyan}\u{1F3A8} CFM UI Customization (AWS)${g.reset}
|
|
132
132
|
`),m&&(console.log(`${g.dim} Review mode: seed kept in survey-ui-config.json; final + diff written on complete.${g.reset}`),console.log(`${g.dim} Apply later with: npx cfm-sdk confirm-ui-config${g.reset}
|
package/package.json
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
> Optional **run** after Phase 6 verify. **Wizard-ready build** is mandatory in Phase 5 — see [`agent-execution-flow.md`](agent-execution-flow.md).
|
|
4
4
|
|
|
5
|
+
### Port selection (strict)
|
|
6
|
+
|
|
7
|
+
The CLI calls `findPort(4200)` — tries **4200**, then **4201**, **4202**, … until a port is free.
|
|
8
|
+
|
|
9
|
+
**Agent rules:**
|
|
10
|
+
|
|
11
|
+
1. **Do not** hardcode `localhost:4200` if the CLI printed a different port.
|
|
12
|
+
2. **Share only** the URL the CLI prints under `🔗 Client wizard link — share this URL only:`.
|
|
13
|
+
3. **Do not** give the client a second link, a file path, or `out/previews/` paths.
|
|
14
|
+
|
|
15
|
+
Example CLI output:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
🔗 Client wizard link — share this URL only:
|
|
19
|
+
http://localhost:4201
|
|
20
|
+
Port 4201 (auto-selected if 4200 was in use)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Client quit without save
|
|
24
|
+
|
|
25
|
+
If the client clicks **Quit** (no Review & Build):
|
|
26
|
+
|
|
27
|
+
- CLI runs preview GC (`out/previews/` deleted)
|
|
28
|
+
- CLI exits without `survey-ui-config.json` update
|
|
29
|
+
- **Agent must NOT** re-run `npx cfm-sdk customize` or send another wizard link
|
|
30
|
+
- **Agent must** ask deploy confirmation (INPUT 4) using the **existing** build from Phase 6
|
|
31
|
+
|
|
32
|
+
See [`wizard-config-handoff.md`](wizard-config-handoff.md) § Quit without save.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
5
36
|
## Default: local wizard (`customize`)
|
|
6
37
|
|
|
7
38
|
When the client says **yes** to customization in agent chat, **always run:**
|
|
@@ -12,6 +12,7 @@ Pipeline: [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) · [`
|
|
|
12
12
|
| Wizard control / symptom | Required fix |
|
|
13
13
|
|--------------------------|--------------|
|
|
14
14
|
| Logo not updating in Branding / Header preview | `data-cfm-logo` img + `data-cfm-logo-text` fallback in `Header.tsx` |
|
|
15
|
+
| Logo shows colored pad behind image | Logo `<img>` must use `background: transparent` — **never** `var(--cfm-primary)` on logo box |
|
|
15
16
|
| Company name not updating | `data-cfm-company` span in `Header.tsx` |
|
|
16
17
|
| Show header works but progress bar toggle does nothing | Progress bar needs `data-cfm-progress` + class `cfm-progress`; header needs class `cfm-header` only — **not** on sticky wrapper |
|
|
17
18
|
| Show footer toggle does nothing | `footer` + class `cfm-footer` |
|
|
@@ -58,6 +58,16 @@ The logo is **not** fetched from wizard memory on CloudFront — only from the d
|
|
|
58
58
|
|
|
59
59
|
**After save:** `POST /api/save-config` also deletes `out/previews/` before CLI exits (preview garbage collection).
|
|
60
60
|
|
|
61
|
+
**Quit without save (no second wizard run):**
|
|
62
|
+
|
|
63
|
+
| Event | Agent action |
|
|
64
|
+
|-------|----------------|
|
|
65
|
+
| Client clicks **Quit** in wizard | Wait for CLI exit; previews GC'd automatically |
|
|
66
|
+
| CLI exits without `✅ UI configuration saved` | **Do not** run `customize` again or share another wizard URL |
|
|
67
|
+
| Build still valid from Phase 6 | Ask deploy confirmation (INPUT 4) — use existing `out/` from prior `npm run build` |
|
|
68
|
+
|
|
69
|
+
The agent does **not** re-open the wizard after quit. Garbage collection removes preview artifacts only; the production build in `out/` from Phase 6 remains unless the client requests a rebuild.
|
|
70
|
+
|
|
61
71
|
---
|
|
62
72
|
|
|
63
73
|
## Remote client flow — `customize-ui` (fallback only)
|
|
@@ -102,11 +102,12 @@ Single-select radio: outer border = focus ring, annulus = cell selected, inner d
|
|
|
102
102
|
| Wizard section | Config path | CSS vars | Apply in |
|
|
103
103
|
|----------------|-------------|----------|----------|
|
|
104
104
|
| Button style | `questionTypes.NPS_SCALE.buttonStyle` | `data-cfm-nps-button-style` | `RatingScale.tsx` |
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
| Number labels | `numberLabelMode`, per-index colors | `--cfm-number-label-mode`, `--cfm-number-mono-color`, `--cfm-number-color-{n}` | Badge row above radios |
|
|
105
|
+
| Hint labels | `hintMinText`, `hintMaxText` + hint colors | `--cfm-hint-label-*` | `hintLabelStyle()` on anchor row in `Question.tsx` |
|
|
106
|
+
| Number labels | `numberLabelMode`, per-index colors | `--cfm-number-*` | `resolveNumberLabelColor()` / badge row in `RatingScale.tsx` |
|
|
108
107
|
| Selection | Theme → Question card | `--cfm-matrix-selected`, `--cfm-input-focus-ring` | `scaleRadioRingStyle` + `scaleRadioDotStyle` |
|
|
109
108
|
|
|
109
|
+
**Removed from wizard UI:** selected fill, track bar, track highlight — NPS selection uses Theme → Question card tokens only.
|
|
110
|
+
|
|
110
111
|
### CFM_MATRIX (`CFM_matrix`, `CFM_dropdown`, `CFM_transpose`, `CFM_likert`, `CFM_bipolar`)
|
|
111
112
|
|
|
112
113
|
| Wizard section | CSS vars | Apply in |
|
|
@@ -4,6 +4,18 @@ import { getEmojiForIndex, CsatStarIcons } from './surveyUiIcons';
|
|
|
4
4
|
import { columnSubmitValue, nonNaScaleColumns } from './surveyUiScaleUtils';
|
|
5
5
|
import { CustomSliderTrack } from './CustomSliderTrack';
|
|
6
6
|
import MatrixDropdown from './MatrixDropdown';
|
|
7
|
+
import {
|
|
8
|
+
matrixRadioRingStyle,
|
|
9
|
+
matrixRadioDotStyle,
|
|
10
|
+
scaleCellSelectedStyle,
|
|
11
|
+
unselectedOpacityStyle,
|
|
12
|
+
emojiSizeStyle,
|
|
13
|
+
cellSelectedVar,
|
|
14
|
+
focusRingVar,
|
|
15
|
+
} from '@/lib/surveyUi/selectionStyles';
|
|
16
|
+
import { csatColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
17
|
+
|
|
18
|
+
const MATRIX_ROW_LABEL_WIDTH = 'var(--cfm-matrix-row-width, 180px)';
|
|
7
19
|
|
|
8
20
|
type CsatMatrixQuestion = CsatQuestion | RatingMatrixQuestion;
|
|
9
21
|
|
|
@@ -190,7 +202,7 @@ function CsatMatrixGrid({
|
|
|
190
202
|
{/* Labels row */}
|
|
191
203
|
{showLabels && m > 0 && (
|
|
192
204
|
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '12px' }}>
|
|
193
|
-
<div style={{ width:
|
|
205
|
+
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
194
206
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
195
207
|
<div style={{ flex: 1, position: 'relative', height: '24px', ...(isGraphics ? { margin: '0 40px' } : {}) }}>
|
|
196
208
|
{labels!.map((label, i) => {
|
|
@@ -219,7 +231,7 @@ function CsatMatrixGrid({
|
|
|
219
231
|
{/* Column tick labels for graphics sliders (shown with or without anchor labels) */}
|
|
220
232
|
{isGraphics && m > 0 && (
|
|
221
233
|
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: showLabels ? '8px' : '16px', minHeight: '24px' }}>
|
|
222
|
-
<div style={{ width:
|
|
234
|
+
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
223
235
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
224
236
|
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} />
|
|
225
237
|
{hasNotApplicable && (
|
|
@@ -232,13 +244,13 @@ function CsatMatrixGrid({
|
|
|
232
244
|
{/* Column Headers for non-graphics */}
|
|
233
245
|
{!isGraphics && !showLabels && m > 0 && (
|
|
234
246
|
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '16px', minHeight: '24px' }}>
|
|
235
|
-
<div style={{ width:
|
|
247
|
+
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
236
248
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
237
249
|
<div style={{ flex: 1, display: 'grid', gridTemplateColumns: `repeat(${scaleColumns.length}, 1fr)` }}>
|
|
238
250
|
{scaleColumns.map(col => (
|
|
239
251
|
<div key={col.id} style={{ display: 'flex', justifyContent: 'center', padding: '0 4px' }}>
|
|
240
252
|
<span
|
|
241
|
-
style={{ fontSize: '13px', fontWeight: 500,
|
|
253
|
+
style={{ fontSize: '13px', fontWeight: 500, textAlign: 'center', lineHeight: 1.2, ...csatColumnLabelStyle() }}
|
|
242
254
|
dangerouslySetInnerHTML={{ __html: col.label }}
|
|
243
255
|
/>
|
|
244
256
|
</div>
|
|
@@ -260,7 +272,7 @@ function CsatMatrixGrid({
|
|
|
260
272
|
display: 'flex', alignItems: 'center', borderRadius: '8px',
|
|
261
273
|
padding: '12px 0', backgroundColor: rowIdx % 2 === 0 ? 'rgba(249, 250, 251, 0.8)' : '#fff',
|
|
262
274
|
}}>
|
|
263
|
-
<div style={{ width:
|
|
275
|
+
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0, padding: '0 16px' }}>
|
|
264
276
|
<span style={{ fontSize: '14px', fontWeight: 500, color: '#111827', lineHeight: 1.4 }}
|
|
265
277
|
dangerouslySetInnerHTML={{ __html: row.statementText }} />
|
|
266
278
|
</div>
|
|
@@ -298,13 +310,8 @@ function CsatMatrixGrid({
|
|
|
298
310
|
const StarNode = isStar ? (isSelected ? (CsatStarIcons.filled as any) : (CsatStarIcons.empty as any)) : null;
|
|
299
311
|
|
|
300
312
|
const RadioNode = (
|
|
301
|
-
<div style={
|
|
302
|
-
|
|
303
|
-
alignItems: 'center', justifyContent: 'center', borderRadius: '50%',
|
|
304
|
-
margin: '0 auto', border: isSelected ? '2px solid #e20074' : '2px solid #d1d5db',
|
|
305
|
-
backgroundColor: '#fff',
|
|
306
|
-
}}>
|
|
307
|
-
{isSelected && <div style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: '#e20074' }} />}
|
|
313
|
+
<div style={matrixRadioRingStyle(isSelected)}>
|
|
314
|
+
<div style={matrixRadioDotStyle(isSelected)} />
|
|
308
315
|
</div>
|
|
309
316
|
);
|
|
310
317
|
|
|
@@ -324,22 +331,24 @@ function CsatMatrixGrid({
|
|
|
324
331
|
cursor: 'pointer', borderRadius: '6px',
|
|
325
332
|
...(isEmoji ? {
|
|
326
333
|
padding: '6px',
|
|
334
|
+
...emojiSizeStyle(),
|
|
327
335
|
transform: isSelected ? 'scale(1.1)' : isHovered ? 'scale(1.25)' : 'scale(1)',
|
|
328
336
|
...(isSelected ? {
|
|
329
|
-
|
|
330
|
-
|
|
337
|
+
outline: `2px solid ${focusRingVar}`,
|
|
338
|
+
backgroundColor: cellSelectedVar,
|
|
331
339
|
borderRadius: '50%',
|
|
332
|
-
} :
|
|
340
|
+
} : unselectedOpacityStyle()),
|
|
333
341
|
} : isStar ? {
|
|
334
342
|
padding: '4px',
|
|
335
343
|
transform: isHovered ? 'scale(1.1)' : 'scale(1)',
|
|
336
|
-
|
|
344
|
+
color: isSelected ? cellSelectedVar : focusRingVar,
|
|
345
|
+
...(isSelected ? {} : unselectedOpacityStyle()),
|
|
337
346
|
} : isNumbered ? {
|
|
338
347
|
width: '40px', height: '40px', borderRadius: '8px',
|
|
339
348
|
fontSize: '14px', fontWeight: 500,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
349
|
+
...(isSelected
|
|
350
|
+
? scaleCellSelectedStyle(true)
|
|
351
|
+
: { border: '1px solid #d1d5db', backgroundColor: '#fff', color: '#111827' }),
|
|
343
352
|
transition: 'all 0.15s',
|
|
344
353
|
} : { padding: '8px' }),
|
|
345
354
|
}}
|
|
@@ -609,7 +618,7 @@ export function CsatMatrixScale({ question, selectedValue = {}, onSelect }: Csat
|
|
|
609
618
|
);
|
|
610
619
|
}
|
|
611
620
|
|
|
612
|
-
if (isDropdown || isSelectBox || isStandardRadio) {
|
|
621
|
+
if (isDropdown || isSelectBox || (isStandardRadio && question.type !== 'RATING_MATRIX')) {
|
|
613
622
|
return (
|
|
614
623
|
<CsatMatrixVerticalList
|
|
615
624
|
question={question}
|
|
@@ -50,7 +50,7 @@ export function CustomSliderTrack({
|
|
|
50
50
|
? tooltipLabel.replace(/<[^>]*>/g, '')
|
|
51
51
|
: String(Math.round(value));
|
|
52
52
|
|
|
53
|
-
const themeColor = disabled ? '#9ca3af' : '
|
|
53
|
+
const themeColor = disabled ? '#9ca3af' : 'var(--cfm-input-focus-ring, var(--cfm-primary))';
|
|
54
54
|
|
|
55
55
|
return (
|
|
56
56
|
<div
|
|
@@ -60,7 +60,7 @@ export function CustomSliderTrack({
|
|
|
60
60
|
>
|
|
61
61
|
<div style={{
|
|
62
62
|
position: 'absolute', top: '50%', left: 0, right: 0, transform: 'translateY(-50%)',
|
|
63
|
-
height: '4px', borderRadius: '2px', backgroundColor: '#e5e7eb',
|
|
63
|
+
height: '4px', borderRadius: '2px', backgroundColor: 'var(--cfm-slider-track, #e5e7eb)',
|
|
64
64
|
}} />
|
|
65
65
|
|
|
66
66
|
<div style={{
|
|
@@ -98,10 +98,10 @@ function FileUploadDropzone({
|
|
|
98
98
|
flexDirection: 'column',
|
|
99
99
|
alignItems: 'center',
|
|
100
100
|
justifyContent: 'center',
|
|
101
|
-
border: `2px
|
|
102
|
-
borderRadius: '12px',
|
|
103
|
-
backgroundColor: isDragActive ? '#fdf2f8' : '#f9fafb',
|
|
104
|
-
padding: '40px 24px',
|
|
101
|
+
border: `2px ${isDragActive ? 'solid' : 'var(--cfm-upload-border-style, dashed)'} var(--cfm-upload-border-color, #d1d5db)`,
|
|
102
|
+
borderRadius: 'var(--cfm-border-radius, 12px)',
|
|
103
|
+
backgroundColor: isDragActive ? 'var(--cfm-mcq-selected-bg, #fdf2f8)' : 'var(--cfm-upload-fill, #f9fafb)',
|
|
104
|
+
padding: 'var(--cfm-upload-padding, 40px 24px)',
|
|
105
105
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
106
106
|
opacity: disabled ? 0.6 : 1,
|
|
107
107
|
textAlign: 'center',
|
|
@@ -57,7 +57,7 @@ export default function Header({ embedded = false }: HeaderProps) {
|
|
|
57
57
|
width: 'var(--cfm-header-logo-width, 120px)',
|
|
58
58
|
height: 'var(--cfm-header-logo-height, 120px)',
|
|
59
59
|
padding: 'var(--cfm-header-logo-padding, 10px)',
|
|
60
|
-
background: '
|
|
60
|
+
background: 'transparent',
|
|
61
61
|
}}
|
|
62
62
|
/>
|
|
63
63
|
) : (
|
|
@@ -26,11 +26,11 @@ function HeatmapSpot({ spot }: { spot: { id: string; x: number; y: number } }) {
|
|
|
26
26
|
style={{
|
|
27
27
|
left: `${spot.x * 100}%`,
|
|
28
28
|
top: `${spot.y * 100}%`,
|
|
29
|
-
width:
|
|
30
|
-
height:
|
|
29
|
+
width: 'var(--cfm-heatmap-pin-size, 16px)',
|
|
30
|
+
height: 'var(--cfm-heatmap-pin-size, 16px)',
|
|
31
31
|
transform: 'translate(-50%, -50%)',
|
|
32
|
-
backgroundColor: '#F97316',
|
|
33
|
-
border: '2px solid
|
|
32
|
+
backgroundColor: 'var(--cfm-heatmap-pin, #F97316)',
|
|
33
|
+
border: '2px solid var(--cfm-heatmap-pin-border, #fff)',
|
|
34
34
|
boxShadow: '0 1px 3px rgba(0,0,0,0.25)',
|
|
35
35
|
}}
|
|
36
36
|
/>
|
|
@@ -2,6 +2,9 @@ import React, { useState } from 'react';
|
|
|
2
2
|
import type { CfmMatrixQuestion, MatrixRowAnswers } from '@explorer02/cfm-survey-sdk';
|
|
3
3
|
import { columnSubmitValue } from './surveyUiScaleUtils';
|
|
4
4
|
import MatrixDropdown from './MatrixDropdown';
|
|
5
|
+
import { matrixColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
6
|
+
|
|
7
|
+
const MATRIX_ROW_LABEL_WIDTH = 'var(--cfm-matrix-row-width, 180px)';
|
|
5
8
|
|
|
6
9
|
type LikertMatrixScaleProps = {
|
|
7
10
|
question: CfmMatrixQuestion;
|
|
@@ -229,12 +232,12 @@ function LikertMatrixGridLayout({
|
|
|
229
232
|
)}
|
|
230
233
|
{showColumnHeaders && (
|
|
231
234
|
<div style={{ display: 'flex', alignItems: 'flex-end', minHeight: '24px' }}>
|
|
232
|
-
<div style={{ flex: isBipolar ? '0 0 25%' :
|
|
235
|
+
<div style={{ flex: isBipolar ? '0 0 25%' : `0 0 ${MATRIX_ROW_LABEL_WIDTH}`, paddingRight: '16px', boxSizing: 'border-box' }} />
|
|
233
236
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
234
237
|
<div style={{ flex: 1, display: 'grid', gridTemplateColumns: `repeat(${gridCols.length}, 1fr)` }}>
|
|
235
238
|
{gridCols.map((col) => (
|
|
236
239
|
<div key={col.id} style={{ display: 'flex', justifyContent: 'center', padding: '0 4px' }}>
|
|
237
|
-
<span style={{ fontSize: '13px', fontWeight: 500,
|
|
240
|
+
<span style={{ fontSize: '13px', fontWeight: 500, textAlign: 'center', lineHeight: 1.2, ...matrixColumnLabelStyle() }}
|
|
238
241
|
dangerouslySetInnerHTML={{ __html: transposeTable ? ('statementText' in col ? col.statementText : col.label) : ('label' in col ? col.label : (col as { statementText: string }).statementText) }} />
|
|
239
242
|
</div>
|
|
240
243
|
))}
|
|
@@ -261,7 +264,7 @@ function LikertMatrixGridLayout({
|
|
|
261
264
|
padding: '12px 0', backgroundColor: !repeatColumnHeaders && rowIdx % 2 === 0 ? 'rgba(249, 250, 251, 0.8)' : '#fff',
|
|
262
265
|
boxSizing: 'border-box'
|
|
263
266
|
}}>
|
|
264
|
-
<div style={{ flex: isBipolar ? '0 0 25%' :
|
|
267
|
+
<div style={{ flex: isBipolar ? '0 0 25%' : `0 0 ${MATRIX_ROW_LABEL_WIDTH}`, padding: '0 16px', boxSizing: 'border-box' }}>
|
|
265
268
|
<span style={{ fontSize: '14px', fontWeight: 500, color: '#111827', lineHeight: 1.4, wordWrap: 'break-word', display: 'block' }}
|
|
266
269
|
dangerouslySetInnerHTML={{ __html: transposeTable ? ('label' in rowItem ? rowItem.label : '') : ('statementText' in rowItem ? rowItem.statementText : '') }} />
|
|
267
270
|
</div>
|
|
@@ -299,7 +302,7 @@ function LikertMatrixGridLayout({
|
|
|
299
302
|
}
|
|
300
303
|
}}
|
|
301
304
|
style={{
|
|
302
|
-
width: '18px', height: '18px', accentColor: '
|
|
305
|
+
width: '18px', height: '18px', accentColor: 'var(--cfm-input-focus-ring)',
|
|
303
306
|
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
|
304
307
|
...(isMultiple ? { borderRadius: '4px' } : {})
|
|
305
308
|
}}
|
|
@@ -19,6 +19,7 @@ import { FileUploadScale } from './FileUploadScale';
|
|
|
19
19
|
import { HeatmapScale } from './HeatmapScale';
|
|
20
20
|
import { RankOrderScale } from './RankOrderScale';
|
|
21
21
|
import RatingScale from './RatingScale';
|
|
22
|
+
import { hintLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
22
23
|
|
|
23
24
|
type QuestionProps = {
|
|
24
25
|
question: SurveyQuestion;
|
|
@@ -141,7 +142,7 @@ export default function Question({
|
|
|
141
142
|
{question.type === QUESTION_TYPE.NPS_SCALE && (
|
|
142
143
|
<div className="space-y-3">
|
|
143
144
|
{(question.minLabel || question.midLabel || question.maxLabel) && (
|
|
144
|
-
<div className="relative w-full h-6 text-xs font-semibold
|
|
145
|
+
<div className="relative w-full h-6 text-xs font-semibold" style={hintLabelStyle()}>
|
|
145
146
|
<span
|
|
146
147
|
className="absolute left-0 top-0 max-w-[30%] text-left leading-tight"
|
|
147
148
|
dangerouslySetInnerHTML={{ __html: question.minLabel ?? '' }}
|
|
@@ -119,11 +119,17 @@ function RankOrderOptionRow({
|
|
|
119
119
|
rankBadge,
|
|
120
120
|
}: RankOrderOptionRowProps) {
|
|
121
121
|
return (
|
|
122
|
-
<div
|
|
122
|
+
<div
|
|
123
|
+
className="flex items-center gap-3 rounded-lg border border-[#e5e5e5] px-4 py-3 transition-colors"
|
|
124
|
+
style={{ backgroundColor: 'var(--cfm-rank-item-bg, #fff)', padding: 'var(--cfm-rank-item-padding, 12px 16px)', marginBottom: 'var(--cfm-rank-item-gap, 12px)' }}
|
|
125
|
+
>
|
|
123
126
|
{rankSelect}
|
|
124
127
|
{dragHandle}
|
|
125
128
|
{rankBadge !== undefined && (
|
|
126
|
-
<span
|
|
129
|
+
<span
|
|
130
|
+
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-sm font-semibold text-white"
|
|
131
|
+
style={{ backgroundColor: 'var(--cfm-rank-badge, var(--cfm-primary))' }}
|
|
132
|
+
>
|
|
127
133
|
{rankBadge}
|
|
128
134
|
</span>
|
|
129
135
|
)}
|
|
@@ -161,7 +167,8 @@ function DropdownRankLayout({
|
|
|
161
167
|
aria-label={`Rank for ${option.optionLabel || option.id}`}
|
|
162
168
|
value={currentRank ?? ''}
|
|
163
169
|
onChange={event => handleRankChange(option.id, event.target.value)}
|
|
164
|
-
className="h-10 w-16 shrink-0 rounded border
|
|
170
|
+
className="h-10 w-16 shrink-0 rounded border bg-white px-2 text-sm outline-none"
|
|
171
|
+
style={{ borderColor: 'var(--cfm-rank-select-border, #d1d5db)' }}
|
|
165
172
|
>
|
|
166
173
|
<option value="">-</option>
|
|
167
174
|
{rankChoices.map(rank => (
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
if (patch.logoUrl) {
|
|
50
50
|
img.src = patch.logoUrl;
|
|
51
51
|
img.style.display = '';
|
|
52
|
+
img.style.background = 'transparent';
|
|
52
53
|
} else if (el.hasAttribute('data-cfm-logo')) {
|
|
53
54
|
img.style.display = 'none';
|
|
54
55
|
img.removeAttribute('src');
|
|
@@ -171,10 +172,22 @@
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
|
-
if (patch.previewState
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
if (patch.previewState) {
|
|
176
|
+
if (patch.previewState.textfieldPlaceholder !== undefined) {
|
|
177
|
+
document.querySelectorAll('[data-cfm-textfield]').forEach(function (el) {
|
|
178
|
+
el.setAttribute('placeholder', patch.previewState.textfieldPlaceholder || '');
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (patch.previewState.hintMinText !== undefined) {
|
|
182
|
+
document.querySelectorAll('[data-cfm-hint-min], .cfm-hint-min').forEach(function (el) {
|
|
183
|
+
el.textContent = patch.previewState.hintMinText || '';
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (patch.previewState.hintMaxText !== undefined) {
|
|
187
|
+
document.querySelectorAll('[data-cfm-hint-max], .cfm-hint-max').forEach(function (el) {
|
|
188
|
+
el.textContent = patch.previewState.hintMaxText || '';
|
|
189
|
+
});
|
|
190
|
+
}
|
|
178
191
|
}
|
|
179
192
|
}
|
|
180
193
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const sharedStyles = `
|
|
4
4
|
.cfm-header { height:var(--cfm-header-height); background:var(--cfm-header-bg); border-bottom:1px solid var(--cfm-header-border); display:flex; align-items:center; padding:0 var(--cfm-header-padding-x); box-shadow:0 2px 8px rgba(0,0,0,0.06); }
|
|
5
|
-
.cfm-header-logo { width:var(--cfm-header-logo-width); height:var(--cfm-header-logo-height); object-fit:contain; padding:var(--cfm-header-logo-padding); background:
|
|
5
|
+
.cfm-header-logo { width:var(--cfm-header-logo-width); height:var(--cfm-header-logo-height); object-fit:contain; padding:var(--cfm-header-logo-padding); background:transparent; }
|
|
6
6
|
.cfm-header-company { font-size:var(--cfm-header-company-size); font-weight:var(--cfm-header-company-weight); color:var(--cfm-header-company-color); margin-left:16px; }
|
|
7
7
|
.cfm-footer { background:var(--cfm-footer-bg); color:var(--cfm-footer-text); padding:var(--cfm-footer-padding-y) var(--cfm-footer-padding-x); display:flex; flex-wrap:wrap; gap:16px; justify-content:space-between; align-items:flex-end; }
|
|
8
8
|
.cfm-footer-links { display:flex; gap:24px; flex-wrap:wrap; }
|
|
@@ -12,27 +12,31 @@ const sharedStyles = `
|
|
|
12
12
|
.cfm-mcq-option { border:2px solid #e5e7eb; border-radius:var(--cfm-mcq-border-radius); padding:var(--cfm-mcq-card-padding); margin-bottom:var(--cfm-mcq-option-gap); display:flex; align-items:center; gap:8px; }
|
|
13
13
|
.cfm-mcq-option.selected { border-color:var(--cfm-mcq-selected-accent); background:var(--cfm-mcq-selected-bg); }
|
|
14
14
|
.cfm-nps-row { display:flex; gap:var(--cfm-nps-cell-gap); flex-wrap:wrap; }
|
|
15
|
-
.cfm-nps-cell { width:var(--cfm-nps-cell-size); height:var(--cfm-nps-cell-size); border-radius:var(--cfm-border-radius); background:var(--cfm-nps-track-bar); display:flex; align-items:center; justify-content:center; font-size:14px; }
|
|
16
|
-
.cfm-nps-cell.selected {
|
|
15
|
+
.cfm-nps-cell { width:var(--cfm-nps-cell-size); height:var(--cfm-nps-cell-size); border-radius:var(--cfm-border-radius); background:var(--cfm-nps-track-bar,#f2f2f2); display:flex; align-items:center; justify-content:center; font-size:14px; box-sizing:border-box; }
|
|
16
|
+
.cfm-nps-cell.selected { border:2px solid var(--cfm-input-focus-ring); background:var(--cfm-matrix-selected); color:#fff; }
|
|
17
|
+
.cfm-hint-row { display:flex; justify-content:space-between; font-size:12px; font-weight:600; margin-bottom:8px; }
|
|
18
|
+
.cfm-hint-min, .cfm-hint-max { color:var(--cfm-hint-label-color,#6b7280); background:var(--cfm-hint-label-bg,transparent); padding:2px 4px; border-radius:4px; }
|
|
19
|
+
.cfm-matrix-col-header { font-size:13px; font-weight:500; text-align:center; color:var(--cfm-csat-column-label-color,var(--cfm-matrix-column-label-color,#4b5563)); background:var(--cfm-csat-column-label-bg,var(--cfm-matrix-column-label-bg,transparent)); padding:2px 6px; border-radius:4px; }
|
|
17
20
|
.cfm-matrix-row { display:flex; align-items:center; padding:8px 0; background:var(--cfm-row-band); margin-bottom:4px; }
|
|
18
21
|
.cfm-matrix-label { width:var(--cfm-matrix-row-width); flex-shrink:0; padding:0 16px; font-size:14px; font-weight:500; }
|
|
19
22
|
.cfm-matrix-cells { display:flex; gap:8px; flex:1; justify-content:center; }
|
|
20
|
-
.cfm-matrix-cell { width:var(--cfm-csat-cell-size); height:var(--cfm-csat-cell-size); border-radius:50%; border:2px solid #d1d5db; }
|
|
21
|
-
.cfm-matrix-cell.selected { border-color:var(--cfm-
|
|
23
|
+
.cfm-matrix-cell { width:var(--cfm-csat-cell-size,18px); height:var(--cfm-csat-cell-size,18px); border-radius:50%; border:2px solid #d1d5db; box-sizing:border-box; }
|
|
24
|
+
.cfm-matrix-cell.selected { border-color:var(--cfm-input-focus-ring); background:var(--cfm-matrix-selected); }
|
|
22
25
|
.cfm-emoji { font-size:var(--cfm-csat-emoji-size); opacity:var(--cfm-csat-unselected-opacity); }
|
|
23
|
-
.cfm-emoji.selected { opacity:1; outline:2px solid var(--cfm-
|
|
24
|
-
.cfm-star { color:var(--cfm-
|
|
25
|
-
.cfm-star.selected { opacity:1; }
|
|
26
|
-
.cfm-badge { width:40px; height:40px; border-radius:8px; background:var(--cfm-
|
|
26
|
+
.cfm-emoji.selected { opacity:1; outline:2px solid var(--cfm-input-focus-ring); border-radius:8px; background:var(--cfm-matrix-selected); }
|
|
27
|
+
.cfm-star { color:var(--cfm-input-focus-ring); font-size:var(--cfm-csat-emoji-size,24px); opacity:var(--cfm-csat-unselected-opacity); }
|
|
28
|
+
.cfm-star.selected { opacity:1; color:var(--cfm-matrix-selected); }
|
|
29
|
+
.cfm-badge { width:40px; height:40px; border-radius:8px; background:var(--cfm-number-color-0,#9ca3af); color:white; display:flex; align-items:center; justify-content:center; font-weight:600; border:1px solid #d1d5db; box-sizing:border-box; }
|
|
30
|
+
.cfm-badge.selected { border:2px solid var(--cfm-input-focus-ring); background:var(--cfm-matrix-selected); color:#fff; }
|
|
27
31
|
.cfm-slider-track { height:8px; background:var(--cfm-slider-track); border-radius:4px; position:relative; margin:24px 0; }
|
|
28
32
|
.cfm-slider-thumb { width:20px; height:20px; background:var(--cfm-slider-thumb); border-radius:50%; position:absolute; top:-6px; left:60%; }
|
|
29
33
|
.cfm-tick { width:var(--cfm-slider-tick-size); height:var(--cfm-slider-tick-size); background:var(--cfm-slider-tick); border-radius:4px; font-size:11px; display:flex; align-items:center; justify-content:center; color:white; }
|
|
30
|
-
.cfm-dropzone { border:2px dashed var(--cfm-upload-
|
|
34
|
+
.cfm-dropzone { border-width:2px; border-style:var(--cfm-upload-border-style,dashed); border-color:var(--cfm-upload-border-color,#d1d5db); border-radius:var(--cfm-border-radius); padding:var(--cfm-upload-padding,24px); background:var(--cfm-upload-fill,#f9fafb); text-align:center; color:var(--cfm-text); }
|
|
31
35
|
.cfm-input { width:100%; height:var(--cfm-input-height); border:1px solid var(--cfm-input-border); border-radius:var(--cfm-input-radius); padding:0 var(--cfm-input-padding); font-family:inherit; box-sizing:border-box; }
|
|
32
36
|
.cfm-rank-item { background:var(--cfm-rank-item-bg); padding:var(--cfm-rank-item-padding); margin-bottom:var(--cfm-rank-item-gap); border-radius:var(--cfm-border-radius); display:flex; align-items:center; gap:12px; }
|
|
33
37
|
.cfm-rank-badge { background:var(--cfm-rank-badge); color:white; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:12px; }
|
|
34
38
|
.cfm-handle { color:var(--cfm-rank-handle); cursor:grab; }
|
|
35
|
-
.cfm-pin { width:var(--cfm-heatmap-pin-size); height:var(--cfm-heatmap-pin-size); background:var(--cfm-heatmap-pin); border:2px solid
|
|
39
|
+
.cfm-pin { width:var(--cfm-heatmap-pin-size); height:var(--cfm-heatmap-pin-size); background:var(--cfm-heatmap-pin); border:2px solid var(--cfm-heatmap-pin-border,#fff); border-radius:50%; position:absolute; }
|
|
36
40
|
.cfm-media { max-width:var(--cfm-media-max-width); border-radius:var(--cfm-media-radius); background:#e5e7eb; height:200px; display:flex; align-items:center; justify-content:center; }
|
|
37
41
|
.cfm-btn-primary { background:var(--cfm-primary); color:var(--cfm-button-text); border:none; border-radius:var(--cfm-border-radius); padding:10px 20px; }
|
|
38
42
|
.cfm-btn-back { background:transparent; color:var(--cfm-primary); border:1px solid var(--cfm-primary); border-radius:var(--cfm-border-radius); padding:10px 20px; }
|
|
@@ -44,8 +48,12 @@ function cells(n) {
|
|
|
44
48
|
).join('');
|
|
45
49
|
}
|
|
46
50
|
|
|
51
|
+
function matrixColHeaders(n) {
|
|
52
|
+
return `<div class="cfm-matrix-row" style="background:transparent;margin-bottom:8px"><div class="cfm-matrix-label"></div><div class="cfm-matrix-cells">${Array.from({ length: n }, (_, i) => `<span class="cfm-matrix-col-header">${i + 1}</span>`).join('')}</div></div>`;
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
function matrixPreview(label) {
|
|
48
|
-
return
|
|
56
|
+
return `${matrixColHeaders(5)}<div class="cfm-matrix-row"><div class="cfm-matrix-label">${label}</div><div class="cfm-matrix-cells">${cells(5)}</div></div>`;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
const npsCells = Array.from({ length: 11 }, (_, i) =>
|
|
@@ -63,7 +71,7 @@ const PREVIEW_PAGE_DEFS = [
|
|
|
63
71
|
{ key: 'MCQ_multiple', title: 'MCQ Multiple', body: `<p style="font-weight:600;margin-bottom:16px">Select all that apply</p><div class="cfm-mcq-option selected"><span>☑</span> Option A</div><div class="cfm-mcq-option"><span>☐</span> Option B</div>` },
|
|
64
72
|
{ key: 'TEXTFIELD_short', title: 'Text Short', body: `<p style="font-weight:600;margin-bottom:8px">Your name</p><input class="cfm-input" placeholder="Type here..." />` },
|
|
65
73
|
{ key: 'TEXTFIELD_long', title: 'Text Long', body: `<p style="font-weight:600;margin-bottom:8px">Comments</p><textarea class="cfm-input" style="height:120px;padding:12px" placeholder="Type your response..."></textarea>` },
|
|
66
|
-
{ key: 'NPS_SCALE', title: 'NPS', body: `<p style="font-weight:600;margin-bottom:8px">How likely are you to recommend us?</p><div class="cfm-nps-row">${npsCells}</div>` },
|
|
74
|
+
{ key: 'NPS_SCALE', title: 'NPS', body: `<p style="font-weight:600;margin-bottom:8px">How likely are you to recommend us?</p><div class="cfm-hint-row"><span class="cfm-hint-min" data-cfm-hint-min>Not likely</span><span class="cfm-hint-max" data-cfm-hint-max>Very likely</span></div><div class="cfm-nps-row">${npsCells}</div>` },
|
|
67
75
|
{ key: 'CFM_likert', title: 'CFM Likert', body: matrixPreview('Statement item 1') },
|
|
68
76
|
{ key: 'CFM_bipolar', title: 'CFM Bipolar', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width)">Left</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width);text-align:right">Right</div></div>` },
|
|
69
77
|
{ key: 'CFM_transpose', title: 'CFM Transpose', body: matrixPreview('Scale item (transposed)') },
|
|
@@ -71,7 +79,7 @@ const PREVIEW_PAGE_DEFS = [
|
|
|
71
79
|
{ key: 'CFM_dropdown', title: 'CFM Dropdown', body: `<div class="cfm-matrix-label">Statement 1</div><select class="cfm-input" style="margin-top:8px"><option>Select...</option></select>` },
|
|
72
80
|
{ key: 'CSAT_emoji', title: 'CSAT Emoji', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">How satisfied?</div><div class="cfm-matrix-cells"><span class="cfm-emoji selected">😞</span><span class="cfm-emoji">🙂</span><span class="cfm-emoji">😊</span></div></div>` },
|
|
73
81
|
{ key: 'CSAT_star', title: 'CSAT Star', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Rate us</div><div class="cfm-matrix-cells"><span class="cfm-star selected">★</span><span class="cfm-star selected">★</span><span class="cfm-star">★</span></div></div>` },
|
|
74
|
-
{ key: 'CSAT_numbered', title: 'CSAT Numbered', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Score</div><div class="cfm-matrix-cells"><div class="cfm-badge">1</div><div class="cfm-badge">2</div><div class="cfm-badge">3</div></div></div>` },
|
|
82
|
+
{ key: 'CSAT_numbered', title: 'CSAT Numbered', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Score</div><div class="cfm-matrix-cells"><div class="cfm-badge">1</div><div class="cfm-badge selected">2</div><div class="cfm-badge">3</div></div></div>` },
|
|
75
83
|
{ key: 'CSAT_graphics', title: 'CSAT Graphics', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Slider row</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
|
|
76
84
|
{ key: 'CSAT_carousel', title: 'CSAT Carousel', body: `${matrixPreview('Carousel row')}<div style="text-align:center"><span style="color:var(--cfm-csat-accent)">●</span> ○</div>` },
|
|
77
85
|
{ key: 'CSAT_dropdown', title: 'CSAT Dropdown', body: `<div class="cfm-matrix-label">Statement</div><select class="cfm-input" style="margin-top:8px"><option>Choose rating...</option></select>` },
|
|
@@ -81,7 +89,7 @@ const PREVIEW_PAGE_DEFS = [
|
|
|
81
89
|
{ key: 'RATING_emoji', title: 'Rating Emoji', body: `<div class="cfm-matrix-cells" style="padding:16px"><span class="cfm-emoji">🙂</span><span class="cfm-emoji selected">😊</span><span class="cfm-emoji">🤩</span></div>` },
|
|
82
90
|
{ key: 'RATING_graphics', title: 'Rating Graphics', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Experience</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
|
|
83
91
|
{ key: 'RATING_slider', title: 'Rating Slider', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Rate on slider</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
|
|
84
|
-
{ key: 'SLIDER_matrix', title: 'Slider Matrix', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Statement 1</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
|
|
92
|
+
{ key: 'SLIDER_matrix', title: 'Slider Matrix', body: `<div class="cfm-matrix-row" style="background:transparent;margin-bottom:4px"><div class="cfm-matrix-label"></div><div style="flex:1;padding:0 16px;display:flex;justify-content:space-between"><span class="cfm-matrix-col-header">Low</span><span class="cfm-matrix-col-header">High</span></div></div><div class="cfm-matrix-row"><div class="cfm-matrix-label">Statement 1</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div><div style="display:flex;justify-content:space-between;margin-top:8px"><span class="cfm-tick">0</span><span class="cfm-tick">5</span><span class="cfm-tick">10</span></div></div></div>` },
|
|
85
93
|
{ key: 'FILE_UPLOAD', title: 'File Upload', body: `<div data-cfm-dropzone-style="dashed" class="cfm-dropzone">Drop files here or click to upload</div>` },
|
|
86
94
|
{ key: 'TEXT_AND_MEDIA', title: 'Text & Media', body: `<div class="cfm-media">Media preview</div>` },
|
|
87
95
|
{ key: 'HEATMAP', title: 'Heatmap', body: `<div style="position:relative;background:#e5e7eb;height:240px;border-radius:var(--cfm-border-radius)"><div class="cfm-pin" style="top:40%;left:55%"></div></div>` },
|