torch-glare 2.5.6 → 2.6.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.
- package/README.md +4 -3
- package/dist/bin/index.js +9 -24
- package/dist/bin/index.js.map +1 -1
- package/dist/src/commands/add.d.ts +2 -2
- package/dist/src/commands/add.d.ts.map +1 -1
- package/dist/src/commands/add.js +4 -70
- package/dist/src/commands/add.js.map +1 -1
- package/dist/src/commands/hook.d.ts +5 -4
- package/dist/src/commands/hook.d.ts.map +1 -1
- package/dist/src/commands/hook.js +7 -75
- package/dist/src/commands/hook.js.map +1 -1
- package/dist/src/commands/init.js +1 -1
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/layout.d.ts +5 -4
- package/dist/src/commands/layout.d.ts.map +1 -1
- package/dist/src/commands/layout.js +7 -75
- package/dist/src/commands/layout.js.map +1 -1
- package/dist/src/commands/provider.d.ts +5 -4
- package/dist/src/commands/provider.d.ts.map +1 -1
- package/dist/src/commands/provider.js +7 -75
- package/dist/src/commands/provider.js.map +1 -1
- package/dist/src/commands/update.d.ts.map +1 -1
- package/dist/src/commands/update.js +78 -43
- package/dist/src/commands/update.js.map +1 -1
- package/dist/src/commands/utils.d.ts +5 -4
- package/dist/src/commands/utils.d.ts.map +1 -1
- package/dist/src/commands/utils.js +7 -80
- package/dist/src/commands/utils.js.map +1 -1
- package/dist/src/shared/addFromRegistry.d.ts +16 -0
- package/dist/src/shared/addFromRegistry.d.ts.map +1 -0
- package/dist/src/shared/addFromRegistry.js +88 -0
- package/dist/src/shared/addFromRegistry.js.map +1 -0
- package/dist/src/shared/getInstallCommand.d.ts.map +1 -1
- package/dist/src/shared/getInstallCommand.js +5 -0
- package/dist/src/shared/getInstallCommand.js.map +1 -1
- package/dist/src/shared/installDependencies.d.ts +0 -10
- package/dist/src/shared/installDependencies.d.ts.map +1 -1
- package/dist/src/shared/installDependencies.js +0 -17
- package/dist/src/shared/installDependencies.js.map +1 -1
- package/dist/src/shared/installFromPlan.d.ts +7 -5
- package/dist/src/shared/installFromPlan.d.ts.map +1 -1
- package/dist/src/shared/installFromPlan.js +103 -36
- package/dist/src/shared/installFromPlan.js.map +1 -1
- package/dist/src/shared/loadRegistry.d.ts +12 -3
- package/dist/src/shared/loadRegistry.d.ts.map +1 -1
- package/dist/src/shared/loadRegistry.js +19 -20
- package/dist/src/shared/loadRegistry.js.map +1 -1
- package/dist/src/shared/registryClient.d.ts +19 -0
- package/dist/src/shared/registryClient.d.ts.map +1 -0
- package/dist/src/shared/registryClient.js +128 -0
- package/dist/src/shared/registryClient.js.map +1 -0
- package/dist/src/shared/resolveEntry.d.ts +10 -7
- package/dist/src/shared/resolveEntry.d.ts.map +1 -1
- package/dist/src/shared/resolveEntry.js +18 -13
- package/dist/src/shared/resolveEntry.js.map +1 -1
- package/dist/src/shared/suggestOtherCommand.d.ts +7 -1
- package/dist/src/shared/suggestOtherCommand.d.ts.map +1 -1
- package/dist/src/shared/suggestOtherCommand.js +29 -23
- package/dist/src/shared/suggestOtherCommand.js.map +1 -1
- package/dist/src/types/main.d.ts +31 -2
- package/dist/src/types/main.d.ts.map +1 -1
- package/package.json +6 -11
- package/apps/lib/components/ActionButton.tsx +0 -93
- package/apps/lib/components/ActionsGroup.tsx +0 -27
- package/apps/lib/components/AlertDialog.tsx +0 -204
- package/apps/lib/components/Avatar.tsx +0 -46
- package/apps/lib/components/Badge.tsx +0 -250
- package/apps/lib/components/BadgeField.tsx +0 -403
- package/apps/lib/components/Breadcrumb.tsx +0 -280
- package/apps/lib/components/Button.tsx +0 -325
- package/apps/lib/components/ButtonGroup.tsx +0 -190
- package/apps/lib/components/Calendar.tsx +0 -115
- package/apps/lib/components/Card.tsx +0 -95
- package/apps/lib/components/Checkbox.tsx +0 -42
- package/apps/lib/components/ColorPicker.tsx +0 -441
- package/apps/lib/components/ConclusionHeader.tsx +0 -148
- package/apps/lib/components/ContextMenu.tsx +0 -571
- package/apps/lib/components/CountBadge.tsx +0 -52
- package/apps/lib/components/DataTable.tsx +0 -213
- package/apps/lib/components/DataViews/badge.ts +0 -49
- package/apps/lib/components/DataViews/cell.tsx +0 -324
- package/apps/lib/components/DataViews/context.ts +0 -144
- package/apps/lib/components/DataViews/data-views.tsx +0 -395
- package/apps/lib/components/DataViews/filters/children.tsx +0 -98
- package/apps/lib/components/DataViews/filters/custom.tsx +0 -34
- package/apps/lib/components/DataViews/filters/filters.tsx +0 -157
- package/apps/lib/components/DataViews/filters/index.ts +0 -4
- package/apps/lib/components/DataViews/filters/labelled.tsx +0 -20
- package/apps/lib/components/DataViews/filters/presets.tsx +0 -65
- package/apps/lib/components/DataViews/filters/sync.tsx +0 -35
- package/apps/lib/components/DataViews/filters/values.ts +0 -173
- package/apps/lib/components/DataViews/header.tsx +0 -220
- package/apps/lib/components/DataViews/hooks/index.ts +0 -5
- package/apps/lib/components/DataViews/hooks/useActiveRow.ts +0 -22
- package/apps/lib/components/DataViews/hooks/useControllable.ts +0 -52
- package/apps/lib/components/DataViews/index.ts +0 -88
- package/apps/lib/components/DataViews/panel/columns.tsx +0 -153
- package/apps/lib/components/DataViews/panel/controls.tsx +0 -106
- package/apps/lib/components/DataViews/panel/index.ts +0 -3
- package/apps/lib/components/DataViews/panel/panel.tsx +0 -164
- package/apps/lib/components/DataViews/panel/saved-views.tsx +0 -67
- package/apps/lib/components/DataViews/panel/section.tsx +0 -81
- package/apps/lib/components/DataViews/panel/sort.tsx +0 -42
- package/apps/lib/components/DataViews/panel/tab.tsx +0 -31
- package/apps/lib/components/DataViews/slots.ts +0 -72
- package/apps/lib/components/DataViews/states.tsx +0 -73
- package/apps/lib/components/DataViews/types.ts +0 -510
- package/apps/lib/components/DataViews/views/board-view.tsx +0 -381
- package/apps/lib/components/DataViews/views/card-rows.tsx +0 -36
- package/apps/lib/components/DataViews/views/inbox-view.tsx +0 -260
- package/apps/lib/components/DataViews/views/pane-views.tsx +0 -196
- package/apps/lib/components/DataViews/views/table-view.tsx +0 -493
- package/apps/lib/components/DataViews/views/tree-view.tsx +0 -367
- package/apps/lib/components/DatePicker.tsx +0 -232
- package/apps/lib/components/Dialog.tsx +0 -116
- package/apps/lib/components/Divider.tsx +0 -28
- package/apps/lib/components/Drawer.tsx +0 -451
- package/apps/lib/components/DropdownMenu.tsx +0 -554
- package/apps/lib/components/FieldHint.tsx +0 -69
- package/apps/lib/components/Form.tsx +0 -182
- package/apps/lib/components/FormBuilder/context.ts +0 -85
- package/apps/lib/components/FormBuilder/field-kind.ts +0 -28
- package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +0 -60
- package/apps/lib/components/FormBuilder/fields/ColorField.tsx +0 -59
- package/apps/lib/components/FormBuilder/fields/CustomField.tsx +0 -11
- package/apps/lib/components/FormBuilder/fields/DateField.tsx +0 -32
- package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +0 -74
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +0 -168
- package/apps/lib/components/FormBuilder/fields/FileField.tsx +0 -39
- package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +0 -132
- package/apps/lib/components/FormBuilder/fields/OtpField.tsx +0 -31
- package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +0 -109
- package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +0 -31
- package/apps/lib/components/FormBuilder/fields/SelectField.tsx +0 -114
- package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +0 -157
- package/apps/lib/components/FormBuilder/fields/SliderField.tsx +0 -67
- package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +0 -41
- package/apps/lib/components/FormBuilder/fields/TableField.tsx +0 -336
- package/apps/lib/components/FormBuilder/fields/TextField.tsx +0 -213
- package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +0 -43
- package/apps/lib/components/FormBuilder/fields/countries.ts +0 -303
- package/apps/lib/components/FormBuilder/fields/index.ts +0 -25
- package/apps/lib/components/FormBuilder/form-builder.tsx +0 -198
- package/apps/lib/components/FormBuilder/index.ts +0 -31
- package/apps/lib/components/FormBuilder/numberFormat.ts +0 -16
- package/apps/lib/components/FormBuilder/submit.tsx +0 -67
- package/apps/lib/components/FormBuilder/types.ts +0 -316
- package/apps/lib/components/FormRenderer/FormDrawer.tsx +0 -250
- package/apps/lib/components/FormRenderer/detail.tsx +0 -256
- package/apps/lib/components/FormRenderer/form-renderer.tsx +0 -358
- package/apps/lib/components/FormRenderer/header.tsx +0 -88
- package/apps/lib/components/FormRenderer/index.ts +0 -17
- package/apps/lib/components/FormRenderer/notch-action.tsx +0 -64
- package/apps/lib/components/FormRenderer/section.tsx +0 -39
- package/apps/lib/components/FormRenderer/stepper.tsx +0 -393
- package/apps/lib/components/FormRenderer/types.ts +0 -115
- package/apps/lib/components/FormSummary.tsx +0 -282
- package/apps/lib/components/HeaderBar.tsx +0 -117
- package/apps/lib/components/ImageAttachment.tsx +0 -209
- package/apps/lib/components/InnerLabelField.tsx +0 -154
- package/apps/lib/components/Input.tsx +0 -224
- package/apps/lib/components/InputField.tsx +0 -147
- package/apps/lib/components/InputOTP.tsx +0 -84
- package/apps/lib/components/Label.tsx +0 -129
- package/apps/lib/components/LabelField.tsx +0 -76
- package/apps/lib/components/LabeledCheckBox.tsx +0 -51
- package/apps/lib/components/LabeledRadio.tsx +0 -51
- package/apps/lib/components/LinkButton.tsx +0 -94
- package/apps/lib/components/LoginButton.tsx +0 -51
- package/apps/lib/components/PasswordLevel.tsx +0 -58
- package/apps/lib/components/Popover.tsx +0 -312
- package/apps/lib/components/ProfileMenu.tsx +0 -85
- package/apps/lib/components/Radio.tsx +0 -59
- package/apps/lib/components/RadioCard.tsx +0 -50
- package/apps/lib/components/ScrollArea.tsx +0 -54
- package/apps/lib/components/SearchField.tsx +0 -62
- package/apps/lib/components/SearchableSelect.tsx +0 -318
- package/apps/lib/components/SearchableTable.tsx +0 -344
- package/apps/lib/components/SearchableTree.tsx +0 -485
- package/apps/lib/components/SearchableTreeDialog.tsx +0 -502
- package/apps/lib/components/SectionBlock.tsx +0 -168
- package/apps/lib/components/Select.tsx +0 -347
- package/apps/lib/components/SimpleSelect.tsx +0 -225
- package/apps/lib/components/Skeleton.tsx +0 -12
- package/apps/lib/components/SlideDatePicker.tsx +0 -255
- package/apps/lib/components/SpinLoading.tsx +0 -174
- package/apps/lib/components/Stepper.tsx +0 -480
- package/apps/lib/components/Switch.tsx +0 -82
- package/apps/lib/components/TabFormItem.tsx +0 -153
- package/apps/lib/components/TabSwitch.tsx +0 -172
- package/apps/lib/components/Table.tsx +0 -597
- package/apps/lib/components/TextEditor/ChartBlockTool.ts +0 -676
- package/apps/lib/components/TextEditor/RichTextField.tsx +0 -46
- package/apps/lib/components/TextEditor/TableDnDWrapper.ts +0 -462
- package/apps/lib/components/TextEditor/TextEditor.tsx +0 -1164
- package/apps/lib/components/TextEditor/TextEditorToolbar.tsx +0 -429
- package/apps/lib/components/TextEditor/editor-tools/AlignmentTune.ts +0 -70
- package/apps/lib/components/TextEditor/editor-tools/ColorInlineTool.ts +0 -50
- package/apps/lib/components/TextEditor/editor-tools/StrikethroughInlineTool.ts +0 -48
- package/apps/lib/components/TextEditor/editor-tools/inlineFormat.ts +0 -98
- package/apps/lib/components/TextEditor/editorjs.d.ts +0 -60
- package/apps/lib/components/TextEditor/index.ts +0 -7
- package/apps/lib/components/TextEditor/markdownParser.ts +0 -346
- package/apps/lib/components/Textarea.tsx +0 -117
- package/apps/lib/components/Timeline.tsx +0 -263
- package/apps/lib/components/Toast.tsx +0 -87
- package/apps/lib/components/Toggle.tsx +0 -125
- package/apps/lib/components/ToggleButton.tsx +0 -140
- package/apps/lib/components/Tooltip.tsx +0 -116
- package/apps/lib/components/TransparentLabel.tsx +0 -71
- package/apps/lib/components/TreeDropDown.tsx +0 -115
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +0 -380
- package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +0 -75
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +0 -354
- package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +0 -60
- package/apps/lib/components/TreeFolder/icons.tsx +0 -60
- package/apps/lib/components/TreeFolder/index.ts +0 -24
- package/apps/lib/components/TreeFolder/treeFolderUtils.ts +0 -109
- package/apps/lib/components/TreeFolder/types.ts +0 -77
- package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +0 -118
- package/apps/lib/hooks/useActiveTreeItem.ts +0 -61
- package/apps/lib/hooks/useClickOutside.ts +0 -27
- package/apps/lib/hooks/useDragDrop.tsx +0 -365
- package/apps/lib/hooks/useHtmlDir.ts +0 -31
- package/apps/lib/hooks/useInfiniteScroll.ts +0 -108
- package/apps/lib/hooks/useIsMobile.ts +0 -21
- package/apps/lib/hooks/useResize.ts +0 -67
- package/apps/lib/hooks/useTagSelection.ts +0 -300
- package/apps/lib/layouts/CNLayout.tsx +0 -372
- package/apps/lib/layouts/DataViewCard.tsx +0 -62
- package/apps/lib/layouts/FieldSection.tsx +0 -91
- package/apps/lib/layouts/TreeSubLayout.tsx +0 -173
- package/apps/lib/providers/ThemeProvider.tsx +0 -84
- package/apps/lib/registry.json +0 -1217
- package/apps/lib/tsconfig.json +0 -9
- package/apps/lib/tsconfig.tsbuildinfo +0 -1
- package/apps/lib/utils/cn.ts +0 -6
- package/apps/lib/utils/color.ts +0 -175
- package/apps/lib/utils/dataViews/path.ts +0 -67
- package/apps/lib/utils/dataViews/query.ts +0 -73
- package/apps/lib/utils/dataViews/types.ts +0 -187
- package/apps/lib/utils/dateFormat.ts +0 -108
- package/apps/lib/utils/resize.ts +0 -31
- package/apps/lib/utils/scroller.ts +0 -26
- package/apps/lib/utils/types.ts +0 -16
- package/docs/BLOCKS.md +0 -129
- package/docs/CHANGELOG-1.1.16.md +0 -108
- package/docs/Cover.png +0 -0
- package/docs/README.md +0 -215
- package/docs/components/action-button.md +0 -638
- package/docs/components/actions-group.md +0 -708
- package/docs/components/alert-dialog.md +0 -920
- package/docs/components/avatar.md +0 -709
- package/docs/components/badge-field.md +0 -738
- package/docs/components/badge.md +0 -301
- package/docs/components/breadcrumb.md +0 -930
- package/docs/components/button-group.md +0 -650
- package/docs/components/button.md +0 -639
- package/docs/components/calendar.md +0 -555
- package/docs/components/card.md +0 -622
- package/docs/components/chart-block-tool.md +0 -129
- package/docs/components/checkbox.md +0 -699
- package/docs/components/cn-layout.md +0 -854
- package/docs/components/color-picker.md +0 -101
- package/docs/components/conclusion-header.md +0 -80
- package/docs/components/context-menu.md +0 -483
- package/docs/components/count-badge.md +0 -560
- package/docs/components/data-table.md +0 -854
- package/docs/components/data-views/backend-response.md +0 -324
- package/docs/components/data-views/examples/a11y-rtl.md +0 -250
- package/docs/components/data-views/examples/api-orders-route.md +0 -130
- package/docs/components/data-views/examples/fields.md +0 -362
- package/docs/components/data-views/examples/filters.md +0 -307
- package/docs/components/data-views/examples/inbox-routing.md +0 -218
- package/docs/components/data-views/examples/index.md +0 -29
- package/docs/components/data-views/examples/overview.md +0 -244
- package/docs/components/data-views/examples/panel.md +0 -212
- package/docs/components/data-views/examples/scale.md +0 -231
- package/docs/components/data-views/examples/server-side.md +0 -210
- package/docs/components/data-views/examples/state.md +0 -250
- package/docs/components/data-views/examples/tree-custom.md +0 -388
- package/docs/components/data-views/examples/view-registry.md +0 -313
- package/docs/components/data-views/examples/views.md +0 -534
- package/docs/components/data-views/guide.md +0 -405
- package/docs/components/data-views/index.md +0 -1521
- package/docs/components/data-views/migration.md +0 -81
- package/docs/components/date-picker.md +0 -857
- package/docs/components/dialog.md +0 -929
- package/docs/components/divider.md +0 -766
- package/docs/components/drawer.md +0 -654
- package/docs/components/dropdown-menu.md +0 -668
- package/docs/components/field-hint.md +0 -898
- package/docs/components/field-section.md +0 -781
- package/docs/components/form-builder.md +0 -284
- package/docs/components/form-renderer.md +0 -396
- package/docs/components/form-summary.md +0 -138
- package/docs/components/form.md +0 -785
- package/docs/components/header-bar.md +0 -188
- package/docs/components/image-attachment.md +0 -906
- package/docs/components/inner-label-field.md +0 -819
- package/docs/components/input-field.md +0 -623
- package/docs/components/input-otp.md +0 -756
- package/docs/components/input.md +0 -634
- package/docs/components/label-field.md +0 -770
- package/docs/components/label.md +0 -761
- package/docs/components/labeled-check-box.md +0 -642
- package/docs/components/labeled-radio.md +0 -796
- package/docs/components/link-button.md +0 -685
- package/docs/components/login-button.md +0 -872
- package/docs/components/password-level.md +0 -803
- package/docs/components/popover.md +0 -649
- package/docs/components/profile-menu.md +0 -656
- package/docs/components/radio-card.md +0 -801
- package/docs/components/radio.md +0 -735
- package/docs/components/scroll-area.md +0 -691
- package/docs/components/search-field.md +0 -702
- package/docs/components/searchable-select.md +0 -368
- package/docs/components/searchable-table.md +0 -438
- package/docs/components/searchable-tree-dialog.md +0 -190
- package/docs/components/searchable-tree.md +0 -200
- package/docs/components/section-block.md +0 -483
- package/docs/components/select.md +0 -709
- package/docs/components/simple-select.md +0 -741
- package/docs/components/skeleton.md +0 -706
- package/docs/components/slide-date-picker.md +0 -837
- package/docs/components/spin-loading.md +0 -696
- package/docs/components/stepper.md +0 -316
- package/docs/components/switch.md +0 -770
- package/docs/components/tab-form-item.md +0 -773
- package/docs/components/tab-switch.md +0 -170
- package/docs/components/table-dnd-wrapper.md +0 -107
- package/docs/components/table.md +0 -1088
- package/docs/components/text-editor.md +0 -735
- package/docs/components/textarea.md +0 -681
- package/docs/components/timeline.md +0 -254
- package/docs/components/toast.md +0 -773
- package/docs/components/toggle-button.md +0 -643
- package/docs/components/toggle.md +0 -882
- package/docs/components/tooltip.md +0 -513
- package/docs/components/transparent-label.md +0 -721
- package/docs/components/tree-drop-down.md +0 -740
- package/docs/components/tree-folder.md +0 -110
- package/docs/components/tree-sub-layout.md +0 -707
- package/docs/explanation/architecture.md +0 -54
- package/docs/explanation/design-system.md +0 -52
- package/docs/how-to/form-and-list-recipes.md +0 -386
- package/docs/how-to/forms-with-form-builder.md +0 -472
- package/docs/how-to/guides.md +0 -1027
- package/docs/migration/changelog.md +0 -60
- package/docs/migration/form-builder-2.5.2.md +0 -113
- package/docs/reference/cli.md +0 -112
- package/docs/reference/components.md +0 -572
- package/docs/reference/hooks.md +0 -1523
- package/docs/reference/providers.md +0 -866
- package/docs/reference/tailwind-plugins.md +0 -942
- package/docs/reference/theme.md +0 -85
- package/docs/reference/types.md +0 -1016
- package/docs/reference/utilities.md +0 -726
- package/docs/tutorials/building-first-form.md +0 -846
- package/docs/tutorials/component-composition.md +0 -882
- package/docs/tutorials/getting-started.md +0 -302
- package/docs/tutorials/theming-basics.md +0 -785
|
@@ -1,846 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Building Your First Form
|
|
3
|
-
description: Complete guide to creating forms with TORCH Glare including validation, error handling, and best practices.
|
|
4
|
-
category: Tutorial
|
|
5
|
-
difficulty: Intermediate
|
|
6
|
-
duration: 30 minutes
|
|
7
|
-
tags: [forms, validation, input, error-handling, user-input]
|
|
8
|
-
related:
|
|
9
|
-
- Getting Started
|
|
10
|
-
- Component Composition
|
|
11
|
-
- LabelField Component
|
|
12
|
-
- InputField Component
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
# Building Your First Form
|
|
16
|
-
|
|
17
|
-
Learn how to build complete, production-ready forms with TORCH Glare. This tutorial covers everything from basic inputs to validation and error handling.
|
|
18
|
-
|
|
19
|
-
> **Build real forms with `FormBuilder`.** In an actual app, do **not** hand-wire fields with
|
|
20
|
-
> `useState` + `LabelField`/`InputField` rows as this tutorial does — that boilerplate is exactly
|
|
21
|
-
> what [`FormBuilder`](../components/form-builder.md) removes. With `FormBuilder` each field is one
|
|
22
|
-
> JSX child (`<FormBuilder.Text name="…" label="…" required />`) and validation comes from a
|
|
23
|
-
> react-hook-form resolver. Add [`FormRenderer`](../components/form-renderer.md) for page/drawer
|
|
24
|
-
> chrome + an `actions` slot for the Save, and [`FormSummary`](../components/form-summary.md) for live totals.
|
|
25
|
-
> See the **[Forms with FormBuilder](../how-to/forms-with-form-builder.md)** guide. The manual
|
|
26
|
-
> approach below is kept only to show what `FormBuilder` does under the hood.
|
|
27
|
-
|
|
28
|
-
## What You'll Build
|
|
29
|
-
|
|
30
|
-
A user registration form with:
|
|
31
|
-
|
|
32
|
-
- Multiple input types (text, email, password)
|
|
33
|
-
- Form validation
|
|
34
|
-
- Error handling and display
|
|
35
|
-
- Password strength indicator
|
|
36
|
-
- Success feedback
|
|
37
|
-
- Responsive design
|
|
38
|
-
|
|
39
|
-
## Prerequisites
|
|
40
|
-
|
|
41
|
-
- Completed the [Getting Started](./getting-started.md) tutorial
|
|
42
|
-
- Basic understanding of React hooks
|
|
43
|
-
- Familiarity with form handling in React
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Step 1: Create the Form Component
|
|
48
|
-
|
|
49
|
-
Let's start with a basic form structure:
|
|
50
|
-
|
|
51
|
-
```tsx
|
|
52
|
-
// components/RegistrationForm.tsx
|
|
53
|
-
"use client";
|
|
54
|
-
|
|
55
|
-
import { useState } from "react";
|
|
56
|
-
import { Button } from "@/components/Button";
|
|
57
|
-
|
|
58
|
-
export default function RegistrationForm() {
|
|
59
|
-
const [formData, setFormData] = useState({
|
|
60
|
-
fullName: "",
|
|
61
|
-
email: "",
|
|
62
|
-
password: "",
|
|
63
|
-
confirmPassword: "",
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
67
|
-
e.preventDefault();
|
|
68
|
-
console.log("Form submitted:", formData);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<form onSubmit={handleSubmit} className="max-w-md mx-auto p-6">
|
|
73
|
-
<h2 className="typography-display-medium-bold mb-6">Create Account</h2>
|
|
74
|
-
|
|
75
|
-
{/* We'll add fields here */}
|
|
76
|
-
|
|
77
|
-
<Button theme="light" variant="PrimeStyle" type="submit" className="w-full">
|
|
78
|
-
Register
|
|
79
|
-
</Button>
|
|
80
|
-
</form>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Step 2: Add Input Fields
|
|
88
|
-
|
|
89
|
-
### Using LabelField Component
|
|
90
|
-
|
|
91
|
-
`LabelField` combines a label and input field with built-in styling:
|
|
92
|
-
|
|
93
|
-
```tsx
|
|
94
|
-
import { useState } from "react";
|
|
95
|
-
import { Button } from "@/components/Button";
|
|
96
|
-
import { LabelField } from "@/components/LabelField";
|
|
97
|
-
|
|
98
|
-
export default function RegistrationForm() {
|
|
99
|
-
const [formData, setFormData] = useState({
|
|
100
|
-
fullName: "",
|
|
101
|
-
email: "",
|
|
102
|
-
password: "",
|
|
103
|
-
confirmPassword: "",
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const handleChange = (field: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
107
|
-
setFormData((prev) => ({
|
|
108
|
-
...prev,
|
|
109
|
-
[field]: e.target.value,
|
|
110
|
-
}));
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<form onSubmit={handleSubmit} className="max-w-md mx-auto p-6 space-y-4">
|
|
115
|
-
<h2 className="typography-display-medium-bold mb-6">Create Account</h2>
|
|
116
|
-
|
|
117
|
-
<LabelField
|
|
118
|
-
theme="light"
|
|
119
|
-
label="Full Name"
|
|
120
|
-
requiredLabel="*"
|
|
121
|
-
value={formData.fullName}
|
|
122
|
-
onChange={handleChange("fullName")}
|
|
123
|
-
placeholder="John Doe"
|
|
124
|
-
/>
|
|
125
|
-
|
|
126
|
-
<LabelField
|
|
127
|
-
theme="light"
|
|
128
|
-
label="Email"
|
|
129
|
-
requiredLabel="*"
|
|
130
|
-
type="email"
|
|
131
|
-
value={formData.email}
|
|
132
|
-
onChange={handleChange("email")}
|
|
133
|
-
placeholder="you@example.com"
|
|
134
|
-
/>
|
|
135
|
-
|
|
136
|
-
<LabelField
|
|
137
|
-
theme="light"
|
|
138
|
-
label="Password"
|
|
139
|
-
requiredLabel="*"
|
|
140
|
-
type="password"
|
|
141
|
-
value={formData.password}
|
|
142
|
-
onChange={handleChange("password")}
|
|
143
|
-
placeholder="••••••••"
|
|
144
|
-
/>
|
|
145
|
-
|
|
146
|
-
<LabelField
|
|
147
|
-
theme="light"
|
|
148
|
-
label="Confirm Password"
|
|
149
|
-
requiredLabel="*"
|
|
150
|
-
type="password"
|
|
151
|
-
value={formData.confirmPassword}
|
|
152
|
-
onChange={handleChange("confirmPassword")}
|
|
153
|
-
placeholder="••••••••"
|
|
154
|
-
/>
|
|
155
|
-
|
|
156
|
-
<Button theme="light" variant="PrimeStyle" type="submit" className="w-full">
|
|
157
|
-
Register
|
|
158
|
-
</Button>
|
|
159
|
-
</form>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
---
|
|
165
|
-
|
|
166
|
-
## Step 3: Add Validation
|
|
167
|
-
|
|
168
|
-
Let's add comprehensive validation:
|
|
169
|
-
|
|
170
|
-
```tsx
|
|
171
|
-
"use client";
|
|
172
|
-
|
|
173
|
-
import { useState, FormEvent } from "react";
|
|
174
|
-
import { Button } from "@/components/Button";
|
|
175
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
176
|
-
import { LabelField } from "@/components/LabelField";
|
|
177
|
-
|
|
178
|
-
interface FormErrors {
|
|
179
|
-
fullName?: string;
|
|
180
|
-
email?: string;
|
|
181
|
-
password?: string;
|
|
182
|
-
confirmPassword?: string;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export default function RegistrationForm() {
|
|
186
|
-
const [formData, setFormData] = useState({
|
|
187
|
-
fullName: "",
|
|
188
|
-
email: "",
|
|
189
|
-
password: "",
|
|
190
|
-
confirmPassword: "",
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
const [errors, setErrors] = useState<FormErrors>({});
|
|
194
|
-
const [touched, setTouched] = useState<Record<string, boolean>>({});
|
|
195
|
-
|
|
196
|
-
// Validation functions
|
|
197
|
-
const validateFullName = (name: string): string | undefined => {
|
|
198
|
-
if (!name.trim()) return "Full name is required";
|
|
199
|
-
if (name.trim().length < 2) return "Name must be at least 2 characters";
|
|
200
|
-
return undefined;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
const validateEmail = (email: string): string | undefined => {
|
|
204
|
-
if (!email) return "Email is required";
|
|
205
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
206
|
-
if (!emailRegex.test(email)) return "Please enter a valid email";
|
|
207
|
-
return undefined;
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const validatePassword = (password: string): string | undefined => {
|
|
211
|
-
if (!password) return "Password is required";
|
|
212
|
-
if (password.length < 8) return "Password must be at least 8 characters";
|
|
213
|
-
if (!/[A-Z]/.test(password)) return "Password must contain an uppercase letter";
|
|
214
|
-
if (!/[a-z]/.test(password)) return "Password must contain a lowercase letter";
|
|
215
|
-
if (!/[0-9]/.test(password)) return "Password must contain a number";
|
|
216
|
-
return undefined;
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
const validateConfirmPassword = (confirmPassword: string): string | undefined => {
|
|
220
|
-
if (!confirmPassword) return "Please confirm your password";
|
|
221
|
-
if (confirmPassword !== formData.password) return "Passwords do not match";
|
|
222
|
-
return undefined;
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
// Handle field changes
|
|
226
|
-
const handleChange =
|
|
227
|
-
(field: keyof typeof formData) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
228
|
-
const value = e.target.value;
|
|
229
|
-
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
230
|
-
|
|
231
|
-
// Validate on change if field has been touched
|
|
232
|
-
if (touched[field]) {
|
|
233
|
-
validateField(field, value);
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
// Handle blur events
|
|
238
|
-
const handleBlur = (field: keyof typeof formData) => () => {
|
|
239
|
-
setTouched((prev) => ({ ...prev, [field]: true }));
|
|
240
|
-
validateField(field, formData[field]);
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
// Validate individual field
|
|
244
|
-
const validateField = (field: keyof typeof formData, value: string) => {
|
|
245
|
-
let error: string | undefined;
|
|
246
|
-
|
|
247
|
-
switch (field) {
|
|
248
|
-
case "fullName":
|
|
249
|
-
error = validateFullName(value);
|
|
250
|
-
break;
|
|
251
|
-
case "email":
|
|
252
|
-
error = validateEmail(value);
|
|
253
|
-
break;
|
|
254
|
-
case "password":
|
|
255
|
-
error = validatePassword(value);
|
|
256
|
-
break;
|
|
257
|
-
case "confirmPassword":
|
|
258
|
-
error = validateConfirmPassword(value);
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
setErrors((prev) => ({ ...prev, [field]: error }));
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
// Validate all fields
|
|
266
|
-
const validateForm = (): boolean => {
|
|
267
|
-
const newErrors: FormErrors = {
|
|
268
|
-
fullName: validateFullName(formData.fullName),
|
|
269
|
-
email: validateEmail(formData.email),
|
|
270
|
-
password: validatePassword(formData.password),
|
|
271
|
-
confirmPassword: validateConfirmPassword(formData.confirmPassword),
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
setErrors(newErrors);
|
|
275
|
-
setTouched({
|
|
276
|
-
fullName: true,
|
|
277
|
-
email: true,
|
|
278
|
-
password: true,
|
|
279
|
-
confirmPassword: true,
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
return !Object.values(newErrors).some((error) => error !== undefined);
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
// Handle form submission
|
|
286
|
-
const handleSubmit = (e: FormEvent) => {
|
|
287
|
-
e.preventDefault();
|
|
288
|
-
|
|
289
|
-
if (validateForm()) {
|
|
290
|
-
console.log("Form is valid:", formData);
|
|
291
|
-
// Submit form data
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
return (
|
|
296
|
-
<form onSubmit={handleSubmit} className="max-w-md mx-auto p-6 space-y-4">
|
|
297
|
-
<h2 className="typography-display-medium-bold mb-6">Create Account</h2>
|
|
298
|
-
|
|
299
|
-
<div>
|
|
300
|
-
<LabelField
|
|
301
|
-
theme="light"
|
|
302
|
-
label="Full Name"
|
|
303
|
-
requiredLabel="*"
|
|
304
|
-
value={formData.fullName}
|
|
305
|
-
onChange={handleChange("fullName")}
|
|
306
|
-
onBlur={handleBlur("fullName")}
|
|
307
|
-
placeholder="John Doe"
|
|
308
|
-
errorMessage={touched.fullName ? errors.fullName : undefined}
|
|
309
|
-
/>
|
|
310
|
-
</div>
|
|
311
|
-
|
|
312
|
-
<div>
|
|
313
|
-
<LabelField
|
|
314
|
-
theme="light"
|
|
315
|
-
label="Email"
|
|
316
|
-
requiredLabel="*"
|
|
317
|
-
type="email"
|
|
318
|
-
value={formData.email}
|
|
319
|
-
onChange={handleChange("email")}
|
|
320
|
-
onBlur={handleBlur("email")}
|
|
321
|
-
placeholder="you@example.com"
|
|
322
|
-
errorMessage={touched.email ? errors.email : undefined}
|
|
323
|
-
/>
|
|
324
|
-
</div>
|
|
325
|
-
|
|
326
|
-
<div>
|
|
327
|
-
<LabelField
|
|
328
|
-
theme="light"
|
|
329
|
-
label="Password"
|
|
330
|
-
requiredLabel="*"
|
|
331
|
-
type="password"
|
|
332
|
-
value={formData.password}
|
|
333
|
-
onChange={handleChange("password")}
|
|
334
|
-
onBlur={handleBlur("password")}
|
|
335
|
-
placeholder="••••••••"
|
|
336
|
-
errorMessage={touched.password ? errors.password : undefined}
|
|
337
|
-
/>
|
|
338
|
-
</div>
|
|
339
|
-
|
|
340
|
-
<div>
|
|
341
|
-
<LabelField
|
|
342
|
-
theme="light"
|
|
343
|
-
label="Confirm Password"
|
|
344
|
-
requiredLabel="*"
|
|
345
|
-
type="password"
|
|
346
|
-
value={formData.confirmPassword}
|
|
347
|
-
onChange={handleChange("confirmPassword")}
|
|
348
|
-
onBlur={handleBlur("confirmPassword")}
|
|
349
|
-
placeholder="••••••••"
|
|
350
|
-
errorMessage={touched.confirmPassword ? errors.confirmPassword : undefined}
|
|
351
|
-
/>
|
|
352
|
-
</div>
|
|
353
|
-
|
|
354
|
-
<Button theme="light" variant="PrimeStyle" type="submit" className="w-full">
|
|
355
|
-
Register
|
|
356
|
-
</Button>
|
|
357
|
-
</form>
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
---
|
|
363
|
-
|
|
364
|
-
## Step 4: Add Password Strength Indicator
|
|
365
|
-
|
|
366
|
-
Use the `PasswordLevel` component to show password strength:
|
|
367
|
-
|
|
368
|
-
```tsx
|
|
369
|
-
import { Button } from "@/components/Button";
|
|
370
|
-
import { LabelField } from "@/components/LabelField";
|
|
371
|
-
import { PasswordLevel } from "@/components/PasswordLevel";
|
|
372
|
-
|
|
373
|
-
// Inside your form component, add this after the password field:
|
|
374
|
-
|
|
375
|
-
<div>
|
|
376
|
-
<LabelField
|
|
377
|
-
theme="light"
|
|
378
|
-
label="Password"
|
|
379
|
-
requiredLabel="*"
|
|
380
|
-
type="password"
|
|
381
|
-
value={formData.password}
|
|
382
|
-
onChange={handleChange("password")}
|
|
383
|
-
onBlur={handleBlur("password")}
|
|
384
|
-
placeholder="••••••••"
|
|
385
|
-
errorMessage={touched.password ? errors.password : undefined}
|
|
386
|
-
/>
|
|
387
|
-
|
|
388
|
-
{/* Password strength indicator */}
|
|
389
|
-
{formData.password && (
|
|
390
|
-
<div className="mt-2">
|
|
391
|
-
<PasswordLevel theme="light" value={formData.password} />
|
|
392
|
-
</div>
|
|
393
|
-
)}
|
|
394
|
-
</div>;
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
---
|
|
398
|
-
|
|
399
|
-
## Step 5: Add Field Hints
|
|
400
|
-
|
|
401
|
-
Use `FieldHint` to provide helpful guidance:
|
|
402
|
-
|
|
403
|
-
```tsx
|
|
404
|
-
import { Button } from "@/components/Button";
|
|
405
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
406
|
-
import { LabelField } from "@/components/LabelField";
|
|
407
|
-
|
|
408
|
-
<div>
|
|
409
|
-
<LabelField
|
|
410
|
-
theme="light"
|
|
411
|
-
label="Email"
|
|
412
|
-
requiredLabel="*"
|
|
413
|
-
type="email"
|
|
414
|
-
value={formData.email}
|
|
415
|
-
onChange={handleChange("email")}
|
|
416
|
-
onBlur={handleBlur("email")}
|
|
417
|
-
placeholder="you@example.com"
|
|
418
|
-
errorMessage={touched.email ? errors.email : undefined}
|
|
419
|
-
/>
|
|
420
|
-
|
|
421
|
-
{!errors.email && !touched.email && (
|
|
422
|
-
<FieldHint
|
|
423
|
-
theme="light"
|
|
424
|
-
state="info"
|
|
425
|
-
label="We'll never share your email with anyone"
|
|
426
|
-
className="mt-2"
|
|
427
|
-
/>
|
|
428
|
-
)}
|
|
429
|
-
</div>;
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
---
|
|
433
|
-
|
|
434
|
-
## Step 6: Add Loading State
|
|
435
|
-
|
|
436
|
-
Show loading feedback during submission:
|
|
437
|
-
|
|
438
|
-
```tsx
|
|
439
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
440
|
-
|
|
441
|
-
const handleSubmit = async (e: FormEvent) => {
|
|
442
|
-
e.preventDefault();
|
|
443
|
-
|
|
444
|
-
if (!validateForm()) return;
|
|
445
|
-
|
|
446
|
-
setIsSubmitting(true);
|
|
447
|
-
|
|
448
|
-
try {
|
|
449
|
-
// Simulate API call
|
|
450
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
451
|
-
|
|
452
|
-
console.log("Registration successful:", formData);
|
|
453
|
-
// Handle success (e.g., redirect, show success message)
|
|
454
|
-
} catch (error) {
|
|
455
|
-
console.error("Registration failed:", error);
|
|
456
|
-
// Handle error
|
|
457
|
-
} finally {
|
|
458
|
-
setIsSubmitting(false);
|
|
459
|
-
}
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
// Update button
|
|
463
|
-
<Button theme="light" variant="PrimeStyle" type="submit" className="w-full" disabled={isSubmitting}>
|
|
464
|
-
{isSubmitting ? "Registering..." : "Register"}
|
|
465
|
-
</Button>;
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
---
|
|
469
|
-
|
|
470
|
-
## Step 7: Add Success Feedback with Toast
|
|
471
|
-
|
|
472
|
-
Use the `toast` function for success notifications:
|
|
473
|
-
|
|
474
|
-
```tsx
|
|
475
|
-
import { Button } from "@/components/Button";
|
|
476
|
-
import { LabelField } from "@/components/LabelField";
|
|
477
|
-
import { toast } from "@/components/Toast";
|
|
478
|
-
|
|
479
|
-
const handleSubmit = async (e: FormEvent) => {
|
|
480
|
-
e.preventDefault();
|
|
481
|
-
|
|
482
|
-
if (!validateForm()) {
|
|
483
|
-
toast.error("Please fix the errors in the form");
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
setIsSubmitting(true);
|
|
488
|
-
|
|
489
|
-
try {
|
|
490
|
-
// Simulate API call
|
|
491
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
492
|
-
|
|
493
|
-
toast.success("Account created successfully!");
|
|
494
|
-
|
|
495
|
-
// Reset form
|
|
496
|
-
setFormData({
|
|
497
|
-
fullName: "",
|
|
498
|
-
email: "",
|
|
499
|
-
password: "",
|
|
500
|
-
confirmPassword: "",
|
|
501
|
-
});
|
|
502
|
-
setErrors({});
|
|
503
|
-
setTouched({});
|
|
504
|
-
} catch (error) {
|
|
505
|
-
toast.error("Registration failed. Please try again.");
|
|
506
|
-
} finally {
|
|
507
|
-
setIsSubmitting(false);
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
---
|
|
513
|
-
|
|
514
|
-
## Step 8: Complete Form Example
|
|
515
|
-
|
|
516
|
-
Here's the complete, production-ready form:
|
|
517
|
-
|
|
518
|
-
```tsx
|
|
519
|
-
"use client";
|
|
520
|
-
|
|
521
|
-
import { useState, FormEvent } from "react";
|
|
522
|
-
import { Button } from "@/components/Button";
|
|
523
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
524
|
-
import { LabelField } from "@/components/LabelField";
|
|
525
|
-
import { PasswordLevel } from "@/components/PasswordLevel";
|
|
526
|
-
import { toast } from "@/components/Toast";
|
|
527
|
-
|
|
528
|
-
interface FormErrors {
|
|
529
|
-
fullName?: string;
|
|
530
|
-
email?: string;
|
|
531
|
-
password?: string;
|
|
532
|
-
confirmPassword?: string;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
export default function RegistrationForm() {
|
|
536
|
-
const [formData, setFormData] = useState({
|
|
537
|
-
fullName: "",
|
|
538
|
-
email: "",
|
|
539
|
-
password: "",
|
|
540
|
-
confirmPassword: "",
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
const [errors, setErrors] = useState<FormErrors>({});
|
|
544
|
-
const [touched, setTouched] = useState<Record<string, boolean>>({});
|
|
545
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
546
|
-
|
|
547
|
-
// Validation functions
|
|
548
|
-
const validateFullName = (name: string): string | undefined => {
|
|
549
|
-
if (!name.trim()) return "Full name is required";
|
|
550
|
-
if (name.trim().length < 2) return "Name must be at least 2 characters";
|
|
551
|
-
return undefined;
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
const validateEmail = (email: string): string | undefined => {
|
|
555
|
-
if (!email) return "Email is required";
|
|
556
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
557
|
-
if (!emailRegex.test(email)) return "Please enter a valid email";
|
|
558
|
-
return undefined;
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
const validatePassword = (password: string): string | undefined => {
|
|
562
|
-
if (!password) return "Password is required";
|
|
563
|
-
if (password.length < 8) return "Password must be at least 8 characters";
|
|
564
|
-
if (!/[A-Z]/.test(password)) return "Password must contain an uppercase letter";
|
|
565
|
-
if (!/[a-z]/.test(password)) return "Password must contain a lowercase letter";
|
|
566
|
-
if (!/[0-9]/.test(password)) return "Password must contain a number";
|
|
567
|
-
return undefined;
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
const validateConfirmPassword = (confirmPassword: string): string | undefined => {
|
|
571
|
-
if (!confirmPassword) return "Please confirm your password";
|
|
572
|
-
if (confirmPassword !== formData.password) return "Passwords do not match";
|
|
573
|
-
return undefined;
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
const handleChange =
|
|
577
|
-
(field: keyof typeof formData) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
578
|
-
const value = e.target.value;
|
|
579
|
-
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
580
|
-
|
|
581
|
-
if (touched[field]) {
|
|
582
|
-
validateField(field, value);
|
|
583
|
-
}
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
const handleBlur = (field: keyof typeof formData) => () => {
|
|
587
|
-
setTouched((prev) => ({ ...prev, [field]: true }));
|
|
588
|
-
validateField(field, formData[field]);
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
const validateField = (field: keyof typeof formData, value: string) => {
|
|
592
|
-
let error: string | undefined;
|
|
593
|
-
|
|
594
|
-
switch (field) {
|
|
595
|
-
case "fullName":
|
|
596
|
-
error = validateFullName(value);
|
|
597
|
-
break;
|
|
598
|
-
case "email":
|
|
599
|
-
error = validateEmail(value);
|
|
600
|
-
break;
|
|
601
|
-
case "password":
|
|
602
|
-
error = validatePassword(value);
|
|
603
|
-
break;
|
|
604
|
-
case "confirmPassword":
|
|
605
|
-
error = validateConfirmPassword(value);
|
|
606
|
-
break;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
setErrors((prev) => ({ ...prev, [field]: error }));
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
const validateForm = (): boolean => {
|
|
613
|
-
const newErrors: FormErrors = {
|
|
614
|
-
fullName: validateFullName(formData.fullName),
|
|
615
|
-
email: validateEmail(formData.email),
|
|
616
|
-
password: validatePassword(formData.password),
|
|
617
|
-
confirmPassword: validateConfirmPassword(formData.confirmPassword),
|
|
618
|
-
};
|
|
619
|
-
|
|
620
|
-
setErrors(newErrors);
|
|
621
|
-
setTouched({
|
|
622
|
-
fullName: true,
|
|
623
|
-
email: true,
|
|
624
|
-
password: true,
|
|
625
|
-
confirmPassword: true,
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
return !Object.values(newErrors).some((error) => error !== undefined);
|
|
629
|
-
};
|
|
630
|
-
|
|
631
|
-
const handleSubmit = async (e: FormEvent) => {
|
|
632
|
-
e.preventDefault();
|
|
633
|
-
|
|
634
|
-
if (!validateForm()) {
|
|
635
|
-
toast.error("Please fix the errors in the form");
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
setIsSubmitting(true);
|
|
640
|
-
|
|
641
|
-
try {
|
|
642
|
-
// Simulate API call
|
|
643
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
644
|
-
|
|
645
|
-
toast.success("Account created successfully!");
|
|
646
|
-
|
|
647
|
-
// Reset form
|
|
648
|
-
setFormData({
|
|
649
|
-
fullName: "",
|
|
650
|
-
email: "",
|
|
651
|
-
password: "",
|
|
652
|
-
confirmPassword: "",
|
|
653
|
-
});
|
|
654
|
-
setErrors({});
|
|
655
|
-
setTouched({});
|
|
656
|
-
} catch (error) {
|
|
657
|
-
toast.error("Registration failed. Please try again.");
|
|
658
|
-
} finally {
|
|
659
|
-
setIsSubmitting(false);
|
|
660
|
-
}
|
|
661
|
-
};
|
|
662
|
-
|
|
663
|
-
return (
|
|
664
|
-
<div className="min-h-screen flex items-center justify-center p-4">
|
|
665
|
-
<form
|
|
666
|
-
onSubmit={handleSubmit}
|
|
667
|
-
className="
|
|
668
|
-
max-w-md w-full p-6 space-y-4
|
|
669
|
-
bg-background-presentation-form-base
|
|
670
|
-
border border-border-presentation-global-primary
|
|
671
|
-
rounded-lg
|
|
672
|
-
"
|
|
673
|
-
>
|
|
674
|
-
<div className="mb-6">
|
|
675
|
-
<h2 className="typography-display-medium-bold mb-2">Create Account</h2>
|
|
676
|
-
<p className="typography-body-medium-regular text-content-presentation-global-secondary">
|
|
677
|
-
Join us and get started today
|
|
678
|
-
</p>
|
|
679
|
-
</div>
|
|
680
|
-
|
|
681
|
-
<div>
|
|
682
|
-
<LabelField
|
|
683
|
-
theme="light"
|
|
684
|
-
label="Full Name"
|
|
685
|
-
requiredLabel="*"
|
|
686
|
-
value={formData.fullName}
|
|
687
|
-
onChange={handleChange("fullName")}
|
|
688
|
-
onBlur={handleBlur("fullName")}
|
|
689
|
-
placeholder="John Doe"
|
|
690
|
-
errorMessage={touched.fullName ? errors.fullName : undefined}
|
|
691
|
-
/>
|
|
692
|
-
</div>
|
|
693
|
-
|
|
694
|
-
<div>
|
|
695
|
-
<LabelField
|
|
696
|
-
theme="light"
|
|
697
|
-
label="Email"
|
|
698
|
-
requiredLabel="*"
|
|
699
|
-
type="email"
|
|
700
|
-
value={formData.email}
|
|
701
|
-
onChange={handleChange("email")}
|
|
702
|
-
onBlur={handleBlur("email")}
|
|
703
|
-
placeholder="you@example.com"
|
|
704
|
-
errorMessage={touched.email ? errors.email : undefined}
|
|
705
|
-
/>
|
|
706
|
-
{!errors.email && !touched.email && (
|
|
707
|
-
<FieldHint
|
|
708
|
-
theme="light"
|
|
709
|
-
state="info"
|
|
710
|
-
label="We'll never share your email"
|
|
711
|
-
className="mt-2"
|
|
712
|
-
/>
|
|
713
|
-
)}
|
|
714
|
-
</div>
|
|
715
|
-
|
|
716
|
-
<div>
|
|
717
|
-
<LabelField
|
|
718
|
-
theme="light"
|
|
719
|
-
label="Password"
|
|
720
|
-
requiredLabel="*"
|
|
721
|
-
type="password"
|
|
722
|
-
value={formData.password}
|
|
723
|
-
onChange={handleChange("password")}
|
|
724
|
-
onBlur={handleBlur("password")}
|
|
725
|
-
placeholder="••••••••"
|
|
726
|
-
errorMessage={touched.password ? errors.password : undefined}
|
|
727
|
-
/>
|
|
728
|
-
{formData.password && (
|
|
729
|
-
<div className="mt-2">
|
|
730
|
-
<PasswordLevel theme="light" value={formData.password} />
|
|
731
|
-
</div>
|
|
732
|
-
)}
|
|
733
|
-
</div>
|
|
734
|
-
|
|
735
|
-
<div>
|
|
736
|
-
<LabelField
|
|
737
|
-
theme="light"
|
|
738
|
-
label="Confirm Password"
|
|
739
|
-
requiredLabel="*"
|
|
740
|
-
type="password"
|
|
741
|
-
value={formData.confirmPassword}
|
|
742
|
-
onChange={handleChange("confirmPassword")}
|
|
743
|
-
onBlur={handleBlur("confirmPassword")}
|
|
744
|
-
placeholder="••••••••"
|
|
745
|
-
errorMessage={touched.confirmPassword ? errors.confirmPassword : undefined}
|
|
746
|
-
/>
|
|
747
|
-
</div>
|
|
748
|
-
|
|
749
|
-
<Button
|
|
750
|
-
theme="light"
|
|
751
|
-
variant="PrimeStyle"
|
|
752
|
-
type="submit"
|
|
753
|
-
className="w-full"
|
|
754
|
-
disabled={isSubmitting}
|
|
755
|
-
>
|
|
756
|
-
{isSubmitting ? "Registering..." : "Register"}
|
|
757
|
-
</Button>
|
|
758
|
-
|
|
759
|
-
<p className="typography-body-small-regular text-content-presentation-global-secondary text-center">
|
|
760
|
-
Already have an account?{" "}
|
|
761
|
-
<a href="/login" className="text-content-presentation-action-light-primary">
|
|
762
|
-
Sign in
|
|
763
|
-
</a>
|
|
764
|
-
</p>
|
|
765
|
-
</form>
|
|
766
|
-
</div>
|
|
767
|
-
);
|
|
768
|
-
}
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
---
|
|
772
|
-
|
|
773
|
-
## Best Practices
|
|
774
|
-
|
|
775
|
-
### 1. Validate on Blur, Not on Every Keystroke
|
|
776
|
-
|
|
777
|
-
```tsx
|
|
778
|
-
// ✓ Good - Validate after user leaves field
|
|
779
|
-
<LabelField
|
|
780
|
-
onBlur={handleBlur('email')}
|
|
781
|
-
onChange={handleChange('email')}
|
|
782
|
-
/>
|
|
783
|
-
|
|
784
|
-
// ✗ Avoid - Immediate validation on every keystroke
|
|
785
|
-
<LabelField
|
|
786
|
-
onChange={(e) => {
|
|
787
|
-
handleChange('email')(e);
|
|
788
|
-
validateField('email', e.target.value);
|
|
789
|
-
}}
|
|
790
|
-
/>
|
|
791
|
-
```
|
|
792
|
-
|
|
793
|
-
### 2. Show Errors Only After Touch
|
|
794
|
-
|
|
795
|
-
```tsx
|
|
796
|
-
// Only show errors for fields the user has interacted with
|
|
797
|
-
errorMessage={touched.email ? errors.email : undefined}
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
### 3. Disable Submit During Submission
|
|
801
|
-
|
|
802
|
-
```tsx
|
|
803
|
-
<Button type="submit" disabled={isSubmitting || Object.keys(errors).length > 0}>
|
|
804
|
-
{isSubmitting ? "Submitting..." : "Submit"}
|
|
805
|
-
</Button>
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
### 4. Provide Clear Error Messages
|
|
809
|
-
|
|
810
|
-
```tsx
|
|
811
|
-
// ✓ Good - Specific and actionable
|
|
812
|
-
"Password must contain at least one uppercase letter";
|
|
813
|
-
|
|
814
|
-
// ✗ Bad - Vague
|
|
815
|
-
"Invalid password";
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
### 5. Reset Form After Success
|
|
819
|
-
|
|
820
|
-
```tsx
|
|
821
|
-
const handleSuccess = () => {
|
|
822
|
-
setFormData(initialState);
|
|
823
|
-
setErrors({});
|
|
824
|
-
setTouched({});
|
|
825
|
-
toast.success("Success!");
|
|
826
|
-
};
|
|
827
|
-
```
|
|
828
|
-
|
|
829
|
-
---
|
|
830
|
-
|
|
831
|
-
## Next Steps
|
|
832
|
-
|
|
833
|
-
Now that you can build forms, explore:
|
|
834
|
-
|
|
835
|
-
1. **[Component Composition](./component-composition.md)** - Combine components for complex UIs
|
|
836
|
-
2. **[Theming Basics](./theming-basics.md)** - Customize form appearance
|
|
837
|
-
3. **[Select Component](../components/select.md)** - Add dropdown fields
|
|
838
|
-
4. **[Checkbox Component](../components/checkbox.md)** - Add checkboxes
|
|
839
|
-
|
|
840
|
-
## Additional Resources
|
|
841
|
-
|
|
842
|
-
- [LabelField API](../components/label-field.md)
|
|
843
|
-
- [InputField API](../components/input-field.md)
|
|
844
|
-
- [PasswordLevel API](../components/password-level.md)
|
|
845
|
-
- [FieldHint API](../components/field-hint.md)
|
|
846
|
-
- [Toast API](../components/toast.md)
|