├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.jsx ├── components │ ├── @elem │ │ └── Stack.jsx │ ├── button │ │ ├── Button.jsx │ │ └── Icon.jsx │ ├── input │ │ └── Input.jsx │ ├── modal │ │ └── Modal.jsx │ └── select │ │ └── Select.jsx ├── features │ ├── Button.jsx │ ├── Input.jsx │ ├── Modal.jsx │ └── Select.jsx └── index.js └── 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 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react components 2 | 3 | - modal 4 | - button 5 | - input 6 | - select 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "second_week_lv2", 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 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-scripts": "5.0.1", 12 | "styled-components": "^5.3.6", 13 | "web-vitals": "^2.1.0" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "react-app", 24 | "react-app/jest" 25 | ] 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/with-key/react_components/aa9ddbfdc85c827ad65ec9d821a9c8279476376d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/with-key/react_components/aa9ddbfdc85c827ad65ec9d821a9c8279476376d/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/with-key/react_components/aa9ddbfdc85c827ad65ec9d821a9c8279476376d/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.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import FeatureButton from "./features/Button"; 3 | import FeatureModal from "./features/Modal"; 4 | import FeatureSelect from "./features/Select"; 5 | import FeatureInput from "./features/Input"; 6 | 7 | const App = () => { 8 | return ( 9 | <> 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /src/components/@elem/Stack.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | const Stack = ({ children, ...restProps }) => { 5 | return {children}; 6 | }; 7 | 8 | export default Stack; 9 | 10 | const StyledStack = styled.div` 11 | display: flex; 12 | flex-direction: ${({ row = "row" }) => (row ? "row" : "column")}; 13 | gap: ${({ gap }) => `${gap}px`}; 14 | `; 15 | -------------------------------------------------------------------------------- /src/components/button/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | 4 | /*--------------------------------------------------------* 5 | * Primitive Button 6 | *--------------------------------------------------------*/ 7 | 8 | const PrimitiveButton = ({ children, rightSlot, ...restProps }) => { 9 | return ( 10 | 11 | {rightSlot ? ( 12 | 13 | <>{children} 14 | <>{rightSlot} 15 | 16 | ) : ( 17 | <>{children} 18 | )} 19 | 20 | ); 21 | }; 22 | 23 | /*--------------------------------------------------------* 24 | * Primary Style 25 | *--------------------------------------------------------*/ 26 | 27 | const PrimaryButton = (props) => { 28 | return ( 29 | 35 | ); 36 | }; 37 | 38 | /*--------------------------------------------------------* 39 | * Negative Style 40 | *--------------------------------------------------------*/ 41 | 42 | const NegativeButton = (props) => { 43 | return ( 44 | 50 | ); 51 | }; 52 | 53 | const Primary = PrimaryButton; 54 | const Negative = NegativeButton; 55 | 56 | const Button = { Negative, Primary }; 57 | export default Button; 58 | 59 | const StyledButton = styled.button` 60 | border: none; 61 | cursor: pointer; 62 | 63 | border-radius: 8px; 64 | background-color: ${({ bc }) => bc}; 65 | color: ${({ color }) => color}; 66 | font-weight: ${({ fw }) => fw}; 67 | 68 | &:active { 69 | background-color: ${({ activeBc }) => activeBc}; 70 | } 71 | 72 | ${({ size }) => { 73 | switch (size) { 74 | case "large": 75 | return css` 76 | height: 50px; 77 | width: 200px; 78 | `; 79 | case "medium": 80 | return css` 81 | height: 45px; 82 | width: 130px; 83 | `; 84 | case "small": 85 | return css` 86 | height: 40px; 87 | width: 100px; 88 | `; 89 | default: 90 | return css` 91 | height: 40px; 92 | width: 100px; 93 | `; 94 | } 95 | }} 96 | 97 | ${({ outlined, bc }) => { 98 | if (outlined) { 99 | return css` 100 | border: 3px solid ${bc}; 101 | background-color: #fff; 102 | font-weight: 600; 103 | 104 | &:active { 105 | background-color: #eeeeee; 106 | } 107 | `; 108 | } 109 | }} 110 | `; 111 | 112 | const ButtonInner = styled.div` 113 | display: flex; 114 | align-items: center; 115 | justify-content: center; 116 | gap: 7px; 117 | `; 118 | -------------------------------------------------------------------------------- /src/components/button/Icon.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | export const IconArrow = ({ color }) => { 5 | return ( 6 | 7 | 14 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | export const IconBell = () => { 27 | return ( 28 | 29 | 36 | 42 | 46 | 52 | 53 | 54 | 55 | ); 56 | }; 57 | 58 | const Container = styled.div` 59 | display: flex; 60 | align-items: center; 61 | justify-content: center; 62 | `; 63 | -------------------------------------------------------------------------------- /src/components/input/Input.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import styled from "styled-components"; 3 | 4 | /*--------------------------------------------------------* 5 | * Primitive Input 6 | *--------------------------------------------------------*/ 7 | 8 | const PrmitiveInput = ({ value, ...restProps }) => { 9 | return ( 10 | 11 | ); 12 | }; 13 | 14 | /*--------------------------------------------------------* 15 | * normal input 16 | *--------------------------------------------------------*/ 17 | 18 | const NormalInput = ({ getValues }) => { 19 | const [value, setValue] = useState(""); 20 | 21 | const onChageHandler = (e) => { 22 | const { value } = e.target; 23 | setValue(e.target.value); 24 | getValues(value); 25 | }; 26 | 27 | return ; 28 | }; 29 | 30 | /*--------------------------------------------------------* 31 | * price format input 32 | *--------------------------------------------------------*/ 33 | 34 | const PriceFormatInput = ({ getValues }) => { 35 | const [value, setValue] = useState({ 36 | raw: "0", 37 | format: "0", 38 | }); 39 | 40 | const onChageHandler = ({ target }) => { 41 | const rex = /\D/g; 42 | 43 | const raw = target.value.replaceAll(",", ""); 44 | const format = new Intl.NumberFormat().format( 45 | target.value.replaceAll(",", "") 46 | ); 47 | 48 | if (!rex.test(target.value.replaceAll(",", ""))) { 49 | setValue((old) => ({ 50 | ...old, 51 | raw, 52 | format, 53 | })); 54 | 55 | getValues && getValues({ raw, format }); 56 | } 57 | }; 58 | 59 | return ; 60 | }; 61 | 62 | const Price = PriceFormatInput; 63 | const Normal = NormalInput; 64 | 65 | export { Price, Normal }; 66 | 67 | const StyledInput = styled.input` 68 | border: 1px solid #333333; 69 | height: 40px; 70 | width: 200px; 71 | outline: none; 72 | border-radius: 8px; 73 | padding-left: 12px; 74 | padding-right: 12px; 75 | 76 | &:focus-within { 77 | box-shadow: 0 0 0 1px #000; 78 | } 79 | `; 80 | -------------------------------------------------------------------------------- /src/components/modal/Modal.jsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | createContext, 3 | useContext, 4 | useEffect, 5 | useRef, 6 | useState, 7 | } from "react"; 8 | import ReactDOM from "react-dom"; 9 | import styled from "styled-components"; 10 | 11 | const ModalContext = createContext({}); 12 | 13 | /*--------------------------------------------------------* 14 | * Modal Root 15 | *--------------------------------------------------------*/ 16 | 17 | const ModalRoot = ({ children }) => { 18 | const [open, setOpen] = useState(false); 19 | return ( 20 | 26 | {children} 27 | 28 | ); 29 | }; 30 | 31 | /*--------------------------------------------------------* 32 | * Modal Trigger 33 | *--------------------------------------------------------*/ 34 | 35 | const ModalTrigger = ({ children, asChild, ...rest }) => { 36 | const { setOpen } = useContext(ModalContext); 37 | return asChild ? ( 38 |
setOpen((pre) => !pre)} {...rest}> 39 | {children} 40 |
41 | ) : ( 42 | 45 | ); 46 | }; 47 | 48 | /*--------------------------------------------------------* 49 | * Modal Portal 50 | *--------------------------------------------------------*/ 51 | 52 | const ModalPortal = ({ children }) => { 53 | const portalTarget = document.getElementById("portal-target"); 54 | 55 | if (!portalTarget) { 56 | return null; 57 | } 58 | 59 | return ReactDOM.createPortal(children, portalTarget); 60 | }; 61 | 62 | /*--------------------------------------------------------* 63 | * Modal Overlay 64 | *--------------------------------------------------------*/ 65 | 66 | const ModalOverlay = ({ onClose }) => { 67 | const { open, setOpen } = useContext(ModalContext); 68 | return open ? ( 69 | (onClose ? setOpen(false) : null)} /> 70 | ) : ( 71 | <> 72 | ); 73 | }; 74 | 75 | const StyledOverlay = styled.div` 76 | width: 100%; 77 | height: 100vh; 78 | inset: 0; 79 | position: fixed; 80 | opacity: 80%; 81 | background-color: #ddd; 82 | `; 83 | 84 | /*--------------------------------------------------------* 85 | * Modal Content 86 | *--------------------------------------------------------*/ 87 | 88 | const ModalContent = ({ children, ...rest }) => { 89 | const { open } = useContext(ModalContext); 90 | const ref = useRef(null); 91 | 92 | const clickHandler = () => { 93 | if (ref) { 94 | console.log(ref.current); 95 | } 96 | }; 97 | 98 | useEffect(() => { 99 | document.addEventListener("click", clickHandler); 100 | return () => document.removeEventListener("click", clickHandler); 101 | }, []); 102 | 103 | return open ? ( 104 | 105 | {children} 106 | 107 | ) : ( 108 | <> 109 | ); 110 | }; 111 | 112 | const StyledContent = styled.div` 113 | position: absolute; 114 | `; 115 | 116 | /*--------------------------------------------------------* 117 | * Modal Close 118 | *--------------------------------------------------------*/ 119 | 120 | const ModalClose = ({ children, asChild, ...rest }) => { 121 | const { setOpen } = useContext(ModalContext); 122 | return asChild ? ( 123 |
setOpen(false)} {...rest}> 124 | {children} 125 |
126 | ) : ( 127 | 130 | ); 131 | }; 132 | 133 | const Root = ModalRoot; 134 | const Trigger = ModalTrigger; 135 | const Portal = ModalPortal; 136 | const Overlay = ModalOverlay; 137 | const Close = ModalClose; 138 | const Content = ModalContent; 139 | 140 | export { Root, Trigger, Portal, Overlay, Close, Content }; 141 | -------------------------------------------------------------------------------- /src/components/select/Select.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useEffect, useState } from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | const SelectContext = createContext({}); 5 | 6 | /*--------------------------------------------------------* 7 | * Select Root 8 | *--------------------------------------------------------*/ 9 | const SelectRoot = ({ children }) => { 10 | const [open, setOpen] = useState(false); 11 | const [selected, setSelected] = useState("1"); 12 | const [options, setOptions] = useState([]); 13 | 14 | return ( 15 | 25 |
{children}
26 |
27 | ); 28 | }; 29 | 30 | /*--------------------------------------------------------* 31 | * Select Trigger 32 | *--------------------------------------------------------*/ 33 | 34 | const SelectTrigger = ({ children, ...rest }) => { 35 | const { setOpen, selected, options } = useContext(SelectContext); 36 | const [value] = options.filter((o) => o.value === selected); 37 | 38 | return ( 39 | 53 | ); 54 | }; 55 | 56 | /*--------------------------------------------------------* 57 | * Select List 58 | *--------------------------------------------------------*/ 59 | 60 | const SelectList = ({ children, ...rest }) => { 61 | const { open, setOptions } = useContext(SelectContext); 62 | 63 | useEffect(() => { 64 | setOptions( 65 | React.Children.toArray(children) 66 | .map((c) => c.props) 67 | .map(({ value, children: label }) => ({ 68 | value, 69 | label, 70 | })) 71 | ); 72 | }, []); 73 | 74 | return open ?
{children}
: <>; 75 | }; 76 | 77 | /*--------------------------------------------------------* 78 | * Select Option 79 | *--------------------------------------------------------*/ 80 | 81 | const SelectOption = ({ children, value, ...rest }) => { 82 | const { setSelected } = useContext(SelectContext); 83 | return ( 84 |
setSelected(value)}> 85 | {children} 86 |
87 | ); 88 | }; 89 | 90 | /*--------------------------------------------------------* 91 | * Select Portal 92 | *--------------------------------------------------------*/ 93 | 94 | const SelectPortal = ({ children }) => { 95 | const portalTarget = document.getElementById("portal-target"); 96 | 97 | if (!portalTarget) { 98 | return null; 99 | } 100 | 101 | return ReactDOM.createPortal(children, portalTarget); 102 | }; 103 | 104 | /*--------------------------------------------------------* 105 | * Result 106 | *--------------------------------------------------------*/ 107 | 108 | const Root = SelectRoot; 109 | const Trigger = SelectTrigger; 110 | const List = SelectList; 111 | const Option = SelectOption; 112 | const Portal = SelectPortal; 113 | 114 | export { Root, Trigger, List, Option, Portal }; 115 | -------------------------------------------------------------------------------- /src/features/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Stack from "../components/@elem/Stack"; 3 | import Button from "../components/button/Button"; 4 | import { IconArrow, IconBell } from "../components/button/Icon"; 5 | 6 | const FeatureButton = () => { 7 | return ( 8 | 9 |

Button

10 | 11 | } 13 | size="large" 14 | outlined 15 | onClick={() => window.alert("버튼을 만들어보세요")} 16 | > 17 | Large Primary Button 18 | 19 | Medium 20 | Small 21 | 22 | 23 | 24 | } 28 | onClick={() => console.log(window.prompt("어렵나요?"))} 29 | > 30 | Large Negative Button 31 | 32 | Medium 33 | Small 34 | 35 |
36 | ); 37 | }; 38 | 39 | export default FeatureButton; 40 | -------------------------------------------------------------------------------- /src/features/Input.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Stack from "../components/@elem/Stack"; 3 | import Button from "../components/button/Button"; 4 | import * as Input from "../components/input/Input"; 5 | 6 | const FeatureInput = () => { 7 | const [form, setForm] = useState({ 8 | name: "", 9 | price: "", 10 | }); 11 | 12 | const onSubmitHandler = (e) => { 13 | e.preventDefault(); 14 | 15 | Object.values(form).filter((el) => el !== "").length === 0 16 | ? window.alert("이름과 가격 모두 입력해주세요.") 17 | : window.alert(`{ name: ${form.name}, price: ${form.price} }`); 18 | }; 19 | 20 | return ( 21 | <> 22 |

Input

23 |
24 | 25 |
26 | 27 | setForm((pre) => ({ ...pre, name: v }))} 29 | /> 30 |
31 |
32 | 33 | setForm((pre) => ({ ...pre, price: v.raw }))} 35 | /> 36 |
37 | 38 | 저장 39 |
40 |
41 | 42 | ); 43 | }; 44 | 45 | export default FeatureInput; 46 | -------------------------------------------------------------------------------- /src/features/Modal.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import Stack from "../components/@elem/Stack"; 4 | import Button from "../components/button/Button"; 5 | import * as Modal from "../components/modal/Modal"; 6 | 7 | const FeatureModal = () => { 8 | return ( 9 |
10 |

Modal

11 | 12 | 13 | 14 | open modal 15 | 16 | 17 | 18 | 19 |
20 | 닫기와 확인 버튼 2개가 있고, 외부 영역을 눌러도 모달이 닫히지 21 | 않아요. 22 |
23 | 24 | 25 | 닫기 26 | 27 | { 29 | console.log("on!"); 30 | }} 31 | > 32 | 확인 33 | 34 | 35 |
36 |
37 |
38 | 39 | 40 | 41 | open modal 42 | 43 | 44 | 45 | 46 | 47 |
48 | 닫기 버튼 1개가 있고, 49 |
50 | 외부 영역을 누르면 모달이 닫혀요. 51 |
52 | 53 | 54 | X 55 | 56 | 57 |
58 |
59 |
60 |
61 |
62 | ); 63 | }; 64 | 65 | export default FeatureModal; 66 | 67 | const ModalContent = styled(Modal.Content)` 68 | left: 50%; 69 | top: 50%; 70 | transform: translate(-50%, -50%); 71 | border-radius: 12px; 72 | box-sizing: border-box; 73 | padding: 24px; 74 | background-color: #fff; 75 | width: 500px; 76 | height: 300px; 77 | `; 78 | 79 | const ModalButtonSetter = styled.div` 80 | position: absolute; 81 | bottom: 12px; 82 | right: 12px; 83 | display: flex; 84 | gap: 5px; 85 | `; 86 | 87 | const ModalButtonSetterSecond = styled.div` 88 | position: absolute; 89 | top: 12px; 90 | right: 12px; 91 | `; 92 | 93 | const StyledModalClose = styled.button` 94 | border: 1px solid #ddd; 95 | width: 40px; 96 | height: 40px; 97 | border-radius: 100%; 98 | cursor: pointer; 99 | :hover { 100 | border: 1px solid #333; 101 | } 102 | `; 103 | 104 | const MiniModalContent = styled(Modal.Content)` 105 | left: 50%; 106 | top: 50%; 107 | transform: translate(-50%, -50%); 108 | border-radius: 12px; 109 | box-sizing: border-box; 110 | padding: 24px; 111 | background-color: #fff; 112 | width: 300px; 113 | height: 200px; 114 | `; 115 | -------------------------------------------------------------------------------- /src/features/Select.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import * as Select from "../components/select/Select"; 4 | 5 | const FeatureSelect = () => { 6 | return ( 7 |
8 | 9 |

Select

10 |
11 | 12 | 트리거 13 | 14 | 15 | 리액트 16 | 자바 17 | 스프링 18 | 리액트네이티브 19 | 20 | 21 | 22 | 23 | 24 | 트리거 25 | 26 | 리액트 27 | 자바 28 | 스프링 29 | 리액트네이티브 30 | 31 | 32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export default FeatureSelect; 39 | 40 | const SelectTrigger = styled(Select.Trigger)` 41 | border: 1px solid #ddd; 42 | height: 40px; 43 | width: 300px; 44 | background-color: #fff; 45 | border-radius: 12px; 46 | `; 47 | 48 | const OverSelectList = styled(Select.List)` 49 | border: 1px solid #eee; 50 | border-radius: 12px; 51 | z-index: 2; 52 | background-color: #fff; 53 | width: 300px; 54 | position: absolute; 55 | top: 650px; 56 | `; 57 | 58 | const SelectList = styled(Select.List)` 59 | border: 1px solid #eee; 60 | border-radius: 12px; 61 | z-index: 2; 62 | background-color: #fff; 63 | width: 300px; 64 | position: absolute; 65 | top: 50px; 66 | `; 67 | 68 | const SelectOption = styled(Select.Option)` 69 | font-size: 12px; 70 | display: flex; 71 | align-items: center; 72 | padding-left: 12px; 73 | height: 40px; 74 | 75 | :hover { 76 | background-color: #eee; 77 | } 78 | 79 | :first-child { 80 | border-top-left-radius: 12px; 81 | border-top-right-radius: 12px; 82 | } 83 | 84 | :last-child { 85 | border-bottom-left-radius: 12px; 86 | border-bottom-right-radius: 12px; 87 | } 88 | `; 89 | 90 | const Container = styled.div` 91 | border: 3px solid #ddd; 92 | height: 200px; 93 | overflow: hidden; 94 | position: relative; 95 | margin-top: 50px; 96 | `; 97 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | 5 | const root = ReactDOM.createRoot(document.getElementById("root")); 6 | root.render( 7 | 8 | 9 | 10 | ); 11 | --------------------------------------------------------------------------------