From 7957686753fe03284f01f3d14708d59df6bcedd3 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Thu, 5 Feb 2026 12:22:03 +0800 Subject: [PATCH 1/2] feat: Add `FormControl` components with comprehensive accessibility support This commit adds the FormControl component system to Tonic-UI, migrating it from experimental to production status. Key changes: - Add 8 form control components (FormControl, FormLabel, FormInput, FormTextarea, FormErrorMessage, FormHelperText, FormCharacterCount) - Add useFormControl hook for context access - Add comprehensive test suite (10 test cases) - Add complete documentation with 11 interactive examples - Remove experimental form-control files - Update package exports and configuration The FormControl system provides accessible, composable form fields with automatic ID generation, ARIA attribute management, and context-based state management. Co-Authored-By: Claude Sonnet 4.5 (1M context) --- .changeset/tonic-ui-form-control.md | 5 + packages/react-docs/config/sidebar-routes.js | 2 +- .../pages/components/form-control/basic.js | 21 ++ .../form-control/character-count.js | 26 +++ .../form-control/complex-form.js} | 47 +++- .../components/form-control/custom-styling.js | 73 +++++++ .../pages/components/form-control/disabled.js | 17 ++ .../components/form-control/error-messages.js | 25 +++ .../pages/components/form-control/error.js | 19 ++ .../components/form-control/helper-text.js | 21 ++ .../components/form-control/horizontal.js | 65 ++++++ .../components/form-control/index.page.mdx | 146 +++++++++++++ .../pages/components/form-control/readonly.js | 17 ++ .../pages/components/form-control/required.js | 17 ++ .../experiments/form-control/basic-setup.js | 98 --------- .../form-control/custom-styling.js | 172 --------------- .../experiments/form-control/index.page.mdx | 126 ----------- packages/react-docs/pages/llms.txt | 2 + packages/react/__tests__/index.test.js | 10 + .../src/form-control/FormCharacterCount.js | 32 +++ .../react/src/form-control/FormControl.js | 60 ++++++ .../src/form-control/FormErrorMessage.js | 50 +++++ .../react/src/form-control/FormHelperText.js | 22 ++ packages/react/src/form-control/FormInput.js | 6 + packages/react/src/form-control/FormLabel.js | 32 +++ .../react/src/form-control/FormTextarea.js | 6 + .../__tests__/FormControl.test.js | 203 ++++++++++++++++++ packages/react/src/form-control/context.js | 7 + packages/react/src/form-control/index.js | 8 + packages/react/src/form-control/styles.js | 127 +++++++++++ .../react/src/form-control/useFormControl.js | 13 ++ .../react/src/form-control/withFormControl.js | 53 +++++ packages/react/src/index.js | 1 + 33 files changed, 1129 insertions(+), 400 deletions(-) create mode 100644 .changeset/tonic-ui-form-control.md create mode 100644 packages/react-docs/pages/components/form-control/basic.js create mode 100644 packages/react-docs/pages/components/form-control/character-count.js rename packages/react-docs/pages/{experiments/form-control/advanced-setup.js => components/form-control/complex-form.js} (76%) create mode 100644 packages/react-docs/pages/components/form-control/custom-styling.js create mode 100644 packages/react-docs/pages/components/form-control/disabled.js create mode 100644 packages/react-docs/pages/components/form-control/error-messages.js create mode 100644 packages/react-docs/pages/components/form-control/error.js create mode 100644 packages/react-docs/pages/components/form-control/helper-text.js create mode 100644 packages/react-docs/pages/components/form-control/horizontal.js create mode 100644 packages/react-docs/pages/components/form-control/index.page.mdx create mode 100644 packages/react-docs/pages/components/form-control/readonly.js create mode 100644 packages/react-docs/pages/components/form-control/required.js delete mode 100644 packages/react-docs/pages/experiments/form-control/basic-setup.js delete mode 100644 packages/react-docs/pages/experiments/form-control/custom-styling.js delete mode 100644 packages/react-docs/pages/experiments/form-control/index.page.mdx create mode 100644 packages/react/src/form-control/FormCharacterCount.js create mode 100644 packages/react/src/form-control/FormControl.js create mode 100644 packages/react/src/form-control/FormErrorMessage.js create mode 100644 packages/react/src/form-control/FormHelperText.js create mode 100644 packages/react/src/form-control/FormInput.js create mode 100644 packages/react/src/form-control/FormLabel.js create mode 100644 packages/react/src/form-control/FormTextarea.js create mode 100644 packages/react/src/form-control/__tests__/FormControl.test.js create mode 100644 packages/react/src/form-control/context.js create mode 100644 packages/react/src/form-control/index.js create mode 100644 packages/react/src/form-control/styles.js create mode 100644 packages/react/src/form-control/useFormControl.js create mode 100644 packages/react/src/form-control/withFormControl.js diff --git a/.changeset/tonic-ui-form-control.md b/.changeset/tonic-ui-form-control.md new file mode 100644 index 0000000000..43def8b35d --- /dev/null +++ b/.changeset/tonic-ui-form-control.md @@ -0,0 +1,5 @@ +--- +"@tonic-ui/react": minor +--- + +feat: Add `FormControl` components with comprehensive accessibility support diff --git a/packages/react-docs/config/sidebar-routes.js b/packages/react-docs/config/sidebar-routes.js index b3b9bec244..fd837de584 100644 --- a/packages/react-docs/config/sidebar-routes.js +++ b/packages/react-docs/config/sidebar-routes.js @@ -89,7 +89,6 @@ export const routes = [ { title: 'Getting started', path: 'experiments' }, { title: 'FORM CONTROLS', heading: true }, - { title: 'FormControl', path: 'experiments/form-control' }, { title: 'ButtonBox', path: 'experiments/button-box' }, { title: 'Dropdown', path: 'experiments/dropdown' }, { title: 'DropdownBase', path: 'experiments/dropdown-base' }, @@ -222,6 +221,7 @@ export const routes = [ }, }, { title: 'CheckboxGroup', path: 'components/checkbox-group' }, + { title: 'FormControl', path: 'components/form-control' }, { title: 'Input', path: 'components/input', diff --git a/packages/react-docs/pages/components/form-control/basic.js b/packages/react-docs/pages/components/form-control/basic.js new file mode 100644 index 0000000000..464722bd16 --- /dev/null +++ b/packages/react-docs/pages/components/form-control/basic.js @@ -0,0 +1,21 @@ +import React from 'react'; +import { + FormControl, + FormLabel, + FormInput, + FormHelperText, +} from '@tonic-ui/react'; + +const App = () => { + return ( + + Username + + + Choose a unique username that others will see + + + ); +}; + +export default App; diff --git a/packages/react-docs/pages/components/form-control/character-count.js b/packages/react-docs/pages/components/form-control/character-count.js new file mode 100644 index 0000000000..4e0b87db3c --- /dev/null +++ b/packages/react-docs/pages/components/form-control/character-count.js @@ -0,0 +1,26 @@ +import React, { useState } from 'react'; +import { + FormControl, + FormLabel, + FormInput, + FormCharacterCount, +} from '@tonic-ui/react'; + +const App = () => { + const [bio, setBio] = useState(''); + const maxChars = 50; + + return ( + + Bio + setBio(e.target.value)} + /> + + + ); +}; + +export default App; diff --git a/packages/react-docs/pages/experiments/form-control/advanced-setup.js b/packages/react-docs/pages/components/form-control/complex-form.js similarity index 76% rename from packages/react-docs/pages/experiments/form-control/advanced-setup.js rename to packages/react-docs/pages/components/form-control/complex-form.js index 82960bb7d9..9bd8d7c8d1 100644 --- a/packages/react-docs/pages/experiments/form-control/advanced-setup.js +++ b/packages/react-docs/pages/components/form-control/complex-form.js @@ -1,18 +1,26 @@ import React, { useState } from 'react'; import { ensureArray } from 'ensure-type'; -import { Box, Stack, Button, Text } from '@tonic-ui/react'; import { + Box, + Stack, + Button, + Text, + Flex, FormControl, - FormInput, FormLabel, + FormInput, + FormTextarea, FormErrorMessage, FormHelperText, -} from '@/experiments/form-control'; + FormCharacterCount, +} from '@tonic-ui/react'; const App = () => { const [formData, setFormData] = useState({ email: '', password: '', + country: '', + bio: '', }); const [errors, setErrors] = useState({}); @@ -39,6 +47,19 @@ const App = () => { } return passwordErrors; } + case 'country': { + return !value ? 'Please select your experience level' : ''; + } + case 'bio': { + const bioErrors = []; + if (value.length < 10) { + bioErrors.push('Bio must be at least 10 characters'); + } + if (value.length > 200) { + bioErrors.push('Bio must not exceed 200 characters'); + } + return bioErrors; + } default: return ''; } @@ -135,6 +156,26 @@ const App = () => { + {/* Bio textarea with character validation */} + + Bio + + + + + Write a brief bio (10-200 characters) + + + + + {/* Submit Button */}