@powerhousedao/academy 2.5.0-dev.27 → 2.5.0-dev.28
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.
- package/CHANGELOG.md +10 -0
- package/docs/academy/01-GetStarted/_04-BuildToDoListEditor +50 -185
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/02-SpecifyTheStateSchema.md +1 -1
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +4 -0
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/01-BuildingDocumentEditors.md +338 -31
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 2.5.0-dev.28 (2025-06-16)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- add app skeleton to html at build time ([1882bb820](https://github.com/powerhouse-inc/powerhouse/commit/1882bb820))
|
|
6
|
+
|
|
7
|
+
### ❤️ Thank You
|
|
8
|
+
|
|
9
|
+
- acaldas
|
|
10
|
+
|
|
1
11
|
## 2.5.0-dev.27 (2025-06-16)
|
|
2
12
|
|
|
3
13
|
This was a version bump only for @powerhousedao/academy to align it with other projects, there were no code changes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Build a ToDoList Editor
|
|
2
2
|
|
|
3
|
-
In this chapter 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.
|
|
3
|
+
In this chapter 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, but also dispaly the statistics we've implemented in our reducers.
|
|
4
4
|
|
|
5
5
|
## Generate the editor template
|
|
6
6
|
|
|
@@ -26,167 +26,6 @@ When building your editor component within the Powerhouse ecosystem, you have se
|
|
|
26
26
|
|
|
27
27
|
Connect Studio provides a dynamic local environment (`ph connect`) to visualize your components instantly as you build them, regardless of the styling method you choose. Manual build steps are typically only needed when publishing packages.
|
|
28
28
|
|
|
29
|
-
Let's look at a simple example component structure a styling method can be applied.
|
|
30
|
-
|
|
31
|
-
**Example Component Structure (Base HTML with inline styles)**
|
|
32
|
-
|
|
33
|
-
<details>
|
|
34
|
-
<summary>Base HTML Example</summary>
|
|
35
|
-
|
|
36
|
-
Here's a basic editor structure using only standard HTML tags.
|
|
37
|
-
This demonstrates how elements look with very minimal default styling:
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
import { EditorProps } from 'document-model';
|
|
41
|
-
// Assuming a simple document model for demonstration
|
|
42
|
-
// import { ExampleDocument, actions } from '../../document-models/example';
|
|
43
|
-
|
|
44
|
-
// Replace with your actual document type props if needed
|
|
45
|
-
export type IProps = EditorProps<any>;
|
|
46
|
-
|
|
47
|
-
export default function Editor({ document, dispatch }: IProps) {
|
|
48
|
-
return (
|
|
49
|
-
<div>
|
|
50
|
-
<h1 style={{ fontWeight: 'bold' }}>Document Title</h1>
|
|
51
|
-
<h2>Document Subtitle</h2>
|
|
52
|
-
<input
|
|
53
|
-
type="text"
|
|
54
|
-
placeholder="Small text input"
|
|
55
|
-
style={{ border: '1px solid gray', marginBottom: '0.5rem' }}
|
|
56
|
-
/>
|
|
57
|
-
<textarea
|
|
58
|
-
placeholder="Large text area"
|
|
59
|
-
rows={4}
|
|
60
|
-
style={{ border: '1px solid gray', display: 'block', marginBottom: '0.5rem' }}
|
|
61
|
-
/>
|
|
62
|
-
<button style={{ backgroundColor: 'yellow' }}>
|
|
63
|
-
Submit
|
|
64
|
-
</button>
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
</details>
|
|
70
|
-
|
|
71
|
-
*Run `ph connect` to see the default styles applied to components in real-time.*
|
|
72
|
-
|
|
73
|
-
**Styling with Tailwind CSS**
|
|
74
|
-
|
|
75
|
-
<details>
|
|
76
|
-
<summary>Tailwind CSS Example</summary>
|
|
77
|
-
|
|
78
|
-
Now, let's add Tailwind utility classes to the same structure for styling:
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
import { EditorProps } from 'document-model';
|
|
82
|
-
// import { ExampleDocument, actions } from '../../document-models/example';
|
|
83
|
-
|
|
84
|
-
export type IProps = EditorProps<any>;
|
|
85
|
-
|
|
86
|
-
export default function Editor({ document, dispatch }: IProps) {
|
|
87
|
-
return (
|
|
88
|
-
<div className="p-4 space-y-4"> {/* Add padding and spacing */}
|
|
89
|
-
<h1 className="text-2xl font-bold">Document Title</h1> {/* Style heading */}
|
|
90
|
-
<h2 className="text-lg text-gray-600 mb-4">Document Subtitle</h2> {/* Style subheading */}
|
|
91
|
-
<input
|
|
92
|
-
type="text"
|
|
93
|
-
placeholder="Small text input"
|
|
94
|
-
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4" // Style input
|
|
95
|
-
/>
|
|
96
|
-
<textarea
|
|
97
|
-
placeholder="Large text area"
|
|
98
|
-
rows={4}
|
|
99
|
-
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" // Style textarea
|
|
100
|
-
/>
|
|
101
|
-
<button className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors"> {/* Style button */}
|
|
102
|
-
Submit
|
|
103
|
-
</button>
|
|
104
|
-
</div>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
</details>
|
|
109
|
-
|
|
110
|
-
*Run `ph connect` to see these Tailwind styles applied in real-time.*
|
|
111
|
-
|
|
112
|
-
**Styling with a Custom CSS File**
|
|
113
|
-
|
|
114
|
-
<details>
|
|
115
|
-
<summary>Custom CSS File Example</summary>
|
|
116
|
-
|
|
117
|
-
You can also import a standard CSS file.
|
|
118
|
-
|
|
119
|
-
1. Create a CSS file (e.g., `editor.css`) in the same directory as your `editor.tsx`:
|
|
120
|
-
|
|
121
|
-
```css
|
|
122
|
-
/* editors/your-editor/editor.css */
|
|
123
|
-
.editor-container {
|
|
124
|
-
padding: 1rem;
|
|
125
|
-
border: 1px solid #ccc;
|
|
126
|
-
border-radius: 4px;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.editor-title {
|
|
130
|
-
color: rgb(51, 51, 54);
|
|
131
|
-
font-size: 2rem;
|
|
132
|
-
margin-bottom: 4px;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.editor-subtitle {
|
|
136
|
-
color: rgb(51, 51, 54);
|
|
137
|
-
font-size: 1.5rem;
|
|
138
|
-
margin-bottom: 4px;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.editor-button {
|
|
142
|
-
background-color: green;
|
|
143
|
-
color: white;
|
|
144
|
-
padding: 0.5rem 1rem;
|
|
145
|
-
border: none;
|
|
146
|
-
border-radius: 4px;
|
|
147
|
-
cursor: pointer;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.editor-button:hover {
|
|
151
|
-
background-color: darkgreen;
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
2. Import the CSS file and use the classes in your component:
|
|
156
|
-
|
|
157
|
-
```typescript
|
|
158
|
-
import { EditorProps } from 'document-model';
|
|
159
|
-
// import { ExampleDocument, actions } from '../../document-models/example';
|
|
160
|
-
import './editor.css'; // Import the CSS file
|
|
161
|
-
|
|
162
|
-
export type IProps = EditorProps<any>;
|
|
163
|
-
|
|
164
|
-
export default function Editor({ document, dispatch }: IProps) {
|
|
165
|
-
return (
|
|
166
|
-
<div className="editor-container"> {/* Use custom class */}
|
|
167
|
-
<h1 className="editor-title">Document Title</h1> {/* Use custom class */}
|
|
168
|
-
<h2 className="editor-subtitle">Document Subtitle</h2> {/* Default or other styles */}
|
|
169
|
-
<input
|
|
170
|
-
type="text"
|
|
171
|
-
placeholder="Small text input"
|
|
172
|
-
className="w-full p-2 border rounded mb-4" // Can mix with Tailwind/defaults
|
|
173
|
-
/>
|
|
174
|
-
<textarea
|
|
175
|
-
placeholder="Large text area"
|
|
176
|
-
rows={4}
|
|
177
|
-
className="w-full p-2 border rounded mb-4" // Can mix with Tailwind/defaults
|
|
178
|
-
/>
|
|
179
|
-
<button className="editor-button"> {/* Use custom class */}
|
|
180
|
-
Submit
|
|
181
|
-
</button>
|
|
182
|
-
</div>
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
</details>
|
|
187
|
-
|
|
188
|
-
*Run `ph connect` to see your custom CSS styles applied.*
|
|
189
|
-
|
|
190
29
|
---
|
|
191
30
|
|
|
192
31
|
## ToDoList Editor
|
|
@@ -200,7 +39,7 @@ These are imported from the Powerhouse Document Engineering design system (`@pow
|
|
|
200
39
|
This system provides a library of reusable components to ensure consistency and speed up development.
|
|
201
40
|
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.
|
|
202
41
|
|
|
203
|
-
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., `
|
|
42
|
+
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:
|
|
204
43
|
:::
|
|
205
44
|
|
|
206
45
|
<details>
|
|
@@ -277,7 +116,6 @@ Below is the complete code for the To-Do List editor. It primarily uses Tailwind
|
|
|
277
116
|
<summary>Complete ToDoList Editor Example (using Tailwind CSS)</summary>
|
|
278
117
|
|
|
279
118
|
```typescript
|
|
280
|
-
// Import necessary types and components.
|
|
281
119
|
import { EditorProps } from 'document-model'; // Core type for editor components.
|
|
282
120
|
import {
|
|
283
121
|
ToDoListState, // Type for the global state of the ToDoList.
|
|
@@ -286,10 +124,10 @@ import {
|
|
|
286
124
|
ToDoItem, // Type for a single item in the list.
|
|
287
125
|
actions, // Object containing action creators for dispatching changes.
|
|
288
126
|
ToDoListDocument // The complete document structure including state and metadata.
|
|
289
|
-
} from '
|
|
127
|
+
} from '../document-models/to-do-list/index.js'; // Path to your document model definition.
|
|
290
128
|
import { useState } from 'react'; // React hook for managing component-local state.
|
|
291
129
|
import { Checkbox } from './components/checkbox.js'; // Custom Checkbox component.
|
|
292
|
-
import { InputField } from './components/
|
|
130
|
+
import { InputField } from './components/inputfield.js'; // Custom InputField component.
|
|
293
131
|
|
|
294
132
|
// Define the props expected by this Editor component. It extends EditorProps with our specific document type.
|
|
295
133
|
export type IProps = EditorProps<ToDoListDocument>;
|
|
@@ -309,41 +147,68 @@ export default function Editor(props: IProps) {
|
|
|
309
147
|
// State to hold the text of the item currently being edited.
|
|
310
148
|
const [editedText, setEditedText] = useState('');
|
|
311
149
|
|
|
150
|
+
// Sort items to show unchecked items first
|
|
151
|
+
const sortedItems: ToDoItem[] = [...state.items].sort((a, b) => {
|
|
152
|
+
return (a.checked ? 1 : 0) - (b.checked ? 1 : 0);
|
|
153
|
+
});
|
|
154
|
+
|
|
312
155
|
// --- JSX Structure (What gets rendered) ---
|
|
313
156
|
return (
|
|
314
157
|
// Main container div.
|
|
315
158
|
// `container`: Sets max-width based on viewport breakpoints.
|
|
316
159
|
// `mx-auto`: Centers the container horizontally.
|
|
317
160
|
// `p-4`: Adds padding on all sides (4 units, typically 1rem).
|
|
318
|
-
// `max-w-
|
|
319
|
-
<div className="container mx-auto p-4 max-w-
|
|
161
|
+
// `max-w-sm`: Sets a maximum width (small size).
|
|
162
|
+
<div className="container mx-auto p-4 max-w-xs">
|
|
320
163
|
{/* Heading for the editor */}
|
|
321
164
|
{/* `text-2xl`: Sets font size to extra-large. */}
|
|
322
165
|
{/* `font-bold`: Makes the text bold. */}
|
|
323
|
-
{/* `mb-4`: Adds margin to the bottom
|
|
166
|
+
{/* `mb-4`: Adds margin to the bottom. */}
|
|
324
167
|
<h1 className="text-2xl font-bold mb-4">To-do List</h1>
|
|
325
168
|
|
|
169
|
+
{/* Stats Section */}
|
|
170
|
+
{state.items.length >= 2 && (
|
|
171
|
+
<div className="mb-4 bg-white rounded-lg px-3 py-2 shadow-md">
|
|
172
|
+
<div className="grid grid-cols-3 gap-3">
|
|
173
|
+
<div>
|
|
174
|
+
<div className="text-xs text-slate-500 mb-0.5">Total</div>
|
|
175
|
+
<div className="text-lg font-semibold text-slate-800">{state.stats.total}</div>
|
|
176
|
+
</div>
|
|
177
|
+
<div>
|
|
178
|
+
<div className="text-xs text-slate-500 mb-0.5">Completed</div>
|
|
179
|
+
<div className="text-lg font-semibold text-green-600">{state.stats.checked}</div>
|
|
180
|
+
</div>
|
|
181
|
+
<div>
|
|
182
|
+
<div className="text-xs text-slate-500 mb-0.5">Remaining</div>
|
|
183
|
+
<div className="text-lg font-semibold text-orange-600">{state.stats.unchecked}</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
)}
|
|
188
|
+
|
|
326
189
|
{/* Container for the input field and "Add" button */}
|
|
327
190
|
{/* `flex items-end`: Enables flexbox layout for children with bottom alignment. */}
|
|
328
191
|
{/* `gap-2`: Adds a small gap between flex items. */}
|
|
329
192
|
{/* `mb-4`: Adds margin to the bottom. */}
|
|
330
193
|
<div className="flex items-end gap-2 mb-4">
|
|
331
194
|
{/* Custom InputField component */}
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
195
|
+
<div className="flex-grow">
|
|
196
|
+
<InputField
|
|
197
|
+
label="New Task" // Prop for accessibility/placeholder.
|
|
198
|
+
input={todoItem} // Current value from state.
|
|
199
|
+
value={todoItem} // Controlled component value.
|
|
200
|
+
handleInputChange={(e) => setTodoItem(e.target.value)} // Update state on change.
|
|
201
|
+
onKeyDown={(e) => { // Handle "Enter" key press to add item.
|
|
202
|
+
if (e.key === 'Enter' && todoItem.trim()) { // Check if key is Enter and input is not empty
|
|
203
|
+
dispatch(actions.addTodoItem({ // Dispatch action to add item.
|
|
204
|
+
id: Math.random().toString(), // Generate a simple unique ID (use a better method in production!).
|
|
205
|
+
text: todoItem,
|
|
206
|
+
}));
|
|
207
|
+
setTodoItem(''); // Clear the input field.
|
|
208
|
+
}
|
|
209
|
+
}}
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
347
212
|
{/* "Add" button */}
|
|
348
213
|
{/* `bg-blue-500`: Sets background color to blue. */}
|
|
349
214
|
{/* `hover:bg-blue-600`: Changes background color on hover. */}
|
|
@@ -373,7 +238,7 @@ export default function Editor(props: IProps) {
|
|
|
373
238
|
{/* `p-0`: Removes default padding. */}
|
|
374
239
|
<ul className="list-none p-0">
|
|
375
240
|
{/* Map over the items array in the global state to render each item */}
|
|
376
|
-
{
|
|
241
|
+
{sortedItems.map((item: ToDoItem) => (
|
|
377
242
|
// List item element for each to-do.
|
|
378
243
|
// `key={item.id}`: React requires a unique key for list items for efficient updates.
|
|
379
244
|
// `flex`: Enables flexbox layout (checkbox, text, delete icon in a row).
|
|
@@ -141,5 +141,5 @@ By completing these steps, you have successfully specified the data structure fo
|
|
|
141
141
|
|
|
142
142
|
</details>
|
|
143
143
|
|
|
144
|
-
For a complete, working example, you can always refer to the [Example ToDoList Repository](./
|
|
144
|
+
For a complete, working example, you can always refer to the [Example ToDoList Repository](./ExampleToDoListRepository.md) which contains the full implementation of the concepts discussed in this Mastery Track.
|
|
145
145
|
|
package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md
CHANGED
|
@@ -132,3 +132,7 @@ Implementing comprehensive tests for your document model reducers is an investme
|
|
|
132
132
|
* **Better Collaboration**: Tests clarify the intended behavior of the document model for all team members.
|
|
133
133
|
|
|
134
134
|
By following the tutorial and applying these best practices, you can build a strong suite of tests that safeguard the integrity and functionality of your document models. This diligence is a hallmark of a "Mastery Track" developer, ensuring that the solutions you build are not just functional but also stable, maintainable, and trustworthy.
|
|
135
|
+
|
|
136
|
+
## Up Next
|
|
137
|
+
In the next chapter of the Mastery Track you will learn how to implement an editor for your document model so you can see a simple user interface for the **ToDoList** document model in action.
|
|
138
|
+
For a complete, working example, you can always refer to the [Example ToDoList Repository](./ExampleToDoListRepository.md) which contains the full implementation of the concepts discussed in this Mastery Track.
|
package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/01-BuildingDocumentEditors.md
CHANGED
|
@@ -144,26 +144,69 @@ Storybook allows you to:
|
|
|
144
144
|
</Form>
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
<details>
|
|
148
|
+
<summary>Tutorial: Implementing the `ToDoList` Editor</summary>
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
## Build a ToDoList Editor
|
|
151
|
+
|
|
152
|
+
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, but also dispaly the statistics we've implemented in our reducers.
|
|
153
|
+
|
|
154
|
+
## Generate the editor template
|
|
155
|
+
|
|
156
|
+
Run the command below to generate the editor template for the **ToDoList** document model.
|
|
157
|
+
This command reads the **ToDoList** document model definition from the `document-models` folder and generates the editor template in the `editors/to-do-list` folder as `editor.tsx`.
|
|
158
|
+
|
|
159
|
+
Notice the `--editor` flag which specifies the **ToDoList** document model, and the `--document-types` flag defines the document type `powerhouse/todolist`.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
ph generate --editor ToDoList --document-types powerhouse/todolist
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Once complete, navigate to the `editors/to-do-list/editor.tsx` file and open it in your editor.
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
### Editor Implementation Options
|
|
169
|
+
|
|
170
|
+
When building your editor component within the Powerhouse ecosystem, you have several options for styling, allowing you to leverage your preferred methods:
|
|
171
|
+
|
|
172
|
+
1. **Default HTML Styling:** Standard HTML tags (`<h1>`, `<p>`, `<button>`, etc.) will render with default styles offered through the boilerplate.
|
|
173
|
+
2. **Tailwind CSS:** Connect Studio comes with Tailwind CSS integrated. You can directly use Tailwind utility classes for rapid, consistent styling without writing separate CSS files.
|
|
174
|
+
3. **Custom CSS Files:** You can import traditional CSS files (`.css`) to apply custom styles or integrate existing style libraries.
|
|
175
|
+
|
|
176
|
+
Connect Studio provides a dynamic local environment (`ph connect`) to visualize your components instantly as you build them, regardless of the styling method you choose. Manual build steps are typically only needed when publishing packages.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## ToDoList Editor
|
|
181
|
+
|
|
182
|
+
:::tip
|
|
183
|
+
### Implementing Components
|
|
184
|
+
The editor we are about to implement makes use of some components from **Powerhouse Document Engineering**.
|
|
185
|
+
When you add the editor code, you'll see it makes use of two components, the `Checkbox` and `InputField`.
|
|
186
|
+
These are imported from the Powerhouse Document Engineering design system (`@powerhousedao/document-engineering/scalars`).
|
|
187
|
+
|
|
188
|
+
This system provides a library of reusable components to ensure consistency and speed up development.
|
|
189
|
+
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.
|
|
190
|
+
|
|
191
|
+
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:
|
|
192
|
+
:::
|
|
193
|
+
|
|
194
|
+
<details>
|
|
195
|
+
<summary>Checkbox</summary>
|
|
151
196
|
```typescript
|
|
152
|
-
// editors/to-do-list/components/checkbox.tsx
|
|
153
197
|
import { Form, BooleanField } from "@powerhousedao/document-engineering/scalars";
|
|
154
198
|
|
|
155
|
-
interface
|
|
199
|
+
interface CheckboxProps {
|
|
156
200
|
value: boolean;
|
|
157
201
|
onChange: (value: boolean) => void;
|
|
158
|
-
label?: string; // Added custom prop or passed through
|
|
159
202
|
}
|
|
160
203
|
|
|
161
|
-
export const Checkbox = ({ value, onChange
|
|
204
|
+
export const Checkbox = ({ value, onChange }: CheckboxProps) => {
|
|
162
205
|
return (
|
|
163
|
-
<Form onSubmit={() => {
|
|
206
|
+
<Form onSubmit={() => {}}>
|
|
164
207
|
<BooleanField
|
|
165
|
-
name="
|
|
166
|
-
description=
|
|
208
|
+
name="checked"
|
|
209
|
+
description="Check this box to mark the todo as completed"
|
|
167
210
|
value={value}
|
|
168
211
|
onChange={onChange}
|
|
169
212
|
/>
|
|
@@ -171,31 +214,295 @@ export const Checkbox = ({ value, onChange, label }: CustomCheckboxProps) => {
|
|
|
171
214
|
);
|
|
172
215
|
};
|
|
173
216
|
```
|
|
174
|
-
|
|
217
|
+
</details>
|
|
218
|
+
|
|
219
|
+
<details>
|
|
220
|
+
<summary>Inputfield</summary>
|
|
221
|
+
```typescript
|
|
222
|
+
import { Form, StringField } from "@powerhousedao/document-engineering/scalars";
|
|
223
|
+
|
|
224
|
+
interface InputFieldProps {
|
|
225
|
+
input: string;
|
|
226
|
+
value: string;
|
|
227
|
+
label?: string;
|
|
228
|
+
onKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
229
|
+
handleInputChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export const InputField = (props: InputFieldProps) => {
|
|
233
|
+
const { input, value, label, onKeyDown, handleInputChange } = props;
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<Form
|
|
237
|
+
defaultValues={{
|
|
238
|
+
input: input,
|
|
239
|
+
}}
|
|
240
|
+
onSubmit={() => {}}
|
|
241
|
+
resetOnSuccessfulSubmit
|
|
242
|
+
>
|
|
243
|
+
<StringField
|
|
244
|
+
style={{
|
|
245
|
+
color: "black",
|
|
246
|
+
}}
|
|
247
|
+
label={label}
|
|
248
|
+
name="input"
|
|
249
|
+
value={value}
|
|
250
|
+
onKeyDown={onKeyDown}
|
|
251
|
+
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
252
|
+
handleInputChange(e);
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
</Form>
|
|
256
|
+
);
|
|
257
|
+
};
|
|
258
|
+
```
|
|
259
|
+
</details>
|
|
175
260
|
|
|
176
|
-
## Conceptual Example: Building a ToDoList Editor
|
|
177
261
|
|
|
178
|
-
|
|
262
|
+
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.
|
|
263
|
+
|
|
264
|
+
<details>
|
|
265
|
+
<summary>Complete ToDoList Editor Example (using Tailwind CSS)</summary>
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { EditorProps } from 'document-model'; // Core type for editor components.
|
|
269
|
+
import {
|
|
270
|
+
ToDoListState, // Type for the global state of the ToDoList.
|
|
271
|
+
ToDoListAction, // Type for actions that can modify the ToDoList state.
|
|
272
|
+
ToDoListLocalState, // Type for local (non-shared) editor state (if needed).
|
|
273
|
+
ToDoItem, // Type for a single item in the list.
|
|
274
|
+
actions, // Object containing action creators for dispatching changes.
|
|
275
|
+
ToDoListDocument // The complete document structure including state and metadata.
|
|
276
|
+
} from '../document-models/to-do-list/index.js'; // Path to your document model definition.
|
|
277
|
+
import { useState } from 'react'; // React hook for managing component-local state.
|
|
278
|
+
import { Checkbox } from './components/checkbox.js'; // Custom Checkbox component.
|
|
279
|
+
import { InputField } from './components/inputfield.js'; // Custom InputField component.
|
|
280
|
+
|
|
281
|
+
// Define the props expected by this Editor component. It extends EditorProps with our specific document type.
|
|
282
|
+
export type IProps = EditorProps<ToDoListDocument>;
|
|
283
|
+
|
|
284
|
+
// Define the main Editor component function.
|
|
285
|
+
export default function Editor(props: IProps) {
|
|
286
|
+
// Destructure props for easier access.
|
|
287
|
+
const { document, dispatch } = props;
|
|
288
|
+
// Access the global state from the document object.
|
|
289
|
+
const { state: { global: state } } = document;
|
|
290
|
+
|
|
291
|
+
// --- Component State ---
|
|
292
|
+
// State for the text input field where new tasks are typed.
|
|
293
|
+
const [todoItem, setTodoItem] = useState('');
|
|
294
|
+
// State to track which item is currently being edited (null if none). Stores the item's ID.
|
|
295
|
+
const [editingItemId, setEditingItemId] = useState<string | null>(null);
|
|
296
|
+
// State to hold the text of the item currently being edited.
|
|
297
|
+
const [editedText, setEditedText] = useState('');
|
|
298
|
+
|
|
299
|
+
// Sort items to show unchecked items first
|
|
300
|
+
const sortedItems: ToDoItem[] = [...state.items].sort((a, b) => {
|
|
301
|
+
return (a.checked ? 1 : 0) - (b.checked ? 1 : 0);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// --- JSX Structure (What gets rendered) ---
|
|
305
|
+
return (
|
|
306
|
+
// Main container div.
|
|
307
|
+
// `container`: Sets max-width based on viewport breakpoints.
|
|
308
|
+
// `mx-auto`: Centers the container horizontally.
|
|
309
|
+
// `p-4`: Adds padding on all sides (4 units, typically 1rem).
|
|
310
|
+
// `max-w-sm`: Sets a maximum width (small size).
|
|
311
|
+
<div className="container mx-auto p-4 max-w-xs">
|
|
312
|
+
{/* Heading for the editor */}
|
|
313
|
+
{/* `text-2xl`: Sets font size to extra-large. */}
|
|
314
|
+
{/* `font-bold`: Makes the text bold. */}
|
|
315
|
+
{/* `mb-4`: Adds margin to the bottom. */}
|
|
316
|
+
<h1 className="text-2xl font-bold mb-4">To-do List</h1>
|
|
317
|
+
|
|
318
|
+
{/* Stats Section */}
|
|
319
|
+
{state.items.length >= 2 && (
|
|
320
|
+
<div className="mb-4 bg-white rounded-lg px-3 py-2 shadow-md">
|
|
321
|
+
<div className="grid grid-cols-3 gap-3">
|
|
322
|
+
<div>
|
|
323
|
+
<div className="text-xs text-slate-500 mb-0.5">Total</div>
|
|
324
|
+
<div className="text-lg font-semibold text-slate-800">{state.stats.total}</div>
|
|
325
|
+
</div>
|
|
326
|
+
<div>
|
|
327
|
+
<div className="text-xs text-slate-500 mb-0.5">Completed</div>
|
|
328
|
+
<div className="text-lg font-semibold text-green-600">{state.stats.checked}</div>
|
|
329
|
+
</div>
|
|
330
|
+
<div>
|
|
331
|
+
<div className="text-xs text-slate-500 mb-0.5">Remaining</div>
|
|
332
|
+
<div className="text-lg font-semibold text-orange-600">{state.stats.unchecked}</div>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
)}
|
|
337
|
+
|
|
338
|
+
{/* Container for the input field and "Add" button */}
|
|
339
|
+
{/* `flex items-end`: Enables flexbox layout for children with bottom alignment. */}
|
|
340
|
+
{/* `gap-2`: Adds a small gap between flex items. */}
|
|
341
|
+
{/* `mb-4`: Adds margin to the bottom. */}
|
|
342
|
+
<div className="flex items-end gap-2 mb-4">
|
|
343
|
+
{/* Custom InputField component */}
|
|
344
|
+
<div className="flex-grow">
|
|
345
|
+
<InputField
|
|
346
|
+
label="New Task" // Prop for accessibility/placeholder.
|
|
347
|
+
input={todoItem} // Current value from state.
|
|
348
|
+
value={todoItem} // Controlled component value.
|
|
349
|
+
handleInputChange={(e) => setTodoItem(e.target.value)} // Update state on change.
|
|
350
|
+
onKeyDown={(e) => { // Handle "Enter" key press to add item.
|
|
351
|
+
if (e.key === 'Enter' && todoItem.trim()) { // Check if key is Enter and input is not empty
|
|
352
|
+
dispatch(actions.addTodoItem({ // Dispatch action to add item.
|
|
353
|
+
id: Math.random().toString(), // Generate a simple unique ID (use a better method in production!).
|
|
354
|
+
text: todoItem,
|
|
355
|
+
}));
|
|
356
|
+
setTodoItem(''); // Clear the input field.
|
|
357
|
+
}
|
|
358
|
+
}}
|
|
359
|
+
/>
|
|
360
|
+
</div>
|
|
361
|
+
{/* "Add" button */}
|
|
362
|
+
{/* `bg-blue-500`: Sets background color to blue. */}
|
|
363
|
+
{/* `hover:bg-blue-600`: Changes background color on hover. */}
|
|
364
|
+
{/* `text-white`: Sets text color to white. */}
|
|
365
|
+
{/* `px-4`: Adds horizontal padding (4 units). */}
|
|
366
|
+
{/* `py-1.5`: Adds vertical padding (1.5 units). */}
|
|
367
|
+
{/* `rounded`: Applies rounded corners. */}
|
|
368
|
+
{/* `transition-colors`: Smoothly animates color changes. */}
|
|
369
|
+
<button
|
|
370
|
+
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-1.5 rounded transition-colors"
|
|
371
|
+
onClick={() => { // Handle button click to add item.
|
|
372
|
+
if (todoItem.trim()) { // Check if input is not empty
|
|
373
|
+
dispatch(actions.addTodoItem({ // Dispatch action to add item.
|
|
374
|
+
id: Math.random().toString(), // Simple unique ID.
|
|
375
|
+
text: todoItem,
|
|
376
|
+
}));
|
|
377
|
+
setTodoItem(''); // Clear the input field.
|
|
378
|
+
}
|
|
379
|
+
}}
|
|
380
|
+
>
|
|
381
|
+
Add
|
|
382
|
+
</button>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
{/* Unordered list to display the to-do items */}
|
|
386
|
+
{/* `list-none`: Removes default list bullet points. */}
|
|
387
|
+
{/* `p-0`: Removes default padding. */}
|
|
388
|
+
<ul className="list-none p-0">
|
|
389
|
+
{/* Map over the items array in the global state to render each item */}
|
|
390
|
+
{sortedItems.map((item: ToDoItem) => (
|
|
391
|
+
// List item element for each to-do.
|
|
392
|
+
// `key={item.id}`: React requires a unique key for list items for efficient updates.
|
|
393
|
+
// `flex`: Enables flexbox layout (checkbox, text, delete icon in a row).
|
|
394
|
+
// `items-center`: Aligns items vertically in the center.
|
|
395
|
+
// `p-2`: Adds padding.
|
|
396
|
+
// `relative`: Needed for positioning the delete icon absolutely (if we were doing that).
|
|
397
|
+
// `border-b`: Adds a bottom border.
|
|
398
|
+
// `border-gray-100`: Sets border color to light gray.
|
|
399
|
+
<li
|
|
400
|
+
key={item.id}
|
|
401
|
+
className="flex items-center p-2 relative border-b border-gray-100"
|
|
402
|
+
>
|
|
403
|
+
{/* Custom Checkbox component */}
|
|
404
|
+
<Checkbox
|
|
405
|
+
value={item.checked} // Bind checked state to item's checked property.
|
|
406
|
+
onChange={() => { // Handle checkbox click.
|
|
407
|
+
dispatch(actions.updateTodoItem({ // Dispatch action to update item.
|
|
408
|
+
id: item.id,
|
|
409
|
+
checked: !item.checked, // Toggle the checked state.
|
|
410
|
+
}));
|
|
411
|
+
}}
|
|
412
|
+
/>
|
|
413
|
+
|
|
414
|
+
{/* Conditional Rendering: Show input field or text based on editing state */}
|
|
415
|
+
{editingItemId === item.id ? (
|
|
416
|
+
// --- Editing State ---
|
|
417
|
+
// Input field shown when this item is being edited.
|
|
418
|
+
// `ml-2`: Adds left margin.
|
|
419
|
+
// `flex-grow`: Allows input to take available horizontal space.
|
|
420
|
+
// `p-1`: Adds small padding.
|
|
421
|
+
// `border`: Adds a default border.
|
|
422
|
+
// `rounded`: Applies rounded corners.
|
|
423
|
+
// `focus:outline-none`: Removes the default browser focus outline.
|
|
424
|
+
// `focus:ring-1 focus:ring-blue-500`: Adds a custom blue ring when focused.
|
|
425
|
+
<input
|
|
426
|
+
className="ml-2 flex-grow p-1 border rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
427
|
+
value={editedText} // Controlled input value from editedText state.
|
|
428
|
+
onChange={(e) => setEditedText(e.target.value)} // Update editedText state.
|
|
429
|
+
onKeyDown={(e) => { // Handle "Enter" key to save changes.
|
|
430
|
+
if (e.key === 'Enter') {
|
|
431
|
+
dispatch(actions.updateTodoItem({ // Dispatch update action.
|
|
432
|
+
id: item.id,
|
|
433
|
+
text: editedText, // Save the edited text.
|
|
434
|
+
}));
|
|
435
|
+
setEditingItemId(null); // Exit editing mode.
|
|
436
|
+
}
|
|
437
|
+
}}
|
|
438
|
+
autoFocus // Automatically focus the input when it appears.
|
|
439
|
+
/>
|
|
440
|
+
) : (
|
|
441
|
+
// --- Display State ---
|
|
442
|
+
// Container for the item text and delete icon when not editing.
|
|
443
|
+
// `ml-2`: Adds left margin.
|
|
444
|
+
// `flex items-center`: Aligns text and icon vertically.
|
|
445
|
+
// `flex-grow`: Allows this container to take available space.
|
|
446
|
+
// `gap-1`: Adds a small gap between text and icon.
|
|
447
|
+
<div className="ml-2 flex items-center flex-grow gap-1">
|
|
448
|
+
{/* The actual to-do item text */}
|
|
449
|
+
{/* `cursor-pointer`: Shows a pointer cursor on hover, indicating clickability. */}
|
|
450
|
+
{/* Conditional class: Apply line-through and gray text if item is checked. */}
|
|
451
|
+
{/* `line-through`: Strikes through the text. */}
|
|
452
|
+
{/* `text-gray-500`: Sets text color to gray. */}
|
|
453
|
+
<span
|
|
454
|
+
className={`cursor-pointer ${item.checked ? 'line-through text-gray-500' : ''}`}
|
|
455
|
+
onClick={() => { // Handle click to enter editing mode.
|
|
456
|
+
setEditingItemId(item.id); // Set the ID of the item being edited.
|
|
457
|
+
setEditedText(item.text); // Initialize the input with current text.
|
|
458
|
+
}}
|
|
459
|
+
>
|
|
460
|
+
{item.text} {/* Display the item's text */}
|
|
461
|
+
</span>
|
|
462
|
+
{/* Delete "button" (using a span styled as a button) */}
|
|
463
|
+
{/* `text-gray-400`: Sets default text color to light gray. */}
|
|
464
|
+
{/* `cursor-pointer`: Shows pointer cursor. */}
|
|
465
|
+
{/* `opacity-40`: Makes it semi-transparent by default. */}
|
|
466
|
+
{/* `transition-all duration-200`: Smoothly animates all changes (opacity, color). */}
|
|
467
|
+
{/* `text-base font-bold`: Sets text size and weight. */}
|
|
468
|
+
{/* `inline-flex items-center`: Needed for proper alignment if using an icon font/SVG. */}
|
|
469
|
+
{/* `pl-1`: Adds small left padding. */}
|
|
470
|
+
{/* `hover:opacity-100`: Makes it fully opaque on hover. */}
|
|
471
|
+
{/* `hover:text-red-500`: Changes text color to red on hover. */}
|
|
472
|
+
<span
|
|
473
|
+
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"
|
|
474
|
+
onClick={() => dispatch(actions.deleteTodoItem({ id: item.id }))} // Dispatch delete action on click.
|
|
475
|
+
>
|
|
476
|
+
× {/* Simple multiplication sign used as delete icon */}
|
|
477
|
+
</span>
|
|
478
|
+
</div>
|
|
479
|
+
)}
|
|
480
|
+
</li>
|
|
481
|
+
))}
|
|
482
|
+
</ul>
|
|
483
|
+
</div>
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
</details>
|
|
488
|
+
|
|
489
|
+
Now you can run the Connect app and see the **ToDoList** editor in action.
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
ph connect
|
|
493
|
+
```
|
|
179
494
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
* Use an `InputField` (or a `StringField` from the library) for text entry.
|
|
183
|
-
* On "Add" button click or "Enter" key press, `dispatch` an `addTodoItem` action with the current input value.
|
|
495
|
+
In Connect, in the bottom right corner you'll find a new Document Model that you can create: **ToDoList**.
|
|
496
|
+
Click on it to create a new ToDoList document.
|
|
184
497
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
* The `Checkbox` `value` should be bound to `item.checked`.
|
|
189
|
-
* Its `onChange` handler should `dispatch` an `updateTodoItem` action to toggle the `checked` status.
|
|
498
|
+
:::info
|
|
499
|
+
The editor will update dynamically, so you can play around with your editor styling while seeing your results appear in Connect Studio.
|
|
500
|
+
:::
|
|
190
501
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
* Conditionally render either the item text (display mode) or an input field (edit mode).
|
|
194
|
-
* When entering edit mode, populate `editedText` with the item's current text.
|
|
195
|
-
* On saving the edit (e.g., "Enter" key in the input), `dispatch` an `updateTodoItem` action with the new text.
|
|
502
|
+
Congratulations!
|
|
503
|
+
If you managed to follow this tutorial until this point, you have successfully implemented the **ToDoList** document model with its reducer operations and editor.
|
|
196
504
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
* Its `onClick` handler should `dispatch` a `deleteTodoItem` action with the item's `id`.
|
|
505
|
+
Now you can move on to creating a [custom drive explorer](/academy/MasteryTrack/BuildingUserExperiences/BuildingADriveExplorer) for your ToDoList document.
|
|
506
|
+
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!
|
|
200
507
|
|
|
201
|
-
|
|
508
|
+
</details>
|