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,898 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: FieldHint
|
|
3
|
-
description: Inline hint and alert component for form fields with state variants (info, warning, error, success)
|
|
4
|
-
group: Feedback & Status
|
|
5
|
-
keywords: [field-hint, alert, validation, message, form, feedback, inline]
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# FieldHint
|
|
9
|
-
|
|
10
|
-
> An inline hint and alert component designed for form field feedback. Provides visual status indicators with customizable icons and messages in four variants: info, warning, error, and success.
|
|
11
|
-
|
|
12
|
-
## Installation
|
|
13
|
-
|
|
14
|
-
TORCH Glare is a copy-in library: the CLI copies this component's source into your project
|
|
15
|
-
(you do **not** install it from the npm package). Run `init` once, then `add`:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npx torch-glare@latest init
|
|
19
|
-
npx torch-glare@latest add FieldHint
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
`add` also copies any components, hooks, and utilities that `FieldHint` depends on.
|
|
23
|
-
|
|
24
|
-
## Import
|
|
25
|
-
|
|
26
|
-
Import from your project's local path — the alias configured in `glare.json` (e.g. `@/*`):
|
|
27
|
-
|
|
28
|
-
```tsx
|
|
29
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Quick Examples
|
|
33
|
-
|
|
34
|
-
### Basic Usage
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
38
|
-
|
|
39
|
-
function Example() {
|
|
40
|
-
return (
|
|
41
|
-
<FieldHint
|
|
42
|
-
state="info"
|
|
43
|
-
label="This is an informational hint"
|
|
44
|
-
/>
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### All States
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
53
|
-
|
|
54
|
-
function AllStates() {
|
|
55
|
-
return (
|
|
56
|
-
<div className="space-y-2">
|
|
57
|
-
<FieldHint
|
|
58
|
-
state="info"
|
|
59
|
-
label="This is general information"
|
|
60
|
-
/>
|
|
61
|
-
|
|
62
|
-
<FieldHint
|
|
63
|
-
state="warning"
|
|
64
|
-
label="This requires your attention"
|
|
65
|
-
/>
|
|
66
|
-
|
|
67
|
-
<FieldHint
|
|
68
|
-
state="error"
|
|
69
|
-
label="There is an error with your input"
|
|
70
|
-
/>
|
|
71
|
-
|
|
72
|
-
<FieldHint
|
|
73
|
-
state="success"
|
|
74
|
-
label="Your input is valid"
|
|
75
|
-
/>
|
|
76
|
-
</div>
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### With Form Field
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
85
|
-
import { InputField } from "@/components/InputField";
|
|
86
|
-
import { useState } from 'react'
|
|
87
|
-
|
|
88
|
-
function WithFormField() {
|
|
89
|
-
const [email, setEmail] = useState('')
|
|
90
|
-
const [error, setError] = useState('')
|
|
91
|
-
|
|
92
|
-
const validateEmail = (value: string) => {
|
|
93
|
-
if (!value) {
|
|
94
|
-
setError('Email is required')
|
|
95
|
-
} else if (!value.includes('@')) {
|
|
96
|
-
setError('Please enter a valid email')
|
|
97
|
-
} else {
|
|
98
|
-
setError('')
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<div className="space-y-2">
|
|
104
|
-
<InputField
|
|
105
|
-
label="Email"
|
|
106
|
-
type="email"
|
|
107
|
-
value={email}
|
|
108
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
109
|
-
onBlur={() => validateEmail(email)}
|
|
110
|
-
/>
|
|
111
|
-
|
|
112
|
-
{error && (
|
|
113
|
-
<FieldHint state="error" label={error} />
|
|
114
|
-
)}
|
|
115
|
-
|
|
116
|
-
{!error && email && (
|
|
117
|
-
<FieldHint state="success" label="Email looks good!" />
|
|
118
|
-
)}
|
|
119
|
-
</div>
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Custom Icon
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
128
|
-
|
|
129
|
-
function CustomIcon() {
|
|
130
|
-
return (
|
|
131
|
-
<div className="space-y-2">
|
|
132
|
-
<FieldHint
|
|
133
|
-
state="info"
|
|
134
|
-
icon={<i className="ri-lightbulb-line" />}
|
|
135
|
-
label="Pro tip: Use a strong password"
|
|
136
|
-
/>
|
|
137
|
-
|
|
138
|
-
<FieldHint
|
|
139
|
-
state="warning"
|
|
140
|
-
icon={<i className="ri-alarm-warning-line" />}
|
|
141
|
-
label="Session expires in 5 minutes"
|
|
142
|
-
/>
|
|
143
|
-
|
|
144
|
-
<FieldHint
|
|
145
|
-
state="success"
|
|
146
|
-
icon={<i className="ri-star-fill" />}
|
|
147
|
-
label="Premium feature unlocked!"
|
|
148
|
-
/>
|
|
149
|
-
</div>
|
|
150
|
-
)
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Password Validation
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
158
|
-
import { InputField } from "@/components/InputField";
|
|
159
|
-
import { PasswordLevel } from "@/components/PasswordLevel";
|
|
160
|
-
import { useState } from 'react'
|
|
161
|
-
|
|
162
|
-
function PasswordValidation() {
|
|
163
|
-
const [password, setPassword] = useState('')
|
|
164
|
-
|
|
165
|
-
const getPasswordFeedback = (pwd: string) => {
|
|
166
|
-
if (!pwd) {
|
|
167
|
-
return { state: 'info', label: 'Password must be at least 6 characters' }
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (pwd.length < 6) {
|
|
171
|
-
return { state: 'error', label: 'Password is too short' }
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const hasSymbol = /[!@#$%^&*(),.?":{}|<>]/.test(pwd)
|
|
175
|
-
const hasUppercase = /[A-Z]/.test(pwd)
|
|
176
|
-
|
|
177
|
-
if (!hasSymbol || !hasUppercase) {
|
|
178
|
-
return {
|
|
179
|
-
state: 'warning',
|
|
180
|
-
label: 'Add uppercase letters and symbols for better security',
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return { state: 'success', label: 'Strong password!' }
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const feedback = getPasswordFeedback(password)
|
|
188
|
-
|
|
189
|
-
return (
|
|
190
|
-
<div className="space-y-2">
|
|
191
|
-
<InputField
|
|
192
|
-
type="password"
|
|
193
|
-
label="Password"
|
|
194
|
-
value={password}
|
|
195
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
196
|
-
/>
|
|
197
|
-
|
|
198
|
-
<PasswordLevel value={password} />
|
|
199
|
-
|
|
200
|
-
<FieldHint
|
|
201
|
-
state={feedback.state as any}
|
|
202
|
-
label={feedback.label}
|
|
203
|
-
/>
|
|
204
|
-
</div>
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### Form Validation
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
213
|
-
import { Button } from "@/components/Button";
|
|
214
|
-
import { InputField } from "@/components/InputField";
|
|
215
|
-
import { useState } from 'react'
|
|
216
|
-
|
|
217
|
-
function FormValidation() {
|
|
218
|
-
const [formData, setFormData] = useState({
|
|
219
|
-
username: '',
|
|
220
|
-
email: '',
|
|
221
|
-
age: '',
|
|
222
|
-
})
|
|
223
|
-
const [errors, setErrors] = useState<Record<string, string>>({})
|
|
224
|
-
|
|
225
|
-
const validate = () => {
|
|
226
|
-
const newErrors: Record<string, string> = {}
|
|
227
|
-
|
|
228
|
-
if (!formData.username) {
|
|
229
|
-
newErrors.username = 'Username is required'
|
|
230
|
-
} else if (formData.username.length < 3) {
|
|
231
|
-
newErrors.username = 'Username must be at least 3 characters'
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (!formData.email) {
|
|
235
|
-
newErrors.email = 'Email is required'
|
|
236
|
-
} else if (!formData.email.includes('@')) {
|
|
237
|
-
newErrors.email = 'Please enter a valid email'
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (formData.age && parseInt(formData.age) < 18) {
|
|
241
|
-
newErrors.age = 'You must be 18 or older'
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
setErrors(newErrors)
|
|
245
|
-
return Object.keys(newErrors).length === 0
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
249
|
-
e.preventDefault()
|
|
250
|
-
if (validate()) {
|
|
251
|
-
console.log('Form submitted:', formData)
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return (
|
|
256
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
257
|
-
<div className="space-y-2">
|
|
258
|
-
<InputField
|
|
259
|
-
label="Username"
|
|
260
|
-
value={formData.username}
|
|
261
|
-
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
|
|
262
|
-
/>
|
|
263
|
-
{errors.username && (
|
|
264
|
-
<FieldHint state="error" label={errors.username} />
|
|
265
|
-
)}
|
|
266
|
-
</div>
|
|
267
|
-
|
|
268
|
-
<div className="space-y-2">
|
|
269
|
-
<InputField
|
|
270
|
-
label="Email"
|
|
271
|
-
type="email"
|
|
272
|
-
value={formData.email}
|
|
273
|
-
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
|
274
|
-
/>
|
|
275
|
-
{errors.email && (
|
|
276
|
-
<FieldHint state="error" label={errors.email} />
|
|
277
|
-
)}
|
|
278
|
-
</div>
|
|
279
|
-
|
|
280
|
-
<div className="space-y-2">
|
|
281
|
-
<InputField
|
|
282
|
-
label="Age"
|
|
283
|
-
type="number"
|
|
284
|
-
value={formData.age}
|
|
285
|
-
onChange={(e) => setFormData({ ...formData, age: e.target.value })}
|
|
286
|
-
/>
|
|
287
|
-
{errors.age ? (
|
|
288
|
-
<FieldHint state="error" label={errors.age} />
|
|
289
|
-
) : (
|
|
290
|
-
<FieldHint state="info" label="Must be 18 or older" />
|
|
291
|
-
)}
|
|
292
|
-
</div>
|
|
293
|
-
|
|
294
|
-
<Button type="submit">Submit</Button>
|
|
295
|
-
</form>
|
|
296
|
-
)
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### API Response Feedback
|
|
301
|
-
|
|
302
|
-
```typescript
|
|
303
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
304
|
-
import { Button } from "@/components/Button";
|
|
305
|
-
import { InputField } from "@/components/InputField";
|
|
306
|
-
import { useState } from 'react'
|
|
307
|
-
|
|
308
|
-
function APIFeedback() {
|
|
309
|
-
const [username, setUsername] = useState('')
|
|
310
|
-
const [checking, setChecking] = useState(false)
|
|
311
|
-
const [status, setStatus] = useState<{
|
|
312
|
-
state: 'success' | 'error' | 'warning'
|
|
313
|
-
message: string
|
|
314
|
-
} | null>(null)
|
|
315
|
-
|
|
316
|
-
const checkAvailability = async () => {
|
|
317
|
-
if (!username) return
|
|
318
|
-
|
|
319
|
-
setChecking(true)
|
|
320
|
-
setStatus(null)
|
|
321
|
-
|
|
322
|
-
try {
|
|
323
|
-
const response = await fetch(`/api/check-username?username=${username}`)
|
|
324
|
-
const data = await response.json()
|
|
325
|
-
|
|
326
|
-
if (data.available) {
|
|
327
|
-
setStatus({
|
|
328
|
-
state: 'success',
|
|
329
|
-
message: 'Username is available!',
|
|
330
|
-
})
|
|
331
|
-
} else {
|
|
332
|
-
setStatus({
|
|
333
|
-
state: 'error',
|
|
334
|
-
message: 'Username is already taken',
|
|
335
|
-
})
|
|
336
|
-
}
|
|
337
|
-
} catch (error) {
|
|
338
|
-
setStatus({
|
|
339
|
-
state: 'warning',
|
|
340
|
-
message: 'Could not check availability. Please try again.',
|
|
341
|
-
})
|
|
342
|
-
} finally {
|
|
343
|
-
setChecking(false)
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return (
|
|
348
|
-
<div className="space-y-2">
|
|
349
|
-
<InputField
|
|
350
|
-
label="Username"
|
|
351
|
-
value={username}
|
|
352
|
-
onChange={(e) => setUsername(e.target.value)}
|
|
353
|
-
childrenSide={
|
|
354
|
-
<Button
|
|
355
|
-
size="S"
|
|
356
|
-
onClick={checkAvailability}
|
|
357
|
-
disabled={checking || !username}
|
|
358
|
-
>
|
|
359
|
-
{checking ? 'Checking...' : 'Check'}
|
|
360
|
-
</Button>
|
|
361
|
-
}
|
|
362
|
-
/>
|
|
363
|
-
|
|
364
|
-
{status && (
|
|
365
|
-
<FieldHint state={status.state} label={status.message} />
|
|
366
|
-
)}
|
|
367
|
-
</div>
|
|
368
|
-
)
|
|
369
|
-
}
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
### File Upload Feedback
|
|
373
|
-
|
|
374
|
-
```typescript
|
|
375
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
376
|
-
import { useState } from 'react'
|
|
377
|
-
|
|
378
|
-
function FileUploadFeedback() {
|
|
379
|
-
const [file, setFile] = useState<File | null>(null)
|
|
380
|
-
const [feedback, setFeedback] = useState<{
|
|
381
|
-
state: 'info' | 'warning' | 'error' | 'success'
|
|
382
|
-
message: string
|
|
383
|
-
}>({ state: 'info', message: 'Max file size: 5MB' })
|
|
384
|
-
|
|
385
|
-
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
386
|
-
const selectedFile = e.target.files?.[0]
|
|
387
|
-
|
|
388
|
-
if (!selectedFile) {
|
|
389
|
-
setFeedback({ state: 'info', message: 'Max file size: 5MB' })
|
|
390
|
-
return
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const maxSize = 5 * 1024 * 1024 // 5MB
|
|
394
|
-
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
|
|
395
|
-
|
|
396
|
-
if (!allowedTypes.includes(selectedFile.type)) {
|
|
397
|
-
setFeedback({
|
|
398
|
-
state: 'error',
|
|
399
|
-
message: 'Only JPEG, PNG, and GIF files are allowed',
|
|
400
|
-
})
|
|
401
|
-
return
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (selectedFile.size > maxSize) {
|
|
405
|
-
setFeedback({
|
|
406
|
-
state: 'error',
|
|
407
|
-
message: 'File is too large. Maximum size is 5MB',
|
|
408
|
-
})
|
|
409
|
-
return
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (selectedFile.size > maxSize * 0.8) {
|
|
413
|
-
setFeedback({
|
|
414
|
-
state: 'warning',
|
|
415
|
-
message: `File size: ${(selectedFile.size / 1024 / 1024).toFixed(2)}MB (approaching limit)`,
|
|
416
|
-
})
|
|
417
|
-
} else {
|
|
418
|
-
setFeedback({
|
|
419
|
-
state: 'success',
|
|
420
|
-
message: `File ready: ${selectedFile.name}`,
|
|
421
|
-
})
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
setFile(selectedFile)
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
return (
|
|
428
|
-
<div className="space-y-2">
|
|
429
|
-
<input
|
|
430
|
-
type="file"
|
|
431
|
-
onChange={handleFileChange}
|
|
432
|
-
accept="image/jpeg,image/png,image/gif"
|
|
433
|
-
/>
|
|
434
|
-
|
|
435
|
-
<FieldHint state={feedback.state} label={feedback.message} />
|
|
436
|
-
</div>
|
|
437
|
-
)
|
|
438
|
-
}
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
### Multi-line Message
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
445
|
-
|
|
446
|
-
function MultilineMessage() {
|
|
447
|
-
return (
|
|
448
|
-
<div className="space-y-2">
|
|
449
|
-
<FieldHint
|
|
450
|
-
state="info"
|
|
451
|
-
label={`Your password must include:
|
|
452
|
-
• At least 8 characters
|
|
453
|
-
• One uppercase letter
|
|
454
|
-
• One number
|
|
455
|
-
• One special character`}
|
|
456
|
-
/>
|
|
457
|
-
|
|
458
|
-
<FieldHint
|
|
459
|
-
state="warning"
|
|
460
|
-
label={`Warning: Your session is about to expire.
|
|
461
|
-
Please save your work before continuing.`}
|
|
462
|
-
/>
|
|
463
|
-
</div>
|
|
464
|
-
)
|
|
465
|
-
}
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
### Dynamic Theme
|
|
469
|
-
|
|
470
|
-
```typescript
|
|
471
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
472
|
-
import { useState } from 'react'
|
|
473
|
-
|
|
474
|
-
function DynamicTheme() {
|
|
475
|
-
const [theme, setTheme] = useState<'light' | 'dark'>('light')
|
|
476
|
-
|
|
477
|
-
return (
|
|
478
|
-
<div className={theme === 'dark' ? 'bg-gray-900 p-4' : 'bg-white p-4'}>
|
|
479
|
-
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
|
|
480
|
-
Toggle Theme
|
|
481
|
-
</button>
|
|
482
|
-
|
|
483
|
-
<div className="space-y-2 mt-4">
|
|
484
|
-
<FieldHint
|
|
485
|
-
theme={theme}
|
|
486
|
-
state="info"
|
|
487
|
-
label="Information message"
|
|
488
|
-
/>
|
|
489
|
-
<FieldHint
|
|
490
|
-
theme={theme}
|
|
491
|
-
state="success"
|
|
492
|
-
label="Success message"
|
|
493
|
-
/>
|
|
494
|
-
</div>
|
|
495
|
-
</div>
|
|
496
|
-
)
|
|
497
|
-
}
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
### Conditional Rendering
|
|
501
|
-
|
|
502
|
-
```typescript
|
|
503
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
504
|
-
import { InputField } from "@/components/InputField";
|
|
505
|
-
import { useState } from 'react'
|
|
506
|
-
|
|
507
|
-
function ConditionalHints() {
|
|
508
|
-
const [value, setValue] = useState('')
|
|
509
|
-
const [touched, setTouched] = useState(false)
|
|
510
|
-
|
|
511
|
-
const showError = touched && !value
|
|
512
|
-
const showSuccess = touched && value.length >= 5
|
|
513
|
-
|
|
514
|
-
return (
|
|
515
|
-
<div className="space-y-2">
|
|
516
|
-
<InputField
|
|
517
|
-
label="Name"
|
|
518
|
-
value={value}
|
|
519
|
-
onChange={(e) => setValue(e.target.value)}
|
|
520
|
-
onBlur={() => setTouched(true)}
|
|
521
|
-
/>
|
|
522
|
-
|
|
523
|
-
{!touched && (
|
|
524
|
-
<FieldHint state="info" label="Enter your full name" />
|
|
525
|
-
)}
|
|
526
|
-
|
|
527
|
-
{showError && (
|
|
528
|
-
<FieldHint state="error" label="Name is required" />
|
|
529
|
-
)}
|
|
530
|
-
|
|
531
|
-
{showSuccess && (
|
|
532
|
-
<FieldHint state="success" label="Looks good!" />
|
|
533
|
-
)}
|
|
534
|
-
</div>
|
|
535
|
-
)
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
## API Reference
|
|
540
|
-
|
|
541
|
-
### FieldHint Props
|
|
542
|
-
|
|
543
|
-
Extends all standard HTML div attributes.
|
|
544
|
-
|
|
545
|
-
| Prop | Type | Default | Description |
|
|
546
|
-
|------|------|---------|-------------|
|
|
547
|
-
| `label` | `ReactNode` | **required** | Message or content to display |
|
|
548
|
-
| `state` | `'info' \| 'warning' \| 'error' \| 'success'` | `'info'` | Visual state variant |
|
|
549
|
-
| `icon` | `ReactNode` | - | Custom icon (uses default if not provided) |
|
|
550
|
-
| `theme` | `'light' \| 'dark' \| 'default'` | - | Theme variant |
|
|
551
|
-
| `className` | `string` | - | Additional CSS classes |
|
|
552
|
-
| ...HTMLDivElement | - | - | All div attributes |
|
|
553
|
-
|
|
554
|
-
### Default Icons
|
|
555
|
-
|
|
556
|
-
| State | Icon | RemixIcon Class |
|
|
557
|
-
|-------|------|-----------------|
|
|
558
|
-
| `info` | ⓘ | `ri-error-warning-fill` |
|
|
559
|
-
| `warning` | ⚠ | `ri-error-warning-fill` |
|
|
560
|
-
| `error` | ⚠ | `ri-alert-fill` |
|
|
561
|
-
| `success` | ✓ | `ri-checkbox-circle-fill` |
|
|
562
|
-
|
|
563
|
-
### State Colors
|
|
564
|
-
|
|
565
|
-
| State | Background | Icon Background |
|
|
566
|
-
|-------|------------|-----------------|
|
|
567
|
-
| `info` | Information secondary | Information primary |
|
|
568
|
-
| `warning` | Warning secondary | Warning primary |
|
|
569
|
-
| `error` | Negative secondary | Negative primary |
|
|
570
|
-
| `success` | Success secondary | Success primary |
|
|
571
|
-
|
|
572
|
-
## TypeScript
|
|
573
|
-
|
|
574
|
-
### Full Type Definitions
|
|
575
|
-
|
|
576
|
-
```typescript
|
|
577
|
-
import { HTMLAttributes, ReactNode } from 'react'
|
|
578
|
-
|
|
579
|
-
type FieldHintVariant = 'success' | 'error' | 'warning' | 'info'
|
|
580
|
-
|
|
581
|
-
interface FieldHintProps extends HTMLAttributes<HTMLDivElement> {
|
|
582
|
-
label: ReactNode
|
|
583
|
-
state?: FieldHintVariant
|
|
584
|
-
icon?: ReactNode
|
|
585
|
-
theme?: 'light' | 'dark' | 'default'
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
export const FieldHint: React.FC<FieldHintProps>
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
### Usage with Types
|
|
592
|
-
|
|
593
|
-
```typescript
|
|
594
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
595
|
-
import { useState } from 'react'
|
|
596
|
-
|
|
597
|
-
type ValidationState = {
|
|
598
|
-
state: 'success' | 'error' | 'warning' | 'info'
|
|
599
|
-
message: string
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
function TypedExample() {
|
|
603
|
-
const [validation, setValidation] = useState<ValidationState>({
|
|
604
|
-
state: 'info',
|
|
605
|
-
message: 'Enter your email',
|
|
606
|
-
})
|
|
607
|
-
|
|
608
|
-
return (
|
|
609
|
-
<FieldHint
|
|
610
|
-
state={validation.state}
|
|
611
|
-
label={validation.message}
|
|
612
|
-
/>
|
|
613
|
-
)
|
|
614
|
-
}
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
## Common Patterns
|
|
618
|
-
|
|
619
|
-
### Validation Hook
|
|
620
|
-
|
|
621
|
-
```typescript
|
|
622
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
623
|
-
import { useState, useCallback } from 'react'
|
|
624
|
-
|
|
625
|
-
function useFieldValidation<T>(
|
|
626
|
-
initialValue: T,
|
|
627
|
-
validator: (value: T) => string | null
|
|
628
|
-
) {
|
|
629
|
-
const [value, setValue] = useState(initialValue)
|
|
630
|
-
const [error, setError] = useState<string | null>(null)
|
|
631
|
-
const [touched, setTouched] = useState(false)
|
|
632
|
-
|
|
633
|
-
const validate = useCallback(() => {
|
|
634
|
-
const errorMessage = validator(value)
|
|
635
|
-
setError(errorMessage)
|
|
636
|
-
return !errorMessage
|
|
637
|
-
}, [value, validator])
|
|
638
|
-
|
|
639
|
-
const handleBlur = useCallback(() => {
|
|
640
|
-
setTouched(true)
|
|
641
|
-
validate()
|
|
642
|
-
}, [validate])
|
|
643
|
-
|
|
644
|
-
return {
|
|
645
|
-
value,
|
|
646
|
-
setValue,
|
|
647
|
-
error,
|
|
648
|
-
touched,
|
|
649
|
-
handleBlur,
|
|
650
|
-
validate,
|
|
651
|
-
showError: touched && error,
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// Usage
|
|
656
|
-
function ValidatedField() {
|
|
657
|
-
const field = useFieldValidation('', (value) =>
|
|
658
|
-
!value ? 'Field is required' : value.length < 3 ? 'Too short' : null
|
|
659
|
-
)
|
|
660
|
-
|
|
661
|
-
return (
|
|
662
|
-
<div className="space-y-2">
|
|
663
|
-
<InputField
|
|
664
|
-
value={field.value}
|
|
665
|
-
onChange={(e) => field.setValue(e.target.value)}
|
|
666
|
-
onBlur={field.handleBlur}
|
|
667
|
-
/>
|
|
668
|
-
|
|
669
|
-
{field.showError && (
|
|
670
|
-
<FieldHint state="error" label={field.error!} />
|
|
671
|
-
)}
|
|
672
|
-
</div>
|
|
673
|
-
)
|
|
674
|
-
}
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
### Form Field Wrapper
|
|
678
|
-
|
|
679
|
-
```typescript
|
|
680
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
681
|
-
import { InputField } from "@/components/InputField";
|
|
682
|
-
import { ReactNode } from 'react'
|
|
683
|
-
|
|
684
|
-
interface FieldWrapperProps {
|
|
685
|
-
label: string
|
|
686
|
-
value: string
|
|
687
|
-
onChange: (value: string) => void
|
|
688
|
-
error?: string
|
|
689
|
-
hint?: string
|
|
690
|
-
required?: boolean
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
function FieldWrapper({
|
|
694
|
-
label,
|
|
695
|
-
value,
|
|
696
|
-
onChange,
|
|
697
|
-
error,
|
|
698
|
-
hint,
|
|
699
|
-
required,
|
|
700
|
-
}: FieldWrapperProps) {
|
|
701
|
-
return (
|
|
702
|
-
<div className="space-y-2">
|
|
703
|
-
<InputField
|
|
704
|
-
label={label}
|
|
705
|
-
value={value}
|
|
706
|
-
onChange={(e) => onChange(e.target.value)}
|
|
707
|
-
required={required}
|
|
708
|
-
/>
|
|
709
|
-
|
|
710
|
-
{error ? (
|
|
711
|
-
<FieldHint state="error" label={error} />
|
|
712
|
-
) : hint ? (
|
|
713
|
-
<FieldHint state="info" label={hint} />
|
|
714
|
-
) : null}
|
|
715
|
-
</div>
|
|
716
|
-
)
|
|
717
|
-
}
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
## Testing
|
|
721
|
-
|
|
722
|
-
### Unit Test Examples
|
|
723
|
-
|
|
724
|
-
```typescript
|
|
725
|
-
import { render, screen } from '@testing-library/react'
|
|
726
|
-
import { FieldHint } from "@/components/FieldHint";
|
|
727
|
-
|
|
728
|
-
describe('FieldHint', () => {
|
|
729
|
-
it('renders label text', () => {
|
|
730
|
-
render(<FieldHint label="Test message" />)
|
|
731
|
-
|
|
732
|
-
expect(screen.getByText('Test message')).toBeInTheDocument()
|
|
733
|
-
})
|
|
734
|
-
|
|
735
|
-
it('renders with different states', () => {
|
|
736
|
-
const { rerender, container } = render(
|
|
737
|
-
<FieldHint state="info" label="Info" />
|
|
738
|
-
)
|
|
739
|
-
|
|
740
|
-
expect(container.querySelector('[class*="information"]')).toBeInTheDocument()
|
|
741
|
-
|
|
742
|
-
rerender(<FieldHint state="error" label="Error" />)
|
|
743
|
-
expect(container.querySelector('[class*="negative"]')).toBeInTheDocument()
|
|
744
|
-
|
|
745
|
-
rerender(<FieldHint state="success" label="Success" />)
|
|
746
|
-
expect(container.querySelector('[class*="success"]')).toBeInTheDocument()
|
|
747
|
-
|
|
748
|
-
rerender(<FieldHint state="warning" label="Warning" />)
|
|
749
|
-
expect(container.querySelector('[class*="warning"]')).toBeInTheDocument()
|
|
750
|
-
})
|
|
751
|
-
|
|
752
|
-
it('renders default icon for each state', () => {
|
|
753
|
-
const { rerender } = render(
|
|
754
|
-
<FieldHint state="error" label="Error" />
|
|
755
|
-
)
|
|
756
|
-
|
|
757
|
-
expect(document.querySelector('.ri-alert-fill')).toBeInTheDocument()
|
|
758
|
-
|
|
759
|
-
rerender(<FieldHint state="success" label="Success" />)
|
|
760
|
-
expect(document.querySelector('.ri-checkbox-circle-fill')).toBeInTheDocument()
|
|
761
|
-
|
|
762
|
-
rerender(<FieldHint state="warning" label="Warning" />)
|
|
763
|
-
expect(document.querySelector('.ri-error-warning-fill')).toBeInTheDocument()
|
|
764
|
-
})
|
|
765
|
-
|
|
766
|
-
it('renders custom icon', () => {
|
|
767
|
-
render(
|
|
768
|
-
<FieldHint
|
|
769
|
-
state="info"
|
|
770
|
-
label="Custom"
|
|
771
|
-
icon={<span data-testid="custom-icon">★</span>}
|
|
772
|
-
/>
|
|
773
|
-
)
|
|
774
|
-
|
|
775
|
-
expect(screen.getByTestId('custom-icon')).toBeInTheDocument()
|
|
776
|
-
})
|
|
777
|
-
|
|
778
|
-
it('applies theme correctly', () => {
|
|
779
|
-
const { container } = render(
|
|
780
|
-
<FieldHint label="Test" theme="dark" />
|
|
781
|
-
)
|
|
782
|
-
|
|
783
|
-
expect(container.querySelector('[data-theme="dark"]')).toBeInTheDocument()
|
|
784
|
-
})
|
|
785
|
-
})
|
|
786
|
-
```
|
|
787
|
-
|
|
788
|
-
## Accessibility
|
|
789
|
-
|
|
790
|
-
- **Color Contrast**: All state colors meet WCAG AA standards
|
|
791
|
-
- **Icons + Text**: Combines visual and textual indicators
|
|
792
|
-
- **Screen Readers**: Content announced in reading order
|
|
793
|
-
- **ARIA**: Consider adding `role="alert"` for errors
|
|
794
|
-
- **Focus Management**: Non-interactive, doesn't affect tab order
|
|
795
|
-
|
|
796
|
-
### Accessibility Best Practices
|
|
797
|
-
|
|
798
|
-
```typescript
|
|
799
|
-
// Add role for error messages
|
|
800
|
-
<FieldHint
|
|
801
|
-
state="error"
|
|
802
|
-
label="Invalid email address"
|
|
803
|
-
role="alert"
|
|
804
|
-
aria-live="polite"
|
|
805
|
-
/>
|
|
806
|
-
|
|
807
|
-
// Link hint to field
|
|
808
|
-
<div>
|
|
809
|
-
<InputField
|
|
810
|
-
id="email-field"
|
|
811
|
-
aria-describedby="email-hint"
|
|
812
|
-
/>
|
|
813
|
-
<FieldHint
|
|
814
|
-
id="email-hint"
|
|
815
|
-
state="info"
|
|
816
|
-
label="We'll never share your email"
|
|
817
|
-
/>
|
|
818
|
-
</div>
|
|
819
|
-
|
|
820
|
-
// Announce validation errors
|
|
821
|
-
<FieldHint
|
|
822
|
-
state="error"
|
|
823
|
-
label={error}
|
|
824
|
-
role="alert"
|
|
825
|
-
aria-atomic="true"
|
|
826
|
-
/>
|
|
827
|
-
```
|
|
828
|
-
|
|
829
|
-
## Performance
|
|
830
|
-
|
|
831
|
-
| Metric | Value |
|
|
832
|
-
|--------|-------|
|
|
833
|
-
| Bundle size (minified) | ~2kb |
|
|
834
|
-
| Bundle size (gzipped) | ~0.8kb |
|
|
835
|
-
| Dependencies | None |
|
|
836
|
-
| Re-renders | Only on prop changes |
|
|
837
|
-
| Tree-shakeable | ✅ |
|
|
838
|
-
|
|
839
|
-
## Styling
|
|
840
|
-
|
|
841
|
-
### Custom Styles
|
|
842
|
-
|
|
843
|
-
```typescript
|
|
844
|
-
// Override colors
|
|
845
|
-
<FieldHint
|
|
846
|
-
state="error"
|
|
847
|
-
className="!bg-red-100 [&>section]:!bg-red-500"
|
|
848
|
-
label="Custom styled error"
|
|
849
|
-
/>
|
|
850
|
-
|
|
851
|
-
// Custom size
|
|
852
|
-
<FieldHint
|
|
853
|
-
state="info"
|
|
854
|
-
className="text-xs"
|
|
855
|
-
label="Small hint text"
|
|
856
|
-
/>
|
|
857
|
-
```
|
|
858
|
-
|
|
859
|
-
### Default Styling
|
|
860
|
-
|
|
861
|
-
- Border radius: 4px
|
|
862
|
-
- Minimum height: 26px
|
|
863
|
-
- Icon size: 26x26px (18px font)
|
|
864
|
-
- Font size: 14px (small)
|
|
865
|
-
- Padding: 1px text, 4px container
|
|
866
|
-
- Text wrapping: Enabled with word-break
|
|
867
|
-
|
|
868
|
-
## Best Practices
|
|
869
|
-
|
|
870
|
-
1. **Use appropriate states**: Match state to message severity
|
|
871
|
-
```typescript
|
|
872
|
-
<FieldHint state="error" label="Required field" />
|
|
873
|
-
<FieldHint state="warning" label="Recommended action" />
|
|
874
|
-
<FieldHint state="success" label="Valid input" />
|
|
875
|
-
<FieldHint state="info" label="Helpful tip" />
|
|
876
|
-
```
|
|
877
|
-
|
|
878
|
-
2. **Keep messages concise**: One or two lines maximum
|
|
879
|
-
|
|
880
|
-
3. **Show one hint at a time**: Avoid multiple hints per field
|
|
881
|
-
|
|
882
|
-
4. **Position below input**: Maintain consistent layout
|
|
883
|
-
|
|
884
|
-
5. **Use with validation**: Show errors after user interaction
|
|
885
|
-
```typescript
|
|
886
|
-
{touched && error && <FieldHint state="error" label={error} />}
|
|
887
|
-
```
|
|
888
|
-
|
|
889
|
-
6. **Provide actionable feedback**: Tell users how to fix errors
|
|
890
|
-
|
|
891
|
-
7. **Test color accessibility**: Ensure sufficient contrast
|
|
892
|
-
|
|
893
|
-
## Related Components
|
|
894
|
-
|
|
895
|
-
- [Toast](./toast.md) - Global notification messages
|
|
896
|
-
- [InputField](./input-field.md) - Input fields with validation
|
|
897
|
-
- [PasswordLevel](./password-level.md) - Password strength indicator
|
|
898
|
-
- [SpinLoading](./spin-loading.md) - Loading states
|