From 02c9d64ca57a99b703ff196414ed647059aa5dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ferreira?= <60441552+JoaoFerreira-FrontEnd@users.noreply.github.com> Date: Mon, 24 Mar 2025 09:57:54 +0000 Subject: [PATCH] feat(avatar): add disabled property (#30284) Issue number: internal --------- ## What is the current behavior? Avatar does not have a disabled state for the ionic theme. ## What is the new behavior? - Added styles for ionic theme disabled state - Added states e2e test & snapshots ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information [Preview](https://ionic-framework-git-rou-11728-ionic1.vercel.app/src/components/avatar/test/states?ionic:theme=ionic) --------- Co-authored-by: Brandy Smith Co-authored-by: ionitron --- core/api.txt | 1 + core/src/components.d.ts | 8 +++++ core/src/components/avatar/avatar.ionic.scss | 6 ++++ core/src/components/avatar/avatar.tsx | 11 ++++-- .../avatar/test/states/avatar.e2e.ts | 27 ++++++++++++++ ...ionic-md-ltr-light-Mobile-Chrome-linux.png | Bin 0 -> 2055 bytes ...onic-md-ltr-light-Mobile-Firefox-linux.png | Bin 0 -> 3066 bytes ...ionic-md-ltr-light-Mobile-Safari-linux.png | Bin 0 -> 2197 bytes .../components/avatar/test/states/index.html | 34 ++++++++++++++++++ packages/angular/src/directives/proxies.ts | 4 +-- .../standalone/src/directives/proxies.ts | 4 +-- packages/vue/src/proxies.ts | 3 +- 12 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 core/src/components/avatar/test/states/avatar.e2e.ts create mode 100644 core/src/components/avatar/test/states/avatar.e2e.ts-snapshots/avatar-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png create mode 100644 core/src/components/avatar/test/states/avatar.e2e.ts-snapshots/avatar-disabled-ionic-md-ltr-light-Mobile-Firefox-linux.png create mode 100644 core/src/components/avatar/test/states/avatar.e2e.ts-snapshots/avatar-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png create mode 100644 core/src/components/avatar/test/states/index.html diff --git a/core/api.txt b/core/api.txt index 55f32d049b..2c79b81465 100644 --- a/core/api.txt +++ b/core/api.txt @@ -185,6 +185,7 @@ ion-app,prop,theme,"ios" | "md" | "ionic",undefined,false,false ion-app,method,setFocus,setFocus(elements: HTMLElement[]) => Promise ion-avatar,shadow +ion-avatar,prop,disabled,boolean,false,false,false ion-avatar,prop,mode,"ios" | "md",undefined,false,false ion-avatar,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false ion-avatar,prop,size,"large" | "medium" | "small" | "xlarge" | "xsmall" | "xxsmall" | undefined,undefined,false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index b798198e7e..cea5a27201 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -343,6 +343,10 @@ export namespace Components { "theme"?: "ios" | "md" | "ionic"; } interface IonAvatar { + /** + * If `true`, the user cannot interact with the avatar. + */ + "disabled": boolean; /** * The mode determines the platform behaviors of the component. */ @@ -5759,6 +5763,10 @@ declare namespace LocalJSX { "theme"?: "ios" | "md" | "ionic"; } interface IonAvatar { + /** + * If `true`, the user cannot interact with the avatar. + */ + "disabled"?: boolean; /** * The mode determines the platform behaviors of the component. */ diff --git a/core/src/components/avatar/avatar.ionic.scss b/core/src/components/avatar/avatar.ionic.scss index 2648f827a4..a37227748f 100644 --- a/core/src/components/avatar/avatar.ionic.scss +++ b/core/src/components/avatar/avatar.ionic.scss @@ -171,3 +171,9 @@ :host(.avatar-xxsmall) ::slotted(ion-badge.badge-vertical-bottom:empty) { transform: translate(globals.$ion-scale-100, calc(globals.$ion-scale-100)); } + +// Avatar Disabled +// -------------------------------------------------- +:host(.avatar-disabled)::before { + @include globals.disabled-state(); +} diff --git a/core/src/components/avatar/avatar.tsx b/core/src/components/avatar/avatar.tsx index b961fa3301..da5ccb8170 100644 --- a/core/src/components/avatar/avatar.tsx +++ b/core/src/components/avatar/avatar.tsx @@ -40,6 +40,11 @@ export class Avatar implements ComponentInterface { */ @Prop() shape?: 'soft' | 'round' | 'rectangular'; + /** + * If `true`, the user cannot interact with the avatar. + */ + @Prop() disabled = false; + get hasImage() { return !!this.el.querySelector('ion-img') || !!this.el.querySelector('img'); } @@ -81,6 +86,7 @@ export class Avatar implements ComponentInterface { } render() { + const { hasImage, hasIcon, disabled } = this; const theme = getIonTheme(this); const size = this.getSize(); const shape = this.getShape(); @@ -91,8 +97,9 @@ export class Avatar implements ComponentInterface { [theme]: true, [`avatar-${size}`]: size !== undefined, [`avatar-${shape}`]: shape !== undefined, - [`avatar-image`]: this.hasImage, - [`avatar-icon`]: this.hasIcon, + [`avatar-image`]: hasImage, + [`avatar-icon`]: hasIcon, + [`avatar-disabled`]: disabled, }} > diff --git a/core/src/components/avatar/test/states/avatar.e2e.ts b/core/src/components/avatar/test/states/avatar.e2e.ts new file mode 100644 index 0000000000..3e2e570328 --- /dev/null +++ b/core/src/components/avatar/test/states/avatar.e2e.ts @@ -0,0 +1,27 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * Avatar does not test RTL behaviors. + * Usages of Avatar in slots are tested in components that use Avatar. + */ +configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ config, screenshot, title }) => { + test.describe(title('avatar: states'), () => { + test('should not have visual regressions', async ({ page }) => { + await page.setContent( + ` +
+ AV + + + +
+ `, + config + ); + const container = page.locator('#container'); + + await expect(container).toHaveScreenshot(screenshot(`avatar-disabled`)); + }); + }); +}); diff --git a/core/src/components/avatar/test/states/avatar.e2e.ts-snapshots/avatar-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/avatar/test/states/avatar.e2e.ts-snapshots/avatar-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..a97cbaf4366308796a747f5e06d91740ea60ede3 GIT binary patch literal 2055 zcmV+i2>ADjP)Px+!AV3xRCt{2-CK|1$QcIUw+b6`FsE)0J3CovlxUP(WDo!U2jnu!9wMzYN)$!P z&Q55c8;rp=c2zFyZn_z8*+4Vd=6$YKOH*cUUVUG=4nk{<(zMn}sfYgtAcPP?XsxA` zLWuH15fK#-WwJ>r^$lvPh@nx1w{B6``LWYcp5 zQ-<+GM3ro^No)N~!E~PSiRdNSWYZbyn&+#_i0FmMv*~ON7qf|o%E+_nY>}aqVmuL% zkvwP7l;MgeA~Lc{7EQ_QpWpxeV}$0LufP3$&jJ7y{`cn}{&APc&f9Ok{>pvy#!oH& z^W#td4wTh@_uZerXlxC|x`~L4N^Z(c{LyU$0ISK^Px7xedc&>-s9-t|AD2REIiCg! zu)4$Eb}$hUl_5)YSn({6CLo%OW^w+%8yznTr^(}RQsI0Y0I>VszGz}wM?}SBnGP$Q zjqcWv?V;BO)_0G>sWbC$l`9R5-s`D8L>JJNu%E*+fJJ=EEvgcsH6Su)4#3tJNR6 z63KjYw+W}=zyXrQG%Kc5JfAET;0(O|(L_XKV62ToAFJ4( zE)t;0Xd9l&WcvQEe|i7tgJ3jXymq@50ITg?H$Q$@`QzCl8P5}-Ie67( zG!fAeiX$ZP$CDKR%V|2+eTk)#XcZ-^@lEhmx0i*}^`QA-wVHkWHC`q_^Xj_AXdm9r@C0 ztL@bQ`Fim6-Dno(!?ZlF0|2r!?6-^?VR(TZ5fu|!>m*6e{8Y>0d=wECk>{^sSTqrl zkv#wG53`AgjPh(cTV61jh^UM_DVR+}^nYcO6lb`4m$&;8QAwL@dhX6nO7ocqL`0b< zVf1|Qan30%B6E_QQ7RLSnmbV{jH9qBEEU@#F;RlC<`XZ_dl@l>nTILAap z2iSiI0id-$_!{19f{DRIM29KLrU#W;JT`Gebi7j8l+pJ-y(mRI@;twjh%QTo^8hqN zlwh5TR3TMRS^$tjTM|;juEDXHOhj}V=DJB~1QDVHNopR>lDc7wmIKL@BBCP{Ws}la zgnAW2qgv7k=w@BJ4atNeqQmT2-*jjyJsBFhS=XJ0WT!_&RqdW{T7+1}$0mw~$Oh>) zj~t1JF3xt{q=DN7;u2F!<<)B#xJMLVM09zcWK+ptiq@N{ajOQ$f@2%LV%PMQ%ifNN z%6gJbv!!0GivlDF;fMHi8K#uzG|_g7p47B;uf=pCqEa^56hzt&_hyIEnD|&1UdZCu zxN=cY9d#PI?J%B*$Sgl2MJf2<{=d?g%niYm^?DP(=noblQgz7gJR-U@c{cf>#{Svr z*$V4&v*U4s#r}-b5J7ank0hdtBLSoe1=CDgBPb}TK2cPE0jrp$6A_t70E@6FEW)_j zh*}|9@Bb8{ps*q$%18j~v}k@hs(vAP@mGo;M??fbrV2^n(M)Oe4y<@raH5c&=Dc4b zda>jx{tUA}pXH%M^cy0LC!bi=uWqWDzEbA66(YK1(%4cMDjOaFswyk8i!ik8Al{%TS+k#c@<`mmPho?0W z(b38to9lI8n`V*fHoQ)`r?RCZqC!&2QvH>bc-2L>1+nWLoRsLb;dRX2-2LCPsUxBy z)m8|&4Y&e}Pg+HByzI8ZIR)2`L~YtQs1& zId>ep=o~vDqCA`0j#$RK^CC{Lj1k5=?k~usDbh!q<7R-AXx7oJ@7wIcBg2WvEWgXZ z4|Nc2B13B|A_TjxR4K0};Wp5Ao`hc8(VYgfiHHpHY|PJwT#+ ze?63xc-3J$M?_|uJD^svo(5vNFyE(gfVzz<7gz1dJBNwrGHoWVn{|<-&k{@k;x*Q( zW-t*^Nl#v?z4g$jKg(m94&Gj~nIodIwqN+a`{MMAwrf)V@%4$vB%+sXGvD+;Y5eO3 zv*0uj=(g~OArBrPqH1|#{k+d}-_QNN6jQ7oACDLh2n6CY(8rjAKwK2| zyD4NJ`#F_`1$kabyRh( zySlo*+-W_`j^~141Se4NIuUY10V4ZEJbJDB+26ml* z6ZYk18WTDs6K5|)l3*bq@DG_nL+Ohw=P7ZdXU>XDx;PN}4(1ZYJlSv5A3p3yOgc9O z)Td8FOTx<@^kDF*k@Qc+DuJoaU>o*^2V&UkBc+iLnb0og{KoW`#ABdPuj4Q>rq7C* zXZhy(wQD*FR4VHS-EdZ^J92cWE~5D3NBJBRS^_QyJ9~J3YUh_xyAZ0PH=?Paj> z8^)Vytc9UE;%v{`wd(4?b!^qWT?JWLS>ocbZjOL1l!7~c`94Qr7%-j^R*}@yf1QhP zz^M1qa3fWbxHQ_}ctaq8SRrSU{twjL%HxxxQq=e7Yph1<4FaK|!4boN3dVCf^ly!| zv|ReaL~Ev`H_G;GnCyW@(_)a{cvbk#=5&K1EBAVFo`?581T0G3R!X}4&i-7OewOmQ zDi2$3HI$GN{jkNbInl5JQFqpD)>mO`TU&R3S6N;gLRVz11^>{g(XOIv#6R12WJdDg zQbUg-BLVI0!?KgwR|m^aPfu@&wIFW3?3~tqm0ut_H&89XRF+HhP~wTY@=gT9h!^7U z$eSJ#tDhLvU~4=^OioMdzott!pG+!RSz;M>_x14(9};PrV(@+tDzg|?;j@{A`7bpdUOPM7lA!>McdeklPtCyKw~2nKMY2;&wgiMdTOXld zDJA~S1izGsJc@%gu#_oNL)Xcq?U7@*N%C|V_+6Ecd(OT(fT~C7XdCIKxOdcp^cz}5 zK}U>%U+bjVM&}&YkMqDgU5FL(mQON#H0kdrZ2^5b_~UHFw;nM)(Tcr%CdpOhjoHvgadn+;+Htu{!QZ@EJ?Xrwmh7`{ z&xLl1M}s&G66ms&R;3v5#Ajdj|Lvf8^!i=DSE%I@!Q|DrpD@B9HVO#*4Co>fcI1SE zAO17qnV1rNF_NB2uSa@>t^>58CHc4mF^u7Z(@2~P$>kh>BdxNEi=xJ z99O_SZ0`x}knh(;uolcht>j;#w|(R*_>X1I<-~7J^rSm$BFxGK7bz|l?SK-Ws)v^8 zZ`=Je%AR$xY<9M0_8A#`-A-LCm007i0-nIC6PqhYVCT)2ZE)~ zG~)QqRl#9FMnYu_4!-1UVona;DKE|oTJ@*OyLOgiR(f#H(MI) zc|SI|Q$a8&oJ%|8qhhBt%>9#3p?qL#S@wm;ywd;l096{fb^IkszZ*LT+Q z>Jyd*eEEPx%x$`%CIB-DBeOhtj0?Tf<=PrnL#iK%FO<&&)HsZv_BumXnJnelb$m$q zRhQoeiiecTV-do02Pa-GcxFOv-(!z+OqT6t7CbY-NHCRuvSb-2mwnIv+;v%UFb}G5 zr)9(rNV6ljPV?`;K;X|MxQn7$6FHgE^9s{NN864z#()v?>~NSU zBy=#OT1|7X*;l6@P4abN-v6nmG1wd`bcJ*H=t*jv`d~`9)V;WkRJoydi=H>xL0@ZA zP7|wa&UkU5?)~doiwaZ_vOP+8yZrOXVli6cBQM52y@xqxCQEtQscYgxmgzda!VinZ z8_haog!Xh|86>1=2}wFa(=Tl@_@QCM@H4s=AP^r&=^Mpa-^Hi1Oogc(0Y5u}xl>oO zyUmrZE7=u?3tENG(9%4oj)f>CASMf6Tmj0h)?}+b5t1(wq)E$NeQcM%-M2?zg*-l`>>O+1M(tKRtb8O|`lyN<(+eHB0c9-ii( z8Va_)nSHlnDWWndym$7rb=x5z#vz(*>V*RCZm!cQZ>ri6xALd`+mWzCCIR^RJEnhU z{VrsMZQ#vonjdhTxZLD9+S+Em;;!b{ryd<8t&Ku3Th%qyDK%A60{npF(3BG#D|b!({}o)-~py~OEmDO7`kalurK zHI_?MR=l3ut4Cy_{8aTkKT~6)NSOVo*Iq4VfMM2rfiU3|tZZIo4nDKJa=tPnqjfe~ x+5d<8XR1zR9!V+U^dtB1Nl{?fB*1^T+~pq|zZsj^VBhIM2D(_x>wg{Z{||?vZ1eyC literal 0 HcmV?d00001 diff --git a/core/src/components/avatar/test/states/avatar.e2e.ts-snapshots/avatar-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/avatar/test/states/avatar.e2e.ts-snapshots/avatar-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..d88b84ce75e6685eb54ab90c59de04753942514a GIT binary patch literal 2197 zcmV;G2x|9Px#1am@3R0s$N2z&@+hyVZxKS@MERCt{2-C0xH$O4ApuUqnpfv`?8Q#mJ9XKr$p z|Nkp;lgi~(QkA6$81Mqf*3#!fNWn2+$u^cUyib)2uqC&+=}*0=+c@W(mXsukveSS4 z8e>fU(^|W15deSxvg767mwc`($aQm#6j0N}aHgP{(g zkg3zP0Kju&rJqkU*G1i~2LPTEE7kK3y^hxe0MCIh%`bqr$mh?Wb$NNI{r!Ete*HRs z-#Mp`A3rLJBE5R`O2@~?bLS2Q1AY4RNs{#T?OQdQ&3ntB#Q*@+uw^UYIF9xG`*)4U zV}1MfZRVK=V~m>3rpDv3PEJnlJ^%FdRO9hDKlU+%0|0DZ>0s#h`*|2dQKa+p^O?_& zj*cWr!{JcV>2&5Z=Uje#cHCnK1_0Q+(!ucK#}7%;%a<=_{?6>~?&d9li;Ihy&jy2m z!Z4IEMu&%o3(F!H0ATY<1w#-78Vm+%G#YyI=8b&c*X8A8O!8jXeJ5exvZd8M{iJ3T#>Bpn_e%JV!O930HNpUJw327`g3D3X*`^8J2)=5qi* z{S*!c=UhIZoV5tDpC>0LGk5pBUQgX_SI)V-J83i;X*!+C^E~bE@2~A200694;b0gH z2AWJJYPDM0-rknh*f1E{fIX*RjhvM zc^<(405xFT0}CZN=aeMLlkcLIjcpJL08kGP216!El6-0Hbz@8c0P1I9Fi7ifJh#0QfyDy?m*2M$_0TjGSU;6em)An-QLota0+K z)bNd34I^R!0DcdpS`2aGbTx6BMn+LmuB!K~Q`2|aY8iQ!m;ivfDijQH;xrDNg2-ys z@n>VCj-P0&O+WxZ9X%Qh>kNdsf^P$&*S2EDDge~SgH^I~PNTr-CR~3gq~9-HhgQSj z=^vs402Fs`iy=vzF0Q38-b`0d!*kl}N|vrA0I1wtFgWLQepN0MCZRNmq$p1R_C2ZP zORYxn3u$;x`&%re2LM%@3x><WaPU?@OFe+9^t0C?8(gEj1w%h5HRscXX^4s$`aT-qwRLD;14~vI_ zly)`sN5zgEPmJP(!Ds-eN*)ZOz!jQ(G`Nw%xO_p=NE!!=k8@5M2LuBERK-eC6uUy> z3eKhD3d1_1w7HY{;h?yuq5Gq1#V zl({wlz87ivmeugn1@!;`^|sXJX@}csMPF32k)Bf) z>#PF6=GkSE!CL8PCv82LN6xoa+TBV+VVP|f*zywqHqW=lNhQV^?&`JE*Cb3|(e0o> z<94H-=8A*Wt6UoZU^#xnGb=B3(Q1^NZB$glBNPB&)9k8KP@htruMGhHt*q}Ed8U3( z7<(|5WFi&w%?oUW1@#py$6v6JHD=L2chrpsqobNjoO-u;(Z4x`mz zd;tKe;7ih0%P5HS=nihvNV=Y;donC~A~}xJ*IfgpA<=23ug8|xFh=bA3;-1|&ZTiM zyh(IDoxP=ZP8v_rUE9{ZJcVb|8BJR)%{2_V%|tsL=Bfff74l$65~tI#=T}DOV<}w2Zf#{W8`6GH+Uq)X znuG!XRAr{WrqeVEBe(HTkfdoSg%P0u09Bjo$p7zQHX~@WI!&kl9BjN;0KhNig25Q0 zzyByVwRBaUmHz%CEgJw(t(h@~yOPA|pC8ij=4mVI?T*v`ju~tQfVy}v7&2eaolXW% zQ)$0^;q>PofdBw?vM?AV>1yKi?Lr!@sr9hkar$$Qhu8q1jtT`sCYU;Xxw!3KDmgxF zjI`5n`fE>WGxG=lYO7Q*+;w^F)E`MfC?(FMYbD)&lQrqfvre6+bkLJ_yMzG%{0{yP X_$R7Osa^Rg00000NkvXXu0mjfGQ1V+ literal 0 HcmV?d00001 diff --git a/core/src/components/avatar/test/states/index.html b/core/src/components/avatar/test/states/index.html new file mode 100644 index 0000000000..687985d9e5 --- /dev/null +++ b/core/src/components/avatar/test/states/index.html @@ -0,0 +1,34 @@ + + + + + Avatar - States + + + + + + + + + + + + + Avatar - States + + + + +

Disabled

+ AV + + + +
+
+ + diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index 9bc44081d0..44480e1b89 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -211,14 +211,14 @@ export declare interface IonApp extends Components.IonApp {} @ProxyCmp({ - inputs: ['mode', 'shape', 'size', 'theme'] + inputs: ['disabled', 'mode', 'shape', 'size', 'theme'] }) @Component({ selector: 'ion-avatar', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['mode', 'shape', 'size', 'theme'], + inputs: ['disabled', 'mode', 'shape', 'size', 'theme'], }) export class IonAvatar { protected el: HTMLIonAvatarElement; diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index a6c871c280..055393b5f0 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -295,14 +295,14 @@ export declare interface IonApp extends Components.IonApp {} @ProxyCmp({ defineCustomElementFn: defineIonAvatar, - inputs: ['mode', 'shape', 'size', 'theme'] + inputs: ['disabled', 'mode', 'shape', 'size', 'theme'] }) @Component({ selector: 'ion-avatar', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['mode', 'shape', 'size', 'theme'], + inputs: ['disabled', 'mode', 'shape', 'size', 'theme'], standalone: true }) export class IonAvatar { diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index 7aa2faa56b..fe28254a3f 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -112,7 +112,8 @@ export const IonAccordionGroup: StencilVueComponent = /*@__PURE__*/ defineContainer('ion-avatar', defineIonAvatar, [ 'size', - 'shape' + 'shape', + 'disabled' ]);