g?tI{ujOGy+t_8Cpc)-}6I@T9Ae9jEo~UcXdZR(>@D*RToMz
z7mqRy^NZ&jcC$&)88rZP?&l<~$iR
z+OW_#pkedcT=JGu*fz^FRR9E8UgJqMx089Brc^S%^*dy_{U$Uro^E8}l+{+G6D1eN
zXgzoV`cakT_sIH@_SWEl1pmuCku%b<$8C!W^0VQyT;?(ELOP?Q&=bxW79{2q>t>yw
zitb^h$eCIVF{8c%BWAyVIul6<`fx{yg&~MFCq34#{~91hdo_zUl0H-=oQq|e(*lAh
zJ}-qf>qsu8b0r9TfDVq&VUoC|1R|4X!hNr06F^V;7!+8lvaFZiFDTxGB#p-wZ8oll
zRyDqpf~1j1mg#C8A*apw@EYM!IzpxnhPTL&FNzHfp^bxe6}yB`M8+3Zq)m(QZJr7b
zyS0l1Pd>g*655{sJ|>ldJWBFioSD1;q4Iy+R+{#oi}qjPP|2|(hfJokRA-vzS#knc
z_x3g*Ncs@TqStB~mOHW8_gU?%^aWa{wubSrR1!@@jHe(oUjkU^bK?Za`s~9m{<-}U
zY88*6h$w#fASqs@=}7XtqynoHXijKPvV1f*^nCeO9!jYvcFLrt>I``}VUb
zP4Y3~6y&<%kr2drRWP*v)GWAqHR9nZ;iWAPs4=K~m60X<(4(|T3W6Han3+Z19(?7^
z?R;_H#POSl?b&@A6n|5ol#tTQgzxvQF#_nTuV6~3RFU;^zZV6>_$RPCG9>U3W3Vqx
zCnW?lD^-z(V~lq#qnU6DYdinDl|+HRndMImpzN`NM@(u{zf$ZyCFleV>&Qkd`_i>P
z)?na>)FSg3I$hzl7RpvhNSRh$j5W4U{My9qB?$HOOJlyGY{N?@#T?xq5;cKquC!gY
zK7)I0>~~4;ADn}}&a!p$peI*+~FFEp1WUwB_S;qf2%tYg=*#W`PV|vIw22JkYV?nuPlT
z?^4zWwSjum+OE1r!OG7CQ1V=xc%;ASp&
zsU(1y1~`y(qP8vSHrAPwZ(^&64aSB>I?SKimcU{<2;erSaYEa_vKoAP3B}sHVD0~C
ze~XF;s@10+(fM2sk~nxS<^1JyD}tq1hOn
z#3R8J)W%QV{c{Ec3ykT!;jBKBymKv)quavfg9<2R(I3TX$-|>`Kyhm))jzBBj^c{C
ziKUyA?@B3zJ47{Ig&?8U1bT}Yuc3sR2Nk-64*2z7A@LD{ewg&ID#OOJO7SpH
zY4(Mw9Kwc_5iHugx#83k$r35I-TY!q{hHl{Ct%&)y$*Y?%uz|(D*4!L*n92SDQ){mae*+tr{aSUhh1DR9Tn8h?^
zlB+Ilf@sTeFmB!mUB0B*ZobAb1l}JCcuq|LMioc5uwI*-j8W-Y291I$SHHTmyKs*k
zo&&Ga1$tywQk9)^1Z#@HKwC(iHC&S_*07CY%wX}aQq{}9O9YUCo4T~?Z1SyZ
zsg_GT7X8#HR%A~8eZV$(^OX)lOIs+8_w@-cxovU-s9Pbr3_FryHwbZ+a+C%PRpZVT
z++MOM2%)JxmV9-L^)B#W&O)gFJ|#DMUQ5$GT%Pwlxe-tBTXsGcHkV&NU1Y)*-97r-
zep+kmqc3zX{n5NE;v5$dB>nT1b+g9hG7Gykx7h8Uu^z-s>A&6Ee*(c%#I^Xlw`vL>
zmwMVuE}}!KzNgx%?Yfb7k5@y`lQ$YRBU*Vg9y|lqCvJ?T&&?njVMp;^T*EAec+q*~
z$vD!nN(B_(5~UST=nq^NufhLc(NeQA)
z$(TLBX8#95;h5G*iE3;qn$GE9P>lV$U;s;^j|gZomAwkYxjEllbcJ!3fYFVZNv->k
z;v5j^69L!$g%`>vLq;bd$Ib5b&xOuFsOeBd+@X^v#zt(RYb2+g{>2wrL=iRU=ar>m
z!S&N+_Cf0dZK=ahGw8)_d$i+UQV>F?srB@@_sE~l$`eb+B%6(p0E$#%j3u|UFgALG
z4*%3kfhE!V5Q4rg3Z$yHpDr_0n66=uuX$}>mgWwZ_=)30cE+oW`|CER^^?_SIMHzi
z{J?bUA!F!jo)bKwEs6%Qo+7Y5vTJIa!rX`0Cs3tG{^2LzMGGEK@3mN*$WhNJ)*3>v
z+gE0Tfez++Nf}4IIH8_LaGDT-V)Y}XAI0_yK0q^Qk5`9zW3RELLYAD1`;N%_1cVF<%F5pFHk4
z7$v}k$#V5SZxBMLFuQ}HyrS!j7g7E3N2`qF@vC4Awv<#A1?x%Dv(oHgGFG41`z%(x!lYedifsUY(%v{4<`E2R+SR)D|1Xs@qWK|_yC(C-U+yhfPtX7
zBqw&Ic<>Ts_bIiTD;%*5EEjmq>NgQBkXUgGreNo{G4t9hU>7(&kchYJLCM~5@lFc4
zdbBSYsQrXN)R;N31*q1O8*!j32!0|gA>%dhX>v0*4}6axg$oJ61Ghkd2Lfb3Oz=PO
zoAcn22G9Rl<^TQW|JoP@ckq9G^M7{xKUJjR`Y=tLY}eUQ1FagyN##c8WNM2eQdMid
z{UibJimOgZM_H;}c85Qr*1qR`6N!m^k?1KSBOoXwZ*R1%k2!8UYSgN+HnP;J*zClP
zQLGH~H66XBH5!jO(kgf@lYH3civkliR8`ldR`37ZU1rgA7gJRBMJG$I?52k24}qTuEi=4Zq=*Pd{b~>_sFQ+dJ%b!=SoCjRwWJ89Xu#xRa8vNEj9V-{y-CQ44m>GA%?4Zxu
z-1XbPaVNxG(&oN21WBhaja0GkiM$#dTKKAq-7>lo8#6s#64_WO5@58~_F=QJbsJ2>
z)rbJF=&|bCCK6r;biC1`q*&8t8Y6>wOyhBYMtv6LLQ@;%q~CL%&XA+s*vn^O8f1}S
z^|kTGxF-+m{AhOjQ0wnNSk&QkC3wU(=QA%Qrihqv!c^R9G3Mhn9;&6{vowga>sWkc
z`6~E1PkR6*I)T1Z#e;t!$FYoz2`HSgy1Hf~f&AqlqF^eT1&_6Q=k0!{nL~4%#y}4J
z5W!3#s`Eg%o@C@-A09fcy_vWFPG;JkGLFoq{RM>@gQrNUlqA1Uzc3BHR+!>eB;_@<
z+s_M|zjlUscSAL2K2zI|n^M4iw1_@aZzPvryCzRpq>`nVCz&dK@>qP!$IPf9zo?M_
z=ubpGLsdont%bngYn|fEdQp;M3>6E9@B|wEi
zuymaK;#KWUNm7LuOG-|v{oA;sVV0Sra+~^LeNiFN{`)NO65@2B7gKM5sj}eklu#Bi
zb8z^cA#f4qUfX^5|XelaA7;zmxj@<31b4
zWPk_KB13P7YD>3{cp5di#qO5xE?VM>(-ruTfrovbo4uf$V39+n$g}St5VJl|T39}q
zmQAa7^7Pc(naqoPAPtJ=402D2n*@bD*kEFe`ADw7rEIww<$O3=#w|JnCQi$#&j6M_
zA^8|YMIlvrn8~{pMcxR{G{Re1u;2ENQ)2^6Lq>)e6~1Dl`|Fy6i6AVE*m}<(6%$w`
zcK6;qK}Y@7*Y}rcTs66jo51(=p_`bQmV)V39(r12qZ=^_jQvT9qpPshB-1>N#-T+VA;xje+NX(+n
z2F+!-2BmKKTmMZdjLwd|6BA6l%VKwhg3jJdr);Ek{J&mKiFbSh*yHH0e}0Z1=_eRr
zI$-ktFuXoccD#G5o{C{p9AWqG?@7ulXhqjkN#FjX!c(W1P_+~V>?Hz
z9%)uaaULE_NEv&=VL0_w@*`esEiL{Bzl4C#>j{(lXfmS4ha3tzP>FZH(MCh0jE!jb16E}+(4
z{;4$S?up|^M6dLfiH5QIkY*Q~fLH7IDar`LWuC2au&a4M^L`^vUMLw{`RRgV$EHsb0(g73d(B!Jmo
ztSpHR8P2VuEzoxpwl2eb8^ZpOm_Fzw4XGlfGmIlFSVLeK-zXJiaDGbu#X5>*s
z)58yu<2rql=h3J8%k|!g8Rb3q&oUvy?pv--*iO>-1`=4co%TYUZW}y05iiIXT$b&|Ye=K*uRo@VLlGS*;s!)^aOm-2J7LElgcYL^=hcBJ1+Yem7z
zx%RrQT*-EnhcP&yql#P4)iwA*aX2e-x;C`#GR&poAd`*UU<>XtK~LwU4a41*UE*O9
zIl973a%l*vE#CcAC_IyBpBu0gQe_%wWtn^G`$5WNTLK8CO)@{wVQoe{c%y?f~Y4j6S3C0WI99gN8O{+9z`2MuA>}O`u~0
z@HSK9$GAmA@$ig5lOW|J%|S+iZ;$K@KMKgT?%%jrx<431I~7Qh!M-ZcZx0dDmTX>nbvK!|
z{Zv>p;%yoUzMLnrwTU*jO}Jjej+zW+1`WS{oZ0*AzI6K{9O1T6+eZ&sEK_K3>&YLE
z?d0s$0&W&ReA{#z$LQ&-b{9KxkifEJaz73OQlR!Q^FjszOy~yi0pBwfhn_{P3~5{KKHm6-<8h$a
zaA5~9Jl#{5adRk#-VlFP2kK;iQ>^y{Q9ylzIb)VOM1uS%T-{M|6Tz0S;zXL43PiPddbD;|S)gJ>U23YcSEv~D4i;X&lMjR35TQ*~^*y`0A~GmY
zHr?lyrU#c*m)GL#+I`??-dyOq;W~32&-y-8yqi4GwjsrKrMsIQu>n_LNrt^zA4lTvISnV
z`G4rHk}tius|t(dOI*g~Kk(aVkN219iyB9xHuvr|DVTMk
zBOu}&vVEg3W0{)9;41RJnBg$Hc})BAlDI84057+6BPI1f5l1Od5q35Kle*Tps6H-O
za`Wq=mYRug`_5E6Kb}H0p;*5eWwAE0SaT4)Z&p@dVGbL<1)a*H>C0gn501^oj&a(P
z=HFroLhC%h*WDFimrWjc&U$F^IWn)~nzQQ_My{4wHurFxkQ{gIt1sOS=Bqb-QIaaj
zur;j@8;~qXdrTfK)QDYLpOm~FD8&{1~{x<5zTI~AoZVq|yP{AP&1v+Dy
zWHZIa0|XwtjiyJZ?J5X0BfO>E6xsi8CTX%Z0nnpBnd{HN=!^^Yz8(aMnF;_G$`a
z?rV&AwNdcNXenJ>Ey_L*o!(xV0MvR+;p(>5HxNA*V
zoaWNx^Vc|e(SeY@UO4ASanq-Oo=#^j%>5;Eyask)z7q!0aF9Mn>*f?UH)f`NB)}VrQ`Taia*<)spVbd#By;7iBtbEf;u{jZle43n18g+2|`IT2!n_xRqhW1r$3Vt&+
z`EeOYjiKxqZ$Z|0kYPjilMifnn$I=;K@W-LKR<`DGM~nZGdP_(=EHuxc2B)G#fT2$
z_s4e3+^0dgVu$~cSkw;JIV=;1Rm7G-io!xNAxI+i8h!uO?2qr?cSx$&=8Cbdtm~)a
z&u}zYWdbj3J4IeMjbi!Om~c$u*j007%(E_u1jnc26)m;v6{{0cj>AUMm#IaW_Qssv
z>@1JA-y3}d0>w$4V`*`R!GcEEja%)UduAf!y0=avaqJKzDdrHO=TYA9b==WccW;qV
zY;}k$tiEE$nl$K&lI@z;K*Ob8Gg=Uh*UpR0M|c~}d?c2MNC#niQof9=!|Jo^(U$D5
z%b&@!ha=KrmcIjWNgO){tV@W|X4GcAsOHWoo7J${I!g!{w0a!H@yel?^Z&-D*tY+R
zj$i?Cn&gU$3Bw|RQ3Eg(z!rMkY4%U7Un`!Vn^@4D_}npLASWjTyRj@b^Vw_xU#~^a
z%U17XI%Bd4z%n>#bE*UJu44c^dORskZ7wMYV>Mr&-PNwV?TJB$Psa}j{QvcU5Qj@=
z8k-Vds)auQf`U?BgGkJ_j&x(Q1OsG)dO9%FMdyT>Z!X=~Te9A`fTI>(Jf6GZ$A*VJ
zf;f&wWWeCi248|R%5*3D)}OO*YttO!x#{H{OXMdR!_eSbGkVL^>tEgLdGpY*L4Ac7Qk?A+dz
z9`A*rGbR*FWue1nhVwxO_2yz#r#uK^9>8)=S;qMF&J18T{s5$es`f@JjDskEEEi56
zLs|WJQFrG721yak5;7_kEN+d3(X~oJ`FOAFPX|dMAzYqLlDlkh5nCE$@E&
zK6ATUJH!MeV!U?mhgsZ7w5sivnH&tN%^arV0VZKRT_$2MB$SdRpUZ8#nnvnpB<1l4
zR;C1x*BXkRf9JIBIr-V*tNhDpaY)%ln;u=ru|1kta@`vBtwFd4w;
z&0Vezf5wv|F54lFJt3Sj(yfXo>${&ybp~#noK#X(x_Nn{K=qcNe?f|~F?ld|&|;D%
zJGd@LOaR0fQO+7(AEmNJDOI-n>;C+#H%qg#2OOz=orRI6YUl1|+XkL$GLQ-w{lk-t
zHsWVuVlj9BK|wq)r`U4AdW}0xF`DsTsUZUh$Gv~FNV=z$&Zp+qe^*(z_y?H`rQhZ*
zJ=wvL0nkW18Q#3;5uTirxkK&?gfLkF@jg2NnKlP@tM*+vc%Ap|&=qSe@j4j3kJ+ii
zud;K)mh6&Fe=cku=Eg4u)xng^O_!>X24PF5v%arBd5ItLzHyu0<{G0X0N9?v#sTa9qJzAxaLkglO=Q57ED`3m&KS
zLsq%;_^;<+m?KsbeXZ)qL6Erc8*sU!b-=~201y0MdrzL4&u>$HRrT?o^oA{pFjn
zi{rW4JHy}HcGd?aTz6M)=$1S3y&$I*GJFy!w(%V{Rxn6~vg;e`y9xqQa`HUKxApn^
znqI3-PCy-qFE7&*o!j+$=Y3Sq?RMGsCU}Ac)f?|zMN66~0zl-%c@3nv@Ps8NVEDYF
z?E?r!f7P65g|X(<1y>pFS2+es?9<~Q4g#@#A&qyB`H=2!PnI^@JM|eYG;MH-GsFjA
z=`dL?!{)^6ymNmvnWt3-V?*v6m)xKF`z>$dW`%58%xjtJpR6UsnkcJnpPo3Mtl^YI
z+XkfCymQ|)4O?V#5g@2>TNmRb!PZOKE)G>y27i^N<1@{@KfUNNz0_oelY3#jjsiDq
z=PNPOm8DB&`%Ts7Hf|WH3`vpDh}_A)zK$rAh~F=|W;CF%ZMe4VY9!6&40e5n0;ynY
zycyoQ;hP(HY&&KaV5o^
z21e4W4-fV0cQq%MuB;b-qbJ@ARO_I&g5$6s^beWPjS)V2e%ONriKP4>#XCj{+745VxYJJXv}8xwv;)p50AULmABd5XM}A54&e7u
zNr~88-iNWsX%)Z^xEZZ+DfK$9PEby3qp`A;rvQ&N=Ku%~4z9b{1azgz2`HoUVh45O
zD-O4{nt;*&{sUg*7Xao0^sy%)o7*GE?x8NrPmcH2E0dU!S8=`m+T6LuKjZIJa|_N5
z1BHZ)7f~?igiC!cwvO6VQ_f&_Yi6p>PKak^$2+cTv7q2y3*47tzE@3Y<{7MCH7fiz
zzBj_VCt-{O7Qsu9%^Y-Ivhgp`(|;8xLByCaLVK
z{CSaQX$7kxye$xz{p|U3a$xp9;c=E&F$>kPzjbbxyNO}AQZ8(>(SQk<*jgo*gU`-8
z2@6;9J|C@iN$iQd(Zq9OKX!$|*J)DkwQsh=j^8;N9JW$DpZcYAy=Br(qEVvDX{0hk5c8>~IfE4#8EUw!y?ylRQtLvYuNnzxqD&TIM`9|%+o^6v~#
z>EwOLwaVup{Y*oH3fCEk$GAhl<{h=+C3Ovr`mf_o_s#&L$0-S-l_mg9H0f~@Jp_$q
zU+raH3hcV*y}xLK?|p&{%I15e842A4>dRdC`mkJU5A#~a^4mM_V}#Jkz4I`w-wy3>
zfJ_;5jljmc(c*@D2?062*LXus1`qW3(i!Q@%WsHbmQy5s*#1KOO_1P6I6QqYD9B5}
z5;OL_TL^LsIk>EWL#bD5p8j2lmX{d}kV)sg*qV6EV)+f2@9u1QGZ`j8&UAs{@C%m0
zhm!;n|E3-T;@+-{RY#8egz%`6nHu2e*I8+F4!%=>A`L9v!u7{H^20Nn{0vTCC}Wpt
z1G{3N#k;syUpx$TyaO@{$VS<&VE7PWA$i;Ub8lq{9%;v=n
zOLC5Zx9_~R`P?}RJZI}LJ{vF3;IJ;Rw+MFBroWC~&ikx!=NfL9^K`DWdaA0?g7q5b
zYH4u&{u16=B$skZ?iR;!<>Iq29qMC$(SV#*UKnvYUiMYnj5(
zl1XQt9{1O{a%b539xa4`PF*bqnDO&*n5Je)0{QjFS27!wWka~Y_%9rU#ksDBfOIcV(a$qA!uWFd&KcQ_mp^r+~2U^
z0Qi07(by*vq?WBrgIiM@$ovNZtz>?e7i
zvg3Jn5A4+1PDis`=~mYBn7{*lxJ18tCtHO!k-mx@0^~{GTonE)ujAe>B@cG}ct@H?
z)M=-)2gk86UmOz!*NEsw|gf7!4ZY?XHXZwjtP85p%J@d-)#eSVG9eW
z!_BB+pJpiqqi=Ik$L=6&LGz9bgP?KdN(uJ8ukZJJ1dyd0h=+p|yxgr<@0aHC7QdrR
zYY*1xz!7L5He?%FZM#G&MGM=fMxm{?*})))5N!QaRqai(ahEUi!+Y(##i9LN15Fm4
zRYte~$BN#5>w)dWpmHDplc+|!)JUsV*J1eCU^G2rolO6r5y?BRc1&$VtI#U|?;_yB
zregSPL#e^Z60gC+MB@<2e47rH!i{Vkg!oDyq#??ZZVq7QpF?M>fL`LTRsk0>tXY69
zy})fAQ8wbWVsGB!ByhB1I+|nb^ckoP=>aY|1;bCp1x@Q7H^GJwF6tRM
zAnk3jq;qSzX?x@%FRQfh+M5Cef>nlUi;pjTXoGcz9!6<#gs7yiE^|wU~b*+&uy@
zE`TU-j$Av(Vn2q;rOlVhW3@VO<;OeXTZB#%C&@6(Upv17_O$~nd_a)?YKBy=U
z@gjFwJ1m(A(!7C#L){BOSP45(!vhdp)yO!7+>|+o2rCUT%R}}GSl4Xw;v$`zfdZ95
z_=p1T@iGjg%FM*cEgABW>uip{D(DR`3g
z9h?>t!ooWji~uSI1IW_fmbKLA;f<#MLZp0ErgJB>xWLBuaxgu>OzY5?;XE0K^&Vb$
zb2n#P3{Hi+lR(e?CE5Vfx_kS!ZiPNK5WHPw$rKSxjh3%SbEc{dz;
zBg#Se&_F-2a|z7u$&p=|yN;8Iu7KrhKHMgL-}r89^X48c_bDz*uXM4W2&u4%#~%Vn
zA1%z|%{lEv!f-JzpKa+82*Q@nljhAzLiC%)jZwo4O^ZTQC1rmncyRnmpZS!N1KHP=ck&;54}v!7B~=OSuDf+;Tnb
z^u;|VFX3mHuS82bV>TI21@Jxm>5-?!t_?>A7y#e?SElff=1cS3ejEDk3gGz9zo`nr
zhlCm#$FBulX~*}JF+cz1V4R21@0=$#5I>wH@L)3>lVvOuo--2o$qp-Yw)w4aj8W4L
zSmA~AOi)m$X~V{oZ@tRn1vnzc!~`7B)7?GVjRugPQG-oxJOGj2GPh-mrSCPG&SePE
zQRu+eD>7IQJDA(w*lX0owF6)vg%ifqZp^D`=l9$e9v8`oOh~v^bF}1<2SPJE9sS^_
z!E`Emk}SF8
z!p#~stepj5Uy45s!f_5$x@@@=y4e>r4Hy
zIe7p}B_lc7h3{D->$A&kv4H~M$-
zDmgt*1mye)-kyM7>GUIm=4F7#k2%kkm2DmcY#@we3h-Tkt#mfinmy|__s#gI!(U>&
zHXX-uu=$Me2bOpudfT%t`p~)((^KiZ{rN2B3nPf_p4D(oZSojDRFkQ7D-x
zJ>e<^08ptI8r=>em}xJ#B69=SE@{@o2loIucw6+HcQ}
zeDu|N4Gv9nns4;CXIQl$5Xt11R^cjEo(KdxGxLR9KnS?mf;g%nVsdF>1>=1TKJ)a#(u?F;}5zf&CaHpw6Kv`7=NED&(e|=_*eU06)fWVzH
zM)HDHP?o|G6m^*9bs_>`PU^V97BuQ=#7VBvX&d(RCj!hy`W85u9)0)?@>S%k12?;#
z{73M~&rFg6g6}Mn=luj%L!Ueqy3=kfZhnmiQ=>>`6fFja*ptnVP~X7!1Cy|}NmA4wH7
zdbJ$)xea1ztG;=bX;nAS)8=Dl4^HNu8d`{)+&rulM(X{wj)b|fhrgj$9mb5lzH$Yi
zn(FKCmTi)S!vueQnu{!$kqmjXAQwjq1f8>XD|Q?=JqHN6dgzx*1ahTtU2apkd`id7F0D1LyS4KvPfl2PD8#22Wsa4YaiUG
zO%6F;3b}$(j$W-I&uj4q%%srB$$Wm?1oEc#a0^;yq;(Ms82+7rwddcTD>T~lfOupw
z)&z){^S>D^t5fgfaTJ)+wKSbwQ-rvYGAN(tNhkD4kBw;;YjL$4~%^AW9BH}q
zXfTuN??;~3_#KS)NkiUxW@GdYwC5G?<>v)DL;YmLfKVMT)uqG_8DIrJfmImDa%f5&bziu1C&gk|<^$Ayr2pJu#{32CV`RSe@dZ)UZTEajZ4dQ=iTI=Szwu2wA*>`@R6tu}4`s(AGOaB$Rlb2{H
zCNu%v9;yHJ(Y@Ei>hQv-00gV9F3q9W?tKENc0eJ#bPB$*q5lIgN>zAwSxPRf&!rSB
zrQI+APfg*u47bsvZPSsNfOesI?(-YqS_PE7`#`eUBU?h!w&G0fTBlyso#utkD^6gK
z7IpWtT#V;8>*f8CIi9|PwO#ej7XrI$f4t>3$U;lGfQ7eAW$jtvP>TIquy=G4y>~J#
zX2Mm&U|zZY@(hG(7d`P}natnRUON;kw1CFM%ZhJ?BPD6cl^7Jk4K}SHB#qgi#CEUJ
z+F`8m=y8Sf>G&-LA5t1MMsoBPFxs{9k9utsV+Qt|PKW2ge0VMXL{Qf`F%)RB@{X|p
z4l~*vC!1fZF1qWP)K+{H+~e=>g=8^xu_OS8HQTb5hyFHL?Z&c=Zi$ImwH=HXn2R%#
zkBfHz*JROS+3@pixW(jzM$Jo8ZBHjB<+9h-$xBi``(XK^1eAKWi|2s!gj}!GVudXJ
z^p(K(KoRv?AtGQaX|)1eE<4#T#wZnx&WOeDu1BPS*98iQ)fHQCRq*n-r1`SC{wPC@
zt*Mwto%#L|Y4s<$qtYe@;7+#tjyf!)7)O`#57w?jwsir*0#2_o#08m6U15L55GH)P
z=Zlv+Y!OOEHGfob+_$h?n{roZ4SbY{<^T?p%@V1=Epd{Q#!*=zQRCaTr}rngx#&_p
z5;NqhH3x1tN&CGBM}~O~O`OA?B?mU8M5XGeNHHdkjC@5x7}yZrm7YgZZ<`{x^ts>j
zu&Z%qf-9OWW-MVdEQu5LKx$X>ZiP4AdaK4EuF0wYB=yrkw7_o5k~#Z!2zmEn86h|>
zWZ}=MTw^q%9)4*lpykHMW1Gt3zREw|jg<`KX*Z?^0S9Bh@%rtw!8eTcU+HT-{T>iR
zD~Ks&^{Ze`Oj-bzRMyxCG2e$&%XKa#t0_(X6e+gyyH$Q+d+C(CyMOyUKpcPERGN+`
zzojXm`d#1P>EfjX!L5Ant?1crzKe5*XCP?;?fBW=YwXJCdr6;aekW6Xp>wKFIcAG)
zlbN`48JuA(Vx>Ktu|~V@T71BKF!g`gUY?fg$)=`itA*J5xNrlUUSti5>JL*O<@xB1
zO}v2em8vp&qb4NA2dt9{uw)nOueA^RtA6-PQWKl`-MT=;?dR;rs&YCHx1L*4oXmT>
z?%?vT8>V#UHuaBQ`k4=2*P4)Iaosvqd&p}HW?HuIF
z*YAUyLG~07N~nv<{3sjQ%dVq+d-6$h6>6_AG?KK_xMz}$oJCw8T!g-8lUjB%4Bs(+
ze9J>^ILs~LhSGD--=C7Kt%D0K5Oi19uY%(qHy$%v4;SW5&sLx->{Ks!+N7)mc26EY
zLtqBUyC-|8z;gc~-m|06t`z%5Ki$)N_^IcP>0S5bc=A*EahhS{G~c1oZEEl<1GaAV
zIn%SiOB~wW@(MSxcWI+GYDANnpYSm|?u`*cQ(i7oO1-%^wyRD{5XCwS{m(a)qupwM
z$bd_KxAVzu5zbbCvttj}P+mY+TYj|&_;Dpo#_9L<_s_N1qVj7l
zo5J^Vk=1kHwr$v7VnYzoWQc^#Mw^18L$e?nDIs)y1Nr7H5adu~2{1DRvR0Y)L|jm7
zy9pJKm>V^2Bm~W7hNdpaBZ_2UudH^JmpA{AQ8CVacLn^ufz^6eXc5uY@Rv(0K|Hbu
zrvPBEDKoLGUW&I1?qBAj(8SusY>>$opdpWg@Kq-tXHdY~S8
zMOP(|)!q~sR0M(}6S%^b#z!+4)@#=W`?F4Gyh-&2U;VG&mOvKw@*=|m62AfCKC8#H8`W^26^no<%r4W}+m?Wsc@Z$rmn`v$b
zEKp0G6r-D}Qe9)lY4{_#RAk^|X1Nce0fbOWk|eX9A8V?tWcwV@Hn+HLfmMgmzZaAg$#*eadnNfL1B8
zKtsQpmfBbvNt?}j$ie+Xdh!andTuXFXRYt=sPoW}+!lG>edc-j`jbaJCB5!#E%I}j
zPRg0_jNA+4P+_iZorTz}ThZkV-yyc3La&$*J!II@_0SDkWU6K|{M#s@h!@x3-$oH+
z&L>x;?zjyuo{W-_x08n|FzfCw%)CGH)uKn{^MND>kuR_aKQHFdwn~CHA1`uL%kkxW9e(7K$YGh{uJ|Mn(ydrmb@7?7Sdd4dxdXG<3
tP?+o9J<)sj{zDM}gZjTUu(B~WH+A`cH`pF8%K!}^q^vR`=b^!W{~yEv=0E@d
literal 0
HcmV?d00001
diff --git a/packages/bootstrap-4/src/AddButton/AddButton.tsx b/packages/bootstrap-4/src/AddButton/AddButton.tsx
new file mode 100644
index 0000000000..5634432d5d
--- /dev/null
+++ b/packages/bootstrap-4/src/AddButton/AddButton.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+
+import { AddButtonProps } from "@rjsf/core";
+import Button from "react-bootstrap/Button";
+import { AiOutlinePlus } from "react-icons/ai";
+
+const AddButton: React.FC = props => (
+
+);
+
+export default AddButton;
diff --git a/packages/bootstrap-4/src/AddButton/index.ts b/packages/bootstrap-4/src/AddButton/index.ts
new file mode 100644
index 0000000000..752d720d32
--- /dev/null
+++ b/packages/bootstrap-4/src/AddButton/index.ts
@@ -0,0 +1,2 @@
+export { default } from './AddButton';
+export * from './AddButton';
diff --git a/packages/bootstrap-4/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx b/packages/bootstrap-4/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx
new file mode 100644
index 0000000000..c9e73e8e58
--- /dev/null
+++ b/packages/bootstrap-4/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx
@@ -0,0 +1,211 @@
+import React from "react";
+import { utils } from "@rjsf/core";
+import Row from "react-bootstrap/Row";
+import Col from "react-bootstrap/Col";
+import Container from "react-bootstrap/Container";
+import { ArrayFieldTemplateProps, IdSchema } from "@rjsf/core";
+
+import AddButton from "../AddButton/AddButton";
+import IconButton from "../IconButton/IconButton";
+
+const { isMultiSelect, getDefaultRegistry } = utils;
+
+const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
+ const { schema, registry = getDefaultRegistry() } = props;
+
+ // TODO: update types so we don't have to cast registry as any
+ if (isMultiSelect(schema, (registry as any).rootSchema)) {
+ return ;
+ } else {
+ return ;
+ }
+};
+
+type ArrayFieldTitleProps = {
+ TitleField: any;
+ idSchema: IdSchema;
+ title: string;
+ required: boolean;
+};
+
+const ArrayFieldTitle = ({
+ TitleField,
+ idSchema,
+ title,
+ required,
+}: ArrayFieldTitleProps) => {
+ if (!title) {
+ return null;
+ }
+
+ const id = `${idSchema.$id}__title`;
+ return ;
+};
+
+type ArrayFieldDescriptionProps = {
+ DescriptionField: any;
+ idSchema: IdSchema;
+ description: string;
+};
+
+const ArrayFieldDescription = ({
+ DescriptionField,
+ idSchema,
+ description,
+}: ArrayFieldDescriptionProps) => {
+ if (!description) {
+ return null;
+ }
+
+ const id = `${idSchema.$id}__description`;
+ return ;
+};
+
+// Used in the two templates
+const DefaultArrayItem = (props: any) => {
+ const btnStyle = {
+ flex: 1,
+ paddingLeft: 6,
+ paddingRight: 6,
+ fontWeight: "bold",
+ };
+ return (
+
+
+ {props.children}
+
+
+ {props.hasToolbar && (
+
+ {(props.hasMoveUp || props.hasMoveDown) && (
+
+
+
+ )}
+
+ {(props.hasMoveUp || props.hasMoveDown) && (
+
+
+
+ )}
+
+ {props.hasRemove && (
+
+
+
+ )}
+
+ )}
+
+
+
+ );
+};
+
+const DefaultFixedArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
+ return (
+
+ );
+};
+
+const DefaultNormalArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
+ return (
+
+
+
+
+
+ {(props.uiSchema["ui:description"] || props.schema.description) && (
+
+ )}
+
+
+ {props.items && props.items.map(p => DefaultArrayItem(p))}
+
+ {props.canAdd && (
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default ArrayFieldTemplate;
diff --git a/packages/bootstrap-4/src/ArrayFieldTemplate/index.ts b/packages/bootstrap-4/src/ArrayFieldTemplate/index.ts
new file mode 100644
index 0000000000..ab908dec2c
--- /dev/null
+++ b/packages/bootstrap-4/src/ArrayFieldTemplate/index.ts
@@ -0,0 +1,2 @@
+export { default } from './ArrayFieldTemplate';
+export * from './ArrayFieldTemplate';
diff --git a/packages/bootstrap-4/src/CheckboxWidget/CheckboxWidget.tsx b/packages/bootstrap-4/src/CheckboxWidget/CheckboxWidget.tsx
new file mode 100644
index 0000000000..88c951a668
--- /dev/null
+++ b/packages/bootstrap-4/src/CheckboxWidget/CheckboxWidget.tsx
@@ -0,0 +1,51 @@
+import React from "react";
+
+import { WidgetProps } from "@rjsf/core";
+import Form from "react-bootstrap/Form";
+
+const CheckboxWidget = (props: WidgetProps) => {
+ const {
+ id,
+ value,
+ required,
+ disabled,
+ readonly,
+ label,
+ schema,
+ autofocus,
+ onChange,
+ onBlur,
+ onFocus,
+ } = props;
+
+ const _onChange = ({
+ target: { checked },
+ }: React.FocusEvent) => onChange(checked);
+ const _onBlur = ({
+ target: { checked },
+ }: React.FocusEvent) => onBlur(id, checked);
+ const _onFocus = ({
+ target: { checked },
+ }: React.FocusEvent) => onFocus(id, checked);
+
+ const desc = label || schema.description;
+ return (
+
+
+
+ );
+};
+
+export default CheckboxWidget;
diff --git a/packages/bootstrap-4/src/CheckboxWidget/index.ts b/packages/bootstrap-4/src/CheckboxWidget/index.ts
new file mode 100644
index 0000000000..b9e3c318ec
--- /dev/null
+++ b/packages/bootstrap-4/src/CheckboxWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from './CheckboxWidget';
+export * from './CheckboxWidget';
diff --git a/packages/bootstrap-4/src/CheckboxesWidget/CheckboxesWidget.tsx b/packages/bootstrap-4/src/CheckboxesWidget/CheckboxesWidget.tsx
new file mode 100644
index 0000000000..22ac9d8ff3
--- /dev/null
+++ b/packages/bootstrap-4/src/CheckboxesWidget/CheckboxesWidget.tsx
@@ -0,0 +1,105 @@
+import React from "react";
+import Form from "react-bootstrap/Form";
+import { WidgetProps } from "@rjsf/core";
+
+const selectValue = (value: any, selected: any, all: any) => {
+ const at = all.indexOf(value);
+ const updated = selected.slice(0, at).concat(value, selected.slice(at));
+
+ // As inserting values at predefined index positions doesn't work with empty
+ // arrays, we need to reorder the updated selection to match the initial order
+ return updated.sort((a: any, b: any) => all.indexOf(a) > all.indexOf(b));
+};
+
+const deselectValue = (value: any, selected: any) => {
+ return selected.filter((v: any) => v !== value);
+};
+
+const CheckboxesWidget = ({
+ schema,
+ label,
+ id,
+ disabled,
+ options,
+ value,
+ autofocus,
+ readonly,
+ required,
+ onChange,
+ onBlur,
+ onFocus,
+}: WidgetProps) => {
+ const { enumOptions, enumDisabled, inline } = options;
+
+ const _onChange = (option: any) => ({
+ target: { checked },
+ }: React.ChangeEvent) => {
+ const all = (enumOptions as any).map(({ value }: any) => value);
+
+ if (checked) {
+ onChange(selectValue(option.value, value, all));
+ } else {
+ onChange(deselectValue(option.value, value));
+ }
+ };
+
+ const _onBlur = ({ target: { value } }: React.FocusEvent) =>
+ onBlur(id, value);
+ const _onFocus = ({
+ target: { value },
+ }: React.FocusEvent) => onFocus(id, value);
+
+ return (
+ <>
+ {label || schema.title}
+
+ {(enumOptions as any).map((option: any, index: number) => {
+ const checked = value.indexOf(option.value) !== -1;
+ const itemDisabled =
+ enumDisabled && (enumDisabled as any).indexOf(option.value) != -1;
+
+ return inline ? (
+
+
+ ) : (
+
+
+ );
+ })}
+
+ >
+ );
+};
+
+export default CheckboxesWidget;
diff --git a/packages/bootstrap-4/src/CheckboxesWidget/index.ts b/packages/bootstrap-4/src/CheckboxesWidget/index.ts
new file mode 100644
index 0000000000..97152004fa
--- /dev/null
+++ b/packages/bootstrap-4/src/CheckboxesWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from './CheckboxesWidget';
+export * from './CheckboxesWidget';
diff --git a/packages/bootstrap-4/src/ColorWidget/ColorWidget.tsx b/packages/bootstrap-4/src/ColorWidget/ColorWidget.tsx
new file mode 100644
index 0000000000..f874dc9ace
--- /dev/null
+++ b/packages/bootstrap-4/src/ColorWidget/ColorWidget.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+import TextWidget, { TextWidgetProps } from "../TextWidget";
+
+const ColorWidget = (props: TextWidgetProps) => {
+ return ;
+};
+
+export default ColorWidget;
diff --git a/packages/bootstrap-4/src/ColorWidget/index.ts b/packages/bootstrap-4/src/ColorWidget/index.ts
new file mode 100644
index 0000000000..95e1f017ed
--- /dev/null
+++ b/packages/bootstrap-4/src/ColorWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./ColorWidget";
+export * from "./ColorWidget";
diff --git a/packages/bootstrap-4/src/DateTimeWidget/DateTimeWidget.tsx b/packages/bootstrap-4/src/DateTimeWidget/DateTimeWidget.tsx
new file mode 100644
index 0000000000..06218eeae6
--- /dev/null
+++ b/packages/bootstrap-4/src/DateTimeWidget/DateTimeWidget.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { utils } from "@rjsf/core";
+import TextWidget, { TextWidgetProps } from "../TextWidget";
+
+const { localToUTC, utcToLocal } = utils;
+
+const DateTimeWidget = (props: TextWidgetProps) => {
+ const value = utcToLocal(props.value);
+ const onChange = (value: any) => {
+ props.onChange(localToUTC(value));
+ };
+
+ return (
+
+ );
+};
+
+export default DateTimeWidget;
diff --git a/packages/bootstrap-4/src/DateTimeWidget/index.ts b/packages/bootstrap-4/src/DateTimeWidget/index.ts
new file mode 100644
index 0000000000..ce097fde2b
--- /dev/null
+++ b/packages/bootstrap-4/src/DateTimeWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./DateTimeWidget";
+export * from "./DateTimeWidget";
diff --git a/packages/bootstrap-4/src/DateWidget/DateWidget.tsx b/packages/bootstrap-4/src/DateWidget/DateWidget.tsx
new file mode 100644
index 0000000000..d0c2dc87ba
--- /dev/null
+++ b/packages/bootstrap-4/src/DateWidget/DateWidget.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import TextWidget, { TextWidgetProps } from "../TextWidget";
+
+const DateWidget = (props: TextWidgetProps) => {
+ return (
+
+ );
+};
+
+export default DateWidget;
\ No newline at end of file
diff --git a/packages/bootstrap-4/src/DateWidget/index.ts b/packages/bootstrap-4/src/DateWidget/index.ts
new file mode 100644
index 0000000000..d909e2ec73
--- /dev/null
+++ b/packages/bootstrap-4/src/DateWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./DateWidget";
+export * from "./DateWidget";
diff --git a/packages/bootstrap-4/src/DescriptionField/DescriptionField.tsx b/packages/bootstrap-4/src/DescriptionField/DescriptionField.tsx
new file mode 100644
index 0000000000..c9c4fb5d99
--- /dev/null
+++ b/packages/bootstrap-4/src/DescriptionField/DescriptionField.tsx
@@ -0,0 +1,16 @@
+import React from "react";
+import { FieldProps } from "@rjsf/core";
+
+export interface DescriptionFieldProps extends Partial {
+ description?: string;
+}
+
+const DescriptionField = ({ description }: Partial) => {
+ if (description) {
+ return ;
+ }
+
+ return null;
+};
+
+export default DescriptionField;
diff --git a/packages/bootstrap-4/src/DescriptionField/index.ts b/packages/bootstrap-4/src/DescriptionField/index.ts
new file mode 100644
index 0000000000..401540d99b
--- /dev/null
+++ b/packages/bootstrap-4/src/DescriptionField/index.ts
@@ -0,0 +1,2 @@
+export { default } from './DescriptionField';
+export * from './DescriptionField';
diff --git a/packages/bootstrap-4/src/EmailWidget/EmailWidget.tsx b/packages/bootstrap-4/src/EmailWidget/EmailWidget.tsx
new file mode 100644
index 0000000000..3888da5132
--- /dev/null
+++ b/packages/bootstrap-4/src/EmailWidget/EmailWidget.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+import TextWidget, { TextWidgetProps } from "../TextWidget";
+
+const EmailWidget = (props: TextWidgetProps) => {
+ return ;
+};
+
+export default EmailWidget;
diff --git a/packages/bootstrap-4/src/EmailWidget/index.ts b/packages/bootstrap-4/src/EmailWidget/index.ts
new file mode 100644
index 0000000000..c48979eea8
--- /dev/null
+++ b/packages/bootstrap-4/src/EmailWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from './EmailWidget';
+export * from './EmailWidget';
diff --git a/packages/bootstrap-4/src/ErrorList/ErrorList.tsx b/packages/bootstrap-4/src/ErrorList/ErrorList.tsx
new file mode 100644
index 0000000000..f30f1320d3
--- /dev/null
+++ b/packages/bootstrap-4/src/ErrorList/ErrorList.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+
+import Card from "react-bootstrap/Card";
+import ListGroup from "react-bootstrap/ListGroup";
+
+import { ErrorListProps } from "@rjsf/core";
+
+const ErrorList = ({ errors }: ErrorListProps) => (
+
+ Errors
+
+
+ {errors.map((error, i: number) => {
+ return (
+
+ {error.stack}
+
+ );
+ })}
+
+
+
+);
+
+export default ErrorList;
diff --git a/packages/bootstrap-4/src/ErrorList/index.ts b/packages/bootstrap-4/src/ErrorList/index.ts
new file mode 100644
index 0000000000..79376ace11
--- /dev/null
+++ b/packages/bootstrap-4/src/ErrorList/index.ts
@@ -0,0 +1,2 @@
+export { default } from './ErrorList';
+export * from './ErrorList';
diff --git a/packages/bootstrap-4/src/FieldTemplate/FieldTemplate.tsx b/packages/bootstrap-4/src/FieldTemplate/FieldTemplate.tsx
new file mode 100644
index 0000000000..5a47e76e16
--- /dev/null
+++ b/packages/bootstrap-4/src/FieldTemplate/FieldTemplate.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+
+import { FieldTemplateProps } from "@rjsf/core";
+
+import Form from "react-bootstrap/Form";
+import ListGroup from "react-bootstrap/ListGroup";
+
+const FieldTemplate = ({
+ id,
+ children,
+ displayLabel,
+ rawErrors = [],
+ rawHelp,
+ rawDescription,
+}: FieldTemplateProps) => {
+ return (
+
+ {children}
+ {displayLabel && rawDescription ? (
+ 0 ? "text-danger" : "text-muted"}>
+ {rawDescription}
+
+ ) : null}
+ {rawErrors.length > 0 && (
+
+ {rawErrors.map((error: string) => {
+ return (
+
+
+ {error}
+
+
+ );
+ })}
+
+ )}
+ {rawHelp && (
+ 0 ? "text-danger" : "text-muted"}
+ id={id}>
+ {rawHelp}
+
+ )}
+
+ );
+};
+
+export default FieldTemplate;
diff --git a/packages/bootstrap-4/src/FieldTemplate/index.ts b/packages/bootstrap-4/src/FieldTemplate/index.ts
new file mode 100644
index 0000000000..6f7dc3861c
--- /dev/null
+++ b/packages/bootstrap-4/src/FieldTemplate/index.ts
@@ -0,0 +1,2 @@
+export { default } from './FieldTemplate';
+export * from './FieldTemplate';
diff --git a/packages/bootstrap-4/src/Fields/Fields.ts b/packages/bootstrap-4/src/Fields/Fields.ts
new file mode 100644
index 0000000000..a1871ac4a8
--- /dev/null
+++ b/packages/bootstrap-4/src/Fields/Fields.ts
@@ -0,0 +1,7 @@
+import DescriptionField from '../DescriptionField/DescriptionField';
+import TitleField from '../TitleField/TitleField';
+
+export default {
+ DescriptionField,
+ TitleField,
+};
diff --git a/packages/bootstrap-4/src/Fields/index.ts b/packages/bootstrap-4/src/Fields/index.ts
new file mode 100644
index 0000000000..c65ffe072d
--- /dev/null
+++ b/packages/bootstrap-4/src/Fields/index.ts
@@ -0,0 +1,2 @@
+export { default } from './Fields';
+export * from './Fields';
diff --git a/packages/bootstrap-4/src/FileWidget/FileWidget.tsx b/packages/bootstrap-4/src/FileWidget/FileWidget.tsx
new file mode 100644
index 0000000000..dae207ab93
--- /dev/null
+++ b/packages/bootstrap-4/src/FileWidget/FileWidget.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+import TextWidget, { TextWidgetProps } from "../TextWidget";
+
+const FileWidget = (props: TextWidgetProps) => {
+ return ;
+};
+
+export default FileWidget;
diff --git a/packages/bootstrap-4/src/FileWidget/index.ts b/packages/bootstrap-4/src/FileWidget/index.ts
new file mode 100644
index 0000000000..6e364e5c94
--- /dev/null
+++ b/packages/bootstrap-4/src/FileWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from './FileWidget';
+export * from './FileWidget';
diff --git a/packages/bootstrap-4/src/Form/Form.tsx b/packages/bootstrap-4/src/Form/Form.tsx
new file mode 100644
index 0000000000..c24d85da93
--- /dev/null
+++ b/packages/bootstrap-4/src/Form/Form.tsx
@@ -0,0 +1,10 @@
+import { withTheme, FormProps } from "@rjsf/core";
+
+import Theme from "../Theme";
+import { StatelessComponent } from "react";
+
+const Form:
+ | React.ComponentClass>
+ | StatelessComponent> = withTheme(Theme);
+
+export default Form;
diff --git a/packages/bootstrap-4/src/Form/index.ts b/packages/bootstrap-4/src/Form/index.ts
new file mode 100644
index 0000000000..dbca2de64e
--- /dev/null
+++ b/packages/bootstrap-4/src/Form/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./Form";
+export * from "./Form";
diff --git a/packages/bootstrap-4/src/IconButton/IconButton.tsx b/packages/bootstrap-4/src/IconButton/IconButton.tsx
new file mode 100644
index 0000000000..77875b233b
--- /dev/null
+++ b/packages/bootstrap-4/src/IconButton/IconButton.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+import Button from "react-bootstrap/Button";
+import { IoIosRemove } from "react-icons/io";
+import { GrAdd } from "react-icons/gr";
+import { AiOutlineArrowUp, AiOutlineArrowDown } from "react-icons/ai";
+
+const mappings: any = {
+ remove: ,
+ plus: ,
+ "arrow-up": ,
+ "arrow-down": ,
+};
+
+type IconButtonProps = {
+ icon: string;
+ className?: string;
+ tabIndex?: number;
+ style?: any;
+ disabled?: any;
+ onClick?: any;
+};
+
+const IconButton = (props: IconButtonProps) => {
+ const { icon, className, ...otherProps } = props;
+ return (
+
+ );
+};
+
+export default IconButton;
diff --git a/packages/bootstrap-4/src/IconButton/index.ts b/packages/bootstrap-4/src/IconButton/index.ts
new file mode 100644
index 0000000000..655ec4c488
--- /dev/null
+++ b/packages/bootstrap-4/src/IconButton/index.ts
@@ -0,0 +1,2 @@
+export { default } from './IconButton';
+export * from './IconButton';
diff --git a/packages/bootstrap-4/src/ObjectFieldTemplate/ObjectFieldTemplate.tsx b/packages/bootstrap-4/src/ObjectFieldTemplate/ObjectFieldTemplate.tsx
new file mode 100644
index 0000000000..0a3106bc41
--- /dev/null
+++ b/packages/bootstrap-4/src/ObjectFieldTemplate/ObjectFieldTemplate.tsx
@@ -0,0 +1,45 @@
+import React from "react";
+
+import Container from "react-bootstrap/Container";
+import Row from "react-bootstrap/Row";
+import Col from "react-bootstrap/Col";
+
+import { ObjectFieldTemplateProps } from "@rjsf/core";
+
+const ObjectFieldTemplate = ({
+ DescriptionField,
+ description,
+ TitleField,
+ title,
+ properties,
+ required,
+ uiSchema,
+ idSchema,
+}: ObjectFieldTemplateProps) => {
+ return (
+ <>
+ {(uiSchema["ui:title"] || title) && (
+
+ )}
+ {description && (
+
+ )}
+
+ {properties.map((element: any, index: number) => (
+
+ {element.content}
+
+ ))}
+
+ >
+ );
+};
+
+export default ObjectFieldTemplate;
diff --git a/packages/bootstrap-4/src/ObjectFieldTemplate/index.ts b/packages/bootstrap-4/src/ObjectFieldTemplate/index.ts
new file mode 100644
index 0000000000..77c68a9a40
--- /dev/null
+++ b/packages/bootstrap-4/src/ObjectFieldTemplate/index.ts
@@ -0,0 +1,2 @@
+export { default } from './ObjectFieldTemplate';
+export * from './ObjectFieldTemplate';
diff --git a/packages/bootstrap-4/src/PasswordWidget/PasswordWidget.tsx b/packages/bootstrap-4/src/PasswordWidget/PasswordWidget.tsx
new file mode 100644
index 0000000000..2f5002421f
--- /dev/null
+++ b/packages/bootstrap-4/src/PasswordWidget/PasswordWidget.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+
+import { WidgetProps } from "@rjsf/core";
+
+const PasswordWidget = ({
+ id,
+ required,
+ readonly,
+ disabled,
+ value,
+ label,
+ onFocus,
+ onBlur,
+ onChange,
+ options,
+ autofocus,
+ schema,
+ rawErrors = [],
+}: WidgetProps) => {
+ const _onChange = ({
+ target: { value },
+ }: React.ChangeEvent) =>
+ onChange(value === "" ? options.emptyValue : value);
+ const _onBlur = ({ target: { value } }: React.FocusEvent) =>
+ onBlur(id, value);
+ const _onFocus = ({
+ target: { value },
+ }: React.FocusEvent) => onFocus(id, value);
+
+ return (
+
+ 0 ? "text-danger" : ""}>
+ {label || schema.title}
+ {(label || schema.title) && required ? "*" : null}
+
+ 0 ? "is-invalid" : ""}
+ required={required}
+ disabled={disabled}
+ readOnly={readonly}
+ type="password"
+ value={value ? value : ""}
+ onFocus={_onFocus}
+ onBlur={_onBlur}
+ onChange={_onChange}
+ />
+
+ );
+};
+
+export default PasswordWidget;
diff --git a/packages/bootstrap-4/src/PasswordWidget/index.ts b/packages/bootstrap-4/src/PasswordWidget/index.ts
new file mode 100644
index 0000000000..84fd5e9026
--- /dev/null
+++ b/packages/bootstrap-4/src/PasswordWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from './PasswordWidget';
+export * from './PasswordWidget';
diff --git a/packages/bootstrap-4/src/RadioWidget/RadioWidget.tsx b/packages/bootstrap-4/src/RadioWidget/RadioWidget.tsx
new file mode 100644
index 0000000000..6297c60e51
--- /dev/null
+++ b/packages/bootstrap-4/src/RadioWidget/RadioWidget.tsx
@@ -0,0 +1,69 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+
+import { WidgetProps } from "@rjsf/core";
+
+const RadioWidget = ({
+ id,
+ schema,
+ options,
+ value,
+ required,
+ disabled,
+ readonly,
+ label,
+ onChange,
+ onBlur,
+ onFocus,
+}: WidgetProps) => {
+ const { enumOptions, enumDisabled } = options;
+
+ const _onChange = ({
+ target: { value },
+ }: React.ChangeEvent) =>
+ onChange(schema.type == "boolean" ? value !== "false" : value);
+ const _onBlur = ({ target: { value } }: React.FocusEvent) =>
+ onBlur(id, value);
+ const _onFocus = ({
+ target: { value },
+ }: React.FocusEvent) => onFocus(id, value);
+
+ const inline = Boolean(options && options.inline);
+
+ return (
+
+
+ {label || schema.title}
+ {(label || schema.title) && required ? "*" : null}
+
+ {(enumOptions as any).map((option: any, i: number) => {
+ const itemDisabled =
+ Array.isArray(enumDisabled) &&
+ enumDisabled.indexOf(option.value) !== -1;
+ const checked = option.value == value;
+
+ const radio = (
+
+ );
+ return radio;
+ })}
+
+ );
+};
+
+export default RadioWidget;
diff --git a/packages/bootstrap-4/src/RadioWidget/index.ts b/packages/bootstrap-4/src/RadioWidget/index.ts
new file mode 100644
index 0000000000..10292dc565
--- /dev/null
+++ b/packages/bootstrap-4/src/RadioWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from './RadioWidget';
+export * from './RadioWidget';
diff --git a/packages/bootstrap-4/src/RangeWidget/RangeWidget.tsx b/packages/bootstrap-4/src/RangeWidget/RangeWidget.tsx
new file mode 100644
index 0000000000..2c7c9a41bc
--- /dev/null
+++ b/packages/bootstrap-4/src/RangeWidget/RangeWidget.tsx
@@ -0,0 +1,56 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+
+import { utils } from "@rjsf/core";
+import { WidgetProps } from "@rjsf/core";
+
+const { rangeSpec } = utils;
+
+const RangeWidget = ({
+ value,
+ readonly,
+ disabled,
+ onBlur,
+ onFocus,
+ options,
+ schema,
+ onChange,
+ required,
+ label,
+ id,
+}: WidgetProps) => {
+ let sliderProps = { value, label, id, ...rangeSpec(schema) };
+
+ const _onChange = ({
+ target: { value },
+ }: React.ChangeEvent) =>
+ onChange(value === "" ? options.emptyValue : value);
+ const _onBlur = ({ target: { value } }: React.FocusEvent) =>
+ onBlur(id, value);
+ const _onFocus = ({
+ target: { value },
+ }: React.FocusEvent) => onFocus(id, value);
+
+ return (
+
+
+ {label}
+ {label && required ? "*" : null}
+
+
+ {value}
+
+ );
+};
+
+export default RangeWidget;
diff --git a/packages/bootstrap-4/src/RangeWidget/index.ts b/packages/bootstrap-4/src/RangeWidget/index.ts
new file mode 100644
index 0000000000..d8c49226c6
--- /dev/null
+++ b/packages/bootstrap-4/src/RangeWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from './RangeWidget';
+export * from './RangeWidget';
diff --git a/packages/bootstrap-4/src/SelectWidget/SelectWidget.tsx b/packages/bootstrap-4/src/SelectWidget/SelectWidget.tsx
new file mode 100644
index 0000000000..d414d53001
--- /dev/null
+++ b/packages/bootstrap-4/src/SelectWidget/SelectWidget.tsx
@@ -0,0 +1,131 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+
+import { WidgetProps } from "@rjsf/core";
+import { utils } from "@rjsf/core";
+
+const { asNumber, guessType } = utils;
+
+const nums = new Set(["number", "integer"]);
+
+/**
+ * This is a silly limitation in the DOM where option change event values are
+ * always retrieved as strings.
+ */
+const processValue = (schema: any, value: any) => {
+ // "enum" is a reserved word, so only "type" and "items" can be destructured
+ const { type, items } = schema;
+ if (value === "") {
+ return undefined;
+ } else if (type === "array" && items && nums.has(items.type)) {
+ return value.map(asNumber);
+ } else if (type === "boolean") {
+ return value === "true";
+ } else if (type === "number") {
+ return asNumber(value);
+ }
+
+ // If type is undefined, but an enum is present, try and infer the type from
+ // the enum values
+ if (schema.enum) {
+ if (schema.enum.every((x: any) => guessType(x) === "number")) {
+ return asNumber(value);
+ } else if (schema.enum.every((x: any) => guessType(x) === "boolean")) {
+ return value === "true";
+ }
+ }
+
+ return value;
+};
+
+const SelectWidget = ({
+ schema,
+ id,
+ options,
+ label,
+ required,
+ disabled,
+ readonly,
+ value,
+ multiple,
+ autofocus,
+ onChange,
+ onBlur,
+ onFocus,
+ placeholder,
+ rawErrors = [],
+}: WidgetProps) => {
+ const { enumOptions, enumDisabled } = options;
+
+ const emptyValue = multiple ? [] : "";
+
+ function getValue(
+ event: React.FocusEvent | React.ChangeEvent | any,
+ multiple: Boolean
+ ) {
+ if (multiple) {
+ return [].slice
+ .call(event.target.options as any)
+ .filter((o: any) => o.selected)
+ .map((o: any) => o.value);
+ } else {
+ return event.target.value;
+ }
+ }
+
+ return (
+
+ 0 ? "text-danger" : ""}>
+ {label || schema.title}
+ {(label || schema.title) && required ? "*" : null}
+
+ 0 ? "is-invalid" : ""}
+ onBlur={
+ onBlur &&
+ ((event: React.FocusEvent) => {
+ const newValue = getValue(event, multiple);
+ onBlur(id, processValue(schema, newValue));
+ })
+ }
+ onFocus={
+ onFocus &&
+ ((event: React.FocusEvent) => {
+ const newValue = getValue(event, multiple);
+ onFocus(id, processValue(schema, newValue));
+ })
+ }
+ onChange={(event: React.ChangeEvent) => {
+ const newValue = getValue(event, multiple);
+ onChange(processValue(schema, newValue));
+ }}>
+ {!multiple && schema.default === undefined && (
+
+ )}
+ {(enumOptions as any).map(({ value, label }: any, i: number) => {
+ const disabled: any =
+ Array.isArray(enumDisabled) &&
+ (enumDisabled as any).indexOf(value) != -1;
+ return (
+
+ );
+ })}
+
+
+ );
+};
+
+export default SelectWidget;
diff --git a/packages/bootstrap-4/src/SelectWidget/index.ts b/packages/bootstrap-4/src/SelectWidget/index.ts
new file mode 100644
index 0000000000..e37ea725b8
--- /dev/null
+++ b/packages/bootstrap-4/src/SelectWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from './SelectWidget';
+export * from './SelectWidget';
diff --git a/packages/bootstrap-4/src/TextWidget/TextWidget.tsx b/packages/bootstrap-4/src/TextWidget/TextWidget.tsx
new file mode 100644
index 0000000000..735bf06f3d
--- /dev/null
+++ b/packages/bootstrap-4/src/TextWidget/TextWidget.tsx
@@ -0,0 +1,73 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+
+import { WidgetProps } from "@rjsf/core";
+
+export interface TextWidgetProps extends WidgetProps {
+ type?: string;
+}
+
+const TextWidget = ({
+ id,
+ required,
+ readonly,
+ disabled,
+ type,
+ label,
+ value,
+ onChange,
+ onBlur,
+ onFocus,
+ autofocus,
+ options,
+ schema,
+ rawErrors = [],
+
+}: TextWidgetProps) => {
+ const _onChange = ({
+ target: { value },
+ }: React.ChangeEvent) =>
+ onChange(value === "" ? options.emptyValue : value);
+ const _onBlur = ({ target: { value } }: React.FocusEvent) =>
+ onBlur(id, value);
+ const _onFocus = ({
+ target: { value },
+ }: React.FocusEvent) => onFocus(id, value);
+
+ // const classNames = [rawErrors.length > 0 ? "is-invalid" : "", type === 'file' ? 'custom-file-label': ""]
+ return (
+
+ 0 ? "text-danger" : ""}>
+ {label || schema.title}
+ {(label || schema.title) && required ? "*" : null}
+
+ 0 ? "is-invalid" : ""}
+ list={schema.examples ? `examples_${id}` : undefined}
+ type={type || (schema.type as string)}
+ value={value || value === 0 ? value : ""}
+ onChange={_onChange}
+ onBlur={_onBlur}
+ onFocus={_onFocus}
+
+ />
+ {schema.examples ? (
+
+ ) : null}
+
+ );
+};
+
+export default TextWidget;
diff --git a/packages/bootstrap-4/src/TextWidget/index.ts b/packages/bootstrap-4/src/TextWidget/index.ts
new file mode 100644
index 0000000000..fc1bc51a03
--- /dev/null
+++ b/packages/bootstrap-4/src/TextWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from './TextWidget';
+export * from './TextWidget';
diff --git a/packages/bootstrap-4/src/TextareaWidget/TextareaWidget.tsx b/packages/bootstrap-4/src/TextareaWidget/TextareaWidget.tsx
new file mode 100644
index 0000000000..02f47725c7
--- /dev/null
+++ b/packages/bootstrap-4/src/TextareaWidget/TextareaWidget.tsx
@@ -0,0 +1,70 @@
+import React from "react";
+
+import { WidgetProps } from "@rjsf/core";
+import FormControl from "react-bootstrap/FormControl";
+import InputGroup from "react-bootstrap/InputGroup";
+
+type CustomWidgetProps = WidgetProps & {
+ options: any;
+};
+
+const TextareaWidget = ({
+ id,
+ placeholder,
+ value,
+ required,
+ disabled,
+ autofocus,
+ label,
+ readonly,
+ onBlur,
+ onFocus,
+ onChange,
+ options,
+ schema,
+ rawErrors = [],
+}: CustomWidgetProps) => {
+ const _onChange = ({
+ target: { value },
+ }: React.ChangeEvent) =>
+ onChange(value === "" ? options.emptyValue : value);
+ const _onBlur = ({
+ target: { value },
+ }: React.FocusEvent) => onBlur(id, value);
+ const _onFocus = ({
+ target: { value },
+ }: React.FocusEvent) => onFocus(id, value);
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
+export default TextareaWidget;
diff --git a/packages/bootstrap-4/src/TextareaWidget/index.ts b/packages/bootstrap-4/src/TextareaWidget/index.ts
new file mode 100644
index 0000000000..20e6d8e26b
--- /dev/null
+++ b/packages/bootstrap-4/src/TextareaWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from './TextareaWidget';
+export * from './TextareaWidget';
diff --git a/packages/bootstrap-4/src/Theme/Theme.tsx b/packages/bootstrap-4/src/Theme/Theme.tsx
new file mode 100644
index 0000000000..cfe10f659f
--- /dev/null
+++ b/packages/bootstrap-4/src/Theme/Theme.tsx
@@ -0,0 +1,36 @@
+import React from "react";
+
+import Button from "react-bootstrap/Button";
+
+import ArrayFieldTemplate from "../ArrayFieldTemplate";
+import ErrorList from "../ErrorList";
+import Fields from "../Fields";
+import FieldTemplate from "../FieldTemplate";
+import ObjectFieldTemplate from "../ObjectFieldTemplate";
+import Widgets from "../Widgets";
+
+import { ThemeProps } from "@rjsf/core";
+import { utils } from "@rjsf/core";
+const { getDefaultRegistry } = utils;
+
+const { fields, widgets } = getDefaultRegistry();
+
+const DefaultChildren = () => (
+
+
+
+);
+
+const Theme: ThemeProps = {
+ children: ,
+ ArrayFieldTemplate,
+ fields: { ...fields, ...Fields },
+ FieldTemplate,
+ ObjectFieldTemplate,
+ widgets: { ...widgets, ...Widgets },
+ ErrorList,
+};
+
+export default Theme;
diff --git a/packages/bootstrap-4/src/Theme/index.ts b/packages/bootstrap-4/src/Theme/index.ts
new file mode 100644
index 0000000000..6dfd7fa6e1
--- /dev/null
+++ b/packages/bootstrap-4/src/Theme/index.ts
@@ -0,0 +1,2 @@
+export { default } from './Theme';
+export * from './Theme';
diff --git a/packages/bootstrap-4/src/TitleField/TitleField.tsx b/packages/bootstrap-4/src/TitleField/TitleField.tsx
new file mode 100644
index 0000000000..04289c9bcc
--- /dev/null
+++ b/packages/bootstrap-4/src/TitleField/TitleField.tsx
@@ -0,0 +1,17 @@
+import React from "react";
+import { FieldProps } from "@rjsf/core";
+
+export interface TitleFieldProps extends Partial {
+ title: string;
+}
+
+const TitleField = ({ title }: Partial) => (
+ <>
+
+
{title}
+
+
+ >
+);
+
+export default TitleField;
diff --git a/packages/bootstrap-4/src/TitleField/index.ts b/packages/bootstrap-4/src/TitleField/index.ts
new file mode 100644
index 0000000000..cfa479d034
--- /dev/null
+++ b/packages/bootstrap-4/src/TitleField/index.ts
@@ -0,0 +1,2 @@
+export { default } from './TitleField';
+export * from './TitleField';
diff --git a/packages/bootstrap-4/src/URLWidget/URLWidget.tsx b/packages/bootstrap-4/src/URLWidget/URLWidget.tsx
new file mode 100644
index 0000000000..c9a3bcd81f
--- /dev/null
+++ b/packages/bootstrap-4/src/URLWidget/URLWidget.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+import TextWidget, { TextWidgetProps } from "../TextWidget";
+
+const URLWidget = (props: TextWidgetProps) => {
+ return ;
+};
+
+export default URLWidget;
diff --git a/packages/bootstrap-4/src/URLWidget/index.ts b/packages/bootstrap-4/src/URLWidget/index.ts
new file mode 100644
index 0000000000..d89bae7345
--- /dev/null
+++ b/packages/bootstrap-4/src/URLWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./URLWidget";
+export * from "./URLWidget";
diff --git a/packages/bootstrap-4/src/UpDownWidget/UpDownWidget.tsx b/packages/bootstrap-4/src/UpDownWidget/UpDownWidget.tsx
new file mode 100644
index 0000000000..60056539ac
--- /dev/null
+++ b/packages/bootstrap-4/src/UpDownWidget/UpDownWidget.tsx
@@ -0,0 +1,50 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+
+import { WidgetProps } from "@rjsf/core";
+
+const UpDownWidget = ({
+ id,
+ required,
+ readonly,
+ disabled,
+ label,
+ value,
+ onChange,
+ onBlur,
+ onFocus,
+ autofocus,
+}: WidgetProps) => {
+ const _onChange = ({
+ target: { value },
+ }: React.ChangeEvent) => onChange(value);
+ const _onBlur = ({ target: { value } }: React.FocusEvent) =>
+ onBlur(id, value);
+ const _onFocus = ({
+ target: { value },
+ }: React.FocusEvent) => onFocus(id, value);
+
+ return (
+
+
+ {label}
+ {label && required ? "*" : null}
+
+
+
+ );
+};
+
+export default UpDownWidget;
diff --git a/packages/bootstrap-4/src/UpDownWidget/index.ts b/packages/bootstrap-4/src/UpDownWidget/index.ts
new file mode 100644
index 0000000000..2e2ae55279
--- /dev/null
+++ b/packages/bootstrap-4/src/UpDownWidget/index.ts
@@ -0,0 +1,2 @@
+export { default } from './UpDownWidget';
+export * from './UpDownWidget';
diff --git a/packages/bootstrap-4/src/Widgets/Widgets.ts b/packages/bootstrap-4/src/Widgets/Widgets.ts
new file mode 100644
index 0000000000..3f6876bbff
--- /dev/null
+++ b/packages/bootstrap-4/src/Widgets/Widgets.ts
@@ -0,0 +1,33 @@
+import CheckboxWidget from "../CheckboxWidget/CheckboxWidget";
+import CheckboxesWidget from "../CheckboxesWidget/CheckboxesWidget";
+import ColorWidget from "../ColorWidget/ColorWidget";
+import DateWidget from "../DateWidget/DateWidget";
+import DateTimeWidget from "../DateTimeWidget/DateTimeWidget";
+import EmailWidget from "../EmailWidget/EmailWidget";
+import PasswordWidget from "../PasswordWidget/PasswordWidget";
+import RadioWidget from "../RadioWidget/RadioWidget";
+import RangeWidget from "../RangeWidget/RangeWidget";
+import SelectWidget from "../SelectWidget/SelectWidget";
+import TextareaWidget from "../TextareaWidget/TextareaWidget";
+import TextWidget from "../TextWidget/TextWidget";
+import UpDownWidget from "../UpDownWidget/UpDownWidget";
+import URLWidget from "../URLWidget/URLWidget";
+import FileWidget from "../FileWidget/FileWidget";
+
+export default {
+ CheckboxWidget,
+ CheckboxesWidget,
+ ColorWidget,
+ DateWidget,
+ DateTimeWidget,
+ EmailWidget,
+ PasswordWidget,
+ RadioWidget,
+ RangeWidget,
+ SelectWidget,
+ TextareaWidget,
+ TextWidget,
+ UpDownWidget,
+ URLWidget,
+ FileWidget
+};
diff --git a/packages/bootstrap-4/src/Widgets/index.ts b/packages/bootstrap-4/src/Widgets/index.ts
new file mode 100644
index 0000000000..de857bf557
--- /dev/null
+++ b/packages/bootstrap-4/src/Widgets/index.ts
@@ -0,0 +1,2 @@
+export { default } from './Widgets';
+export * from './Widgets';
diff --git a/packages/bootstrap-4/src/index.ts b/packages/bootstrap-4/src/index.ts
new file mode 100644
index 0000000000..73b55acbf6
--- /dev/null
+++ b/packages/bootstrap-4/src/index.ts
@@ -0,0 +1,10 @@
+import Form from "./Form/Form";
+
+export { default as Fields } from "./Fields";
+export { default as FieldTemplate } from "./FieldTemplate";
+export { default as Form } from "./Form";
+export { default as ObjectFieldTemplate } from "./ObjectFieldTemplate";
+export { default as Theme } from "./Theme";
+export { default as Widgets } from "./Widgets";
+
+export default Form;
diff --git a/packages/bootstrap-4/test/AddButton.test.tsx b/packages/bootstrap-4/test/AddButton.test.tsx
new file mode 100644
index 0000000000..27ac0c9052
--- /dev/null
+++ b/packages/bootstrap-4/test/AddButton.test.tsx
@@ -0,0 +1,18 @@
+import React from "react";
+import AddButton from "../src/AddButton";
+import renderer from "react-test-renderer";
+
+describe("AddButton", () => {
+ test("simple", () => {
+ const tree = renderer
+ .create(
+ void 0}
+ disabled={false}
+ />
+ )
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/Array.test.tsx b/packages/bootstrap-4/test/Array.test.tsx
new file mode 100644
index 0000000000..c21977bdba
--- /dev/null
+++ b/packages/bootstrap-4/test/Array.test.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import Form from "../src/index";
+import { JSONSchema7 } from "json-schema";
+import renderer from "react-test-renderer";
+
+describe("array fields", () => {
+ test("array", () => {
+ const schema: JSONSchema7 = {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ };
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("fixed array", () => {
+ const schema: JSONSchema7 = {
+ type: "array",
+ items: [
+ {
+ type: "string"
+ },
+ {
+ type: "number"
+ }
+ ]
+ };
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("checkboxes", () => {
+ const schema: JSONSchema7 = {
+ type: "array",
+ items: {
+ type: "string",
+ enum: ["a", "b", "c"]
+ },
+ uniqueItems: true
+ };
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
\ No newline at end of file
diff --git a/packages/bootstrap-4/test/ArrayFieldTemplate.test.tsx b/packages/bootstrap-4/test/ArrayFieldTemplate.test.tsx
new file mode 100644
index 0000000000..c6c91ff1d0
--- /dev/null
+++ b/packages/bootstrap-4/test/ArrayFieldTemplate.test.tsx
@@ -0,0 +1,37 @@
+import React from "react";
+import ArrayFieldTemplate from "../src/ArrayFieldTemplate";
+import DescriptionField from "../src/DescriptionField";
+import renderer from "react-test-renderer";
+import TitleField from "../src/TitleField";
+import { mockSchema } from "./helpers/createMocks";
+import { utils } from "@rjsf/core";
+const { getDefaultRegistry } = utils;
+
+describe("ArrayFieldTemplate", () => {
+ test("simple", () => {
+ const tree = renderer
+ .create(
+ }
+ TitleField={() => }
+ canAdd={true}
+ className=""
+ disabled={false}
+ onAddClick={() => void 0}
+ readonly={false}
+ required={false}
+ schema={mockSchema}
+ uiSchema={{}}
+ title=""
+ formContext={{}}
+ formData={{}}
+ registry={{ ...getDefaultRegistry() }}
+ // TODO : isSchema should be fixed here
+ // @ts-ignore
+ idSchema={{}}
+ />
+ )
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/CheckboxWidget.test.tsx b/packages/bootstrap-4/test/CheckboxWidget.test.tsx
new file mode 100644
index 0000000000..8a8d049381
--- /dev/null
+++ b/packages/bootstrap-4/test/CheckboxWidget.test.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import CheckboxWidget from "../src/CheckboxWidget";
+import renderer from "react-test-renderer";
+import { makeWidgetMockProps } from "./helpers/createMocks";
+
+describe("CheckboxWidget", () => {
+ test("simple", () => {
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/CheckboxesWidget.test.tsx b/packages/bootstrap-4/test/CheckboxesWidget.test.tsx
new file mode 100644
index 0000000000..775375f177
--- /dev/null
+++ b/packages/bootstrap-4/test/CheckboxesWidget.test.tsx
@@ -0,0 +1,33 @@
+import React from "react";
+import CheckboxesWidget from "../src/CheckboxesWidget";
+import renderer from "react-test-renderer";
+import { makeWidgetMockProps } from "./helpers/createMocks";
+
+describe("CheckboxesWidget", () => {
+ test("simple", () => {
+ const tree = renderer
+ .create(
+
+ )
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("inline", () => {
+ const tree = renderer
+ .create(
+
+ )
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/ColorWidget.test.tsx b/packages/bootstrap-4/test/ColorWidget.test.tsx
new file mode 100644
index 0000000000..0681dc71f6
--- /dev/null
+++ b/packages/bootstrap-4/test/ColorWidget.test.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import ColorWidget from "../src/ColorWidget";
+import renderer from "react-test-renderer";
+import { makeWidgetMockProps } from "./helpers/createMocks";
+
+describe("ColorWidget", () => {
+ test("simple", () => {
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/DateTimeWidget.test.tsx b/packages/bootstrap-4/test/DateTimeWidget.test.tsx
new file mode 100644
index 0000000000..8c06d0be8e
--- /dev/null
+++ b/packages/bootstrap-4/test/DateTimeWidget.test.tsx
@@ -0,0 +1,15 @@
+import React from "react";
+import DateTimeWidget from "../src/DateTimeWidget";
+import renderer from "react-test-renderer";
+import { makeWidgetMockProps } from "./helpers/createMocks";
+
+describe("DateTimeWidget", () => {
+ test("simple", () => {
+ const tree = renderer
+ .create(
+
+ )
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/DateWidget.test.tsx b/packages/bootstrap-4/test/DateWidget.test.tsx
new file mode 100644
index 0000000000..996a422613
--- /dev/null
+++ b/packages/bootstrap-4/test/DateWidget.test.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import DateWidget from "../src/DateWidget";
+import renderer from "react-test-renderer";
+import { makeWidgetMockProps } from "./helpers/createMocks";
+
+describe("DateWidget", () => {
+ test("simple", () => {
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/DescriptionField.test.tsx b/packages/bootstrap-4/test/DescriptionField.test.tsx
new file mode 100644
index 0000000000..fbfc4808de
--- /dev/null
+++ b/packages/bootstrap-4/test/DescriptionField.test.tsx
@@ -0,0 +1,17 @@
+import React from "react";
+import DescriptionField from "../src/DescriptionField";
+import renderer from "react-test-renderer";
+
+describe("DescriptionField", () => {
+ test("should return null when no description as a props is passed", () => {
+ const tree = renderer.create().toJSON();
+ expect(tree).toBe(null);
+ });
+
+ test("should return h2 element when description is being passed as props", () => {
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/Form.test.tsx b/packages/bootstrap-4/test/Form.test.tsx
new file mode 100644
index 0000000000..7fb76f4841
--- /dev/null
+++ b/packages/bootstrap-4/test/Form.test.tsx
@@ -0,0 +1,200 @@
+import React from "react";
+import Form from "../src/index";
+import { JSONSchema7 } from "json-schema";
+import renderer from "react-test-renderer";
+import { UiSchema } from "@rjsf/core";
+
+describe("single fields", () => {
+ describe("string field", () => {
+ test("regular", () => {
+ const schema: JSONSchema7 = {
+ type: "string",
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("format email", () => {
+ const schema: JSONSchema7 = {
+ type: "string",
+ format: "email",
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("format uri", () => {
+ const schema: JSONSchema7 = {
+ type: "string",
+ format: "uri",
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("format data-url", () => {
+ const schema: JSONSchema7 = {
+ type: "string",
+ format: "data-url",
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ });
+ test("string field with placeholder", () => {
+ const schema: JSONSchema7 = {
+ type: "string",
+ };
+ const uiSchema: UiSchema = {
+ "ui:placeholder": "placeholder",
+ };
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("number field", () => {
+ const schema: JSONSchema7 = {
+ type: "number",
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("number field 0", () => {
+ const schema: JSONSchema7 = {
+ type: "number",
+ };
+ const formData = 0;
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("null field", () => {
+ const schema: JSONSchema7 = {
+ type: "null",
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("unsupported field", () => {
+ const schema: JSONSchema7 = {
+ type: undefined,
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("format color", () => {
+ const schema: JSONSchema7 = {
+ type: "string",
+ format: "color",
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("format date", () => {
+ const schema: JSONSchema7 = {
+ type: "string",
+ format: "date",
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("format datetime", () => {
+ const schema: JSONSchema7 = {
+ type: "string",
+ format: "datetime",
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("password field", () => {
+ const schema: JSONSchema7 = {
+ type: "string",
+ };
+ const uiSchema: UiSchema = {
+ "ui:widget": "password",
+ };
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("textarea field", () => {
+ const schema: JSONSchema7 = {
+ type: "string",
+ };
+ const uiSchema: UiSchema = {
+ "ui:widget": "textarea",
+ };
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("select field", () => {
+ const schema: JSONSchema7 = {
+ type: "string",
+ enum: ["foo", "bar"],
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("checkbox field", () => {
+ const schema: JSONSchema7 = {
+ type: "boolean",
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("checkbox field", () => {
+ const schema: JSONSchema7 = {
+ type: "boolean",
+ };
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("checkboxes field", () => {
+ const schema: JSONSchema7 = {
+ type: "array",
+ items: {
+ type: "string",
+ enum: ["foo", "bar", "fuzz", "qux"],
+ },
+ uniqueItems: true,
+ };
+ const uiSchema: UiSchema = {
+ "ui:widget": "checkboxes",
+ };
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("radio field", () => {
+ const schema: JSONSchema7 = {
+ type: "boolean",
+ };
+ const uiSchema: UiSchema = {
+ "ui:widget": "radio",
+ };
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ // There is a bug in the Material UI component that prevents this from working.
+ // Error: `TypeError: Cannot read property 'addEventListener' of null`
+ // From: https://github.com/mui-org/material-ui/blob/v4.5.2/packages/material-ui/src/Slider/Slider.js#L622
+ test.skip("slider field", () => {
+ const schema: JSONSchema7 = {
+ type: "integer",
+ minimum: 42,
+ maximum: 100,
+ };
+ const uiSchema: UiSchema = {
+ "ui:widget": "range",
+ };
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/IconButton.test.tsx b/packages/bootstrap-4/test/IconButton.test.tsx
new file mode 100644
index 0000000000..d5ea5065c9
--- /dev/null
+++ b/packages/bootstrap-4/test/IconButton.test.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+import IconButton from "../src/IconButton";
+import renderer from "react-test-renderer";
+
+describe("IconButton", () => {
+ test("simple", () => {
+ const tree = renderer
+ .create(
+ void 0}
+ disabled={false}
+ />
+ )
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ test("disable", () => {
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/Object.test.tsx b/packages/bootstrap-4/test/Object.test.tsx
new file mode 100644
index 0000000000..253b5574be
--- /dev/null
+++ b/packages/bootstrap-4/test/Object.test.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import Form from "../src/index";
+import { JSONSchema7 } from "json-schema";
+import renderer from "react-test-renderer";
+
+describe("object fields", () => {
+ test("object", () => {
+ const schema: JSONSchema7 = {
+ type: "object",
+ properties: {
+ a: {type: "string"},
+ b: {type: "number"}
+ }
+ };
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
\ No newline at end of file
diff --git a/packages/bootstrap-4/test/TextAreaWidget.test.tsx b/packages/bootstrap-4/test/TextAreaWidget.test.tsx
new file mode 100644
index 0000000000..0a60c6f471
--- /dev/null
+++ b/packages/bootstrap-4/test/TextAreaWidget.test.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+import TextareaWidget from "../src/TextareaWidget";
+import { makeWidgetMockProps } from "./helpers/createMocks";
+import renderer from "react-test-renderer";
+
+describe("TextareaWidget", () => {
+ test("simple without errors", () => {
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+ test("simple with errors", () => {
+ const tree = renderer
+ .create(
+
+ )
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ test("simple without required", () => {
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/TextWidget.test.tsx b/packages/bootstrap-4/test/TextWidget.test.tsx
new file mode 100644
index 0000000000..94251b424f
--- /dev/null
+++ b/packages/bootstrap-4/test/TextWidget.test.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import TextWidget from "../src/TextWidget";
+import renderer from "react-test-renderer";
+import { makeWidgetMockProps } from "./helpers/createMocks";
+
+describe("TextWidget", () => {
+ test("simple", () => {
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/TitleField.test.tsx b/packages/bootstrap-4/test/TitleField.test.tsx
new file mode 100644
index 0000000000..07c1211ad4
--- /dev/null
+++ b/packages/bootstrap-4/test/TitleField.test.tsx
@@ -0,0 +1,10 @@
+import React from "react";
+import TitleField from "../src/TitleField";
+import renderer from "react-test-renderer";
+
+describe("TitleField", () => {
+ test("simple", () => {
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/packages/bootstrap-4/test/__snapshots__/AddButton.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/AddButton.test.tsx.snap
new file mode 100644
index 0000000000..502640b09b
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/AddButton.test.tsx.snap
@@ -0,0 +1,44 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AddButton simple 1`] = `
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/Array.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/Array.test.tsx.snap
new file mode 100644
index 0000000000..4fc9300ef4
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/Array.test.tsx.snap
@@ -0,0 +1,268 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`array fields array 1`] = `
+
+`;
+
+exports[`array fields checkboxes 1`] = `
+
+`;
+
+exports[`array fields fixed array 1`] = `
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/ArrayFieldTemplate.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/ArrayFieldTemplate.test.tsx.snap
new file mode 100644
index 0000000000..9f4723fb47
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/ArrayFieldTemplate.test.tsx.snap
@@ -0,0 +1,74 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ArrayFieldTemplate simple 1`] = `
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/CheckboxWidget.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/CheckboxWidget.test.tsx.snap
new file mode 100644
index 0000000000..571040a70c
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/CheckboxWidget.test.tsx.snap
@@ -0,0 +1,32 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CheckboxWidget simple 1`] = `
+
+
+
+
+
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/CheckboxesWidget.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/CheckboxesWidget.test.tsx.snap
new file mode 100644
index 0000000000..e822c891d7
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/CheckboxesWidget.test.tsx.snap
@@ -0,0 +1,73 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CheckboxesWidget inline 1`] = `
+Array [
+ ,
+ ,
+]
+`;
+
+exports[`CheckboxesWidget simple 1`] = `
+Array [
+ ,
+ ,
+]
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/ColorWidget.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/ColorWidget.test.tsx.snap
new file mode 100644
index 0000000000..550693ea3a
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/ColorWidget.test.tsx.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ColorWidget simple 1`] = `
+
+
+
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/DateTimeWidget.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/DateTimeWidget.test.tsx.snap
new file mode 100644
index 0000000000..e25f2c4d49
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/DateTimeWidget.test.tsx.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`DateTimeWidget simple 1`] = `
+
+
+
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/DateWidget.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/DateWidget.test.tsx.snap
new file mode 100644
index 0000000000..d4b2800fc1
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/DateWidget.test.tsx.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`DateWidget simple 1`] = `
+
+
+
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/DescriptionField.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/DescriptionField.test.tsx.snap
new file mode 100644
index 0000000000..af50c1de45
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/DescriptionField.test.tsx.snap
@@ -0,0 +1,11 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`DescriptionField should return h2 element when description is being passed as props 1`] = `
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/Form.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/Form.test.tsx.snap
new file mode 100644
index 0000000000..5762f0bc00
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/Form.test.tsx.snap
@@ -0,0 +1,920 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`single fields checkbox field 1`] = `
+
+`;
+
+exports[`single fields checkbox field 2`] = `
+
+`;
+
+exports[`single fields checkboxes field 1`] = `
+
+`;
+
+exports[`single fields format color 1`] = `
+
+`;
+
+exports[`single fields format date 1`] = `
+
+`;
+
+exports[`single fields format datetime 1`] = `
+
+`;
+
+exports[`single fields null field 1`] = `
+
+`;
+
+exports[`single fields number field 0 1`] = `
+
+`;
+
+exports[`single fields number field 1`] = `
+
+`;
+
+exports[`single fields password field 1`] = `
+
+`;
+
+exports[`single fields radio field 1`] = `
+
+`;
+
+exports[`single fields select field 1`] = `
+
+`;
+
+exports[`single fields string field format data-url 1`] = `
+
+`;
+
+exports[`single fields string field format email 1`] = `
+
+`;
+
+exports[`single fields string field format uri 1`] = `
+
+`;
+
+exports[`single fields string field regular 1`] = `
+
+`;
+
+exports[`single fields string field with placeholder 1`] = `
+
+`;
+
+exports[`single fields textarea field 1`] = `
+
+`;
+
+exports[`single fields unsupported field 1`] = `
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/IconButton.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/IconButton.test.tsx.snap
new file mode 100644
index 0000000000..d4a1e76a22
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/IconButton.test.tsx.snap
@@ -0,0 +1,59 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`IconButton disable 1`] = `
+
+`;
+
+exports[`IconButton simple 1`] = `
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/Object.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/Object.test.tsx.snap
new file mode 100644
index 0000000000..bb8226ca56
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/Object.test.tsx.snap
@@ -0,0 +1,107 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`object fields object 1`] = `
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/TextAreaWidget.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/TextAreaWidget.test.tsx.snap
new file mode 100644
index 0000000000..6d287d6aa3
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/TextAreaWidget.test.tsx.snap
@@ -0,0 +1,96 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TextareaWidget simple with errors 1`] = `
+Array [
+ ,
+
+
+
,
+]
+`;
+
+exports[`TextareaWidget simple without errors 1`] = `
+Array [
+ ,
+
+
+
,
+]
+`;
+
+exports[`TextareaWidget simple without required 1`] = `
+Array [
+ ,
+
+
+
,
+]
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/TextWidget.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/TextWidget.test.tsx.snap
new file mode 100644
index 0000000000..39fdf7dd07
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/TextWidget.test.tsx.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TextWidget simple 1`] = `
+
+
+
+
+`;
diff --git a/packages/bootstrap-4/test/__snapshots__/TitleField.test.tsx.snap b/packages/bootstrap-4/test/__snapshots__/TitleField.test.tsx.snap
new file mode 100644
index 0000000000..9c478aa038
--- /dev/null
+++ b/packages/bootstrap-4/test/__snapshots__/TitleField.test.tsx.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TitleField simple 1`] = `
+
+
+ Hello
+
+
+
+`;
diff --git a/packages/bootstrap-4/test/helpers/createMocks.ts b/packages/bootstrap-4/test/helpers/createMocks.ts
new file mode 100644
index 0000000000..cd069e0931
--- /dev/null
+++ b/packages/bootstrap-4/test/helpers/createMocks.ts
@@ -0,0 +1,35 @@
+import { WidgetProps } from "@rjsf/core";
+import { JSONSchema7 } from "json-schema";
+
+export const mockSchema: JSONSchema7 = {
+ type: "array",
+ items: {
+ type: "string",
+ },
+};
+
+export const mockEventHandlers = (): void => void 0;
+
+export function makeWidgetMockProps(
+ props: Partial = {}
+): WidgetProps {
+ return {
+ uiSchema: {},
+ schema: mockSchema,
+ required: true,
+ disabled: false,
+ readonly: true,
+ autofocus: true,
+ label: "Some simple label",
+ onChange: mockEventHandlers,
+ onBlur: mockEventHandlers,
+ onFocus: mockEventHandlers,
+ multiple: false,
+ rawErrors: [""],
+ value: "value",
+ options: {},
+ formContext: {},
+ id: "_id",
+ ...props,
+ };
+}
diff --git a/packages/bootstrap-4/tsconfig.json b/packages/bootstrap-4/tsconfig.json
new file mode 100644
index 0000000000..8d3b50e5bf
--- /dev/null
+++ b/packages/bootstrap-4/tsconfig.json
@@ -0,0 +1,32 @@
+{
+ "include": ["src", "types"],
+ "compilerOptions": {
+ "target": "es5",
+ "module": "esnext",
+ "lib": ["dom", "esnext"],
+ "importHelpers": true,
+ "declaration": true,
+ "sourceMap": true,
+ "rootDir": "./src",
+ "strict": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "strictFunctionTypes": true,
+ "strictPropertyInitialization": true,
+ "noImplicitThis": true,
+ "alwaysStrict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "allowSyntheticDefaultImports": true,
+ "moduleResolution": "node",
+ "baseUrl": "./",
+ "paths": {
+ "*": ["src/*", "node_modules/*"]
+ },
+ "jsx": "react",
+ "esModuleInterop": true,
+ "allowJs": true
+ }
+}
diff --git a/packages/material-ui/src/IconButton/IconButton.tsx b/packages/material-ui/src/IconButton/IconButton.tsx
index b5046a42bf..3ad7f2f195 100644
--- a/packages/material-ui/src/IconButton/IconButton.tsx
+++ b/packages/material-ui/src/IconButton/IconButton.tsx
@@ -1,17 +1,17 @@
-import React from 'react';
+import React from "react";
-import Button from '@material-ui/core/Button';
-import Add from '@material-ui/icons/Add';
-import ArrowUpward from '@material-ui/icons/ArrowUpward';
-import ArrowDownward from '@material-ui/icons/ArrowDownward';
-import Remove from '@material-ui/icons/Remove';
-import { IconButtonProps as MuiIconButtonProps } from '@material-ui/core/IconButton';
+import Button from "@material-ui/core/Button";
+import Add from "@material-ui/icons/Add";
+import ArrowUpward from "@material-ui/icons/ArrowUpward";
+import ArrowDownward from "@material-ui/icons/ArrowDownward";
+import Remove from "@material-ui/icons/Remove";
+import { IconButtonProps as MuiIconButtonProps } from "@material-ui/core/IconButton";
const mappings: any = {
remove: ,
plus: ,
- 'arrow-up': ,
- 'arrow-down': ,
+ "arrow-up": ,
+ "arrow-down": ,
};
type IconButtonProps = MuiIconButtonProps & {
diff --git a/packages/playground/package.json b/packages/playground/package.json
index 6abce0dd74..904c1e5022 100644
--- a/packages/playground/package.json
+++ b/packages/playground/package.json
@@ -42,6 +42,7 @@
"@fluentui/react": "^7.121.5",
"@material-ui/core": "^4.9.4",
"@rjsf/antd": "^2.2.2",
+ "@rjsf/bootstrap-4": "^2.2.2",
"@rjsf/core": "^2.2.2",
"@rjsf/fluent-ui": "^2.2.2",
"@rjsf/material-ui": "^2.2.2",
diff --git a/packages/playground/src/index.js b/packages/playground/src/index.js
index d570bf2b0c..0e6a80121b 100644
--- a/packages/playground/src/index.js
+++ b/packages/playground/src/index.js
@@ -3,6 +3,7 @@ import { Theme as MuiTheme } from "@rjsf/material-ui";
import { Theme as FluentUITheme } from "@rjsf/fluent-ui";
import { Theme as SuiTheme } from "@rjsf/semantic-ui";
import { Theme as AntdTheme } from "@rjsf/antd";
+import { Theme as Bootstrap4Theme } from "@rjsf/bootstrap-4";
import Playground from "./app";
import { render } from "react-dom";
@@ -104,6 +105,11 @@ const themes = {
"//static2.sharepointonline.com/files/fabric/office-ui-fabric-core/11.0.0/css/fabric.min.css",
theme: FluentUITheme,
},
+ "bootstrap-4": {
+ stylesheet:
+ "https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css",
+ theme: Bootstrap4Theme,
+ },
};
render(, document.getElementById("app"));