diff --git a/exercises/08.focus/01.problem/README.mdx b/exercises/08.focus/01.problem/README.mdx new file mode 100644 index 00000000..c83fb3ca --- /dev/null +++ b/exercises/08.focus/01.problem/README.mdx @@ -0,0 +1 @@ +# flushSync diff --git a/exercises/08.focus/01.problem/index.css b/exercises/08.focus/01.problem/index.css new file mode 100644 index 00000000..2c3ff326 --- /dev/null +++ b/exercises/08.focus/01.problem/index.css @@ -0,0 +1,24 @@ +main { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 3rem; +} +.editable-text { + button { + /* remove button styles. Make it look like text */ + background: none; + border: none; + padding: 4px 8px; + font-size: 1.5rem; + font-weight: bold; + } + + input { + /* make it the same size as the button */ + font-size: 1.5rem; + font-weight: bold; + padding: 4px 8px; + border: none; + } +} diff --git a/exercises/08.focus/01.problem/index.tsx b/exercises/08.focus/01.problem/index.tsx new file mode 100644 index 00000000..eef01ff3 --- /dev/null +++ b/exercises/08.focus/01.problem/index.tsx @@ -0,0 +1,92 @@ +import { useRef, useState } from 'react' +import * as ReactDOM from 'react-dom/client' + +function EditableText({ + id, + initialValue = '', + fieldName, + inputLabel, + buttonLabel, +}: { + id?: string + initialValue?: string + fieldName: string + inputLabel: string + buttonLabel: string +}) { + const [edit, setEdit] = useState(false) + const [value, setValue] = useState(initialValue) + const inputRef = useRef(null) + // 🐨 add a button ref here + + return edit ? ( +
{ + event.preventDefault() + // here's where you'd send the updated value to the server + // 🐨 wrap these calls in a flushSync + setValue(inputRef.current?.value ?? '') + setEdit(false) + // 🐨 after flushSync, focus the button with the button ref + }} + > + { + if (event.key === 'Escape') { + // 🐨 wrap this in a flushSync + setEdit(false) + // 🐨 after the flushSync, focus the button + } + }} + onBlur={event => { + // 🐨 wrap these in a flushSync + setValue(event.currentTarget.value) + setEdit(false) + // 🐨 after the flushSync, focus the button + }} + /> +
+ ) : ( + + ) +} + +function App() { + return ( +
+ +
+ +
+ +
+ ) +} + +const rootEl = document.createElement('div') +document.body.append(rootEl) +ReactDOM.createRoot(rootEl).render() diff --git a/exercises/08.focus/01.solution/README.mdx b/exercises/08.focus/01.solution/README.mdx new file mode 100644 index 00000000..c83fb3ca --- /dev/null +++ b/exercises/08.focus/01.solution/README.mdx @@ -0,0 +1 @@ +# flushSync diff --git a/exercises/08.focus/01.solution/index.css b/exercises/08.focus/01.solution/index.css new file mode 100644 index 00000000..2c3ff326 --- /dev/null +++ b/exercises/08.focus/01.solution/index.css @@ -0,0 +1,24 @@ +main { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 3rem; +} +.editable-text { + button { + /* remove button styles. Make it look like text */ + background: none; + border: none; + padding: 4px 8px; + font-size: 1.5rem; + font-weight: bold; + } + + input { + /* make it the same size as the button */ + font-size: 1.5rem; + font-weight: bold; + padding: 4px 8px; + border: none; + } +} diff --git a/exercises/08.focus/01.solution/index.tsx b/exercises/08.focus/01.solution/index.tsx new file mode 100644 index 00000000..a68cedff --- /dev/null +++ b/exercises/08.focus/01.solution/index.tsx @@ -0,0 +1,94 @@ +import { useRef, useState } from 'react' +import { flushSync } from 'react-dom' +import * as ReactDOM from 'react-dom/client' + +function EditableText({ + id, + initialValue = '', + fieldName, + inputLabel, + buttonLabel, +}: { + id?: string + initialValue?: string + fieldName: string + inputLabel: string + buttonLabel: string +}) { + const [edit, setEdit] = useState(false) + const [value, setValue] = useState(initialValue) + const inputRef = useRef(null) + const buttonRef = useRef(null) + + return edit ? ( +
{ + event.preventDefault() + // here's where you'd send the updated value to the server + flushSync(() => { + setValue(inputRef.current?.value ?? '') + setEdit(false) + }) + buttonRef.current?.focus() + }} + > + { + if (event.key === 'Escape') { + flushSync(() => { + setEdit(false) + }) + buttonRef.current?.focus() + } + }} + onBlur={event => { + setValue(event.currentTarget.value) + setEdit(false) + }} + /> +
+ ) : ( + + ) +} + +function App() { + return ( +
+ +
+ +
+ +
+ ) +} + +const rootEl = document.createElement('div') +document.body.append(rootEl) +ReactDOM.createRoot(rootEl).render() diff --git a/exercises/08.focus/README.mdx b/exercises/08.focus/README.mdx new file mode 100644 index 00000000..752c40d3 --- /dev/null +++ b/exercises/08.focus/README.mdx @@ -0,0 +1,4 @@ +# Focus Management + +This example was uses code from +[trellix](https://github.com/remix-run/example-trellix/blob/3379b3d5e9c0173381031e4f062877e8a3696b2e/app/routes/board.%24id/components.tsx).