From 05f33c8e6c89f1a91e0b231c2d1fc42b16f16e4a Mon Sep 17 00:00:00 2001 From: Leo Lamprecht Date: Fri, 24 May 2024 13:44:02 +0200 Subject: [PATCH] Added `Image` component (#3) * Added `Image` component * Ensure correct types * Test JS client --- bun.lockb | Bin 82568 -> 82929 bytes package.json | 29 +--- src/components/image.tsx | 155 ++++++++++++++++++ .../rich-text.tsx} | 4 +- src/index.ts | 3 + tests/integration/index.test.ts | 2 +- 6 files changed, 167 insertions(+), 26 deletions(-) create mode 100644 src/components/image.tsx rename src/{components.tsx => components/rich-text.tsx} (99%) diff --git a/bun.lockb b/bun.lockb index b2ecc4597fc7641d95bb72e250d4eb028d6de075..becdacf2f809c508e4e895b08905a26d7cd60315 100755 GIT binary patch delta 11553 zcmeHNdt6mj_CM#sMGneKK{#F!L=A;R-~t!oalm|Jib`gR4(bg-Pyq$h>>*d1OwGyB zHD&lnv6?2!seaUyV~$$(G97btw5w5KY9_v4shG@n?Q@Rsvwrpa&F}O3{QhZwSZ6)< z+Iz3P*4lfYbJ+`?u%3U?`uo^kpLWT4XY$nbcKJ;G!Z-i4_xaN|JYu`8OUR0zqTT## zUBc_myJd;X!KI_Zt1lc7wDD*wNv?|WvXV0J{Qy497di{7KsN!}fggp|3UF!VEJ!42 zwzFbVC2(gCy?%Cl3OAPm4894N`^omy@lo(0;N8G&xx;3QBn1MWBUug^6zrvUKN^_L zsRj1sT43(xEok%bYk`A+mjU|$F97EH*}(q5CFGPts#BoKwZ+cL-bGWLlg1deIl$a& zIxvsj3%CRDSLmWW@cY1>fo+g;dmls39|kio8`!kg6P>ZO*C1zn4j4mMyN-gupX&<) zo92H4J^=E~z}z4SHnZM3JlKGxz--tVc$*EWta4V(s4T492fh>dg7WeyC54id3Qu$U z+0N3b?DZ-%Vt!i5#HxzI>OyHU1niem^vZsFu)ys!>tgQ6I>R`#6KoLK5)6rt8Yf3ad&= z3#HPM(vpJSMP;VPywMSds@X8`A~1XE_I{n1C{Lj9p~3jsz!dHV*Eh;^A!czZO9VW@wFd;xdn0ft@C(4~k43cL@tE^oovtdevuI16=!W+Au%f^+&gFj5(YgQ}_vJ~o z{to5O;QETz(3G!JO$SX*CO4kbNe$BEZIl-T6TN9gP^O$sZit>FHCU5(Q(my9cp(9J zAWw3(cPInE^%Da%1UZyN;E+qY50v-8ArvqgW?Ez)TG266PA7LqO@5fv5KTFUsKV7Q zN!S&Kc-=45xvSrTLw>kqYUt>IO=_qnhfyA$qo^iSQ~Z&SJVP&H?Lpx58A4lm(%@t< z`wtCGPtj8|xm8Wkklj2oCK2k8t0}LOru-GMY{-QF(lCA1rpfat56_RO#-=H4 zuwsYkcEQgh!107F!s}0gyGL-fJyNKqvnH$L?yM=*SSxY59Jam=j%}5AY-CYL&7r&jE)lx0DcGT$0mu1*sAB9CENOPVm7G0NyusWGxOEG-L|Pr1 zri5byW4R(+kq1ui0B*886C|>54}|QCHtZj34;+TWsXi!8nE{H!=Od!?7C5%ri)uq1 z$^~#dBSgo~p+sU4akpM#kP3qn{mFkOHBwVNv8-4_EDmKTIJR70D$B?Xjn5%Vh77Z5 z@34Add8SZ($227q6kDf{I|p1}p~bnomDKK<(qw3e-mGa@$mtXonWoGF#p_8Qa~C*v z1Uw1fwdvG)wJZTQRIlTe2aYY#XVU>2M#`;T27_a3timTx{inJQj5@4Q^y1O^)_9Q) zYX-Q!!m4Ue>n(_o>cZ$AvnZ5}w4O}~pv2qF=k0WLwccHv!O`bqCcs@(+XifPfLXv)idaNIK zIfwFMH0uoPzGEprCe8Xes60XS>Tb$@3u=Ur9RQUtsKg$4;SkhnP)f#X$& zsCRZKhrw}P>lqY^O=h@g4bLgX;4(!W);f+dHAPcSK&JQKHPK=1o+L>{v^qIWSp>>h z5tzdcaJnn>E2oZ&X^$%*zJ^;rdgF#-sr-5rh9{6cOmph_r>7Y487q;;MiRrV(SV= z1v?!aTv-T?HE}o~W!8|Ip(*u-296d?sT!$sKxj<+%dIniNhqfw{qOl1f*b%cpK zPISnNs3u#Jcab|=v-o6Dr@on11*c#hh4oFdJ_u^0pxyyBOi*5M^I$>cg31-tI#4-+ zItxnZSu*dYPW|qxZU++?xU|MHcpS_UV?O{F#yH}n2tXSEmcA&hu?Hm40B#o}YJV12 z3zgQ2+yT;2VvKbpI>Oo!7;8e5{|&aF2e?=Cmn@CdJ46sf`6=v$8svw>4G}L0*J|YdHW9T0JC$UK`>K2&GH?Q&QO{BMnW)c)gOw8~jgU?%B!pls_P(np;m~4uvt> zH5tH;Dg|(T8Gs98mQQB}<>xS7Or@Cs9_=9j_cPnT4+FFOQAxTFNDxBdF~LdF&tY!J z@nt;M&~J?u${83Q&%-S?YX3VJZXFmx?+pwU4q0Zj{5jknIxiY}jM<()0$BbofXlyu z_0;`08vf#d?2?T_OUE4PEdb7}odB-iCDFlQKG->#)XV<}lkcE#;lbTTYsQ{{y$1hN znClM!*n@6Ek1@;Z0i1G&0c>CcfJWAyIx}sM~KZUv1s{rn;8Nj3SgBOAon0YTD)G?E747s- zA43kT=S3@la92A+p*3caFCN^1KQQwFcz6ScGV^a?eSFcFH*^Lrtugn_$;6m9buNr~ zMMMDDGf@C8tue>p7dLlv{MMT~cf>q*5NqK7tIfTzWp~F&C_lBSn{r-Szi#foZtk29 ztq%b`iGSVP;gw%E_f~uP|M%uzcQE&<{zJMPKM->4NWyk`miLghUnZ5TT$cL$yvocbNYBaC_hYl2^ZgFC|YJ{!(W-mr7qsvu1Y1@mMFX?%+ugtJiX+uldXP zEBMN4i>k2}(dNC^ysdH;eq}~4AJi@0P|^!6;(b!S;MI-aTMkn5{tOP`&1Su@bQ=JU z5T|Zyo=;`t^FVsv(BboIJ}aSo0N{4KJ|+UVYzMH6UnM31xO@m;86Pzz0Fgo;f#3o7 zq%}V#J^{_0@$nx4;KHwatfv6F1GxMhz#a1E1HX-N;kP%I!MnHK$v+3pZTaM%1K_^@ z!Q-Sdm`=rU2N|e*##~AJ8AbhVBQjEC4VVz%n*~Wr2V@0c2SnGR7_yUwIu} zc1OwMDC}Uk984Jp!&AFJpPk1qZhnAkpsxd(05m8tSsrFEZYEM{r863Ygp9fq3aL71BoJWfRzXLoCSOjX67JnIeM+~TNK16+VIz)Zj_gC5R5-b8~jf%A#;hV$hS zKm*_~0H3$wyCi_EL78?0DRPIe2gp4DcGPEef%P^|br;B;0FEg8o7Wg)H)S`3`>Z_z z5+$IS${OyV=NgLDK45)dp7~EIZ#gbDE;c^VLCpi#!mL07&*x;}O+n{^!1Fby`$UduEtu zi9s+jg@2YqcFd4lDE6hMKt8gj5WgPNm--oPN}fNxyV7$9B%5|F7}&l2`w^v%}vyqA422BCVRq_n0aNP}DKI+?mE6Q*Gw| z|4&UEGqOH?Yd`3wKo`Rtr6tg9qVdN&cEPO8`yXTERfh(AzXtvqm&8uzp0r)wJOgboJD%ScH1~wvX5LD9apa5ZYd#xO zfck{kRJP_E?LMJ)Hm|Y7ELkaShHs^`P0 zPxZ(_eWLD@X4=-MTD*hk%f?WgCzl)TcsfsW4nBfs0Og7j%UVZ6#{;RWG$Cva>W(grF$r`pV`6=|Ep zYoE3c)leUgoPcGAsP>#HpQladREuW>J$gM<4xzIUMUZmdF5gKR=T$k0;+sM|;^VSu z`gyx8CqlpA@IYZv*_dc=Uv!qN@7UvL3$&+^8(Ms9@(Hk}hwILmCkh`7k{6*J@ARTK z%DfPybnmL)g_%cVFWBWJwB&-CWZt)VyZO`wpH4e^c*=>f@$i)-eFW`rw9j2}=1kVw zJvUowj#2#u)#eeY*Vpy%%#S2W)XY&Hnf{K+<^$Q01wrF`pQ{1J{&f9Qo^L8}6+If1zsKF0#tn{tZh|gYn?21m)$N#15r2(=T zfiTLwB2KJc)O<-zGVd6@lKRZF`_v0-JmvV(qfmSJPyb_~$VG(j#nMoc?ZN87m(6CkeLV9}|2gpNE!)a{ZliD&p>d>j`dwDZ( z#3m$SkAIEIzgOi%TKK&k*FrXf)+M9qJm>|~_NpqUP>-wjeU%Ec?hUKLu3nH3;mma*PQvNm7 zX5O6|(bzHhf!QUOF&iA7yg@CbrPul^%Mkn!%({`jzJ}uq953$2-zeN2ojPoz;aNyM zQYh$pg3Y{|wX$YMr|G5dwiOB(k>5p}*Nv?v`gSADt5qZCJN+jArS5T~TNoh%MmMf^ zvOSWfyZ_UZSH8ZmxOShI2-_)f0Asp6iF!8l$HB)U5XFzGn{sdmW*44Y$#z4<;gEEL z>Bn!_ZRT~YM@~)JwV-`tB=?V)pyg)T1KsV^ctf@Q_|3rVy+4KjpeBvY(wE7eR4PuM#zEpZ~?>&xU92UfWW$h)RD@Z7-l6Uj?pX*X&b{cJr{b)NiEPA5{Dv zvFQi3`zdJQcLUeRC0X+h`tm*R%1QHL+GxX`?p^-T&?o&S zJa|4+mbmO*m=oi!+a>D8BS4ZoCB;RqBJkaSrp)K(=9Qsd3p4|th0zM=#L|Z$k))}) zC1XoLTlnbhQxj5oxa4Q>wV8sN!@Dbp*fd;U`I*TMV2R%%(9MLzxPgkD}%H})| z8py4ntfmsZdHUx-!$9YP27%53<@RFGV9=42D@VBF(3M-q<(76XD9jyukI{MuC~Hjw z<+)=(LqT^!MGMe%pshfgL(bzRqn{N9Gk*@IF*U8d06AM*W6;B(nA+{BLIwXkUmMgk ze?9mR$XA2%z*yMK{a(O>4R{=s4Lb;LvmvErxn)yI^Gi2_j{=`pTs*;*FG*eCX&yf{ zcVZ!XeLMy+Kgl(wtR&x^FO7nL{gMZ*?6=V+xq11BR~2}cx52V`B_*KTUg#<b zCb}lN@;Vn3nI3C^VH~PD!$2=6d+K_4k zM@eyE;gm^|lzodNMWat5D2J`3!Cz`8NhtT{*qm99|WHBD-e_y zA8xe!+^#Rg8>`bDpa_)b15h^nWl&z&-weJGG*WU)p24W_c1{-p$zuoQeHjDF-V6n0 z12%Qi>A-kB@h@Omh~(A0tbv>jUWH3Dym9iz!#;Xz9G*s#w{}@d=fMQ{U*~? zzZiFLcYX94P|m)9bbS_A(YX8)S6Qj6sG!g_c3hb++5^x&E=})`EE0atD=jUXIw@ar zXXvvdn8Ml~n%?hGgJP_(enk&G&YXeL3SgAI|smug71e^~mPPTrD(ZH)Kd3?vEZ}IN=ZqPZL@dlj;wKRr%7YV5jmrxSnD#I?AW0B2<%a zCoi63$sVT3YsnP`3!6}7ScdE*FGMrR-cpl4BUejJx#X|U<4coaWp@O>mzbwE%&B<5 zrHYAo1?#{e0I(TWTFxm{8J;00k~dtFCy_luQw}4u;9!p=?CO9h4HDg1)iiL(29Hd& z;ZE3OkJRK)a^aap6_J`^#eU)i`Vsfm!09VQZ)K*zVJyt}(CCS{KvNxgRZU4oF7wP- zM5I%mLar!Hc^9%w$b|dlTJlC|Siaq+$>rq2^JA*8Y072f>D{_raC2YmR$j10IDIy_ zyL1j2u!$;KX>u@mTWQJ^?2UNQvI^EN2glY*JiE0PTneoWPm?>6H(FB)TbOyY%H~ww z0GEWmK9tbXsT>5y3DZQRK^)E)cD_g-%k)q>)*{3DK7>iMGBQnRiKB{LqzE?*0H+Uy z8!Yp}L;`*RA^V{T`^IX+YwsR95tgP*K#fD!R79o{9NX(ht0JAsQE+X#-G0)HZYm%$mErgf@+ z6y9;7iOt;sj*Zb5rr@~XKcQ#LG3RJ?)pT%Ez_FS7 zj$H+gos7`7bXp_vK1!sOY2E#~08qF9;Smt1yDnGP9V ze!Kja#< z!)Rh`n)P$k9uT#d_NHtaYWE4*R@8=zTBlp_ks)e?TCS*_MGdW8y8CvJq_IM@4z+wy zi|!~%BSme-)!H7^bakn>nXS*GrW(Jy~WKYuM=HyD!lw9l|J}C49ssbDb725=1u_fpkgMedu4F<;{ zwh$i%&x5-|jN^m63LL)^{6v7V$)2JqZy}U(r zsq6yBd99~V5KfwV#AtX<838Usv|-!hg=SCHlzout`V*3z)~IAjDxj4qX^ICmV@F^O z8^CeaV~#FP*_T{hHKhkO6Wqiyo6}kZt~X7@QS%;Ze4tu6T8iXEJirChD%f9172Px? z5(XAPrh97^IAewFIJ&MhO}PRY3^TmcqpR*S#2-oj2spMyAN>(H_L+}3%37j^PJ+9VO7%+4Q^G2qsEcIRz;+rH8@xu?Nqjc<4EXh2tbwX z1P3ebG`JT0GEo+Q<5LTVK$6q)c?ONo%uoVwlp`V-03l8WZzk_?dr!@>Nu%*SGpt`? z8Q4ob)2war1{^GE51}?d)ILM4zo>;E6$gr1E^1k#_BLvwpXG8-8sDp%n?JS~T&|@u z_+|i8yply>+5(6HngAUDF4s~Y@YvB}TqkZs`5o%Mp#v+x_LZ0-LP8O?j-c2VqWlqR zfd*us(3gxq8$^M>f0O#51=%9;KtxuQYbnbSGEtbam#{^|0+4>9{3i89vrL!ojG*?t zL#TW2cz07sM(Cq%N_o0bMmtjuP_98o8#E7;3sW}V1+YI00q#E$;KG#UlbAsP)g5w; zz-*B4#VSn!*no!tR#a}#sh}*M4sc=0?K7A``8~?xW&un+{3jCGsw>uG?^n9u!aZ=VRN?$buBa3M=jm~u)lV+MsO8?qc=`C5R>{~^^q z{6`e>lGX{cbjl%K4{+vf0l57ufXj^(?~t1}{EnP1-xWa*_6>1!zJ9BByeZ}OT>$&= z2csWTmj4KFRDJ^3#Ge5!KZ~bMS-u}&dccrhL!p-T_KoSw>JI^|?kK>gQH{Z$2IX=s zWp!r&re^^zf>Kt$7!i;{$gia= z4>07QdP1;(WzF#54nYPV4C)U)f*bz;<*|IgGq^NT%+I=;0ZciIxG?1{5CgDBu07?! z3ZDIM2YB4?obqVBc{`8q%^NaoE?KJhp9Qj+?j^_@tFV2y7(ri4xquqG!qJ)=n~ZAQ7vm#zNhxF3HA-vV&i3a~={bAdm&xO@$;j9=~M7xy;Qc`U!(X9BEyJ0Lzg z_!VCQu%+Ju+%X6k53r>>0ah3c^a9w>?*NvC0Q~@#u>mYYETve0jb~r8j6Wf61K4=} z5@cB@u$y1|Z2fM4Ck_M30WNz0mbC=tY|ElUK52CuNj&dMH z?}?GaDSb~&S9U3Xtpx(-QLhIs02cu-a11yOR0Agf4$n8hdU|$G7v%tIYk@at>z+HB z??7!G@By#@@KD$kUo&uf#IC&-klYvrT6oBo-|KZO6qs9LtBJ~08 z0^)%rpaakm@P{|}Bl$GyKLGOq{2k_&icl#B3V~c80pPFaGoT#X(E!J4B=C2Dv$X_p z0pkHqz&n9{Kz|?>xEG}+JFn9{%lF+tl=dw~_8oV-la4s_t5 zwB&$`+WnFwccGkLvgB^G{1-+ITPvKY=wrq{_tp5C-RK@=Q(jfwWc@p@a8!-_Q&FQ_&yRRM9H*ucg^X!dqjV z=IxE4@=JSqpIZVCjYvjEw6Mx+I-2(e2JfhDKdm_A0d#cg8nY?u2wU*fF}=GgKTnH~ zs5bLv$M*Xxzv+8+$svn8i^GE4$);@>5l7x54tX6#A60GU{fwPck_u*Sc;^JP$HzI? zUUu>C`Yn--)jcM>+v+#G3ti^|=;YBz*-B|%Reqe>;wkC_%-b2+eI7d-(K_f}F?~Fn zYTn~m*k$Nb9o|pxYLP3EY6?PO-t;JVY~e9m-N#cf1_raqU(+(L!)D$Pd42Hfm5a6v z%|m-)TvrbAVXDQepxIMn>ddJu z`BTcQb=b`N20qI};izqj-L&LZ&+V~u@<)fs9*pDDoOMOes54ytV}?uiyoYV(%UzbALxQ|<1@=Z7;ckzf6<(L!vU zPPF2@1J^u$LS0C1cH%g&NfE(W)VZGDqJ8Qec;im5S6kiq#^o0-e^*^drg!TjdE;)b z@6^<^9c)8=q}a6)6n7!)x(1thgUPX`?X&M%mS40Wl8HFo`S)t~1=VKWe7f&=c*>Be zt~#v7u+2@YFWjljMbIO#>ir~Nr<3T3?E?O-|EYz3JGmKb?MZ=}uuZ`%BVK6s+@> zndrlY-56@@0a1Ku>7^{(V5z}#4W(aJ@ea$moF%(y`DI6P4^7`H)2qjBo!jDgJDwvU zt}8~~_`NU-9!p4!OMR3A8&unk?Ts^LHmGtG4MVTQ9=d(OXP$p9CS%*HoWf2K$i6hc zK}{Zw_HfvLsNON*;4MCu#`bcwM50COT;V^ys&!3c%L3ZlptfIu_Gq*Zp5Nn { + const imageElement = useRef(null); + const renderTime = useRef(Date.now()); + + const isMediaObject = typeof input === "object" && input !== null; + const width = defaultSize || defaultWidth; + const height = defaultSize || defaultHeight; + + if (!height && !width) + throw new Error( + "Either `width`, `height`, or `size` must be defined for `Image`.", + ); + + // Validate given `quality` property. + if (quality && (quality < 0 || quality > 100)) + throw new Error( + "The given `quality` was not in the range between 0 and 100.", + ); + + const optimizationParams = new URLSearchParams({ + ...(width ? { w: width.toString() } : {}), + ...(height ? { h: height.toString() } : {}), + q: quality ? quality.toString() : "100", + }); + + const responsiveOptimizationParams = new URLSearchParams({ + ...(width ? { h: (width * 2).toString() } : {}), + ...(height ? { h: (height * 2).toString() } : {}), + q: quality ? quality.toString() : "100", + }); + + const source = isMediaObject ? `${input.src}?${optimizationParams}` : input; + + const responsiveSource = isMediaObject + ? `${input.src}?${optimizationParams} 1x, ` + + `${input.src}?${responsiveOptimizationParams} 2x` + : input; + + const placeholder = + input && typeof input !== "string" ? input.placeholder?.base64 : null; + + const onLoad = useCallback(() => { + const duration = Date.now() - renderTime.current; + const threshold = 150; + + // Fade in and gradually reduce blur of the real image if loading takes + // longer than the specified threshold. + if (duration > threshold) { + imageElement.current?.animate( + [ + { filter: "blur(4px)", opacity: 0 }, + { filter: "blur(0px)", opacity: 1 }, + ], + { + duration: 200, + }, + ); + } + }, []); + + return ( +
+ {/* Blurred preview being displayed until the actual image is loaded. */} + {placeholder && ( + {alt} + )} + + {/* The optimized image, responsive to the specified size. */} + {alt} +
+ ); +}; + +export default Image; diff --git a/src/components.tsx b/src/components/rich-text.tsx similarity index 99% rename from src/components.tsx rename to src/components/rich-text.tsx index b4f8d14..53ba210 100644 --- a/src/components.tsx +++ b/src/components/rich-text.tsx @@ -57,7 +57,7 @@ export type RichTextContent = )[]; }; -export const RichText = ({ +const RichText = ({ data, components, }: { @@ -177,3 +177,5 @@ export const RichText = ({ ); }); }; + +export default RichText; diff --git a/src/index.ts b/src/index.ts index 7ee6eae..b18b94c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,5 @@ export * from "ronin"; export { default } from "ronin"; + +export { default as RichText } from "./components/rich-text"; +export { default as Image } from "./components/image"; diff --git a/tests/integration/index.test.ts b/tests/integration/index.test.ts index e2a54f9..3077527 100644 --- a/tests/integration/index.test.ts +++ b/tests/integration/index.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, mock, test } from "bun:test"; -import createSyntaxFactory from "@/index"; +import createSyntaxFactory from "ronin"; let mockResolvedRequestText: string | undefined = undefined;