S=^n|8kjaFf(q$R(VCS?5rkeZH zVE9$roMuO3>y1}83knO5N*y1g|H3yK2ld^*AL2YGr1_kDOlpAAH_BWbL^E-e1 z1JHMWRtO5zm#f$W-Zhy+!u3Kx$!z;hp{7`B6>K2S3KV0r(;VQWNGYtXs}ly4hEkIK zI!BicoTRI?BJrSt%H6U$hQ0Ar(o*Cxnr*#=2Lse2Q*hqdIXU!wV5KOmShyx9Z==IS z#_8t1=3I{6M}NORc1W#0QcY+^EhmZuAn#GIF>K4e_1qa`@7-4oy3Gm~ZuC!d4L0__ z5@!#5s08Gxj-5Dhf}8Xpjrpj#AT>ah+iBUG=X8u7)2*!ms ML9Vp`n<;3S `#UQN%GbKT!qoV_j)&;Fbtcp5xD7!?t z42l@k1rtL<$FyK&uD-A=qIxyMQ7xoN)vw(p1+lvNzV~mV;jBo6;4~!~W17{MoQA|! zUk|s7kBL&XYZKD>$3?Ym&4lWWpMaPiUb4+mWRuT-a;xbMP@VhUp Ef{t*$0=Q?vNX-xlBz0tf zrcVzHFa`ndJS6!k_+SIrw#G)#k00v+#p+! CqMqB2sO2}TpR`FYD ;GScmqvIkY%V;-q7g7w`YS >!!!^_1ChV{Bts}p8bLRc=Z(k)w#(d>1NZLFKp156qGDoB6tZ^> zWo4j1Shr(UH3O@lRC8 8e)7(Zvhmo3|zwaNs`rA;|CUlM0ug8m?}bEIDz^42Z`JH|kR}L#ly> z8lN1Yl|{A1k>Lz*Q oREXA6>@_qW46&F+>SYvB|{2q|?OHW$se4y%Bb>Krq z=BS!pEIP^?IUo~5tDx+}#U}?lmGKixN_t(q!z9e0NU)dY qhGU_kHD2*#vYsvX&+R%p)X<;;o >NxKlVqK*kAGVw^~T5tx1X`0+kbg^e~fHK~3_ zLdCfctSr%VzZy5*Q0>3)P|M|KXSs#4Hn{vPo)QTHCBxDMrkAY&D%km)>C5*bGuD7? z=lWtP;*9DI1z{utV1@P)Vawx=ALkk8L~c>hLFBA)?boMcGW&K;IH{jqP*5P8xj+@n zL35-MB-~U&8l6_|FB@n&4Xz=d4)W=9O`vQZCnGRq53nNIYB}unNcqQ&b&oC#q#@C5 z0i{u{mfs>;P`UwNgmvV*r!PKXDOFWfZ~<^?n82yt zfzx2i1i{7yh8DrJT25af`r8*isWV<2cgz@Uy$Nl!EK;bu);4!`a*Ve!E$6+hXBO4^ zhLT!GN5|mcV7)<;aoB-@0l$zCt@Ick8sga<6yBl?H0Oh^MJF$EHVgKzVBZt7vr^$- zp19!#a9Krau)&{REeZqVs#0o!b|P`4faUZg`-Pz>-o1;1!kDC@2`eul(aiL(*M9{9 za6Ssg*VnsGojRrER^y`bOD~@$et)As>Mi~A?e~UrOp9fsg dBxvvGo}FGzkCS(dO{4i5WL=o5V@uY9FJbVvFt78VxXyE(+4?a;{O z%sL|CNB3s3X@aXS>{8Z+Tw*;-V-&dyHd zE#`cjv^&rl)L%p5{)epFvO|3 ;X ztu^L?Tu)aMaCd@CWJP5h=2!0;?wA-KKV{!(kFLW(4oL0Qvai3te{Fm|WF9|9>&NHk zIZU~;n?vtBE7~ybWwWQHr6o8hNS#+9l+e6f9|UjSuUTPQ>}MHVfqaoxL@z6(X P4 -n*8K3P#z g|`131o5BB3m>$!=@XzY4(BHX2s7R zxJ%as^UhDrh#>m)X_?Gl(tA|cy~*>Pqjx%9ndK-5-aczWxRiqKM8iD=nu@K`n zG+03^vN+sNm5@NU<5UEC*Q#*OP52!9O`~PVo=6^|2W1lkafc2a-5a3VR}o{syplpX zYrp-Cj9k5q@qmT9DA6h~ L)H%e_aN!08XUFJ#T*IowE zhJEX4DbloUV?*=8to2B){cR?f6WsVfg_Lw6GZ#doZ8SvseqY}y`aPagfl43)1HAEb z$r0ARxX1X?rpUDE91@)w`23NtfEuxSOEg>4sh6G-7E}!Z>)fQ3dwH-lErJQjZSLz6 zMGi;ldN`eV?I+Ow;i-F#DFN2_F|+cfNjuHRX5+n{ox%uM93#xkdx&NOUy01nWL3 z+Si^7=k;0ms9N8{A*|318J&0d1~gxaF0I`eQU37Vujw+)G=^$+54hQ6 97z=XQk#p{6fWahyhog8Wt_+(d=w>CPhK3g_)-9lk6uh;G2yL5-@b)G5h_9pr9a# zg@1YR9%O)5*FS%oe(M)*Sz?CG(e)0?mx@AF!A#}S)gRLAwQ5j1x~2n@JC*O0N?o4A z0Jwn;`n$j1&klRVa#}SUJ<8@YifAxAv8$(JK(0N`Q_(saOk -Vm91h_0M$<7~ya#IIR}#*1BD z=mA;FHp2(R%lCJCY)nnny#C#AZoLE3lg^FyQ*yP|YMZ9fmpM2#<8vpnC9hn6z$f-w9Bc{Q$qMh%W-S_CBT~^s4wwTX(B}n*HP@@T9n2XuzBDn _FZUteSHTu{Oz}qHy>4M~-*X{&y zMB2+eYs~OyjDUG6A$#?i5yzoXGrGkG={q ` ghOWT%2o>z;AR4I?m+(R zw`X_#<=YQ__N ofle*f)Zy0st|8FyjB4T1uW9G)M$Uc6rl8E1Ns zP8pY*qDy$~_Ku)Q@bTT5rZK0W;1Hki1TkL7&=04f;H)-7=*a%#)R_Yc>3`kxT~NSW z95`8)3P8M+p<-iV5^{2K7!2qn#wSDvM@L$QjF%3g19wB&lvf%=2jj3-;39wLB!e2r zK+0 ij( z9$27d(vUbX O&D8kO6+i! CrW>z?fcnBFIAv>T9xB>R07l`==QcaFJ2gmDgY gVJbrPn7{cD1(B6?o`IySuaR9QC6}f}JzQYm}PoPks!Vfz)3j_24dG=IGn8 z_4XU~Qlx3C5xN3!F`-70&HxM}(IEgjJ+-es4RZAdAeX-e!chZOB(u^+%X j|tepWfc^pL}*pj zzutS_edBn3Y1J~0ahhgZm4?T-;nzb|LH!l>C4jy2M0)Q>$z85NV2Y8lkQhKxkOcLd z&w?r3LNR_*2hsSsgDyGl+An-Z`;zg7MqU!+?uv>EWNEY) ~l^Ha^TGr$4kanFWUgIS1r?NO0=`j;=P#0O49 zuXuUe3T&a2efEpb?qzF=kakSM*tA+Gdt7Z&6Vy0Q6^VUxRslwrgDJoIo73E1X>+b| z8{*?fySgFfbAAXe+3#ROY-2c5=XK!lc&^Qu@cJCc{#-@{X4DzSbtkf6wv9GBi^P8R zAOSv%Xo6jmaV~BlmEzqHPriX;n@Hg#0J7-`aU~@s{a0=ZVKAb1WE=Bu&nj4rV$0(f zQEuyc+nF<>Rr_U|fMO&zXCZWuewc}bM$9pSreQ5?8tedPZwuHf&l;-)I02pjfx*40 z$o#;<<6y&)7#7*znZA-wzJG{McAqc@2zo1Ko9&Mpi=3};1wbJK3+K=%(v~rA{J=FM z0SvsBK^CP@ItUF949?NnRa^oWq$blbV>V3We)1BO*X+&WLLi9b>waL3BhV{o31GUp z`J+4N3BdK!I-W<}r ZHg*xRbI@|GK|;iV-~a^)Uf z 285WMm8;i*iIqcJpSS}5&i&NB#jC(k_R zjHE*qE`!+L4+RVCQIT3C_9&WeXvIWZPfu}b_*7t0yE=NT)!9*X+BkL$opiAWWW8g< zxP^WBF~$)4cLhU2LTC)Uj@C3Z6m3R5=>8*k=RwrE{F9BtRl0V4_ZFUrx 7vWvhPN){+&BieX0k3@jaJ3l^ z{7(B8nyo-FjhpnrV{(0yaAFqX2_zsL$vo>3-G~bAB2`5pA4ggj8G(_*Ks%1q^8EDD zGg81hf-ub;tKcL9io6TG8QuhpPXGIk=gmM#Cu$D?NsT>GQNjHi=S;{wkR%GN1jOwe zSHIJ*e}@e*f&nMS>-W$gIeS2WPab0-@Gp0K|N9w}?CVMqqYuEw%o;Cx^Sml5JNt|o zO{9j RDAjmQ1tkAJ;^YzgVaZV<%_$8HH!6Gd=C zHiMsC@%36Bc? )v1+K0A<D1b1Ihib?;Y&>(F!CSnxR5C~`7nC5|LSiW5I3*wf z_RkS=z=D-ZGOU1UUFuiEqQQHp!U$#O-n{i>Wu>cC3tqpwp95jsbP8Kfr@!&|U%#zS zN8t4AL}GIRHJdv+l$;4&oVrDW7kWSVHoM07Y`XMP9YhUV7g%2Yhz9<26de5wZ>4bX zP9t?+etNVXIk13trpE9iFK_TY<@5wR )AGr7A>RB3evdyO(gl A4MwTNa|vKYG8(lg=adMV_HFebtlA)(^MCF0`+i)$c@Vm{ z?gKfiD}!;Gn&deXz?rmPSfLz&f#2V#ReBuPpyCh+oQ?l|g;ym-G@eC*RRaQ!bJvPB zETV9JRUako)>2$#arw3W=%^d=QZ26Iq+Wo(zY2(uXGJHpQ48kc5mKg=O}IzQwgaF1 zP;zzyFR6fz3^Ihut$is2Wq>#mZd@|3bmgdAAb{o4zuAL#9TvdYW#p#SU%4ekiZqqS zv^+x|);i;UK7OwV1@~mUV=S-x(pdzTZr|0|+p9%RczPB9V{urmaJl#`s|X&i8i}TF ziyfUd-rF?y4izUt&F``1E=oloI;#eqDSshG%k+4*m)&D$1bg-xpA `O{Zp{AKN4po31!-YC$3B5gy*CocUm3p@zp6Q|& z0Po&eEX;p|D(9@`jBlZ70M3~{q-7DS&MeWa Tfj%OSI8vuO#NJl2JKitot zBIuK!;7lEMP1ZV98?vX`jr}5Bs^E8ioUPj&5;E6cz!oI8Qox2528ga`wNc(h@r^^Q z!y?*hT$HIjxJadsACi3hB>fdwE|FiqfHk8-f|Pyqb;&)9H{pculvj%&xX?K{fS)|x zHqO|NH_6mY$(A2boph2{vrz^#ux`>R`)@)ZFt%QgvW*=&4$1C~v$MHwYWJ&9Y(x|n z_b4NVEB)~v6wF2T(|I|VG%N>~ky4`%+&WGSaN78x@q${I6xC3#Yk_&~evuaNqdsuv zfEnLkK5ea9Y88~Fp)!7Keg0)Bti&rWZJXQ1v)S?kW~t;XnejWUeDKFTlbl4ggU(DY zwHU$AdWVcb?+@R-NmqU22TcqYenyCC0oOW?bIV}7Wwt%aWZB AR)PovVw`J zsb>3od7nO~)xvN}XL<0fNI-iT!9qmK4c?1C@zAw2gG)#7#?=*GdI4+1xFRsixP?}* zCKX@*isZqPY0BkSAg}hRUr#_)qDv;n#wxWC9Jb9mS}%-gXI((o7t|QPJH?5d@ovum zl)I$B!NEMSjt|5SsNJEb6Ih{akHGcfD)bb$4Tr9);*!WY{aifkmpJx2MhqlCwEZ2f z3&u-)S)$eSL;Uhw3#p~^;7=OA?`pK4ltDv-nqF-lJ5+BeOvB8~OgmUL9tZpb0_b~V zr;X AY2FqftekeM`y`KLC2vGL=&1SC#MF;v3j|iNt zm!C@02jYzP^uVy1{`r3y1dbh05;P->Q2lT?8Zv%YE?U!@V2MvK8arL@)8{fLH>B t0&E|nTB?)NieMbLHiom6x0 z#x=|I=(~5kf2;9olG+%XoK}qli%`Hxd>U6jqknw p!XOQnph zH6G@wo$Wl|8Qlm#Ae6dc#lgbt4w~fU4esW;(l_Zdg7+TOY%w^z#(5N1U3 yo y;KjTK$kPhXmrw+fc*^T41cpSJj~%pKq}#)F0jAX)M<|b`=H;pFI*L| zk^F!J-eaCHF7FVE9HFbNsybz?L$WW>@}&~}1gB4H!8GDmXl1xBQ>JWw@ir)XtTn(h zXvI`UfTr{`+E>o$EBE6%(j^;O`h ww1%At!$d!27!u;_wa0YLikYai{cwi@H9k^M zM&Huw9UF&-fS}x&E0Ps$y;{N>^sQ%%hc u(o0s{}k#H$HAr6n6eQP`O!O7j4ql z_u+f=7Mf}6<)P&2LM!6~%{dLF&$f>lFf4rbGpVa)l{!^R-IBLtu$JjvX(mW>(G8oL zSJOw!!pJ+$T%LgOZSJq%zDCImk~hQcsZ}m2a _CsMqejdAy@gJUsV>`o1CybKJ&crcHSqWzDCDtzkS-+EA#nft!#nOk zCOD?K_>7Fk6iC&$`oe;@&3*K9#?Kxz9)HM2S6@KhIH~slhVkv(_e2v`tV;se0=dhA z4>|BnIG&kTn&)-$o (-JL2m=T+@%Sw}ykcI; zRK|%vtA95D;gs=mCa(StLfGd)KYjfk<2x$+v<9xw(a;9tMcwX{Prf#cV!({mSs59k z0;T R$Dmec)M`&ZtQ@SQ7cDo5kt2JD|wq*?4AyCbhZ;;NQ& zu9MqZ$zkLIs@gT0VL2Z4x<`YjAcbo2gyq|6C=hJ>`eKmtacJdZS75q1m-~>0JGOn; z)|o+C?{*48xO}Mz+>T>1t^s-=?B7 }6wwG?yQh{4O_C*_A_i0kuoBo!c=_S?= z9NOO?($!=uP-Xo(<3mnnH&J3svwVyZm(@};FPQggbWc~dS+^ZU%FpzX%xeX@s%$uy zvh#Dd9w_U#e(n}ug*~N@qB><-!8&ec)36UgRgm6v2pVNX-K5L|P)5o(KDK>tI1YOC zaYE|{b7iNRTE5C?eD#kL!rm{bWPHI*jOkEe)X^TCgBsqtB@isfgtYAgp?V`ulmDN5 zyV&@dfU6@Kw1E3sEyfKq+_3bs|E>}RHvLH~ kb@C$3Uy JbO)4Pq2+a3v` zU&2j5Ir%n_+TM7$?_@pCBQ@OQbF8)jXe6Xb5qAvm+!PO7_sKkf@H>8+7ddCZ^_Y-f zeB*BRzAVEzq>Ay;&gk0w$Zdf)owr1Ge8v?TSpz?M$+~yLtM Pv;0~4WOZXK# z9^yNB@|aMirFk6X$0)f3snd0w{DeTdYJA(Gd@amaUge$mLtGqyi@%iIJ;j(v{k Na( zjsM`QLum7Vd8L?(7&&f42pmL3&)o-gi}%U8!o3?W@96@b9s)DF?LC2zUJG(q3P6*1 zQUpZUJ#s$;ei+w0RjVwPDosxICiKmr0+jp`+sV2*c`HzV+(c@x{-2kgeoELK)I*({ zvUOct0jhNc?X=M7UkqHhZ^ti5y|~_3@!#{AJ~te~@i0Dzg`a@vqw&t9@nez0ud6dm z6#ZAel^46@k^Jv}#O}1QXMJREaI5xT%!{_!BL1IS?)a|#$tFra-u}CBy_aLc;fH6( zajp%a4XMlW%OHq6e-^|d!?!7ud3s|`mXo;BU0>IgCNw9E{4z~=bv0nEL5JheH%?9_ z<`xd(wv4wrL4cA$NrOih@YoE(W-Or3sEaP+69iH()mA9XS;>U{ukXDqkLy${+nN{{ zFn&O9oT5t|UeleO(pv?CD_WgDsTIN)_m!QdSXmGs+oT^NiiTI@ZPu$#$XwyKLZn eg{Zy@0G{)BAvq;QJvP(T zg)!}{?jTI=sPO4mpxE(wM`+4QH@APyap$Guj@@Wc`P49eaR6OssV6ASuc0oa$KUwA z8Jwgrzs7!h404C{2eXVpfP7U=E}1!mbZY?@5I}C;3D5p2Kad;4ka> zCM3owE1X*_Pg;~Ca#h bx Ng4D^WR7#Zi;yWSp {1k<17Sq`43&?p61QE9Y!BpSyIx;)h}zPZOH zxY3BlYX >3nEuHz@#BrqVNCw>9!%Wd!*h- z7;bp!Ylmr ){rioXn9j z?XU(Z9!YNA&s@EI|3*n>W|NE8!G><*##wJB9vDD;505$+-adLTo}o)_!r>0n0SMfQ zhO6eR%omTp?8W7335oIQBYXluJ}T4?sToPb^0i3l=p%SEKz9SwI2+*y;;bJRkWi#| zf3;X>d3PBa4Ysf@WGudRbo&W_RB#F{S !K25oD;p~y1Gsm#7!hn4UUX}o)^}? z$phd8MbItLvqmplrLkVXnZE;nc#@j5AP{q{Wq)^bKTW4j`5@Z?`a&^MQIn7TD$vDy z0?+G=bAlR9{l3*d-um+xFy C Hk?^1bRJjYg0@ReSf4xYZv>?!W?)&MP!v6b| zodVh%IeMHapFJv?PrfO+3JBq6=`a2lZWZlDU2Su^dq8H!xt~K3RVLbWFXNv=u$hcO zG`2uFDFkIwaBbT^aShFS_6e@Y+#HyG?<=lx>{muW1Sm^>_u#d!F>u!*0i1{$tsmbg zyoY2{$T)*X&0|Xey FNuC_$n3K8ATh 5#*meBR++opZ_Q=|15bklr_E@U|Jn9kLww;_+|xa25iuDxcz5(m6PenCN>;+ra?g zNQ0%8|7QT%Uy9URfxrPVJ&Ud(_~#$IB#d!}Ev(;e e>Se zP87j3GBy|JL<+Lsf&=e|v_vx-q?7aX#34ID{?{PCA<(6#z|G^0U3UIpKFFC7T^6k$ zkU`}pYC4wS(_cqc`QmdPfvq$V@a_#D{rf;RZMH~x!yLjWlEBT=X8(yLtz!j)Q^Q_n{FDmkY@cXZdCceggLzd1v zlwpv_oSXpsLa4I3`sf4$39v0ZUVLtWJG77G`Ima765X)XkCR%-mf@k>8Y5E|>)}l2 zKXGWsxsBdTGwtBid>`P6V$z`eiiV9jfAcJ-hNl;ZBj=|Vy~GEnooUE{O5?lBU{>UK zqCJi5qX&hL#Y4Y~#;;Q#=kP$BG#gR~$_VR%)axxOhVl2mC43$-_J6(#ghQ-Rp}ADe z`MYbj-gvpo^Y|$m%e_tO!Swk$cDPi7iAJLLpF{$T-(|Xf@++{dGczfR>@C3V?{t9j zzJ1 ~Napg-S2{-*M>FXwSr^q 2ffRS=rfcR)z39E%^8s!oTvc!(d&VxmRd?kMu~H z>a_@(nr0N$ (^nr`L*L$?db__Rj|)saZ6NP_aUHDMV2= _$c=xub-P{WBu2fFFD)QxP#N8ZAq(~Tps-Pc~GS+ 1Xmx7dUFaI{olRs#q%bzj}iw76}NPd_3K4 zMbd3$zen}7TjL2wSn(^k$WJrAE_~scu+mNF@I5t9VDr^Gi#hb%ZU=hht9b0_TAlr$ zd2F4qzd63;Ez9PW?O)SDZTvtI)bhpgqwJlAX(v$~)NiaoE*)H3yZo@O`|jPs@|7rY zPRKk{X=rbcLl%frc`U~j@-wRqm+l#LZtHsdR0J>1R2X#+nam1?sF9G6pgayAUSepX zX8?2(4O$9_knGyas^#sc{OAcwo*+9r69`@Vwdc?5?PE#yi2SH%6#O_xybEcw7{rPt z%VS65?w#k`-!4Oia2}s?Ahj>R9a*r4^TE71%1FRmUPDvUA>oD6-!w|V)gwh?qa$?r z{SNv>2a@U)xMO1=A3q2Zk5q7G0#p!q^|xmO`;?pd{pD^o8Yo K@&nYIe5aSjuZU9cX%7BYc% zF(EV|$%oWFe4w7Qd5*TTeoit2vK0Z0oL1Q1yu>5XwPGn?7dRxo3Hx+S)8jVtwbE S@wp8KWZ>t&B$UVg%(Zb7rx%qY}2VF%{K0ssB z$9A9ra@c;5SR|YdhapQt+8EjGgN)v@g#@T6vi-{+#*(y*m(B6Ub)cG&6Q}U6>af8E z5`*Bj9zh=I23gBCC&7hp^wFrtk)|zoxVzfgV*Q{4# I=XgNN;_k8Z^ai zw73|#6%4TU5A=b>Pnj7SJvj(u%-971SVoHEP?#ev1%2+L&BB4YGENFuPpu)rb&(!} zt&r912N!nbHc+15a0}(czyCJ)iSFd=vv2(X9Kdnl;BbkV$RD4b^&UF~*0^t>78-^; zcYd9*yvcM|HBWy3mGJtFjpDEpH3L#`D+tv;eiQ`QJbzcIa?m65^BGH`1- x%|9YniK%tC%=}0rNR{Uf1gM~ zmGyfeojBCk*jWCrcYa5mZb=2zJo-y_p0aDh$B*(n%rsAw)a8WQ#E(qQc+k@sg9_SN z^bXrPezp#Dzx}P5mB9m+FB_o*psxyexR4C@gCfyU1@xxlyUSa6q&E4E{G}T>BOUR& zGMDBZa80HG0B#_^82IoZ(Oh@p2eleJsQ? F2q7C2Dm8-HftICjFz5!(6%_l`; zxo>#?BT&)HaEI0p8M_pWv;(x2<>_Q8#F{ss^3b(B!LgscTU#s>?ADxjIOjC`_N54c zma&Z^BXG*@FU-tjzZtQ2;EeHeaws>A_eXJIxT6BZy~FwKbUL8!{hu&;0B7jW#_8S+ z82i1q < dPxWuDtXzEHI2~7`Rye$%FmUdH zR)$5u$;aoEI!)t&t+@0cW6Otk^$qRaySJEik+xP_=(#{9QcNSrNk)STBmrA`CQ^9& z6+Kr7Wny(O2Aj8k*apgFj?+A3tNxD ;kr~!w6N37*9 zWy%w|1Wu%ba2m%>@JvWh!_EjUe_isV66CW;?MO7nAky`CLwPDnDy6iQZm;IgBG{71 z94V+y7yy%i#ExpLpVsUWxZGv?TNPcWS|F?5@}x62(k~C$yFX%+fN8vRa7@_BP{~2n z(ZD%toiOQ^Q{rnsiKG)d;su5B#A#hI1zgYJDqq~-RLh1gGMd$ocY&bcq8hAAgei|~ z?}%Yk;soj0QOFGdPTF%%Z0JJeWbwJV^y*G-t)_edBM$q{DZ{;~*-HOt{o>j0#MX24 z@XyrK3kuvF%!myopyC}pmQ#U_QI(v$q5u16=APCxM`cBnY53LAo(UwI+&_mjP1Zti z)NX;`^FGPQjL!)nzxw$4wP$I<7!LOtB%oNbvlSfg-Isph-oIzE$j1|cOCkt&&IGJ& zFu($-bWbi=z~{Hz2^uB>Ezad9opB;;ml|^L3_HdrZ;RE052|e1%i2~jvJWGr(z^sF zknL@?X_>Y*UIVF%1{c#kWZY|0N1ySAZjl4HQ4PY_+nL34L@>LN1h2lJ+4E0nb{t%c zNTK23+!4Somh(V|8{62U*VQ?U@{#7Wa43s8(Cq1k8CMw>YW&}MijGUOauq#V2!cm> z#ew1d4z#Ql_76_cJ?IMnTTOQWR8)HV>#ZVNDTjnX2{_0e=U)4sNQXH{K)d}vUyBH; zs%x386fFGd>rW+DpFBzT78tjEuf5!-oJEwc7&lostvs`J%{*{UBAt{hUa|pXuC>C0 zdeD- Qa)jjCV69hrk@W`ts>z(b4VS_&Nw@c2s W|6;ntIu*TJ-`aiS0D 3 w!s~VOKd)phCF`Yv{sc%Gr-p(Ij&2kmTr21V6MLY z{N1@UA<&3aE-dI&j`1?5ng^UM2h_;@ Z6N=9q M _z4KY3HCnkeqGOZI=i zpeuplj@WfUvSj#71+6Uzpp}6AwIAN4m6yK()^afvtvcvOzvs$WGTO&DqjC8Oux&`~ zz9550rAWZGT4o2O5Og?@ZY(I1?9zi;6Fpwlw^{7}*&U+kMMl#Bre$A$e!u)gzsa-j zu>H64Up)OZDyeZ~{9!PpyWp82D3I*bx&2F$ouX$L)Sz(#@ZXVNL_Uo#bIkeB@UC>K zGq{UMpHnAv@HpfD0sr$2KdOgTjnwPkJ!SDTdbb+cOAmnu*dr^do42+g|M{kSK^;DR zNR87XB=-0SlCR$(cOLWr-LGapM phQjA_x}zbqhQ=pgDBe+NY*;0cGyx; z;ey+}0UH}Ac=OgY&>GbUMm2z=pvJbFp20OT!x=>J$d-`spWyONBX3JotzKG(B(N2> zaA(8ThciJ^bm_%N&Lv}wEqO~tdnqdHED^@+XHZDU(UT|Nyn7l2jf|{KQUf%Oq2RB< z{v=J8U;LT )3Gf|--5&+1DlDLGhB^n&VgKfswrQ81W;Ez<;YEuU00x*Wez78YdJR=)frt%K_0~Y%| za%imx8R=KE0NNB?vO1;NRB**w>hRQT7`QiXtDzqQ?UKFn736!7_8%v;LSCStZv%n) ze?IdRfjdo?f;Hvt6>-MDb>b<`x*Bq@9IXW;d4YvdkQJOcv$^B$pfI)ypb=_Alj_Y$ zOKYmIDOG}u4;BTjVA?XOOt?BAe1tKka939!**bpexbg!!$czo&c4YLU={cDJ+(bF6 zqAnyhu0Y?ShLDW3vER5g ;uO$eq|sAL`I< 34H+i?f(vMgOM;@JpGp0Vr*mx
CXbj^bKL#*ne~C<3X(GLGv`1o|D6!11j?p$?NzjU8e_JgYjW@Z)4i+oa!hJ zP4VY)tkGck&mTbNw&|K)hX!pd%%V_On^8cX6TveftSj zE*j2dfDT6Y%Rs6@vZ8M*RK(9-WH@R& ;#Xngv1Rj|KpR ix*N3c8 z#Wb+Wg{3UetgZZ9MSwl}!+a5ICoDfS+Rq{6^tC&y?5AJ#T_n)R{CxHWz?U7(p>*+v zX0cmJK*RRBX?{((#=Uc^T^y=72Ivcqh?b_$ HU0+KSpF>`W3$;lBJ2*Q4HHRLGlK^k9N^us#=Tya+18&}=^ z*r-+yFRv8j#=A7g)9Y0WGG70UySKU4a=PWPK2o;|f?#>DwQKsU>cX!fYjoy;twXw| zX1HH3y{C&7A9?!)&X$x^XHYI;K*P*uKZvd8Mz~vYJYL>egETZzCKW(=GCe%1Ma>Kf zm)cs?D;&q G1+q7JNFap+#G{mOeq%g91u4i{T(!6?+#0T*!qnUl4Nabw zOslCtDr(R%(`gtPe9WO@H-IIi>8xN+j+n9uxR}UD4kZuB0 3CWPi$2nhiKG$0`)q2X#k0*OHb0!;|%YC;2& z5J+eONq(N )16-L<^2Kg z`)b#zb9BM^<=wSw*WSD8+*8A@U3Jc>fhAlXbQG9%RMEKY?Dm|9l+)C8GjhD!Ys{b{ z_(We@TRV%0fBNMwfj!{nV-R$|f1xc?jd8#XX?3UJD$rUQ7zL(USK2WKzzGb)YovKk zXG0B5jsRY2b}X<3U!z_T?gKRrR}ANbQVu(oFa=VCeHlAOSdXhHg`?pVm|~K2EWF)f zkL^cGv0Z`%GpFq=sQum6_6F7$vNez dS-(EWo2e^nH2(SSB%nuuBV!0Basl1b6E<%P($QqUjL8X-t>DVmM|n z&+h2KQAHQMB*Oe@GC~8hXJH`$dXs>d!>ccMWq$g75d!B%GA}W3Y!|G(9O_%Tnj`>1 zrd#=gN;@_=xdXmy0ch^SZx3Lt$j<)D7s)b{fH!|AejF>x@jiuik E^{@O1VFGikufzZoY6=4i#4rwJI0VhN1Gv9(hvVwo=h%K%<; z0F5SqC6wa=I!xPy*`C0FQ)i~8s4Jn7DJk$+%$S!A42UfI^tVC@*0}}4uHhJ_iPny@ z&;%OIkxKX9zv`UGR_n>sCel!TF9uGARUb~Vtpe5O`u#vUh+5FWynE+|Gh>5lEmzF? z+JgtzwC`C{vk9^;I0JvZaM92p{`Sl>&!iPIqx$PfoT^FiScp+}>mfW}xe5>VgOB#| zcB>acC-*m&fUk$6$)y2u>z e+6AO-L4FrIG;Hse3~!4YD85d0Fa7OrKXVYQ zZW%NO^(S|+j0K!~fh|8b#^8;sOei~&KmP0mV41iEVvda!FpSZbP^?%3iH2J9?(2pI zL943^Y53?-9W5i?8HM_v&_=TiI6cRVTd$u%swXAy Ky^Pa)%-n9)mIt_2pOs^qZ@j=i&}kkVwx(=BHYb;S_Q;+Mp-Dwy!VSUe5EA&T zp_*7j89@n&iMv4T6*(H0i9pR-59g!%1rs0y)wkQOeQ@)gj2SM$z_b$Y{a9F5@VM-6 zDFQa6;p-O{h5PWE;sYN@Dqp<$qOjd+X@dz$Vd|R-4gW=iHUX1GNk!C^h8my~)|!Zj zFS-iy>eiX8ty`TDS}_as+HcP4d$KI?wpmyDNB8(qu>^Fh&_Tf~8ZlxN!P#Y;UIjMq z#@8F*v4x#W25N-^=vz&JEE9>~$OHBDy3$)9slnnD`fVl-E19iqXvfZ-8{xN`5D?{t zDP$pLs4X@`wzxH_(S%~&KWL`wPGF 8~*FDZ@@Dz1SAf1CnO|<=>~L6w^@6PA5I0&di>~qrc&n*RD02h%@`h< zdC(9E97@~>IaWr4b?)b6A_$>1$tewXGN66&z8GE(TxKzlu0N`hfGfH^R;a>q>O%LK zaTBJCrU?Qy8iN1F7hf>JAOdm _^-G;9)0(hZ zLV K(Tt_jeM~LB(q5~$I{?4R#Qhkd-PB^1IOFy3=Lu*T(?%KBeW>!^nj1T0a#N4 z>)^3l1HAt%(_RxPoG;tQt>?Dv;f>i116JIy1D_PM4iB%K)h&^=8i3i0o@d@#D9}p{ z0LW2gt-=W2BujMIobj#3@n8x8=2!!}*q%QBDUb(nFv9mPEL?zY&p0PL_2#P{QNe1p z8IzFDo{O1TG?9D^Wr#mN{k);K+ii&dbptrzb` 1yKj)`~S Tdii^G9ePo_wcr3pYazJs2+PI^SDyn%QwY%i49aeoiQs5v@kuzTbhdFw zn_Q(T5Cd4QZ~t283h{u`>$u3!u-mQYtwkehRJ#LFD_K|dMqY(Q3@++XF~vic={?!b z`ViirC; zYLTvkN2|mhYNAMFg4gaaoJ*TEs<_rD7EHE@u%z^3YzsE-V%AQv>ClH$=VmCYX1zFf zsH#L0+{L`w+Q o)z=n`2!80b?Wykqw6J0?{xdN%^QcN1#m5DI)T>n zSRGCN5SZ0->o xE9gsu%paU#<0l`~5q^o1*5y7eM!g54SIqB$dC zbqqEZ4}nBr#Cq}q8=si?#;fobSt@|hnkL@x%F{p{(ONe+8tkUn9R=is;MS$3rACot z(g?HCn-kyomNU}FKgo45%WHKhvta {B}&jfwv$*)A?#?w)!R*oXwO9jH*Si73$b= zZ8s56$9&G_0xg^XYJ(GCcT`YTmh3z_`;%vBLUr0e?Z` 3oB+MzO-onL zi`G!f0j=Tzvq2hQXt{8#LTW~eu!6NY7UtSR&LHM;pxS=G2fK}ZBs1ZJ(Sl&G)I$J^ zjyDll(J35kXy}`roo$z(X?V$S%vc$gxWHpt=lcf-H{glVA9Uqx`xwPo%sMnOQq|NH z4i50{*TiRHuKzR5x25dsB;ZaQAAs%4R^%0NsISUY`mqL%Ij>^L`oI;8RkgOpnvexh z?Re!=y}0OdZoudVd6&c*GY5Qt4=!XwFffT1kh17dL~LsvaC$5PYI~fvF;i@G-V7Aj zZm6ZSoE%o=oiGoP^$`nX&CE<8HksdERKy|zFdU#uQ5b`1!UbxreK`8TGgnlB*h&fX zz|^I3Bh%IW?)TTHK^7sWt4S!#Z60 O?(Wjt_)JT7g4i&GCIk%J!1inN2OK-;5W ^YhPfbI$`iCj4I z$Bb^*{GHe6qC vsq-x)P|LjR1R8U7vv}ZlM&?G=BzK3o~;OT3Z?#n zog&+`%KzMn6ITAQb=2vd5h1zsT_YY}Z>Mj9Mk258&IFbg7G6g8;LHeOdg>lKEFFhI z0vs^_R%$qvx9!a*B LlyFvyZi2x;z)>h`I z`nEnt8(|a9oLrf1)vgBoV>pHph` Fbb@0Tfb5bgpZG*#;xU=*OWLU~fn44_cBVMF1q^djzrjOKXdsnE;PV;#c!#2Z ztAJya_!WAm^tUTdJwF{ms6g&?f>nMY+Bj_7VZGcLRqJo+u}u`yHEb10qgp||-)z8Q zP7*iyL2R~5xdl$h=uu}P^$8E0YN!TKmKhDou*V~8!A{tvYtb(AMm1QLIIG}U^Oixn zM@E9=*ro+=1tk*cPV7_P6vg5jI)#(XhK7dRd$lsLpVcb(ce)Hjdgo#JS9@$Myulfx z@BGsW>*(mY*&qN`sZ~=C0n@vIU`2wu)oRT*zpcJ1OIVEH+WPwAx@aXPty!nJ$Q~m=6lugBgtATT%Es9jy!Fx-#+S=m)MU+7HOXMr zf;IS6vNTeSY=`o-rC^~0b~mZzx%EX!p$F9NMelrrjk7It$eMiuP8%bwA#&jXcpQ8N zVWX#7^+rKI2|-2!psNd%!0{Cxm`ro}!>xZXu?(?_4}^GR=4J6l8oXf0jG&~@)c^>l zOT;%}AZ|ap7nl!*BXE1*2AQI(PXYnFFdx|L&*!c rsc{K?G+5o#>%jb8v5<0*{G?xsza%o+=&!?yx4XNu*6!80?rTT#v8hd{i{X@GP?e z&@F(?{AAKQzlC>vhrVGmnCZ H$qYd(J-- $hwfc18?0Ou)&L^6$kqDN-L8k#l*Il%j~YX z4?97t-8U)*2mkcgD+Bhpv*dq%co7%G!5#SctL9Ry!;}SUG-U!18G6l})(S{^;XZwV zIh&VoCSZSUZADd;j|hNKPeRZ9&y&xB3d9o#4f5Q6)tJ>@a$r?X*ufeo17+tZ$;jF0 zKYfA4#yopKBU6K|@^--+y?(ER(yEW<;H$8(zzJB&BFxA4Svv~AASHeBkck3 osZ1>V3$>3kNsEEAfmkx`0Oe1 +^wJ!hVMK;d;aI_SFH@?IoZimB;LYRspofFuxjcG}`U4j#E z?n<=|qDd3-mN~Nd<|F2uc{Svak&~C(KZZpkFq*XM^eT+8sKATu?eTe5%}a2Qc-LR* zqb(KiVF~)fX{FD`h8vAIvG$YKv$M0`mc8)uqiqX `nVctC;#Qy(xrxnTUJu5K!_Kn^_SKXi4mGKV0kc{Fr@YMcZul;E}0 zwvjHpmT`%I#zc2wB4FMCb>aapzuIT$N} 6MVrFP`E*8u %U~Zb|e)~1>jvDOKWiD?%-E{#gV~5xPl%afu#TYkRw;Q)^aIkY1l+!dbH63)emRQ z>j4=X6Vr?Nm8t*nrB}hJnK#r=HnxRW%uUeA#Po?|)%maK*q*gvgD-*wy2PW}a-H3t z!NJXDO#k6M61eB)Z C!)aVYk{%ua$`xuty6<1mfg^lY`l1p{8Jk_wL=k9UW?e9jfyC4@$ILuZpv0 zOF-YgFF_<8c!Xw9!0w_>Rv96S{|`?XHd@>EtGYAlFd+dn#{gSpg{Sto;H>s=`%#f= zrDWUd^#h_Jthsqn*CCiuHAkNR%F6+*a%!(IPCPvhE)nC)ViCSA}Uw&KT@o z@b2i^FM4)^Vw+Yk3MERTV-s|dG{Cdlw#c;Q*oPy`Svv7cf4%S;{6uDe_um7@uIKtw zG*eoQhH v zUqAyiA`3p7iU&}w1@P&U>g&RHGIP$m2?Qag3j~K8hv} Y=XNg5thYpZzY zKUT4&<-oC2% 4!rJ!OW%Xc?G9&`LKO#WgM0r$mH>e5C`L&6AStx| zvu4>IO~;iWJoRd;MTPIA+9frsy|!CvTub3-;4nh5)r7+MI4YfNhD_@KI3gafHX!jJ zfB&SB-bt_!b<>x?&wlF>seGv`BcoM4ydRRFLpUuipn-qQ^u@4dw21{p#Wi5<_rbyn zJXY@fS`CT%rUm~1`_fg+cQyN308?~D%@Gk^OOIv8s^`h{Qaw t$uZb) z^>w{*`CbJ3$feEdRh?@gL0Dim>%aU)Cskg~n=t R~-#!5`!?I9=t; zXeXRNH?N?5rd4As05$LCE7a2upX9}W_b<|9>z+|Wz$_1eoT{;HTelX)sqf5Lks{=K z0-DjNVb}rR77_Nsn^kkD#zk=Hfne_vkSR36l^N7j^&YIgjDN#o)~dG)vv2H5gnalM zkN}4WPFSB`wsY66_92lKR>S9w0dM?_J<) h`IKh1OW#Qt{C&3x&64c zsVPmZ6Vx_RpIn4!97`qk4u$dmL*ZC!cZGTn>1uy}8bt#E(S{Ua-x=1L6Lt);|5?3` zJ1w&K)H{3l=h%zZ=oSGo1x~=*P#`kQyWQJn!4bslsAF+@A-n}R?8kRmy~5IlAqz4< z+){W}qmZl!94;3uoWZSj0z_5?FD9EtRG>U|G%QzkI2|y;EucoyRJT`?ZI+UoU=Vh0 zqI1D=0I_`E=>ub9V^uU}*kWTvg|meS&=vyZY;vp|J+k~A>+{SGm#Ygw$5?RM4zV3< zTQNF;z;4JBt7u6n!ud*D?3&}>0F;G_0-?^SP|dAfLj0)lZJHKOuv)@t1lE=jal9ps z1E4)WZc~K5bf1W3#XC|xLQ^QvGd*394zf0;V?8`-p%d7&bo263FQ6(KSfrcb)Y#RF z7U^tO=1!KJZrSX1-hT#YQ KZ<@7jGP8K39U%*P+rr!u5W z7a;6Vf4DA>6fB++VKZKCR?_CESK}fROqDdiyVCK-g>clEgHxj{_^5ZE2hNl!MzoaE zC3;U^SJ?Ge)9cJCG`o5bDPaQ*Q4{Na6fU(Rl7C52wYH9Kf2O-8pZZmkwymW@Z4wP8 zb+0uaOWPC304tgqcoz4mpj6;Axa88NK6uLy06F3Gh6FaHp?vRhR>1r%S-e|7m1CfEnhY^e(k)jrHKpi(>*xiU>GB!HT$Tiba? zxJzGw@5X#nf7l=qIdQr$^F#1E;NS6;$aw+i^@i7_2L2BZQY+WMDSf>DNVIzX z0dMPIPDl5CCKfOeeh~Pi>Iy-;M>R^Us=oD@I-U; hy^jS0ZN1gN)BLb_=-un`;Y?u0l0m#=O>?M57}wtiZ>Q>jo*2ot3%xFkG_{UW=; z+rLfOTo1pZtLxVJT_C#o*ShZlC4lLgr-Y(MbrM)G6o|Dh-NJl~bi9-$nbN4NhI(nE zo`p@gZhjjXYh8*q44_AJM+0)=Uw-m~#VU0~#)!GD8n0MsyqvK7s(dKD`nr)~1Felo z>)A#-;ilml;0ECYys`O+wcQGi$-|5@6XYb=U#S D?ZuRo!U=@n%Q7n`?~C4ThF~FZ5F!-7vD+}PR(5<7UMo4Z4zGKi z3%^FOI4`GDXE)>RpCQJI7O`ah@J^!0h_ !CIsl#; z0}*Xlw(|wW$3KN67|wC}Ktjp_1mSr*cI=+dl-CH%d^g-qHBD%3v9z%$O#AxiUWftd zqu0-~6Z|{y;XOmTBT^63Cgxh9Za=D?-B$7DY@(5PTQ6r@-F{_PSy`4A!z-0ug0=w@ zYqPGMXUk?|?46%afX61y=mvDcJPwMH;LER!E#Dgh8jD%X1yqkSJ+nJo;A^V{ij`SY zsiwrFvMJ}mg+%}X!rKEI ;9J_zbZ2 zZF2%<*aXdwg47cxLHU}GN7U=1a3glBWRlOQx9wP%{@yF#04>_ef^;+G9x?gSE{= zSFA@U%U+-iUQ_Uds*-TO;2k0j@WS!o%&5TUuf2EPZgTb(EV35ZL}R*jv^65!LYpyx z&A?bgepo~-VkfQ@X3*_cXID9csMsD*Mb#RB;) fopcij!YpoZ3TXtlmz9Z#_OTZ$1*l^N`p`Lpv3ApA zgI4PYB*NWtx2K7XQo@ 60K%jUoFRzH4y6Vcq{f@IQwPl&<$xmQQH(=Ma5ef0Z z57e4!FiiVhLEA_7c4`8 |qV&zv2p<5zS)y+}fb}bSZ0jG>~J$yMitq`DTn5A@81d z#$$c)3Fd$ivWp3Yqhe!I%}B;53cvC!Jf+T2om|1;g=05pIxrs+*k_TkJ)na2hE*sK z84)4Gf_S4Qq;V`(bx_b5>OEjlzDOsRm^dJ6mVa#623I==gPWzdUaRNVORySARDj0* zSR&anExr?f{@U(>nge6H&Lw28Gs0&t1|PJa37jE4>|=bU)uxVt>N8L-9^8&Bzao+R z76uN;5XQw40AQB55Q0<0vrb2H>_@@2o_5o@$IMZk1-Yc7A{|Cpy;|)#JkTp67zAB? z&N^E_;sI0b!}0xTQR_cgpK}sg<$dKlpyGuycq0?t;U87gW4H4Ld9iKc+{L6w7>kZ| zt)6hAO?)R&s||)d_;-fVPH4@z{3s7%=CkGee6fiWCTB`9np#aW5z{3z-csxwn#j7Z z79Oizh4|>Hbs!uI??ZJ!Y!(#>VR`H8sHL 8=aMMPuQsVy$sIHt zM}mTa8XeuIL>Y;fs>1B!Hb__kmmf)@cEmEz<8XZ=Bkw-f7m;=4=V0zqL GW068z1Xy8&FqaV`?~Opba7|(X548maur1?d?2n3Zl$Hlca8ON? zJ3j$~8DW9hd0l@*SQ>Nw!Qh$@RuMp%3;2#6Pi> Wa>06QxX0(h7z1EnK_I(p@sz=O`c zlV+14QTrpn7hnq&uv=t>He(`x?;piyqY=Ociy$1wMjCT4hCRIzbP|ryU2smYN7|68 zm}=K>RxY5!TCXbXT6j!T#d_g$E5!${X8vPuYkdGsy ARml!Ah*U@)B^| 0_`a{<8hkx1?DZx%mbGK*&OsEHF+tDs_ABs-( z?vn`ESF;bh0@xMHK`3>K <9qImxN)T1U4M0vH+4^Ub?K#hm{tjm@^pOm=V$(=Sg zCcBgO?}F3nm?IG)_QFqsn+RiX!n-o{({HIGYpL4>RD`?2($rBkQyGCww-|blm^6P* zC X{&x@wJyYH>}j-8n7I{c)&!%Ap0PeWbvmjcBh!&Cd={<4z#~* z&3?#0ux+31nt~W_#LcyapbnN(bz6A@!1AlN9~bIQjPJdde|6?BPriU4mXHS=5x!^% zzHX?XFuj76)rbZw;lRZ*3f`5M=QxBKay~WVg`Mer`M@jSI8ZuwD@6Ma6MKN#+X0We zeK<`6LCg_*CF`_Zt@UWw5H2v&WpHh9cY>~WUtf;u0kl `HR)C%;z_3)6Ij>mQYs6iKv(~_#Kmr0AE_{bbzXr|no3qi sPerho8iTt)*;xz*cTg)9$TuJFUW5l2f8#Zl@IG5jY}2!% zHNF2MAC*mqBfj)GERdx6%5UqSNoB@V+930n^Q6@xw{X+6~^eMV6 z`Y;cB)mw5BY6~2RXYl4u-Jc~nuA>bY()N3f2Bhr7r_Tq%$3BE5ZZEHY`4LMqrpnF# zKfHD394oo{qx-hwh%ZJn5E|#Dj?Rp(fyjrI57-1(E+p{Wp?7pU5s@~`U@BLK!0-m^ zW*ra7+h0wbVd1O8ZdoYoG|OhDrmEFBxGbJdxW$D-OuUzu*CBX-piGt010dPt-~MVO za02_zeQ*(Q0ypl5kB64zY?hiD44G!^GOV&XDt@UVa^zCBSA&4RON(sF?yu{J$~K zRkV2hGLM*wGII52*M4=zj#viSLUtE0)mCe(7(ZRX5LYmdn$>Lqf`KL=K$v*UTxq=S zj!t8t&p`;NLpNRqf;tv)7~Tk1GBeIn0L+Sa?bmZGK9#;h?_2=MUIULfp)sn7u!bm^ z@3Gn)RYagf_0`${2FEZO?cPG6zrVj7Y0;2|V7&}GcI+|%e}3o{q3@h}EAkx}=7Dl6 z12hckEHy(dwP%GJ*mLyg(M)EF4YS*b6Bv6GPOUNKz?yLk;P5igBX9}ycbWi0%&j}4 zzJiKC7eAO>v>BP-e0ZaTvEHV(geDimuaM-{_{CK4p1i>|*^>(t;H-GStQGY};;=ou zUJF^7iE$Z=ye@qyLRHRGKg1;!)x6jIPF6S|882Y;AKFEauho}-JbrkUw9u#;nrt@w zlk)LmK`!0G> DDwh)`u-@Wr z^`X7@+_{@S@>YM^(G(TY#y>L 8)#@) zB++Pred>WfrV4BcoD**SR1F{WpSK~Xw M+eVl++ zNy|7t+3dBc1q7XdecugfMwlCu)e&WLy4gd3ruq9PN(xPoAyT)3)U6g2t?PgZ+`*pD zzNL}JrKEtSc5_=vcCR;HcAlFBL7bWrtX6YUD_bIGRb>QNH4B*lOGuzb=_Wb_Ru)_F zDp(1{yn8 EDj8AFTAKd?+Fu7$4?0g6`qcMs?i1PzeKRm9W< z#3%r*b+9Swuk#>E77|pQBk5pyiMcU(>WYrdzG)zSt|=;wceU=Z!*@j$rSLZEbr!X% zltxR@;rb9uX+Q&Fst}v`J;J6Iqyn!zV^um$#2lQ(Pw5ehT!H|A`U5L1 vh;lbwGd=k!hTelz#AX7^NTu?6y;&t9Ba#CWY2ff((}x%Zt^p&okjt3 zv0E3*RQC-dM*Txht!DILNgR`qxgW^733)gj>lo_1i^-|wJ>ctLp;V!dfnLpfQirTM gO>Yo`QwLe>pC0`#yh)B1w()Q3YShv7Rc;9W9}8Wk_y7O^ literal 0 HcmV?d00001 diff --git a/packages/ibm-products-styles/src/components/FilterPanel/_carbon-imports.scss b/packages/ibm-products-styles/src/components/FilterPanel/_carbon-imports.scss new file mode 100644 index 0000000000..178afd4773 --- /dev/null +++ b/packages/ibm-products-styles/src/components/FilterPanel/_carbon-imports.scss @@ -0,0 +1,9 @@ +// +// Copyright IBM Corp. 2024, 2024 +// +// This source code is licensed under the Apache-2.0 license found in the +// LICENSE file in the root directory of this source tree. +// + +// Import any Carbon component styles used from FilterPanel in this file. +// FilterPanel uses the following Carbon components: diff --git a/packages/ibm-products-styles/src/components/FilterPanel/_filter-panel-checkbox.scss b/packages/ibm-products-styles/src/components/FilterPanel/_filter-panel-checkbox.scss new file mode 100644 index 0000000000..317ddd890a --- /dev/null +++ b/packages/ibm-products-styles/src/components/FilterPanel/_filter-panel-checkbox.scss @@ -0,0 +1,32 @@ +// +// Copyright IBM Corp. 2024, 2024 +// +// This source code is licensed under the Apache-2.0 license found in the +// LICENSE file in the root directory of this source tree. +// + +/* stylelint-disable max-nesting-depth */ + +// Standard imports. +@use '../../global/styles/project-settings' as c4p-settings; +@use '../../global/styles/mixins'; + +@use '@carbon/styles/scss/type'; + +// The block part of our conventional BEM class names (blockClass__E--M). +$block-class: #{c4p-settings.$pkg-prefix}--filter-panel-checkbox; +$label: #{c4p-settings.$pkg-prefix}--filter-panel-label; + +// Set Carbon's Checkbox label to 100% the width of its container. +.#{$block-class} .#{c4p-settings.$carbon-prefix}--checkbox-label, +.#{$block-class} .#{c4p-settings.$carbon-prefix}--checkbox-label-text { + width: 100%; +} + +.#{$block-class} .#{$label}__text { + @include type.type-style('body-compact-01'); +} + +.#{$block-class} .#{$label}__count { + @include type.type-style('label-01'); +} diff --git a/packages/ibm-products-styles/src/components/FilterPanel/_filter-panel-label.scss b/packages/ibm-products-styles/src/components/FilterPanel/_filter-panel-label.scss new file mode 100644 index 0000000000..30f846fd1b --- /dev/null +++ b/packages/ibm-products-styles/src/components/FilterPanel/_filter-panel-label.scss @@ -0,0 +1,47 @@ +// +// Copyright IBM Corp. 2024, 2024 +// +// This source code is licensed under the Apache-2.0 license found in the +// LICENSE file in the root directory of this source tree. +// + +/* stylelint-disable max-nesting-depth */ + +// Standard imports. +@use '../../global/styles/project-settings' as c4p-settings; +@use '../../global/styles/mixins'; + +@use '@carbon/styles/scss/spacing' as *; +@use '@carbon/styles/scss/theme' as *; +@use '@carbon/styles/scss/type'; + +// FilterPanel uses the following Carbon for IBM Products components: +// TODO: @use(s) of IBM Products component styles used by FilterPanel + +// The block part of our conventional BEM class names (blockClass__E--M). +$block-class: #{c4p-settings.$pkg-prefix}--filter-panel-label; + +.#{$block-class} { + display: flex; + align-items: center; +} + +.#{$block-class}__text { + overflow: hidden; + flex: 1 1; + text-overflow: ellipsis; + white-space: nowrap; +} + +.#{$block-class}__count { + margin-left: $spacing-04; + color: $text-secondary; +} + +// Surround the value with parentheses. +.#{$block-class}__count::before { + content: '('; +} +.#{$block-class}__count::after { + content: ')'; +} diff --git a/packages/ibm-products-styles/src/components/FilterPanel/_filter-panel.scss b/packages/ibm-products-styles/src/components/FilterPanel/_filter-panel.scss new file mode 100644 index 0000000000..f4e1df5933 --- /dev/null +++ b/packages/ibm-products-styles/src/components/FilterPanel/_filter-panel.scss @@ -0,0 +1,29 @@ +// +// Copyright IBM Corp. 2024, 2024 +// +// This source code is licensed under the Apache-2.0 license found in the +// LICENSE file in the root directory of this source tree. +// + +/* stylelint-disable max-nesting-depth */ + +// Standard imports. +@use '../../global/styles/project-settings' as c4p-settings; +@use '../../global/styles/mixins'; + +@use '@carbon/styles/scss/spacing' as *; +@use '@carbon/styles/scss/theme' as *; +@use '@carbon/styles/scss/type'; + +// FilterPanel uses the following Carbon for IBM Products components: +// TODO: @use(s) of IBM Products component styles used by FilterPanel + +// The block part of our conventional BEM class names (blockClass__E--M). +$block-class: #{c4p-settings.$pkg-prefix}--filter-panel; + +.#{$block-class}__title { + @include type.type-style('body-compact-01'); + + margin-top: $spacing-04; + margin-bottom: $spacing-04; +} diff --git a/packages/ibm-products-styles/src/components/FilterPanel/_index-with-carbon.scss b/packages/ibm-products-styles/src/components/FilterPanel/_index-with-carbon.scss new file mode 100644 index 0000000000..db8f053a65 --- /dev/null +++ b/packages/ibm-products-styles/src/components/FilterPanel/_index-with-carbon.scss @@ -0,0 +1,11 @@ +// +// Copyright IBM Corp. 2024, 2024 +// +// This source code is licensed under the Apache-2.0 license found in the +// LICENSE file in the root directory of this source tree. +// + +@use './carbon-imports'; +@use './filter-panel'; +@use './filter-panel-checkbox'; +@use './filter-panel-label'; diff --git a/packages/ibm-products-styles/src/components/FilterPanel/_index.scss b/packages/ibm-products-styles/src/components/FilterPanel/_index.scss new file mode 100644 index 0000000000..2cc40c8413 --- /dev/null +++ b/packages/ibm-products-styles/src/components/FilterPanel/_index.scss @@ -0,0 +1,10 @@ +// +// Copyright IBM Corp. 2024, 2024 +// +// This source code is licensed under the Apache-2.0 license found in the +// LICENSE file in the root directory of this source tree. +// + +@use './filter-panel'; +@use './filter-panel-checkbox'; +@use './filter-panel-label'; diff --git a/packages/ibm-products-styles/src/components/_index-with-carbon.scss b/packages/ibm-products-styles/src/components/_index-with-carbon.scss index 347f09f811..3cf9234be8 100644 --- a/packages/ibm-products-styles/src/components/_index-with-carbon.scss +++ b/packages/ibm-products-styles/src/components/_index-with-carbon.scss @@ -71,3 +71,4 @@ @use './StringFormatter/index-with-carbon' as *; @use './UserAvatar/index-with-carbon' as *; @use './StatusIndicator/index-with-carbon' as *; +@use './FilterPanel/index-with-carbon' as *; diff --git a/packages/ibm-products-styles/src/components/_index.scss b/packages/ibm-products-styles/src/components/_index.scss index aeaa505b5e..d776f6d745 100644 --- a/packages/ibm-products-styles/src/components/_index.scss +++ b/packages/ibm-products-styles/src/components/_index.scss @@ -79,3 +79,4 @@ @use './StringFormatter'; @use './UserAvatar'; @use './StatusIndicator'; +@use './FilterPanel'; diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanel.js b/packages/ibm-products/src/components/FilterPanel/FilterPanel.js new file mode 100644 index 0000000000..c756b66f44 --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanel.js @@ -0,0 +1,59 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +import PropTypes from 'prop-types'; +import cx from 'classnames'; + +import { getDevtoolsProps } from '../../global/js/utils/devtools'; +import { pkg } from '../../settings'; + +// The block part of our conventional BEM class names (blockClass__E--M). +const blockClass = `${pkg.prefix}--filter-panel`; +const componentName = 'FilterPanel'; + +/** + * The container for filter panel subcomponents. + */ +export let FilterPanel = React.forwardRef( + ({ children, className, title, ...rest }, ref) => { + return ( + + {title && + ); + } +); + +// Return a placeholder if not released and not enabled by feature flag +FilterPanel = pkg.checkComponentEnabled(FilterPanel, componentName); + +FilterPanel.displayName = componentName; + +FilterPanel.propTypes = { + /** + * Provide the contents of the FilterPanel. + */ + children: PropTypes.node, + + /** + * Provide an optional class to be applied to the containing node. + */ + className: PropTypes.string, + + /** + * Title text for the filter panel. + */ + title: PropTypes.node, +}; diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanel.mdx b/packages/ibm-products/src/components/FilterPanel/FilterPanel.mdx new file mode 100644 index 0000000000..a6f90ae77a --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanel.mdx @@ -0,0 +1,27 @@ +import { Story, ArgsTable, Source, Canvas } from '@storybook/addon-docs'; +import { CodesandboxLink } from '../../global/js/utils/story-helper'; +import { FilterPanel } from '.'; +import * as FilterPanelStories from './FilterPanel.stories'; + +# FilterPanel + +## Table of Contents + +- [Overview](#overview) +- [Example usage](#example-usage) +- [Component API](#component-api) + +## Overview + +The `FilterPanel` allows for an optional title, and acts as the container for +its subcomponents. + +## Example usage + + + +## Component API + +{title}
} + {children} +diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanel.stories.js b/packages/ibm-products/src/components/FilterPanel/FilterPanel.stories.js new file mode 100644 index 0000000000..8c495baa31 --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanel.stories.js @@ -0,0 +1,94 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import { action } from '@storybook/addon-actions'; + +import uuidv4 from '../../global/js/utils/uuidv4'; + +import { FilterPanel, FilterPanelCheckbox } from '.'; +import mdx from './FilterPanel.mdx'; + +import styles from './_storybook-styles.scss'; + +const storyClass = 'filter-panel-stories'; + +export default { + title: 'IBM Products/Components/Filter panel/Filter Panel', + component: FilterPanel, + tags: ['autodocs'], + parameters: { + styles, + docs: { + page: mdx, + }, + }, + argTypes: { + children: { control: { type: {} } }, + className: { control: { type: {} } }, + title: { + control: { + type: 'select', + labels: { + 0: 'No title', + 1: 'Plain text', + 2: 'Using markup', + }, + }, + mapping: { + 0: null, + 1: 'Filter panel title', + 2: ( + <> + Filter panel title + > + ), + }, + options: [0, 1, 2], + }, + }, + args: { + title: 1, + }, +}; + +const Template = (args) => { + return ( + ++ ); +}; + +export const Default = Template.bind({}); +Default.storyName = 'Filter Panel'; diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanel.test.js b/packages/ibm-products/src/components/FilterPanel/FilterPanel.test.js new file mode 100644 index 0000000000..978883bf31 --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanel.test.js @@ -0,0 +1,54 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { pkg } from '../../settings'; +import uuidv4 from '../../global/js/utils/uuidv4'; + +import { FilterPanel } from '.'; + +const blockClass = `${pkg.prefix}--filter-panel`; +const componentName = FilterPanel.displayName; + +// values to use +const children = `hello, world (${uuidv4()})`; +const className = `class-${uuidv4()}`; +const dataTestId = uuidv4(); + +const renderComponent = ({ ...rest } = {}) => + render(+ ++ action('FilterPanelCheckbox onChange')(checked, id, event) + } + /> + + action('FilterPanelCheckbox onChange')(checked, id, event) + } + /> + + action('FilterPanelCheckbox onChange')(checked, id, event) + } + /> + ); + +describe(componentName, () => { + it('renders a component FilterPanel', async () => { + renderComponent(); + expect(screen.getByTestId(dataTestId)).toBeInTheDocument(); + }); + + it('has no accessibility violations', async () => { + const { container } = renderComponent(); + expect(container).toBeAccessible(componentName); + expect(container).toHaveNoAxeViolations(); + }); + + it(`renders children`, async () => { + renderComponent({ children: children }); + expect(screen.getByText(children)).toBeInTheDocument(); + }); + + it('applies className to the containing node', async () => { + renderComponent({ className: className }); + expect(screen.getByTestId(dataTestId)).toHaveClass(className); + }); + + it('forwards a ref to an appropriate node', async () => { + const ref = React.createRef(); + renderComponent({ className: className, ref: ref }); + expect(ref.current).toHaveClass(blockClass); + }); +}); diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.js b/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.js new file mode 100644 index 0000000000..1e2cb0a192 --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.js @@ -0,0 +1,83 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import { Checkbox } from '@carbon/react'; +import { FilterPanelLabel } from '../FilterPanelLabel'; + +import { getDevtoolsProps } from '../../../global/js/utils/devtools'; +import { pkg } from '../../../settings'; + +// The block part of our conventional BEM class names (blockClass__E--M). +const blockClass = `${pkg.prefix}--filter-panel-checkbox`; +const componentName = 'FilterPanelCheckbox'; + +/** + * Provides a checkbox, label, and count. + */ +export let FilterPanelCheckbox = React.forwardRef( + ({ className, count, labelText, title, ...rest }, ref) => { + return ( + + } + ref={ref} + {...getDevtoolsProps(componentName)} + /> + ); + } +); + +// Return a placeholder if not released and not enabled by feature flag +FilterPanelCheckbox = pkg.checkComponentEnabled( + FilterPanelCheckbox, + componentName +); + +FilterPanelCheckbox.displayName = componentName; + +FilterPanelCheckbox.propTypes = { + /** + * IMPORTANT NOTE + * + * This component is returning Carbon's Checkbox. + * + * All of Carbon Checkbox's props are directly available + * through "...rest", including id, onClick, etc. + */ + + /** + * Optional class to be applied to the containing node. + */ + className: PropTypes.string, + + /** + * Number to be displayed with the checkbox. + */ + count: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + + /** + * Unique identifier. + */ + id: PropTypes.string.isRequired, + + /** + * Label to be displayed with the checkbox. + */ + labelText: PropTypes.node.isRequired, + + /** + * Optional title attribute for the label. + */ + title: PropTypes.string, +}; diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.mdx b/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.mdx new file mode 100644 index 0000000000..9376c6ff91 --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.mdx @@ -0,0 +1,22 @@ +import { Story, ArgsTable, Source, Canvas } from '@storybook/addon-docs'; +import { CodesandboxLink } from '../../../global/js/utils/story-helper'; +import { FilterPanelCheckbox } from '.'; +import * as FilterPanelCheckboxStories from './FilterPanelCheckbox.stories'; + +# FilterPanelCheckbox + +## Table of Contents + +- [Overview](#overview) +- [Example usage](#example-usage) +- [Component API](#component-api) + +## Example usage + + + +## Component API + + diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.stories.js b/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.stories.js new file mode 100644 index 0000000000..766770739b --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.stories.js @@ -0,0 +1,93 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import { action } from '@storybook/addon-actions'; + +import uuidv4 from '../../../global/js/utils/uuidv4'; + +import { FilterPanel, FilterPanelCheckbox } from '..'; +import mdx from './FilterPanelCheckbox.mdx'; + +import styles from '../_storybook-styles.scss'; + +const storyClass = 'filter-panel-stories'; + +export default { + title: 'IBM Products/Components/Filter panel/Filter Panel Checkbox', + component: FilterPanelCheckbox, + tags: ['autodocs'], + argTypes: { + className: { control: { type: {} } }, + id: { table: { disable: true } }, + onChange: { table: { disable: true } }, + count: { + control: { + type: 'select', + labels: { + 0: 'As number: 10', + 1: 'As string: "1,500"', + }, + }, + mapping: { + 0: 10, + 1: '1,500', + }, + options: [0, 1], + }, + labelText: { + control: { + type: 'select', + labels: { + 0: 'Plain text', + 1: 'Very long text', + 2: 'Using markup', + }, + }, + mapping: { + 0: 'Label', + 1: 'Really, really long label name', + 2: ( + <> + Formatted label + > + ), + }, + options: [0, 1, 2], + }, + }, + args: { + count: 0, + labelText: 0, + // Pass-through prop: Carbon's Checkbox onChange handler. + onChange: (event, { checked, id }) => + action('FilterPanelCheckbox onChange')(checked, id, event), + }, + parameters: { + styles, + docs: { + page: mdx, + }, + }, +}; + +const Template = (args) => { + return ( + ++ ); +}; + +export const Default = Template.bind({}); +Default.storyName = 'Filter Panel Checkbox'; +Default.args = { + id: uuidv4(), + title: '', +}; diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.test.js b/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.test.js new file mode 100644 index 0000000000..06a9b2fb5f --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/FilterPanelCheckbox.test.js @@ -0,0 +1,75 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { pkg } from '../../../settings'; +import uuidv4 from '../../../global/js/utils/uuidv4'; + +import { FilterPanelCheckbox } from '.'; + +const blockClass = `${pkg.prefix}--filter-panel-checkbox`; +const componentName = FilterPanelCheckbox.displayName; + +// values to use +const className = `class-${uuidv4()}`; +const dataTestId = uuidv4(); +const id = uuidv4(); +const labelText = `hello, world (${uuidv4()})`; + +const renderComponent = ({ ...rest } = {}) => + render( ++ ++ + ); + +describe(componentName, () => { + it('renders a component FilterPanelCheckbox', async () => { + const { container } = renderComponent(); + + // NOTE + // FilterPanelCheckbox returns the Carbon Checkbox directly. + // Carbon Checkbox renders and applies props as: + // + //+ + // Test wrapper div exists. + expect(container.querySelector(`.${blockClass}`)).toBeInTheDocument(); + // Test checkbox exists. + expect(screen.getByRole('checkbox')).toBeInTheDocument(); + }); + + it('has no accessibility violations', async () => { + const { container } = renderComponent(); + expect(container).toBeAccessible(componentName); + expect(container).toHaveNoAxeViolations(); + }); + + it('applies className to the containing node', async () => { + const { container } = renderComponent({ className: className }); + expect(container.querySelector(`.${blockClass}`)).toHaveClass(className); + }); + + it('adds additional props to the containing node', async () => { + renderComponent({ 'data-testid': dataTestId }); + screen.getByTestId(dataTestId); + }); + + it('forwards a ref to an appropriate node', async () => { + const ref = React.createRef(); + renderComponent({ ref: ref }); + expect(ref.current).toBeInTheDocument(); + }); + + it('adds the Devtools attribute to the containing node', async () => { + renderComponent({ 'data-testid': dataTestId }); + expect(screen.getByTestId(dataTestId)).toHaveDevtoolsAttribute( + componentName + ); + }); +}); diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/index.js b/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/index.js new file mode 100644 index 0000000000..234c92987f --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanelCheckbox/index.js @@ -0,0 +1,8 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +export { FilterPanelCheckbox } from './FilterPanelCheckbox'; diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.js b/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.js new file mode 100644 index 0000000000..c718fe5879 --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.js @@ -0,0 +1,67 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +import PropTypes from 'prop-types'; +import cx from 'classnames'; + +import { getDevtoolsProps } from '../../../global/js/utils/devtools'; +import { pkg } from '../../../settings'; + +// The block part of our conventional BEM class names (blockClass__E--M). +const blockClass = `${pkg.prefix}--filter-panel-label`; +const componentName = 'FilterPanelLabel'; + +/** + * A container with a label and optional count. + */ +export let FilterPanelLabel = React.forwardRef( + ({ className, count, labelText, title, ...rest }, ref) => { + return ( + + + {labelText} + + + {count} + + ); + } +); + +// Return a placeholder if not released and not enabled by feature flag +FilterPanelLabel = pkg.checkComponentEnabled(FilterPanelLabel, componentName); + +FilterPanelLabel.displayName = componentName; + +FilterPanelLabel.propTypes = { + /** + * Optional class to be applied to the containing node. + */ + className: PropTypes.string, + + /** + * Number to be displayed with the label. + */ + count: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + + /** + * The label for the component. + */ + labelText: PropTypes.node.isRequired, + + /** + * Optional title attribute for the label. + */ + title: PropTypes.string, +}; diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.mdx b/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.mdx new file mode 100644 index 0000000000..753b27cb62 --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.mdx @@ -0,0 +1,24 @@ +import { Story, ArgsTable, Source, Canvas } from '@storybook/addon-docs'; +import { CodesandboxLink } from '../../../global/js/utils/story-helper'; +import { FilterPanelLabel } from '.'; +import * as FilterPanelLabelStories from './FilterPanelLabel.stories'; + +# FilterPanelLabel + +## Table of Contents + +- [Overview](#overview) +- [Example usage](#example-usage) +- [Component API](#component-api) + +## Overview + +`FilterPanelLabel` _**is for internal use only by the FilterPanel.**_ + + + +## Component API + ++ // diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.stories.js b/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.stories.js new file mode 100644 index 0000000000..781d452cd6 --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.stories.js @@ -0,0 +1,85 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +import { FilterPanel, FilterPanelLabel } from '..'; +import mdx from './FilterPanelLabel.mdx'; + +import styles from '../_storybook-styles.scss'; + +const storyClass = 'filter-panel-stories'; + +export default { + title: 'IBM Products/Internal/FilterPanelLabel', + component: FilterPanelLabel, + tags: ['autodocs'], + parameters: { + styles, + docs: { + page: mdx, + }, + }, + argTypes: { + className: { control: { type: {} } }, + onChange: { table: { disable: true } }, + count: { + control: { + type: 'select', + labels: { + 0: 'As number: 10', + 1: 'As string: "1,500"', + }, + }, + mapping: { + 0: 10, + 1: '1,500', + }, + options: [0, 1], + }, + labelText: { + control: { + type: 'select', + labels: { + 0: 'Plain text', + 1: 'Very long text', + 2: 'Using markup', + }, + }, + mapping: { + 0: 'Label', + 1: 'Really, really long label name', + 2: ( + <> + Formatted label + > + ), + }, + options: [0, 1, 2], + }, + }, + args: { + count: 0, + labelText: 0, + }, +}; + +const Template = (args) => { + return ( + ++ ); +}; + +export const Default = Template.bind({}); +Default.storyName = 'Filter Panel Label'; +Default.args = { + title: '', +}; diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.test.js b/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.test.js new file mode 100644 index 0000000000..efdbc4e31c --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/FilterPanelLabel.test.js @@ -0,0 +1,71 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { pkg } from '../../../settings'; +import uuidv4 from '../../../global/js/utils/uuidv4'; + +import { FilterPanelLabel } from '.'; + +const blockClass = `${pkg.prefix}--filter-panel-label`; +const componentName = FilterPanelLabel.displayName; + +// values to use +const className = `class-${uuidv4()}`; +const dataTestId = uuidv4(); +const count = 5; +const labelText = `hello, world (${uuidv4()})`; + +const renderComponent = ({ ...rest } = {}) => + render( ++ ++ + ); + +describe(componentName, () => { + it('renders a component FilterPanelLabel', async () => { + renderComponent(); + expect(screen.getByTestId(dataTestId)).toBeInTheDocument(); + }); + + it('renders a count', async () => { + const { container } = renderComponent({ count: count }); + expect(container.querySelector(`.${blockClass}__count`).textContent).toBe( + '5' + ); + }); + + it('has no accessibility violations', async () => { + const { container } = renderComponent(); + expect(container).toBeAccessible(componentName); + expect(container).toHaveNoAxeViolations(); + }); + + it('applies className to the containing node', async () => { + renderComponent({ className: className }); + expect(screen.getByTestId(dataTestId)).toHaveClass(className); + }); + + it('forwards a ref to an appropriate node', async () => { + const ref = React.createRef(); + renderComponent({ className: className, ref: ref }); + expect(ref.current).toHaveClass(blockClass); + }); + + it('adds the Devtools attribute to the containing node', async () => { + renderComponent(); + + expect(screen.getByTestId(dataTestId)).toHaveDevtoolsAttribute( + componentName + ); + }); +}); diff --git a/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/index.js b/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/index.js new file mode 100644 index 0000000000..31b7b9df32 --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/FilterPanelLabel/index.js @@ -0,0 +1,8 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +export { FilterPanelLabel } from './FilterPanelLabel'; diff --git a/packages/ibm-products/src/components/FilterPanel/_storybook-styles.scss b/packages/ibm-products/src/components/FilterPanel/_storybook-styles.scss new file mode 100644 index 0000000000..817e54a4ad --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/_storybook-styles.scss @@ -0,0 +1,17 @@ +// +// Copyright IBM Corp. 2024, 2024 +// +// This source code is licensed under the Apache-2.0 license found in the +// LICENSE file in the root directory of this source tree. +// + +/* stylelint-disable carbon/layout-token-use */ + +@forward '../../../../ibm-products-styles/src/global/styles/project-settings'; + +// TODO: add any additional styles used by FilterPanel.stories.js +.filter-panel-stories__viewport { + // outline: 1px dashed #9cf; // TODO: DELETE ME + width: 16rem; + padding: 1rem; +} diff --git a/packages/ibm-products/src/components/FilterPanel/index.js b/packages/ibm-products/src/components/FilterPanel/index.js new file mode 100644 index 0000000000..b85b33687f --- /dev/null +++ b/packages/ibm-products/src/components/FilterPanel/index.js @@ -0,0 +1,10 @@ +/** + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +export { FilterPanel } from './FilterPanel'; +export { FilterPanelCheckbox } from './FilterPanelCheckbox/FilterPanelCheckbox'; +export { FilterPanelLabel } from './FilterPanelLabel/FilterPanelLabel'; diff --git a/packages/ibm-products/src/components/index.js b/packages/ibm-products/src/components/index.js index 0250e6ddfd..7cc7901e12 100644 --- a/packages/ibm-products/src/components/index.js +++ b/packages/ibm-products/src/components/index.js @@ -115,3 +115,8 @@ export { Nav } from './Nav'; export { StringFormatter } from './StringFormatter'; export { UserAvatar } from './UserAvatar'; export { StatusIndicator } from './StatusIndicator'; +export { + FilterPanel, + FilterPanelCheckbox, + FilterPanelLabel, +} from './FilterPanel'; diff --git a/packages/ibm-products/src/global/js/package-settings.js b/packages/ibm-products/src/global/js/package-settings.js index 159567b54e..0b81d671ec 100644 --- a/packages/ibm-products/src/global/js/package-settings.js +++ b/packages/ibm-products/src/global/js/package-settings.js @@ -75,6 +75,9 @@ const defaults = { StringFormatter: false, StatusIndicator: false, StatusIndicatorStep: false, + FilterPanel: false, + FilterPanelCheckbox: false, + FilterPanelLabel: false, /* new component flags here - comment used by generate CLI */