├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── home.jpg ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.css ├── App.test.tsx ├── App.tsx ├── InlineStylesControls.tsx ├── Title │ └── title.tsx ├── index.css ├── index.tsx ├── logo.svg ├── react-app-env.d.ts ├── reportWebVitals.ts └── setupTests.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | This project used draft.js and customize some style after typing some keys 4 | 5 | ## How to run 6 | 7 | In the project directory, you can install packages using yran: 8 | ``` 9 | yarn install 10 | ``` 11 | 12 | ``` 13 | yarn start 14 | ``` 15 | 16 | 17 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 18 | 19 | 20 | # How this works 21 | 22 | You might see this image once you run the app. 23 | 24 | ![home](https://user-images.githubusercontent.com/110060055/235153797-fae9f1f6-632f-4cf1-8409-2e0d5d3a5e4e.jpg) 25 | 26 | **Note: There're two ways to set style not only clicking the button shown on the image** 27 | 28 | ## - To set the unset the Bold Style 29 | ### You can set the Bold Style after typing `"* and space"` and unset typing again `"* and space"` 30 | 31 | 32 | ## - Header Style 33 | ### After typing `"# and space"` and unset typing again `"# and space"` 34 | 35 | 36 | ## - Text Color to Red 37 | ### After typing `"** and space"` and unset typing again `"** and space"` 38 | 39 | 40 | ## - UnderLine Style 41 | ### You can set the Bold Style after typing `"*** and space"` and unset typing again `"*** and space"` 42 | 43 | 44 | ## - Save to LocalStorage 45 | ### Cliking Save button will save content on editor to `localstorage` and won't disappear after you refer the page. 46 | 47 | 48 | 49 | ## Result page look like this 50 | 51 | 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-sorcerer", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.14.1", 7 | "@testing-library/react": "^13.0.0", 8 | "@testing-library/user-event": "^13.2.1", 9 | "@types/draft-js": "^0.11.10", 10 | "@types/jest": "^27.0.1", 11 | "@types/node": "^16.7.13", 12 | "@types/react": "^18.0.0", 13 | "@types/react-dom": "^18.0.0", 14 | "draft-js": "^0.11.7", 15 | "draft-js-modifiers": "^0.2.2", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "react-scripts": "5.0.1", 19 | "typescript": "^4.4.2", 20 | "web-vitals": "^2.1.0" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "tailwindcss": "^3.3.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooljameswei/React-Draft.js-editor-custom/a118b008d1fed6468cfcf9d4d342a2efabe55b76/public/favicon.ico -------------------------------------------------------------------------------- /public/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooljameswei/React-Draft.js-editor-custom/a118b008d1fed6468cfcf9d4d342a2efabe55b76/public/home.jpg -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooljameswei/React-Draft.js-editor-custom/a118b008d1fed6468cfcf9d4d342a2efabe55b76/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooljameswei/React-Draft.js-editor-custom/a118b008d1fed6468cfcf9d4d342a2efabe55b76/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useEffect } from "react"; 2 | import * as React from 'react' 3 | import logo from "./logo.svg"; 4 | import "./App.css"; 5 | import Title from "./Title/title"; 6 | import { ReactDOM } from "react"; 7 | import {convertToRaw, Editor, EditorState, RichUtils, DraftInlineStyle, ContentState, Modifier, convertFromRaw, getDefaultKeyBinding, KeyBindingUtil} from "draft-js"; 8 | import "draft-js/dist/Draft.css"; 9 | import InlineStyleControls, { inlineStyles } from "./InlineStylesControls"; 10 | 11 | const { hasCommandModifier } = KeyBindingUtil; 12 | function App() { 13 | const [editorState, setEditorState] = useState(() => 14 | EditorState.createEmpty() 15 | ); 16 | 17 | useEffect(() => { 18 | // handleKeyCommand = handleKeyCommand.bind(); 19 | const savedContentState = localStorage.getItem('editorState'); 20 | if(savedContentState != null) 21 | { 22 | const deserializedContentState = JSON.parse(savedContentState); 23 | const updateState = EditorState.createWithContent(convertFromRaw(deserializedContentState)); 24 | setEditorState(updateState); 25 | } 26 | }, []) 27 | 28 | //on Editor Change 29 | const handleEditorChange = (newState: any) => { 30 | setEditorState(newState); 31 | const rawContentState = convertToRaw(newState.getCurrentContent()); 32 | // let text = rawContentState.blocks[0].text; 33 | let text= ''; 34 | rawContentState.blocks.forEach(block => { 35 | text = block.text; 36 | 37 | 38 | let subString, subString2, subString3, last; 39 | last = text.substring(text.length-1, text.length); 40 | let value = last.valueOf(); 41 | subString = text.substring(text.length-2, text.length); 42 | subString2 = text.substring(text.length-3, text.length); 43 | subString3 = text.substring(text.length-4, text.length); 44 | if(subString3 == "*** ") 45 | delteLastCharacters(4, text, "UNDERLINE"); 46 | else if(subString2 == "** ") 47 | delteLastCharacters(3, text, "FONT_RED"); 48 | else { 49 | if(subString == "* ") 50 | delteLastCharacters(2, text, "BOLD"); 51 | else if(subString == "# ") 52 | delteLastCharacters(2, text, "FONT_SIZE_30"); 53 | } 54 | }); 55 | 56 | } 57 | 58 | 59 | //on Save button clicked 60 | const onSave = () => { 61 | const contentState = editorState.getCurrentContent(); 62 | const serializedContentState = JSON.stringify(convertToRaw(contentState)); 63 | localStorage.setItem('editorState', serializedContentState); 64 | } 65 | 66 | //Set Style as customize 67 | const delteLastCharacters = (len: number, text: string, style: string) => { 68 | toggleInlineStyle(style); 69 | const currentInlineStyle = editorState.getCurrentInlineStyle; 70 | text = `${text.slice(0, text.length-len)}`; 71 | console.log(text); 72 | 73 | const contentState = editorState.getCurrentContent(); 74 | const selectionState = editorState.getSelection(); 75 | const lastTwoChars = contentState.getBlockForKey(selectionState.getAnchorKey()) 76 | .getText() 77 | .slice(len * -1 + 1); 78 | 79 | const newSelection = selectionState.merge({ 80 | anchorOffset: selectionState.getFocusOffset() - len + 1, 81 | focusOffset: selectionState.getFocusOffset(), 82 | }) 83 | const newContentState = Modifier.replaceText( 84 | contentState, 85 | newSelection, 86 | '', 87 | ); 88 | 89 | // Create a new EditorState with the new ContentState and selection 90 | const newEditorState = EditorState.push( 91 | editorState, 92 | newContentState, 93 | 'remove-range' 94 | ) 95 | let updatedEditorState = EditorState.forceSelection( 96 | newEditorState, 97 | newContentState.getSelectionAfter() 98 | ); 99 | 100 | const newState = RichUtils.toggleInlineStyle(updatedEditorState, style); 101 | const newState2 = RichUtils.toggleBlockType(newState, "header-one"); 102 | setEditorState(newState2); 103 | } 104 | 105 | const keyBindingFn = (e: any) => { 106 | // if(e.keyCode == 13) 107 | // return 'default-style'; 108 | return getDefaultKeyBinding(e); 109 | } 110 | 111 | const handleKeyCommand = (command: string) => { 112 | if(command === 'default-style') 113 | { 114 | console.log("asdfa"); 115 | // Remove all inline styles from the editor 116 | 117 | return "handled"; 118 | } 119 | return 'not-handled'; 120 | } 121 | 122 | const editor = useRef(null); 123 | const focusEditor = () => { 124 | editor.current?.focus(); 125 | }; 126 | 127 | const customStyleMap = { 128 | STRIKETHROUGH: { 129 | textDecoration: "line-through" 130 | }, 131 | FONT_SIZE_30: { 132 | fontSize: "30px" 133 | }, 134 | FONT_RED: { 135 | color: "red" 136 | } 137 | }; 138 | 139 | const toggleInlineStyle = (inlineStyle: string) => { 140 | const newState = RichUtils.toggleInlineStyle(editorState, inlineStyle); 141 | const newState2 = RichUtils.toggleBlockType(newState, "header-one"); 142 | setEditorState(newState2); 143 | }; 144 | 145 | return ( 146 |
147 | 148 | <div className="m-10" 149 | style={{ 150 | border: "1px solid black", 151 | minHeight: "6em", 152 | cursor: "text", 153 | }} 154 | onClick={focusEditor} 155 | > 156 | <InlineStyleControls 157 | currentInlineStyle={editorState.getCurrentInlineStyle()} 158 | onToggle={toggleInlineStyle} 159 | /> 160 | 161 | <Editor 162 | ref={editor} 163 | customStyleMap={customStyleMap} 164 | editorState={editorState} 165 | onChange={handleEditorChange} 166 | keyBindingFn={keyBindingFn} 167 | handleKeyCommand={handleKeyCommand} 168 | placeholder="Write something!" 169 | /> 170 | </div> 171 | </div> 172 | ); 173 | } 174 | 175 | export default App; 176 | -------------------------------------------------------------------------------- /src/InlineStylesControls.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const inlineStyles = [ 4 | { label: "B", desc: "font-bold", style: "BOLD" }, 5 | { label: "I", desc: "italic", style: "ITALIC" }, 6 | { label: "U", desc: "underline", style: "UNDERLINE" }, 7 | { label: "S", desc: "line-through", style: "STRIKETHROUGH" }, 8 | { label: "H1", style: "FONT_SIZE_30", desc: "heading" }, 9 | { label: "R", style: "FONT_RED", desc: "text-red-600"} 10 | ]; 11 | 12 | 13 | interface InlineStyleControlsProps { 14 | currentInlineStyle: any; 15 | onToggle: (inlineStyle: string) => void; 16 | } 17 | 18 | const InlineStyleControls = ({ currentInlineStyle, onToggle}: InlineStyleControlsProps) => { 19 | return ( 20 | <div> 21 | {inlineStyles.map(type => ( 22 | <button 23 | key={type.label} 24 | onMouseDown={e => { 25 | e.preventDefault(); 26 | onToggle(type.style); 27 | }} 28 | className="border-[1px] pl-2 pr-2 mr-3 border-blue-500 hover:bg-blue-500 visited:bg-blue-300" 29 | > 30 | {<p className={type.desc}>{type.label}</p>} 31 | 32 | 33 | 34 | </button> 35 | ))} 36 | </div> 37 | ); 38 | }; 39 | 40 | export default InlineStyleControls; 41 | -------------------------------------------------------------------------------- /src/Title/title.tsx: -------------------------------------------------------------------------------- 1 | import { convertToRaw } from "draft-js"; 2 | import React from "react"; 3 | 4 | 5 | 6 | interface titleProps { 7 | onSave: () => void; 8 | text: any; 9 | } 10 | 11 | const Title = (props: titleProps) => { 12 | const onSave = () => { 13 | const contentState = props.text.getCurrentContent(); 14 | const serializedContentState = JSON.stringify(convertToRaw(contentState)); 15 | localStorage.setItem('editorState', serializedContentState); 16 | } 17 | return ( 18 | <> 19 | <div className="flex justify-center items-center p-10"> 20 | <h1 className="ml-auto"> Demo editor by Bryan</h1> 21 | <div className="border-[1px] border-blue-700 pl-2 pr-2 hover:bg-blue-300 ml-auto"> 22 | <button className="" onClick={onSave}>Save</button> 23 | </div> 24 | </div> 25 | </> 26 | ); 27 | } 28 | 29 | export default Title; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | margin: 0; 7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 9 | sans-serif; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | code { 15 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 16 | monospace; 17 | } 18 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot( 8 | document.getElementById('root') as HTMLElement 9 | ); 10 | root.render( 11 | <App /> 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg> -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="react-scripts" /> 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./src/**/*.{js,jsx,ts,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } 11 | 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------