From 06c624cd754b9483bd04a95e8ad58069d77f3473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Mon, 8 Jan 2024 12:58:09 +0100 Subject: [PATCH] chore(Field.Date): add FieldBlock --- .../forms/feature-fields/Date/Examples.tsx | 4 +- .../src/extensions/forms/Field/Date/Date.tsx | 100 ++++++++++++++---- .../Date/__tests__/Date.screenshot.test.ts | 27 +++++ .../forms/Field/Date/__tests__/Date.test.tsx | 89 +++++++++------- ...for-ui-have-to-match-with-a-label.snap.png | Bin 0 -> 4479 bytes ...or-ui-have-to-match-with-an-error.snap.png | Bin 0 -> 8679 bytes .../forms/Field/Date/stories/Date.stories.tsx | 27 +++++ .../forms/Field/PhoneNumber/PhoneNumber.tsx | 8 +- .../extensions/forms/hooks/useDataValue.ts | 9 +- .../dnb-eufemia/src/extensions/forms/types.ts | 12 ++- 10 files changed, 199 insertions(+), 77 deletions(-) create mode 100644 packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/Date.screenshot.test.ts create mode 100644 packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/__image_snapshots__/date-for-ui-have-to-match-with-a-label.snap.png create mode 100644 packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/__image_snapshots__/date-for-ui-have-to-match-with-an-error.snap.png create mode 100644 packages/dnb-eufemia/src/extensions/forms/Field/Date/stories/Date.stories.tsx diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/Examples.tsx index d00edfa6cc7..c99934d4a5b 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/Examples.tsx @@ -33,7 +33,7 @@ export const Label = () => { export const LabelAndValue = () => { return ( - + { export const Error = () => { return ( - + +export type Props = FieldHelpProps & + FieldProps & { + // Validation + pattern?: string + } function DateComponent(props: Props) { const sharedContext = useContext(SharedContext) + const tr = sharedContext?.translation.Forms + + const errorMessages = useMemo( + () => ({ + required: tr.dateErrorRequired, + pattern: tr.inputErrorPattern, + ...props.errorMessages, + }), + [tr, props.errorMessages] + ) + + const schema = useMemo( + () => + props.schema ?? { + type: 'string', + pattern: props.pattern, + }, + [props.schema, props.pattern] + ) + + const validateRequired = useCallback( + (value: string, { required, error }) => { + if (required && (!value || !isValid(parseISO(value)))) { + return error + } + + return undefined + }, + [] + ) + const preparedProps: Props = { ...props, + errorMessages, + schema, fromInput: ({ date }: { date: string }) => { return date }, - emptyValue: null, + validateRequired, } const { + id, className, label, + labelDescription, + labelSecondary, value, help, + info, + warning, error, + hasError, disabled, handleFocus, handleBlur, @@ -30,27 +77,38 @@ function DateComponent(props: Props) { } = useDataValue(preparedProps) return ( - {help.contents} - ) : undefined - } - on_change={handleChange} - on_reset={handleChange} - on_show={handleFocus} - on_hide={handleBlur} + error={error} {...pickSpacingProps(props)} - /> + > + {help.contents} + ) : undefined + } + on_change={handleChange} + on_reset={handleChange} + onFocus={handleFocus} + onBlur={handleBlur} + {...pickSpacingProps(props)} + /> + ) } diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/Date.screenshot.test.ts b/packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/Date.screenshot.test.ts new file mode 100644 index 00000000000..11af8ae45a2 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/Date.screenshot.test.ts @@ -0,0 +1,27 @@ +import { + makeScreenshot, + setupPageScreenshot, +} from '../../../../../core/jest/jestSetupScreenshots' + +const url = '/uilib/extensions/forms/feature-fields/Date/demos' + +describe.each(['ui'])('Date for %s', (themeName) => { + setupPageScreenshot({ + themeName, + url, + }) + + it('have to match with a label', async () => { + const screenshot = await makeScreenshot({ + selector: '[data-visual-test="date-label"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) + + it('have to match with an error', async () => { + const screenshot = await makeScreenshot({ + selector: '[data-visual-test="date-error"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) +}) diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/Date.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/Date.test.tsx index 069cafc78ec..86a6a306611 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/Date.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/Date.test.tsx @@ -1,21 +1,19 @@ import React from 'react' -import { act, render } from '@testing-library/react' -import Date, { Props } from '..' +import { render, waitFor, screen, fireEvent } from '@testing-library/react' +import Date from '..' import userEvent from '@testing-library/user-event' import { axeComponent } from '../../../../../core/jest/jestSetup' -const props: Props = {} - describe('Field.Date', () => { it('should render with props', () => { - render() + render() }) it('should show required warning', async () => { - render() + render() const datepicker = document.querySelector('.dnb-date-picker') - const inputs: Array = Array.from( + const [, , year]: Array = Array.from( datepicker.querySelectorAll('.dnb-date-picker__input') ) @@ -26,31 +24,17 @@ describe('Field.Date', () => { datepicker.querySelector('.dnb-form-status__text') ).not.toBeInTheDocument() - act(() => { - inputs[inputs.length - 1].focus() - inputs[inputs.length - 1].setSelectionRange(4, 4) - }) + expect(screen.queryByRole('alert')).not.toBeInTheDocument() - await userEvent.keyboard('{Backspace>8}') + fireEvent.focus(year) + await userEvent.type(year, '{Backspace>2}') + fireEvent.blur(year) - expect(datepicker.classList).toContain( - 'dnb-date-picker__status--error' - ) - expect( - datepicker.querySelector('.dnb-form-status__text') - ).toBeInTheDocument() - expect( - datepicker.querySelector('.dnb-form-status__text') - ).toHaveTextContent('The value is required') + expect(screen.queryByRole('alert')).toBeInTheDocument() await userEvent.keyboard('20231207') - expect(datepicker.classList).not.toContain( - 'dnb-date-picker__status--error' - ) - expect( - datepicker.querySelector('.dnb-form-status__text') - ).not.toBeInTheDocument() + expect(screen.queryByRole('alert')).not.toBeInTheDocument() await userEvent.click( document.querySelector('.dnb-input__submit-button__button') @@ -62,20 +46,47 @@ describe('Field.Date', () => { .querySelectorAll('.dnb-button--tertiary ')[0] ) - expect(datepicker.classList).toContain( - 'dnb-date-picker__status--error' - ) - expect( - datepicker.querySelector('.dnb-form-status__text') - ).toBeInTheDocument() - expect( - datepicker.querySelector('.dnb-form-status__text') - ).toHaveTextContent('The value is required') + expect(screen.queryByRole('alert')).toBeInTheDocument() }) - it('should validate with ARIA rules', async () => { - const result = render() + describe('error handling', () => { + describe('with validateInitially', () => { + it('should show error message initially', async () => { + render() + await waitFor(() => { + expect(screen.getByRole('alert')).toBeInTheDocument() + }) + }) + }) + + describe('with validateUnchanged', () => { + it('should show error message when blurring without any changes', async () => { + jest.spyOn(console, 'log').mockImplementationOnce(jest.fn()) // because of the invalid date + render( + + ) + const input = document.querySelector('input') + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + input.focus() + fireEvent.blur(input) + await waitFor(() => { + expect(screen.getByRole('alert')).toBeInTheDocument() + }) + }) + }) + }) + + describe('ARIA', () => { + it('should validate with ARIA rules', async () => { + const result = render( + + ) - expect(await axeComponent(result)).toHaveNoViolations() + expect(await axeComponent(result)).toHaveNoViolations() + }) }) }) diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/__image_snapshots__/date-for-ui-have-to-match-with-a-label.snap.png b/packages/dnb-eufemia/src/extensions/forms/Field/Date/__tests__/__image_snapshots__/date-for-ui-have-to-match-with-a-label.snap.png new file mode 100644 index 0000000000000000000000000000000000000000..b8f96b04e36d7db451cfe80b64af6fcfcc5d2e82 GIT binary patch literal 4479 zcmb_gXFMC;_fABNn5`IJwMy-+Xi+OREov0CV#MBxsue+6n<{Ek)GAV=wustWtM;nd z5~HP2`3Kf7W&QfQVDR56J}DasgE zgG@2PWJbk6aEROcr{tAzGwKpe5QH%Gu#wgTSHq&6NCw4MWVx}IqpT%USZM@ijqiSw zHw3rYbIGt8V#=H<0}3I5y(<5=B0ds=bAGU$Y+g@Fi=ve;w>q29{47%lzbC&lQ55CT`t!Y%<3Nf}VsG=>ZY?$gMEAgOOm3S^ z?pF_kLqlQHogyH(4W&rQ#;9=%3_4f7zThd=O3rZo`kwfPP;R0~XM^{OjeO9)NL6iT zt7(S6!+zU=Qpwb4K*Mx>4sc$!&p1H!ac6lP4(Izf6Dfbnq&o23MUe>42W9G8-pE zTAFmc2*^?jYqjzpl?il)v+4W%Wcl37Y%5aRc~<%0WYNFQ=Q}K9ymhxm*wVYB(JJ7Z zsRl5m@;HTP#6*!AqC1+>Z{D?RkT1uBTO4~)_b^*}5Os8MG%gN2cdzYmQf>OxcWc-t zWRF)bQ!ITjjep^K`NeMa^pc9w)nA8&Y4j~GmrFQP?`Jw?k(IEMyoCSmYRb;L6dPh9 zh*kuuE`=VA8np#_%I%aFdn{vQRltmt&!PSoTn z5q`RvIc#j8$h+Q`m?qk^XtXt!C(N!8*g(}{P@p916LHXSvhhC0t4>wQQ&NfMEfqEa zLKgUe|Jr@>oyMSmf#M$%_f1NVJVZZ)BlHl*B%seEZX?2<_w4mlwWV2K61#hZQ|blL zz(CxG_TNK2JKL;#60L3SO|DGRUhpmH_OkZai6?7bS(e8YN!-Mj!e-DfQp48$T%(M-6 zt-h1-4dLtK08jw*Nu5iD{`H~assT@h?6;Iz17325Kd;#Jxy=#0aU+DiF#dHSCCT|z zg|Vklz-m`y-JDZS`p)`mJ+ahm8TSm*2LG**RAMa5_5LM>$^d)N;^U!BBTN}J-u$rb zpwn_-5CpaQ&&Ut38N+OY=@fzY_lNx~&3rOJE2gp18CH$pk63PWH*iG((B=cz&tZFtqT8`FMq#*K*SWe<@I7D9c-u~8)qLTfjWmN# z*m~_nSE(yy;Qr6D0j6$D>g5Vf7~{gm%4k|Fgev2hn7wt_)W+G6gO2Kmq+O{Hg2%0AvhLF(ZV&+GH{Ux(k}08HKTz(g(uvyfB8Lj}fhMH5fms}aVj&D!l~mBlXo zn1z7tydYg-Xz%&oY_2Pxe97=7T?D_}5i&gQUd!HUX+q6b8CV;OQq(r}IABX(4__n} zGNuwFnO@MvuYOBCZ8Z9;UY9Orv{DZYi8d>4)Ie<_Q-B;<^y*0yYA3rk1k zkjzG1ZP`+*`10RxpnQ!O&X`lG2j}c_%&RoZ8kTUL+}i-z@Na-gAApFb_=E>LT3)tO|wuguL8ivxYLFFIFv^^DcYp({Tt zrz}qi%~n-g^dMHKg1o>oGjkC!0*^`gq#IU@)+rtn76fP%Z}+A9t&& z&ImBKL*_XC^=W;3H?tJY%FA3~H&EX9mQ8+INm!dfexg!3gB`&6;zv;iD32Rt>G4rx zbvDb;JjjB&ZNIfjlmd__UGfeoQ`sd(7R-=<0MNorwaUoF+r^RlIC9Pg=^&r4DwR+( zt_T#E<*BE30SjHI3?^Tdb(2(#&Af*zDn^Hf+w^V!YRU!&MNw{7JSdpqI;Nd+M@c&i zAQ{iVZE^kd?XU}{8#ef8%YrsIKZZ~bRHDXv(l9JMP&=9lmP`Exbx?Vq9gK@RM>Tpo zoCSG}nES)#yV-R0r5r6xIq-&NYGzu0sF6L<2#^93rO@tD(8|(NUac3TGVFLbRN$3S zSAtT}g2G0Mn#il$%H4in#4bUA{!{%TAWL88-!&?|b@ChwD~vyry1R?+7HF zkF&5Bkq6au%(i$B{f2oWS*}BWB7vpZRd!7BVh`!=(nf2ntuU7zl%vbK zMHNcQ9(GCgv+>X)-yM6vUk=gAyL3VLix~nzA1qRY=}RbBA_XN5TY`N@n^GngKe`*@ zKv&wNI}iS{0|lUA%}TzwvHfr3xuiy3{Y{@}X8}`tLMB_tlbgEUwk*o48{Rb{V*ro< zk}CPVI6euFY*y9(LG|QrK@DApaWE-|?tt?;Kn&s%HJ|8bI|~zL#hlYY;>8$6>mu2e z-{^p$rxepSW+Gt}ohrR%#hNtd)b!a-kcNd*q~gNeuY9XcP2G)My@g}Y)fl}m%yA+r z<&Y3bFI$W5fC<9)FK>98HGCY6^mZ_xY2#@K2+at!gpUInn5%E2YyJwb+!&@tETjqF zg3MbZshKBJ5@T|&s{T~h*?Mq*rpbpnl9n_}WT8UTxU{Ja1u&mA6B@6l+YcM^gY(k6 z`|tM)BY@*+bXs0Hw6$xQ*`KW&FmjHi&-kZ%h&P^i_Hki$goW9|}qd>=(u z3qO%Q>F11?-D4hB`$Ky%N$mNPH?P;L9Dqd?N!)naDOFY(w<$>GSH5uN=}+|VvYVW9 zcdSz5#aagfbuM5Tl$lX5T&D{4FbbK@k}04)!MJ$x13bN2h#wWMZMS!TMw^pX?KdB- zqw*e(q@*nI)$eii1>E5&CMs5|=FUuVT!{HSPZ`N4c<&-`F-5}eB>)bk9)MPQq!itj zFmlK#Hf8yRB_**uS}P$p-418C^zODRu}b*L9MvfIFE5GK8T^4R&sxT)pih` zF4#vK(GuUq)Wm4U5(De=y16>w4C`!GKxo`wOPj{*!8dXTtx~ zdEYLiKXLFlcF@dNGU`o9!ThsJ_0hf*-OX^4@rO9h{&)UHR1?24qewGR&P8kFKtWVI zsvvPCscIdRmuA|hR{3U|`X+LWgh%$!z0FV5)iUtm;vF-AZf`YQK44a^2N$FliUec0~KzTNFQHAklq9|nND7)wB|lyHoMtQjJ?o>X7v;~8G+ zSffRkt5U6RM?H%SHjdR1K; zt>zu;tkFD~=a690TBsqHsP22g%^_8y3U_!xcIn5CCrl_+;d5)$EmB{`D|Hi4YAf%z_HzOeN@lOpF@B2L1sTlDIsJFbNj@B;5;I##DK zqrRNldZ#}-yfVYuk2JppE_+e4i%KI8WbIH7jfZy1U(D>nqydNk;Un5{m!oQ!(a1zB z(%M;H#*msg;WK|0`FWS`9g!d4b|a4Eszh$U{w;4mkO=`ADzNRGT#a^NOn^1svcEMXx6h@&mOI6qV; zVyaJ-Cqko$0KF1=X9=#Aky6pQUXeMHxAv6bc6H|(jg(85~6e{A$7+0 z{m#$ty3UXDXFqGtUh%BFpZlH_rK%!_gH48whK7ctATOM-=?OAVhOpj|tD+GCLIM#%Dj*qgJXQGA%X~01@KH*f13d>PI*>L$HE0s>@5`9@ z9_kG&2vrr3_!Nptm1X6-p?(K_d;Q-s`;0%yRY8OR9kA`V_+A()$3Oe8%>QOvWyH}V zgPK13{|B4z4gw-xUte!0?B1Rr)4umdDEa#OX7B`Dt$8r8u_S@xO?~VYSpde{lPrIPUE>6sE{ZqFtIo3jVOzA=UF(rev>=`ff$XQqwrXzLEfFm z_@4G+S~>OmZ?AXxlxS6IyKct`j0OVGS6f~>EOIL-D17`#Cx=mNVngTU<>h*N^jYcZ zcy0H5j0;zhYPpPdlMASbA%0p>S%WBjoi)n)Rb-nhm-A$!$u7zL=ad8nE+zYdk%y7$ z-zixlKmcm2RpF-|&wkyj1&a?4=jZ2(Gpb}WZ}{IXixorvcF^h}37muCcPGeui9@$4mMoMwp zDfr&bCo?Fe4x?(dXMZXh!Qh^84e{9U`|Nu$A#ncdQ=X!VN_^WV??3x9onCHU*WV+G zEU|xCNIwPA(1qCK`Dw3vA8`Cy`CMCEQZlkN5Mwo&{ZiITE|yeH8e_xnDo1UU@pYzr z7QdtA?d1{N2+awFkgNC_n=k@<@GB>ZzkVc z$oJ0QGfL{8os53Xl*HKG?pM~Prl*s7J>FmK24PZiTYjrC68`?@DKR^@U-xO>Q$}4O z@87mJr~SBAqsc7iwE_1^+SU3DUEY7-(gN1wqy)P6N1r!#21ra^NGP`q?9B#RHw7?EW*cO@NNBpE-|dtjB&sYut3D#nJU_faG}zrGS&+w61ooF~@?j zPxbGU4HKvC8|zujlspS+tdIGZCmUR|pWbkdR@shIdlNEd=FwYPng+Gne5JfWEqDfA z-r1@K!;EV7P#FwOmzu*5UdL^FvB78htoLCg#x4p@MnxZJtfCpbUijef`rrbn}_?M-+RH#jnfEuX|=FL{~0qnMizXv#aj_aLzr7Ai4<0WeFNNb|# zT$`sb*c33G$0FS24Uj0R2B_hrS&W*Z)$190^uLQHet)#ELhTw7D1_&AF;>NIV}Rs)edg zeH&hTIj-N-jtwBgsK&;|i4)TI z$e)EV%D#a$y(h>=r{G|wJl}@ikBVY0o8kn$HY>_ZoCYo9%=C?bRV~{A;zeR~#PJ6? zc-OEeC`FWyZ%&uf_ZdE}Mm9O701oQhnkXjw73`A5yG`8W{&QYAi~luh?^q9x^w26K zcafD^o+Hr~jN@j{GuGXc6rMLLFT-`Gq&*)vYg1Y$mhqlG7CjQX1qED6JW+ z!h<><b>a^kEjzM^p@b-gO$|n?!)(uf9qhDu^O$Et}S!J`=DB;KJ;vR=(&sy z_SBlT=`kSR)eRSf6+~4r{wVwxzix2mtMeObmr%W$@YjoZaZIq*?p(&8&bw`)S zsClqPreiC+J<4E9V^t2hG2726!t~^!#+if|3c9kCnr{zKcn!yQy z8%}-mpK=keO#E5Ouf6N=CqX@X!M#+?mrz%b_V>WFqEoLA!yu|!ioOi|=cih4wS3pt zp(c41AQAv_*c5jXHa_sq#7@u+n)myONAa?^E*|(Lj8OClFWbKPp<%3;_*r6k6y2_7 za73VqWgTGS&%#g;wU$eKprSh+loL)}fjM83LY@sxi4U>*pG+8X0alyh&}Qu5t6%yQAEdMG&d$His&ef6<9(IeTz!}xUgBY-X>&{=#7Rj!!$!blLy-T z)`H>YOMak?uXc`X@cc01w}YqfW%>f^@!|8EqVwi}JhFutoPX1T*D2YfV?~eWAeHlU zPnMxYI#x4eZ3K{P@l|fpOANbp z5H%V{cNZnB(Ph`Hx2c>dz7E+!t^Qq_GCZmDnuay{x_>9M3~v3eCC=4I8jgoQc7M=1 z{EJTNAzA@HlEVqB7@}Z`A~B?X$CRtv@cOWCB@W^0Gws|vnS&nzg!2i#N9dBs^^xSC zj<44uQ+*FHtSST^k~NBj6z7KWNn|3H^B%%!h1&(JSA?zr(UOjxXCpSS&bFE<6+t9^!u=>QGQXxS*6+3{RdK=L%Y z`#hJFIG`8vg%v|czd_5ekVAEK)N*|4dkNs^uMLw+$>}T(J@qC;5{SQmR>LUSqVkDM z(J8F4o+B+)`aIx4<9E72J%cZ%Vt2{o6;d55$>H9!Y2FL(-gv|r3R>ruVBN_(y^~9gR%5O2oJOdhTCG{spMUDU z%rtT4ylp?&0V`h&GGE)L(J}9}kg@5!{;h%ghcc(Z?%D-8+_RPs>lhyNpk%50P|@oy z*DI$N_7`y{7?B!eWb5xNj;E737v;E|9F2~oU=hD*9}G*WTr*N$cq_HhJ*cS2UWGX7 zoKJ$Qm)p(Fc?22Qg{%Tyi)3u|2#;(2d_$TLNILU_liA?9ZpUrv+oNm~p=*3L@oLH# zcZf}Nl8eu;I#=J zr{-=-DDThTGh3NFcHb6b$dBbrRqG{;PV-6lzK?ju@eW3mmWpk`DJrTTmJ`p=D2*s0HPFs6hGJr-WY!z$<-;kupdQD6C}+ zk1)b0j@-}j3v8cyb6-7Bi@*C^II{MB=EsCvhTmeu`JbjlX5V$fY?4GY<+UgQ%bv|* zwwQ`056RCF5_$~i&el#YwJZXbE~J=7RWjHg`)s{|zJ^M!(8T2sZ~%rpZHp{ULgaLx zJZgW{0R~wPYFi~MsMYg;Vj_!{;$f?3KzZ?UFWLL*{N11wHUZ6)oQor%ZauX+d_crrf3Zp0E=AGW~Ppu>clILQioBQeX^@$PYu~uZceluzKc=C?uW%@g|iq=KX5=+%?qP48) zG&cBfNCR)&JP5m&A|%y8mOBn0v(4D^*z~3NoKZRPFZhezjmN{p`D~8p%ZooNAH&~9 zCl|eXZ`ZuX0pr#heK4e$)shCO3QB<%J2n+tXw!%4i|L4T+um(IB0-$u#FvtUhf5a8 z^D3aMv)GP010EQ5PH1u=8x7tRj=4kOGxf3$Bl|N04&vs}**P;lMU3?_jhk-9L(86g z0zVt<&qK%3WK|F8JnAoBF5T6T@h|GC*EwcYMQ~AsI5zTedsU-tIkn=vuWK7@TbVc{ z`!Y!JV=3lHq9VJi;GbLHhK?nL-<{1@43S%CI~H9HV_2FwqgRJ7w|>I^ zyBrmZE+CPymRg61?V;V|-!!7ewAQQ;jttaY+GYz^{g}CXY2jon1X1>|;6lfmaU=(> zF~gFbWv;A$n=zrYS`vTQX(QN+;8gB#?7lK?naILwr7{t~ms^WJ#L@5VLd}EfkD-u0BI=1B`$ZIa%b{>?$*(!yA6e4J*jUBzoSLhFveN=P^p_`E(y0h zq9+H3CPAKUN;Q!?jLo0h_xfG*nYa`q%K!JwU#i4(ntfNOt><%@ z2ayMr&3&R*o!7~~VgdS7qtSAI@_P~pMfs|I{k`6h^&fSh1bH5Ib!fEs!9H$Sss`>4 z*%Ms@+l(v0AX0!nrr@g;le0XC%eR()B3>v-(Bg9QJta!M+ko1mM~h$o^ZHSE3Xd6x z7Ziw)knY5w2&IMkgOVYAkI&pe(c;q7s%FE1e@H)zUS*eaE1Q{3w=qWzkC(zn5cXj;{2 zyhv|&zE5J*%t006P|-XIQ9NScT;8FI!oxh1!dys8zR<~W*i$9HIfXQ1);}k0a-TwI zRSWPU?csuT=3$l_o<%xb>GkM?P))+1=xA}xB#FtMpoWvZye{w0SRDGGBg6ZMJb(Qk zgw{OSAqstaMR^k$bONST6Pf%e({!?}6em(;T+?+D=xuE}T(!9)?LR+@Jtprly+%ya z$tl|Rhl{Qqj19yfZ&kwAd}_zDc=}@-s~Qd$OiHZd$Xp3eU6`x}3aczCz*jOHu$_RL z5{K^FWBil4^&F4Pja|{dwoynv&ucRUk;l70C}bZ7}~ncA?qywkQ<>ukF@<=D4i6<;R~XvUC?;F8fJyuwrTF=yQk=CZ*q{-VI=W zbR|ByR3KGC6_Yq{P)Y#`1+|E+mp%t%OcA-Whi@D?@fFpES&~i&d zm(S zxD?)78ZmpU1|S_qCQ=sag2oW*q}wUrK1K@%cLK`iyt3BRQjO*lWD2iUQYCbMeHFb> zcDtd;w&e5QlGZ!#$3-A*5}Je#*ndscNa%~jH(}qxS?Bt6vXC&Xw z>9F6G0uD1d(*g30@N1|2rN+hTEPm(I`0F(d7dycb51Lmilvbn9s6^Qt^QU6pRec>r zyoDC~Io^3nvD2fs!xxNvs&8(;={4?fi7fastCT3FSYkhPZEh!6(VNp_ot?@4N;UO$ z?)qB+8#{3+AjK6d)#Giutf(wD&Jh}nbB(5W`VbfD4vDP8WS1P2H_nC2toOL_T$-j~ zs_l3T?9ah>!$`(!^TPVR2J*?z&`>7dQhG^$=&dd?kcr{IBxWSKK9Au_-Nv)5i92~X z8Q56FRVaREkBa@5vz$kzh82xG*S#d!?7l75&#=E*>l731e<5-}^>|13R8=-nHxYb{ z(c-Az3_tOGYn2%Io@u@Nvtp@{K7T}Nbntx^R&{z3pd?IttbYY)M^<%N8{ zN|nXPc7F_Oa4FPHg4=A%W`x?fB6>e7rHyhcuZN|ZHk-aCBqU7F8x@?LB!G3I4bVxb z(wMuH4FIy?11XCs?lQ28i#qSw{cF!@+KkY2OpeI;^i$32Hu)8};0+D;W4HwnHns4H zR)f8P6!(enK`d{qmZW;YmIlA`5^dVmPa53S_i2W!rExbo0zBx#u$}GVst?pGtQSw} zN|}#yehmd0(%O<2)Z9 zow*Etx&zS#*L<;qsY={hz?rlwt&{h#)4L8=Qbb;v1+x!f52pmYsEV>|g=P297p2vW z#`k_H9T_K@R!19@njsV5w67r=-}8^0{>{$ruQ*f0qT(jVD{L^|^Qw+5uGX?AzO@g@ z=bZaacv9@v!NN?h*gk+0!_{tWYFVM5Hwhx&le5V$0w9TW(%n0#O%sqv*EUUiq!6~( zRO6+WMr`_aKJV~FI%Gh;*iSl)Dz?hg4|O!RRD9o&y083Mn4eVkEWC*~T zXfxz)O4M0BDk`k<&4W!|%CbhCdR#XMszitW_SA$!k2a6XnBDNK420W0oG&;Mcy+vi zG$Ho?Tkd-^Auy;+HIEFK#+hEyUo zoG4!mTeAHO_Qi4Y7m<(uAruw=OF462(-xZpy`B9MGN}ZX42ZnBpsZ}l!xepgvU?9+V?n-1O zlw3)|5xoZ$-aOf#t3LP$Y40)Yh8#hH&XYu#o483|>|on5g%C64Ir#6XWTO^1F*)xo zP49c#jc#cIf`TuG15p%M$<`tv^+`~TmuJ`8CGgdz4zu@dbm;gy)VeN{S^+@z!Yi}0 zL^O7Dl80W&8#I~W_jtB$#?NClE%Jv0 zty$4cwogSMixY$PV;k?9T?cWpEql;lSY~MBS{Rj-=)VRA24ET+DG*62WU#|B?eGUK zZBcB6zn@g#RmdO%EG7ElN?i@mWFx3Wn*v+6{*R@vV~n_NkZ(hdJjsq3G?aN_ptjo_R?6ESwsKAcq}VbOw*kt-Ov-KO<%p^{`M00UGqp<(4b7i)m-hrDXLS zlouz6J$An1Z7KSJIU8(vlwsaXU}2XEs2!6WkN7(I&D=YQk^v~%*q6wieObopYa8Y%Y#=J}TBl$zieUhLvQJDZqGPdXp; zN!0H~c*0M1HVOL*aP4~18-n>dlbf>AtSk&)+Mc*#CR;Lpzx%Js%^Vf#ekudA9>FWXo9c`${T3QfVPBbml z7G%r7(YLxEPLy{mMmk(Vlp0n=YDBJctd>7y>deJD+%v}_8w9N0pO%YBiHB%rEyvS} zgVOb_4tIx4{CRw&e>UWVrw@TaDoVHVY`Q;Xp1@jf&Hi}ipe_d#{<{mn%mU4Sa^2H6frIfOFi0Xfsiuq};er(LwE*|95)W8ogQuwL$sdpTT(n!BRd5d@N?d}pS{u4#Kz!dY#Nwm;u)L&}SzYnH+S%3vn z;v>}Vx^Lg0$P8V+C3L!04)YT@^cjk~O_^1{VgcgC{bN2zN{Vb+s1T}`k`c|9#sB{+ ce!zV*z+qA? { + update('2023-01-18') + }, []) + + return ( + { + console.log('onChange', value) + update(value) + }} + /> + ) +} diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx index f8bfe087367..70918cbed29 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx @@ -6,7 +6,7 @@ import countries, { CountryType } from '../../constants/countries' import StringComponent, { Props as InputProps } from '../String' import { useDataValue } from '../../hooks' import FieldBlock from '../../FieldBlock' -import { FieldHelpProps, FieldProps, FormError } from '../../types' +import { FieldHelpProps, FieldProps } from '../../types' import { pickSpacingProps } from '../../../../components/flex/utils' import SharedContext from '../../../../shared/Context' import { @@ -82,11 +82,7 @@ function PhoneNumber(props: Props) { ) const validateRequired = useCallback( - (value: string, { required, isChanged }) => { - const error = new FormError('The value is required', { - validationRule: 'required', - }) - + (value: string, { required, isChanged, error }) => { if (required) { const [countryCode, phoneNumber] = splitValue(value) diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts index 59e11c486a9..ef24e7f5a45 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts @@ -58,14 +58,12 @@ export default function useDataValue< toEvent = (value: Value) => value, transformValue = (value: Value) => value, fromExternal = (value: Value) => value, - validateRequired = (value: Value, { emptyValue, required }) => { + validateRequired = (value: Value, { emptyValue, required, error }) => { const res = required && (value === emptyValue || (typeof emptyValue === 'undefined' && value === '')) - ? new FormError('The value is required', { - validationRule: 'required', - }) + ? error : undefined return res }, @@ -289,6 +287,9 @@ export default function useDataValue< emptyValue, required, isChanged: changedRef.current, + error: new FormError('The value is required', { + validationRule: 'required', + }), } ) if (requiredError instanceof Error) { diff --git a/packages/dnb-eufemia/src/extensions/forms/types.ts b/packages/dnb-eufemia/src/extensions/forms/types.ts index 9a82e3ab387..6c62e20a82d 100644 --- a/packages/dnb-eufemia/src/extensions/forms/types.ts +++ b/packages/dnb-eufemia/src/extensions/forms/types.ts @@ -72,7 +72,7 @@ export function omitDataValueReadProps( export interface DataValueWriteProps< Value = unknown, - EmptyValue = undefined | string | number, + EmptyValue = undefined | string, > { emptyValue?: EmptyValue onFocus?: (value: Value | EmptyValue) => void @@ -107,7 +107,7 @@ export function omitDataValueWriteProps( export type DataValueReadWriteProps< Value = unknown, - EmptyValue = undefined | string | number, + EmptyValue = undefined | string, > = DataValueReadProps & DataValueWriteProps export function pickDataValueReadWriteProps< @@ -147,14 +147,14 @@ export type DataValueReadComponentProps = ComponentProps & export type DataValueReadWriteComponentProps< Value = unknown, - EmptyValue = undefined | string | number, + EmptyValue = undefined | string, > = ComponentProps & DataValueReadProps & DataValueWriteProps export interface FieldProps< Value = unknown, - EmptyValue = undefined | string | number, + EmptyValue = undefined | string, ErrorMessages extends { required?: string } = DefaultErrorMessages, > extends DataValueReadWriteComponentProps { /** ID added to the actual field component, and linked to the label via for-attribute */ @@ -218,10 +218,12 @@ export interface FieldProps< emptyValue, required, isChanged, + error, }: { - emptyValue: undefined | string | number + emptyValue: EmptyValue required: boolean isChanged: boolean + error: FormError | undefined } ) => FormError | undefined }