@powerhousedao/academy 5.1.0-dev.9 → 5.1.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +43 -1108
  2. package/blog/BeyondCommunication-ABlueprintForDevelopment.md +1 -2
  3. package/blog/TheChallengeOfChange.md +0 -1
  4. package/docs/academy/00-EthereumArgentinaHackathon.md +207 -0
  5. package/docs/academy/01-GetStarted/00-ExploreDemoPackage.mdx +27 -24
  6. package/docs/academy/01-GetStarted/01-CreateNewPowerhouseProject.md +9 -118
  7. package/docs/academy/01-GetStarted/02-DefineToDoListDocumentModel.md +28 -110
  8. package/docs/academy/01-GetStarted/03-ImplementOperationReducers.md +145 -191
  9. package/docs/academy/01-GetStarted/04-BuildToDoListEditor.md +218 -0
  10. package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/05-VetraStudio.md +6 -48
  11. package/docs/academy/01-GetStarted/06-ReactorMCP.md +58 -0
  12. package/docs/academy/01-GetStarted/_04-BuildToDoListEditor +1 -1
  13. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/03-BuilderTools.md +2 -2
  14. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/02-SpecifyTheStateSchema.md +44 -75
  15. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/03-SpecifyDocumentOperations.md +22 -28
  16. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/04-UseTheDocumentModelGenerator.md +31 -28
  17. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/05-ImplementDocumentReducers.md +206 -211
  18. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +62 -176
  19. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md +0 -21
  20. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/01-BuildingDocumentEditors.md +319 -309
  21. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/00-DocumentToolbar.mdx +0 -4
  22. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/01-OperationHistory.md +0 -4
  23. package/docs/academy/02-MasteryTrack/05-Launch/04-ConfigureEnvironment.md +1 -1
  24. package/docs/academy/03-ExampleUsecases/Chatroom/02-CreateNewPowerhouseProject.md +35 -111
  25. package/docs/academy/03-ExampleUsecases/Chatroom/03-DefineChatroomDocumentModel.md +76 -255
  26. package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +160 -281
  27. package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +35 -188
  28. package/docs/academy/03-ExampleUsecases/Chatroom/06-LaunchALocalReactor.md +7 -95
  29. package/docs/academy/03-ExampleUsecases/Chatroom/_category_.json +1 -1
  30. package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +2 -6
  31. package/docs/academy/04-APIReferences/01-ReactHooks.md +501 -291
  32. package/docs/academy/05-Architecture/00-PowerhouseArchitecture.md +39 -7
  33. package/docs/academy/05-Architecture/02-ReferencingMonorepoPackages +65 -0
  34. package/docs/academy/05-Architecture/04-MovingBeyondCRUD +61 -0
  35. package/docs/academy/06-ComponentLibrary/00-DocumentEngineering.md +24 -72
  36. package/docs/academy/08-Glossary.md +0 -7
  37. package/docusaurus.config.ts +3 -28
  38. package/package.json +1 -1
  39. package/sidebars.ts +12 -49
  40. package/src/css/custom.css +18 -26
  41. package/docs/academy/01-GetStarted/04-WriteDocumentModelTests.md +0 -378
  42. package/docs/academy/01-GetStarted/05-BuildToDoListEditor.md +0 -560
  43. package/docs/academy/03-ExampleUsecases/TodoList/00-GetTheStarterCode.md +0 -24
  44. package/docs/academy/03-ExampleUsecases/TodoList/01-GenerateTodoListDocumentModel.md +0 -211
  45. package/docs/academy/03-ExampleUsecases/TodoList/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
  46. package/docs/academy/03-ExampleUsecases/TodoList/03-AddTestsForTodoListActions.md +0 -462
  47. package/docs/academy/03-ExampleUsecases/TodoList/04-GenerateTodoListDocumentEditor.md +0 -45
  48. package/docs/academy/03-ExampleUsecases/TodoList/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
  49. package/docs/academy/03-ExampleUsecases/TodoList/06-GenerateTodoDriveExplorer.md +0 -61
  50. package/docs/academy/03-ExampleUsecases/TodoList/07-AddSharedComponentForShowingTodoListStats.md +0 -384
  51. package/docs/academy/03-ExampleUsecases/TodoList/_category_.json +0 -8
  52. package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/VetraPackageLibrary.md +0 -7
  53. package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/_category_.json +0 -9
  54. package/docs/academy/04-APIReferences/06-VetraRemoteDrive.md +0 -160
  55. package/docs/academy/04-APIReferences/renown-sdk/00-Overview.md +0 -316
  56. package/docs/academy/04-APIReferences/renown-sdk/01-Authentication.md +0 -672
  57. package/docs/academy/04-APIReferences/renown-sdk/02-APIReference.md +0 -957
  58. package/docs/academy/04-APIReferences/renown-sdk/_category_.json +0 -8
  59. package/docs/academy/10-TodoListTutorial/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
  60. package/docs/academy/10-TodoListTutorial/03-AddTestsForTodoListActions.md +0 -462
  61. package/docs/academy/10-TodoListTutorial/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
  62. package/docs/academy/10-TodoListTutorial/07-AddSharedComponentForShowingTodoListStats.md +0 -370
  63. package/static/img/Vetra-logo-dark.svg +0 -11
  64. package/static/img/vetra-logo-light.svg +0 -11
  65. /package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/images/Modules.png +0 -0
  66. /package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/images/VetraStudioDrive.png +0 -0
  67. /package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/{02-RevisionHistoryTimeline/360/237/232/247" → 02-RevisionHistoryTimeline} +0 -0
  68. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /01-WhatIsADocumentModel" → 01-WhatIsADocumentModel} +0 -0
  69. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-DAOandDocumentsModelsQ+A" → 02-DAOandDocumentsModelsQ+A} +0 -0
  70. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-domain-modeling" → 02-domain-modeling} +0 -0
  71. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /03-BenefitsOfDocumentModels" → 03-BenefitsOfDocumentModels} +0 -0
  72. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /04-UtilitiesAndTips" → 04-UtilitiesAndTips} +0 -0
  73. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /05-best-practices" → 05-best-practices} +0 -0
  74. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /_category_.json" → _category_.json} +0 -0
  75. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /three-data-layers.png" → three-data-layers.png} +0 -0
@@ -1,6 +1,6 @@
1
1
  # Build document editors
2
2
 
3
- ## Build with React on Powerhouse
3
+ ## Build with react on powerhouse
4
4
 
5
5
  At Powerhouse, frontend development for document editors follows a simple and familiar flow, leveraging the power and flexibility of React.
6
6
 
@@ -26,13 +26,13 @@ Powerhouse aims to keep your developer experience clean, familiar, and focused:
26
26
  To kickstart your editor development, Powerhouse provides a command to generate a basic editor template. This command reads your document model specifications and creates the initial `editor.tsx` file.
27
27
  If you want a refresher on how to define your document model specification please read the chapter on [specifying the State Schema](/academy/MasteryTrack/DocumentModelCreation/SpecifyTheStateSchema)
28
28
 
29
- For example, to generate an editor for a TodoList document model with a document type `powerhouse/todo-list`:
29
+ For example, to generate an editor for a To-do List document model with a document type `powerhouse/todolist`:
30
30
 
31
31
  ```bash
32
- ph generate --editor todo-list-editor --document-types powerhouse/todo-list
32
+ ph generate --editor ToDoList --document-types powerhouse/todolist
33
33
  ```
34
34
 
35
- This will create the template in the `editors/todo-list-editor/editor.tsx` folder.
35
+ This will create the template in the `editors/to-do-list/editor.tsx` folder.
36
36
 
37
37
  ### Styling your editor
38
38
 
@@ -41,11 +41,11 @@ You have several options for styling your editor components:
41
41
  1. **Default HTML Styling**: Standard HTML tags (`<h1>`, `<p>`, `<button>`, etc.) will render with default browser styles or any base styling provided by the Connect environment. This is suitable for basic structure and quick prototyping.
42
42
 
43
43
  2. **Tailwind CSS**: Connect Studio comes with Tailwind CSS integrated. You can directly use Tailwind utility classes in your JSX for rapid and consistent styling without writing separate CSS files.
44
- _Example (from the TodoList Editor):_
44
+ _Example (from the ToDoList Editor):_
45
45
 
46
46
  ```typescript
47
47
  <div className="container mx-auto p-4 max-w-md">
48
- <h1 className="text-2xl font-bold mb-4">TodoList</h1>
48
+ <h1 className="text-2xl font-bold mb-4">ToDoList</h1>
49
49
  {/* ... more Tailwind styled elements */}
50
50
  </div>
51
51
  ```
@@ -103,137 +103,46 @@ In any package the styles are being generated through the styles.css file with t
103
103
  - When installing a package with `ph install` on any instance, package styles are automatically added to styles.css. This ensures production builds always include the required package styles.
104
104
  :::
105
105
 
106
- ## State management in editors: Hooks vs Props
106
+ ### State management in editors
107
107
 
108
- When you build an editor in Powerhouse, there are **two ways** to access and modify document state. Understanding the difference is important for choosing the right approach for your component.
108
+ When you build an editor in Powerhouse, your main editor component receives `EditorProps`. These props are crucial for interacting with the document:
109
109
 
110
- ### Understanding the two approaches
110
+ - **`document`**: This object contains the entire document structure, including its current state. You'll typically access the global document state via `document.state.global`.
111
+ - **`dispatch`**: This function is your gateway to modifying the document's state. You call `dispatch` with an action object (usually created by action creators from your document model's generated code) to signal an intended change.
111
112
 
112
- <details>
113
- <summary>ℹ️ For Non-Technical Readers</summary>
114
-
115
- Think of it like ordering food at a restaurant:
116
-
117
- **Hooks Approach** 🪝: Like having a direct line to the kitchen. Any component can call the kitchen directly to get the current menu (state) or place an order (dispatch an action). It's independent and self-sufficient.
118
-
119
- **Props Approach** 📦: Like a waiter passing you a menu and taking your order. The main component receives everything and passes it down to child components. Children can only work with what they're given.
120
-
121
- **Which is better?** For Powerhouse editors, we recommend the **Hooks approach** because:
122
- - Components are more independent
123
- - Easier to move components around
124
- - Less "prop drilling" (passing data through many layers)
125
- - Matches modern React best practices
126
-
127
- </details>
128
-
129
- ### Method 1: Using Hooks (Recommended) 🪝
130
-
131
- The **hook-based approach** uses `useSelectedTodoListDocument` — a React hook that Powerhouse generates for your document model. Any component can call this hook to get the current document state and a function to dispatch changes.
113
+ **Local vs. Global State:**
132
114
 
115
+ - **Local Component State**: For UI-specific state that doesn't need to be part of the persisted document model (e.g., the current text in an input field before submission, visibility of a dropdown), use React's `useState` hook.
133
116
  ```typescript
134
- import { useSelectedTodoListDocument } from "todo-tutorial/document-models/todo-list";
135
- import { addTodoItem } from "todo-tutorial/document-models/todo-list";
136
-
137
- export function AddTodo() {
138
- // The hook returns [document, dispatch]
139
- const [todoList, dispatch] = useSelectedTodoListDocument();
140
-
141
- if (!todoList) return null;
142
-
143
- const handleAdd = (text: string) => {
144
- dispatch(addTodoItem({ text }));
145
- };
146
-
147
- return (
148
- <button onClick={() => handleAdd("New task")}>
149
- Add Todo
150
- </button>
151
- );
152
- }
153
- ```
154
-
155
- **Why hooks are recommended:**
156
- - ✅ **Self-contained components**: Each component gets its own connection to the document
157
- - ✅ **Less boilerplate**: No need to pass props through multiple levels
158
- - ✅ **Easier refactoring**: Move components around without rewiring props
159
- - ✅ **Modern React pattern**: Follows React's recommended approach for state management
160
-
161
- ### Method 2: Using Props 📦
117
+ const [inputValue, setInputValue] = useState('');
118
+ // ...
119
+ <input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
120
+ ```
121
+ - **Global Document State**: For data that is part of the document itself and should be saved (e.g., the items in a to-do list), you modify it by dispatching actions. The `document.state.global` object provides read-only access to this state within your editor.
162
122
 
163
- The **props-based approach** receives the document and dispatch function as properties passed from a parent component.
123
+ **Dispatching Actions:**
124
+ Your document model's generated code (e.g., in `document-models/your-model/index.js` or `document-models/your-model/gen/operations.js`) will provide action creators.
164
125
 
165
126
  ```typescript
166
- import { EditorProps } from 'document-model';
167
- import { TodoListDocument } from '../../document-models/todo-list/index.js';
168
-
169
- export type IProps = EditorProps<TodoListDocument>;
170
-
171
- export default function Editor(props: IProps) {
172
- const { document, dispatch } = props;
173
- const state = document.state.global;
174
-
175
- // Now you'd pass state and dispatch to child components as props
176
- return (
177
- <div>
178
- <TodoList items={state.items} dispatch={dispatch} />
179
- </div>
180
- );
181
- }
127
+ // Assuming 'actions' are imported from your document model
128
+ // import { actions } from '../../document-models/to-do-list/index.js';
129
+
130
+ // Inside your editor component:
131
+ // function Editor({ document, dispatch }: IProps) {
132
+ // ...
133
+ // const handleAddItem = () => {
134
+ // if (todoItem.trim()) {
135
+ // dispatch(actions.addTodoItem({ // Dispatch action to add item.
136
+ // id: Math.random().toString(), // Generate a simple unique ID
137
+ // text: todoItem,
138
+ // }));
139
+ // setTodoItem(''); // Clear local input state
140
+ // }
141
+ // };
142
+ // }
182
143
  ```
183
144
 
184
- **When props might be useful:**
185
- - When you need strict control over which components can access state
186
- - When building components that should work outside of Powerhouse context
187
- - For testing purposes where you want to inject mock state
188
-
189
- ### Which should you use?
190
-
191
- | Scenario | Recommended Approach |
192
- |----------|---------------------|
193
- | Building a standard Powerhouse editor | **Hooks** 🪝 |
194
- | Component needs document state | **Hooks** 🪝 |
195
- | Building reusable UI components (buttons, inputs) | **Props** 📦 |
196
- | Need to test components in isolation | **Props** 📦 |
197
-
198
- **Bottom line**: Use hooks for most Powerhouse editor development. It's simpler, cleaner, and matches the patterns used in the [todo-demo repository](https://github.com/powerhouse-inc/todo-demo).
199
-
200
- ## Local vs. Global State
201
-
202
- When building editors, you'll work with two types of state:
203
-
204
- - **Global Document State**: Data that is part of the document itself and should be saved. This is accessed via hooks (`useSelectedTodoListDocument`) or props (`document.state.global`). You modify it by dispatching actions.
205
-
206
- - **Local Component State**: UI-specific state that doesn't need to be saved (e.g., "is the dropdown open?", "what's in the input field before submission?"). Use React's `useState` hook for this.
207
-
208
- ```typescript
209
- import { useState } from 'react';
210
- import { useSelectedTodoListDocument, addTodoItem } from "todo-tutorial/document-models/todo-list";
211
-
212
- export function AddTodo() {
213
- // Local state - just for this component's UI
214
- const [inputValue, setInputValue] = useState('');
215
-
216
- // Global document state - saved in the document
217
- const [todoList, dispatch] = useSelectedTodoListDocument();
218
-
219
- const handleSubmit = () => {
220
- if (inputValue.trim()) {
221
- dispatch(addTodoItem({ text: inputValue })); // Updates global state
222
- setInputValue(''); // Clears local state
223
- }
224
- };
225
-
226
- return (
227
- <div>
228
- <input
229
- value={inputValue}
230
- onChange={(e) => setInputValue(e.target.value)}
231
- />
232
- <button onClick={handleSubmit}>Add</button>
233
- </div>
234
- );
235
- }
236
- ```
145
+ The actual state modification logic resides in your document model's reducers, ensuring that all changes are consistent and follow the defined operations.
237
146
 
238
147
  ## Powerhouse component library
239
148
 
@@ -275,24 +184,24 @@ Storybook allows you to:
275
184
  ```
276
185
 
277
186
  <details>
278
- <summary>Tutorial: Implementing the TodoList Editor</summary>
187
+ <summary>Tutorial: Implementing the `To-do List` Editor</summary>
279
188
 
280
- ## Build a TodoList editor
189
+ ## Build a To-do List editor
281
190
 
282
- In this final part of our tutorial we will continue with the interface or editor implementation of the **TodoList** document model. This means you will create a simple user interface for the **TodoList** document model which will be used inside the Connect app to create, update and delete your TodoList items, and also display the statistics we've implemented in our reducers (if you followed the advanced version).
191
+ In this final part of our tutorial we will continue with the interface or editor implementation of the **To-do List** document model. This means you will create a simple user interface for the **To-do List** document model which will be used inside the Connect app to create, update and delete your To-do List items, but also dispaly the statistics we've implemented in our reducers.
283
192
 
284
193
  ## Generate the editor template
285
194
 
286
- Run the command below to generate the editor template for the **TodoList** document model.
287
- This command reads the **TodoList** document model definition from the `document-models` folder and generates the editor template in the `editors/todo-list-editor` folder as `editor.tsx`.
195
+ Run the command below to generate the editor template for the **To-do List** document model.
196
+ This command reads the **To-do List** document model definition from the `document-models` folder and generates the editor template in the `editors/to-do-list` folder as `editor.tsx`.
288
197
 
289
- Notice the `--editor` flag which specifies the editor name, and the `--document-types` flag defines the document type `powerhouse/todo-list`.
198
+ Notice the `--editor` flag which specifies the **To-do List** document model, and the `--document-types` flag defines the document type `powerhouse/todolist`.
290
199
 
291
200
  ```bash
292
- ph generate --editor todo-list-editor --document-types powerhouse/todo-list
201
+ ph generate --editor ToDoList --document-types powerhouse/todolist
293
202
  ```
294
203
 
295
- Once complete, navigate to the `editors/todo-list-editor/editor.tsx` file and open it in your editor.
204
+ Once complete, navigate to the `editors/to-do-list/editor.tsx` file and open it in your editor.
296
205
 
297
206
  ### Editor implementation options
298
207
 
@@ -306,222 +215,323 @@ Connect Studio provides a dynamic local environment (`ph connect`) to visualize
306
215
 
307
216
  ---
308
217
 
309
- ## TodoList editor using hooks (Recommended)
310
-
311
- This approach uses the `useSelectedTodoListDocument` hook, which is the same pattern used in the Get Started tutorial and the [todo-demo repository](https://github.com/powerhouse-inc/todo-demo).
312
-
313
- ### Main editor file
218
+ ## To-do List editor
314
219
 
315
- ```typescript
316
- // editors/todo-list-editor/editor.tsx
317
- import { TodoList } from "./components/TodoList.js";
220
+ :::tip Implementing components
221
+ The editor we are about to implement makes use of some components from **Powerhouse Document Engineering**.
222
+ When you add the editor code, you'll see it makes use of two components, the `Checkbox` and `InputField`.
223
+ These are imported from the Powerhouse Document Engineering design system (`@powerhousedao/document-engineering/scalars`) which you should find under 'devdependencies' in your package.json file.
318
224
 
319
- export function Editor() {
320
- return (
321
- <div className="py-4 px-8">
322
- <TodoList />
323
- </div>
324
- );
325
- }
326
- ```
225
+ This system provides a library of reusable components to ensure consistency and speed up development.
226
+ You can explore available components, see usage examples, and understand their properties (props) using our Storybook instance. For a detailed guide on how to leverage the Document Engineering design system and Storybook, see [Using the Powerhouse Document Engineering](/academy/ComponentLibrary/DocumentEngineering) page.
327
227
 
328
- ### TodoList container component
228
+ For this tutorial, create a `components` folder inside `editors/to-do-list`. Then, within this new `components` folder, create the files for the `Checkbox` and `InputField` components (e.g., `checkbox.tsx` and `inputfield.tsx`) with the following code:
229
+ :::
329
230
 
231
+ <details>
232
+ <summary>Checkbox</summary>
330
233
  ```typescript
331
- // editors/todo-list-editor/components/TodoList.tsx
332
- import { useSelectedTodoListDocument } from "todo-tutorial/document-models/todo-list";
333
- import { Todos } from "./Todos.js";
334
- import { AddTodo } from "./AddTodo.js";
335
-
336
- export function TodoList() {
337
- const [selectedTodoList] = useSelectedTodoListDocument();
234
+ import { Form, BooleanField } from "@powerhousedao/document-engineering/scalars";
338
235
 
339
- if (!selectedTodoList) return null;
340
-
341
- const todos = selectedTodoList.state.global.items;
236
+ interface CheckboxProps {
237
+ value: boolean;
238
+ onChange: (value: boolean) => void;
239
+ }
342
240
 
241
+ export const Checkbox = ({ value, onChange }: CheckboxProps) => {
343
242
  return (
344
- <div>
345
- <h1 className="text-2xl font-bold mb-4">TodoList</h1>
346
- <section className="mb-4">
347
- <Todos todos={todos} />
348
- </section>
349
- <section>
350
- <AddTodo />
351
- </section>
352
- </div>
353
- );
354
- }
355
- ```
243
+ <Form onSubmit={() => {}}>
244
+ <BooleanField
245
+ name="checked"
246
+ description="Check this box to mark the todo as completed"
247
+ value={value}
248
+ onChange={onChange}
249
+ />
250
+ </Form>
251
+ );
252
+ };
356
253
 
357
- ### AddTodo component
254
+ ````
255
+ </details>
358
256
 
257
+ <details>
258
+ <summary>Inputfield</summary>
359
259
  ```typescript
360
- // editors/todo-list-editor/components/AddTodo.tsx
361
- import type { FormEventHandler } from "react";
362
- import { addTodoItem, useSelectedTodoListDocument } from "todo-tutorial/document-models/todo-list";
363
-
364
- export function AddTodo() {
365
- const [todoList, dispatch] = useSelectedTodoListDocument();
366
-
367
- if (!todoList) return null;
368
-
369
- const onSubmitAddTodo: FormEventHandler<HTMLFormElement> = (event) => {
370
- event.preventDefault();
371
-
372
- const form = event.currentTarget;
373
- const addTodoInput = form.elements.namedItem("addTodo") as HTMLInputElement;
374
- const text = addTodoInput.value;
375
- if (!text) return;
260
+ import { Form, StringField } from "@powerhousedao/document-engineering/scalars";
261
+
262
+ interface InputFieldProps {
263
+ input: string;
264
+ value: string;
265
+ label?: string;
266
+ onKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
267
+ handleInputChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
268
+ }
376
269
 
377
- dispatch(addTodoItem({ text }));
378
- form.reset();
379
- };
270
+ export const InputField = (props: InputFieldProps) => {
271
+ const { input, value, label, onKeyDown, handleInputChange } = props;
380
272
 
381
273
  return (
382
- <form onSubmit={onSubmitAddTodo} className="flex mx-auto min-w-fit gap-2">
383
- <input
384
- className="py-1 px-2 grow min-w-fit placeholder:text-gray-600 rounded border border-gray-600 text-gray-800"
385
- type="text"
386
- name="addTodo"
387
- placeholder="What needs to be done?"
388
- autoFocus
274
+ <Form
275
+ defaultValues={{
276
+ input: input,
277
+ }}
278
+ onSubmit={() => {}}
279
+ resetOnSuccessfulSubmit
280
+ >
281
+ <StringField
282
+ style={{
283
+ color: "black",
284
+ }}
285
+ label={label}
286
+ name="input"
287
+ value={value}
288
+ onKeyDown={onKeyDown}
289
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
290
+ handleInputChange(e);
291
+ }}
389
292
  />
390
- <button
391
- type="submit"
392
- className="text-gray-600 rounded border border-gray-600 px-3 py-1"
393
- >
394
- Add
395
- </button>
396
- </form>
293
+ </Form>
397
294
  );
398
- }
399
- ```
400
-
401
- ### Todo item component
402
-
403
- ```typescript
404
- // editors/todo-list-editor/components/Todo.tsx
405
- import { useState, type ChangeEventHandler, type FormEventHandler, type MouseEventHandler } from "react";
406
- import { deleteTodoItem, updateTodoItem, useSelectedTodoListDocument } from "todo-tutorial/document-models/todo-list";
407
- import type { TodoItem } from "todo-tutorial/document-models/todo-list";
408
-
409
- type Props = {
410
- todo: TodoItem;
411
295
  };
296
+ ````
412
297
 
413
- export function Todo({ todo }: Props) {
414
- const [isEditing, setIsEditing] = useState(false);
415
- const [todoList, dispatch] = useSelectedTodoListDocument();
416
-
417
- if (!todoList) return null;
418
-
419
- const onChangeTodoChecked: ChangeEventHandler<HTMLInputElement> = (event) => {
420
- dispatch(updateTodoItem({ id: todo.id, checked: event.target.checked }));
421
- };
422
-
423
- const onClickDeleteTodo: MouseEventHandler<HTMLButtonElement> = () => {
424
- dispatch(deleteTodoItem({ id: todo.id }));
425
- };
426
-
427
- const onSubmitUpdateTodoText: FormEventHandler<HTMLFormElement> = (event) => {
428
- event.preventDefault();
429
- const form = event.currentTarget;
430
- const textInput = form.elements.namedItem("todoText") as HTMLInputElement;
431
- const text = textInput.value;
432
- if (!text) return;
433
- dispatch(updateTodoItem({ id: todo.id, text }));
434
- setIsEditing(false);
435
- };
436
-
437
- if (isEditing) {
438
- return (
439
- <form className="flex gap-2 items-center" onSubmit={onSubmitUpdateTodoText}>
440
- <input className="p-1 grow" type="text" name="todoText" defaultValue={todo.text} autoFocus />
441
- <button type="submit" className="text-sm text-gray-600">Save</button>
442
- <button className="text-sm text-red-800" onClick={() => setIsEditing(false)}>Cancel</button>
443
- </form>
444
- );
445
- }
446
-
447
- return (
448
- <div className="flex justify-between items-center">
449
- <div className="flex items-center gap-2 p-1">
450
- <input type="checkbox" checked={todo.checked} onChange={onChangeTodoChecked} />
451
- <span className={todo.checked ? "line-through" : ""}>{todo.text}</span>
452
- </div>
453
- <span className="flex place-items-center gap-2 text-sm">
454
- <button className="text-gray-600" onClick={() => setIsEditing(true)}>Edit</button>
455
- <button className="text-red-800" onClick={onClickDeleteTodo}>Delete</button>
456
- </span>
457
- </div>
458
- );
459
- }
460
- ```
461
-
462
- ---
298
+ </details>
463
299
 
464
- ## Advanced: Adding stats display
300
+ Below is the complete code for the To-Do List editor. It primarily uses Tailwind CSS for styling and imports the local `Checkbox` and `InputField` components you created in the previous step. These local components, in turn, utilize elements from the Powerhouse Document Engineering design system.
465
301
 
466
- :::info Advanced Feature
467
- If you implemented the advanced version with statistics tracking, you can add a stats component to display the todo counts.
468
- :::
302
+ <details>
303
+ <summary>Complete To-do list editor example (using Tailwind CSS)</summary>
469
304
 
470
305
  ```typescript
471
- // Add to TodoList.tsx
472
- export function TodoList() {
473
- const [selectedTodoList] = useSelectedTodoListDocument();
474
-
475
- if (!selectedTodoList) return null;
476
-
477
- const { items, stats } = selectedTodoList.state.global;
478
-
479
- return (
480
- <div>
481
- <h1 className="text-2xl font-bold mb-4">TodoList</h1>
482
-
483
- {/* Stats section (only show if there are items) */}
484
- {items.length >= 2 && (
306
+ import { EditorProps } from 'document-model'; // Core type for editor components.
307
+ import {
308
+ ToDoListState, // Type for the global state of the ToDoList.
309
+ ToDoListAction, // Type for actions that can modify the ToDoList state.
310
+ ToDoListLocalState, // Type for local (non-shared) editor state (if needed).
311
+ ToDoItem, // Type for a single item in the list.
312
+ actions, // Object containing action creators for dispatching changes.
313
+ ToDoListDocument // The complete document structure including state and metadata.
314
+ } from '../../document-models/to-do-list/index.js'; // Path to your document model definition.
315
+ import { useState } from 'react'; // React hook for managing component-local state.
316
+ import { Checkbox } from './components/checkbox.js'; // Custom Checkbox component.
317
+ import { InputField } from './components/inputfield.js'; // Custom InputField component.
318
+
319
+ // Define the props expected by this Editor component. It extends EditorProps with our specific document type.
320
+ export type IProps = EditorProps<ToDoListDocument>;
321
+
322
+ // Define the main Editor component function.
323
+ export default function Editor(props: IProps) {
324
+ // Destructure props for easier access.
325
+ const { document, dispatch } = props;
326
+ // Access the global state from the document object.
327
+ const { state: { global: state } } = document;
328
+
329
+ // --- Component State ---
330
+ // State for the text input field where new tasks are typed.
331
+ const [todoItem, setTodoItem] = useState('');
332
+ // State to track which item is currently being edited (null if none). Stores the item's ID.
333
+ const [editingItemId, setEditingItemId] = useState<string | null>(null);
334
+ // State to hold the text of the item currently being edited.
335
+ const [editedText, setEditedText] = useState('');
336
+
337
+ // Sort items to show unchecked items first
338
+ const sortedItems: ToDoItem[] = [...state.items].sort((a, b) => {
339
+ return (a.checked ? 1 : 0) - (b.checked ? 1 : 0);
340
+ });
341
+
342
+ // --- JSX Structure (What gets rendered) ---
343
+ return (
344
+ // Main container div.
345
+ // `container`: Sets max-width based on viewport breakpoints.
346
+ // `mx-auto`: Centers the container horizontally.
347
+ // `p-4`: Adds padding on all sides (4 units, typically 1rem).
348
+ // `max-w-sm`: Sets a maximum width (small size).
349
+ <div className="container mx-auto p-4 max-w-xs">
350
+ {/* Heading for the editor */}
351
+ {/* `text-2xl`: Sets font size to extra-large. */}
352
+ {/* `font-bold`: Makes the text bold. */}
353
+ {/* `mb-4`: Adds margin to the bottom. */}
354
+ <h1 className="text-2xl font-bold mb-4">To-do List</h1>
355
+
356
+ {/* Stats Section */}
357
+ {state.items.length >= 2 && (
485
358
  <div className="mb-4 bg-white rounded-lg px-3 py-2 shadow-md">
486
359
  <div className="grid grid-cols-3 gap-3">
487
360
  <div>
488
- <div className="text-xs text-slate-500">Total</div>
489
- <div className="text-lg font-semibold">{stats.total}</div>
361
+ <div className="text-xs text-slate-500 mb-0.5">Total</div>
362
+ <div className="text-lg font-semibold text-slate-800">{state.stats.total}</div>
490
363
  </div>
491
364
  <div>
492
- <div className="text-xs text-slate-500">Completed</div>
493
- <div className="text-lg font-semibold text-green-600">{stats.checked}</div>
365
+ <div className="text-xs text-slate-500 mb-0.5">Completed</div>
366
+ <div className="text-lg font-semibold text-green-600">{state.stats.checked}</div>
494
367
  </div>
495
368
  <div>
496
- <div className="text-xs text-slate-500">Remaining</div>
497
- <div className="text-lg font-semibold text-orange-600">{stats.unchecked}</div>
369
+ <div className="text-xs text-slate-500 mb-0.5">Remaining</div>
370
+ <div className="text-lg font-semibold text-orange-600">{state.stats.unchecked}</div>
498
371
  </div>
499
372
  </div>
500
373
  </div>
501
374
  )}
502
375
 
503
- <section className="mb-4">
504
- <Todos todos={items} />
505
- </section>
506
- <section>
507
- <AddTodo />
508
- </section>
376
+ {/* Container for the input field and "Add" button */}
377
+ {/* `flex items-end`: Enables flexbox layout for children with bottom alignment. */}
378
+ {/* `gap-2`: Adds a small gap between flex items. */}
379
+ {/* `mb-4`: Adds margin to the bottom. */}
380
+ <div className="flex items-end gap-2 mb-4">
381
+ {/* Custom InputField component */}
382
+ <div className="flex-grow">
383
+ <InputField
384
+ label="New Task" // Prop for accessibility/placeholder.
385
+ input={todoItem} // Current value from state.
386
+ value={todoItem} // Controlled component value.
387
+ handleInputChange={(e) => setTodoItem(e.target.value)} // Update state on change.
388
+ onKeyDown={(e) => { // Handle "Enter" key press to add item.
389
+ if (e.key === 'Enter' && todoItem.trim()) { // Check if key is Enter and input is not empty
390
+ dispatch(actions.addTodoItem({ // Dispatch action to add item.
391
+ id: Math.random().toString(), // Generate a simple unique ID (use a better method in production!).
392
+ text: todoItem,
393
+ }));
394
+ setTodoItem(''); // Clear the input field.
395
+ }
396
+ }}
397
+ />
398
+ </div>
399
+ {/* "Add" button */}
400
+ {/* `bg-blue-500`: Sets background color to blue. */}
401
+ {/* `hover:bg-blue-600`: Changes background color on hover. */}
402
+ {/* `text-white`: Sets text color to white. */}
403
+ {/* `px-4`: Adds horizontal padding (4 units). */}
404
+ {/* `py-1.5`: Adds vertical padding (1.5 units). */}
405
+ {/* `rounded`: Applies rounded corners. */}
406
+ {/* `transition-colors`: Smoothly animates color changes. */}
407
+ <button
408
+ className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-1.5 rounded transition-colors"
409
+ onClick={() => { // Handle button click to add item.
410
+ if (todoItem.trim()) { // Check if input is not empty
411
+ dispatch(actions.addTodoItem({ // Dispatch action to add item.
412
+ id: Math.random().toString(), // Simple unique ID.
413
+ text: todoItem,
414
+ }));
415
+ setTodoItem(''); // Clear the input field.
416
+ }
417
+ }}
418
+ >
419
+ Add
420
+ </button>
421
+ </div>
422
+
423
+ {/* Unordered list to display the to-do items */}
424
+ {/* `list-none`: Removes default list bullet points. */}
425
+ {/* `p-0`: Removes default padding. */}
426
+ <ul className="list-none p-0">
427
+ {/* Map over the items array in the global state to render each item */}
428
+ {sortedItems.map((item: ToDoItem) => (
429
+ // List item element for each to-do.
430
+ // `key={item.id}`: React requires a unique key for list items for efficient updates.
431
+ // `flex`: Enables flexbox layout (checkbox, text, delete icon in a row).
432
+ // `items-center`: Aligns items vertically in the center.
433
+ // `p-2`: Adds padding.
434
+ // `relative`: Needed for positioning the delete icon absolutely (if we were doing that).
435
+ // `border-b`: Adds a bottom border.
436
+ // `border-gray-100`: Sets border color to light gray.
437
+ <li
438
+ key={item.id}
439
+ className="flex items-center p-2 relative border-b border-gray-100"
440
+ >
441
+ {/* Custom Checkbox component */}
442
+ <Checkbox
443
+ value={item.checked} // Bind checked state to item's checked property.
444
+ onChange={() => { // Handle checkbox click.
445
+ dispatch(actions.updateTodoItem({ // Dispatch action to update item.
446
+ id: item.id,
447
+ checked: !item.checked, // Toggle the checked state.
448
+ }));
449
+ }}
450
+ />
451
+
452
+ {/* Conditional Rendering: Show input field or text based on editing state */}
453
+ {editingItemId === item.id ? (
454
+ // --- Editing State ---
455
+ // Input field shown when this item is being edited.
456
+ // `ml-2`: Adds left margin.
457
+ // `flex-grow`: Allows input to take available horizontal space.
458
+ // `p-1`: Adds small padding.
459
+ // `border`: Adds a default border.
460
+ // `rounded`: Applies rounded corners.
461
+ // `focus:outline-none`: Removes the default browser focus outline.
462
+ // `focus:ring-1 focus:ring-blue-500`: Adds a custom blue ring when focused.
463
+ <input
464
+ className="ml-2 flex-grow p-1 border rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
465
+ value={editedText} // Controlled input value from editedText state.
466
+ onChange={(e) => setEditedText(e.target.value)} // Update editedText state.
467
+ onKeyDown={(e) => { // Handle "Enter" key to save changes.
468
+ if (e.key === 'Enter') {
469
+ dispatch(actions.updateTodoItem({ // Dispatch update action.
470
+ id: item.id,
471
+ text: editedText, // Save the edited text.
472
+ }));
473
+ setEditingItemId(null); // Exit editing mode.
474
+ }
475
+ }}
476
+ autoFocus // Automatically focus the input when it appears.
477
+ />
478
+ ) : (
479
+ // --- Display State ---
480
+ // Container for the item text and delete icon when not editing.
481
+ // `ml-2`: Adds left margin.
482
+ // `flex items-center`: Aligns text and icon vertically.
483
+ // `flex-grow`: Allows this container to take available space.
484
+ // `gap-1`: Adds a small gap between text and icon.
485
+ <div className="ml-2 flex items-center flex-grow gap-1">
486
+ {/* The actual to-do item text */}
487
+ {/* `cursor-pointer`: Shows a pointer cursor on hover, indicating clickability. */}
488
+ {/* Conditional class: Apply line-through and gray text if item is checked. */}
489
+ {/* `line-through`: Strikes through the text. */}
490
+ {/* `text-gray-500`: Sets text color to gray. */}
491
+ <span
492
+ className={`cursor-pointer ${item.checked ? 'line-through text-gray-500' : ''}`}
493
+ onClick={() => { // Handle click to enter editing mode.
494
+ setEditingItemId(item.id); // Set the ID of the item being edited.
495
+ setEditedText(item.text); // Initialize the input with current text.
496
+ }}
497
+ >
498
+ {item.text} {/* Display the item's text */}
499
+ </span>
500
+ {/* Delete "button" (using a span styled as a button) */}
501
+ {/* `text-gray-400`: Sets default text color to light gray. */}
502
+ {/* `cursor-pointer`: Shows pointer cursor. */}
503
+ {/* `opacity-40`: Makes it semi-transparent by default. */}
504
+ {/* `transition-all duration-200`: Smoothly animates all changes (opacity, color). */}
505
+ {/* `text-base font-bold`: Sets text size and weight. */}
506
+ {/* `inline-flex items-center`: Needed for proper alignment if using an icon font/SVG. */}
507
+ {/* `pl-1`: Adds small left padding. */}
508
+ {/* `hover:opacity-100`: Makes it fully opaque on hover. */}
509
+ {/* `hover:text-red-500`: Changes text color to red on hover. */}
510
+ <span
511
+ className="text-gray-400 cursor-pointer opacity-40 transition-all duration-200 text-base font-bold inline-flex items-center pl-1 hover:opacity-100 hover:text-red-500"
512
+ onClick={() => dispatch(actions.deleteTodoItem({ id: item.id }))} // Dispatch delete action on click.
513
+ >
514
+ × {/* Simple multiplication sign used as delete icon */}
515
+ </span>
516
+ </div>
517
+ )}
518
+ </li>
519
+ ))}
520
+ </ul>
509
521
  </div>
510
522
  );
511
523
  }
512
524
  ```
513
525
 
514
- ---
515
-
516
- ## Test your editor
526
+ </details>
517
527
 
518
- Now you can run the Connect app and see the **TodoList** editor in action:
528
+ Now you can run the Connect app and see the **To-do List** editor in action.
519
529
 
520
530
  ```bash
521
531
  ph connect
522
532
  ```
523
533
 
524
- In Connect, in the bottom right corner you'll find a new Document Model that you can create: **TodoList**. Click on it to create a new TodoList document.
534
+ In Connect, in the bottom right corner you'll find a new Document Model that you can create: **To-do List**. Click on it to create a new To-do List document.
525
535
 
526
536
  :::tip Connect as your dynamic development environment
527
537
  The editor will update dynamically, so you can play around with your editor styling while seeing your results appear in Connect Studio.
@@ -530,9 +540,9 @@ The editor will update dynamically, so you can play around with your editor styl
530
540
  </details>
531
541
 
532
542
  Congratulations!
533
- If you managed to follow this tutorial until this point, you have successfully implemented the **TodoList** document model with its reducer operations and editor.
543
+ If you managed to follow this tutorial until this point, you have successfully implemented the **To-do List** document model with its reducer operations and editor.
534
544
 
535
545
  ## Up Next
536
546
 
537
- Now you can move on to creating a [custom drive explorer](/academy/MasteryTrack/BuildingUserExperiences/BuildingADriveExplorer) for your TodoList document.
538
- Imagine you have many TodoLists sitting in a drive. A custom drive explorer will allow you to organize and track them at a glance, opening up a new world of possibilities to increase the functionality of your documents!
547
+ Now you can move on to creating a [custom drive explorer](/academy/MasteryTrack/BuildingUserExperiences/BuildingADriveExplorer) for your To-do List document.
548
+ Imagine you have many To-do Lists sitting in a drive. A custom drive explorer will allow you to organize and track them at a glance, opening up a new world of possibilities to increase the functionality of your documents!