@jsenv/navi 0.0.1 → 0.1.1
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/dist/jsenv_navi.js +22959 -0
- package/index.js +66 -16
- package/package.json +23 -11
- package/src/actions.js +50 -26
- package/src/browser_integration/browser_integration.js +31 -6
- package/src/browser_integration/via_history.js +42 -9
- package/src/components/action_execution/render_actionable_component.jsx +6 -4
- package/src/components/action_execution/use_action.js +51 -282
- package/src/components/action_execution/use_execute_action.js +106 -92
- package/src/components/action_execution/use_run_on_mount.js +9 -0
- package/src/components/action_renderer.jsx +21 -32
- package/src/components/demos/0_button_demo.html +574 -103
- package/src/components/demos/10_column_reordering_debug.html +277 -0
- package/src/components/demos/11_table_selection_debug.html +432 -0
- package/src/components/demos/1_checkbox_demo.html +579 -202
- package/src/components/demos/2_input_textual_demo.html +81 -138
- package/src/components/demos/3_radio_demo.html +0 -2
- package/src/components/demos/4_select_demo.html +19 -23
- package/src/components/demos/6_tablist_demo.html +77 -0
- package/src/components/demos/7_table_selection_demo.html +176 -0
- package/src/components/demos/8_table_fixed_headers_demo.html +584 -0
- package/src/components/demos/9_table_column_drag_demo.html +325 -0
- package/src/components/demos/action/0_button_demo.html +2 -4
- package/src/components/demos/action/1_input_text_demo.html +643 -222
- package/src/components/demos/action/3_details_demo.html +146 -115
- package/src/components/demos/action/4_input_checkbox_demo.html +442 -322
- package/src/components/demos/action/5_input_checkbox_state_demo.html +270 -0
- package/src/components/demos/action/6_checkbox_list_demo.html +304 -72
- package/src/components/demos/action/7_radio_list_demo.html +310 -170
- package/src/components/demos/action/{8_editable_text_demo.html → 8_editable_demo.html} +65 -76
- package/src/components/demos/action/9_link_demo.html +84 -62
- package/src/components/demos/ui_transition/0_action_renderer_ui_transition_demo.html +695 -0
- package/src/components/demos/ui_transition/1_nested_ui_transition_demo.html +429 -0
- package/src/components/demos/ui_transition/2_height_transition_test.html +295 -0
- package/src/components/details/details.jsx +62 -64
- package/src/components/edition/editable.jsx +186 -0
- package/src/components/field/README.md +247 -0
- package/src/components/{input → field}/button.jsx +151 -130
- package/src/components/field/checkbox_list.jsx +184 -0
- package/src/components/{collect_form_element_values.js → field/collect_form_element_values.js} +7 -4
- package/src/components/{input → field}/field_css.js +4 -1
- package/src/components/field/form.jsx +211 -0
- package/src/components/{input → field}/input.jsx +1 -0
- package/src/components/{input → field}/input_checkbox.jsx +132 -155
- package/src/components/{input → field}/input_radio.jsx +135 -46
- package/src/components/field/input_textual.jsx +418 -0
- package/src/components/field/label.jsx +32 -0
- package/src/components/field/radio_list.jsx +182 -0
- package/src/components/{input → field}/select.jsx +17 -32
- package/src/components/field/use_action_events.js +132 -0
- package/src/components/field/use_form_events.js +55 -0
- package/src/components/field/use_ui_state_controller.js +506 -0
- package/src/components/item_tracker/README.md +461 -0
- package/src/components/item_tracker/use_isolated_item_tracker.jsx +209 -0
- package/src/components/item_tracker/use_isolated_item_tracker_demo.html +148 -0
- package/src/components/item_tracker/use_isolated_item_tracker_demo.jsx +460 -0
- package/src/components/item_tracker/use_item_tracker.jsx +143 -0
- package/src/components/item_tracker/use_item_tracker_demo.html +207 -0
- package/src/components/item_tracker/use_item_tracker_demo.jsx +216 -0
- package/src/components/keyboard_shortcuts/active_keyboard_shortcuts.jsx +87 -0
- package/src/components/keyboard_shortcuts/aria_key_shortcuts.js +61 -0
- package/src/components/keyboard_shortcuts/keyboard_key_meta.js +17 -0
- package/src/components/keyboard_shortcuts/keyboard_shortcuts.js +371 -0
- package/src/components/link/link.jsx +65 -102
- package/src/components/link/link_with_icon.jsx +52 -0
- package/src/components/loader/loader_background.jsx +85 -64
- package/src/components/loader/rectangle_loading.jsx +38 -19
- package/src/components/route.jsx +8 -4
- package/src/components/selection/selection.jsx +1583 -0
- package/src/components/svg/font_sized_svg.jsx +45 -0
- package/src/components/svg/icon_and_text.jsx +21 -0
- package/src/components/svg/svg_mask_overlay.jsx +105 -0
- package/src/components/table/drag/table_drag.jsx +506 -0
- package/src/components/table/resize/table_resize.jsx +650 -0
- package/src/components/table/resize/table_size.js +43 -0
- package/src/components/table/selection/table_selection.js +106 -0
- package/src/components/table/selection/table_selection.jsx +203 -0
- package/src/components/table/sticky/sticky_group.js +354 -0
- package/src/components/table/sticky/table_sticky.js +25 -0
- package/src/components/table/sticky/table_sticky.jsx +501 -0
- package/src/components/table/table.jsx +721 -0
- package/src/components/table/table_css.js +211 -0
- package/src/components/table/table_ui.jsx +49 -0
- package/src/components/table/use_cells_and_columns.js +90 -0
- package/src/components/table/use_object_array_to_cells.js +46 -0
- package/src/components/table/z_indexes.js +23 -0
- package/src/components/tablist/tablist.jsx +99 -0
- package/src/components/text/overflow.jsx +15 -0
- package/src/components/text/text_and_count.jsx +28 -0
- package/src/components/ui_transition.jsx +128 -0
- package/src/components/use_auto_focus.js +58 -7
- package/src/components/use_batch_during_render.js +33 -0
- package/src/components/use_debounce_true.js +7 -7
- package/src/components/use_dependencies_diff.js +35 -0
- package/src/components/use_focus_group.js +4 -3
- package/src/components/use_initial_value.js +8 -34
- package/src/components/use_signal_sync.js +1 -1
- package/src/components/use_stable_callback.js +68 -0
- package/src/components/use_state_array.js +16 -9
- package/src/docs/actions.md +22 -0
- package/src/notes.md +33 -12
- package/src/route/route.js +97 -47
- package/src/store/resource_graph.js +2 -1
- package/src/store/tests/{resource_graph_dependencies.test.js → resource_graph_dependencies.test_manual.js} +13 -13
- package/src/utils/is_signal.js +20 -0
- package/src/utils/stringify_for_display.js +4 -23
- package/src/validation/constraints/confirm_constraint.js +14 -0
- package/src/validation/constraints/create_unique_value_constraint.js +27 -0
- package/src/validation/constraints/native_constraints.js +313 -0
- package/src/validation/constraints/readonly_constraint.js +36 -0
- package/src/validation/constraints/single_space_constraint.js +13 -0
- package/src/validation/custom_constraint_validation.js +599 -0
- package/src/validation/custom_message.js +18 -0
- package/src/validation/demos/browser_style.png +0 -0
- package/src/validation/demos/form_validation_demo.html +142 -0
- package/src/validation/demos/form_validation_demo_preact.html +87 -0
- package/src/validation/demos/form_validation_native_popover_demo.html +168 -0
- package/src/validation/demos/form_validation_vs_native_demo.html +172 -0
- package/src/validation/demos/validation_message_demo.html +203 -0
- package/src/validation/hooks/use_constraints.js +23 -0
- package/src/validation/hooks/use_custom_validation_ref.js +73 -0
- package/src/validation/hooks/use_validation_message.js +19 -0
- package/src/validation/validation_message.js +741 -0
- package/src/components/editable_text/editable_text.jsx +0 -96
- package/src/components/form.jsx +0 -144
- package/src/components/input/checkbox_list.jsx +0 -294
- package/src/components/input/field.jsx +0 -61
- package/src/components/input/input_textual.jsx +0 -338
- package/src/components/input/radio_list.jsx +0 -283
- package/src/components/input/use_form_event.js +0 -20
- package/src/components/input/use_on_change.js +0 -12
- package/src/components/selection/selection.js +0 -5
- package/src/components/selection/selection_context.jsx +0 -262
- package/src/components/shortcut/shortcut_context.jsx +0 -390
- package/src/components/use_action_events.js +0 -37
- package/src/utils/iterable_weak_set.js +0 -62
- /package/src/components/demos/action/{11_nested_shortcuts_demo.html → 11_nested_shortcuts_demo.xhtml} +0 -0
- /package/src/components/{shortcut → keyboard_shortcuts}/os.js +0 -0
- /package/src/route/{route.test.html → route.xtest.html} +0 -0
|
@@ -61,6 +61,24 @@
|
|
|
61
61
|
border-color: #bee5eb;
|
|
62
62
|
color: #0c5460;
|
|
63
63
|
}
|
|
64
|
+
nav a {
|
|
65
|
+
color: #007bff;
|
|
66
|
+
text-decoration: none;
|
|
67
|
+
}
|
|
68
|
+
nav a:hover {
|
|
69
|
+
text-decoration: underline;
|
|
70
|
+
}
|
|
71
|
+
h2 {
|
|
72
|
+
margin-top: 40px;
|
|
73
|
+
margin-bottom: 20px;
|
|
74
|
+
padding-top: 20px;
|
|
75
|
+
border-top: 2px solid #e0e0e0;
|
|
76
|
+
}
|
|
77
|
+
h2:first-of-type {
|
|
78
|
+
margin-top: 0;
|
|
79
|
+
border-top: none;
|
|
80
|
+
padding-top: 0;
|
|
81
|
+
}
|
|
64
82
|
.button-group {
|
|
65
83
|
display: flex;
|
|
66
84
|
gap: 10px;
|
|
@@ -77,23 +95,17 @@
|
|
|
77
95
|
button:hover {
|
|
78
96
|
background: #f8f9fa;
|
|
79
97
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
color: white;
|
|
83
|
-
border-color: #007bff;
|
|
84
|
-
}
|
|
85
|
-
button[type="submit"]:hover {
|
|
86
|
-
background: #0056b3;
|
|
87
|
-
}
|
|
88
|
-
button[type="reset"] {
|
|
89
|
-
background: #6c757d;
|
|
90
|
-
color: white;
|
|
91
|
-
border-color: #6c757d;
|
|
98
|
+
.control-group {
|
|
99
|
+
margin-bottom: 15px;
|
|
92
100
|
}
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
label {
|
|
102
|
+
margin-bottom: 15px;
|
|
103
|
+
font-weight: 500;
|
|
104
|
+
color: #555;
|
|
95
105
|
}
|
|
96
|
-
input {
|
|
106
|
+
label input {
|
|
107
|
+
display: block;
|
|
108
|
+
margin-top: 5px;
|
|
97
109
|
width: 100%;
|
|
98
110
|
padding: 8px 12px;
|
|
99
111
|
border: 1px solid #ccc;
|
|
@@ -101,15 +113,6 @@
|
|
|
101
113
|
font-size: 14px;
|
|
102
114
|
box-sizing: border-box;
|
|
103
115
|
}
|
|
104
|
-
.control-group {
|
|
105
|
-
margin-bottom: 15px;
|
|
106
|
-
}
|
|
107
|
-
label {
|
|
108
|
-
display: block;
|
|
109
|
-
margin-bottom: 5px;
|
|
110
|
-
font-weight: 500;
|
|
111
|
-
color: #555;
|
|
112
|
-
}
|
|
113
116
|
</style>
|
|
114
117
|
</head>
|
|
115
118
|
<body>
|
|
@@ -119,8 +122,14 @@
|
|
|
119
122
|
<script type="module" jsenv-type="module/jsx">
|
|
120
123
|
import { render } from "preact";
|
|
121
124
|
import { useState } from "preact/hooks";
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
import {
|
|
126
|
+
// eslint-disable-next-line no-unused-vars
|
|
127
|
+
Input,
|
|
128
|
+
// eslint-disable-next-line no-unused-vars
|
|
129
|
+
Form,
|
|
130
|
+
// eslint-disable-next-line no-unused-vars
|
|
131
|
+
Button,
|
|
132
|
+
} from "@jsenv/navi";
|
|
124
133
|
|
|
125
134
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
126
135
|
|
|
@@ -128,18 +137,501 @@
|
|
|
128
137
|
const App = () => {
|
|
129
138
|
return (
|
|
130
139
|
<>
|
|
140
|
+
<div className="demo-card" style={{ marginBottom: "30px" }}>
|
|
141
|
+
<h2>Input Text Demo - Overview</h2>
|
|
142
|
+
<p>
|
|
143
|
+
This demo showcases different ways to use the Input component
|
|
144
|
+
with text inputs.
|
|
145
|
+
</p>
|
|
146
|
+
<nav>
|
|
147
|
+
<h3>Sections:</h3>
|
|
148
|
+
<ul style={{ marginLeft: "20px" }}>
|
|
149
|
+
<li>
|
|
150
|
+
<a href="#basic-inputs">
|
|
151
|
+
1. Basic Input (no action/no form)
|
|
152
|
+
</a>{" "}
|
|
153
|
+
- Simple inputs with value handling
|
|
154
|
+
</li>
|
|
155
|
+
<li>
|
|
156
|
+
<a href="#input-actions">2. Input with Actions</a> - Inputs
|
|
157
|
+
that trigger actions on value change, including error
|
|
158
|
+
handling
|
|
159
|
+
</li>
|
|
160
|
+
<li>
|
|
161
|
+
<a href="#input-forms">3. Input inside Form</a> - Form
|
|
162
|
+
integration patterns
|
|
163
|
+
</li>
|
|
164
|
+
<li>
|
|
165
|
+
<a href="#datetime-local">4. Datetime-Local Inputs</a> -
|
|
166
|
+
Special handling for datetime inputs with timezone
|
|
167
|
+
conversion
|
|
168
|
+
</li>
|
|
169
|
+
</ul>
|
|
170
|
+
</nav>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<h2 id="basic-inputs">1. Basic Input (no action/no form)</h2>
|
|
174
|
+
<div className="demo-grid">
|
|
175
|
+
<BasicInputNoActionDemo />
|
|
176
|
+
<InputWithDefaultValueDemo />
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<h2 id="input-actions">2. Input with Actions</h2>
|
|
131
180
|
<div className="demo-grid">
|
|
132
181
|
<BasicInputDemo />
|
|
133
182
|
<InputWithInitialValueDemo />
|
|
134
183
|
<InputWithExternalControlDemo />
|
|
184
|
+
<InputWithErrorDemo />
|
|
185
|
+
<InputWithValidationErrorDemo />
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<h2 id="input-forms">3. Input inside Form</h2>
|
|
189
|
+
<div className="demo-grid">
|
|
135
190
|
<FormInputDemo />
|
|
136
|
-
<
|
|
137
|
-
|
|
191
|
+
<FormInputErrorDemo />
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<h2 id="datetime-local">4. Datetime-Local Inputs</h2>
|
|
195
|
+
<div className="demo-grid">
|
|
196
|
+
<DateTimeLocalDemo />
|
|
197
|
+
<DateTimeLocalFormDemo />
|
|
138
198
|
</div>
|
|
139
199
|
</>
|
|
140
200
|
);
|
|
141
201
|
};
|
|
142
202
|
|
|
203
|
+
// eslint-disable-next-line no-unused-vars
|
|
204
|
+
const DateTimeLocalDemo = () => {
|
|
205
|
+
// External state (what the server/app knows) - stored as UTC ISO string
|
|
206
|
+
const [externalDateTime, setExternalDateTime] = useState(
|
|
207
|
+
"2024-01-15T14:30:00Z", // UTC time
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// UI state tracking
|
|
211
|
+
const [uiDateTime, setUiDateTime] = useState(externalDateTime);
|
|
212
|
+
const [actionValue, setActionValue] = useState("");
|
|
213
|
+
const [result, setResult] = useState("");
|
|
214
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
215
|
+
|
|
216
|
+
const saveDateTime = async (dateTimeValue) => {
|
|
217
|
+
setIsLoading(true);
|
|
218
|
+
setActionValue(dateTimeValue || "(empty)");
|
|
219
|
+
setResult("Saving datetime...");
|
|
220
|
+
try {
|
|
221
|
+
await delay(1000);
|
|
222
|
+
|
|
223
|
+
// Simulate server response
|
|
224
|
+
const response = {
|
|
225
|
+
saved: dateTimeValue,
|
|
226
|
+
message: `Datetime saved successfully`,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Update external state to match what was saved
|
|
230
|
+
setExternalDateTime(dateTimeValue);
|
|
231
|
+
setResult(`✅ ${response.message}\\nSaved: ${dateTimeValue}`);
|
|
232
|
+
return response;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
setResult(`❌ Save failed: ${error.message}`);
|
|
235
|
+
throw error;
|
|
236
|
+
} finally {
|
|
237
|
+
setIsLoading(false);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<div className="demo-card">
|
|
243
|
+
<h3 className="demo-title">Datetime-Local State Demo</h3>
|
|
244
|
+
|
|
245
|
+
{/* External State Control */}
|
|
246
|
+
<div
|
|
247
|
+
style={{
|
|
248
|
+
marginBottom: "20px",
|
|
249
|
+
padding: "12px",
|
|
250
|
+
background: "#e3f2fd",
|
|
251
|
+
borderRadius: "4px",
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
<h4
|
|
255
|
+
style={{
|
|
256
|
+
margin: "0 0 10px 0",
|
|
257
|
+
fontSize: "14px",
|
|
258
|
+
color: "#1976d2",
|
|
259
|
+
}}
|
|
260
|
+
>
|
|
261
|
+
📊 External State (UTC)
|
|
262
|
+
</h4>
|
|
263
|
+
<div
|
|
264
|
+
style={{
|
|
265
|
+
fontFamily: "monospace",
|
|
266
|
+
fontSize: "12px",
|
|
267
|
+
marginBottom: "10px",
|
|
268
|
+
}}
|
|
269
|
+
>
|
|
270
|
+
Current: {externalDateTime || "(not set)"}
|
|
271
|
+
</div>
|
|
272
|
+
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
|
|
273
|
+
<button
|
|
274
|
+
onClick={() => setExternalDateTime("2024-01-15T14:30:00Z")}
|
|
275
|
+
style={{ padding: "4px 8px", fontSize: "12px" }}
|
|
276
|
+
>
|
|
277
|
+
Set Jan 15, 2:30 PM UTC
|
|
278
|
+
</button>
|
|
279
|
+
<button
|
|
280
|
+
onClick={() => setExternalDateTime("2024-12-25T09:00:00Z")}
|
|
281
|
+
style={{ padding: "4px 8px", fontSize: "12px" }}
|
|
282
|
+
>
|
|
283
|
+
Set Dec 25, 9:00 AM UTC
|
|
284
|
+
</button>
|
|
285
|
+
<button
|
|
286
|
+
onClick={() => setExternalDateTime(null)}
|
|
287
|
+
style={{ padding: "4px 8px", fontSize: "12px" }}
|
|
288
|
+
>
|
|
289
|
+
Clear
|
|
290
|
+
</button>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
{/* UI Input */}
|
|
295
|
+
<div className="control-group">
|
|
296
|
+
<label>
|
|
297
|
+
Datetime Input (Local Time):
|
|
298
|
+
<Input
|
|
299
|
+
type="datetime-local"
|
|
300
|
+
name="datetime"
|
|
301
|
+
value={externalDateTime} // External state controls the input
|
|
302
|
+
onUIStateChange={(datetime) => {
|
|
303
|
+
setUiDateTime(datetime);
|
|
304
|
+
}}
|
|
305
|
+
action={saveDateTime}
|
|
306
|
+
/>
|
|
307
|
+
</label>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
{/* State Display */}
|
|
311
|
+
<div style={{ marginBottom: "15px" }}>
|
|
312
|
+
<div
|
|
313
|
+
style={{ fontSize: "12px", color: "#666", marginBottom: "8px" }}
|
|
314
|
+
>
|
|
315
|
+
<strong>UI State:</strong> {uiDateTime || "(empty)"}
|
|
316
|
+
</div>
|
|
317
|
+
<div
|
|
318
|
+
style={{ fontSize: "12px", color: "#666", marginBottom: "8px" }}
|
|
319
|
+
>
|
|
320
|
+
<strong>Action receives:</strong>{" "}
|
|
321
|
+
{actionValue || "(waiting for action...)"}
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<div
|
|
326
|
+
className={`result-display ${
|
|
327
|
+
isLoading
|
|
328
|
+
? "result-loading"
|
|
329
|
+
: result.startsWith("✅")
|
|
330
|
+
? "result-success"
|
|
331
|
+
: result.startsWith("❌")
|
|
332
|
+
? "result-error"
|
|
333
|
+
: ""
|
|
334
|
+
}`}
|
|
335
|
+
>
|
|
336
|
+
{result ||
|
|
337
|
+
"Change the datetime and blur/enter to trigger action..."}
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
);
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// eslint-disable-next-line no-unused-vars
|
|
344
|
+
const DateTimeLocalFormDemo = () => {
|
|
345
|
+
const [externalDateTime, setExternalDateTime] = useState(
|
|
346
|
+
"2024-06-01T10:00:00Z",
|
|
347
|
+
);
|
|
348
|
+
const [uiDateTime, setUiDateTime] = useState(externalDateTime);
|
|
349
|
+
const [result, setResult] = useState("");
|
|
350
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
351
|
+
|
|
352
|
+
const submitForm = async (formData) => {
|
|
353
|
+
setIsLoading(true);
|
|
354
|
+
setResult("Submitting form...");
|
|
355
|
+
try {
|
|
356
|
+
await delay(1200);
|
|
357
|
+
|
|
358
|
+
const response = {
|
|
359
|
+
datetime: formData.appointmentTime,
|
|
360
|
+
message: "Appointment scheduled successfully",
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// Update external state
|
|
364
|
+
setExternalDateTime(formData.appointmentTime);
|
|
365
|
+
setResult(
|
|
366
|
+
`✅ ${response.message}\\nScheduled for: ${formData.appointmentTime}`,
|
|
367
|
+
);
|
|
368
|
+
return response;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
setResult(`❌ Form error: ${error.message}`);
|
|
371
|
+
throw error;
|
|
372
|
+
} finally {
|
|
373
|
+
setIsLoading(false);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const getResetValue = () => {
|
|
378
|
+
return externalDateTime || "(not set)";
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<div className="demo-card">
|
|
383
|
+
<h3 className="demo-title">Datetime-Local in Form</h3>
|
|
384
|
+
|
|
385
|
+
{/* External State Control */}
|
|
386
|
+
<div
|
|
387
|
+
style={{
|
|
388
|
+
marginBottom: "20px",
|
|
389
|
+
padding: "12px",
|
|
390
|
+
background: "#e3f2fd",
|
|
391
|
+
borderRadius: "4px",
|
|
392
|
+
}}
|
|
393
|
+
>
|
|
394
|
+
<h4
|
|
395
|
+
style={{
|
|
396
|
+
margin: "0 0 10px 0",
|
|
397
|
+
fontSize: "14px",
|
|
398
|
+
color: "#1976d2",
|
|
399
|
+
}}
|
|
400
|
+
>
|
|
401
|
+
📊 External State Control (UTC)
|
|
402
|
+
</h4>
|
|
403
|
+
<div
|
|
404
|
+
style={{
|
|
405
|
+
fontFamily: "monospace",
|
|
406
|
+
fontSize: "12px",
|
|
407
|
+
marginBottom: "10px",
|
|
408
|
+
}}
|
|
409
|
+
>
|
|
410
|
+
Current: {externalDateTime || "(not set)"}
|
|
411
|
+
</div>
|
|
412
|
+
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
|
|
413
|
+
<button
|
|
414
|
+
onClick={() => setExternalDateTime("2024-03-15T09:00:00Z")}
|
|
415
|
+
style={{ padding: "4px 8px", fontSize: "12px" }}
|
|
416
|
+
>
|
|
417
|
+
Set Mar 15, 9:00 AM UTC
|
|
418
|
+
</button>
|
|
419
|
+
<button
|
|
420
|
+
onClick={() => setExternalDateTime("2024-08-20T16:30:00Z")}
|
|
421
|
+
style={{ padding: "4px 8px", fontSize: "12px" }}
|
|
422
|
+
>
|
|
423
|
+
Set Aug 20, 4:30 PM UTC
|
|
424
|
+
</button>
|
|
425
|
+
<button
|
|
426
|
+
onClick={() => setExternalDateTime("2024-12-31T23:59:00Z")}
|
|
427
|
+
style={{ padding: "4px 8px", fontSize: "12px" }}
|
|
428
|
+
>
|
|
429
|
+
Set New Year's Eve
|
|
430
|
+
</button>
|
|
431
|
+
<button
|
|
432
|
+
onClick={() => setExternalDateTime(null)}
|
|
433
|
+
style={{ padding: "4px 8px", fontSize: "12px" }}
|
|
434
|
+
>
|
|
435
|
+
Clear
|
|
436
|
+
</button>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
|
|
440
|
+
<Form action={submitForm}>
|
|
441
|
+
<div className="control-group">
|
|
442
|
+
<label>
|
|
443
|
+
Appointment Time:
|
|
444
|
+
<Input
|
|
445
|
+
type="datetime-local"
|
|
446
|
+
name="appointmentTime"
|
|
447
|
+
value={externalDateTime}
|
|
448
|
+
onUIStateChange={setUiDateTime}
|
|
449
|
+
/>
|
|
450
|
+
</label>
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
<div
|
|
454
|
+
style={{
|
|
455
|
+
fontSize: "12px",
|
|
456
|
+
color: "#666",
|
|
457
|
+
marginBottom: "10px",
|
|
458
|
+
}}
|
|
459
|
+
>
|
|
460
|
+
<strong>UI State:</strong> {uiDateTime || "(empty)"}
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
<div className="button-group">
|
|
464
|
+
<Button type="submit">
|
|
465
|
+
Schedule Appointment
|
|
466
|
+
{uiDateTime && (
|
|
467
|
+
<span style={{ fontSize: "11px", opacity: "0.7" }}>
|
|
468
|
+
{" "}
|
|
469
|
+
({uiDateTime})
|
|
470
|
+
</span>
|
|
471
|
+
)}
|
|
472
|
+
</Button>
|
|
473
|
+
<Button type="reset">
|
|
474
|
+
Reset to Saved
|
|
475
|
+
<span style={{ fontSize: "11px", opacity: "0.7" }}>
|
|
476
|
+
{" "}
|
|
477
|
+
({getResetValue()})
|
|
478
|
+
</span>
|
|
479
|
+
</Button>
|
|
480
|
+
</div>
|
|
481
|
+
</Form>
|
|
482
|
+
|
|
483
|
+
<div
|
|
484
|
+
className={`result-display ${
|
|
485
|
+
isLoading
|
|
486
|
+
? "result-loading"
|
|
487
|
+
: result.startsWith("✅")
|
|
488
|
+
? "result-success"
|
|
489
|
+
: result.startsWith("❌")
|
|
490
|
+
? "result-error"
|
|
491
|
+
: ""
|
|
492
|
+
}`}
|
|
493
|
+
>
|
|
494
|
+
{result || "Select a datetime and submit..."}
|
|
495
|
+
</div>
|
|
496
|
+
</div>
|
|
497
|
+
);
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
// eslint-disable-next-line no-unused-vars
|
|
501
|
+
const BasicInputNoActionDemo = () => {
|
|
502
|
+
const [result, setResult] = useState("");
|
|
503
|
+
|
|
504
|
+
return (
|
|
505
|
+
<div className="demo-card">
|
|
506
|
+
<h3 className="demo-title">Basic Input (No Action)</h3>
|
|
507
|
+
<div className="control-group">
|
|
508
|
+
<label>
|
|
509
|
+
Enter some text:
|
|
510
|
+
<Input
|
|
511
|
+
name="text"
|
|
512
|
+
placeholder="Type something..."
|
|
513
|
+
onUIStateChange={(text) => {
|
|
514
|
+
setResult(text ? `Current value: "${text}"` : "");
|
|
515
|
+
}}
|
|
516
|
+
/>
|
|
517
|
+
</label>
|
|
518
|
+
</div>
|
|
519
|
+
<div className="result-display">
|
|
520
|
+
{result || "Type in the input to see the value..."}
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
);
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// eslint-disable-next-line no-unused-vars
|
|
527
|
+
const InputWithDefaultValueDemo = () => {
|
|
528
|
+
return (
|
|
529
|
+
<div className="demo-card">
|
|
530
|
+
<h3 className="demo-title">Input with Default Value</h3>
|
|
531
|
+
<div className="control-group">
|
|
532
|
+
<label>
|
|
533
|
+
Input with default value:
|
|
534
|
+
<Input
|
|
535
|
+
name="text"
|
|
536
|
+
defaultValue="Hello World!"
|
|
537
|
+
placeholder="Enter text..."
|
|
538
|
+
/>
|
|
539
|
+
</label>
|
|
540
|
+
</div>
|
|
541
|
+
<div className="result-display">
|
|
542
|
+
Input starts with default value.
|
|
543
|
+
</div>
|
|
544
|
+
</div>
|
|
545
|
+
);
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
// eslint-disable-next-line no-unused-vars
|
|
549
|
+
const InputWithErrorDemo = () => {
|
|
550
|
+
const [result, setResult] = useState("");
|
|
551
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
552
|
+
|
|
553
|
+
return (
|
|
554
|
+
<div className="demo-card">
|
|
555
|
+
<h3 className="demo-title">Input with Action (Always Fails)</h3>
|
|
556
|
+
<div className="control-group">
|
|
557
|
+
<label>
|
|
558
|
+
Enter text (will always fail):
|
|
559
|
+
<Input
|
|
560
|
+
name="text"
|
|
561
|
+
placeholder="Type something..."
|
|
562
|
+
action={async (text) => {
|
|
563
|
+
setIsLoading(true);
|
|
564
|
+
setResult("Processing...");
|
|
565
|
+
try {
|
|
566
|
+
await delay(1200);
|
|
567
|
+
throw new Error(`Processing failed for input: "${text}"`);
|
|
568
|
+
} catch (error) {
|
|
569
|
+
setResult(`❌ ${error.message}`);
|
|
570
|
+
throw error;
|
|
571
|
+
} finally {
|
|
572
|
+
setIsLoading(false);
|
|
573
|
+
}
|
|
574
|
+
}}
|
|
575
|
+
/>
|
|
576
|
+
</label>
|
|
577
|
+
</div>
|
|
578
|
+
<div
|
|
579
|
+
className={`result-display ${isLoading ? "result-loading" : result.startsWith("❌") ? "result-error" : ""}`}
|
|
580
|
+
>
|
|
581
|
+
{result || "This input action will always fail..."}
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
);
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// eslint-disable-next-line no-unused-vars
|
|
588
|
+
const InputWithValidationErrorDemo = () => {
|
|
589
|
+
const [result, setResult] = useState("");
|
|
590
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
591
|
+
|
|
592
|
+
return (
|
|
593
|
+
<div className="demo-card">
|
|
594
|
+
<h3 className="demo-title">
|
|
595
|
+
Input with Validation (Fails on Short Text)
|
|
596
|
+
</h3>
|
|
597
|
+
<div className="control-group">
|
|
598
|
+
<label>
|
|
599
|
+
Enter text (min 5 characters):
|
|
600
|
+
<Input
|
|
601
|
+
name="text"
|
|
602
|
+
placeholder="Must be at least 5 characters..."
|
|
603
|
+
action={async (text) => {
|
|
604
|
+
setIsLoading(true);
|
|
605
|
+
setResult("Validating...");
|
|
606
|
+
try {
|
|
607
|
+
await delay(800);
|
|
608
|
+
if (!text || text.length < 5) {
|
|
609
|
+
throw new Error(
|
|
610
|
+
"Text must be at least 5 characters long",
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
const response = `✅ Valid input: "${text}" (${text.length} characters)`;
|
|
614
|
+
setResult(response);
|
|
615
|
+
return response;
|
|
616
|
+
} catch (error) {
|
|
617
|
+
setResult(`❌ Validation failed: ${error.message}`);
|
|
618
|
+
throw error;
|
|
619
|
+
} finally {
|
|
620
|
+
setIsLoading(false);
|
|
621
|
+
}
|
|
622
|
+
}}
|
|
623
|
+
/>
|
|
624
|
+
</label>
|
|
625
|
+
</div>
|
|
626
|
+
<div
|
|
627
|
+
className={`result-display ${isLoading ? "result-loading" : result.startsWith("✅") ? "result-success" : result.startsWith("❌") ? "result-error" : ""}`}
|
|
628
|
+
>
|
|
629
|
+
{result || "Enter at least 5 characters to pass validation..."}
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
632
|
+
);
|
|
633
|
+
};
|
|
634
|
+
|
|
143
635
|
// eslint-disable-next-line no-unused-vars
|
|
144
636
|
const BasicInputDemo = () => {
|
|
145
637
|
const [result, setResult] = useState("");
|
|
@@ -149,27 +641,28 @@
|
|
|
149
641
|
<div className="demo-card">
|
|
150
642
|
<h3 className="demo-title">Basic Input with Action</h3>
|
|
151
643
|
<div className="control-group">
|
|
152
|
-
<label
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
644
|
+
<label>
|
|
645
|
+
Enter some text:
|
|
646
|
+
<Input
|
|
647
|
+
name="text"
|
|
648
|
+
placeholder="Type something..."
|
|
649
|
+
action={async (text) => {
|
|
650
|
+
setIsLoading(true);
|
|
651
|
+
setResult("Processing...");
|
|
652
|
+
try {
|
|
653
|
+
await delay(1000);
|
|
654
|
+
const response = `✅ Processed: "${text}"`;
|
|
655
|
+
setResult(response);
|
|
656
|
+
return response;
|
|
657
|
+
} catch (error) {
|
|
658
|
+
setResult(`❌ Error: ${error.message}`);
|
|
659
|
+
throw error;
|
|
660
|
+
} finally {
|
|
661
|
+
setIsLoading(false);
|
|
662
|
+
}
|
|
663
|
+
}}
|
|
664
|
+
/>
|
|
665
|
+
</label>
|
|
173
666
|
</div>
|
|
174
667
|
<div
|
|
175
668
|
className={`result-display ${isLoading ? "result-loading" : result.startsWith("✅") ? "result-success" : result.startsWith("❌") ? "result-error" : ""}`}
|
|
@@ -182,25 +675,28 @@
|
|
|
182
675
|
|
|
183
676
|
// eslint-disable-next-line no-unused-vars
|
|
184
677
|
const InputWithInitialValueDemo = () => {
|
|
678
|
+
const [value, setValue] = useState("Hello World!");
|
|
185
679
|
const [result, setResult] = useState("");
|
|
186
680
|
|
|
187
681
|
return (
|
|
188
682
|
<div className="demo-card">
|
|
189
683
|
<h3 className="demo-title">Input with Initial Value</h3>
|
|
190
684
|
<div className="control-group">
|
|
191
|
-
<label
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
685
|
+
<label>
|
|
686
|
+
Input with preset value:
|
|
687
|
+
<Input
|
|
688
|
+
name="text"
|
|
689
|
+
value={value}
|
|
690
|
+
onUIStateChange={setValue}
|
|
691
|
+
action={async (text) => {
|
|
692
|
+
setResult("Processing...");
|
|
693
|
+
await delay(800);
|
|
694
|
+
const response = `✅ Submitted: "${text}"`;
|
|
695
|
+
setResult(response);
|
|
696
|
+
return response;
|
|
697
|
+
}}
|
|
698
|
+
/>
|
|
699
|
+
</label>
|
|
204
700
|
</div>
|
|
205
701
|
<div
|
|
206
702
|
className={`result-display ${result.startsWith("✅") ? "result-success" : ""}`}
|
|
@@ -213,48 +709,48 @@
|
|
|
213
709
|
|
|
214
710
|
// eslint-disable-next-line no-unused-vars
|
|
215
711
|
const InputWithExternalControlDemo = () => {
|
|
216
|
-
const [
|
|
712
|
+
const [value, setValue] = useState(undefined);
|
|
217
713
|
const [result, setResult] = useState("");
|
|
218
714
|
|
|
219
715
|
return (
|
|
220
716
|
<div className="demo-card">
|
|
221
717
|
<h3 className="demo-title">Input with External Control</h3>
|
|
222
718
|
<div className="control-group">
|
|
223
|
-
<label
|
|
719
|
+
<label>
|
|
224
720
|
Externally controlled input:
|
|
721
|
+
<Input
|
|
722
|
+
name="text"
|
|
723
|
+
value={value}
|
|
724
|
+
placeholder="Use buttons below to set value"
|
|
725
|
+
action={async (text) => {
|
|
726
|
+
setResult("Processing...");
|
|
727
|
+
await delay(600);
|
|
728
|
+
const response = `✅ Action executed with: "${text}"`;
|
|
729
|
+
setResult(response);
|
|
730
|
+
setValue(text);
|
|
731
|
+
return response;
|
|
732
|
+
}}
|
|
733
|
+
/>
|
|
225
734
|
</label>
|
|
226
|
-
<Input
|
|
227
|
-
id="controlled-input"
|
|
228
|
-
name="text"
|
|
229
|
-
value={inputValue}
|
|
230
|
-
placeholder="Use buttons below to set value"
|
|
231
|
-
action={async ({ text }) => {
|
|
232
|
-
setResult("Processing...");
|
|
233
|
-
await delay(600);
|
|
234
|
-
const response = `✅ Action executed with: "${text}"`;
|
|
235
|
-
setResult(response);
|
|
236
|
-
return response;
|
|
237
|
-
}}
|
|
238
|
-
/>
|
|
239
735
|
</div>
|
|
240
736
|
<div className="button-group">
|
|
241
737
|
<button
|
|
242
738
|
onClick={() => {
|
|
243
|
-
|
|
739
|
+
setValue("Preset Value 1");
|
|
244
740
|
}}
|
|
245
741
|
>
|
|
246
742
|
Set Value 1
|
|
247
743
|
</button>
|
|
248
744
|
<button
|
|
249
745
|
onClick={() => {
|
|
250
|
-
|
|
746
|
+
setValue("Preset Value 2");
|
|
251
747
|
}}
|
|
252
748
|
>
|
|
253
749
|
Set Value 2
|
|
254
750
|
</button>
|
|
255
751
|
<button
|
|
256
752
|
onClick={() => {
|
|
257
|
-
|
|
753
|
+
setValue("");
|
|
258
754
|
}}
|
|
259
755
|
>
|
|
260
756
|
Clear
|
|
@@ -263,7 +759,7 @@
|
|
|
263
759
|
<div
|
|
264
760
|
className={`result-display ${result.startsWith("✅") ? "result-success" : ""}`}
|
|
265
761
|
>
|
|
266
|
-
Current value: "{
|
|
762
|
+
Current value: "{value}"<br />
|
|
267
763
|
{result || "Click buttons to change value, then submit..."}
|
|
268
764
|
</div>
|
|
269
765
|
</div>
|
|
@@ -275,43 +771,33 @@
|
|
|
275
771
|
const [result, setResult] = useState("");
|
|
276
772
|
const [isLoading, setIsLoading] = useState(false);
|
|
277
773
|
|
|
774
|
+
const formDemoAction = async ({ username, email }) => {
|
|
775
|
+
setIsLoading(true);
|
|
776
|
+
setResult("Submitting form...");
|
|
777
|
+
try {
|
|
778
|
+
await delay(1200);
|
|
779
|
+
const response = `✅ Form submitted!\nUsername: ${username}\nEmail: ${email}`;
|
|
780
|
+
setResult(response);
|
|
781
|
+
return response;
|
|
782
|
+
} catch (error) {
|
|
783
|
+
setResult(`❌ Form error: ${error.message}`);
|
|
784
|
+
throw error;
|
|
785
|
+
} finally {
|
|
786
|
+
setIsLoading(false);
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
|
|
278
790
|
return (
|
|
279
791
|
<div className="demo-card">
|
|
280
792
|
<h3 className="demo-title">Input inside Form</h3>
|
|
281
|
-
<Form
|
|
282
|
-
action={async ({ username, email }) => {
|
|
283
|
-
setIsLoading(true);
|
|
284
|
-
setResult("Submitting form...");
|
|
285
|
-
try {
|
|
286
|
-
await delay(1200);
|
|
287
|
-
const response = `✅ Form submitted!\nUsername: ${username}\nEmail: ${email}`;
|
|
288
|
-
setResult(response);
|
|
289
|
-
return response;
|
|
290
|
-
} catch (error) {
|
|
291
|
-
setResult(`❌ Form error: ${error.message}`);
|
|
292
|
-
throw error;
|
|
293
|
-
} finally {
|
|
294
|
-
setIsLoading(false);
|
|
295
|
-
}
|
|
296
|
-
}}
|
|
297
|
-
>
|
|
793
|
+
<Form action={formDemoAction}>
|
|
298
794
|
<div className="control-group">
|
|
299
|
-
<label
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
placeholder="Enter username"
|
|
304
|
-
/>
|
|
305
|
-
</div>
|
|
306
|
-
<div className="control-group">
|
|
307
|
-
<label htmlFor="form-email">Email:</label>
|
|
308
|
-
<Input
|
|
309
|
-
id="form-email"
|
|
310
|
-
name="email"
|
|
311
|
-
type="email"
|
|
312
|
-
placeholder="Enter email"
|
|
313
|
-
/>
|
|
795
|
+
<label>
|
|
796
|
+
Username:
|
|
797
|
+
<Input name="username" placeholder="Enter username" />
|
|
798
|
+
</label>
|
|
314
799
|
</div>
|
|
800
|
+
<div className="control-group"></div>
|
|
315
801
|
<div className="button-group">
|
|
316
802
|
<Button type="submit">Submit Form</Button>
|
|
317
803
|
<Button type="reset">Reset</Button>
|
|
@@ -327,69 +813,54 @@
|
|
|
327
813
|
};
|
|
328
814
|
|
|
329
815
|
// eslint-disable-next-line no-unused-vars
|
|
330
|
-
const
|
|
331
|
-
const [username, setUsername] = useState(undefined);
|
|
332
|
-
const [email, setEmail] = useState(undefined);
|
|
816
|
+
const FormInputErrorDemo = () => {
|
|
333
817
|
const [result, setResult] = useState("");
|
|
818
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
819
|
+
|
|
820
|
+
const formErrorAction = async ({ username, email }) => {
|
|
821
|
+
setIsLoading(true);
|
|
822
|
+
setResult("Submitting form...");
|
|
823
|
+
try {
|
|
824
|
+
await delay(1000);
|
|
825
|
+
// Simulate server-side validation errors
|
|
826
|
+
if (!username || username.length < 3) {
|
|
827
|
+
throw new Error("Username must be at least 3 characters long");
|
|
828
|
+
}
|
|
829
|
+
if (username === "admin") {
|
|
830
|
+
throw new Error(
|
|
831
|
+
"Username 'admin' is reserved and cannot be used",
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
// This will never be reached due to the conditions above
|
|
835
|
+
const response = `✅ Form submitted!\nUsername: ${username}\nEmail: ${email}`;
|
|
836
|
+
setResult(response);
|
|
837
|
+
return response;
|
|
838
|
+
} catch (error) {
|
|
839
|
+
setResult(`❌ Form error: ${error.message}`);
|
|
840
|
+
throw error;
|
|
841
|
+
} finally {
|
|
842
|
+
setIsLoading(false);
|
|
843
|
+
}
|
|
844
|
+
};
|
|
334
845
|
|
|
335
846
|
return (
|
|
336
847
|
<div className="demo-card">
|
|
337
|
-
<h3 className="demo-title">Form with
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
setResult("Submitting form...");
|
|
341
|
-
await delay(1000);
|
|
342
|
-
const response = `✅ Form submitted!\nUsername: ${params.username}\nEmail: ${params.email}`;
|
|
343
|
-
setResult(response);
|
|
344
|
-
return response;
|
|
345
|
-
}}
|
|
848
|
+
<h3 className="demo-title">Form with Validation Errors</h3>
|
|
849
|
+
<p
|
|
850
|
+
style={{ fontSize: "14px", color: "#666", marginBottom: "15px" }}
|
|
346
851
|
>
|
|
852
|
+
This form demonstrates server-side validation errors. Try: empty
|
|
853
|
+
fields, username < 3 chars, invalid email, or "admin" username.
|
|
854
|
+
</p>
|
|
855
|
+
<Form action={formErrorAction}>
|
|
347
856
|
<div className="control-group">
|
|
348
|
-
<label
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
</div>
|
|
356
|
-
<div className="control-group">
|
|
357
|
-
<label htmlFor="controlled-form-email">Email:</label>
|
|
358
|
-
<Input
|
|
359
|
-
id="controlled-form-email"
|
|
360
|
-
name="email"
|
|
361
|
-
value={email}
|
|
362
|
-
placeholder="Controlled by buttons"
|
|
363
|
-
/>
|
|
364
|
-
</div>
|
|
365
|
-
<div className="button-group">
|
|
366
|
-
<button
|
|
367
|
-
type="button"
|
|
368
|
-
onClick={() => {
|
|
369
|
-
setUsername("john_doe");
|
|
370
|
-
setEmail("john@example.com");
|
|
371
|
-
}}
|
|
372
|
-
>
|
|
373
|
-
Set User 1
|
|
374
|
-
</button>
|
|
375
|
-
<button
|
|
376
|
-
type="button"
|
|
377
|
-
onClick={() => {
|
|
378
|
-
setUsername("jane_smith");
|
|
379
|
-
setEmail("jane@example.com");
|
|
380
|
-
}}
|
|
381
|
-
>
|
|
382
|
-
Set User 2
|
|
383
|
-
</button>
|
|
384
|
-
<button
|
|
385
|
-
type="button"
|
|
386
|
-
onClick={() => {
|
|
387
|
-
setUsername("");
|
|
388
|
-
setEmail("");
|
|
389
|
-
}}
|
|
390
|
-
>
|
|
391
|
-
Clear All
|
|
392
|
-
</button>
|
|
857
|
+
<label>
|
|
858
|
+
Username:
|
|
859
|
+
<Input
|
|
860
|
+
name="username"
|
|
861
|
+
placeholder="Enter username (try 'admin' or < 3 chars)"
|
|
862
|
+
/>
|
|
863
|
+
</label>
|
|
393
864
|
</div>
|
|
394
865
|
<div className="button-group">
|
|
395
866
|
<Button type="submit">Submit Form</Button>
|
|
@@ -397,64 +868,14 @@
|
|
|
397
868
|
</div>
|
|
398
869
|
</Form>
|
|
399
870
|
<div
|
|
400
|
-
className={`result-display ${result.startsWith("✅") ? "result-success" : ""}`}
|
|
401
|
-
>
|
|
402
|
-
Current: {username} / {email}
|
|
403
|
-
<br />
|
|
404
|
-
{result || "Use buttons to set values, then submit..."}
|
|
405
|
-
</div>
|
|
406
|
-
</div>
|
|
407
|
-
);
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
// eslint-disable-next-line no-unused-vars
|
|
411
|
-
const SharedActionDemo = () => {
|
|
412
|
-
const [result, setResult] = useState("");
|
|
413
|
-
|
|
414
|
-
return (
|
|
415
|
-
<div className="demo-card">
|
|
416
|
-
<h3 className="demo-title">Shared Action Example</h3>
|
|
417
|
-
<div className="control-group">
|
|
418
|
-
<label htmlFor="shared-input-1">Input 1 (shared action):</label>
|
|
419
|
-
<Input
|
|
420
|
-
id="shared-input-1"
|
|
421
|
-
name="name"
|
|
422
|
-
placeholder="Enter name for input 1"
|
|
423
|
-
action={sharedAction}
|
|
424
|
-
onActionStart={() => {
|
|
425
|
-
setResult("Processing...");
|
|
426
|
-
}}
|
|
427
|
-
onActionEnd={(e) => {
|
|
428
|
-
setResult(e.detail.data);
|
|
429
|
-
}}
|
|
430
|
-
/>
|
|
431
|
-
</div>
|
|
432
|
-
<div className="control-group">
|
|
433
|
-
<label htmlFor="shared-input-2">
|
|
434
|
-
Input 2 (same shared action):
|
|
435
|
-
</label>
|
|
436
|
-
<Input
|
|
437
|
-
id="shared-input-2"
|
|
438
|
-
name="name"
|
|
439
|
-
placeholder="Enter name for input 2"
|
|
440
|
-
action={sharedAction}
|
|
441
|
-
/>
|
|
442
|
-
</div>
|
|
443
|
-
<div
|
|
444
|
-
className={`result-display ${result.startsWith("✅") ? "result-success" : ""}`}
|
|
871
|
+
className={`result-display ${isLoading ? "result-loading" : result.startsWith("✅") ? "result-success" : result.startsWith("❌") ? "result-error" : ""}`}
|
|
445
872
|
>
|
|
446
|
-
{result || "
|
|
873
|
+
{result || "Fill form and submit to see validation errors..."}
|
|
447
874
|
</div>
|
|
448
875
|
</div>
|
|
449
876
|
);
|
|
450
877
|
};
|
|
451
878
|
|
|
452
|
-
const sharedAction = createAction(async ({ name }) => {
|
|
453
|
-
await delay(800);
|
|
454
|
-
const response = `✅ Hello ${name}! (from shared action)`;
|
|
455
|
-
return response;
|
|
456
|
-
});
|
|
457
|
-
|
|
458
879
|
render(<App />, document.querySelector("#root"));
|
|
459
880
|
</script>
|
|
460
881
|
</body>
|