11// Named import from main entry file. This example has been configured to use Webpack's tree shaking
22// to only bundle imported components. Without this optimization, all components will be imported
33// your build process.
4- import { Alert , Button , Drawer , UsaBanner } from '@cmsgov/design-system' ;
4+ import {
5+ Alert ,
6+ Button ,
7+ Drawer ,
8+ MultiInputDateField ,
9+ TextField ,
10+ UsaBanner ,
11+ } from '@cmsgov/design-system' ;
512import { useState } from 'react' ;
613import ReactDOM from 'react-dom' ;
714
15+ function ExampleForm ( ) {
16+ const [ formData , setFormData ] = useState ( {
17+ username : '' ,
18+ password : '' ,
19+ birthdate : { day : '' , month : '' , year : '' } ,
20+ } ) ;
21+
22+ const [ errors , setErrors ] = useState ( {
23+ username : '' ,
24+ password : '' ,
25+ birthdate : '' ,
26+ } ) ;
27+
28+ const [ touched , setTouched ] = useState ( false ) ;
29+
30+ const validateInput = ( type ) => {
31+ setTouched ( true ) ;
32+ const yearNum = parseInt ( formData ?. birthdate . year , 10 ) ;
33+ const newErrors = { ...errors } ;
34+
35+ switch ( type ) {
36+ case 'username' :
37+ formData . username . length < 5
38+ ? ( newErrors . username = 'Username must be at least 5 characters' )
39+ : ( newErrors . username = '' ) ;
40+ break ;
41+ case 'password' :
42+ formData . password . length < 6
43+ ? ( newErrors . password = 'Password must be at least 6 characters' )
44+ : ( newErrors . password = '' ) ;
45+ break ;
46+ case 'year' :
47+ ! yearNum || yearNum >= new Date ( ) . getFullYear ( )
48+ ? ( newErrors . birthdate = 'Please enter a year in the past' )
49+ : ( newErrors . birthdate = '' ) ;
50+ break ;
51+ default :
52+ break ;
53+ }
54+
55+ setErrors ( newErrors ) ;
56+ } ;
57+
58+ const handleSubmit = ( e ) => {
59+ e . preventDefault ( ) ;
60+ console . log ( 'Form submitted:' , formData ) ;
61+ } ;
62+
63+ return (
64+ < form onSubmit = { handleSubmit } >
65+ < h2 > Form Example</ h2 >
66+
67+ < TextField
68+ name = "username"
69+ label = "Username"
70+ hint = "Your username can only include letters in the American-English alphabet."
71+ required
72+ minlength = { 5 }
73+ maxlength = { 100 }
74+ pattern = "^[A-Za-z]+$"
75+ defaultValue = { formData . username }
76+ onChange = { ( e ) => setFormData ( ( prev ) => ( { ...prev , username : e . target . value } ) ) }
77+ onBlur = { ( ) => validateInput ( 'username' ) }
78+ errorMessage = { touched ? errors . username : '' }
79+ errorPlacement = "bottom"
80+ />
81+
82+ < TextField
83+ name = "password"
84+ label = "Password"
85+ type = "password"
86+ required
87+ minlength = { 6 }
88+ defaultValue = { formData . password }
89+ onChange = { ( e ) => setFormData ( ( prev ) => ( { ...prev , password : e . target . value } ) ) }
90+ onBlur = { ( ) => validateInput ( 'password' ) }
91+ errorMessage = { touched ? errors . password : '' }
92+ errorPlacement = "bottom"
93+ />
94+
95+ < MultiInputDateField
96+ label = "Date of Birth"
97+ hint = "For example: 10/31/1965"
98+ monthLabel = "Month"
99+ dayLabel = "Day"
100+ yearLabel = "Year"
101+ errorMessage = { touched ? errors . birthdate : '' }
102+ errorPlacement = "bottom"
103+ monthValue = { formData . birthdate . month }
104+ dayValue = { formData . birthdate . day }
105+ yearValue = { formData . birthdate . year }
106+ onChange = { ( event , dateObject ) =>
107+ setFormData ( ( prev ) => ( { ...prev , birthdate : dateObject } ) )
108+ }
109+ onBlur = { ( ) => validateInput ( 'year' ) }
110+ dayInvalid = { ! ! errors . birthdate }
111+ monthInvalid = { ! ! errors . birthdate }
112+ yearInvalid = { ! ! errors . birthdate }
113+ />
114+
115+ < div className = "ds-u-margin-top--2" >
116+ < Button type = "submit" > Submit</ Button >
117+ </ div >
118+ </ form >
119+ ) ;
120+ }
121+
122+ const ExampleInstantValidationForm = ( ) => {
123+ const [ username , setUsername ] = useState ( '' ) ;
124+ const [ error , setError ] = useState ( false ) ;
125+
126+ const validateUsername = ( ) => {
127+ if ( username . length < 5 ) {
128+ setError ( true ) ;
129+ } else {
130+ setError ( false ) ;
131+ }
132+ } ;
133+
134+ return (
135+ < >
136+ < h2 > Instant Validation Example</ h2 >
137+ < p >
138+ Because of the way VoiceOver works (or does not), we need to test{ ' ' }
139+ < a href = "https://design.cms.gov/patterns/Forms/error-validation/#instant-validation" >
140+ instant validation
141+ </ a > { ' ' }
142+ in the following manner:
143+ </ p >
144+ < ol >
145+ < li > Have your screen reader of choice going (likely VoiceOver)</ li >
146+ < li > Tab to the input to focus it</ li >
147+ < li > Enter text that does not meet the requirement</ li >
148+ < li > Tab to the next element</ li >
149+ < li >
150+ Observe the alert being fired audibly. VoiceOver should read out the alert{ ' ' }
151+ < code > onBlur</ code >
152+ </ li >
153+ </ ol >
154+ < p >
155+ Note: there is no other focusable element after the TextField because VoiceOver will hide
156+ any alert and just announce the content in the next focusable element instead of the alert.
157+ </ p >
158+
159+ < TextField
160+ name = "username"
161+ label = "Username"
162+ requirementLabel = "Required."
163+ hint = "Must be at least 5 characters long."
164+ defaultValue = { username }
165+ onChange = { ( e ) => setUsername ( e . target . value ) }
166+ onBlur = { validateUsername }
167+ errorMessage = { error ? 'Username must be longer.' : '' }
168+ errorPlacement = "bottom"
169+ />
170+ </ >
171+ ) ;
172+ } ;
173+
8174const Example = function ( ) {
9175 const [ open , setOpen ] = useState ( false ) ;
10176 return (
@@ -21,22 +187,23 @@ const Example = function () {
21187 < h1 className = "ds-text-heading--3xl" > React-app example</ h1 >
22188 < Alert heading = "Hello world" >
23189 < p className = "ds-c-alert__text" > You did it! You’ve ran the example.</ p >
24- { open && (
25- < Drawer
26- footerTitle = "Footer Title"
27- footerBody = { < p className = "ds-text ds-u-margin--0" > Footer content</ p > }
28- heading = "Drawer Heading"
29- onCloseClick = { ( ) => setOpen ( false ) }
30- hasFocusTrap = { true }
31- >
32- Test
33- </ Drawer >
34- ) }
190+ < Drawer
191+ footerTitle = "Footer Title"
192+ footerBody = { < p className = "ds-text ds-u-margin--0" > Footer content</ p > }
193+ heading = "Drawer Heading"
194+ isOpen = { open }
195+ onCloseClick = { ( ) => setOpen ( false ) }
196+ hasFocusTrap = { true }
197+ >
198+ Test
199+ </ Drawer >
35200 < Button onClick = { ( ) => setOpen ( true ) } className = "ds-u-margin-top--2" >
36201 Learn more
37202 </ Button >
38203 </ Alert >
39204 </ div >
205+ < ExampleForm />
206+ < ExampleInstantValidationForm />
40207 </ div >
41208 </ div >
42209 ) ;
0 commit comments