From 295ba5f8210354ed37f2d560cc4435f0748ed247 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:06:20 -0500 Subject: [PATCH 1/8] chore: Generate a VPAT (#1625) Co-authored-by: scurker <1062039+scurker@users.noreply.github.com> --- vpats/2024-08-07-cauldron.md | 65 ++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 vpats/2024-08-07-cauldron.md diff --git a/vpats/2024-08-07-cauldron.md b/vpats/2024-08-07-cauldron.md new file mode 100644 index 000000000..95642842d --- /dev/null +++ b/vpats/2024-08-07-cauldron.md @@ -0,0 +1,65 @@ +# Cauldron Accessibility Conformance Report WCAG Edition + +**Name of Product**: Cauldron + +**Report Date**: 2024-08-07 + +## Table 1: Success Criteria, Level A + +| Criteria | Conformance Level | Remarks and Explanations | +| --- | --- | --- | +| [1.1.1 Non-text Content](http://www.w3.org/TR/WCAG20/#text-equiv-all) (Level A) | Supports | | +| [1.2.1 Audio-only and Video-only (Prerecorded)](http://www.w3.org/TR/WCAG20/#media-equiv-av-only-alt) (Level A) | Supports | | +| [1.2.2 Captions (Prerecorded)](http://www.w3.org/TR/WCAG20/#media-equiv-captions) (Level A) | Supports | | +| [1.2.3 Audio Description or Media Alternative (Prerecorded)](http://www.w3.org/TR/WCAG20/#media-equiv-audio-desc) (Level A) | Supports | | +| [1.3.1 Info and Relationships](http://www.w3.org/TR/WCAG20/#content-structure-separation-programmatic) (Level A) | Supports | | +| [1.3.2 Meaningful Sequence](http://www.w3.org/TR/WCAG20/#content-structure-separation-sequence) (Level A) | Supports | | +| [1.3.3 Sensory Characteristics](http://www.w3.org/TR/WCAG20/#content-structure-separation-understanding) (Level A) | Supports | | +| [1.4.1 Use of Color](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-without-color) (Level A) | Supports | | +| [1.4.2 Audio Control](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-dis-audio) (Level A) | Supports | | +| [2.1.1 Keyboard](http://www.w3.org/TR/WCAG20/#keyboard-operation-keyboard-operable) (Level A) | Supports | | +| [2.1.2 No Keyboard Trap](http://www.w3.org/TR/WCAG20/#keyboard-operation-trapping) (Level A) | Supports | | +| [2.1.4 Character Key Shortcuts](http://www.w3.org/TR/WCAG20/#keyboard-operation-keyboard-operable) (Level A) | Supports | | +| [2.2.1 Timing Adjustable](http://www.w3.org/TR/WCAG20/#time-limits-required-behaviors) (Level A) | Supports | | +| [2.2.2 Pause, Stop, Hide](http://www.w3.org/TR/WCAG20/#time-limits-pause) (Level A) | Supports | | +| [2.3.1 Three Flashes or Below Threshold](http://www.w3.org/TR/WCAG20/#seizure-does-not-violate) (Level A) | Supports | | +| [2.4.1 Bypass Blocks](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-skip) (Level A) | Supports | | +| [2.4.2 Page Titled](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-title) (Level A) | Supports | | +| [2.4.3 Focus Order](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-focus-order) (Level A) | Supports | | +| [2.4.4 Link Purpose (In Context)](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-refs) (Level A) | Supports | | +| [2.5.1 Pointer Gestures](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-mult-loc) (Level A) | Supports | | +| [2.5.2 Pointer Cancellation](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-mult-loc) (Level A) | Supports | | +| [2.5.3 Label in Name](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-descriptive) (Level A) | Supports | | +| [2.5.4 Motion Actuation](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-motion-actuation) (Level A) | Supports | | +| [3.1.1 Language of Page](http://www.w3.org/TR/WCAG20/#meaning-doc-lang-id) (Level A) | Supports | | +| [3.2.1 On Focus](http://www.w3.org/TR/WCAG20/#consistent-behavior-receive-focus) (Level A) | Supports | | +| [3.2.2 On Input](http://www.w3.org/TR/WCAG20/#consistent-behavior-unpredictable-change) (Level A) | Supports | | +| [3.3.1 Error Identification](http://www.w3.org/TR/WCAG20/#minimize-error-identified) (Level A) | Supports | | +| [3.3.2 Labels or Instructions](http://www.w3.org/TR/WCAG20/#minimize-error-cues) (Level A) | Supports | | +| [4.1.1 Parsing](http://www.w3.org/TR/WCAG20/#ensure-compat-parses) (Level A) | Supports | | +| [4.1.2 Name, Role, Value](http://www.w3.org/TR/WCAG20/#ensure-compat-rsv) (Level A) | Supports | | + +## Table 2: Success Criteria, Level AA + +| Criteria | Conformance Level | Remarks and Explanations | +| --- | --- | --- | +| [1.2.4 Captions (Prerecorded)](http://www.w3.org/TR/WCAG20/#media-equiv-captions) (Level AA) | Supports | | +| [1.2.5 Audio Description or Media Alternative (Prerecorded)](http://www.w3.org/TR/WCAG20/#media-equiv-audio-desc) (Level AA) | Supports | | +| [1.3.4 Orientation](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-orientation) (Level AA) | Supports | | +| [1.3.5 Identify Input Purpose](http://www.w3.org/TR/WCAG20/#input-purposes) (Level AA) | Supports | | +| [1.4.3 Contrast (Minimum)](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast) (Level AA) | Supports | | +| [1.4.4 Resize text](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-scale) (Level AA) | Supports | | +| [1.4.5 Images of Text](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-text-presentation) (Level AA) | Supports | | +| [1.4.10 Reflow](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-scale) (Level AA) | Supports | | +| [1.4.11 Non-text Contrast](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast) (Level AA) | Supports | | +| [1.4.12 Text Spacing](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-spacing) (Level AA) | Supports | | +| [1.4.13 Content on Hover or Focus](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-dis-audio) (Level AA) | Supports | | +| [2.4.5 Multiple Ways](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-mult-loc) (Level AA) | Supports | | +| [2.4.6 Headings and Labels](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-descriptive) (Level AA) | Partially Supports |
J}$VF)Rt{%1&`d3htEZ#tRvgO8$`1qC<}qr&^W*`!^T*4$bMgp6TE zz3AovvZ<>JDgjdpjg6f6ktuw`)d*rfE*jF#R3E!h+>QJ!@180v!<2+Ew?}W>1+>x- zNLhMjbXhQFRuXy!S5AP{{-(o{4vy1)07AF}hC#Wy0;f1Ss`HxrTehfA}IDeOT z<_39TZmE@Mj?NX1oF;6+r tw!*qPn9e9=2|fAbt2%txdoChR8X 4hc0!3x!reVx|7^bxTQgU>}-dAv7@i>UzaI;GWrdkt{ppvEwWr!~3hailuF{ zGcArB#cNS={VhFuLTuE(kI#X@lEIQvaA~V#78?2L#s3yu_zpigwTPm&Q1XpOIAy6S zjCAzT>vs`v8okJ{V8$daS+^lPTc{qZC&c|aOBaMQ7-h2xzu=$q60Yo4D&D~RHaIis zU@ lKgk_BFpwhEpY;}5WC=JKYBK;-=Tj2xj2IkS42kxXcQHpu050Bs10Xz33l zm(9J>bx-CrEp=Zi+=PT7w~Mh KhHxjJ1Tx zy%85dhVk7oyT ^26-(T2c2914j`by%8A#&wFur36z-S4=ArzbB^?daG-KipRTLwBo_kNyROV^Xy z6>nvCI;_+LGzk?^^Qt`^7yonGhN`BSb4Me08cJ9{sliY#EKmrfl&&Xj_tHy mWwZm#Tuu$+WC{di!bsk@_35Q~3MFjKs*FmGpCs7WLa$EkJR=rAH9UT#x z>USTUClQ~3>3ItWbMiJamQ2+5cGA2AF>98Wzb^q5tM#2TeO*7+lhdqGbT;cVw0Wh& zKGA+!(T`kfD{5SMOF}*CUpyea_-)ZV0Or~C{Bn0NQ|vP2vOg{PS@}%shh|S95=Tzn z)-A^`X5fQ*PE1{A<7p=+V&3em4;;Y8qdiBugND63BrASO;j3mnHXf=~tf7l1@dZhm z@R!qaP}tpNQxbmV`j^6L2c@xbBaj)g@Sq^b$drNrdt{j_gR`O{S5sln zuS~PrP8UYvb9I@*iN_5|<=oBu@P5{%d8owYo}7*BT47LaX;=ON7VNzsex1PzTZ`vG z`5T>6TedhieVML>H2Z5lHOeG*`zna lpEb^zr%ES!m!0< zatFFi$4 N51T*4IL!|`N|N0VUom8E^BgZr=XdZGdJ4_8;)OO;4&-fmx*KD_|I zO5NA1Yk7U^8@uJ-aacbJgzfwusi_$fffx={jU;uI0+|a}j{N4KPI4eap!1BZC$Dys zFNThaEQvy^XEh~@z);f6!ElWsu|_?2hF1a)W%^%S-XQ<0CHERwpoFWWMUoW3&pGJn z)5o6TZ##(+*+wt7sFoquw4UNGMw*$vZU-Hs)Spo6B<@HGLjIx$aQEaJ)b;d(m$_mA z5(P6_VIMyK+h{*RVz?b;TUjS|8+G$xlRmxoe`z-02On|)OB6`rKIdn_v-Tx7Mt#Bs z?@pxp^PP9<|H$UUuTX+}Z{01z;Ov^2R0|>Z&6WD@#$b?;1z5?m?`KwdUTU(%_z^v% z5x-;O_rJ(-XG-B0%gppy0OHZE4hok~I%(?I3Vv?F1JH5|`-XFP#KG*W)ENC`0-kyn zh7*vLru)+`LSxg7dM(xhZ=M3ZRqaz^p!p=^KFAfm=NhH~LmOq&0AKLvi5N;~7(Bs< zOo>k9OBHUl*!h8abxRW$(zIv@@;Us(Gx%a#to#0#s)N2EQ)vg>EClTVvVjGhrN;aE zw|%{tR#dZqQMPCs{rOsiJiR1z|Z~ z!JY^-x;t{bw)DnCb5kf+O#Ep(?)<9)pnuBydUHd-@1BmP?GKwxxmbvkAiiSTUYR-P z?QakmdH64d2Yr5q|7HUBp0*grC=SYh8IW1qh!}U;8YjIgBtxz3zA!WdJ!hJEc4zBp ziAJ=+d*ZrtPp2hogEn)(WjsPV7lA=G!y$*CM1Mg30(#+PanaD;^nWJ7TKPg5LK`6k z+3Po0M+xX( w_6N zZC)=llWg3%{Y6*YMP;ptFTpSY$CuNL^-F4Xlb;Lg#Ch+RxO}5>RZgJsf&bvSU}NXl zowi@Tf@de)uheA_V4|A@z2uGUUC{N^X0OSftzY{y8-S7f3bfgx24(?6=rY {#dEU|h L))sCKB{GF#d=XJd0ieuT;Gu8cWQMYsZNc$(gu5Y`?z*VGtGN)DQ367K!gQ==e zXY12KCZFPz+G-gH?=VL^A+$MR5b7EVvQXc}guIrujwZ(jL@c?xJlNsV;lu~DYQB@9 zcm;y#Yv*Ru7ru#aStq9j)I4n$GICb#VMbhtoV`ja7}^Ilu&hepgLW6&Hq?wg)5c^H zLXs;s1|x$VXuO5-Yl3rEJ3sp`K^-o#%C831>X3Oor`-_Q6R`2ZE9Sr0cyU$d)d@Mg z5pC5sAWoFWM2$RkhbZ~~==rc|Ko^?oXCnUiM-Al|dUTK|y@lNYoY$ZW?LC}#9cy*G zJ+YM0zA+=an5{swdru*nN0u}?X}|n#I6H;#E;m10Y>Di|pyyd4^WZ&E?0@>#c*s5- zI9cl7%(<0P ;|3Q z?;OpiTk%p5=)Y>Y5knC4eYXYI4;&mtVgv&gCO6eJq=auKP3|!axoA{%6TRK;FW0S_ zbrE1sq7LejOsuHcf|_|SNwHF;tD=RQ7xsI^V^yy$B Iyu EEG!bH>IeU1pkYE#~Gx?d)z|e>Z^f~Nk1bS)( znhpoTqKDq5O+c*`wMQcDRGjZ3cx4D; )Z>UgMH97# zcrHjTg*>bbxE|v5+Ed*P)BbsSGGG1Jz^tQy2IFu0c^Y=oW5Ct-Xee$$R{%for9PXU zmdR7Ah0(R1Ll(0#Mo(z!vwjs=bZFvpkQR-uId;kimuj;KyKcWi`d69O6W>W*`9a&m z&@{jeG}b 8YHMY 5sjlnbN7Kn&-p$}#QPkt6zF z&+bOdu*Z6ihYI)IgZ=uh-iU>np70_pm^om@v%_@-w+4O)Y}fepzgXjgNl$xLWUFRM z4Xfp}v6@?MH(sfg5@DjtokDaH=rQdMc2i#57mUgmQ-B#twsI(m{uqkIv4cfLP>Qjl z^{ZnYMqC9O8IVW7y8&*V5EUY^$A>h;U$L>&u>PZ CAbyLbn2|31;y36L*L+2<>4SPI8huhIzJF=$ z`LcMj)bt|Fsa9t)*e%l3aU9TBRMQ+4) 3I!}` z|GWm-N>+xJyDeI+u7?6-I7H{!L0CLM0EoxqRV`jVj=cslLE15_WwCs|+RU~FY+b=R z5Cr=d );_mkiwSDdo0_p1bCySj7Y(yI6CF&!Jg dXtJ^>h_W>@5q9h~~QPrxq?ghtvd~R(cfYBuQOSI{;JkG{y1CC&6_; z%#$q2?T6`J`g2#5yl=p5Iz5d0UFC2%gNuyS$|s}ii7>XwA}J|Pj|7cAVFq8*{|^gz zZu(=`=gf)9JqTQCdH#2>7wcYtl<{N_C+*&Wlt}cO0&|rWYi9G%p+@A#y-pWsX5XF0 zTC2rT>YQ6+COa1g%Ywtuo?l~i*|!R&8`T5<&fm+wV!#du_4Bb#zbJ1{D;X)%y*hH6 zTWR}Uno@Eyw6@luWCj^|mg2PdD;*+s#vtn7eFe9-P_@INRQdH _U+nC+h7r1rPv-WJEP?Z1y>5!d8 zAVp%vi)Kmbd2IF2QMH_kxD=HEI&BsJgOGhN{ls&a!Y72)FJRHlz^vW#RyWOuxz8+s z80vNIfn3TJSM2WQN{gdFeA!@U94k*ux1`}Ro*jczEr62e@8lRTx#t7FSWY)bREsIe zV`XSNOjKUS2HbfQQnq~3Y-jxb0rxY~_6WLsAHG58TMoc<{Yog?VU>|}6C+7FLlEQ{ zUE HGSi;1Ok7>cEXj< aEa*_oZ4LpBXx zoSRXUCR0lUWoEQm+rfeO;&U|QcDt riFl@dE38 z>{6@XF%{l=ujN-Xm@$YV3i$iWq~E-(T_ojlc0e*`S`6)S% {wl#-m^d?d-0k9zI41jvw^ZiIFLIpUZq-q0qlI7`6W{=#6o8
)5do03lBGoIZIGQ}H~^-2wR1cNTKa zU?zP_F}MG@V?Z27xMs>%@R_Wt#tl#WnJty41?{F-#S=qr&oSQ0W@U kBzZS0lbJeXTdJ-k0jxj@ aE&sf)X8+Gk1f9BbbbFdSJ{lJDp!1JQwlg1K}KCCuH;uD6(JHg$KGNtdB#xY z@U!(~6=FKPu=c{o(yQmX{JY)hGO4nD#O bnlfS6 z<}s?MC$3oMAl7-P^rbKm9zK(tQ?C0~C{x?Cro!j7_M41I^~8W3(N|(ssV*Cdj-tH- za{~w^Mv&${o6>Q;;;9zO^@vmE2m2fJ(A7CKbB`VTQG4~&X`+mQB%_EoJB12o>}!fU zwsIkT>gbn49|FinIjcuQ`O(8V(09c~e!) !rQ5UM1w)N50oP%;&@S2k zrwpe&1bQJ9 0b=2B!<|V7sq9*?Esp Pz>!oGH05h$ts?&qIRD?n diff --git a/packages/react/src/components/Tag/index.test.tsx b/packages/react/src/components/Tag/index.test.tsx index c65ab1bc7..334ba0bc5 100644 --- a/packages/react/src/components/Tag/index.test.tsx +++ b/packages/react/src/components/Tag/index.test.tsx @@ -34,6 +34,12 @@ test('passes arbitrary props through', () => { expect(screen.getByText('hi')).toHaveAttribute('data-bar', 'yes'); }); +test('should render small tag', () => { + render( bye ); + const SmallTag = screen.getByText('bye'); + expect(SmallTag).toHaveClass('Tag--small'); +}); + test('should return no axe violations', async () => { const { container } = render(diff --git a/packages/react/src/components/Tag/index.tsx b/packages/react/src/components/Tag/index.tsx index a48f21e1e..0ab3404e6 100644 --- a/packages/react/src/components/Tag/index.tsx +++ b/packages/react/src/components/Tag/index.tsx @@ -4,6 +4,7 @@ import classNames from 'classnames'; interface TagProps { children: React.ReactNode; className?: string; + size?: 'default' | 'small'; } export const TagLabel = ({ children, className, ...other }: TagProps) => ( @@ -13,8 +14,13 @@ export const TagLabel = ({ children, className, ...other }: TagProps) => ( ); TagLabel.displayName = 'TagLabel'; -const Tag = ({ children, className, ...other }: TagProps) => ( - +const Tag = ({ children, className, size = 'default', ...other }: TagProps) => ( +{children}); diff --git a/packages/styles/button.css b/packages/styles/button.css index ea2c9e8ff..d72d83c23 100644 --- a/packages/styles/button.css +++ b/packages/styles/button.css @@ -80,7 +80,7 @@ button.Link { } .Button--tag:before { - border-radius: 11px; + border-radius: var(--button-height); } .Button--primary:not([disabled]):not([aria-disabled='true']):hover:before { diff --git a/packages/styles/tag.css b/packages/styles/tag.css index 343ea56bb..458a4e233 100644 --- a/packages/styles/tag.css +++ b/packages/styles/tag.css @@ -1,8 +1,11 @@ :root { --tag-text-color: var(--gray-90); - --tag-label-text-color: var(--gray-60); - --tag-background-color: var(--gray-20); - --tag-border-color: var(--gray-90); + --tag-background-color: var(--background-light); + --tag-border-color: var(--gray-30); + + --tag-height: var(--button-height); + --tag-small-height: 1.5rem; + --tag-font-size: var(--text-size-body-small); } .cauldron--theme-dark { @@ -15,18 +18,22 @@ .Tag { color: var(--tag-text-color); background-color: var(--tag-background-color); - font-size: var(--text-size-smaller); border: 1px solid var(--tag-border-color); - border-radius: 11px; + border-radius: var(--tag-height); display: inline-flex; justify-content: center; align-items: center; - padding: 2px 8px; - font-weight: var(--font-weight-medium); + padding: 0 var(--space-smallest); + min-height: var(--tag-height); + font-size: var(--tag-font-size); +} + +.Tag--small { + border-radius: var(--tag-small-height); + min-height: var(--tag-small-height); } .Tag__label { - color: var(--tag-label-text-color); margin-right: 3px; - font-weight: var(--font-weight-normal); + font-weight: var(--font-weight-medium); } From c570eceb5fa743ffff7ff75ea907652aa6968a42 Mon Sep 17 00:00:00 2001 From: JasonDate: Tue, 13 Aug 2024 10:56:01 -0500 Subject: [PATCH 6/8] feat(react): add CopyButton component (#1603) --- docs/components/CopyToClipboardButton.tsx | 114 ------------ docs/components/Example.tsx | 33 +++- docs/components/example.css | 1 + docs/pages/components/CopyButton.mdx | 99 ++++++++++ e2e/screenshots/copybutton-condensed-.png | Bin 0 -> 2480 bytes .../copybutton-condensed-thin-.png | Bin 0 -> 1980 bytes e2e/screenshots/copybutton-thin-.png | Bin 0 -> 6013 bytes e2e/screenshots/copybutton.png | Bin 0 -> 7279 bytes .../dark--copybutton-condensed-.png | Bin 0 -> 2747 bytes .../dark--copybutton-condensed-thin-.png | Bin 0 -> 2219 bytes e2e/screenshots/dark--copybutton-thin-.png | Bin 0 -> 6285 bytes e2e/screenshots/dark--copybutton.png | Bin 0 -> 7737 bytes packages/react/package.json | 3 +- .../components/CopyButton/CopyButton.test.tsx | 175 ++++++++++++++++++ .../react/src/components/CopyButton/index.tsx | 107 +++++++++++ .../components/CopyButton/screenshots.e2e.tsx | 150 +++++++++++++++ .../react/src/components/Icon/icons/copy.svg | 2 +- packages/react/src/index.ts | 1 + packages/react/src/setupTests.ts | 18 ++ .../react/src/utils/copyTextToClipboard.ts | 43 +++++ packages/styles/button.css | 9 + 21 files changed, 633 insertions(+), 122 deletions(-) delete mode 100644 docs/components/CopyToClipboardButton.tsx create mode 100644 docs/pages/components/CopyButton.mdx create mode 100644 e2e/screenshots/copybutton-condensed-.png create mode 100644 e2e/screenshots/copybutton-condensed-thin-.png create mode 100644 e2e/screenshots/copybutton-thin-.png create mode 100644 e2e/screenshots/copybutton.png create mode 100644 e2e/screenshots/dark--copybutton-condensed-.png create mode 100644 e2e/screenshots/dark--copybutton-condensed-thin-.png create mode 100644 e2e/screenshots/dark--copybutton-thin-.png create mode 100644 e2e/screenshots/dark--copybutton.png create mode 100644 packages/react/src/components/CopyButton/CopyButton.test.tsx create mode 100644 packages/react/src/components/CopyButton/index.tsx create mode 100644 packages/react/src/components/CopyButton/screenshots.e2e.tsx create mode 100644 packages/react/src/utils/copyTextToClipboard.ts diff --git a/docs/components/CopyToClipboardButton.tsx b/docs/components/CopyToClipboardButton.tsx deleted file mode 100644 index 2dc43c338..000000000 --- a/docs/components/CopyToClipboardButton.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { useRef, useEffect, useState, useMemo } from 'react'; -import { IconButton, Toast } from '@deque/cauldron-react'; - -interface CopyToClipboardButtonProps - extends React.HTMLAttributes { - label?: string; - value: string; -} - -function copyTextToClipboard(text: string) { - const element = document.createElement('textarea'); - element.value = text; - element.setAttribute('aria-hidden', 'true'); - document.body.appendChild(element); - - element.select(); - - let copied; - try { - copied = document.execCommand('copy'); - } catch (ex) { - copied = false; - } - - element.remove(); - - return copied; -} - -// Note: eventually we want to natively handle multiple toasts, but for now we will limit the copy toast notification -// to only display a single notification to prevent weird focus issues -const notificationsMap = new Map >(); -function CopyNotificationToast( - props: React.ComponentProps -): JSX.Element { - const { show, onDismiss } = props; - const id = useMemo(() => Symbol('toast'), []); - const [deferredShow, setDeferredShow] = useState(false); - - useEffect(() => { - if (show) { - Array.from(notificationsMap.values()).forEach(({ onDismiss }) => { - // force any open toasts to dismiss themselves - onDismiss(); - }); - notificationsMap.set(id, props); - // toast sets show via set timeout, so this matches the behavior to avoid a race condition - setTimeout(() => setDeferredShow(show)); - } else { - notificationsMap.delete(id); - setDeferredShow(false); - } - - return () => { - notificationsMap.delete(id); - }; - }, [show]); - - return ; -} - -export default function CopyToClipboardButton({ - value, - label = 'copy to clipboard', - ...props -}: CopyToClipboardButtonProps) { - const ref = useRef (); - const toastRef = useRef (); - const [accessibleName, setAccessibleName] = useState(label); - const [showToast, setShowToast] = useState(false); - const handleClick = () => { - copyTextToClipboard(value); - setShowToast(true); - toastRef.current?.focus(); - }; - - const handleDismiss = () => { - setShowToast(false); - ref.current?.focus(); - }; - - useEffect(() => { - // We don't know what context this button will be included in, so providing - // a minimal accessible name of "example, x of y" - const elements = Array.from( - document.querySelectorAll('[data-copy-example]') - ); - const index = elements.findIndex((element) => element === ref.current); - if (index !== -1 && elements.length) { - setAccessibleName(`${label}, ${index + 1} of ${elements.length}`); - } - }, [value]); - - return ( - <> - - - Example copied to clipboard! - - > - ); -} diff --git a/docs/components/Example.tsx b/docs/components/Example.tsx index ebc8fbd29..055d5c70f 100644 --- a/docs/components/Example.tsx +++ b/docs/components/Example.tsx @@ -1,6 +1,5 @@ -import React from 'react'; -import { Code } from '@deque/cauldron-react'; -import CopyToClipboardButton from './CopyToClipboardButton'; +import React, { useEffect, useState, useRef } from 'react'; +import { Code, CopyButton } from '@deque/cauldron-react'; import './example.css'; interface ExampleProps extends React.HTMLAttributes{ @@ -8,15 +7,37 @@ interface ExampleProps extends React.HTMLAttributes { } export default function Example({ children, raw, ...props }: ExampleProps) { + const label = 'copy code example to clipboard'; + const [accessibleName, setAccessibleName] = useState(label); + const ref = useRef (); + + useEffect(() => { + // We don't know what context this button will be included in, so providing + // a minimal accessible name of "example, x of y" + const elements = Array.from( + document.querySelectorAll('[data-copy-example]') + ); + const index = elements.findIndex((element) => element === ref.current); + if (index !== -1 && elements.length) { + setAccessibleName(`${label}, ${index + 1} of ${elements.length}`); + } + }, [raw]); + return ( {children} ); diff --git a/docs/components/example.css b/docs/components/example.css index df6f7cb1c..db15b7310 100644 --- a/docs/components/example.css +++ b/docs/components/example.css @@ -41,6 +41,7 @@ body.cauldron--theme-dark { display: flex; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; + align-items: flex-start; } .Component__example__code pre.Code { diff --git a/docs/pages/components/CopyButton.mdx b/docs/pages/components/CopyButton.mdx new file mode 100644 index 000000000..975ecf289 --- /dev/null +++ b/docs/pages/components/CopyButton.mdx @@ -0,0 +1,99 @@ +--- +title: CopyButton +description: An interactive component that copies the provided value to the user's clipboard when clicked. +source: https://github.com/dequelabs/cauldron/tree/develop/packages/react/src/components/CopyButton/index.tsx +--- + +import { CopyButton } from '@deque/cauldron-react' + +```js +import { CopyButton } from '@deque/cauldron-react' +``` + +## Examples + +### Default + +```jsx example +-
+ notificationLabel="copied" + hideVisibleLabel + thin + > + {accessibleName} + +``` + +### Label + +Both the accessible name and the notification label can be customized. + +```jsx example + + Copy CSS Selector + +``` + +### Hide Visible Label + +When space is limited, the visible label of the button can be optionally hidden, with the label show on hover or focus with a tooltip. + +```jsx example ++``` + +### Variants + +`CopyButton` is an extension of `Button` and limited variants are available to be used: + +```jsx example + + +``` + +## Props + +Unless otherwise noted, prop types are inherited from [Button](./Button). + + + +## Related Components + +- [Button](./Button) \ No newline at end of file diff --git a/e2e/screenshots/copybutton-condensed-.png b/e2e/screenshots/copybutton-condensed-.png new file mode 100644 index 0000000000000000000000000000000000000000..05d9c922cdfca5968c410b358f0dd54bd391f5cd GIT binary patch literal 2480 zcmV;h2~YNkP) Px;YDq*vRCt{2om*@Z*%`;rjK|~HGx06PDy 6cr z7Sxxjq^(+!A{10XNQ?=j0>N9=N>y>RfKWxZeOPHPV!g0op$m-&N|bB~B_yO&$;5Vi zjmP7eJ`4q8$I0wu&UmJKelLl~XXg9so4=30IdkS1i^h@wfzTUxxdk8)azMsK=n`aH zgf2nGMd%V_T!bzm$}IpP0RiwCfuH61EC2%JSQx;K0I$Gt69zEIu@(U`uI3Q{H 59XXCdvK@Iff5I{DXd)yP9d^gTc=LAlG-{a QdXZ7nhIGs+YvBKf-KW^VTed@RSTMumBvK4A9>F}%&yPckVH!kFa+Q1U~ zW(( MOc=FdbUJNCKJ4`;6=ed`CEoO@C!69BRViH z0#OVyE<%?e<05p4VxtQGo|w3F>Eig n)GRt5n2dCMG5>UAi<@T7$>q z*|lqz$K!#gK?`Uh4_TItM5eQ)FB^;`>2OvVjikKeD`VX0>FJhzKbimLR)7DUPeLD` z>p5RlRi#MKVh#KD?ORAge<&2{>FI%_K?`Uh4;;shk3Hb|`7Z9#=~N;ftM;uR2stKH zrc}6n>vJi$WK*fs%^RO760w+Dw{A()aO1`eNE)QTaa? m7L$T zB0SHhl5-3`N WU5dhsGJ)2^5s82&D57gscC_iFJCUZ z28Ll;T3U`CJ<4%hsWvRjo;Y!$z_mV~PZ8H@BoEPO7~X8pQphoGI2;B5tXjSLO6b~^ z(6zEmyB0O9UcEXL3WY+UEnBvzEMRPGtgWqWYHDg=V4$n3YfkTqeeK=5ck?%Tz245w zPDNa+g*=FL0sz2NQBnKCn%WoEP;{jVLD8s!Oag&GG#VBE@9*#L?(Qz|F6_ScD~=rF zibZ|?GoUyw^*GX7O3@(i$ckuiyWROO`}+Fwz00!f@#Dt}`r4YB8o%GKtZU1jhiBv! z3o2axDkI(Nl{y2Vdf75XA}&V5G9@$|I&`QYVqagMSTl$f!oa{l{<}V(ud}n0qNv4( zo*$L-FefA_bp`?{cZRWi ZI&Hze(LM%ckI})V6*Mp zw{O~{z;nDbO&>dU%;)pvZxm~W;tFB;^5uTNUj^5ypND7Ub+Tp)WwSdL+@sQJqbQl* zKk{!t6f769zxBYLy+3|5UV287w;J|%duwa!-o1Or$Hz-G@Or%pYN)KN?Ck73eE4vF z#J;}1f@VISucM L80KD0>(`K{F4k3el4ZXd+0Dz{ZCY#L$DI87Hfk2?ZyZQKhKGpi#d{oW@c)rIK zAqYaWW+szKCSm{po84hBD08z`PN}fqpxyoZBR#=$J;8JErg2Ena6cFf27^J^Q))!- z7Ss$PD!5iVd6 -|ug4Zyy~U6 `_Vg_e+X}+y{V~5iiYOqX4OXfMenX#x30dvULDtJ zBo8*5L&}h_&F(Z96k8NW&L?2a+O>ZQzSG-#Egp-_*#O54FV@x7)va5!yppV4yEYgM z_V)J1N@~FC>gww1>fmW8hN7q=M~jEm4E$hZhyq66cSBq@86 zzjQL0jPyB8s`9^6RE9ig*c}>{1^`Xd$z;-Mvoj0>^_+ GKK$ShzyIC-)&s@wVmMCGYCLsa0B*ruuTZhusnL^+ zzzO!vR=W@Ga4E}hlYfgnJT+}}8kTIQaf?R3ld)(F?$0nS67+LX*=TO!j}hj*L^hJ+ zSpoVg;W)ra%;+$ h=c2T3T!i=oeaSD_=?|0fyJ>ckgXmoxkZz*#MRV1w z6upWxST*WeWL(`KWQAN>5VE|GgKH(sAYew^V8$?`Ry_-V)@xkEQ!pb&7%`2H0;vf~ u1VWb}<05nkGA=@wAmbu*2{JB1m;4tm_*CCkN}rYh0000 Px+c1c7*RCt{2oNa6qWdO&Y=k9v9_HON7+D52DKnUsDI1@xsL{o~Gg%6teN%W&= z)UZGpAJ|JmQe!v6*!-eXqa;RA{Gfs{QAy>4qNoVP1P6i}P$sOjUEjOjUGMQjv5vls z+;ewci1+)_^m%Uo`}g|q^LEb-Cet}!@c)L;+kk=o7$Gn)LSSHoz`zKB0RR9{dK nyY#|1$D1F?lS1X>UCGX_{R{ zw?4`e%zvE%-9qrufe&ALd7E#8KbeSMzW684^B5vDO 8T}&$R?|h;iQt(1wH1>7Q zw#G~_B!-j{bPGWwa{2xT)+T4;g+gx8qD2hDV2Bip#aJu`0J#5wwPT|rwFeaPS%EKN zn?wi+e6aw4g|k))r4lNf=OrGsu4mQq-@3k4;!)u|FO`&ZEtu#wMa*WiEX$@-NxR*S zcLf0e48z#%_H-)AvTQb+tv!HOkbs6F5)@J*K!CCmYWb(E1VBJYi9pg}WT^8x0nhW; zSFjAjFnCR|BBUFyD*Kmiaj-@x>JB22%kfxjehEoYot++%RHs*qL?W@9^(l(#?Cexa zUu(4XF9^IS2saf$Xf|8q>fnc9Y;@#{!-s0i7rR_ (Ohe XfaYyX){x^B6^^F^8F@k3AYR eJv29*Sq-$JOR9v&W^KN9qKJpKLs3YY&h z^ )F1oXS=ME$ FMd|QBj{^n7+QgKp-$N zF#!NLdGchX%I$V5Pk`3`t8h5)a5(Q!MM{Mc?CWFu{Jux3)<;!jY;4Tu^QofGFwEfK zpxf=9U+8wb2L}h0w*ie%(SKd8N3UJ@V`712iCZG0B`Ei)(V1xY)XDkf@pxR7iFH|j zW@hHpsZ(0$mnr9<(jKUo0ImI(6+}TS 0l_4n`JABjY2tCp*wUv;jbp&`{ZHT6#r#FD#~*WDvS zE6cIG_}J}syWOtjeERBZjn@7Z{^A>9+&eJPI|MXM7mJE^fE9~H?ByMN{<&-_``78) z&@BWU$K`T4tF1K_i(y~K@ ML=?r<_pMp7^logOL?UtI zo3Gz~>&@*u>fHQ=1i^CkmshbNEQEa%n?8}hI21!7YFTG(S&IMg3?CDthtik!#!YRc zqnjfvI<(DD$A4FITR(<2{qAh+%d9Y4l6a(OmoNbc8k$;3=Tp|j&)6tE9z)YJ1h+8= zh^bQU93THCm-(&0M@7sW5P)`4&G(qwA7{B$G-=i6fm9;|_#gq53J3`jD&c>1kVMc# zkY)%?x@~AP9! IcQ+^?mqtX84iN;APU-GYx+DdpyQF^e z`Tp^~=Z~55?3p>w%$&W~T6;xls3{WQQsV*uK!8NZX#xNm4>*5{jRqb^LcV+hPY@4H zMH!%M6t)cj6e38u7uvp=`&oX*T6*(+V e40?lA{{QdX%z?L+9g9;%6H_LOJz?Ukbl?|HP4zaj50T z<=uJ>M-?Ul5s`gJ&z&9MagfBOesGw*C9#F>@UjR%d;6S$C?(9&?hz3-I@+-V7LxeC z&{O;mOz7Z5pOQr$jK8hmLqz{~gSk1jGR>Rk48!<%l-xtMlo4oeYHRp)p_p*vc1BT{ z6egcC&8Y(#xCsAubI9*+E -XTat{5e>ybl=!{|H-tnhPw-!T!Z`H)&Tc8#e!#?e~NV? z^c+$+BauI!^EY#_D6!;;F^6V8BN{T1>W9b8^r|e@jLTKmpB0X0X~W~2`a-Izs+WgW z^b$t{HzBtZB@;IK^O@#o`Icmtli-T_lH$(w`DZ$eW00VY09m%LMh7+0l0Wqyl4bsk zRA)R{J4z%cz+mSY>8IGzyuWEttqviX9$(Zpc=>H&`>9i6a_9PS_v5DJ(JV!xZt#{y zCE_8z{e7kM@ye(E-wx^UyRdoG*=suBZIev=`?0q A1j!sfPqe{N3BAr|;|W#g=-Y(L-F_;EszwWXzHd<;|%N-Op;JA0}2cTdm8 zY^7y?T4WxiXZ4L;QMr?o-NF3m#<4 4)3(+{?Z fNs?G8d;e>NP41WI>X@XYCVqZa`Ift}s&SmTf TYySp|N}q_RL) zeHPnCv&3a(WmV0yye3ZLGOU`?5;*;g(H~Y;;K&c&+PlEh!aBUwZgSc+A`Le=ka1 zn3{4jxN=V~JUt0+KH9kMRk${-JET-Wu^ 4;7+(Tl4wzq8h7F)t}NQ%FBi| zM_&54FyVmB)hl%MF*iJlW?`2lhm3khE-tP(!j)*MB#>eY&Cgb4X_#7;*o?Z*s zj2gEEOCmocCnq~he@~MR2@Vc!jfojrY6%#zKVrzU^B>EW#H2krJ-s>VBD@JqmdR@f zdARS6BJpr{ud1rD9?K?JprNLYr u119AAN|5ipuw*qW1%@l~bM?G ~gDjy#1s=oZ zKZ6@*Bz@9h%HDM@3!NM8mfpdz6?#`e#`}wXqkzl(%aaRoY;@E*iG)P2_x{CrPkAV- zihiZL7x*)geUDc=+h-Gel0QlqfFU!DpXUw_u;$~iFCZET3F$B;qGMp-yxT0TG&k_R zx!A9FSvIM)*YWlBwYIi4HiiLay|GlJ@*A6*o!z94Z>f@-d=6W04w_;pcv}OnPmb67 z2wnx9j) l=BvxL2QWkSv$L~VkOxaGAQseGvP~4K=Ck9D zet*8KmLW`FjSc3=Ctg#jko()%*w~LBNt47Z-{7mlE=b=#9XeS2QJtI1z{zR9)a+*( zc-)gFVB^0dSFG*4P!~6}0bZV1UY2HL?5IleAeFBb6j_Dy5MlF==IXM#!C{s9-)1Iu zE-vEt{afz+yvR^pRQ%)e>_me!J0Ayy70S!~bO-qSw-R}T>((|4733XaR-bTMWoWQe zC5hVBrZ*mJqU>|@O0<>4)57F9wQA}F2Wf)s%l#C#Jwd3WOVF;*aNt#)h>aGc76Mj8 z%sr;%o!*F!Kz!sU&bk&e1Lhv+M1q^+;#80G6-5aT+UoiJ2gd6GU}M9IYNouU-fiME zojO@bNYqye(LZ#54=Hm#5Ed40Z4G8hB#2*!K;9k&PZpmRxNsYtI 1NYFQZ>@c860(#&iHWO?wx3b%EIt9}C$lVGr- z<7h-Wp &p;^GwK 8*?+io|5GDjOTw;3`-6%;( zJ+F>dvzavVG)O5Z6crWQt~WD)H-m$NC_;L*9I2KCX9s(GZB89mS62igvaPKxBO}9# z>)#6g`?%7E_1xF(u0M?7Lu%d#yb01CzS!sNkE0wBn(Z6MZZI2&E8TYI#_ zf*;6IXNrf+l|;ne>x=lKd`COy$?Y2S4k6E?Dci|?POLs+SI>ee`+3sRcr|7)5IsI8 z4xby?%cJ77T3A`u)>C1Yr@RO~Awj1Ayy|V% GP`9BD^X zPNUWK+Ay8u^iZCf=io0kHnuJrRRaSmpnYbBqVDGA<`IxB;kUNB%I~(;4YDmi{^?3b z*!INNb-0P4AufRU-{AAKnuT=?44^NIX_+6XsHu&D%#o*X@VjXyR~7<+^zLfN%VVez zKmhBJ^yCj8+)p=0A|oSrcI<&SO-
QO?fJ8two9Dv%brT-_23AeQEGViMu#_|);R&PZKd z9b8H4tD2RKje3T#6Nm!3x@_a8?btLcsZr`5SU7C$9c}eEeoobeXqo4jW##oQ#@An=M%XfYvl&|JyH2Hu3FIkdKaw8D#+CCCui z0 ;4vWo!)%4OiFKRC7jOVRt<- z6nUgCwuOxGrfoa7mO(%R>+PuNuvLqllusNhDmps-Xw8QIXT7Zgs4SX6qP@KhOvPQO zcgbO7!}PKRm65^@5dc{K`S8DCHZ+nU0s<&hVEc~KkpKrr6x0*Cy1GV2G{d7WbW05} z4m*js(zu@a!R8kh+AiiDXpQ`%+k>yk`;KK%(BI8|0$g0Xx7Vi}92{6s7~c}7hb0&J zWlb64`GsT_r031hKCFz#a`B^6^o6&MM^f_-q?COP*IN>yDIJ+S+cQd^Ool;a`pan- zA12$w6_&s6WACg&U2{L!NfahBQSwJL%#q&R7yiAxS%}JW%*$oqmAo52B xbNP+ee2qe8^YW*G=yhe z^u4=8mINg!)Nu_4{{x|LwEwiII^J*Viune$?dQApI^idT}u^S&U|i*4EZ?>3;9; z@2{ZaaQAfg_M~3<@HoPpv=q!E9wJ PMv9E-WPeO?v51xEyLlBmn z%&!7vQyo2NqZ;3Ve8qPF0Loqf2uN$uEdEc>QwhkfdLj R2@DKmRY`T-9HxSzy7*M=?d>%*h@Ms^D;0pqLP#%#kB1lb za(8!E+;03!O^qO3!q&k-@WvonA_5%?ri-03mJk)iUqsy9*9Ue4y}q=e&!*?+8{IdQ z!bmZ&uxwtxUTkn<78COaPsPQ>_+2%%wXYgn&3}EC=R5zRSv)>50rH%qlau%bYa#-C z8C+c4LiHSI%g)9I84;1|{=5)ff{68SYHTe2+vjG(Vr Y+jCG))od$Z@ZrkB_GT2BnUq(8crccE`NE2eYP zij;R?P6D-my0cb|CMm|luEaP-S4F&LcL$uCTa~ncM0u~qPzZQ4#+K(OC?d2&6_g>< zQRS%k!d|O}2pya`D3c&@eI w{Du;oj+M<^qXuU|VT3t(- s zlB!skm@V}j)BxYo(C+%|gJjUj)hKO9;=t?wBu1zRIU2BpYigpQadf?{WwOLe1=3hV z&))4-QbP-eO;Ss9Gn-nvC$IQI;I)S3o6+wjB|`%P+p|)9HWvf&wBl>)>q$vTZm=97 zhm<^gi0+72I@eOAW#6xS#d4Dtr3p_nGqao+Np58}p-{2O QH1rIS2>a4SxKET8EiLWKc1HXSSQl}!)Qt3u z9(`&M6HVA_G@{L6QEZI0`!F*j6{0{CKD 0YMVfdj&|xY8g7|V2RNoEV0swyp+NSh)>BcTkrjOd7^Gm*p+%% z57i*7+J F!XUl}k~h)_x+)U`u|*;aibdcU{R_lZTRPlo7@3yyj>B%23#$ zuJqzdaw$+Q6#-5r 1)Pz=Z`WMgUEwgj`pVz;v&>`mxSHPyl19ix;qX*f zs3rSc!*+;TV2Y>NgOs><6B(CY%iU;GO5I=S*|f-2^yn^#EK#`7WcDrA+e{2JG&I6K zHcB_9{|w6E(5cd|RKZ8S5)MVg_3kx>PHnggM7<}cda#~6{$?W7-vf9!gG~l84Gm4Q z(vuX#hP!AZ-dU(_N%GK!MU-}drdN{{UIOabp2DxWm#`ErMgY?mBkX@J8G?)Q4Wuj_ zfK@v^MEMeb%oSj^>}|NeL4BWV!+8V*+~0~C*P{O7CiH>j{>8g$nC5|6ptp7x2Q7~P z4^Z76KF)a6_2_@TA4A}aeo;6p<(QWAl+d!KC{Kl=+pKs5+_7Q4chx-fSOV&N@d}fO zh(+G|b mbeyHN>iAzp?IX1XV ze3x 0fsmN!tDHh#|J+2bF50aH*Kl?hLAg}xSHz#${=dn3 zMI#)UO%x?}Ku5==R|fUFCR1K`7HbBX`dlvc>uKA!FX`v8@fe%)^;Qk-5Exx=ZC6%G zv|{N8BF*McEh@yU=>N~&)g#-bE-#r6*52K*`=TJ(36Wsy6xJ}4R)4)d($#}NML W)~#>pUeE7 zlo7O>J3ARTWEhCB!{5Eo3 `2YX_ literal 0 HcmV?d00001 diff --git a/e2e/screenshots/copybutton.png b/e2e/screenshots/copybutton.png new file mode 100644 index 0000000000000000000000000000000000000000..a77d4a932e327f2b5474105697bc2643f9798b48 GIT binary patch literal 7279 zcmZ{JbyQSe)b=H%;Ri@Lgh(mj5Yi Fxg`r=*tJ8vMdw?3L@d&WMSZ_ zgTBb?-`vI^3qt z3{HGwnZfzDx+V;phP1rX%K6-2^154&L7DCK=_f4l W0U-r%?1x{ ?oyF|g%S&{6RkTtMBv;asMwZf253IPKKYLGU zKsj&S!f0 9+z0l}i1~2gm+Fv-r4_Eu2NKlZ_l~6Y;bF zTC4AzjQRbX9uM)kEPfk?-pAveCoA%D#d=O*FYAFz6Cc%UKOcWGs=1Ozb|{h8@Mer| zk@DTpLka@i0+ERS?Nr`wov&Qf2V<#FpOK%%9sjKF-HrVuzPZ6^ok%d#RR1@_`B{9| zE+phG!#sEzgXyx{k-PSLF1<-TfSa%Lc!04QCiqb;_E$uY#c749CjESw7g3be#E;ce zr$Fla^Vg5J?r#0g%VJ_)oEeR^X>(C?zpUD>$cwM5t0QBUPPn8W#bL?wCZdTiZv8yx z*qAXl_RHqiRzp}%kO#zta_gOIg0b1t?> 81Aqo#rxKTi%$5|cx2FEZ OgT5{ zYMiI1rl;$fn% L{kl@QZI{wY`%N9OH8{EVAtt03KZJWOrl&1bT46r>agwW*l z{5d$MEU$QVUGcfytr3nBacMC{rRmER*ZLCPYXJ?HyKD? ll^gAui^A_g?ScF{*mfafeDwjAkmONY3)z$GXF7ozNc)7WQ*5}V! zZ5J1$+U{N-o?mU ChLw(E0ZU6ukdVUhj`d;SIRzSx?(gq! zx`%^}E$lis Ln$EeQ$9 zwY6=l1WmkR^*Fqe4G4WoA-+!*LqVV@#4xBv!O*BuJ$`w4ug=hU>LMhSzsEuRrRUl1 z%rmzvwd5bGCtK?cu ~QSLO1u_m6L0(rx3XS~zX)6|LwzU`oyifd~}$H$LgGRNbZGB-FJZK1nI zXm-^P*M7oGAG5NV^ VKByXCmW{2L$t3=6=;jTYT{GzTf& zH1WUef L35 zEuEw^$C^2(CLKw%WnXl4(7I`@A@;qtpa7q{{XWGXj);Ae3V3m=|H%)un}A6wIU+ZA zVg6>0FEcZ9b91w?u~8t?`C9@#F)iO$sPoazb8&Lo4dsaO zJ~gWRosf_aPy6)qgrm7RM$Bt1fmufS@ngagj@_9?ZYilC5C(pJ;MGM=fKLD(u=&l- z%;m{e4Cw=W;CwNL`E+~wsF(OD8wUqA0Ah|tp0w%9m+PCGVm`ZI=o^-vj!=ypNl#aa z^fGCvs!9Z$R_K%;pPiY5&sPLX^A?}4Fscuah#+T@B)~|&`aw+-|2{L5kjnCOXEq}v z!%iUc@^q&y@Z#|8+qYFg88UC-mD{`PFv3XIN~QT 4 r27QZ8N(8=#Qd@xm?KL@&;gpemg zuz-)^E|*K}cN3>8UDrB+fAuam%J_40&0bcmK&~-3`{ysOPJ^>O;!vwe@k}8{An?Ad z_C~$*qL>=KxjGyE@$ZUDO4<_Zho=jR7P+uJ3l zDn5Mz0BG>FFVOu36WkVb^(GBX&CKj3oDE{F=bhJU04Odl#snTEg6shnTGifueRGC! zb9QbALGpK{F)xkktgt(W&(?3=*$6BwtW(7*(86_f0+N!DH@(HG8P4WXf#>dUI79Ye zQhYp^0?NzFLFYB9vqAsqiNFGCYipl&o)@@Wm6dS;-H8uG{^W_cE#LwPOi~Ph`|b4w zINNI@0HRpt;2JnyTF(o)?PDBQ^q=CCI8*|<7disSA)?*3^`)A5u24GZ+w eAY4# z53hoQZ+m(k;y#Cn?%&TITsXOKB_~gw$;#@7#g8QgONl>GL5|LK203+yhPkz0&;Z@T z!%vR}HV?1PO4XCA%gWAM_AhG|`({n0!PF6O7R*jT^j@Vu+|Mh{bsFXZluBySkg zai&^pSTMPj@zfLyw2e6F4YNNy`MmfMi~Eax*kKlDyYs9mW##1#({-;nHFI;GIsN{~ zZ*9m!lP;3VModiH(ca$M*GCv0+BXQk_T9xrZC%~8^mOmz4Q)|(9ALxyQs(v`xu&Kj z_a-iRsxzn!UK!m*d;h-6eCuoQ(^TL3rtdvsXvJcL5`cQHD90*LGBCiw%gl_4k`h~2 zXc!^EnWC(tqhrWp0K(JX2_*PWMox~2scF#G?@BPB4T$)yz7lYx5w0e(p$iIFGu3Dq z7*r^}juyT!;`t4FAUh?@)O7xVTYE}wt~BUsc0TXpYfsQAk6r{HqB-BRr}SIBva#{? z^##E*X3%zWa*~sc&Bn$ifsVhR@W@FhYxav9IUV%Rpa>??x<6CUZWq&qiKoT(z%NwP zHLgju(M?MkT=Vsp!T~IxbPSDg&`kFnODLTo&xd4lP`9AyNl(a0{iSmLVgXPRVgeG- z>c|PWzK3S%xUaAO^ewkGb+ML^72O&$MZ$7(Cd(7<-STu#raaA58iav->GEw@4+wE8 zsqgQfwK_yZj(d|;*^PBC>#L*OCU(B4si@gs%>Vr9w7kYk7yUkc?%BqPwzjp!{>bF; zFh=H6MFlm`y?P)iB^7ehXoFdpU$*A>`b|AY$S>}Z?c08 ;H>0X;oE z3S53Zf5`;P!;8RLg(W$JQ=98{MX+n*TFAxWD=vI6&^Xc_b8slJQDOn25DjN%XV6|A z9tU4zsa_;OGSkwI_7~-& OfZEfu!1UE9u9JL1%Qo1t8OYCy}zM)X1 z?d@~)LF4yZhdDXbvwnG6|3^)2gLxnZNdk9`iwoZz2Q$8e&u#;zul ;WFqMF`&mH3C>Y6I@`v^}iOzRgVoMiJ#bUeJr0sxdBBo>WtdW(}) zKgvK&I{u18+aSFL2h|NyR}RkiSt7vvVdP$-{87!F{Xw)izc^zb)Mv7#2^Uus-Y5X& z`aXn&Vh%HwB+m9Qnuh5FFI)6g*Z~%ivzf0`bhF>!iAr3QE~*#DdlE7deoN Wqlpf>>*#Q)2JY@`1&yqPmYLN zu8TE7!!970c8Mp?q}|s8DM>A{z?Fsu0IfR~C<_U##0(zlAU!=DA>orN>ahfR4{M5j zPdHvY4X@|O`EE4YyTOrte36%nrk+07V>SZhm6I|xwVfK3I-m7b#2IN-izv(Q1yWY4 zl-mn1h?73)^@1zc?>y&y-{{-y@ha|mb zZjDRSlbI#n v_>WE z^j<8EI2eDy{y_)P0!dq+1(b-0h?*!$ft6m!o?>pB0K5^oTjMNRT{tpUp PGa|9XlkV6Phd_sZ^ zlnzXbdgXdnwas7-cc;Vw2uMj0LJp}e)~J~N-IA-ou_Te+j0U*NPt}^3o#8k+IXRGq zO^l4V-&~&jJ1eHkoFlD;P2}%?!rwfbB~uIww2|9g@+{N`zNfvLO`u={0J73X8Np1R zu27cvek-93eU&J=1zD7bw7Hh9DNH*(W+1-bHajc(!Gpizq6dXpJfaB80E)g10F2c~ z)UhFV&m6R914>fk(_Rk=yAlGvPJ^@6Y<$65^B#kTp|ZZTzaQsYT7n2=GSumPIjj4$ zvjHk8nS-|ZJvpiPBAGT$0SJ}waXs>t6wXFu2e`caYd5}}r>O>)lnTa=>ASH1x%T(7 z3IHU5I>+FUkm>RPgnGcL7EJEP;*91j&`dRk##x7jtY=P?R8+IONSB8!EZsf9u0OlO za5B}_8l5JO{iQ&G;p!T_c^^vnZg~BLk`fIKO_o{-2>iX!Um;Z*LEhdC8>15~IMo^% zR7W}S3al^ VKfNr{QAp>&Cf ziG?qcqay-fFv5(VuhV7;ltnIz7~NJpTfD_PK#F!+qf}^AWAR*87JQO>VCs>}uQB8i zP5QZd_@VZhh=|CuXV0XiK~DZyS~|CH(HVr={`>R6d7c}aCkYBq1gRKrC!rsCP+PVe z4Ot}|kJ8XfQx#g>7a!X5HCU)srEKI<==y3}W1iW9T#H{BL^V9tT3l(#{0Tx>DjSR# zk)%tF=j7a!WmGrq%#dg+OcmG(Xv}A2XG2N)pb2QJQG21n=b-TAC#g>@dekr3rX(`7 z&LWUW;_qc-H2pru8R#bVT8o1_v|009R2Pe7)Q=}ICFb#`J*8mdQMq34!QnPDWng41 zoZJe&eC#w;vpB0Go%ohcEp=t|QOs*`gaWHr$gTuKqOj*LdmuC~PX_pNUfTdNDWqAz zd ;WS}?_7QN340+qP${vTv@CwQ#N$*qvf%~DX% zwdhy!#3~#fpqRg;iW@^nUdj;>6DKNJ+uLt!Rm+?rFE1}AkT2I)S1S!`<=80IvxRp+ z;=vra-ve6CZLZl;INNS83lG>oIKTox8ltDK&uiA+Jdue?pw~4toT6-qTmmD#eHoS9 zb#pokx`i+u$;)apMj`tVP*dH>hKT0v?!&GYL<3H@bxSoW%{u%yM)Ljq{A}wliZjf@ zkA8w`pvttZNi$DcOiYY-*+D@87nTa-)#|6ZPMpT?9Ua!H@kJWBN``$cx53 z@|7#JkxY8}?#(8Jqa#6H5}-COHD=zO=avZE$g9y)ne^rp6}1^FLeoAC4q5V@saxfy zOrudP9@E{#*x2UM5JinI+wqvTLXxm0bY<@TB7@e4L+sempFV*)_R{zJyL|WZy_pH_ zsHn-kxz3&Q_0Co+b{L$`>{@!=^NsVi!asVZPgoi)m5g%}xHpn7?{2k7jfhb7k;~uz zRjvyvagc<7f>u@8HwPm0X4?>g5`zqZ>x{`b*x6ylW5>tG0|RRLT6E0JUEZ5IrF6>* zpMJT_G?bT=luW2 U3vjW(Kr- zu5=KCLDt%VFDSswgH9$ukq=8HAtJimC`^tfp+CIGyUn3iVp}XnrdlkSGPLQ<@X_Hh zjcUJ6a?_CvN#FH#1a+JeLIZ~@uE#=pd5b*lNl+lp%AtRRS&Sf^+3@) =iyNS7g$63+W^m3w&_)e zR{Zt0cdYapx;mZt;<|8tF@1aEvv*N9j}O*n?rg3MfbJf7^LzL+#({4{gcuq}EshUP zqPjTSuUXU3;wL-wr8e^F35gf{+-)E?AX{Sw1K{*q;sa0)W9scR+erCLnrlFDYpDMD zWyy=X(^ ?Z)X_{@Ce>*NBhmqU(zh32NUVE;eHrj1fFY!dTV8RiUfmi@oHT|e25~tlJ zqmez_Lvm=Yssd-1ZR$sO(ax?OnT8Ax|4d9`dP))J=~