├── src ├── vite-env.d.ts ├── App.tsx ├── global.css ├── main.tsx ├── components │ ├── WidgetForm │ │ ├── Loading.tsx │ │ ├── Steps │ │ │ ├── FeedbackTypeStep.tsx │ │ │ ├── FeedbackSuccessStep.tsx │ │ │ └── FeedbackContentStep.tsx │ │ ├── ScreenshotButton.tsx │ │ └── index.tsx │ ├── CloseButton.tsx │ └── Widget.tsx └── assets │ ├── thought.svg │ ├── idea.svg │ └── bug.svg ├── postcss.config.js ├── tsconfig.node.json ├── vite.config.ts ├── index.html ├── .gitignore ├── tailwind.config.js ├── tsconfig.json └── package.json /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Widget } from "./components/Widget"; 2 | 3 | export function App() { 4 | return 5 | } -------------------------------------------------------------------------------- /src/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | @apply bg-[#09090A] text-zinc-100 7 | } -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import { App } from './App' 4 | 5 | import './global.css' 6 | 7 | ReactDOM.createRoot(document.getElementById('root')!).render( 8 | 9 | 10 | 11 | ) 12 | 13 | -------------------------------------------------------------------------------- /src/components/WidgetForm/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { CircleNotch } from "phosphor-react"; 2 | 3 | export function Loading() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite App 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/components/CloseButton.tsx: -------------------------------------------------------------------------------- 1 | import { Popover } from '@headlessui/react' 2 | import { X } from 'phosphor-react' 3 | 4 | export function CloseButton() { 5 | return ( 6 | 7 | 8 | 9 | ) 10 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { borderRadius } = require('tailwindcss/defaultTheme'); 2 | 3 | module.exports = { 4 | content: ["./src/**/*.tsx"], 5 | theme: { 6 | extend: { 7 | colors: { 8 | brand: { 9 | 300: '#996DFF', 10 | 500: '#8257e6', 11 | } 12 | }, 13 | borderRadius: { 14 | md: '4px', 15 | } 16 | }, 17 | }, 18 | plugins: [ 19 | require('@tailwindcss/forms'), 20 | require('tailwind-scrollbar'), 21 | ], 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/thought.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ts", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@headlessui/react": "^1.6.1", 12 | "html2canvas": "^1.4.1", 13 | "phosphor-react": "^1.4.1", 14 | "react": "^18.0.0", 15 | "react-dom": "^18.0.0" 16 | }, 17 | "devDependencies": { 18 | "@tailwindcss/forms": "^0.5.1", 19 | "@types/react": "^18.0.0", 20 | "@types/react-dom": "^18.0.0", 21 | "@vitejs/plugin-react": "^1.3.0", 22 | "autoprefixer": "^10.4.7", 23 | "postcss": "^8.4.13", 24 | "tailwind-scrollbar": "^1.3.1", 25 | "tailwindcss": "^3.0.24", 26 | "typescript": "^4.6.3", 27 | "vite": "^2.9.7" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Widget.tsx: -------------------------------------------------------------------------------- 1 | import { ChatTeardropDots } from "phosphor-react" 2 | import { Popover } from "@headlessui/react" 3 | import { WidgetForm } from './WidgetForm' 4 | 5 | export function Widget() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Feedback 18 | 19 | 20 | 21 | 22 | ) 23 | } -------------------------------------------------------------------------------- /src/components/WidgetForm/Steps/FeedbackTypeStep.tsx: -------------------------------------------------------------------------------- 1 | import { FeedbackType, feedbackTypes } from ".." 2 | import { CloseButton } from "../../CloseButton"; 3 | 4 | interface FeedbackTypeStepProps { 5 | onFeedbackTypeChanged: (type: FeedbackType) => void; 6 | } 7 | 8 | export function FeedbackTypeStep({ onFeedbackTypeChanged }: FeedbackTypeStepProps) { 9 | return ( 10 | <> 11 |
12 | Deixe seu feedback 13 | 14 |
15 | 16 |
17 | {Object.entries(feedbackTypes).map(([key, value]) => { 18 | return ( 19 | 27 | ) 28 | })} 29 |
30 | 31 | ) 32 | } -------------------------------------------------------------------------------- /src/assets/idea.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/WidgetForm/Steps/FeedbackSuccessStep.tsx: -------------------------------------------------------------------------------- 1 | import { CloseButton } from "../../CloseButton"; 2 | 3 | interface FeedbackSuccessStepProps { 4 | onFeedbackRestartRequested: () => void; 5 | } 6 | 7 | export function FeedbackSuccessStep({ onFeedbackRestartRequested }: FeedbackSuccessStepProps) { 8 | return ( 9 | <> 10 |
11 | 12 |
13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | Agradecemos o feedback! 21 | 22 | 28 |
29 | 30 | ) 31 | } -------------------------------------------------------------------------------- /src/components/WidgetForm/ScreenshotButton.tsx: -------------------------------------------------------------------------------- 1 | import { Camera, Trash } from "phosphor-react"; 2 | import html2canvas from "html2canvas"; 3 | import { useState } from "react"; 4 | import { Loading } from "./Loading"; 5 | 6 | interface ScreenshotButtonProps { 7 | screenshot: string | null; 8 | onScreenshotTook: (screenshot: string | null) => void; 9 | } 10 | 11 | export function ScreenshotButton({ screenshot, onScreenshotTook }: ScreenshotButtonProps) { 12 | const [isTakingScreenshot, setIsTakingScreenshot] = useState(false) 13 | 14 | async function handleTakeScreenshot() { 15 | setIsTakingScreenshot(true); 16 | 17 | const canvas = await html2canvas(document.querySelector('html')!) 18 | const base64image = canvas.toDataURL('image/png') 19 | 20 | onScreenshotTook(base64image) 21 | setIsTakingScreenshot(false); 22 | } 23 | 24 | if (screenshot) { 25 | return ( 26 | 39 | ) 40 | } 41 | return ( 42 | 50 | ) 51 | } -------------------------------------------------------------------------------- /src/components/WidgetForm/index.tsx: -------------------------------------------------------------------------------- 1 | import { CloseButton } from "../CloseButton"; 2 | 3 | import bugImageUrl from '../../assets/bug.svg'; 4 | import ideaImageUrl from '../../assets/idea.svg'; 5 | import thoughImageUrl from '../../assets/thought.svg'; 6 | import { useState } from "react"; 7 | import { FeedbackTypeStep } from "./Steps/FeedbackTypeStep"; 8 | import { FeedbackContentStep } from "./Steps/FeedbackContentStep"; 9 | import { FeedbackSuccessStep } from "./Steps/FeedbackSuccessStep"; 10 | 11 | export const feedbackTypes = { 12 | BUG: { 13 | title: 'Problema', 14 | image: { 15 | source: bugImageUrl, 16 | alt: 'Imagem de um inseto' 17 | }, 18 | }, 19 | IDEA: { 20 | title: 'Ideia', 21 | image: { 22 | source: ideaImageUrl, 23 | alt: 'Imagem de uma lâmpada' 24 | }, 25 | }, 26 | OTHER: { 27 | title: 'Outro', 28 | image: { 29 | source: thoughImageUrl, 30 | alt: 'Imagem de um balão de pensamento' 31 | }, 32 | }, 33 | } 34 | export type FeedbackType = keyof typeof feedbackTypes; 35 | 36 | export function WidgetForm() { 37 | const [feedbackType, setFeedbackType] = useState(null) 38 | const [feedbackSent, setFeedbackSent] = useState(false); 39 | 40 | function handleRestartFeedback() { 41 | setFeedbackSent(false); 42 | setFeedbackType(null); 43 | } 44 | 45 | return ( 46 |
47 | {feedbackSent ? () : 48 | ( 49 | <> 50 | {!feedbackType ? ( 51 | 52 | ) : ( 53 | setFeedbackSent(true)} /> 57 | ) 58 | } 59 | 60 | )} 61 | 64 |
65 | ) 66 | } -------------------------------------------------------------------------------- /src/components/WidgetForm/Steps/FeedbackContentStep.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowLeft, Camera } from "phosphor-react"; 2 | import { FormEvent, useState } from "react"; 3 | import { FeedbackType, feedbackTypes } from ".." 4 | import { CloseButton } from "../../CloseButton" 5 | import { ScreenshotButton } from "../ScreenshotButton"; 6 | 7 | interface FeedbackContentStepProps { 8 | feedbackType: FeedbackType; 9 | onFeedbackRestartRequested: () => void; 10 | onFeedbackSent: () => void; 11 | } 12 | 13 | export function FeedbackContentStep({ 14 | feedbackType, onFeedbackRestartRequested, onFeedbackSent }: FeedbackContentStepProps) { 15 | const [screenshot, setScreenshot] = useState(null) 16 | const [comment, setComment] = useState(''); 17 | 18 | const feedbackTypeInfo = feedbackTypes[feedbackType]; 19 | 20 | function handleSubmitFeedback(event: FormEvent) { 21 | event.preventDefault(); 22 | 23 | console.log({ 24 | screenshot, 25 | comment, 26 | }); 27 | 28 | onFeedbackSent(); 29 | } 30 | 31 | return ( 32 | <> 33 |
34 | 41 | 42 | 43 | {feedbackTypeInfo.image.alt} 44 | {feedbackTypeInfo.title} 45 | 46 | 47 | 48 |
49 | 50 |
51 |