From 7bf263875b19dbbe5bee8ed25f374393b86d6758 Mon Sep 17 00:00:00 2001 From: "Irsyad A. Panjaitan" Date: Fri, 14 Feb 2025 18:51:58 +0700 Subject: [PATCH 1/4] bump --- bun.lockb | Bin 309364 -> 309676 bytes package.json | 12 ++++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bun.lockb b/bun.lockb index e8d3fca177d8c7ab0d5fd89da9e65628574e8317..b7086f5162123151dafd88fa886d93d88bf127c4 100755 GIT binary patch delta 13292 zcmb803tWxa*TtKh+kJy<9yy12-`FVpG{nYYY1H#Tn>-~)f7NM$t>k$RcF!p? zON^Z!K4V5y`1IewEjTXeo2?wz96EAl+~jz$&^b{tanQCI9LIx?hc<)W58W6#G%kMX z#K}=JOktbD-V9$8=-8OJ8585;IL&sBvxIE~P3=By<2YmJH_-5Fn{-1eBt6gMxMrvb zkDC}eB{VGjE^JfSC40-v6J?=yc1Y%V=qAAJ4@q&}&XdMF0oxk9F*J2k15WxWG<$BLQjgrG>{3ou6@LmmwN-4B@VN?V0qxwNy=Bc$n;G65XFqgLm%P-y8#^3y7OcYUh2(MOqAnV@Qn2*w zC?s_=6p}+$3Rd&&1@H0xf|ZlKP&6Js(1xLs_SWfSoi5U8!wI^2ole&2BAqszsH@lM zWSuV3X~VFKG|pbb@ZR41qAeWfg2)v@fRmQL1#2)YnUEHz<%i)JFj8;HvV`JYo_sl2 zcZmgP`LA)e?+HsOs6Di*7+C!Uzm;x$AxeEkFLj8PH^8l(#^8hiKdsDD@X7I%trb#o zJmp=t3ukgX_^|C9=P!0c!|sFCUviJv%H9hpe|XAVgkt=gDDb;IRmv=m8!nVMx$&VW zNx`F?EL|wx?J2t{@O#kl4q@OP58fb~C41ade>Idm zc5+;V$TCn07yS13QtrZ2PehYZ3K2_Z8cLmh*Ry1lg2md04W&LgdbRX zI4($Jzc-W&cI(-Almf-tT$DzLC6hfIH%u&rprjR25BE}{&{r@$Bo_J$CAn^@)_Xb5 zTP#JQ)K@I+Y+!Fu@)cR{eY8&0VlhgR-x-vowoUd+e!Y|^NN5{MgT)4Kb(LBatVy1(KiNouKkA7U_QAhy zLJIy}Dij~}RNcm7ew5&s;U?D}76ul0$QB7H1)i$P!yMO3C^_n;YIlU=rjvsz2PGFF zHN;KD@;NR{WL=JO+&Hn6f)au|>c(F{iT1QgD8&`Q;|;(WmO`{VVL6KXDZpK;ii0&? z?C}Lkqp2U6kC5`Grz+(b$MvD!WOV{x=*iE+`@vAOlL_j1TK+t&fnuKl@mk&r?+Rpb zg4$WDnhmSJkc!QH3Z?F%S6Vl%ss>hX8i98yls3JwkTzV)$HJnEMJ@zP*YXEo`HB{< zJGuOXur$O&)>|k(>B%oSA$2Dg(q?K^d9X%`6E{1_aZ|;P6Hw}d#ujw3@CC3a4HkkL zX{d#zOGB)d?}i7aR2Q&P%SXW)4^Li58?WWto|XbK6zI}l3Trs(FmVqp-=>J;++Z09 zrDL@GSXd6QOvRhcdRP--8Hw$_D#pUVQi;|Aoz+OR?!t;~s0%nFjUd(ibykW+YUgy0 z<0dqCZh_SsmYjM~J%i;U_)T}?+m}d-Dsp}lESei4M*OQ_eU9TLtgwduhM$*umqtGD zAC}bxshyOC53p#Fq^SB_l-fykM_~0rol+?E*UFof3TH|@_<&Mrjin$`U{OeNp>(#E zFN8G!ma!P1)g{Rj$)Dn42!r}E6tHFZL$IWcMcYhyxgk$j-5IcG%@nj|s&}x43Z{K} z2|gD+`J5~IEk&F49W2TkJaKP!`b(-a5Yz!$)%UR61k-_T`~j54zze&pK+CJI;<|+; zg+BroMI~8jutvd>a#00q9xN#r;nz5BQbXMZSd?w4uE%w0cS@_Y6c+V~i^fT-It9x^ z@QZijm3T*^Xt5^PZ=+#(z)}mPxK!4{qNs3TV-Mfd)it7r65rwm-n&pIW=`b|tC!%n z+>KwT<5(TE*#oN&ybOeZSuTb!++koQs;OQB58+U(MFf*>S1+-)ksJitmU;g zr4aFq#Ud5L@<$z#gI+r0jm}%LJhZauLh&_ES+2le_v8a_Nr{(|xDFOgOzNW?mc3+Q ziHvW5=@|g4r&ve#hh?zn{(xSPo@=luS!B5w-f4K)Vq57yf5WT z>g88hJy53>)Vs9&b67Mwa)}#}%LD!9EJZGVfJOI6Y!USN2P}#VqvO(j0*kVu5^$4J zwRk9A?(S~IbX0*amk3)go9Egw$?sd?iXWSLDb`%Qt_5w*^>IY^kuQjda14z@i<74UKzA)2Gt= zo>JV46Jd>LXm=1+H(1E+1}$&c zn&bQ$`dbW3mv`)xI#|-)2=LVManCufUxVjCSl>2Sb+Eh|tkf6MZjg50HCWPY=pt_Q zQg6{tiTDpIr@^A@;2&6@cc$~JFK_ZYU{SuL8%-@Nnh~C_;=60{V^4nh>xOFzS3nUg zT1WUhYx#OuVX&l~FyRfy`M?tQwJdut|D?`r?}ysR=DB0lvLNq7$4NHEWADX9_BZLj z^Q7JTF&paI{}{jK$K;bc+c%rvSvVpbKF8WU&v=~DcSZS;$oF&imAIH0KTk2}IQ2L8 zW!|C7WE>~s>doq*^SRG|%8cl@)hCR8>bdY>b;!>H4V%^rtL_EN+fg5PXs3-lwDEH7 z)564uLp(Lr;k`OM3!Qy?fZfrg# zn;IFN`Z{FVA1%IX^1$W9-GoOOpJq?aPus9RU|r?I8CQQd=vjLqGUU5ozf+bE3t*_9a5Pfv_@{I5 zo<`#yRaOkkxbosp%W-uDuR42+376A{N$&LLd~-_gD}Su=-gb3L(vMN)Ezc`czc=f4 z=wfuhuGBN_AM{T?@Abe?HSithuh|{3;(Gdl53546f3iJ&Hpt+r+qS|Uwu^pT$?_?@ z_bDxEuQq?(D?j<8qsfL**Zi9A$u7@oIpuPPb78-(7!dI3XQyb>I!#K)-G6y`eLM4= z?UYD!pSdY{3+_wZ3P znZNnbdzXt#>~-PM;+8Z7$8pL&<(BjEl(L6O#&Lg~zO=D!Pw=@RTi@Nduta$*c>nYH zNeTT{cTk7#)>PU)`IvoZ@H?y6g6*Z|lT5xn@MEQ>|J#1^SqX)A!uj>D+X~&I3(MxN z%u2YhJR{d|)A}nJr=JCe#qIhgWX+%v{oi#l}zb^HK)IMaZv=mao~Po-iYB z)2E?Zbc?G*XcxW=@2~oI^=<_RJ}hR;d5WveqO{kqHz>Q*eQWbgc*c)27fy`XoxCZm zJX>>ebkIfZhOM`5I$a3x^HeO~7a4diFl=+$a+WY2;YG~Md^sY0ePrgFXW`Qh)O|J3 zbMaQ+fsy`&e>Q5fWL6Oh68v$X9U-&3z&pE!P?NVL# zC_KEOHvB2~){Ors+&M9{i`A*Ot9GuRv@N=F@Q^nZWAc2OeD9iVx;*35$IU0^8*Ajl zehV1>V5IHAhaG;I{apiYq?Jn&Umy!DJZg@;TCN=2yQcEQ=*aFX9TsNYYg#k(+7;i%v1Vejq#JiF^Eo98 zWHqN`rfk_!kfGO)# z0&tx`Y6*ZDD+7?3Gl%m~YL)`UTrQxx`aG&FnCk^_OO{5eDXSvYjCoyzYR=M0S+Q57 zG|Z8DEJaOvSnG9z*{oeWvEsxh*WE~n^YUdUx8}NLP@n_d8FDi z^Kj%{sw?Ys9miHrIT`DuSmHvpPNuU*(OqMtQLx8R?rkH zFhzHkRRJ*S7J%t308bWl3&8L;Kt2I4#@_}wNFe$)z_%=qK;#_&+dBaLSkxT=tGfUt z1O_n8U4SA233mZ}STTV)_W-)y0~o~O?*VkY4^TnCk9E2aaGgNveSjgXj6m`O0QUy~ z0W9SKfXhRGY68QU>qCIY1lBzS7{RIttbGLF_Xr@6r9Z+y-jx6!2?Q~pN`M*yyD9<3 zuv!9{j{$-o0|c|I#{i?M08Fa@LRe50fZ-E>d;+12e*$ojK=c!Ui7bymM4NLGk_8T5lr(8pol=iGk_>oOkhqmK-X%3$t=DapyP9Z3Ifrr({q691X7;^#IP~~ z$u9uhUjR&FDK7wAUIJ7Th-0oV0Ui@r_YzfY~hl6@d3^fR6;?na^v0 z8Unjs1ANbF31q$j2z~>Qz_Q)|jCu=T`W7IO1-%6@dcT0+~EOFb|N=vM^OP$_T*J2%vxk837m?1LPAp z&Uj;hg9M_D0SZ|jfk+boTN8kjEXoAH$`qi4z-gv21t=nrU|PstBxY z0^rvK;2KMB0^n@{@R2|n^RWP^A+XB=pq$kb$g~6qwgjkPS(X5!ngWeENs5Fe3>fY1b%&Dp*Hdr`uDw>iRWwzW_b|<&MVuTdB&?oc-`d-qvA1 z%s(K%L4JlzgiL^hKq%#M$OpV()j{YpM@??&8~Fyy#P{M`m-vG^x0}lF`v~n0;ZUZJ z(Q_f6Acn9FAdMg@NDH_qp%oBol*!gW$j1a?4B;V05UQj0{UGBYeIf30{36*4g~pI} z5bDT`$#{jEysez|R=37V~h!?~ILaaCBIzpqe-iLHBgof&gy0OrM zAY&kUpV26fgbaoZfn0-M5cDXBFQh+&+W0^ab|UAE!T`uX2sQG9(7-eZbxMo|3)I;Z zEDfOVe8d-g7|Q;Tp^yN`aGfTm50C=V`+3S^q|WrS8V|=%ouolefkZ;WAa9XqeDlRc zpd1eQPRBm!{`7n@_#{XagxE^R3dnNEY!+awut=PWBDqCFVj$BYv5;wyRLD<|WXO+@ zM92@2cnEbn2SVZKhodm&>TC*;hOL24fbNUA&V&9QvJ66f@iHWRDGJnik_5PHI8tKf z>ulru51f^VP2Yb*!l4UmnHEXWqf?~u)qO^{s> z;#(n9NBqCDonYCJ9T4)(gmlEv+o88XI-r~Zjk-jRoXLqQNs}Wn8h~sXWDjIFgz~T# zVgjFi(72Iu&!Ed7#gKdm`5u8BfEuh58*xkkoi~dx6 z0(k(bggk`k=kf^UD#&99C6|0@1CgfKBh3}Cu#$zj9%c#)g@J5b4?BB1hwY=9D8}G3 z)3hW-v)q_y#U>d(B&lN*j@J0*^uPe=w8rxh_nVrpH7k_M#yO&qGaDbH=uQrtJ-*{@ ziHw}D`fJys&F@l`GRa|Aj6x$iM8lyq9MbZHBPDSI4D=3n;ee6q^nT~z*9Ly+rXw}S zz27v^`*n*|Xq;#yHNLHF2Zt(Om4CHt(lMO_Y8-Vn#cm1X?Po3-rf-^nrWk_7DtgGA zbI-;q9)2bNW-HsXM$s9c&??p_+-&q`;;h>$HUw;Re257;*f}`aIpZ`Pb7Tr%*?2ae zDSBAz57SxB`N6vV#yh{sWj{C|6ZTj)c7Z80&brfbNj+9goO^uT#ss_f5M@7n9h-PK(XFowZ!6=q}f6XG7O2lC1w{AICDKK1x{iS|n6|;x5mQ)2?zD)m+;i3@UEw!Ge^f7K?FF+Rl6{%EY*dzXE=GTxQ=Omv6QjL?HE4Pj1r(;z8fxg8)9)q^LP5vl3*xNaAci5|RMHjqS z*#4qu)d+8M>dlI#DtpZBDGSKZ6>HeEjS7vt@gerpMunAi`$N(ZDciJxQzvc|X3J%r zv4->~Hn(gfX7vOs+lXcRqQ@_5pE9paSX&V_x9!nz7BAt*uq3zRODaB3VJ3UEO`&0# zn-t^u$|F)T#bz436(YpuE$un|5x)WVru6H4JU8!iYUKHB{APufT%FGbZ-s81&wk#F zov%L}`1J8FzYObE=!+J#4%iKS*xk*DERfZan#G!KQTXAF5?iFH3RebK#}c+68Ky_s z+AUalUB`Iaez*nG6{BsA3-vZ@gRphgzPgDjdPr%-$01!@i08>xY*Hx^4fXf{5QFB{ z_l}8gLP_zj_FpgeJn{p=w72VF*WHO5$TnmkzED<;6xcd)6aJLmgzwgu{NB;gbS*p` z?7G`I-~mrA|K63OE>tO-O_~2TMa%zdcxr=<(l8Bc{ZYz)S-uD-=@=kRng;if-`{+J zE>m5;_2(v^+_H9Yxs!AqHQ3X%63z3Qgj=H`HYZcjUFTt=KbCo=>pzL7mrW@_w@y;GaJ-YLutK}qOsrT2>zal1=+7+< z|MmUfcQlS+pZy=OP&mjNpJns1u>18VHLJU?IqtY*{D9B#_GdY8bkZN~9Q`8pMd>To zn9n`w4}b2PJu1RMbl+!h=}C$g zpZiND~Df#gH}pHHj`!Jrl>zQdZJutyfZjR3kL`3u6U6RL53-- zlq?(l$D5pKKIiR%&gXm{N?IO$-gPS@+safsaos#+!*(LaHEani8~x!`>#K9^1LGaP zQPLC4!Oju)(M#;*PFx25%zYQ;Cq}1XvAYzJ_^zOA7v}o!QyIm)en*=tYz&mO{#ubYi>dW#d2STH#l7iwbsUKQ^?~ z+FE}E_F0bmCd>UJI*N|naj(Z&WU*hVL;3%+nX~?6Y#Y;8n`;~4eO3w^cNqQI**!Up zgewmHm+5MdbDLYN{y;-$a=TlsdoGsQoh9ZX5&A>AYrZN9pV?#>y`1PC$-1fh`;ekT z58ag6;Dd@5PXF%wFB$nC2~TFFl>P_oDJk8`4`;3N6cN@z_$1y0DeU{!Y=3rf0o{om zTjpZUH0F0&Qi*y_+{KdE%WQW(RzZKlcxtU{jo+l9)@b2q=WK_K z^?`lLSIpNBpliSHD4I@U7ii1`R&^AYgRaK805z2?pg_^XMt{t>Ecxqtk1@$Sg&|#r zM$g#B0)>^6{#bAOKgJXc&3|-J3RH_Hmj1->{98}Mhg^2G`8nxu-(jR2r5^H&S z){YQLk-?4)E{>i+wQQLmXU9<3diznMal+wp|reqVe-FyWQ6p%&u28h%b0Wu$zB zA6Gn$l__|~E;d$1tDNm-M9gJswbCMYu!%BH#vY_7TeD-PN(&Zasx)T<8!KBjFdLTN zSZTnr|C6cAl^9CEWFlxO|_3pF6;g8%>k delta 13245 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 diff --git a/package.json b/package.json index 2e0e69e..9330b1d 100644 --- a/package.json +++ b/package.json @@ -21,20 +21,20 @@ "axios": "^1.7.9", "husky": "^9.1.7", "laravel-vite-plugin": "^1.2.0", - "prettier": "^3.4.2", + "prettier": "^3.5.1", "prettier-plugin-organize-imports": "^4.1.0", "prettier-plugin-tailwindcss": "^0.6.11", "release-it": "^17.11.0", - "tailwindcss": "^4.0.4", + "tailwindcss": "^4.0.6", "typescript": "^5.7.3", "vite-plugin-watch": "^0.3.1" }, "dependencies": { - "@tailwindcss/vite": "^4.0.4", - "@types/node": "^22.13.1", + "@tailwindcss/vite": "^4.0.6", + "@types/node": "^22.13.4", "clsx": "^2.1.1", - "justd-icons": "^1.10.24", - "motion": "^12.4.0", + "justd-icons": "^1.10.25", + "motion": "^12.4.2", "react": "^19.0.0", "react-aria-components": "^1.6.0", "react-dom": "^19.0.0", From 2377a9b1b063cfb123202c16ee1bccf6a124b38d Mon Sep 17 00:00:00 2001 From: "Irsyad A. Panjaitan" Date: Fri, 14 Feb 2025 18:57:50 +0700 Subject: [PATCH 2/4] bump --- README.md | 2 +- bun.lockb | Bin 309676 -> 309676 bytes resources/css/app.css | 5 +- resources/js/components/ui/avatar.tsx | 2 +- resources/js/components/ui/button.tsx | 5 +- resources/js/components/ui/card.tsx | 82 +++++++------ resources/js/components/ui/checkbox.tsx | 8 +- resources/js/components/ui/dialog.tsx | 16 +-- resources/js/components/ui/dropdown.tsx | 10 +- resources/js/components/ui/field.tsx | 43 ++++--- resources/js/components/ui/keyboard.tsx | 2 +- resources/js/components/ui/link.tsx | 4 +- resources/js/components/ui/list-box.tsx | 141 ++++++++++------------ resources/js/components/ui/menu.tsx | 10 +- resources/js/components/ui/modal.tsx | 75 ++++++------ resources/js/components/ui/navbar.tsx | 60 ++++----- resources/js/components/ui/pagination.tsx | 18 +-- resources/js/components/ui/popover.tsx | 67 ++++------ resources/js/components/ui/select.tsx | 82 ++++++++----- resources/js/components/ui/sheet.tsx | 26 ++-- resources/js/components/ui/table.tsx | 22 ++-- resources/js/components/ui/text-field.tsx | 24 ++-- resources/js/components/ui/toast.tsx | 14 +-- resources/js/layouts/app-navbar.tsx | 4 +- 24 files changed, 373 insertions(+), 349 deletions(-) diff --git a/README.md b/README.md index 2cf439e..1ed5295 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -### TL;DR +### TL;DR ```bash composer create-project justd/laravel your-project-name diff --git a/bun.lockb b/bun.lockb index b7086f5162123151dafd88fa886d93d88bf127c4..6a92df2297eb7cb64397268e78c202300279cc68 100755 GIT binary patch delta 88 zcmZ4US!m5?p@tU57N#xCTLqa-^$e%)5M)kgG@Gs<#Ow>C0(LQraKsrI=@}a9nN05z sVm1~r&CM^#%+F(Bh`#+jqk7w&bS5T->G@L3;_WYln76+WVmY!50PHFp`v3p{ delta 92 zcmZ4US!m5?p@tU57N#xCTLqa-^o*wO5M)kgG?}g-#Ow>C0(LQraKsrI=@}a98BXsK wVm6jA$;~gx%+F(Bh`#+jqk7w&bSB0)LnA$t=?A5lh1*{UF>ik%#ByXA0AQ#dX8-^I diff --git a/resources/css/app.css b/resources/css/app.css index 970fb0e..1af5ab8 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -4,8 +4,9 @@ @variant dark (&:is(.dark *)); @theme { - --font-sans: 'Figtree', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', - 'Segoe UI Symbol', 'Noto Color Emoji'; + --font-sans: + 'Figtree', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; --color-border: var(--border); --color-input: var(--input); diff --git a/resources/js/components/ui/avatar.tsx b/resources/js/components/ui/avatar.tsx index 3d0eda8..70da228 100644 --- a/resources/js/components/ui/avatar.tsx +++ b/resources/js/components/ui/avatar.tsx @@ -1,4 +1,4 @@ -import { tv, type VariantProps } from 'tailwind-variants'; +import { type VariantProps, tv } from 'tailwind-variants'; const avatar = tv({ base: [ diff --git a/resources/js/components/ui/button.tsx b/resources/js/components/ui/button.tsx index 3071c13..06f1811 100644 --- a/resources/js/components/ui/button.tsx +++ b/resources/js/components/ui/button.tsx @@ -54,7 +54,8 @@ const buttonStyles = tv({ plain: ['border-transparent data-hovered:bg-secondary data-pressed:bg-secondary'] }, size: { - 'extra-small': 'h-8 px-[calc(var(--spacing)*2.7)] text-xs/4 lg:text-[0.800rem]/4', + 'extra-small': + 'h-8 px-[calc(var(--spacing)*2.7)] text-xs/4 **:data-[slot=avatar]:*:size-3.5 **:data-[slot=avatar]:size-3.5 **:data-[slot=icon]:size-3 lg:text-[0.800rem]/4', small: 'h-9 px-3.5 text-sm/5 sm:text-sm/5', medium: 'h-10 px-4 text-base sm:text-sm/6', large: 'h-11 px-4.5 text-base *:data-[slot=icon]:mx-[-1.5px] sm:*:data-[slot=icon]:size-5 lg:text-base/7', @@ -109,5 +110,5 @@ const Button = ({ className, intent, appearance, size, shape, ref, ...props }: B ); }; -export { Button, ButtonPrimitive, buttonStyles }; +export { Button, buttonStyles }; export type { ButtonProps }; diff --git a/resources/js/components/ui/card.tsx b/resources/js/components/ui/card.tsx index 4c9c8f8..cd7157d 100644 --- a/resources/js/components/ui/card.tsx +++ b/resources/js/components/ui/card.tsx @@ -1,25 +1,17 @@ -import { tv } from 'tailwind-variants'; - +import { cn } from '@/utils/classes'; import { Heading } from './heading'; -const card = tv({ - slots: { - root: [ - 'xrkr xkd2 rounded-lg border bg-bg text-fg shadow-xs has-[table]:overflow-hidden **:data-[slot=table-header]:bg-muted/50 has-[table]:**:data-[slot=card-footer]:border-t **:[table]:overflow-hidden' - ], - header: 'flex flex-col gap-y-1 px-6 py-5', - title: 'font-semibold leading-none tracking-tight sm:leading-6', - description: 'text-muted-fg text-sm', - content: - 'px-6 pb-6 has-[table]:border-t has-[[data-slot=table-header]]:bg-muted/40 has-[table]:p-0 **:data-[slot=table-cell]:px-6 **:data-[slot=table-column]:px-6 [&:has(table)+[data-slot=card-footer]]:py-5', - footer: 'flex items-center p-6 pt-0' - } -}); - -const { root, header, title, description, content, footer } = card(); - const Card = ({ className, ...props }: React.HTMLAttributes) => { - return
; + return ( +
+ ); }; interface HeaderProps extends React.HTMLAttributes { @@ -27,34 +19,50 @@ interface HeaderProps extends React.HTMLAttributes { description?: string; } -const Header = ({ className, title, description, children, ...props }: HeaderProps) => ( -
- {title && {title}} - {description && {description}} - {!title && typeof children === 'string' ? {children} : children} +const CardHeader = ({ className, title, description, children, ...props }: HeaderProps) => ( +
+ {title && {title}} + {description && {description}} + {!title && typeof children === 'string' ? {children} : children}
); -const Title = ({ className, level = 3, ...props }: React.ComponentProps) => { - return ; +const CardTitle = ({ className, level = 3, ...props }: React.ComponentProps) => { + return ( + + ); }; -const Description = ({ className, ...props }: React.HTMLAttributes) => { - return
; +const CardDescription = ({ className, ...props }: React.HTMLAttributes) => { + return
; }; -const Content = ({ className, ...props }: React.HTMLAttributes) => { - return
; +const CardContent = ({ className, ...props }: React.HTMLAttributes) => { + return ( +
+ ); }; -const Footer = ({ className, ...props }: React.HTMLAttributes) => { - return
; +const CardFooter = ({ className, ...props }: React.HTMLAttributes) => { + return
; }; -Card.Content = Content; -Card.Description = Description; -Card.Footer = Footer; -Card.Header = Header; -Card.Title = Title; +Card.Content = CardContent; +Card.Description = CardDescription; +Card.Footer = CardFooter; +Card.Header = CardHeader; +Card.Title = CardTitle; export { Card }; diff --git a/resources/js/components/ui/checkbox.tsx b/resources/js/components/ui/checkbox.tsx index 1a93f0e..a53cb56 100644 --- a/resources/js/components/ui/checkbox.tsx +++ b/resources/js/components/ui/checkbox.tsx @@ -24,7 +24,7 @@ interface CheckboxGroupProps extends CheckboxGroupPrimitiveProps { const CheckboxGroup = ({ className, ...props }: CheckboxGroupProps) => { return ( - + {props.label && } {props.children as React.ReactNode} {props.description && {props.description}} {props.errorMessage} @@ -89,7 +89,11 @@ const Checkbox = ({ className, ...props }: CheckboxProps) => {
<> - {props.label ? : (props.children as React.ReactNode)} + {props.label ? ( + + ) : ( + (props.children as React.ReactNode) + )} {props.description && {props.description}}
diff --git a/resources/js/components/ui/dialog.tsx b/resources/js/components/ui/dialog.tsx index 7f9219c..70bfa1e 100644 --- a/resources/js/components/ui/dialog.tsx +++ b/resources/js/components/ui/dialog.tsx @@ -1,8 +1,8 @@ import { useEffect, useRef } from 'react'; import { IconX } from 'justd-icons'; -import type { ButtonProps as ButtonPrimitiveProps, DialogProps, HeadingProps } from 'react-aria-components'; -import { Button as ButtonPrimitive, Dialog as DialogPrimitive, Heading } from 'react-aria-components'; +import type { HeadingProps } from 'react-aria-components'; +import { Button as ButtonPrimitive, Dialog as DialogPrimitive, Heading, Text } from 'react-aria-components'; import { tv } from 'tailwind-variants'; import { useMediaQuery } from '@/utils/use-media-query'; @@ -17,10 +17,10 @@ const dialogStyles = tv({ 'relative flex flex-col gap-0.5 p-4 sm:gap-1 sm:p-6 [&[data-slot=dialog-header]:has(+[data-slot=dialog-footer])]:pb-0', description: 'text-muted-fg text-sm', body: [ - 'isolate flex flex-1 flex-col overflow-auto px-4 sm:px-6', + 'isolate flex flex-1 flex-col overflow-auto px-4 py-1 sm:px-6', 'max-h-[calc(var(--visual-viewport-height)-var(--visual-viewport-vertical-padding)-var(--dialog-header-height,0px)-var(--dialog-footer-height,0px))]' ], - footer: 'isolate mt-auto flex flex-col-reverse justify-between gap-3 p-4 sm:flex-row sm:p-6', + footer: 'isolate mt-auto flex flex-col-reverse justify-between gap-3 p-4 pt-3 sm:flex-row sm:p-6 sm:pt-5', closeIndicator: 'close absolute top-1 right-1 z-50 grid size-8 place-content-center rounded-xl data-focused:bg-secondary data-hovered:bg-secondary data-focused:outline-hidden data-focus-visible:ring-1 data-focus-visible:ring-primary sm:top-2 sm:right-2 sm:size-7 sm:rounded-md' } @@ -28,11 +28,11 @@ const dialogStyles = tv({ const { root, header, description, body, footer, closeIndicator } = dialogStyles(); -const Dialog = ({ role, className, ...props }: DialogProps) => { - return ; +const Dialog = ({ role = 'dialog', className, ...props }: React.ComponentProps) => { + return ; }; -const Trigger = (props: ButtonPrimitiveProps) => ; +const Trigger = (props: React.ComponentProps) => ; type DialogHeaderProps = React.HTMLAttributes & { title?: string; @@ -89,7 +89,7 @@ const Title = ({ level = 2, className, ref, ...props }: DialogTitleProps) => ( type DialogDescriptionProps = React.ComponentProps<'div'>; const Description = ({ className, ref, ...props }: DialogDescriptionProps) => ( -
+ ); type DialogBodyProps = React.ComponentProps<'div'>; diff --git a/resources/js/components/ui/dropdown.tsx b/resources/js/components/ui/dropdown.tsx index 4898dfa..0f64ca0 100644 --- a/resources/js/components/ui/dropdown.tsx +++ b/resources/js/components/ui/dropdown.tsx @@ -2,7 +2,6 @@ import { cn } from '@/utils/classes'; import { IconCheck } from 'justd-icons'; import { Collection, - composeRenderProps, Header, ListBoxItem as ListBoxItemPrimitive, type ListBoxItemProps, @@ -11,7 +10,8 @@ import { Separator, type SeparatorProps, Text, - type TextProps + type TextProps, + composeRenderProps } from 'react-aria-components'; import { tv } from 'tailwind-variants'; import { Keyboard } from './keyboard'; @@ -145,11 +145,11 @@ const DropdownKeyboard = ({ className, ...props }: React.ComponentProps { return ; @@ -71,12 +68,18 @@ const FieldError = ({ className, ref, ...props }: FieldErrorProps) => { const fieldGroupStyles = tv({ base: [ - 'group flex h-10 items-center overflow-hidden rounded-lg border border-input transition duration-200 ease-out', - 'focus-within:ring-4 group-data-invalid:focus-within:border-danger group-data-invalid:focus-within:ring-danger/20', - '[&>[role=progressbar]]:mr-2.5', - '**:data-[slot=icon]:size-4 **:data-[slot=icon]:shrink-0', - '*:data-[slot=suffix]:mr-2.5 *:data-[slot=suffix]:text-muted-fg', - '*:data-[slot=prefix]:ml-2.5 *:data-[slot=prefix]:text-muted-fg' + 'group flex h-10 items-center overflow-hidden rounded-lg border border-input shadow-xs transition duration-200 ease-out', + 'relative focus-within:ring-4 group-data-invalid:focus-within:border-danger group-data-invalid:focus-within:ring-danger/20', + '[&>[role=progressbar]:first-child]:ml-2.5 [&>[role=progressbar]:last-child]:mr-2.5', + '**:data-[slot=icon]:size-4 **:data-[slot=icon]:shrink-0 **:[button]:shrink-0', + '[&>button:has([data-slot=icon]):first-child]:left-0 [&>button:has([data-slot=icon]):last-child]:right-0 [&>button:has([data-slot=icon])]:absolute', + '*:data-[slot=icon]:pointer-events-none *:data-[slot=icon]:absolute *:data-[slot=icon]:top-[calc(var(--spacing)*2.7)] *:data-[slot=icon]:z-10 *:data-[slot=icon]:size-4 *:data-[slot=icon]:text-muted-fg', + '[&>[data-slot=icon]:first-child]:left-2.5 [&>[data-slot=icon]:last-child]:right-2.5', + '[&:has([data-slot=icon]+input)]:pl-6 [&:has(input+[data-slot=icon])]:pr-6', + '[&:has([data-slot=icon]+[role=group])]:pl-6 [&:has([role=group]+[data-slot=icon])]:pr-6', + 'has-[[data-slot=icon]:last-child]:[&_input]:pr-7', + '*:[button]:h-8 *:[button]:rounded-[calc(var(--radius-sm)-1px)] *:[button]:px-2.5', + '[&>button:first-child]:ml-[calc(var(--spacing)*0.7)] [&>button:last-child]:mr-[calc(var(--spacing)*0.7)]' ], variants: { isFocusWithin: focusStyles.variants.isFocused, @@ -104,8 +107,18 @@ const FieldGroup = ({ className, ...props }: GroupProps) => { interface InputProps extends InputPrimitiveProps { ref?: React.RefObject; } + const Input = ({ className, ref, ...props }: InputProps) => { - return ; + return ( + + ); }; export { Description, FieldError, FieldGroup, Input, Label }; diff --git a/resources/js/components/ui/keyboard.tsx b/resources/js/components/ui/keyboard.tsx index 1006390..cde1b0a 100644 --- a/resources/js/components/ui/keyboard.tsx +++ b/resources/js/components/ui/keyboard.tsx @@ -3,7 +3,7 @@ import { tv } from 'tailwind-variants'; const keyboardStyles = tv({ slots: { - base: 'hidden text-current/70 group-data-focused:text-fg group-data-hovered:text-fg group-data-disabled:opacity-50 group-data-focused:opacity-90 lg:inline-flex lg:inline-flex forced-colors:group-data-focused:text-[HighlightText]', + base: 'hidden text-current/70 group-data-focused:text-fg group-data-hovered:text-fg group-data-disabled:opacity-50 group-data-focused:opacity-90 lg:inline-flex forced-colors:group-data-focused:text-[HighlightText]', kbd: 'inline-grid min-h-5 min-w-[2ch] place-content-center rounded text-center font-sans text-[.75rem] uppercase' } }); diff --git a/resources/js/components/ui/link.tsx b/resources/js/components/ui/link.tsx index 117e0b1..a58c31b 100644 --- a/resources/js/components/ui/link.tsx +++ b/resources/js/components/ui/link.tsx @@ -1,4 +1,4 @@ -import { composeRenderProps, Link as LinkPrimitive, type LinkProps as LinkPrimitiveProps } from 'react-aria-components'; +import { Link as LinkPrimitive, type LinkProps as LinkPrimitiveProps, composeRenderProps } from 'react-aria-components'; import { tv } from 'tailwind-variants'; import { focusButtonStyles } from './primitive'; @@ -37,5 +37,5 @@ const Link = ({ className, ref, ...props }: LinkProps) => { ); }; -export { Link }; +export { Link, linkStyles }; export type { LinkProps }; diff --git a/resources/js/components/ui/list-box.tsx b/resources/js/components/ui/list-box.tsx index 1e460ea..c9ae418 100644 --- a/resources/js/components/ui/list-box.tsx +++ b/resources/js/components/ui/list-box.tsx @@ -1,114 +1,99 @@ import { IconCheck, IconHamburger } from 'justd-icons'; import type { ListBoxItemProps as ListBoxItemPrimitiveProps, ListBoxProps } from 'react-aria-components'; -import { composeRenderProps, ListBoxItem, ListBox as ListBoxPrimitive } from 'react-aria-components'; -import { tv } from 'tailwind-variants'; +import { + ListBoxItem as ListBoxItemPrimitive, + ListBox as ListBoxPrimitive, + composeRenderProps +} from 'react-aria-components'; import { cn } from '@/utils/classes'; -import { DropdownItemDetails, DropdownSection } from './dropdown'; -import { composeTailwindRenderProps } from './primitive'; - -const listBoxStyles = tv({ - base: 'flex max-h-96 w-full min-w-56 flex-col gap-y-1 overflow-y-auto rounded-xl border p-1 shadow-lg outline-hidden [scrollbar-width:thin] [&::-webkit-scrollbar]:size-0.5' -}); +import { DropdownItemDetails, DropdownLabel, DropdownSection, dropdownItemStyles } from './dropdown'; const ListBox = ({ className, ...props }: ListBoxProps) => ( listBoxStyles({ ...renderProps, className }))} + className={composeRenderProps(className, (className) => + cn( + [ + 'flex max-h-96 w-full min-w-56 flex-col gap-y-1 overflow-y-auto rounded-xl border p-1 shadow-lg outline-hidden [scrollbar-width:thin] [&::-webkit-scrollbar]:size-0.5', + "grid grid-cols-[auto_1fr] overflow-auto *:[[role='group']+[role=group]]:mt-4 *:[[role='group']+[role=separator]]:mt-1" + ], + className + ) + )} /> ); -const listBoxItemStyles = tv({ - base: 'lbi relative cursor-pointer rounded-[calc(var(--radius-lg)-1px)] p-2 text-base outline-hidden sm:text-sm', - variants: { - isFocusVisible: { - true: 'bg-secondary text-accent-fg text-accent-fg/70' - }, - isHovered: { - true: 'bg-accent text-accent-fg [&:hover_[slot=description]]:text-accent-fg/70 [&:hover_[slot=label]]:text-accent-fg [&_.text-muted-fg]:text-accent-fg/80' - }, - isFocused: { - true: 'bg-accent text-accent-fg **:data-[slot=icon]:text-accent-fg **:data-[slot=label]:text-accent-fg [&_.text-muted-fg]:text-accent-fg/80' - }, - isSelected: { - true: 'bg-accent text-accent-fg **:data-[slot=icon]:text-accent-fg **:data-[slot=label]:text-accent-fg [&_.text-muted-fg]:text-accent-fg/80' - }, - isDragging: { true: 'cursor-grabbing bg-secondary text-secondary-fg' }, - isDisabled: { - true: 'cursor-default text-muted-fg opacity-70' - } - } -}); +// const listBoxItemStyles = tv({ +// base: "lbi col-span-full relative cursor-pointer rounded-[calc(var(--radius-lg)-1px)] p-2 text-base outline-hidden sm:text-sm", +// variants: { +// isFocusVisible: { +// true: "bg-secondary text-accent-fg", +// }, +// isHovered: { +// true: "bg-accent text-accent-fg [&:hover_[slot=description]]:text-accent-fg/70 [&:hover_[slot=label]]:text-accent-fg [&_.text-muted-fg]:text-accent-fg/80", +// }, +// isFocused: { +// true: "bg-accent text-accent-fg **:data-[slot=icon]:text-accent-fg **:data-[slot=label]:text-accent-fg [&_.text-muted-fg]:text-accent-fg/80", +// }, +// isSelected: { +// true: "bg-accent text-accent-fg **:data-[slot=icon]:text-accent-fg **:data-[slot=label]:text-accent-fg [&_.text-muted-fg]:text-accent-fg/80", +// }, +// isDragging: { true: "cursor-grabbing bg-secondary text-secondary-fg" }, +// isDisabled: { +// true: "cursor-default text-muted-fg opacity-70", +// }, +// }, +// }) interface ListBoxItemProps extends ListBoxItemPrimitiveProps { className?: string; } -const Item = ({ children, className, ...props }: ListBoxItemProps) => { +const ListBoxItem = ({ children, className, ...props }: ListBoxItemProps) => { const textValue = typeof children === 'string' ? children : undefined; return ( - - listBoxItemStyles({ + dropdownItemStyles({ ...renderProps, className }) )} > - {(values) => ( -
- <> - {values.allowsDragging && ( - - )} -
- {typeof children === 'function' ? children(values) : children} - - {values.isSelected && ( - - - + {({ allowsDragging, isSelected, isFocused, isDragging }) => ( + <> + {allowsDragging && ( + - -
- )} - - ); -}; - -type ListBoxPickerProps = ListBoxProps; - -const ListBoxPicker = ({ className, ...props }: ListBoxPickerProps) => { - return ( - + )} + {isSelected && } + {typeof children === 'string' ? {children} : children} + )} - {...props} - /> + ); }; -const Section = ({ className, ...props }: React.ComponentProps) => { +type ListBoxSectionProps = React.ComponentProps; +const ListBoxSection = ({ className, ...props }: ListBoxSectionProps) => { return ; }; -ListBox.Section = Section; -ListBox.ItemDetails = DropdownItemDetails; -ListBox.Item = Item; -ListBox.Picker = ListBoxPicker; +const ListBoxItemDetails = DropdownItemDetails; + +ListBox.Section = ListBoxSection; +ListBox.ItemDetails = ListBoxItemDetails; +ListBox.Item = ListBoxItem; -export { ListBox, listBoxStyles }; -export type { ListBoxItemProps, ListBoxPickerProps }; +export { ListBox }; +export type { ListBoxItemProps, ListBoxSectionProps }; diff --git a/resources/js/components/ui/menu.tsx b/resources/js/components/ui/menu.tsx index 59e67ad..85e0b3d 100644 --- a/resources/js/components/ui/menu.tsx +++ b/resources/js/components/ui/menu.tsx @@ -12,13 +12,13 @@ import type { import { Button, Collection, - composeRenderProps, Header, MenuItem as MenuItemPrimitive, Menu as MenuPrimitive, MenuSection as MenuSectionPrimitive, MenuTrigger as MenuTriggerPrimitive, - SubmenuTrigger as SubmenuTriggerPrimitive + SubmenuTrigger as SubmenuTriggerPrimitive, + composeRenderProps } from 'react-aria-components'; import type { VariantProps } from 'tailwind-variants'; import { tv } from 'tailwind-variants'; @@ -26,11 +26,11 @@ import { tv } from 'tailwind-variants'; import { cn } from '@/utils/classes'; import { DropdownItemDetails, - dropdownItemStyles, DropdownKeyboard, DropdownLabel, - dropdownSectionStyles, - DropdownSeparator + DropdownSeparator, + dropdownItemStyles, + dropdownSectionStyles } from './dropdown'; import { Popover } from './popover'; diff --git a/resources/js/components/ui/modal.tsx b/resources/js/components/ui/modal.tsx index 1bc88a0..ba4ebda 100644 --- a/resources/js/components/ui/modal.tsx +++ b/resources/js/components/ui/modal.tsx @@ -1,10 +1,14 @@ import type { DialogProps, DialogTriggerProps, ModalOverlayProps } from 'react-aria-components'; -import { composeRenderProps, DialogTrigger, ModalOverlay, Modal as ModalPrimitive } from 'react-aria-components'; -import { tv, type VariantProps } from 'tailwind-variants'; +import { DialogTrigger, ModalOverlay, Modal as ModalPrimitive, composeRenderProps } from 'react-aria-components'; +import { type VariantProps, tv } from 'tailwind-variants'; import { Dialog } from './dialog'; -const overlay = tv({ +const Modal = (props: DialogTriggerProps) => { + return ; +}; + +const modalOverlayStyles = tv({ base: [ 'fixed top-0 left-0 isolate z-50 h-(--visual-viewport-height) w-full', 'flex items-end justify-end bg-fg/15 text-center sm:items-center sm:justify-center dark:bg-bg/40', @@ -18,11 +22,11 @@ const overlay = tv({ true: 'fade-in animate-in duration-200 ease-out' }, isExiting: { - true: 'fade-out animate-out duration-150 ease-in' + true: 'fade-out animate-out ease-in' } } }); -const content = tv({ +const modalContentStyles = tv({ base: [ 'max-h-full w-full rounded-t-2xl bg-overlay text-left align-middle text-overlay-fg shadow-lg ring-1 ring-fg/5', 'overflow-hidden sm:rounded-2xl dark:ring-border' @@ -51,18 +55,10 @@ const content = tv({ } }); -const Modal = (props: DialogTriggerProps) => { - return ; -}; - interface ModalContentProps - extends Omit, 'children'>, - Omit, - VariantProps { - 'aria-label'?: DialogProps['aria-label']; - 'aria-labelledby'?: DialogProps['aria-labelledby']; - role?: DialogProps['role']; - children?: DialogProps['children']; + extends Omit, + Pick, + VariantProps { closeButton?: boolean; isBlurred?: boolean; classNames?: { @@ -73,41 +69,44 @@ interface ModalContentProps const ModalContent = ({ classNames, - isDismissable = true, + isDismissable: isDismissableInternal, isBlurred = false, children, size, - role, + role = 'dialog', closeButton = true, ...props }: ModalContentProps) => { - const _isDismissable = role === 'alertdialog' ? false : isDismissable; + const isDismissable = isDismissableInternal ?? role !== 'alertdialog'; + return ( { - return overlay({ + isDismissable={isDismissable} + className={composeRenderProps(classNames?.overlay, (className, renderProps) => + modalOverlayStyles({ ...renderProps, isBlurred, className - }); - })} + }) + )} {...props} > - content({ + modalContentStyles({ ...renderProps, size, className }) )} + {...props} > - + {(values) => ( <> {typeof children === 'function' ? children(values) : children} - {closeButton && } + {closeButton && } )} @@ -116,13 +115,21 @@ const ModalContent = ({ ); }; -Modal.Trigger = Dialog.Trigger; -Modal.Header = Dialog.Header; -Modal.Title = Dialog.Title; -Modal.Description = Dialog.Description; -Modal.Footer = Dialog.Footer; -Modal.Body = Dialog.Body; -Modal.Close = Dialog.Close; +const ModalTrigger = Dialog.Trigger; +const ModalHeader = Dialog.Header; +const ModalTitle = Dialog.Title; +const ModalDescription = Dialog.Description; +const ModalFooter = Dialog.Footer; +const ModalBody = Dialog.Body; +const ModalClose = Dialog.Close; + +Modal.Trigger = ModalTrigger; +Modal.Header = ModalHeader; +Modal.Title = ModalTitle; +Modal.Description = ModalDescription; +Modal.Footer = ModalFooter; +Modal.Body = ModalBody; +Modal.Close = ModalClose; Modal.Content = ModalContent; export { Modal }; diff --git a/resources/js/components/ui/navbar.tsx b/resources/js/components/ui/navbar.tsx index de9478a..78f89ff 100644 --- a/resources/js/components/ui/navbar.tsx +++ b/resources/js/components/ui/navbar.tsx @@ -3,8 +3,8 @@ import { createContext, use, useCallback, useId, useMemo, useState } from 'react import { IconHamburger } from 'justd-icons'; import { LayoutGroup, motion } from 'motion/react'; import type { LinkProps } from 'react-aria-components'; -import { composeRenderProps, Link } from 'react-aria-components'; -import { tv, type VariantProps } from 'tailwind-variants'; +import { Link, composeRenderProps } from 'react-aria-components'; +import { type VariantProps, tv } from 'tailwind-variants'; import { cn } from '@/utils/classes'; import { useMediaQuery } from '@/utils/use-media-query'; @@ -43,7 +43,7 @@ interface NavbarProps extends React.ComponentProps<'header'>, NavbarOptions { } const navbarStyles = tv({ - base: '@container relative isolate flex w-full flex-col', + base: 'relative isolate flex w-full flex-col', variants: { intent: { floating: 'px-2.5 pt-2', @@ -64,7 +64,7 @@ const Navbar = ({ intent = 'navbar', ...props }: NavbarProps) => { - const isCompact = useMediaQuery('(max-width: 765px)'); + const isCompact = useMediaQuery('(max-width: 768px)'); const [_open, _setOpen] = useState(defaultOpen); const open = openProp ?? _open; @@ -106,8 +106,8 @@ const Navbar = ({ const navStyles = tv({ base: [ - 'group peer @md:flex hidden h-(--navbar-height) w-full items-center px-4 [--navbar-height:3.5rem]', - '[&>div]:mx-auto @md:[&>div]:flex [&>div]:w-full [&>div]:max-w-[1680px] [&>div]:items-center' + 'group peer hidden h-(--navbar-height) w-full items-center px-4 [--navbar-height:3.5rem] md:flex', + '[&>div]:mx-auto [&>div]:w-full [&>div]:max-w-[1680px] [&>div]:items-center md:[&>div]:flex' ], variants: { isSticky: { @@ -115,11 +115,11 @@ const navStyles = tv({ }, intent: { floating: - 'mx-auto w-full max-w-7xl rounded-xl border bg-navbar @md:px-4 text-navbar-fg 2xl:max-w-(--breakpoint-2xl)', - navbar: 'border-b bg-navbar @md:px-6 text-navbar-fg', + 'mx-auto w-full max-w-7xl rounded-xl border bg-navbar text-navbar-fg md:px-4 2xl:max-w-(--breakpoint-2xl)', + navbar: 'border-b bg-navbar text-navbar-fg md:px-6', inset: [ - 'mx-auto @md:px-6', - '[&>div]:mx-auto @md:[&>div]:flex [&>div]:w-full [&>div]:items-center 2xl:[&>div]:max-w-(--breakpoint-2xl)' + 'mx-auto md:px-6', + '[&>div]:mx-auto [&>div]:w-full [&>div]:items-center md:[&>div]:flex 2xl:[&>div]:max-w-(--breakpoint-2xl)' ] } } @@ -129,12 +129,13 @@ interface NavbarNavProps extends React.ComponentProps<'div'> { intent?: 'navbar' | 'floating' | 'inset'; isSticky?: boolean; side?: 'left' | 'right'; + useDefaultResponsive?: boolean; } -const NavbarNav = ({ className, ref, ...props }: NavbarNavProps) => { +const NavbarNav = ({ useDefaultResponsive = true, className, ref, ...props }: NavbarNavProps) => { const { isCompact, side, intent, isSticky, open, setOpen } = useNavbar(); - if (isCompact) { + if (isCompact && useDefaultResponsive) { return ( { }} isFloat={intent === 'floating'} > - {props.children} + {props.children} ); @@ -184,7 +185,7 @@ const NavbarTrigger = ({ className, onPress, ref, ...props }: NavbarTriggerProps ); }; -const Section = ({ className, ...props }: React.ComponentProps<'div'>) => { +const NavbarSection = ({ className, ...props }: React.ComponentProps<'div'>) => { const { isCompact } = useNavbar(); const id = useId(); return ( @@ -202,7 +203,7 @@ const Section = ({ className, ...props }: React.ComponentProps<'div'>) => { const navItemStyles = tv({ base: [ - '*:data-[slot=icon]:-mx-0.5 relative flex cursor-pointer items-center gap-x-2 px-2 @md:text-sm text-muted-fg no-underline outline-hidden transition-colors forced-colors:transform-none forced-colors:outline-0 forced-colors:data-disabled:text-[GrayText]', + '*:data-[slot=icon]:-mx-0.5 relative flex cursor-pointer items-center gap-x-2 px-2 text-muted-fg no-underline outline-hidden transition-colors md:text-sm forced-colors:transform-none forced-colors:outline-0 forced-colors:data-disabled:text-[GrayText]', 'data-focused:text-fg data-hovered:text-fg data-pressed:text-fg data-focus-visible:outline-1 data-focus-visible:outline-primary', '**:data-[slot=chevron]:size-4 **:data-[slot=chevron]:transition-transform', 'data-pressed:**:data-[slot=chevron]:rotate-180 *:data-[slot=icon]:size-4 *:data-[slot=icon]:shrink-0', @@ -219,7 +220,7 @@ interface NavbarItemProps extends LinkProps { isCurrent?: boolean; } -const Item = ({ className, isCurrent, ...props }: NavbarItemProps) => { +const NavbarItem = ({ className, isCurrent, ...props }: NavbarItemProps) => { const { intent, isCompact } = useNavbar(); return ( { {(isCurrent || values.isCurrent) && !isCompact && intent !== 'floating' && ( )} @@ -246,24 +248,24 @@ const Item = ({ className, isCurrent, ...props }: NavbarItemProps) => { ); }; -const Logo = ({ className, ...props }: LinkProps) => { +const NavbarLogo = ({ className, ...props }: LinkProps) => { return ( ); }; -const Flex = ({ className, ref, ...props }: React.ComponentProps<'div'>) => { - return
; +const NavbarFlex = ({ className, ref, ...props }: React.ComponentProps<'div'>) => { + return
; }; const compactStyles = tv({ - base: 'flex @md:hidden justify-between bg-navbar text-navbar-fg peer-has-[[data-navbar-intent=floating]]:border', + base: 'flex justify-between bg-navbar text-navbar-fg peer-has-[[data-navbar-intent=floating]]:border md:hidden', variants: { intent: { floating: 'h-12 rounded-lg border px-3.5', @@ -286,19 +288,19 @@ const insetStyles = tv({ variants: { intent: { floating: '', - inset: '@md:rounded-lg bg-bg @md:shadow-xs @md:ring-1 @md:ring-fg/15 dark:bg-navbar @md:dark:ring-border', + inset: 'bg-bg md:rounded-lg md:shadow-xs md:ring-1 md:ring-fg/15 dark:bg-navbar md:dark:ring-border', navbar: '' } } }); -const Inset = ({ className, ref, ...props }: React.ComponentProps<'div'>) => { +const NavbarInset = ({ className, ref, ...props }: React.ComponentProps<'div'>) => { const { intent } = useNavbar(); return (
{props.children}
@@ -306,13 +308,13 @@ const Inset = ({ className, ref, ...props }: React.ComponentProps<'div'>) => { }; Navbar.Nav = NavbarNav; -Navbar.Inset = Inset; +Navbar.Inset = NavbarInset; Navbar.Compact = NavbarCompact; -Navbar.Flex = Flex; +Navbar.Flex = NavbarFlex; Navbar.Trigger = NavbarTrigger; -Navbar.Logo = Logo; -Navbar.Item = Item; -Navbar.Section = Section; +Navbar.Logo = NavbarLogo; +Navbar.Item = NavbarItem; +Navbar.Section = NavbarSection; export { Navbar }; export type { NavbarCompactProps, NavbarItemProps, NavbarNavProps, NavbarProps, NavbarTriggerProps }; diff --git a/resources/js/components/ui/pagination.tsx b/resources/js/components/ui/pagination.tsx index dc214aa..8d6ef9b 100644 --- a/resources/js/components/ui/pagination.tsx +++ b/resources/js/components/ui/pagination.tsx @@ -1,12 +1,12 @@ import { IconChevronLgLeft, IconChevronLgRight, - IconChevronsLgLeft, - IconChevronsLgRight, + IconChevronWallLeft, + IconChevronWallRight, IconDotsHorizontal } from 'justd-icons'; import type { ListBoxItemProps, ListBoxProps, ListBoxSectionProps } from 'react-aria-components'; -import { composeRenderProps, ListBox, ListBoxItem, ListBoxSection, Separator } from 'react-aria-components'; +import { ListBox, ListBoxItem, ListBoxSection, Separator, composeRenderProps } from 'react-aria-components'; import { cn } from '@/utils/classes'; import { tv } from 'tailwind-variants'; @@ -58,7 +58,7 @@ const PaginationSection = ({ className, ref, ...props }: Pagin interface PaginationListProps extends ListBoxProps { ref?: React.RefObject; } -const List = ({ className, ref, ...props }: PaginationListProps) => { +const PaginationList = ({ className, ref, ...props }: PaginationListProps) => { return ( ); case 'first': - return renderPaginationIndicator(); + return renderPaginationIndicator(); case 'last': - return renderPaginationIndicator(); + return renderPaginationIndicator(); default: return renderListItem( { @@ -184,8 +184,8 @@ const Item = ({ } }; -Pagination.Item = Item; -Pagination.List = List; +Pagination.Item = PaginationItem; +Pagination.List = PaginationList; Pagination.Section = PaginationSection; export { Pagination }; diff --git a/resources/js/components/ui/popover.tsx b/resources/js/components/ui/popover.tsx index 30e93b7..65b92b0 100644 --- a/resources/js/components/ui/popover.tsx +++ b/resources/js/components/ui/popover.tsx @@ -4,7 +4,6 @@ import type { PopoverProps as PopoverPrimitiveProps } from 'react-aria-components'; import { - composeRenderProps, type DialogProps, DialogTrigger, Modal, @@ -12,6 +11,7 @@ import { OverlayArrow, PopoverContext, Popover as PopoverPrimitive, + composeRenderProps, useSlottedContext } from 'react-aria-components'; import { tv } from 'tailwind-variants'; @@ -23,23 +23,23 @@ import type { DialogBodyProps, DialogFooterProps, DialogHeaderProps, DialogTitle import { Dialog } from './dialog'; type PopoverProps = DialogTriggerProps; -const Popover = ({ children, ...props }: PopoverProps) => { - return {children}; +const Popover = (props: PopoverProps) => { + return ; }; -const Title = ({ level = 2, className, ...props }: DialogTitleProps) => ( +const PopoverTitle = ({ level = 2, className, ...props }: DialogTitleProps) => ( ); -const Header = ({ className, ...props }: DialogHeaderProps) => ( +const PopoverHeader = ({ className, ...props }: DialogHeaderProps) => ( ); -const Footer = ({ className, ...props }: DialogFooterProps) => ( +const PopoverFooter = ({ className, ...props }: DialogFooterProps) => ( ); -const Body = ({ className, ref, ...props }: DialogBodyProps) => ( +const PopoverBody = ({ className, ref, ...props }: DialogBodyProps) => ( ); @@ -53,7 +53,7 @@ const content = tv({ false: 'min-w-80' }, isMenu: { - true: 'p-0' + true: '' }, isEntering: { true: [ @@ -94,15 +94,13 @@ const drawer = tv({ }); interface PopoverContentProps - extends Omit, 'children'>, - Omit, - Omit { + extends Omit, + Omit, + Pick { children: React.ReactNode; showArrow?: boolean; style?: React.CSSProperties; respectScreen?: boolean; - 'aria-label'?: DialogProps['aria-label']; - 'aria-labelledby'?: DialogProps['aria-labelledby']; className?: string | ((values: { defaultClassName?: string }) => string); } @@ -131,7 +129,7 @@ const PopoverContent = ({ drawer({ ...renderProps, isMenu, className }) )} > - + {children} @@ -139,13 +137,13 @@ const PopoverContent = ({ ) : ( content({ ...renderProps, className }) )} + {...props} > {showArrow && ( @@ -159,40 +157,25 @@ const PopoverContent = ({ )} - + {children} ); }; -const Picker = ({ children, className, ...props }: PopoverContentProps) => { - return ( - - content({ - ...renderProps, - isPicker: true, - className - }) - )} - > - {children} - - ); -}; +const PopoverTrigger = Dialog.Trigger; +const PopoverClose = Dialog.Close; +const PopoverDescription = Dialog.Description; -Popover.Primitive = PopoverPrimitive; -Popover.Trigger = Dialog.Trigger; -Popover.Close = Dialog.Close; +Popover.Trigger = PopoverTrigger; +Popover.Close = PopoverClose; +Popover.Description = PopoverDescription; Popover.Content = PopoverContent; -Popover.Description = Dialog.Description; -Popover.Body = Body; -Popover.Footer = Footer; -Popover.Header = Header; -Popover.Picker = Picker; -Popover.Title = Title; +Popover.Body = PopoverBody; +Popover.Footer = PopoverFooter; +Popover.Header = PopoverHeader; +Popover.Title = PopoverTitle; -export { Popover }; +export { Popover, PopoverContent }; export type { PopoverContentProps, PopoverProps }; diff --git a/resources/js/components/ui/select.tsx b/resources/js/components/ui/select.tsx index 67055a9..79574ed 100644 --- a/resources/js/components/ui/select.tsx +++ b/resources/js/components/ui/select.tsx @@ -1,19 +1,23 @@ +import { cn } from '@/utils/classes'; import { IconChevronLgDown } from 'justd-icons'; -import type { ListBoxProps, SelectProps as SelectPrimitiveProps, ValidationResult } from 'react-aria-components'; -import { Button, composeRenderProps, Select as SelectPrimitive, SelectValue } from 'react-aria-components'; +import type { + ListBoxProps, + PopoverProps, + SelectProps as SelectPrimitiveProps, + ValidationResult +} from 'react-aria-components'; +import { Button, Select as SelectPrimitive, SelectValue, composeRenderProps } from 'react-aria-components'; import { tv } from 'tailwind-variants'; - -import type { Placement } from '@react-types/overlays'; import { DropdownItem, DropdownItemDetails, DropdownLabel, DropdownSection, DropdownSeparator } from './dropdown'; import { Description, FieldError, Label } from './field'; import { ListBox } from './list-box'; -import { Popover } from './popover'; +import { PopoverContent } from './popover'; import { composeTailwindRenderProps, focusStyles } from './primitive'; const selectTriggerStyles = tv({ extend: focusStyles, base: [ - 'btr flex h-10 w-full cursor-default items-center items-center gap-4 gap-x-2 rounded-lg border border-input py-2 pr-2 pl-3 text-start shadow-[inset_0_1px_0_0_rgba(255,255,255,0.1)] transition **:data-[slot=icon]:size-4 group-data-disabled:opacity-50 dark:shadow-none', + 'btr flex h-10 w-full cursor-default items-center gap-4 gap-x-2 rounded-lg border border-input py-2 pr-2 pl-3 text-start shadow-[inset_0_1px_0_0_rgba(255,255,255,0.1)] transition **:data-[slot=icon]:size-4 group-data-disabled:opacity-50 dark:shadow-none', 'group-data-open:border-ring/70 group-data-open:ring-4 group-data-open:ring-ring/20', 'text-fg group-data-invalid:border-danger group-data-invalid:ring-danger/20 forced-colors:group-data-invalid:border-[Mark]' ], @@ -32,41 +36,47 @@ interface SelectProps extends SelectPrimitiveProps { className?: string; } -const Select = ({ - label, - description, - errorMessage, - children, - className, - ...props -}: SelectProps) => { +const Select = ({ label, description, errorMessage, className, ...props }: SelectProps) => { return ( - {label && } - {children as React.ReactNode} - {description && {description}} - {errorMessage} + {(values) => ( + <> + {label && } + {typeof props.children === 'function' ? props.children(values) : props.children} + {description && {description}} + {errorMessage} + + )} ); }; -interface ListProps extends ListBoxProps { +interface SelectListProps extends ListBoxProps, Pick { items?: Iterable; - placement?: Placement; - children: React.ReactNode | ((item: T) => React.ReactNode); - className?: string; + popoverClassName?: PopoverProps['className']; } -const List = ({ className, children, items, placement, ...props }: ListProps) => { +const SelectList = ({ + children, + items, + className, + popoverClassName, + ...props +}: SelectListProps) => { return ( - - + + {children} - - + + ); }; @@ -95,13 +105,19 @@ const SelectTrigger = ({ className, ...props }: SelectTriggerProps) => { ); }; -Select.OptionDetails = DropdownItemDetails; -Select.Option = DropdownItem; -Select.Label = DropdownLabel; -Select.Separator = DropdownSeparator; -Select.Section = DropdownSection; +const SelectSection = DropdownSection; +const SelectSeparator = DropdownSeparator; +const SelectLabel = DropdownLabel; +const SelectOptionDetails = DropdownItemDetails; +const SelectOption = DropdownItem; + +Select.OptionDetails = SelectOptionDetails; +Select.Option = SelectOption; +Select.Label = SelectLabel; +Select.Separator = SelectSeparator; +Select.Section = SelectSection; Select.Trigger = SelectTrigger; -Select.List = List; +Select.List = SelectList; export { Select }; export type { SelectProps, SelectTriggerProps }; diff --git a/resources/js/components/ui/sheet.tsx b/resources/js/components/ui/sheet.tsx index d5966c3..5660077 100644 --- a/resources/js/components/ui/sheet.tsx +++ b/resources/js/components/ui/sheet.tsx @@ -1,6 +1,6 @@ import type { DialogProps, DialogTriggerProps, ModalOverlayProps } from 'react-aria-components'; -import { composeRenderProps, DialogTrigger, Modal, ModalOverlay } from 'react-aria-components'; -import { tv, type VariantProps } from 'tailwind-variants'; +import { DialogTrigger, Modal, ModalOverlay, composeRenderProps } from 'react-aria-components'; +import { type VariantProps, tv } from 'tailwind-variants'; import { Dialog } from './dialog'; @@ -132,14 +132,22 @@ const SheetContent = ({ ); }; -Sheet.Trigger = Dialog.Trigger; -Sheet.Footer = Dialog.Footer; +const SheetTrigger = Dialog.Trigger; +const SheetFooter = Dialog.Footer; +const SheetHeader = Dialog.Header; +const SheetTitle = Dialog.Title; +const SheetDescription = Dialog.Description; +const SheetBody = Dialog.Body; +const SheetClose = Dialog.Close; + +Sheet.Trigger = SheetTrigger; +Sheet.Footer = SheetFooter; +Sheet.Header = SheetHeader; +Sheet.Title = SheetTitle; +Sheet.Description = SheetDescription; +Sheet.Body = SheetBody; +Sheet.Close = SheetClose; Sheet.Content = SheetContent; -Sheet.Header = Dialog.Header; -Sheet.Title = Dialog.Title; -Sheet.Description = Dialog.Description; -Sheet.Body = Dialog.Body; -Sheet.Close = Dialog.Close; export { Sheet }; export type { SheetContentProps, SheetProps, Sides }; diff --git a/resources/js/components/ui/table.tsx b/resources/js/components/ui/table.tsx index b3a9393..8eec09d 100644 --- a/resources/js/components/ui/table.tsx +++ b/resources/js/components/ui/table.tsx @@ -16,12 +16,12 @@ import { Collection, Column, ColumnResizer as ColumnResizerPrimitive, - composeRenderProps, ResizableTableContainer, Row, - TableBody, - TableHeader, + TableBody as TableBodyPrimitive, + TableHeader as TableHeaderPrimitive, Table as TablePrimitive, + composeRenderProps, useTableOptions } from 'react-aria-components'; import { tv } from 'tailwind-variants'; @@ -33,7 +33,7 @@ const table = tv({ slots: { root: 'table w-full min-w-full caption-bottom border-spacing-0 text-sm outline-hidden [--table-selected-bg:color-mix(in_oklab,var(--color-primary)_5%,white_90%)] **:data-drop-target:border **:data-drop-target:border-primary dark:[--table-selected-bg:color-mix(in_oklab,var(--color-primary)_25%,black_70%)]', header: 'x32 border-b', - row: 'tr group relative cursor-default border-b bg-bg text-fg/70 outline-hidden ring-primary data-selected:data-hovered:bg-(--table-selected-bg)/70 data-selected:bg-(--table-selected-bg) data-focus-visible:ring-1 data-focused:ring-0 dark:data-selected:data-hovered:bg-[color-mix(in_oklab,var(--color-primary)_40%,black_60%)] dark:data-selected:data-hovered:bg-subtle/60', + row: 'tr group relative cursor-default border-b bg-bg text-muted-fg outline-hidden ring-primary data-selected:data-hovered:bg-(--table-selected-bg)/70 data-selected:bg-(--table-selected-bg) data-focus-visible:ring-1 data-focused:ring-0 dark:data-selected:data-hovered:bg-[color-mix(in_oklab,var(--color-primary)_30%,black_70%)]', cellIcon: 'grid size-[1.15rem] flex-none shrink-0 place-content-center rounded bg-secondary text-fg *:data-[slot=icon]:size-3.5 *:data-[slot=icon]:shrink-0 *:data-[slot=icon]:transition-transform *:data-[slot=icon]:duration-200', columnResizer: [ @@ -88,8 +88,8 @@ const ColumnResizer = ({ className, ...props }: ColumnResizerProps) => ( ); -const Body = (props: TableBodyProps) => ( - +const TableBody = (props: TableBodyProps) => ( + ); interface TableCellProps extends CellProps { @@ -159,16 +159,16 @@ interface TableHeaderProps extends HeaderProps { ref?: React.Ref; } -const Header = ({ children, ref, className, columns, ...props }: TableHeaderProps) => { +const TableHeader = ({ children, ref, className, columns, ...props }: TableHeaderProps) => { const { selectionBehavior, selectionMode, allowsDragging } = useTableOptions(); return ( - + {allowsDragging && } {selectionBehavior === 'toggle' && ( {selectionMode === 'multiple' && } )} {children} - + ); }; @@ -213,10 +213,10 @@ const TableRow = ({ children, className, columns, id, ref, ... ); }; -Table.Body = Body; +Table.Body = TableBody; Table.Cell = TableCell; Table.Column = TableColumn; -Table.Header = Header; +Table.Header = TableHeader; Table.Row = TableRow; export { Table }; diff --git a/resources/js/components/ui/text-field.tsx b/resources/js/components/ui/text-field.tsx index ea1fc3c..d18c567 100644 --- a/resources/js/components/ui/text-field.tsx +++ b/resources/js/components/ui/text-field.tsx @@ -7,7 +7,6 @@ import { TextField as TextFieldPrimitive, type TextFieldProps as TextFieldPrimitiveProps } from 'react-aria-components'; -import { twJoin } from 'tailwind-merge'; import type { FieldProps } from './field'; import { Description, FieldError, FieldGroup, Input, Label } from './field'; @@ -57,26 +56,17 @@ const TextField = ({ {!props.children ? ( <> {label && } [data-slot=suffix]>button]:mr-[calc(var(--spacing)*-1.7)] [&>[data-slot=suffix]>button]:data-focus-visible:outline-1 [&>[data-slot=suffix]>button]:data-focus-visible:outline-offset-1', - '[&>[data-slot=prefix]>button]:ml-[calc(var(--spacing)*-1.7)] [&>[data-slot=prefix]>button]:data-focus-visible:outline-1 [&>[data-slot=prefix]>button]:data-focus-visible:outline-offset-1' - )} + isInvalid={!!errorMessage} data-loading={isPending ? 'true' : undefined} > - {prefix ? ( - - {prefix} - - ) : null} + {prefix && typeof prefix === 'string' ? {prefix} : prefix} {isRevealable ? ( : } ) : isPending ? ( - + ) : suffix ? ( - {suffix} + typeof suffix === 'string' ? ( + {suffix} + ) : ( + suffix + ) ) : null} {description && {description}} diff --git a/resources/js/components/ui/toast.tsx b/resources/js/components/ui/toast.tsx index ea0958c..d957583 100644 --- a/resources/js/components/ui/toast.tsx +++ b/resources/js/components/ui/toast.tsx @@ -34,8 +34,8 @@ const Toast = ({ ...props }: ToasterProps) => { 'has-data-description:**:data-title:font-medium [&:has([data-description])_[data-title]]:text-base!', 'has-data-[slot=icon]:**:data-content:pl-0', 'has-data-button:*:data-content:mb-10', - 'has-data-button:**:data-close-button:hidden! flex w-full rounded-xl p-4', - 'inset-ring-1 inset-ring-current/10 backdrop-blur-3xl' + 'has-data-button:hover:**:data-close-button:hidden! flex w-full rounded-xl p-4', + 'inset-ring-1 inset-ring-current/10 border-transparent backdrop-blur-3xl' ), icon: 'absolute top-[0.2rem] [--toast-icon-margin-end:7px] *:data-[slot=icon]:text-fg *:data-[slot=icon]:size-4.5 **:data-[slot=icon]:text-current', title: '', @@ -43,12 +43,12 @@ const Toast = ({ ...props }: ToasterProps) => { default: 'bg-bg text-fg [--gray2:theme(--color-fg/10%)]', content: 'pr-6 *:data-description:text-current/65! *:data-description:text-sm!', error: - 'inset-ring-danger/15 dark:inset-ring-danger/25 [--error-bg:theme(--color-danger/10%)] [--error-border:transparent] [--error-text:var(--color-danger)]', - info: 'inset-ring-sky-600/15 dark:inset-ring-sky-500/20 [--info-border:transparent] [--info-bg:theme(--color-sky-500/10%)] [--info-text:var(--color-sky-700)] dark:[--info-bg:theme(--color-sky-500/15%)] dark:[--info-text:var(--color-sky-400)]', + 'inset-ring-danger/15 dark:inset-ring-danger/20 [--error-bg:theme(--color-danger/5%)] [--error-text:var(--color-danger)]', + info: 'inset-ring-sky-600/15 dark:inset-ring-sky-500/20 [--info-bg:theme(--color-sky-500/5%)] [--info-text:var(--color-sky-700)] dark:[--info-text:var(--color-sky-400)]', warning: - 'inset-ring-warning/30 dark:inset-ring-warning/15 [--warning-bg:theme(--color-warning/20%)] dark:[--warning-bg:theme(--color-warning/10%)] [--warning-border:transparent] [--warning-text:var(--color-warning-fg)] dark:[--warning-text:var(--color-warning)]', + 'inset-ring-warning/15 dark:inset-ring-warning/20 [--warning-bg:theme(--color-warning/7%)] [--warning-text:var(--color-warning-fg)] dark:[--warning-text:var(--color-warning)]', success: - 'inset-ring-success/20 [--success-bg:theme(--color-success/80%)] dark:[--success-bg:theme(--color-success/20%)] [--success-border:transparent] [--success-text:#fff] dark:[--success-text:var(--color-success)]', + 'inset-ring-success/15 [--success-bg:theme(--color-success/5%)] [--success-text:var(--color-success)]', cancelButton: buttonStyles({ className: 'hover:border-secondary-fg/10 hover:bg-secondary/90 self-start absolute bottom-4 left-4 justify-self-start', @@ -60,7 +60,7 @@ const Toast = ({ ...props }: ToasterProps) => { size: 'extra-small' }), closeButton: - '*:[svg]:size-12 size-6! rounded-md! [--gray1:transparent] [--gray4:transparent] [--gray5:transparent] [--gray12:current] [--toast-close-button-start:full] [--toast-close-button-end:-6px] [--toast-close-button-transform:translate(-75%,60%)] absolute' + '*:[svg]:size-5 group-hover:block! hidden! size-6! rounded-md! [--gray1:transparent] [--gray4:transparent] [--gray5:transparent] [--gray12:current] [--toast-close-button-start:full] [--toast-close-button-end:-6px] [--toast-close-button-transform:translate(-75%,60%)] absolute' } }} {...props} diff --git a/resources/js/layouts/app-navbar.tsx b/resources/js/layouts/app-navbar.tsx index c672518..83197e4 100644 --- a/resources/js/layouts/app-navbar.tsx +++ b/resources/js/layouts/app-navbar.tsx @@ -10,6 +10,7 @@ import { IconColorPalette, IconColorSwatch, IconLogout, + IconPackage, IconSettings } from 'justd-icons'; import React from 'react'; @@ -54,6 +55,7 @@ export function AppNavbar({ children, ...props }: React.ComponentProps + Blocks @@ -68,7 +70,7 @@ export function AppNavbar({ children, ...props }: React.ComponentProps - + Components From 86c025f14c7ca8aa12258efb0c75f9e799032694 Mon Sep 17 00:00:00 2001 From: "Irsyad A. Panjaitan" Date: Fri, 14 Feb 2025 19:04:53 +0700 Subject: [PATCH 3/4] migrate from prettier to biome --- .prettierignore | 6 - .prettierrc | 12 - biome.json | 80 +++++ bun.lockb | Bin 309676 -> 310662 bytes package.json | 6 +- resources/js/app.tsx | 37 +- resources/js/bootstrap.ts | 6 +- resources/js/components/flash-message.tsx | 20 +- resources/js/components/footer.tsx | 134 ++++---- resources/js/components/header.tsx | 18 +- resources/js/components/input-error.tsx | 12 +- resources/js/components/logo.tsx | 8 +- resources/js/components/providers.tsx | 10 +- resources/js/components/theme-provider.tsx | 74 ++-- resources/js/components/theme-switcher.tsx | 24 +- resources/js/components/ui/avatar.tsx | 61 ++-- resources/js/components/ui/button.tsx | 127 +++---- resources/js/components/ui/card.tsx | 73 ++-- resources/js/components/ui/checkbox.tsx | 89 ++--- resources/js/components/ui/container.tsx | 26 +- resources/js/components/ui/dialog.tsx | 210 +++++++----- resources/js/components/ui/dropdown.tsx | 142 ++++---- resources/js/components/ui/field.tsx | 116 ++++--- resources/js/components/ui/form.tsx | 14 +- resources/js/components/ui/heading.tsx | 52 +-- resources/js/components/ui/index.ts | 54 +-- resources/js/components/ui/keyboard.tsx | 37 +- resources/js/components/ui/link.tsx | 44 +-- resources/js/components/ui/list-box.tsx | 67 ++-- resources/js/components/ui/loader.tsx | 92 ++--- resources/js/components/ui/menu.tsx | 170 +++++----- resources/js/components/ui/modal.tsx | 138 ++++---- resources/js/components/ui/navbar.tsx | 316 ++++++++++-------- resources/js/components/ui/pagination.tsx | 198 ++++++----- resources/js/components/ui/popover.tsx | 170 +++++----- resources/js/components/ui/primitive.tsx | 42 +-- resources/js/components/ui/select.tsx | 129 ++++--- resources/js/components/ui/separator.tsx | 30 +- resources/js/components/ui/sheet.tsx | 159 ++++----- resources/js/components/ui/table.tsx | 186 ++++++----- resources/js/components/ui/text-field.tsx | 66 ++-- resources/js/components/ui/toast.tsx | 72 ++-- resources/js/components/ui/touch-target.tsx | 10 +- .../js/components/ui/visually-hidden.tsx | 14 +- resources/js/layouts/app-layout.tsx | 10 +- resources/js/layouts/app-navbar.tsx | 135 +++++--- resources/js/layouts/guest-layout.tsx | 22 +- resources/js/pages/about.tsx | 14 +- resources/js/pages/auth/confirm-password.tsx | 32 +- resources/js/pages/auth/forgot-password.tsx | 26 +- resources/js/pages/auth/login.tsx | 53 +-- resources/js/pages/auth/register.tsx | 49 +-- resources/js/pages/auth/reset-password.tsx | 44 ++- resources/js/pages/auth/verify-email.tsx | 29 +- resources/js/pages/dashboard.tsx | 14 +- resources/js/pages/home.tsx | 76 +++-- resources/js/pages/profile/edit.tsx | 22 +- .../profile/partials/delete-user-form.tsx | 49 +-- resources/js/pages/profile/partials/index.ts | 6 +- .../profile/partials/update-password-form.tsx | 56 ++-- .../update-profile-information-form.tsx | 50 +-- resources/js/ssr.tsx | 40 ++- resources/js/types/global.d.ts | 14 +- resources/js/types/index.ts | 28 +- resources/js/utils/classes.ts | 8 +- resources/js/utils/use-media-query.ts | 22 +- 66 files changed, 2248 insertions(+), 1902 deletions(-) delete mode 100644 .prettierignore delete mode 100644 .prettierrc create mode 100644 biome.json diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 74c039c..0000000 --- a/.prettierignore +++ /dev/null @@ -1,6 +0,0 @@ -**/vendor -**/public -**/bootstrap -**/.git -**/.svn -**/.hg diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 0e60db7..0000000 --- a/.prettierrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "tailwindFunctions": ["clsx", "tw", "tv", "cn", "twMerge", "tm", "cx"], - "plugins": ["prettier-plugin-tailwindcss", "prettier-plugin-organize-imports"], - "printWidth": 120, - "singleQuote": true, - "trailingComma": "none", - "tabWidth": 2, - "semi": true, - "useTabs": false, - "bracketSpacing": true, - "bracketSameLine": false -} diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..0f7859d --- /dev/null +++ b/biome.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "organizeImports": { + "enabled": true + }, + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": ["vendor", "node_modules", "resources/js/ziggy.js"] + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noNonNullAssertion": "off", + "noParameterAssign": "warn", + "useImportType": "error" + }, + "complexity": { + "useArrowFunction": "error" + }, + "a11y": { + "noSvgWithoutTitle": "off", + "useValidAnchor": "off", + "useSemanticElements": "off" + }, + "correctness": { + "useHookAtTopLevel": "error", + "noUnusedImports": "error", + "useExhaustiveDependencies": "off", + "noChildrenProp": "off" + }, + "suspicious": { + "noExplicitAny": "off", + "noArrayIndexKey": "off", + "noConsoleLog": "warn" + }, + "nursery": { + "useSortedClasses": { + "level": "error", + "fix": "safe", + "options": { + "attributes": ["classList"], + "functions": ["cn", "twJoin", "tv", "composeRenderProps", "composeTailwindRenderProps"] + } + } + } + }, + "ignore": [] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100, + "ignore": [] + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "arrowParentheses": "always", + "bracketSameLine": false, + "bracketSpacing": true, + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "semicolons": "asNeeded", + "trailingCommas": "all" + } + }, + "json": { + "formatter": { + "trailingCommas": "none" + } + } +} diff --git a/bun.lockb b/bun.lockb index 6a92df2297eb7cb64397268e78c202300279cc68..764e7d0ad1e3140b7c4af21386bdd3ba8a73b16f 100755 GIT binary patch delta 53902 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�H$=3;zs&EXV@C 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 ei=wTmlza (title ? title + ' / ' + appName : appName), - resolve: (name) => resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob('./pages/**/*.tsx')), + title: (title) => (title ? `${title} / ${appName}` : appName), + resolve: (name) => + resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob("./pages/**/*.tsx")), setup({ el, App, props }) { // @ts-expect-error - window.route = useRoute(Ziggy as any); + window.route = useRoute(Ziggy as any) const appElement = ( - ); + ) if (import.meta.env.SSR) { - hydrateRoot(el, appElement); - return; + hydrateRoot(el, appElement) + return } - createRoot(el).render(appElement); + createRoot(el).render(appElement) }, - progress: false -}); + progress: false, +}) diff --git a/resources/js/bootstrap.ts b/resources/js/bootstrap.ts index d66f224..0b333ed 100644 --- a/resources/js/bootstrap.ts +++ b/resources/js/bootstrap.ts @@ -1,5 +1,5 @@ -import axios from 'axios'; +import axios from "axios" -window.axios = axios; +window.axios = axios -window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; +window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest" diff --git a/resources/js/components/flash-message.tsx b/resources/js/components/flash-message.tsx index cc9c0f3..7ca9041 100644 --- a/resources/js/components/flash-message.tsx +++ b/resources/js/components/flash-message.tsx @@ -1,15 +1,15 @@ -import { FlashMessageData } from '@/types/index'; -import { usePage } from '@inertiajs/react'; -import { useEffect } from 'react'; -import { toast } from 'sonner'; -import { Toast } from 'ui'; +import type { FlashMessageData } from "@/types" +import { usePage } from "@inertiajs/react" +import { useEffect } from "react" +import { toast } from "sonner" +import { Toast } from "ui" export function FlashMessage() { - const { flash_message } = usePage<{ flash_message: FlashMessageData }>().props; + const { flash_message } = usePage<{ flash_message: FlashMessageData }>().props useEffect(() => { - if (flash_message && flash_message.message) { - (toast as any)[flash_message.type](flash_message.message); + if (flash_message?.message) { + ;(toast as any)[flash_message.type](flash_message.message) } - }, [flash_message]); - return ; + }, [flash_message]) + return } diff --git a/resources/js/components/footer.tsx b/resources/js/components/footer.tsx index 4dee121..0c6e5f4 100644 --- a/resources/js/components/footer.tsx +++ b/resources/js/components/footer.tsx @@ -1,35 +1,35 @@ -import { ThemeSwitcher } from '@/components/theme-switcher'; -import { SVGProps } from 'react'; -import { Button, Link, TextField } from 'ui'; +import { ThemeSwitcher } from "@/components/theme-switcher" +import type { SVGProps } from "react" +import { Button, Link, TextField } from "ui" const navigation = { solutions: [ - { name: 'Marketing', href: '#' }, - { name: 'Analytics', href: '#' }, - { name: 'Commerce', href: '#' }, - { name: 'Insights', href: '#' } + { name: "Marketing", href: "#" }, + { name: "Analytics", href: "#" }, + { name: "Commerce", href: "#" }, + { name: "Insights", href: "#" }, ], support: [ - { name: 'Pricing', href: '#' }, - { name: 'Documentation', href: '#' }, - { name: 'Guides', href: '#' }, - { name: 'API Status', href: '#' } + { name: "Pricing", href: "#" }, + { name: "Documentation", href: "#" }, + { name: "Guides", href: "#" }, + { name: "API Status", href: "#" }, ], company: [ - { name: 'About', href: '/about' }, - { name: 'Blog', href: '#' }, - { name: 'Jobs', href: '#' }, - { name: 'Press', href: '#' }, - { name: 'Partners', href: '#' } + { name: "About", href: "/about" }, + { name: "Blog", href: "#" }, + { name: "Jobs", href: "#" }, + { name: "Press", href: "#" }, + { name: "Partners", href: "#" }, ], legal: [ - { name: 'Privacy', href: '/privacy-policy' }, - { name: 'Terms', href: '/terms-of-service' } + { name: "Privacy", href: "/privacy-policy" }, + { name: "Terms", href: "/terms-of-service" }, ], social: [ { - name: 'Facebook', - href: '#', + name: "Facebook", + href: "#", icon: (props: SVGProps) => ( - ) + ), }, { - name: 'Instagram', - href: '#', + name: "Instagram", + href: "#", icon: (props: SVGProps) => ( - ) + ), }, { - name: 'Twitter', - href: '#', + name: "Twitter", + href: "#", icon: (props: SVGProps) => ( - ) + ), }, { - name: 'GitHub', - href: '#', + name: "GitHub", + href: "#", icon: (props: SVGProps) => ( - ) + ), }, { - name: 'YouTube', - href: '#', + name: "YouTube", + href: "#", icon: (props: SVGProps) => ( - ) - } - ] -}; + ), + }, + ], +} export function Footer() { return ( @@ -97,16 +97,19 @@ export function Footer() { -
+
-

Solutions

-
    +

    Solutions

    +
      {navigation.solutions.map((item) => (
    • - + {item.name}
    • @@ -114,11 +117,14 @@ export function Footer() {
-

Support

-
    +

    Support

    +
      {navigation.support.map((item) => (
    • - + {item.name}
    • @@ -128,11 +134,14 @@ export function Footer() {
-

Company

-
    +

    Company

    +
      {navigation.company.map((item) => (
    • - + {item.name}
    • @@ -140,11 +149,14 @@ export function Footer() {
-

Legal

-
    +

    Legal

    +
      {navigation.legal.map((item) => (
    • - + {item.name}
    • @@ -154,9 +166,11 @@ export function Footer() {
-
-

Subscribe to our newsletter

-

+

+

+ Subscribe to our newsletter +

+

The latest news, articles, and resources, sent to your inbox weekly.

@@ -170,7 +184,7 @@ export function Footer() { isRequired placeholder="Enter your email" /> -
+
@@ -178,7 +192,7 @@ export function Footer() {
-
+
{navigation.social.map((item) => ( ))}
-

- © 2020 Inertia.ts by{' '} - +

+ © 2020 Inertia.ts by{" "} + irsyadadl , Inc. All rights reserved. @@ -201,5 +219,5 @@ export function Footer() {

- ); + ) } diff --git a/resources/js/components/header.tsx b/resources/js/components/header.tsx index 4838301..1725095 100644 --- a/resources/js/components/header.tsx +++ b/resources/js/components/header.tsx @@ -1,16 +1,16 @@ -import { cn } from '@/utils/classes'; -import * as React from 'react'; -import { Container } from 'ui'; +import { cn } from "@/utils/classes" +import * as React from "react" +import { Container } from "ui" const Header = React.forwardRef>( ({ className, ...props }, ref) => ( -
+
-

{props.title}

+

{props.title}

- ) -); -Header.displayName = 'Header'; + ), +) +Header.displayName = "Header" -export { Header }; +export { Header } diff --git a/resources/js/components/input-error.tsx b/resources/js/components/input-error.tsx index 30234b1..cd31c69 100644 --- a/resources/js/components/input-error.tsx +++ b/resources/js/components/input-error.tsx @@ -1,15 +1,15 @@ -import { cn } from '@/utils/classes'; -import { HTMLAttributes } from 'react'; -import { Description } from 'ui'; +import { cn } from "@/utils/classes" +import type { HTMLAttributes } from "react" +import { Description } from "ui" export function InputError({ message, - className = '', + className = "", ...props }: HTMLAttributes & { message?: string }) { return message ? ( - + {message} - ) : null; + ) : null } diff --git a/resources/js/components/logo.tsx b/resources/js/components/logo.tsx index 9fb9ffd..cb75b2b 100644 --- a/resources/js/components/logo.tsx +++ b/resources/js/components/logo.tsx @@ -1,7 +1,7 @@ -import { cn } from '@/utils/classes'; -import { IconBrandLaravel } from 'justd-icons'; -import React from 'react'; +import { cn } from "@/utils/classes" +import { IconBrandLaravel } from "justd-icons" +import type React from "react" export function Logo({ className, ...props }: React.SVGProps) { - return ; + return } diff --git a/resources/js/components/providers.tsx b/resources/js/components/providers.tsx index dc5b4be..cb825f4 100644 --- a/resources/js/components/providers.tsx +++ b/resources/js/components/providers.tsx @@ -1,12 +1,12 @@ -import { ThemeProvider } from '@/components/theme-provider'; -import { router } from '@inertiajs/react'; -import React from 'react'; -import { RouterProvider } from 'react-aria-components'; +import { ThemeProvider } from "@/components/theme-provider" +import { router } from "@inertiajs/react" +import type React from "react" +import { RouterProvider } from "react-aria-components" export function Providers({ children }: { children: React.ReactNode }) { return ( router.visit(to, options as any)}> {children} - ); + ) } diff --git a/resources/js/components/theme-provider.tsx b/resources/js/components/theme-provider.tsx index 11c078e..0c84eb2 100644 --- a/resources/js/components/theme-provider.tsx +++ b/resources/js/components/theme-provider.tsx @@ -1,71 +1,75 @@ -import * as React from 'react'; +import * as React from "react" -type Theme = 'dark' | 'light' | 'system'; +type Theme = "dark" | "light" | "system" type ThemeProviderProps = { - children: React.ReactNode; - defaultTheme?: Theme; - storageKey?: string; -}; + children: React.ReactNode + defaultTheme?: Theme + storageKey?: string +} type ThemeProviderState = { - theme: Theme; - setTheme: (theme: Theme) => void; -}; + theme: Theme + setTheme: (theme: Theme) => void +} const initialState: ThemeProviderState = { - theme: 'system', - setTheme: () => null -}; + theme: "system", + setTheme: () => null, +} -const ThemeProviderContext = React.createContext(initialState); +const ThemeProviderContext = React.createContext(initialState) export function ThemeProvider({ children, - defaultTheme = 'system', - storageKey = 'vite-ui-theme', + defaultTheme = "system", + storageKey = "vite-ui-theme", ...props }: ThemeProviderProps) { - const [theme, setTheme] = React.useState(() => (localStorage.getItem(storageKey) as Theme) || defaultTheme); + const [theme, setTheme] = React.useState( + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme, + ) React.useEffect(() => { - const root = window.document.documentElement; + const root = window.document.documentElement - root.classList.remove('light', 'dark'); + root.classList.remove("light", "dark") - if (theme === 'system') { - const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + if (theme === "system") { + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light" - root.classList.add(systemTheme); - return; + root.classList.add(systemTheme) + return } - if (theme === 'light' || theme === 'dark') { - root.classList.add(theme); + if (theme === "light" || theme === "dark") { + root.classList.add(theme) } - }, [theme]); + }, [theme]) const value = { theme, setTheme: (newTheme: Theme) => { - if (newTheme === 'light' || newTheme === 'dark' || newTheme === 'system') { - localStorage.setItem(storageKey, newTheme); - setTheme(newTheme); + if (newTheme === "light" || newTheme === "dark" || newTheme === "system") { + localStorage.setItem(storageKey, newTheme) + setTheme(newTheme) } - } - }; + }, + } return ( {children} - ); + ) } export const useTheme = () => { - const context = React.useContext(ThemeProviderContext); + const context = React.useContext(ThemeProviderContext) - if (context === undefined) throw new Error('useTheme must be used within a ThemeProvider'); + if (context === undefined) throw new Error("useTheme must be used within a ThemeProvider") - return context; -}; + return context +} diff --git a/resources/js/components/theme-switcher.tsx b/resources/js/components/theme-switcher.tsx index 1e4034f..b367f04 100644 --- a/resources/js/components/theme-switcher.tsx +++ b/resources/js/components/theme-switcher.tsx @@ -1,31 +1,31 @@ -import { useTheme } from '@/components/theme-provider'; -import { IconDeviceDesktop2, IconMoon, IconSun } from 'justd-icons'; -import { Button, composeTailwindRenderProps } from 'ui'; +import { useTheme } from "@/components/theme-provider" +import { IconDeviceDesktop2, IconMoon, IconSun } from "justd-icons" +import { Button, composeTailwindRenderProps } from "ui" export function ThemeSwitcher({ - shape = 'square', - appearance = 'plain', + shape = "square", + appearance = "plain", className, ...props }: React.ComponentProps) { - const { theme, setTheme } = useTheme(); + const { theme, setTheme } = useTheme() const toggleTheme = () => { - const nextTheme = theme === 'light' ? 'dark' : theme === 'dark' ? 'system' : 'light'; - setTheme(nextTheme); - }; + const nextTheme = theme === "light" ? "dark" : theme === "dark" ? "system" : "light" + setTheme(nextTheme) + } return ( - ); + ) } diff --git a/resources/js/components/ui/avatar.tsx b/resources/js/components/ui/avatar.tsx index 70da228..d1116a7 100644 --- a/resources/js/components/ui/avatar.tsx +++ b/resources/js/components/ui/avatar.tsx @@ -1,59 +1,66 @@ -import { type VariantProps, tv } from 'tailwind-variants'; +import { type VariantProps, tv } from "tailwind-variants" const avatar = tv({ base: [ - 'inline-grid shrink-0 align-middle [--avatar-radius:20%] [--ring-opacity:20%] *:col-start-1 *:row-start-1', - '-outline-offset-1 outline-1 outline-fg/(--ring-opacity)' + "inline-grid shrink-0 align-middle [--avatar-radius:20%] [--ring-opacity:20%] *:col-start-1 *:row-start-1", + "-outline-offset-1 outline-1 outline-fg/(--ring-opacity)", ], variants: { shape: { - square: 'rounded-(--avatar-radius) *:rounded-(--avatar-radius)', - circle: 'rounded-full *:rounded-full' + square: "rounded-(--avatar-radius) *:rounded-(--avatar-radius)", + circle: "rounded-full *:rounded-full", }, size: { - 'extra-small': 'size-5 *:size-5', - small: 'size-6 *:size-6', - medium: 'size-8 *:size-8', - large: 'size-10 *:size-10', - 'extra-large': 'size-12 *:size-12' - } - } -}); + "extra-small": "size-5 *:size-5", + small: "size-6 *:size-6", + medium: "size-8 *:size-8", + large: "size-10 *:size-10", + "extra-large": "size-12 *:size-12", + }, + }, +}) interface AvatarProps extends VariantProps { - src?: string | null; - initials?: string; - alt?: string; - className?: string; + src?: string | null + initials?: string + alt?: string + className?: string } const Avatar = ({ src = null, - shape = 'circle', - size = 'medium', + shape = "circle", + size = "medium", initials, - alt = '', + alt = "", className, ...props -}: AvatarProps & React.ComponentPropsWithoutRef<'span'>) => { +}: AvatarProps & React.ComponentPropsWithoutRef<"span">) => { return ( {initials && ( {alt && {alt}} - + {initials} )} {src && {alt}} - ); -}; + ) +} -export { Avatar }; -export type { AvatarProps }; +export { Avatar } +export type { AvatarProps } diff --git a/resources/js/components/ui/button.tsx b/resources/js/components/ui/button.tsx index 06f1811..093ff3f 100644 --- a/resources/js/components/ui/button.tsx +++ b/resources/js/components/ui/button.tsx @@ -1,92 +1,93 @@ import { Button as ButtonPrimitive, type ButtonProps as ButtonPrimitiveProps, - composeRenderProps -} from 'react-aria-components'; -import { tv } from 'tailwind-variants'; + composeRenderProps, +} from "react-aria-components" +import { tv } from "tailwind-variants" -import { focusButtonStyles } from './primitive'; +import { focusButtonStyles } from "./primitive" const buttonStyles = tv({ extend: focusButtonStyles, base: [ - 'kbt32x relative inline-flex items-center justify-center gap-x-2 border font-medium', - 'forced-colors:[--btn-icon:ButtonText] forced-colors:data-hovered:[--btn-icon:ButtonText]', - '*:data-[slot=icon]:-mx-0.5 data-hovered:*:data-[slot=icon]:text-current/90 data-pressed:*:data-[slot=icon]:text-current *:data-[slot=icon]:my-1 *:data-[slot=icon]:size-4 *:data-[slot=icon]:shrink-0 *:data-[slot=icon]:text-current/60 *:data-[slot=icon]:transition', - '*:data-[slot=avatar]:-mx-0.5 *:data-[slot=avatar]:my-1 *:data-[slot=avatar]:*:size-4 *:data-[slot=avatar]:size-4 *:data-[slot=avatar]:shrink-0' + "kbt32x relative inline-flex items-center justify-center gap-x-2 border font-medium", + "forced-colors:[--btn-icon:ButtonText] forced-colors:data-hovered:[--btn-icon:ButtonText]", + "*:data-[slot=icon]:-mx-0.5 data-hovered:*:data-[slot=icon]:text-current/90 data-pressed:*:data-[slot=icon]:text-current *:data-[slot=icon]:my-1 *:data-[slot=icon]:size-4 *:data-[slot=icon]:shrink-0 *:data-[slot=icon]:text-current/60 *:data-[slot=icon]:transition", + "*:data-[slot=avatar]:-mx-0.5 *:data-[slot=avatar]:my-1 *:data-[slot=avatar]:*:size-4 *:data-[slot=avatar]:size-4 *:data-[slot=avatar]:shrink-0", ], variants: { intent: { primary: [ - 'outline-primary [--btn-bg:theme(--color-primary/95%)] [--btn-border:var(--color-primary)] [--btn-fg:var(--color-primary-fg)] dark:[--btn-bg:theme(--color-primary/90%)]', - '[--btn-bg-hovered:theme(--color-primary/87%)] [--btn-border-hovered:theme(--color-primary/87%)] dark:[--btn-bg-hovered:theme(--color-primary)] dark:[--btn-border-hovered:theme(--color-primary)]', - 'inset-shadow-primary-fg/20 data-hovered:inset-shadow-primary-fg/25 data-pressed:inset-shadow-primary-fg/20' + "outline-primary [--btn-bg:theme(--color-primary/95%)] [--btn-border:var(--color-primary)] [--btn-fg:var(--color-primary-fg)] dark:[--btn-bg:theme(--color-primary/90%)]", + "[--btn-bg-hovered:theme(--color-primary/87%)] [--btn-border-hovered:theme(--color-primary/87%)] dark:[--btn-bg-hovered:theme(--color-primary)] dark:[--btn-border-hovered:theme(--color-primary)]", + "inset-shadow-primary-fg/20 data-hovered:inset-shadow-primary-fg/25 data-pressed:inset-shadow-primary-fg/20", ], secondary: [ - '[--btn-bg:theme(--color-secondary/95%)] [--btn-border:theme(--color-secondary-fg/10%)] [--btn-fg:var(--color-secondary-fg)] dark:[--btn-bg:theme(--color-secondary/85%)] dark:[--btn-border:theme(--color-secondary-fg/7%)]', - '[--btn-bg-hovered:color-mix(in_oklab,var(--color-secondary)_60%,white_20%)] dark:[--btn-bg-hovered:color-mix(in_oklab,var(--color-secondary)_96%,white_4%)]', - 'inset-shadow-white/15 data-hovered:inset-shadow-white/20 data-pressed:inset-shadow-white/15' + "[--btn-bg:theme(--color-secondary/95%)] [--btn-border:theme(--color-secondary-fg/10%)] [--btn-fg:var(--color-secondary-fg)] dark:[--btn-bg:theme(--color-secondary/85%)] dark:[--btn-border:theme(--color-secondary-fg/7%)]", + "[--btn-bg-hovered:color-mix(in_oklab,var(--color-secondary)_60%,white_20%)] dark:[--btn-bg-hovered:color-mix(in_oklab,var(--color-secondary)_96%,white_4%)]", + "inset-shadow-white/15 data-hovered:inset-shadow-white/20 data-pressed:inset-shadow-white/15", ], warning: [ - '[--btn-warning:theme(--color-warning/97%)]', - '[--btn-warning-hovered:color-mix(in_oklab,var(--color-warning)_85%,white_15%)]', - 'dark:[--btn-warning-hovered:color-mix(in_oklab,var(--color-warning)_90%,white_10%)]', - 'outline-warning [--btn-bg:var(--btn-warning)] [--btn-border:var(--btn-warning)] [--btn-fg:var(--color-warning-fg)]', - '[--btn-bg-hovered:var(--btn-warning-hovered)] [--btn-border-hovered:var(--btn-warning-hovered)]', - 'inset-shadow-white/25 data-hovered:inset-shadow-white/30 data-pressed:inset-shadow-white/25' + "[--btn-warning:theme(--color-warning/97%)]", + "[--btn-warning-hovered:color-mix(in_oklab,var(--color-warning)_85%,white_15%)]", + "dark:[--btn-warning-hovered:color-mix(in_oklab,var(--color-warning)_90%,white_10%)]", + "outline-warning [--btn-bg:var(--btn-warning)] [--btn-border:var(--btn-warning)] [--btn-fg:var(--color-warning-fg)]", + "[--btn-bg-hovered:var(--btn-warning-hovered)] [--btn-border-hovered:var(--btn-warning-hovered)]", + "inset-shadow-white/25 data-hovered:inset-shadow-white/30 data-pressed:inset-shadow-white/25", ], danger: [ - 'outline-danger [--btn-bg:theme(--color-danger/95%)] [--btn-border:var(--color-danger)] [--btn-fg:var(--color-danger-fg)] dark:[--btn-bg:var(--color-danger)]', - '[--btn-danger-hovered:color-mix(in_oklab,var(--color-danger)_93%,white_7%)]', - 'dark:[--btn-danger-hovered:color-mix(in_oklab,var(--color-danger)_96%,white_4%)]', - '[--btn-bg-hovered:var(--btn-danger-hovered)] [--btn-border-hovered:var(--btn-danger-hovered)]', - 'inset-shadow-danger-fg/30 data-hovered:inset-shadow-danger-fg/35 data-pressed:inset-shadow-danger-fg/30' - ] + "outline-danger [--btn-bg:theme(--color-danger/95%)] [--btn-border:var(--color-danger)] [--btn-fg:var(--color-danger-fg)] dark:[--btn-bg:var(--color-danger)]", + "[--btn-danger-hovered:color-mix(in_oklab,var(--color-danger)_93%,white_7%)]", + "dark:[--btn-danger-hovered:color-mix(in_oklab,var(--color-danger)_96%,white_4%)]", + "[--btn-bg-hovered:var(--btn-danger-hovered)] [--btn-border-hovered:var(--btn-danger-hovered)]", + "inset-shadow-danger-fg/30 data-hovered:inset-shadow-danger-fg/35 data-pressed:inset-shadow-danger-fg/30", + ], }, appearance: { solid: [ - 'inset-ring-0 dark:inset-ring dark:border-0', - 'inset-ring-(--btn-border) inset-shadow-2xs border-(--btn-border) bg-(--btn-bg) text-(--btn-fg)', - 'data-hovered:bg-(--btn-bg-hovered) data-hovered:ring-(--btn-border-hovered)', - 'data-pressed:border-(--btn-border) data-pressed:bg-(--btn-bg)' + "inset-ring-0 dark:inset-ring dark:border-0", + "inset-ring-(--btn-border) inset-shadow-2xs border-(--btn-border) bg-(--btn-bg) text-(--btn-fg)", + "data-hovered:bg-(--btn-bg-hovered) data-hovered:ring-(--btn-border-hovered)", + "data-pressed:border-(--btn-border) data-pressed:bg-(--btn-bg)", ], - outline: ['border data-hovered:bg-secondary data-pressed:bg-secondary'], - plain: ['border-transparent data-hovered:bg-secondary data-pressed:bg-secondary'] + outline: ["border data-hovered:bg-secondary data-pressed:bg-secondary"], + plain: ["border-transparent data-hovered:bg-secondary data-pressed:bg-secondary"], }, size: { - 'extra-small': - 'h-8 px-[calc(var(--spacing)*2.7)] text-xs/4 **:data-[slot=avatar]:*:size-3.5 **:data-[slot=avatar]:size-3.5 **:data-[slot=icon]:size-3 lg:text-[0.800rem]/4', - small: 'h-9 px-3.5 text-sm/5 sm:text-sm/5', - medium: 'h-10 px-4 text-base sm:text-sm/6', - large: 'h-11 px-4.5 text-base *:data-[slot=icon]:mx-[-1.5px] sm:*:data-[slot=icon]:size-5 lg:text-base/7', - 'square-petite': 'size-9 shrink-0' + "extra-small": + "h-8 px-[calc(var(--spacing)*2.7)] text-xs/4 **:data-[slot=avatar]:*:size-3.5 **:data-[slot=avatar]:size-3.5 **:data-[slot=icon]:size-3 lg:text-[0.800rem]/4", + small: "h-9 px-3.5 text-sm/5 sm:text-sm/5", + medium: "h-10 px-4 text-base sm:text-sm/6", + large: + "h-11 px-4.5 text-base *:data-[slot=icon]:mx-[-1.5px] sm:*:data-[slot=icon]:size-5 lg:text-base/7", + "square-petite": "size-9 shrink-0", }, shape: { - square: 'rounded-lg', - circle: 'rounded-full' + square: "rounded-lg", + circle: "rounded-full", }, isDisabled: { - false: 'cursor-pointer forced-colors:data-disabled:text-[GrayText]', - true: 'inset-shadow-none cursor-default border-0 opacity-50 ring-0 dark:inset-ring-0 forced-colors:data-disabled:text-[GrayText]' + false: "cursor-pointer forced-colors:data-disabled:text-[GrayText]", + true: "inset-shadow-none cursor-default border-0 opacity-50 ring-0 dark:inset-ring-0 forced-colors:data-disabled:text-[GrayText]", }, isPending: { - true: 'cursor-default opacity-50' - } + true: "cursor-default opacity-50", + }, }, defaultVariants: { - intent: 'primary', - appearance: 'solid', - size: 'medium', - shape: 'square' - } -}); + intent: "primary", + appearance: "solid", + size: "medium", + shape: "square", + }, +}) interface ButtonProps extends ButtonPrimitiveProps { - intent?: 'primary' | 'secondary' | 'danger' | 'warning'; - size?: 'medium' | 'large' | 'square-petite' | 'extra-small' | 'small'; - shape?: 'square' | 'circle'; - appearance?: 'solid' | 'outline' | 'plain'; - ref?: React.Ref; + intent?: "primary" | "secondary" | "danger" | "warning" + size?: "medium" | "large" | "square-petite" | "extra-small" | "small" + shape?: "square" | "circle" + appearance?: "solid" | "outline" | "plain" + ref?: React.Ref } const Button = ({ className, intent, appearance, size, shape, ref, ...props }: ButtonProps) => { @@ -101,14 +102,16 @@ const Button = ({ className, intent, appearance, size, shape, ref, ...props }: B appearance, size, shape, - className - }) + className, + }), )} > - {(values) => <>{typeof props.children === 'function' ? props.children(values) : props.children}} + {(values) => ( + <>{typeof props.children === "function" ? props.children(values) : props.children} + )} - ); -}; + ) +} -export { Button, buttonStyles }; -export type { ButtonProps }; +export { Button, buttonStyles } +export type { ButtonProps } diff --git a/resources/js/components/ui/card.tsx b/resources/js/components/ui/card.tsx index cd7157d..ccb2586 100644 --- a/resources/js/components/ui/card.tsx +++ b/resources/js/components/ui/card.tsx @@ -1,68 +1,85 @@ -import { cn } from '@/utils/classes'; -import { Heading } from './heading'; +import { cn } from "@/utils/classes" +import { Heading } from "./heading" const Card = ({ className, ...props }: React.HTMLAttributes) => { return (
- ); -}; + ) +} interface HeaderProps extends React.HTMLAttributes { - title?: string; - description?: string; + title?: string + description?: string } const CardHeader = ({ className, title, description, children, ...props }: HeaderProps) => ( -
+
{title && {title}} {description && {description}} - {!title && typeof children === 'string' ? {children} : children} + {!title && typeof children === "string" ? {children} : children}
-); +) const CardTitle = ({ className, level = 3, ...props }: React.ComponentProps) => { return ( - ); -}; + ) +} const CardDescription = ({ className, ...props }: React.HTMLAttributes) => { - return
; -}; + return ( +
+ ) +} const CardContent = ({ className, ...props }: React.HTMLAttributes) => { return (
- ); -}; + ) +} const CardFooter = ({ className, ...props }: React.HTMLAttributes) => { - return
; -}; + return ( +
+ ) +} -Card.Content = CardContent; -Card.Description = CardDescription; -Card.Footer = CardFooter; -Card.Header = CardHeader; -Card.Title = CardTitle; +Card.Content = CardContent +Card.Description = CardDescription +Card.Footer = CardFooter +Card.Header = CardHeader +Card.Title = CardTitle -export { Card }; +export { Card } diff --git a/resources/js/components/ui/checkbox.tsx b/resources/js/components/ui/checkbox.tsx index a53cb56..c6b0fad 100644 --- a/resources/js/components/ui/checkbox.tsx +++ b/resources/js/components/ui/checkbox.tsx @@ -1,71 +1,74 @@ -import { IconCheck, IconMinus } from 'justd-icons'; +import { IconCheck, IconMinus } from "justd-icons" import type { CheckboxGroupProps as CheckboxGroupPrimitiveProps, CheckboxProps as CheckboxPrimitiveProps, - ValidationResult -} from 'react-aria-components'; + ValidationResult, +} from "react-aria-components" import { CheckboxGroup as CheckboxGroupPrimitive, Checkbox as CheckboxPrimitive, - composeRenderProps -} from 'react-aria-components'; -import { tv } from 'tailwind-variants'; + composeRenderProps, +} from "react-aria-components" +import { tv } from "tailwind-variants" -import { cn } from '@/utils/classes'; -import { Description, FieldError, Label } from './field'; -import { composeTailwindRenderProps } from './primitive'; +import { cn } from "@/utils/classes" +import { Description, FieldError, Label } from "./field" +import { composeTailwindRenderProps } from "./primitive" interface CheckboxGroupProps extends CheckboxGroupPrimitiveProps { - label?: string; - description?: string; - errorMessage?: string | ((validation: ValidationResult) => string); + label?: string + description?: string + errorMessage?: string | ((validation: ValidationResult) => string) } const CheckboxGroup = ({ className, ...props }: CheckboxGroupProps) => { return ( - + {props.label && } {props.children as React.ReactNode} {props.description && {props.description}} {props.errorMessage} - ); -}; + ) +} const checkboxStyles = tv({ - base: 'group flex items-center gap-2 text-sm transition', + base: "group flex items-center gap-2 text-sm transition", variants: { isDisabled: { - true: 'opacity-50' - } - } -}); + true: "opacity-50", + }, + }, +}) const boxStyles = tv({ - base: 'flex size-4 shrink-0 items-center justify-center rounded border border-input text-bg transition *:data-[slot=icon]:size-3', + base: "flex size-4 shrink-0 items-center justify-center rounded border border-input text-bg transition *:data-[slot=icon]:size-3", variants: { isSelected: { - false: 'bg-muted', + false: "bg-muted", true: [ - 'border-primary bg-primary text-primary-fg', - 'group-data-invalid:border-danger/70 group-data-invalid:bg-danger group-data-invalid:text-danger-fg' - ] + "border-primary bg-primary text-primary-fg", + "group-data-invalid:border-danger/70 group-data-invalid:bg-danger group-data-invalid:text-danger-fg", + ], }, isFocused: { true: [ - 'border-primary ring-4 ring-primary/20', - 'group-data-invalid:border-danger/70 group-data-invalid:text-danger-fg group-data-invalid:ring-danger/20' - ] + "border-primary ring-4 ring-primary/20", + "group-data-invalid:border-danger/70 group-data-invalid:text-danger-fg group-data-invalid:ring-danger/20", + ], }, isInvalid: { - true: 'border-danger/70 bg-danger/20 text-danger-fg ring-danger/20' - } - } -}); + true: "border-danger/70 bg-danger/20 text-danger-fg ring-danger/20", + }, + }, +}) interface CheckboxProps extends CheckboxPrimitiveProps { - description?: string; - label?: string; + description?: string + label?: string } const Checkbox = ({ className, ...props }: CheckboxProps) => { @@ -73,15 +76,15 @@ const Checkbox = ({ className, ...props }: CheckboxProps) => { - checkboxStyles({ ...renderProps, className }) + checkboxStyles({ ...renderProps, className }), )} > {({ isSelected, isIndeterminate, ...renderProps }) => ( -
+
{isIndeterminate ? : isSelected ? : null} @@ -90,7 +93,9 @@ const Checkbox = ({ className, ...props }: CheckboxProps) => {
<> {props.label ? ( - + ) : ( (props.children as React.ReactNode) )} @@ -100,8 +105,8 @@ const Checkbox = ({ className, ...props }: CheckboxProps) => {
)} - ); -}; + ) +} -export { Checkbox, CheckboxGroup }; -export type { CheckboxGroupProps, CheckboxProps }; +export { Checkbox, CheckboxGroup } +export type { CheckboxGroupProps, CheckboxProps } diff --git a/resources/js/components/ui/container.tsx b/resources/js/components/ui/container.tsx index c8caf97..ccc8572 100644 --- a/resources/js/components/ui/container.tsx +++ b/resources/js/components/ui/container.tsx @@ -1,26 +1,26 @@ -import { tv } from 'tailwind-variants'; +import { tv } from "tailwind-variants" const containerStyles = tv({ - base: '@container mx-auto w-full max-w-7xl lg:max-w-(--breakpoint-xl) 2xl:max-w-(--breakpoint-2xl)', + base: "@container mx-auto w-full max-w-7xl lg:max-w-(--breakpoint-xl) 2xl:max-w-(--breakpoint-2xl)", variants: { intent: { - constrained: 'sm:px-6 lg:px-8', - 'padded-content': 'px-4 sm:px-6 lg:px-8' - } + constrained: "sm:px-6 lg:px-8", + "padded-content": "px-4 sm:px-6 lg:px-8", + }, }, defaultVariants: { - intent: 'padded-content' - } -}); + intent: "padded-content", + }, +}) interface ContainerProps extends React.HTMLAttributes { - intent?: 'constrained' | 'padded-content'; - ref?: React.Ref; + intent?: "constrained" | "padded-content" + ref?: React.Ref } const Container = ({ className, intent, ref, ...props }: ContainerProps) => (
-); +) -export { Container }; -export type { ContainerProps }; +export { Container } +export type { ContainerProps } diff --git a/resources/js/components/ui/dialog.tsx b/resources/js/components/ui/dialog.tsx index 70bfa1e..f3ed909 100644 --- a/resources/js/components/ui/dialog.tsx +++ b/resources/js/components/ui/dialog.tsx @@ -1,145 +1,171 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useRef } from "react" -import { IconX } from 'justd-icons'; -import type { HeadingProps } from 'react-aria-components'; -import { Button as ButtonPrimitive, Dialog as DialogPrimitive, Heading, Text } from 'react-aria-components'; -import { tv } from 'tailwind-variants'; +import { IconX } from "justd-icons" +import type { HeadingProps } from "react-aria-components" +import { + Button as ButtonPrimitive, + Dialog as DialogPrimitive, + Heading, + Text, +} from "react-aria-components" +import { tv } from "tailwind-variants" -import { useMediaQuery } from '@/utils/use-media-query'; -import { Button, type ButtonProps } from './button'; +import { useMediaQuery } from "@/utils/use-media-query" +import { Button, type ButtonProps } from "./button" const dialogStyles = tv({ slots: { root: [ - 'peer/dialog group/dialog relative flex max-h-[inherit] flex-col overflow-hidden outline-hidden [scrollbar-width:thin] [&::-webkit-scrollbar]:size-0.5' + "peer/dialog group/dialog relative flex max-h-[inherit] flex-col overflow-hidden outline-hidden [scrollbar-width:thin] [&::-webkit-scrollbar]:size-0.5", ], header: - 'relative flex flex-col gap-0.5 p-4 sm:gap-1 sm:p-6 [&[data-slot=dialog-header]:has(+[data-slot=dialog-footer])]:pb-0', - description: 'text-muted-fg text-sm', + "relative flex flex-col gap-0.5 p-4 sm:gap-1 sm:p-6 [&[data-slot=dialog-header]:has(+[data-slot=dialog-footer])]:pb-0", + description: "text-muted-fg text-sm", body: [ - 'isolate flex flex-1 flex-col overflow-auto px-4 py-1 sm:px-6', - 'max-h-[calc(var(--visual-viewport-height)-var(--visual-viewport-vertical-padding)-var(--dialog-header-height,0px)-var(--dialog-footer-height,0px))]' + "isolate flex flex-1 flex-col overflow-auto px-4 py-1 sm:px-6", + "max-h-[calc(var(--visual-viewport-height)-var(--visual-viewport-vertical-padding)-var(--dialog-header-height,0px)-var(--dialog-footer-height,0px))]", ], - footer: 'isolate mt-auto flex flex-col-reverse justify-between gap-3 p-4 pt-3 sm:flex-row sm:p-6 sm:pt-5', + footer: + "isolate mt-auto flex flex-col-reverse justify-between gap-3 p-4 pt-3 sm:flex-row sm:p-6 sm:pt-5", closeIndicator: - 'close absolute top-1 right-1 z-50 grid size-8 place-content-center rounded-xl data-focused:bg-secondary data-hovered:bg-secondary data-focused:outline-hidden data-focus-visible:ring-1 data-focus-visible:ring-primary sm:top-2 sm:right-2 sm:size-7 sm:rounded-md' - } -}); - -const { root, header, description, body, footer, closeIndicator } = dialogStyles(); - -const Dialog = ({ role = 'dialog', className, ...props }: React.ComponentProps) => { - return ; -}; + "close absolute top-1 right-1 z-50 grid size-8 place-content-center rounded-xl data-focused:bg-secondary data-hovered:bg-secondary data-focused:outline-hidden data-focus-visible:ring-1 data-focus-visible:ring-primary sm:top-2 sm:right-2 sm:size-7 sm:rounded-md", + }, +}) + +const { root, header, description, body, footer, closeIndicator } = dialogStyles() + +const Dialog = ({ + role = "dialog", + className, + ...props +}: React.ComponentProps) => { + return +} -const Trigger = (props: React.ComponentProps) => ; +const Trigger = (props: React.ComponentProps) => ( + +) type DialogHeaderProps = React.HTMLAttributes & { - title?: string; - description?: string; -}; + title?: string + description?: string +} const Header = ({ className, ...props }: DialogHeaderProps) => { - const headerRef = useRef(null); + const headerRef = useRef(null) useEffect(() => { - const header = headerRef.current; + const header = headerRef.current if (!header) { - return; + return } const observer = new ResizeObserver((entries) => { for (const entry of entries) { - header.parentElement?.style.setProperty('--dialog-header-height', `${entry.target.clientHeight}px`); + header.parentElement?.style.setProperty( + "--dialog-header-height", + `${entry.target.clientHeight}px`, + ) } - }); + }) - observer.observe(header); - return () => observer.unobserve(header); - }, []); + observer.observe(header) + return () => observer.unobserve(header) + }, []) return (
{props.title && {props.title}} {props.description && {props.description}} - {!props.title && typeof props.children === 'string' ? : props.children} + {!props.title && typeof props.children === "string" ? <Title {...props} /> : props.children} </div> - ); -}; + ) +} const titleStyles = tv({ - base: 'flex flex-1 items-center text-fg', + base: "flex flex-1 items-center text-fg", variants: { level: { - 1: 'font-semibold text-lg sm:text-xl', - 2: 'font-semibold text-lg sm:text-xl', - 3: 'font-semibold text-base sm:text-lg', - 4: 'font-semibold text-base' - } - } -}); - -interface DialogTitleProps extends Omit<HeadingProps, 'level'> { - level?: 1 | 2 | 3 | 4; - ref?: React.Ref<HTMLHeadingElement>; + 1: "font-semibold text-lg sm:text-xl", + 2: "font-semibold text-lg sm:text-xl", + 3: "font-semibold text-base sm:text-lg", + 4: "font-semibold text-base", + }, + }, +}) + +interface DialogTitleProps extends Omit<HeadingProps, "level"> { + level?: 1 | 2 | 3 | 4 + ref?: React.Ref<HTMLHeadingElement> } const Title = ({ level = 2, className, ref, ...props }: DialogTitleProps) => ( - <Heading slot="title" level={level} ref={ref} className={titleStyles({ level, className })} {...props} /> -); - -type DialogDescriptionProps = React.ComponentProps<'div'>; + <Heading + slot="title" + level={level} + ref={ref} + className={titleStyles({ level, className })} + {...props} + /> +) + +type DialogDescriptionProps = React.ComponentProps<"div"> const Description = ({ className, ref, ...props }: DialogDescriptionProps) => ( <Text slot="description" className={description({ className })} ref={ref} {...props} /> -); +) -type DialogBodyProps = React.ComponentProps<'div'>; +type DialogBodyProps = React.ComponentProps<"div"> const Body = ({ className, ref, ...props }: DialogBodyProps) => ( <div data-slot="dialog-body" ref={ref} className={body({ className })} {...props} /> -); +) -type DialogFooterProps = React.ComponentProps<'div'>; +type DialogFooterProps = React.ComponentProps<"div"> const Footer = ({ className, ...props }: DialogFooterProps) => { - const footerRef = useRef<HTMLDivElement>(null); + const footerRef = useRef<HTMLDivElement>(null) useEffect(() => { - const footer = footerRef.current; + const footer = footerRef.current if (!footer) { - return; + return } const observer = new ResizeObserver((entries) => { for (const entry of entries) { - footer.parentElement?.style.setProperty('--dialog-footer-height', `${entry.target.clientHeight}px`); + footer.parentElement?.style.setProperty( + "--dialog-footer-height", + `${entry.target.clientHeight}px`, + ) } - }); + }) - observer.observe(footer); + observer.observe(footer) return () => { - observer.unobserve(footer); - }; - }, []); - return <div ref={footerRef} data-slot="dialog-footer" className={footer({ className })} {...props} />; -}; + observer.unobserve(footer) + } + }, []) + return ( + <div ref={footerRef} data-slot="dialog-footer" className={footer({ className })} {...props} /> + ) +} -const Close = ({ className, appearance = 'outline', ref, ...props }: ButtonProps) => { - return <Button slot="close" className={className} ref={ref} appearance={appearance} {...props} />; -}; +const Close = ({ className, appearance = "outline", ref, ...props }: ButtonProps) => { + return <Button slot="close" className={className} ref={ref} appearance={appearance} {...props} /> +} interface CloseButtonIndicatorProps extends ButtonProps { - className?: string; - isDismissable?: boolean | undefined; + className?: string + isDismissable?: boolean | undefined } const CloseIndicator = ({ className, ...props }: CloseButtonIndicatorProps) => { - const isMobile = useMediaQuery('(max-width: 600px)'); - const buttonRef = useRef<HTMLButtonElement>(null); + const isMobile = useMediaQuery("(max-width: 600px)") + const buttonRef = useRef<HTMLButtonElement>(null) useEffect(() => { if (isMobile && buttonRef.current) { - buttonRef.current.focus(); + buttonRef.current.focus() } - }, [isMobile]); + }, [isMobile]) return props.isDismissable ? ( <ButtonPrimitive ref={buttonRef} @@ -150,24 +176,24 @@ const CloseIndicator = ({ className, ...props }: CloseButtonIndicatorProps) => { > <IconX className="size-4" /> </ButtonPrimitive> - ) : null; -}; - -Dialog.Trigger = Trigger; -Dialog.Header = Header; -Dialog.Title = Title; -Dialog.Description = Description; -Dialog.Body = Body; -Dialog.Footer = Footer; -Dialog.Close = Close; -Dialog.CloseIndicator = CloseIndicator; - -export { Dialog }; + ) : null +} + +Dialog.Trigger = Trigger +Dialog.Header = Header +Dialog.Title = Title +Dialog.Description = Description +Dialog.Body = Body +Dialog.Footer = Footer +Dialog.Close = Close +Dialog.CloseIndicator = CloseIndicator + +export { Dialog } export type { CloseButtonIndicatorProps, DialogBodyProps, DialogDescriptionProps, DialogFooterProps, DialogHeaderProps, - DialogTitleProps -}; + DialogTitleProps, +} diff --git a/resources/js/components/ui/dropdown.tsx b/resources/js/components/ui/dropdown.tsx index 0f64ca0..6229f78 100644 --- a/resources/js/components/ui/dropdown.tsx +++ b/resources/js/components/ui/dropdown.tsx @@ -1,5 +1,5 @@ -import { cn } from '@/utils/classes'; -import { IconCheck } from 'justd-icons'; +import { cn } from "@/utils/classes" +import { IconCheck } from "justd-icons" import { Collection, Header, @@ -11,107 +11,121 @@ import { type SeparatorProps, Text, type TextProps, - composeRenderProps -} from 'react-aria-components'; -import { tv } from 'tailwind-variants'; -import { Keyboard } from './keyboard'; + composeRenderProps, +} from "react-aria-components" +import { tv } from "tailwind-variants" +import { Keyboard } from "./keyboard" const dropdownItemStyles = tv({ base: [ - 'col-span-full grid grid-cols-[auto_1fr_1.5rem_0.5rem_auto] not-has-data-[slot=dropdown-item-details]:items-center has-data-[slot=dropdown-item-details]:**:data-[slot=checked-icon]:mt-[1.5px] supports-[grid-template-columns:subgrid]:grid-cols-subgrid', - 'group relative cursor-default select-none rounded-[calc(var(--radius-lg)-1px)] px-[calc(var(--spacing)*2.3)] py-[calc(var(--spacing)*1.3)] forced-color:text-[Highlight] text-base text-fg outline-0 forced-color-adjust-none sm:text-sm/6 forced-colors:text-[LinkText]', - '**:data-[slot=avatar]:*:mr-2 **:data-[slot=avatar]:*:size-6 **:data-[slot=avatar]:mr-2 **:data-[slot=avatar]:size-6 sm:**:data-[slot=avatar]:*:size-5 sm:**:data-[slot=avatar]:size-5', - 'data-danger:**:data-[slot=icon]:text-danger/60 **:data-[slot=icon]:size-4 **:data-[slot=icon]:shrink-0 **:data-[slot=icon]:text-muted-fg data-focused:data-danger:**:data-[slot=icon]:text-danger', - 'data-[slot=menu-radio]:*:data-[slot=icon]:size-3 *:data-[slot=icon]:mr-2', - 'forced-colors:**:data-[slot=icon]:text-[CanvasText] forced-colors:group-data-focused:**:data-[slot=icon]:text-[Canvas] ', - '[&>[slot=label]+[data-slot=icon]]:absolute [&>[slot=label]+[data-slot=icon]]:right-0' + "col-span-full grid grid-cols-[auto_1fr_1.5rem_0.5rem_auto] not-has-data-[slot=dropdown-item-details]:items-center has-data-[slot=dropdown-item-details]:**:data-[slot=checked-icon]:mt-[1.5px] supports-[grid-template-columns:subgrid]:grid-cols-subgrid", + "group relative cursor-default select-none rounded-[calc(var(--radius-lg)-1px)] px-[calc(var(--spacing)*2.3)] py-[calc(var(--spacing)*1.3)] forced-color:text-[Highlight] text-base text-fg outline-0 forced-color-adjust-none sm:text-sm/6 forced-colors:text-[LinkText]", + "**:data-[slot=avatar]:*:mr-2 **:data-[slot=avatar]:*:size-6 **:data-[slot=avatar]:mr-2 **:data-[slot=avatar]:size-6 sm:**:data-[slot=avatar]:*:size-5 sm:**:data-[slot=avatar]:size-5", + "data-danger:**:data-[slot=icon]:text-danger/60 **:data-[slot=icon]:size-4 **:data-[slot=icon]:shrink-0 **:data-[slot=icon]:text-muted-fg data-focused:data-danger:**:data-[slot=icon]:text-danger", + "data-[slot=menu-radio]:*:data-[slot=icon]:size-3 *:data-[slot=icon]:mr-2", + "forced-colors:**:data-[slot=icon]:text-[CanvasText] forced-colors:group-data-focused:**:data-[slot=icon]:text-[Canvas] ", + "[&>[slot=label]+[data-slot=icon]]:absolute [&>[slot=label]+[data-slot=icon]]:right-0", ], variants: { isDisabled: { - true: 'text-muted-fg forced-colors:text-[GrayText]' + true: "text-muted-fg forced-colors:text-[GrayText]", }, isSelected: { - true: '**:data-[slot=avatar]:*:hidden **:data-[slot=avatar]:hidden **:data-[slot=icon]:hidden' + true: "**:data-[slot=avatar]:*:hidden **:data-[slot=avatar]:hidden **:data-[slot=icon]:hidden", }, isFocused: { - false: 'data-danger:text-danger', + false: "data-danger:text-danger", true: [ - '**:data-[slot=icon]:text-accent-fg **:[kbd]:text-accent-fg', - 'bg-accent text-accent-fg forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]', - 'data-danger:bg-danger/10 data-danger:text-danger', - 'data-[slot=description]:text-accent-fg data-[slot=label]:text-accent-fg [&_.text-muted-fg]:text-accent-fg/80' - ] - } - } -}); + "**:data-[slot=icon]:text-accent-fg **:[kbd]:text-accent-fg", + "bg-accent text-accent-fg forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]", + "data-danger:bg-danger/10 data-danger:text-danger", + "data-[slot=description]:text-accent-fg data-[slot=label]:text-accent-fg [&_.text-muted-fg]:text-accent-fg/80", + ], + }, + }, +}) const dropdownSectionStyles = tv({ slots: { - section: 'col-span-full grid grid-cols-[auto_1fr]', - header: 'col-span-full px-2.5 py-1 font-medium text-muted-fg text-sm sm:text-xs' - } -}); + section: "col-span-full grid grid-cols-[auto_1fr]", + header: "col-span-full px-2.5 py-1 font-medium text-muted-fg text-sm sm:text-xs", + }, +}) -const { section, header } = dropdownSectionStyles(); +const { section, header } = dropdownSectionStyles() interface DropdownSectionProps<T> extends SectionProps<T> { - title?: string; + title?: string } const DropdownSection = <T extends object>({ className, ...props }: DropdownSectionProps<T>) => { return ( <ListBoxSection className={section({ className })}> - {'title' in props && <Header className={header()}>{props.title}</Header>} + {"title" in props && <Header className={header()}>{props.title}</Header>} <Collection items={props.items}>{props.children}</Collection> </ListBoxSection> - ); -}; + ) +} -type DropdownItemProps = ListBoxItemProps; +type DropdownItemProps = ListBoxItemProps const DropdownItem = ({ className, ...props }: DropdownItemProps) => { - const textValue = props.textValue || (typeof props.children === 'string' ? props.children : undefined); + const textValue = + props.textValue || (typeof props.children === "string" ? props.children : undefined) return ( <ListBoxItemPrimitive textValue={textValue} className={composeRenderProps(className, (className, renderProps) => - dropdownItemStyles({ ...renderProps, className }) + dropdownItemStyles({ ...renderProps, className }), )} {...props} > {composeRenderProps(props.children, (children, { isSelected }) => ( <> {isSelected && <IconCheck className="-mx-0.5 mr-2" data-slot="checked-icon" />} - {typeof children === 'string' ? <DropdownLabel>{children}</DropdownLabel> : children} + {typeof children === "string" ? <DropdownLabel>{children}</DropdownLabel> : children} </> ))} </ListBoxItemPrimitive> - ); -}; + ) +} interface DropdownItemDetailProps extends TextProps { - label?: TextProps['children']; - description?: TextProps['children']; + label?: TextProps["children"] + description?: TextProps["children"] classNames?: { - label?: TextProps['className']; - description?: TextProps['className']; - }; + label?: TextProps["className"] + description?: TextProps["className"] + } } -const DropdownItemDetails = ({ label, description, classNames, ...props }: DropdownItemDetailProps) => { - const { slot, children, title, ...restProps } = props; +const DropdownItemDetails = ({ + label, + description, + classNames, + ...props +}: DropdownItemDetailProps) => { + const { slot, children, title, ...restProps } = props return ( - <div data-slot="dropdown-item-details" className="col-start-2 flex flex-col gap-y-1" {...restProps}> + <div + data-slot="dropdown-item-details" + className="col-start-2 flex flex-col gap-y-1" + {...restProps} + > {label && ( - <Text slot={slot ?? 'label'} className={cn('font-medium sm:text-sm', classNames?.label)} {...restProps}> + <Text + slot={slot ?? "label"} + className={cn("font-medium sm:text-sm", classNames?.label)} + {...restProps} + > {label} </Text> )} {description && ( <Text - slot={slot ?? 'description'} - className={cn('text-muted-fg text-xs', classNames?.description)} + slot={slot ?? "description"} + className={cn("text-muted-fg text-xs", classNames?.description)} {...restProps} > {description} @@ -119,24 +133,28 @@ const DropdownItemDetails = ({ label, description, classNames, ...props }: Dropd )} {!title && children} </div> - ); -}; + ) +} interface MenuLabelProps extends TextProps { - ref?: React.Ref<HTMLDivElement>; + ref?: React.Ref<HTMLDivElement> } const DropdownLabel = ({ className, ref, ...props }: MenuLabelProps) => ( - <Text slot="label" ref={ref} className={cn('col-start-2', className)} {...props} /> -); + <Text slot="label" ref={ref} className={cn("col-start-2", className)} {...props} /> +) const DropdownSeparator = ({ className, ...props }: SeparatorProps) => ( - <Separator orientation="horizontal" className={cn('-mx-1 col-span-full my-1 h-px bg-border', className)} {...props} /> -); + <Separator + orientation="horizontal" + className={cn("-mx-1 col-span-full my-1 h-px bg-border", className)} + {...props} + /> +) const DropdownKeyboard = ({ className, ...props }: React.ComponentProps<typeof Keyboard>) => { - return <Keyboard className={cn('absolute right-2 pl-2', className)} {...props} />; -}; + return <Keyboard className={cn("absolute right-2 pl-2", className)} {...props} /> +} /** * Note: This is not exposed component, but it's used in other components to render dropdowns. @@ -150,6 +168,6 @@ export { DropdownSection, DropdownSeparator, dropdownItemStyles, - dropdownSectionStyles -}; -export type { DropdownItemDetailProps, DropdownItemProps, DropdownSectionProps }; + dropdownSectionStyles, +} +export type { DropdownItemDetailProps, DropdownItemProps, DropdownSectionProps } diff --git a/resources/js/components/ui/field.tsx b/resources/js/components/ui/field.tsx index 97eb4c7..314d896 100644 --- a/resources/js/components/ui/field.tsx +++ b/resources/js/components/ui/field.tsx @@ -5,90 +5,96 @@ import type { LabelProps, TextFieldProps as TextFieldPrimitiveProps, TextProps, - ValidationResult -} from 'react-aria-components'; + ValidationResult, +} from "react-aria-components" import { FieldError as FieldErrorPrimitive, Group, Input as InputPrimitive, Label as LabelPrimitive, Text, - composeRenderProps -} from 'react-aria-components'; -import { tv } from 'tailwind-variants'; + composeRenderProps, +} from "react-aria-components" +import { tv } from "tailwind-variants" -import { composeTailwindRenderProps, focusStyles } from './primitive'; +import { composeTailwindRenderProps, focusStyles } from "./primitive" interface FieldProps { - label?: string; - placeholder?: string; - description?: string; - errorMessage?: string | ((validation: ValidationResult) => string); - 'aria-label'?: TextFieldPrimitiveProps['aria-label']; - 'aria-labelledby'?: TextFieldPrimitiveProps['aria-labelledby']; + label?: string + placeholder?: string + description?: string + errorMessage?: string | ((validation: ValidationResult) => string) + "aria-label"?: TextFieldPrimitiveProps["aria-label"] + "aria-labelledby"?: TextFieldPrimitiveProps["aria-labelledby"] } const fieldStyles = tv({ slots: { - description: 'text-pretty text-muted-fg text-sm/6', - label: 'w-fit cursor-default font-medium text-secondary-fg text-sm/6', - fieldError: 'text-danger text-sm/6 forced-colors:text-[Mark]' - } -}); + description: "text-pretty text-muted-fg text-sm/6", + label: "w-fit cursor-default font-medium text-secondary-fg text-sm/6", + fieldError: "text-danger text-sm/6 forced-colors:text-[Mark]", + }, +}) -const { description, label, fieldError } = fieldStyles(); +const { description, label, fieldError } = fieldStyles() const Label = ({ className, ...props }: LabelProps) => { - return <LabelPrimitive {...props} className={label({ className })} />; -}; + return <LabelPrimitive {...props} className={label({ className })} /> +} interface DescriptionProps extends TextProps { - isWarning?: boolean; - ref?: React.RefObject<HTMLElement>; + isWarning?: boolean + ref?: React.RefObject<HTMLElement> } const Description = ({ ref, className, ...props }: DescriptionProps) => { - const isWarning = props.isWarning ?? false; + const isWarning = props.isWarning ?? false return ( <Text ref={ref} {...props} slot="description" - className={description({ className: isWarning ? 'text-warning' : className })} + className={description({ className: isWarning ? "text-warning" : className })} /> - ); -}; + ) +} interface FieldErrorProps extends FieldErrorPrimitiveProps { - ref?: React.RefObject<HTMLElement>; + ref?: React.RefObject<HTMLElement> } const FieldError = ({ className, ref, ...props }: FieldErrorProps) => { - return <FieldErrorPrimitive ref={ref} {...props} className={composeTailwindRenderProps(className, fieldError())} />; -}; + return ( + <FieldErrorPrimitive + ref={ref} + {...props} + className={composeTailwindRenderProps(className, fieldError())} + /> + ) +} const fieldGroupStyles = tv({ base: [ - 'group flex h-10 items-center overflow-hidden rounded-lg border border-input shadow-xs transition duration-200 ease-out', - 'relative focus-within:ring-4 group-data-invalid:focus-within:border-danger group-data-invalid:focus-within:ring-danger/20', - '[&>[role=progressbar]:first-child]:ml-2.5 [&>[role=progressbar]:last-child]:mr-2.5', - '**:data-[slot=icon]:size-4 **:data-[slot=icon]:shrink-0 **:[button]:shrink-0', - '[&>button:has([data-slot=icon]):first-child]:left-0 [&>button:has([data-slot=icon]):last-child]:right-0 [&>button:has([data-slot=icon])]:absolute', - '*:data-[slot=icon]:pointer-events-none *:data-[slot=icon]:absolute *:data-[slot=icon]:top-[calc(var(--spacing)*2.7)] *:data-[slot=icon]:z-10 *:data-[slot=icon]:size-4 *:data-[slot=icon]:text-muted-fg', - '[&>[data-slot=icon]:first-child]:left-2.5 [&>[data-slot=icon]:last-child]:right-2.5', - '[&:has([data-slot=icon]+input)]:pl-6 [&:has(input+[data-slot=icon])]:pr-6', - '[&:has([data-slot=icon]+[role=group])]:pl-6 [&:has([role=group]+[data-slot=icon])]:pr-6', - 'has-[[data-slot=icon]:last-child]:[&_input]:pr-7', - '*:[button]:h-8 *:[button]:rounded-[calc(var(--radius-sm)-1px)] *:[button]:px-2.5', - '[&>button:first-child]:ml-[calc(var(--spacing)*0.7)] [&>button:last-child]:mr-[calc(var(--spacing)*0.7)]' + "group flex h-10 items-center overflow-hidden rounded-lg border border-input shadow-xs transition duration-200 ease-out", + "relative focus-within:ring-4 group-data-invalid:focus-within:border-danger group-data-invalid:focus-within:ring-danger/20", + "[&>[role=progressbar]:first-child]:ml-2.5 [&>[role=progressbar]:last-child]:mr-2.5", + "**:data-[slot=icon]:size-4 **:data-[slot=icon]:shrink-0 **:[button]:shrink-0", + "[&>button:has([data-slot=icon]):first-child]:left-0 [&>button:has([data-slot=icon]):last-child]:right-0 [&>button:has([data-slot=icon])]:absolute", + "*:data-[slot=icon]:pointer-events-none *:data-[slot=icon]:absolute *:data-[slot=icon]:top-[calc(var(--spacing)*2.7)] *:data-[slot=icon]:z-10 *:data-[slot=icon]:size-4 *:data-[slot=icon]:text-muted-fg", + "[&>[data-slot=icon]:first-child]:left-2.5 [&>[data-slot=icon]:last-child]:right-2.5", + "[&:has([data-slot=icon]+input)]:pl-6 [&:has(input+[data-slot=icon])]:pr-6", + "[&:has([data-slot=icon]+[role=group])]:pl-6 [&:has([role=group]+[data-slot=icon])]:pr-6", + "has-[[data-slot=icon]:last-child]:[&_input]:pr-7", + "*:[button]:h-8 *:[button]:rounded-[calc(var(--radius-sm)-1px)] *:[button]:px-2.5", + "[&>button:first-child]:ml-[calc(var(--spacing)*0.7)] [&>button:last-child]:mr-[calc(var(--spacing)*0.7)]", ], variants: { isFocusWithin: focusStyles.variants.isFocused, isInvalid: focusStyles.variants.isInvalid, isDisabled: { - true: 'opacity-50 forced-colors:border-[GrayText]' - } - } -}); + true: "opacity-50 forced-colors:border-[GrayText]", + }, + }, +}) const FieldGroup = ({ className, ...props }: GroupProps) => { return ( @@ -97,15 +103,15 @@ const FieldGroup = ({ className, ...props }: GroupProps) => { className={composeRenderProps(className, (className, renderProps) => fieldGroupStyles({ ...renderProps, - className - }) + className, + }), )} /> - ); -}; + ) +} interface InputProps extends InputPrimitiveProps { - ref?: React.RefObject<HTMLInputElement>; + ref?: React.RefObject<HTMLInputElement> } const Input = ({ className, ref, ...props }: InputProps) => { @@ -115,11 +121,11 @@ const Input = ({ className, ref, ...props }: InputProps) => { {...props} className={composeTailwindRenderProps( className, - 'w-full min-w-0 bg-transparent px-2.5 py-2 text-base text-fg placeholder-muted-fg outline-hidden data-focused:outline-hidden sm:text-sm/6 [&::-ms-reveal]:hidden [&::-webkit-search-cancel-button]:hidden' + "w-full min-w-0 bg-transparent px-2.5 py-2 text-base text-fg placeholder-muted-fg outline-hidden data-focused:outline-hidden sm:text-sm/6 [&::-ms-reveal]:hidden [&::-webkit-search-cancel-button]:hidden", )} /> - ); -}; + ) +} -export { Description, FieldError, FieldGroup, Input, Label }; -export type { FieldErrorProps, FieldProps, InputProps }; +export { Description, FieldError, FieldGroup, Input, Label } +export type { FieldErrorProps, FieldProps, InputProps } diff --git a/resources/js/components/ui/form.tsx b/resources/js/components/ui/form.tsx index 5ec069d..be50876 100644 --- a/resources/js/components/ui/form.tsx +++ b/resources/js/components/ui/form.tsx @@ -1,12 +1,12 @@ -import type { FormProps as FormPrimitiveProps } from 'react-aria-components'; -import { Form as FormPrimitive } from 'react-aria-components'; +import type { FormProps as FormPrimitiveProps } from "react-aria-components" +import { Form as FormPrimitive } from "react-aria-components" interface FormProps extends FormPrimitiveProps { - ref?: React.RefObject<HTMLFormElement>; + ref?: React.RefObject<HTMLFormElement> } const Form = ({ ref, ...props }: FormProps) => { - return <FormPrimitive ref={ref} {...props} />; -}; + return <FormPrimitive ref={ref} {...props} /> +} -export { Form }; -export type { FormProps }; +export { Form } +export type { FormProps } diff --git a/resources/js/components/ui/heading.tsx b/resources/js/components/ui/heading.tsx index 33aea73..77e0b6c 100644 --- a/resources/js/components/ui/heading.tsx +++ b/resources/js/components/ui/heading.tsx @@ -1,44 +1,46 @@ -import { tv } from 'tailwind-variants'; +import { tv } from "tailwind-variants" const headingStyles = tv({ - base: 'font-sans text-fg tracking-tight', + base: "font-sans text-fg tracking-tight", variants: { level: { - 1: 'font-bold text-xl sm:text-2xl', - 2: 'font-semibold text-lg sm:text-xl', - 3: 'font-semibold text-base sm:text-lg', - 4: 'font-semibold text-base' + 1: "font-bold text-xl sm:text-2xl", + 2: "font-semibold text-lg sm:text-xl", + 3: "font-semibold text-base sm:text-lg", + 4: "font-semibold text-base", }, tracking: { - tighter: 'tracking-tighter', - tight: 'tracking-tight', - normal: 'tracking-normal', - wide: 'tracking-wide', - wider: 'tracking-wider', - widest: 'tracking-widest' - } - } -}); -type HeadingType = { level?: 1 | 2 | 3 | 4 } & React.ComponentPropsWithoutRef<'h1' | 'h2' | 'h3' | 'h4'>; + tighter: "tracking-tighter", + tight: "tracking-tight", + normal: "tracking-normal", + wide: "tracking-wide", + wider: "tracking-wider", + widest: "tracking-widest", + }, + }, +}) +type HeadingType = { level?: 1 | 2 | 3 | 4 } & React.ComponentPropsWithoutRef< + "h1" | "h2" | "h3" | "h4" +> interface HeadingProps extends HeadingType { - tracking?: 'tighter' | 'tight' | 'normal' | 'wide' | 'wider' | 'widest'; - className?: string | undefined; + tracking?: "tighter" | "tight" | "normal" | "wide" | "wider" | "widest" + className?: string | undefined } -const Heading = ({ className, tracking = 'normal', level = 1, ...props }: HeadingProps) => { - const Element: `h${typeof level}` = `h${level}`; +const Heading = ({ className, tracking = "normal", level = 1, ...props }: HeadingProps) => { + const Element: `h${typeof level}` = `h${level}` return ( <Element className={headingStyles({ level, tracking, - className + className, })} {...props} /> - ); -}; + ) +} -export { Heading }; -export type { HeadingProps }; +export { Heading } +export type { HeadingProps } diff --git a/resources/js/components/ui/index.ts b/resources/js/components/ui/index.ts index 01a66c0..52678ab 100644 --- a/resources/js/components/ui/index.ts +++ b/resources/js/components/ui/index.ts @@ -1,27 +1,27 @@ -export * from './avatar'; -export * from './button'; -export * from './card'; -export * from './checkbox'; -export * from './container'; -export * from './dialog'; -export * from './dropdown'; -export * from './field'; -export * from './form'; -export * from './heading'; -export * from './keyboard'; -export * from './link'; -export * from './list-box'; -export * from './loader'; -export * from './menu'; -export * from './modal'; -export * from './navbar'; -export * from './pagination'; -export * from './popover'; -export * from './primitive'; -export * from './select'; -export * from './separator'; -export * from './sheet'; -export * from './table'; -export * from './text-field'; -export * from './toast'; -export * from './visually-hidden'; +export * from "./avatar" +export * from "./button" +export * from "./card" +export * from "./checkbox" +export * from "./container" +export * from "./dialog" +export * from "./dropdown" +export * from "./field" +export * from "./form" +export * from "./heading" +export * from "./keyboard" +export * from "./link" +export * from "./list-box" +export * from "./loader" +export * from "./menu" +export * from "./modal" +export * from "./navbar" +export * from "./pagination" +export * from "./popover" +export * from "./primitive" +export * from "./select" +export * from "./separator" +export * from "./sheet" +export * from "./table" +export * from "./text-field" +export * from "./toast" +export * from "./visually-hidden" diff --git a/resources/js/components/ui/keyboard.tsx b/resources/js/components/ui/keyboard.tsx index cde1b0a..ed46ef6 100644 --- a/resources/js/components/ui/keyboard.tsx +++ b/resources/js/components/ui/keyboard.tsx @@ -1,34 +1,37 @@ -import { Keyboard as KeyboardPrimitive } from 'react-aria-components'; -import { tv } from 'tailwind-variants'; +import { Keyboard as KeyboardPrimitive } from "react-aria-components" +import { tv } from "tailwind-variants" const keyboardStyles = tv({ slots: { - base: 'hidden text-current/70 group-data-focused:text-fg group-data-hovered:text-fg group-data-disabled:opacity-50 group-data-focused:opacity-90 lg:inline-flex forced-colors:group-data-focused:text-[HighlightText]', - kbd: 'inline-grid min-h-5 min-w-[2ch] place-content-center rounded text-center font-sans text-[.75rem] uppercase' - } -}); + base: "hidden text-current/70 group-data-focused:text-fg group-data-hovered:text-fg group-data-disabled:opacity-50 group-data-focused:opacity-90 lg:inline-flex forced-colors:group-data-focused:text-[HighlightText]", + kbd: "inline-grid min-h-5 min-w-[2ch] place-content-center rounded text-center font-sans text-[.75rem] uppercase", + }, +}) -const { base, kbd } = keyboardStyles(); +const { base, kbd } = keyboardStyles() interface KeyboardProps extends React.HTMLAttributes<HTMLElement> { - keys: string | string[]; + keys: string | string[] classNames?: { - base?: string; - kbd?: string; - }; + base?: string + kbd?: string + } } const Keyboard = ({ keys, classNames, className, ...props }: KeyboardProps) => { return ( <KeyboardPrimitive className={base({ className: classNames?.base ?? className })} {...props}> - {(Array.isArray(keys) ? keys : keys.split('')).map((char, index) => ( - <kbd key={index} className={kbd({ className: index > 0 && char.length > 1 ? 'pl-1' : classNames?.kbd })}> + {(Array.isArray(keys) ? keys : keys.split("")).map((char, index) => ( + <kbd + key={index} + className={kbd({ className: index > 0 && char.length > 1 ? "pl-1" : classNames?.kbd })} + > {char} </kbd> ))} </KeyboardPrimitive> - ); -}; + ) +} -export { Keyboard }; -export type { KeyboardProps }; +export { Keyboard } +export type { KeyboardProps } diff --git a/resources/js/components/ui/link.tsx b/resources/js/components/ui/link.tsx index a58c31b..1881a21 100644 --- a/resources/js/components/ui/link.tsx +++ b/resources/js/components/ui/link.tsx @@ -1,26 +1,30 @@ -import { Link as LinkPrimitive, type LinkProps as LinkPrimitiveProps, composeRenderProps } from 'react-aria-components'; -import { tv } from 'tailwind-variants'; +import { + Link as LinkPrimitive, + type LinkProps as LinkPrimitiveProps, + composeRenderProps, +} from "react-aria-components" +import { tv } from "tailwind-variants" -import { focusButtonStyles } from './primitive'; +import { focusButtonStyles } from "./primitive" const linkStyles = tv({ extend: focusButtonStyles, - base: 'transition-[color,_opacity] data-disabled:cursor-default data-disabled:opacity-60 forced-colors:data-disabled:text-[GrayText]', + base: "transition-[color,_opacity] data-disabled:cursor-default data-disabled:opacity-60 forced-colors:data-disabled:text-[GrayText]", variants: { intent: { - unstyled: 'text-current', - primary: 'text-fg data-hovered:underline', - secondary: 'text-muted-fg data-hovered:text-secondary-fg' - } + unstyled: "text-current", + primary: "text-fg data-hovered:underline", + secondary: "text-muted-fg data-hovered:text-secondary-fg", + }, }, defaultVariants: { - intent: 'unstyled' - } -}); + intent: "unstyled", + }, +}) interface LinkProps extends LinkPrimitiveProps { - intent?: 'primary' | 'secondary' | 'unstyled'; - ref?: React.RefObject<HTMLAnchorElement>; + intent?: "primary" | "secondary" | "unstyled" + ref?: React.RefObject<HTMLAnchorElement> } const Link = ({ className, ref, ...props }: LinkProps) => { @@ -29,13 +33,15 @@ const Link = ({ className, ref, ...props }: LinkProps) => { ref={ref} {...props} className={composeRenderProps(className, (className, renderProps) => - linkStyles({ ...renderProps, intent: props.intent, className }) + linkStyles({ ...renderProps, intent: props.intent, className }), )} > - {(values) => <>{typeof props.children === 'function' ? props.children(values) : props.children}</>} + {(values) => ( + <>{typeof props.children === "function" ? props.children(values) : props.children}</> + )} </LinkPrimitive> - ); -}; + ) +} -export { Link, linkStyles }; -export type { LinkProps }; +export { Link, linkStyles } +export type { LinkProps } diff --git a/resources/js/components/ui/list-box.tsx b/resources/js/components/ui/list-box.tsx index c9ae418..bc8783f 100644 --- a/resources/js/components/ui/list-box.tsx +++ b/resources/js/components/ui/list-box.tsx @@ -1,13 +1,16 @@ -import { IconCheck, IconHamburger } from 'justd-icons'; -import type { ListBoxItemProps as ListBoxItemPrimitiveProps, ListBoxProps } from 'react-aria-components'; +import { IconCheck, IconHamburger } from "justd-icons" +import type { + ListBoxItemProps as ListBoxItemPrimitiveProps, + ListBoxProps, +} from "react-aria-components" import { ListBoxItem as ListBoxItemPrimitive, ListBox as ListBoxPrimitive, - composeRenderProps -} from 'react-aria-components'; + composeRenderProps, +} from "react-aria-components" -import { cn } from '@/utils/classes'; -import { DropdownItemDetails, DropdownLabel, DropdownSection, dropdownItemStyles } from './dropdown'; +import { cn } from "@/utils/classes" +import { DropdownItemDetails, DropdownLabel, DropdownSection, dropdownItemStyles } from "./dropdown" const ListBox = <T extends object>({ className, ...props }: ListBoxProps<T>) => ( <ListBoxPrimitive @@ -15,14 +18,14 @@ const ListBox = <T extends object>({ className, ...props }: ListBoxProps<T>) => className={composeRenderProps(className, (className) => cn( [ - 'flex max-h-96 w-full min-w-56 flex-col gap-y-1 overflow-y-auto rounded-xl border p-1 shadow-lg outline-hidden [scrollbar-width:thin] [&::-webkit-scrollbar]:size-0.5', - "grid grid-cols-[auto_1fr] overflow-auto *:[[role='group']+[role=group]]:mt-4 *:[[role='group']+[role=separator]]:mt-1" + "flex max-h-96 w-full min-w-56 flex-col gap-y-1 overflow-y-auto rounded-xl border p-1 shadow-lg outline-hidden [scrollbar-width:thin] [&::-webkit-scrollbar]:size-0.5", + "grid grid-cols-[auto_1fr] overflow-auto *:[[role='group']+[role=group]]:mt-4 *:[[role='group']+[role=separator]]:mt-1", ], - className - ) + className, + ), )} /> -); +) // const listBoxItemStyles = tv({ // base: "lbi col-span-full relative cursor-pointer rounded-[calc(var(--radius-lg)-1px)] p-2 text-base outline-hidden sm:text-sm", @@ -47,11 +50,11 @@ const ListBox = <T extends object>({ className, ...props }: ListBoxProps<T>) => // }) interface ListBoxItemProps<T extends object> extends ListBoxItemPrimitiveProps<T> { - className?: string; + className?: string } const ListBoxItem = <T extends object>({ children, className, ...props }: ListBoxItemProps<T>) => { - const textValue = typeof children === 'string' ? children : undefined; + const textValue = typeof children === "string" ? children : undefined return ( <ListBoxItemPrimitive @@ -60,8 +63,8 @@ const ListBoxItem = <T extends object>({ children, className, ...props }: ListBo className={composeRenderProps(className, (className, renderProps) => dropdownItemStyles({ ...renderProps, - className - }) + className, + }), )} > {({ allowsDragging, isSelected, isFocused, isDragging }) => ( @@ -69,31 +72,33 @@ const ListBoxItem = <T extends object>({ children, className, ...props }: ListBo {allowsDragging && ( <IconHamburger className={cn( - 'size-4 shrink-0 text-muted-fg transition', - isFocused && 'text-fg', - isDragging && 'text-fg', - isSelected && 'text-accent-fg/70' + "size-4 shrink-0 text-muted-fg transition", + isFocused && "text-fg", + isDragging && "text-fg", + isSelected && "text-accent-fg/70", )} /> )} {isSelected && <IconCheck className="-mx-0.5 mr-2" data-slot="checked-icon" />} - {typeof children === 'string' ? <DropdownLabel>{children}</DropdownLabel> : children} + {typeof children === "string" ? <DropdownLabel>{children}</DropdownLabel> : children} </> )} </ListBoxItemPrimitive> - ); -}; + ) +} -type ListBoxSectionProps = React.ComponentProps<typeof DropdownSection>; +type ListBoxSectionProps = React.ComponentProps<typeof DropdownSection> const ListBoxSection = ({ className, ...props }: ListBoxSectionProps) => { - return <DropdownSection className={cn(className, '[&_.lbi:last-child]:-mb-1.5 gap-y-1')} {...props} />; -}; + return ( + <DropdownSection className={cn(className, "[&_.lbi:last-child]:-mb-1.5 gap-y-1")} {...props} /> + ) +} -const ListBoxItemDetails = DropdownItemDetails; +const ListBoxItemDetails = DropdownItemDetails -ListBox.Section = ListBoxSection; -ListBox.ItemDetails = ListBoxItemDetails; -ListBox.Item = ListBoxItem; +ListBox.Section = ListBoxSection +ListBox.ItemDetails = ListBoxItemDetails +ListBox.Item = ListBoxItem -export { ListBox }; -export type { ListBoxItemProps, ListBoxSectionProps }; +export { ListBox } +export type { ListBoxItemProps, ListBoxSectionProps } diff --git a/resources/js/components/ui/loader.tsx b/resources/js/components/ui/loader.tsx index 7a5056f..24bddd4 100644 --- a/resources/js/components/ui/loader.tsx +++ b/resources/js/components/ui/loader.tsx @@ -1,38 +1,38 @@ -import { cn } from '@/utils/classes'; -import { IconLoader } from 'justd-icons'; -import { ProgressBar } from 'react-aria-components'; -import type { VariantProps } from 'tailwind-variants'; -import { tv } from 'tailwind-variants'; +import { cn } from "@/utils/classes" +import { IconLoader } from "justd-icons" +import { ProgressBar } from "react-aria-components" +import type { VariantProps } from "tailwind-variants" +import { tv } from "tailwind-variants" const loaderStyles = tv({ - base: 'relative', + base: "relative", variants: { intent: { - current: 'text-current', - primary: 'text-primary', - secondary: 'text-muted-fg', - success: 'text-success', - warning: 'text-warning', - danger: 'text-danger' + current: "text-current", + primary: "text-primary", + secondary: "text-muted-fg", + success: "text-success", + warning: "text-warning", + danger: "text-danger", }, size: { - small: 'size-4', - medium: 'size-6', - large: 'size-8', - 'extra-large': 'size-10' - } + small: "size-4", + medium: "size-6", + large: "size-8", + "extra-large": "size-10", + }, }, defaultVariants: { - intent: 'current', - size: 'small' - } -}); + intent: "current", + size: "small", + }, +}) -type LoaderVariantProps = VariantProps<typeof loaderStyles>; +type LoaderVariantProps = VariantProps<typeof loaderStyles> const Bars = ({ className, ...props }: React.SVGProps<SVGSVGElement>) => ( <svg - className={cn('size-4', className)} + className={cn("size-4", className)} data-slot="icon" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" @@ -130,10 +130,10 @@ const Bars = ({ className, ...props }: React.SVGProps<SVGSVGElement>) => ( /> </rect> </svg> -); -const Ring = (props: React.SVGProps<SVGSVGElement>) => <IconLoader {...props} />; +) +const Ring = (props: React.SVGProps<SVGSVGElement>) => <IconLoader {...props} /> const Spin = ({ className, ...props }: React.SVGProps<SVGSVGElement>) => ( - <svg className={cn('size-4', className)} data-slot="icon" viewBox="0 0 2400 2400" {...props}> + <svg className={cn("size-4", className)} data-slot="icon" viewBox="0 0 2400 2400" {...props}> <g strokeWidth="200" strokeLinecap="round" fill="none"> <line x1="1200" y1="600" x2="1200" y2="100" /> <line opacity="0.5" x1="1200" y1="2300" x2="1200" y2="1800" /> @@ -160,33 +160,33 @@ const Spin = ({ className, ...props }: React.SVGProps<SVGSVGElement>) => ( /> </g> </svg> -); +) const LOADERS = { bars: Bars, ring: Ring, - spin: Spin -}; + spin: Spin, +} -const DEFAULT_SPINNER = 'spin'; +const DEFAULT_SPINNER = "spin" interface LoaderProps - extends Omit<React.ComponentPropsWithoutRef<'svg'>, 'display' | 'opacity' | 'intent'>, + extends Omit<React.ComponentPropsWithoutRef<"svg">, "display" | "opacity" | "intent">, LoaderVariantProps { - variant?: keyof typeof LOADERS; - percentage?: number; - isIndeterminate?: boolean; - formatOptions?: Intl.NumberFormatOptions; - ref?: React.RefObject<SVGSVGElement>; + variant?: keyof typeof LOADERS + percentage?: number + isIndeterminate?: boolean + formatOptions?: Intl.NumberFormatOptions + ref?: React.RefObject<SVGSVGElement> } const Loader = ({ isIndeterminate = true, ref, ...props }: LoaderProps) => { - const { className, variant = DEFAULT_SPINNER, intent, size, ...spinnerProps } = props; - const LoaderPrimitive = LOADERS[variant in LOADERS ? variant : DEFAULT_SPINNER]; + const { className, variant = DEFAULT_SPINNER, intent, size, ...spinnerProps } = props + const LoaderPrimitive = LOADERS[variant in LOADERS ? variant : DEFAULT_SPINNER] return ( <ProgressBar - aria-label={props['aria-label'] ?? 'Loading...'} + aria-label={props["aria-label"] ?? "Loading..."} formatOptions={props.formatOptions} isIndeterminate={isIndeterminate} > @@ -196,16 +196,16 @@ const Loader = ({ isIndeterminate = true, ref, ...props }: LoaderProps) => { intent, size, className: cn([ - ['ring'].includes(variant) && 'animate-spin', - variant === 'spin' && 'stroke-current', - className - ]) + ["ring"].includes(variant) && "animate-spin", + variant === "spin" && "stroke-current", + className, + ]), })} ref={ref} {...spinnerProps} /> </ProgressBar> - ); -}; + ) +} -export { Loader }; +export { Loader } diff --git a/resources/js/components/ui/menu.tsx b/resources/js/components/ui/menu.tsx index 85e0b3d..75f4220 100644 --- a/resources/js/components/ui/menu.tsx +++ b/resources/js/components/ui/menu.tsx @@ -1,14 +1,14 @@ -import { createContext, use } from 'react'; +import { createContext, use } from "react" -import { IconBulletFill, IconCheck, IconChevronLgRight } from 'justd-icons'; +import { IconBulletFill, IconCheck, IconChevronLgRight } from "justd-icons" import type { ButtonProps, MenuItemProps as MenuItemPrimitiveProps, MenuProps as MenuPrimitiveProps, MenuSectionProps as MenuSectionPrimitiveProps, MenuTriggerProps as MenuTriggerPrimitiveProps, - PopoverProps -} from 'react-aria-components'; + PopoverProps, +} from "react-aria-components" import { Button, Collection, @@ -18,30 +18,30 @@ import { MenuSection as MenuSectionPrimitive, MenuTrigger as MenuTriggerPrimitive, SubmenuTrigger as SubmenuTriggerPrimitive, - composeRenderProps -} from 'react-aria-components'; -import type { VariantProps } from 'tailwind-variants'; -import { tv } from 'tailwind-variants'; + composeRenderProps, +} from "react-aria-components" +import type { VariantProps } from "tailwind-variants" +import { tv } from "tailwind-variants" -import { cn } from '@/utils/classes'; +import { cn } from "@/utils/classes" import { DropdownItemDetails, DropdownKeyboard, DropdownLabel, DropdownSeparator, dropdownItemStyles, - dropdownSectionStyles -} from './dropdown'; -import { Popover } from './popover'; + dropdownSectionStyles, +} from "./dropdown" +import { Popover } from "./popover" interface MenuContextProps { - respectScreen: boolean; + respectScreen: boolean } -const MenuContext = createContext<MenuContextProps>({ respectScreen: true }); +const MenuContext = createContext<MenuContextProps>({ respectScreen: true }) interface MenuProps extends MenuTriggerPrimitiveProps { - respectScreen?: boolean; + respectScreen?: boolean } const Menu = ({ respectScreen = true, ...props }: MenuProps) => { @@ -49,41 +49,47 @@ const Menu = ({ respectScreen = true, ...props }: MenuProps) => { <MenuContext value={{ respectScreen }}> <MenuTriggerPrimitive {...props}>{props.children}</MenuTriggerPrimitive> </MenuContext> - ); -}; + ) +} const MenuSubMenu = ({ delay = 0, ...props }) => ( <SubmenuTriggerPrimitive {...props} delay={delay}> {props.children} </SubmenuTriggerPrimitive> -); +) const menuStyles = tv({ slots: { menu: "grid max-h-[calc(var(--visual-viewport-height)-10rem)] grid-cols-[auto_1fr] overflow-auto rounded-xl p-1 outline-hidden [clip-path:inset(0_0_0_0_round_calc(var(--radius-lg)-2px))] sm:max-h-[inherit] *:[[role='group']+[role=group]]:mt-4 *:[[role='group']+[role=separator]]:mt-1", - popover: 'z-50 p-0 shadow-xs outline-hidden sm:min-w-40', - trigger: ['relative inline text-left outline-hidden data-focus-visible:ring-1 data-focus-visible:ring-primary'] - } -}); + popover: "z-50 p-0 shadow-xs outline-hidden sm:min-w-40", + trigger: [ + "relative inline text-left outline-hidden data-focus-visible:ring-1 data-focus-visible:ring-primary", + ], + }, +}) -const { menu, popover, trigger } = menuStyles(); +const { menu, popover, trigger } = menuStyles() interface MenuTriggerProps extends ButtonProps { - className?: string; - ref?: React.Ref<HTMLButtonElement>; + className?: string + ref?: React.Ref<HTMLButtonElement> } const MenuTrigger = ({ className, ref, ...props }: MenuTriggerProps) => ( <Button ref={ref} data-slot="menu-trigger" className={trigger({ className })} {...props}> - {(values) => <>{typeof props.children === 'function' ? props.children(values) : props.children}</>} + {(values) => ( + <>{typeof props.children === "function" ? props.children(values) : props.children}</> + )} </Button> -); - -interface MenuContentProps<T> extends Omit<PopoverProps, 'children' | 'style'>, MenuPrimitiveProps<T> { - className?: string; - popoverClassName?: string; - showArrow?: boolean; - respectScreen?: boolean; +) + +interface MenuContentProps<T> + extends Omit<PopoverProps, "children" | "style">, + MenuPrimitiveProps<T> { + className?: string + popoverClassName?: string + showArrow?: boolean + respectScreen?: boolean } const MenuContent = <T extends object>({ @@ -92,27 +98,27 @@ const MenuContent = <T extends object>({ popoverClassName, ...props }: MenuContentProps<T>) => { - const { respectScreen } = use(MenuContext); + const { respectScreen } = use(MenuContext) return ( <Popover.Content respectScreen={respectScreen} showArrow={showArrow} className={popover({ - className: popoverClassName + className: popoverClassName, })} {...props} > <MenuPrimitive className={menu({ className })} {...props} /> </Popover.Content> - ); -}; + ) +} interface MenuItemProps extends MenuItemPrimitiveProps, VariantProps<typeof dropdownItemStyles> { - isDanger?: boolean; + isDanger?: boolean } const MenuItem = ({ className, isDanger = false, children, ...props }: MenuItemProps) => { - const textValue = props.textValue || (typeof children === 'string' ? children : undefined); + const textValue = props.textValue || (typeof children === "string" ? children : undefined) return ( <MenuItemPrimitive className={composeRenderProps(className, (className, renderProps) => @@ -120,22 +126,22 @@ const MenuItem = ({ className, isDanger = false, children, ...props }: MenuItemP ...renderProps, className: renderProps.hasSubmenu ? cn([ - 'data-open:data-danger:bg-danger/10 data-open:data-danger:text-danger', - 'data-open:bg-accent data-open:text-accent-fg data-open:*:data-[slot=icon]:text-accent-fg data-open:*:[.text-muted-fg]:text-accent-fg', - className + "data-open:data-danger:bg-danger/10 data-open:data-danger:text-danger", + "data-open:bg-accent data-open:text-accent-fg data-open:*:data-[slot=icon]:text-accent-fg data-open:*:[.text-muted-fg]:text-accent-fg", + className, ]) - : className - }) + : className, + }), )} textValue={textValue} - data-danger={isDanger ? 'true' : undefined} + data-danger={isDanger ? "true" : undefined} {...props} > {(values) => ( <> {values.isSelected && ( <> - {values.selectionMode === 'single' && ( + {values.selectionMode === "single" && ( <span data-slot="bullet-icon" className="-mx-0.5 mr-2 flex size-4 shrink-0 items-center justify-center **:data-[slot=indicator]:size-2.5 **:data-[slot=indicator]:shrink-0" @@ -143,67 +149,69 @@ const MenuItem = ({ className, isDanger = false, children, ...props }: MenuItemP <IconBulletFill data-slot="indicator" /> </span> )} - {values.selectionMode === 'multiple' && ( + {values.selectionMode === "multiple" && ( <IconCheck className="-mx-0.5 mr-2 size-4" data-slot="checked-icon" /> )} </> )} - {typeof children === 'function' ? children(values) : children} + {typeof children === "function" ? children(values) : children} - {values.hasSubmenu && <IconChevronLgRight data-slot="chevron" className="absolute right-2 size-3.5" />} + {values.hasSubmenu && ( + <IconChevronLgRight data-slot="chevron" className="absolute right-2 size-3.5" /> + )} </> )} </MenuItemPrimitive> - ); -}; + ) +} export interface MenuHeaderProps extends React.ComponentProps<typeof Header> { - separator?: boolean; + separator?: boolean } const MenuHeader = ({ className, separator = false, ...props }: MenuHeaderProps) => ( <Header className={cn( - 'col-span-full px-2.5 py-2 font-semibold text-base sm:text-sm', - separator && '-mx-1 mb-1 border-b sm:px-3 sm:pb-[0.625rem]', - className + "col-span-full px-2.5 py-2 font-semibold text-base sm:text-sm", + separator && "-mx-1 mb-1 border-b sm:px-3 sm:pb-[0.625rem]", + className, )} {...props} /> -); +) -const { section, header } = dropdownSectionStyles(); +const { section, header } = dropdownSectionStyles() interface MenuSectionProps<T> extends MenuSectionPrimitiveProps<T> { - ref?: React.Ref<HTMLDivElement>; - title?: string; + ref?: React.Ref<HTMLDivElement> + title?: string } const MenuSection = <T extends object>({ className, ref, ...props }: MenuSectionProps<T>) => { return ( <MenuSectionPrimitive ref={ref} className={section({ className })} {...props}> - {'title' in props && <Header className={header()}>{props.title}</Header>} + {"title" in props && <Header className={header()}>{props.title}</Header>} <Collection items={props.items}>{props.children}</Collection> </MenuSectionPrimitive> - ); -}; - -const MenuSeparator = DropdownSeparator; -const MenuItemDetails = DropdownItemDetails; -const MenuKeyboard = DropdownKeyboard; -const MenuLabel = DropdownLabel; - -Menu.Keyboard = MenuKeyboard; -Menu.Content = MenuContent; -Menu.Header = MenuHeader; -Menu.Item = MenuItem; -Menu.Section = MenuSection; -Menu.Separator = MenuSeparator; -Menu.ItemDetails = MenuItemDetails; -Menu.Label = MenuLabel; -Menu.Trigger = MenuTrigger; -Menu.Submenu = MenuSubMenu; - -export { Menu }; -export type { MenuContentProps, MenuItemProps, MenuProps, MenuSectionProps, MenuTriggerProps }; + ) +} + +const MenuSeparator = DropdownSeparator +const MenuItemDetails = DropdownItemDetails +const MenuKeyboard = DropdownKeyboard +const MenuLabel = DropdownLabel + +Menu.Keyboard = MenuKeyboard +Menu.Content = MenuContent +Menu.Header = MenuHeader +Menu.Item = MenuItem +Menu.Section = MenuSection +Menu.Separator = MenuSeparator +Menu.ItemDetails = MenuItemDetails +Menu.Label = MenuLabel +Menu.Trigger = MenuTrigger +Menu.Submenu = MenuSubMenu + +export { Menu } +export type { MenuContentProps, MenuItemProps, MenuProps, MenuSectionProps, MenuTriggerProps } diff --git a/resources/js/components/ui/modal.tsx b/resources/js/components/ui/modal.tsx index ba4ebda..6aa91d6 100644 --- a/resources/js/components/ui/modal.tsx +++ b/resources/js/components/ui/modal.tsx @@ -1,70 +1,80 @@ -import type { DialogProps, DialogTriggerProps, ModalOverlayProps } from 'react-aria-components'; -import { DialogTrigger, ModalOverlay, Modal as ModalPrimitive, composeRenderProps } from 'react-aria-components'; -import { type VariantProps, tv } from 'tailwind-variants'; +import type { DialogProps, DialogTriggerProps, ModalOverlayProps } from "react-aria-components" +import { + DialogTrigger, + ModalOverlay, + Modal as ModalPrimitive, + composeRenderProps, +} from "react-aria-components" +import { type VariantProps, tv } from "tailwind-variants" -import { Dialog } from './dialog'; +import { Dialog } from "./dialog" const Modal = (props: DialogTriggerProps) => { - return <DialogTrigger {...props} />; -}; + return <DialogTrigger {...props} /> +} const modalOverlayStyles = tv({ base: [ - 'fixed top-0 left-0 isolate z-50 h-(--visual-viewport-height) w-full', - 'flex items-end justify-end bg-fg/15 text-center sm:items-center sm:justify-center dark:bg-bg/40', - '[--visual-viewport-vertical-padding:16px] sm:[--visual-viewport-vertical-padding:32px]' + "fixed top-0 left-0 isolate z-50 h-(--visual-viewport-height) w-full", + "flex items-end justify-end bg-fg/15 text-center sm:items-center sm:justify-center dark:bg-bg/40", + "[--visual-viewport-vertical-padding:16px] sm:[--visual-viewport-vertical-padding:32px]", ], variants: { isBlurred: { - true: 'bg-bg supports-backdrop-filter:bg-bg/15 supports-backdrop-filter:backdrop-blur dark:supports-backdrop-filter:bg-bg/40' + true: "bg-bg supports-backdrop-filter:bg-bg/15 supports-backdrop-filter:backdrop-blur dark:supports-backdrop-filter:bg-bg/40", }, isEntering: { - true: 'fade-in animate-in duration-200 ease-out' + true: "fade-in animate-in duration-200 ease-out", }, isExiting: { - true: 'fade-out animate-out ease-in' - } - } -}); + true: "fade-out animate-out ease-in", + }, + }, +}) const modalContentStyles = tv({ base: [ - 'max-h-full w-full rounded-t-2xl bg-overlay text-left align-middle text-overlay-fg shadow-lg ring-1 ring-fg/5', - 'overflow-hidden sm:rounded-2xl dark:ring-border' + "max-h-full w-full rounded-t-2xl bg-overlay text-left align-middle text-overlay-fg shadow-lg ring-1 ring-fg/5", + "overflow-hidden sm:rounded-2xl dark:ring-border", ], variants: { isEntering: { - true: ['fade-in slide-in-from-bottom animate-in duration-200 ease-out', 'sm:zoom-in-95 sm:slide-in-from-bottom-0'] + true: [ + "fade-in slide-in-from-bottom animate-in duration-200 ease-out", + "sm:zoom-in-95 sm:slide-in-from-bottom-0", + ], }, isExiting: { - true: ['slide-out-to-bottom sm:slide-out-to-bottom-0 sm:zoom-out-95 animate-out duration-150 ease-in'] + true: [ + "slide-out-to-bottom sm:slide-out-to-bottom-0 sm:zoom-out-95 animate-out duration-150 ease-in", + ], }, size: { - xs: 'sm:max-w-xs', - sm: 'sm:max-w-sm', - md: 'sm:max-w-md', - lg: 'sm:max-w-lg', - xl: 'sm:max-w-xl', - '2xl': 'sm:max-w-2xl', - '3xl': 'sm:max-w-3xl', - '4xl': 'sm:max-w-4xl', - '5xl': 'sm:max-w-5xl' - } + xs: "sm:max-w-xs", + sm: "sm:max-w-sm", + md: "sm:max-w-md", + lg: "sm:max-w-lg", + xl: "sm:max-w-xl", + "2xl": "sm:max-w-2xl", + "3xl": "sm:max-w-3xl", + "4xl": "sm:max-w-4xl", + "5xl": "sm:max-w-5xl", + }, }, defaultVariants: { - size: 'lg' - } -}); + size: "lg", + }, +}) interface ModalContentProps - extends Omit<ModalOverlayProps, 'className' | 'children'>, - Pick<DialogProps, 'aria-label' | 'aria-labelledby' | 'role' | 'children'>, + extends Omit<ModalOverlayProps, "className" | "children">, + Pick<DialogProps, "aria-label" | "aria-labelledby" | "role" | "children">, VariantProps<typeof modalContentStyles> { - closeButton?: boolean; - isBlurred?: boolean; + closeButton?: boolean + isBlurred?: boolean classNames?: { - overlay?: ModalOverlayProps['className']; - content?: ModalOverlayProps['className']; - }; + overlay?: ModalOverlayProps["className"] + content?: ModalOverlayProps["className"] + } } const ModalContent = ({ @@ -73,11 +83,11 @@ const ModalContent = ({ isBlurred = false, children, size, - role = 'dialog', + role = "dialog", closeButton = true, ...props }: ModalContentProps) => { - const isDismissable = isDismissableInternal ?? role !== 'alertdialog'; + const isDismissable = isDismissableInternal ?? role !== "alertdialog" return ( <ModalOverlay @@ -86,8 +96,8 @@ const ModalContent = ({ modalOverlayStyles({ ...renderProps, isBlurred, - className - }) + className, + }), )} {...props} > @@ -97,39 +107,39 @@ const ModalContent = ({ modalContentStyles({ ...renderProps, size, - className - }) + className, + }), )} {...props} > <Dialog role={role}> {(values) => ( <> - {typeof children === 'function' ? children(values) : children} + {typeof children === "function" ? children(values) : children} {closeButton && <Dialog.CloseIndicator isDismissable={isDismissable} />} </> )} </Dialog> </ModalPrimitive> </ModalOverlay> - ); -}; + ) +} -const ModalTrigger = Dialog.Trigger; -const ModalHeader = Dialog.Header; -const ModalTitle = Dialog.Title; -const ModalDescription = Dialog.Description; -const ModalFooter = Dialog.Footer; -const ModalBody = Dialog.Body; -const ModalClose = Dialog.Close; +const ModalTrigger = Dialog.Trigger +const ModalHeader = Dialog.Header +const ModalTitle = Dialog.Title +const ModalDescription = Dialog.Description +const ModalFooter = Dialog.Footer +const ModalBody = Dialog.Body +const ModalClose = Dialog.Close -Modal.Trigger = ModalTrigger; -Modal.Header = ModalHeader; -Modal.Title = ModalTitle; -Modal.Description = ModalDescription; -Modal.Footer = ModalFooter; -Modal.Body = ModalBody; -Modal.Close = ModalClose; -Modal.Content = ModalContent; +Modal.Trigger = ModalTrigger +Modal.Header = ModalHeader +Modal.Title = ModalTitle +Modal.Description = ModalDescription +Modal.Footer = ModalFooter +Modal.Body = ModalBody +Modal.Close = ModalClose +Modal.Content = ModalContent -export { Modal }; +export { Modal } diff --git a/resources/js/components/ui/navbar.tsx b/resources/js/components/ui/navbar.tsx index 78f89ff..4b80a00 100644 --- a/resources/js/components/ui/navbar.tsx +++ b/resources/js/components/ui/navbar.tsx @@ -1,57 +1,57 @@ -import { createContext, use, useCallback, useId, useMemo, useState } from 'react'; +import { createContext, use, useCallback, useId, useMemo, useState } from "react" -import { IconHamburger } from 'justd-icons'; -import { LayoutGroup, motion } from 'motion/react'; -import type { LinkProps } from 'react-aria-components'; -import { Link, composeRenderProps } from 'react-aria-components'; -import { type VariantProps, tv } from 'tailwind-variants'; +import { IconHamburger } from "justd-icons" +import { LayoutGroup, motion } from "motion/react" +import type { LinkProps } from "react-aria-components" +import { Link, composeRenderProps } from "react-aria-components" +import { type VariantProps, tv } from "tailwind-variants" -import { cn } from '@/utils/classes'; -import { useMediaQuery } from '@/utils/use-media-query'; -import { Button, type ButtonProps } from './button'; -import { composeTailwindRenderProps } from './primitive'; -import { Sheet } from './sheet'; +import { cn } from "@/utils/classes" +import { useMediaQuery } from "@/utils/use-media-query" +import { Button, type ButtonProps } from "./button" +import { composeTailwindRenderProps } from "./primitive" +import { Sheet } from "./sheet" type NavbarOptions = { - side?: 'left' | 'right'; - isSticky?: boolean; - intent?: 'navbar' | 'floating' | 'inset'; -}; + side?: "left" | "right" + isSticky?: boolean + intent?: "navbar" | "floating" | "inset" +} type NavbarContextProps = { - open: boolean; - setOpen: (open: boolean) => void; - isCompact: boolean; - toggleNavbar: () => void; -} & NavbarOptions; + open: boolean + setOpen: (open: boolean) => void + isCompact: boolean + toggleNavbar: () => void +} & NavbarOptions -const NavbarContext = createContext<NavbarContextProps | null>(null); +const NavbarContext = createContext<NavbarContextProps | null>(null) function useNavbar() { - const context = use(NavbarContext); + const context = use(NavbarContext) if (!context) { - throw new Error('useNavbar must be used within a Navbar.'); + throw new Error("useNavbar must be used within a Navbar.") } - return context; + return context } -interface NavbarProps extends React.ComponentProps<'header'>, NavbarOptions { - defaultOpen?: boolean; - isOpen?: boolean; - onOpenChange?: (open: boolean) => void; +interface NavbarProps extends React.ComponentProps<"header">, NavbarOptions { + defaultOpen?: boolean + isOpen?: boolean + onOpenChange?: (open: boolean) => void } const navbarStyles = tv({ - base: 'relative isolate flex w-full flex-col', + base: "relative isolate flex w-full flex-col", variants: { intent: { - floating: 'px-2.5 pt-2', - navbar: '', - inset: 'min-h-svh bg-navbar dark:bg-bg' - } - } -}); + floating: "px-2.5 pt-2", + navbar: "", + inset: "min-h-svh bg-navbar dark:bg-bg", + }, + }, +}) const Navbar = ({ children, @@ -59,29 +59,29 @@ const Navbar = ({ onOpenChange: setOpenProp, defaultOpen = false, className, - side = 'left', + side = "left", isSticky = false, - intent = 'navbar', + intent = "navbar", ...props }: NavbarProps) => { - const isCompact = useMediaQuery('(max-width: 768px)'); - const [_open, _setOpen] = useState(defaultOpen); - const open = openProp ?? _open; + const isCompact = useMediaQuery("(max-width: 768px)") + const [_open, _setOpen] = useState(defaultOpen) + const open = openProp ?? _open const setOpen = useCallback( (value: boolean | ((value: boolean) => boolean)) => { if (setOpenProp) { - return setOpenProp?.(typeof value === 'function' ? value(open) : value); + return setOpenProp?.(typeof value === "function" ? value(open) : value) } - _setOpen(value); + _setOpen(value) }, - [setOpenProp, open] - ); + [setOpenProp, open], + ) const toggleNavbar = useCallback(() => { - setOpen((open) => !open); - }, [setOpen]); + setOpen((open) => !open) + }, [setOpen]) const contextValue = useMemo<NavbarContextProps>( () => ({ @@ -91,49 +91,53 @@ const Navbar = ({ toggleNavbar, intent, isSticky, - side + side, }), - [open, setOpen, isCompact, toggleNavbar, intent, isSticky, side] - ); + [open, setOpen, isCompact, toggleNavbar, intent, isSticky, side], + ) return ( <NavbarContext value={contextValue}> - <header data-navbar-intent={intent} className={navbarStyles({ intent, className })} {...props}> + <header + data-navbar-intent={intent} + className={navbarStyles({ intent, className })} + {...props} + > {children} </header> </NavbarContext> - ); -}; + ) +} const navStyles = tv({ base: [ - 'group peer hidden h-(--navbar-height) w-full items-center px-4 [--navbar-height:3.5rem] md:flex', - '[&>div]:mx-auto [&>div]:w-full [&>div]:max-w-[1680px] [&>div]:items-center md:[&>div]:flex' + "group peer hidden h-(--navbar-height) w-full items-center px-4 [--navbar-height:3.5rem] md:flex", + "[&>div]:mx-auto [&>div]:w-full [&>div]:max-w-[1680px] [&>div]:items-center md:[&>div]:flex", ], variants: { isSticky: { - true: 'sticky top-0 z-40' + true: "sticky top-0 z-40", }, intent: { floating: - 'mx-auto w-full max-w-7xl rounded-xl border bg-navbar text-navbar-fg md:px-4 2xl:max-w-(--breakpoint-2xl)', - navbar: 'border-b bg-navbar text-navbar-fg md:px-6', + "mx-auto w-full max-w-7xl rounded-xl border bg-navbar text-navbar-fg md:px-4 2xl:max-w-(--breakpoint-2xl)", + navbar: "border-b bg-navbar text-navbar-fg md:px-6", inset: [ - 'mx-auto md:px-6', - '[&>div]:mx-auto [&>div]:w-full [&>div]:items-center md:[&>div]:flex 2xl:[&>div]:max-w-(--breakpoint-2xl)' - ] - } - } -}); - -interface NavbarNavProps extends React.ComponentProps<'div'> { - intent?: 'navbar' | 'floating' | 'inset'; - isSticky?: boolean; - side?: 'left' | 'right'; - useDefaultResponsive?: boolean; + "mx-auto md:px-6", + "[&>div]:mx-auto [&>div]:w-full [&>div]:items-center md:[&>div]:flex 2xl:[&>div]:max-w-(--breakpoint-2xl)", + ], + }, + }, +}) + +interface NavbarNavProps extends React.ComponentProps<"div"> { + intent?: "navbar" | "floating" | "inset" + isSticky?: boolean + side?: "left" | "right" + useDefaultResponsive?: boolean } const NavbarNav = ({ useDefaultResponsive = true, className, ref, ...props }: NavbarNavProps) => { - const { isCompact, side, intent, isSticky, open, setOpen } = useNavbar(); + const { isCompact, side, intent, isSticky, open, setOpen } = useNavbar() if (isCompact && useDefaultResponsive) { return ( @@ -143,99 +147,108 @@ const NavbarNav = ({ useDefaultResponsive = true, className, ref, ...props }: Na aria-label="Compact Navbar" data-navbar="compact" classNames={{ - content: 'text-fg [&>button]:hidden' + content: "text-fg [&>button]:hidden", }} - isFloat={intent === 'floating'} + isFloat={intent === "floating"} > <Sheet.Body className="px-2 md:px-4">{props.children}</Sheet.Body> </Sheet.Content> </Sheet> - ); + ) } return ( - <div data-navbar-nav="true" ref={ref} className={navStyles({ isSticky, intent, className })} {...props}> + <div + data-navbar-nav="true" + ref={ref} + className={navStyles({ isSticky, intent, className })} + {...props} + > <div>{props.children}</div> </div> - ); -}; + ) +} interface NavbarTriggerProps extends ButtonProps { - ref?: React.RefObject<HTMLButtonElement>; + ref?: React.RefObject<HTMLButtonElement> } const NavbarTrigger = ({ className, onPress, ref, ...props }: NavbarTriggerProps) => { - const { toggleNavbar } = useNavbar(); + const { toggleNavbar } = useNavbar() return ( <Button ref={ref} data-navbar-trigger="true" appearance="plain" - aria-label={props['aria-label'] || 'Toggle Navbar'} + aria-label={props["aria-label"] || "Toggle Navbar"} size="square-petite" className={className} onPress={(event) => { - onPress?.(event); - toggleNavbar(); + onPress?.(event) + toggleNavbar() }} {...props} > <IconHamburger /> <span className="sr-only">Toggle Navbar</span> </Button> - ); -}; + ) +} -const NavbarSection = ({ className, ...props }: React.ComponentProps<'div'>) => { - const { isCompact } = useNavbar(); - const id = useId(); +const NavbarSection = ({ className, ...props }: React.ComponentProps<"div">) => { + const { isCompact } = useNavbar() + const id = useId() return ( <LayoutGroup id={id}> <div data-navbar-section="true" - className={cn('flex', isCompact ? 'flex-col gap-y-4' : 'flex-row items-center gap-x-3', className)} + className={cn( + "flex", + isCompact ? "flex-col gap-y-4" : "flex-row items-center gap-x-3", + className, + )} {...props} > {props.children} </div> </LayoutGroup> - ); -}; + ) +} const navItemStyles = tv({ base: [ - '*:data-[slot=icon]:-mx-0.5 relative flex cursor-pointer items-center gap-x-2 px-2 text-muted-fg no-underline outline-hidden transition-colors md:text-sm forced-colors:transform-none forced-colors:outline-0 forced-colors:data-disabled:text-[GrayText]', - 'data-focused:text-fg data-hovered:text-fg data-pressed:text-fg data-focus-visible:outline-1 data-focus-visible:outline-primary', - '**:data-[slot=chevron]:size-4 **:data-[slot=chevron]:transition-transform', - 'data-pressed:**:data-[slot=chevron]:rotate-180 *:data-[slot=icon]:size-4 *:data-[slot=icon]:shrink-0', - 'data-disabled:cursor-default data-disabled:opacity-50 data-disabled:forced-colors:text-[GrayText]' + "*:data-[slot=icon]:-mx-0.5 relative flex cursor-pointer items-center gap-x-2 px-2 text-muted-fg no-underline outline-hidden transition-colors md:text-sm forced-colors:transform-none forced-colors:outline-0 forced-colors:data-disabled:text-[GrayText]", + "data-focused:text-fg data-hovered:text-fg data-pressed:text-fg data-focus-visible:outline-1 data-focus-visible:outline-primary", + "**:data-[slot=chevron]:size-4 **:data-[slot=chevron]:transition-transform", + "data-pressed:**:data-[slot=chevron]:rotate-180 *:data-[slot=icon]:size-4 *:data-[slot=icon]:shrink-0", + "data-disabled:cursor-default data-disabled:opacity-50 data-disabled:forced-colors:text-[GrayText]", ], variants: { isCurrent: { - true: 'cursor-default text-navbar-fg' - } - } -}); + true: "cursor-default text-navbar-fg", + }, + }, +}) interface NavbarItemProps extends LinkProps { - isCurrent?: boolean; + isCurrent?: boolean } const NavbarItem = ({ className, isCurrent, ...props }: NavbarItemProps) => { - const { intent, isCompact } = useNavbar(); + const { intent, isCompact } = useNavbar() return ( <Link data-navbar-item="true" - aria-current={isCurrent ? 'page' : undefined} + aria-current={isCurrent ? "page" : undefined} className={composeRenderProps(className, (className, ...renderProps) => - navItemStyles({ ...renderProps, isCurrent, className }) + navItemStyles({ ...renderProps, isCurrent, className }), )} {...props} > {(values) => ( <> - {typeof props.children === 'function' ? props.children(values) : props.children} + {typeof props.children === "function" ? props.children(values) : props.children} - {(isCurrent || values.isCurrent) && !isCompact && intent !== 'floating' && ( + {(isCurrent || values.isCurrent) && !isCompact && intent !== "floating" && ( <motion.span layoutId="current-indicator" data-slot="current-indicator" @@ -245,76 +258,83 @@ const NavbarItem = ({ className, isCurrent, ...props }: NavbarItemProps) => { </> )} </Link> - ); -}; + ) +} const NavbarLogo = ({ className, ...props }: LinkProps) => { return ( <Link className={composeTailwindRenderProps( className, - 'relative flex items-center gap-x-2 px-2 py-4 text-fg data-focus-visible:outline-1 data-focus-visible:outline-primary data-focused:outline-hidden md:mr-4 md:px-0 md:py-0' + "relative flex items-center gap-x-2 px-2 py-4 text-fg data-focus-visible:outline-1 data-focus-visible:outline-primary data-focused:outline-hidden md:mr-4 md:px-0 md:py-0", )} {...props} /> - ); -}; + ) +} -const NavbarFlex = ({ className, ref, ...props }: React.ComponentProps<'div'>) => { - return <div ref={ref} className={cn('flex items-center gap-2 md:gap-3', className)} {...props} />; -}; +const NavbarFlex = ({ className, ref, ...props }: React.ComponentProps<"div">) => { + return <div ref={ref} className={cn("flex items-center gap-2 md:gap-3", className)} {...props} /> +} const compactStyles = tv({ - base: 'flex justify-between bg-navbar text-navbar-fg peer-has-[[data-navbar-intent=floating]]:border md:hidden', + base: "flex justify-between bg-navbar text-navbar-fg peer-has-[[data-navbar-intent=floating]]:border md:hidden", variants: { intent: { - floating: 'h-12 rounded-lg border px-3.5', - inset: 'h-14 border-b px-4', - navbar: 'h-14 border-b px-4' - } - } -}); + floating: "h-12 rounded-lg border px-3.5", + inset: "h-14 border-b px-4", + navbar: "h-14 border-b px-4", + }, + }, +}) -interface NavbarCompactProps extends React.ComponentProps<'div'>, VariantProps<typeof compactStyles> { - ref?: React.RefObject<HTMLDivElement>; +interface NavbarCompactProps + extends React.ComponentProps<"div">, + VariantProps<typeof compactStyles> { + ref?: React.RefObject<HTMLDivElement> } const NavbarCompact = ({ className, ref, ...props }: NavbarCompactProps) => { - const { intent } = useNavbar(); - return <div ref={ref} className={compactStyles({ intent, className })} {...props} />; -}; + const { intent } = useNavbar() + return <div ref={ref} className={compactStyles({ intent, className })} {...props} /> +} const insetStyles = tv({ - base: 'grow', + base: "grow", variants: { intent: { - floating: '', - inset: 'bg-bg md:rounded-lg md:shadow-xs md:ring-1 md:ring-fg/15 dark:bg-navbar md:dark:ring-border', - navbar: '' - } - } -}); + floating: "", + inset: + "bg-bg md:rounded-lg md:shadow-xs md:ring-1 md:ring-fg/15 dark:bg-navbar md:dark:ring-border", + navbar: "", + }, + }, +}) -const NavbarInset = ({ className, ref, ...props }: React.ComponentProps<'div'>) => { - const { intent } = useNavbar(); +const NavbarInset = ({ className, ref, ...props }: React.ComponentProps<"div">) => { + const { intent } = useNavbar() return ( <main ref={ref} data-navbar-intent={intent} - className={cn('flex flex-1 flex-col', intent === 'inset' && 'bg-navbar pb-2 md:px-2 dark:bg-bg', className)} + className={cn( + "flex flex-1 flex-col", + intent === "inset" && "bg-navbar pb-2 md:px-2 dark:bg-bg", + className, + )} > <div className={insetStyles({ intent, className })}>{props.children}</div> </main> - ); -}; - -Navbar.Nav = NavbarNav; -Navbar.Inset = NavbarInset; -Navbar.Compact = NavbarCompact; -Navbar.Flex = NavbarFlex; -Navbar.Trigger = NavbarTrigger; -Navbar.Logo = NavbarLogo; -Navbar.Item = NavbarItem; -Navbar.Section = NavbarSection; - -export { Navbar }; -export type { NavbarCompactProps, NavbarItemProps, NavbarNavProps, NavbarProps, NavbarTriggerProps }; + ) +} + +Navbar.Nav = NavbarNav +Navbar.Inset = NavbarInset +Navbar.Compact = NavbarCompact +Navbar.Flex = NavbarFlex +Navbar.Trigger = NavbarTrigger +Navbar.Logo = NavbarLogo +Navbar.Item = NavbarItem +Navbar.Section = NavbarSection + +export { Navbar } +export type { NavbarCompactProps, NavbarItemProps, NavbarNavProps, NavbarProps, NavbarTriggerProps } diff --git a/resources/js/components/ui/pagination.tsx b/resources/js/components/ui/pagination.tsx index 8d6ef9b..c1d1ff2 100644 --- a/resources/js/components/ui/pagination.tsx +++ b/resources/js/components/ui/pagination.tsx @@ -3,32 +3,38 @@ import { IconChevronLgRight, IconChevronWallLeft, IconChevronWallRight, - IconDotsHorizontal -} from 'justd-icons'; -import type { ListBoxItemProps, ListBoxProps, ListBoxSectionProps } from 'react-aria-components'; -import { ListBox, ListBoxItem, ListBoxSection, Separator, composeRenderProps } from 'react-aria-components'; + IconDotsHorizontal, +} from "justd-icons" +import type { ListBoxItemProps, ListBoxProps, ListBoxSectionProps } from "react-aria-components" +import { + ListBox, + ListBoxItem, + ListBoxSection, + Separator, + composeRenderProps, +} from "react-aria-components" -import { cn } from '@/utils/classes'; -import { tv } from 'tailwind-variants'; -import { buttonStyles } from './button'; +import { cn } from "@/utils/classes" +import { tv } from "tailwind-variants" +import { buttonStyles } from "./button" const paginationStyles = tv({ slots: { - pagination: 'mx-auto flex w-full justify-center gap-[5px]', - section: 'flex h-9 gap-[5px]', - list: 'flex flex-row items-center gap-[5px]', + pagination: "mx-auto flex w-full justify-center gap-[5px]", + section: "flex h-9 gap-[5px]", + list: "flex flex-row items-center gap-[5px]", itemButton: - 'cursor-pointer font-normal text-fg data-focus-visible:border-primary data-focus-visible:bg-primary/10 data-focus-visible:ring-4 data-focus-visible:ring-primary/20', - itemLabel: 'grid h-9 place-content-center px-3.5 tabular-nums', - itemSeparator: 'grid h-9 place-content-center', + "cursor-pointer font-normal text-fg data-focus-visible:border-primary data-focus-visible:bg-primary/10 data-focus-visible:ring-4 data-focus-visible:ring-primary/20", + itemLabel: "grid h-9 place-content-center px-3.5 tabular-nums", + itemSeparator: "grid h-9 place-content-center", itemEllipsis: - 'flex size-9 items-center justify-center rounded-lg border border-transparent data-focus-visible:border-primary data-focus-visible:bg-primary/10 data-focused:outline-hidden data-focus-visible:ring-4 data-focus-visible:ring-primary/20', - itemEllipsisIcon: 'flex size-9 items-center justify-center', + "flex size-9 items-center justify-center rounded-lg border border-transparent data-focus-visible:border-primary data-focus-visible:bg-primary/10 data-focused:outline-hidden data-focus-visible:ring-4 data-focus-visible:ring-primary/20", + itemEllipsisIcon: "flex size-9 items-center justify-center", defaultItem: - 'cursor-pointer font-normal tabular-nums disabled:cursor-default disabled:opacity-100 data-focus-visible:border-primary data-focus-visible:bg-primary/10 data-focus-visible:ring-4 data-focus-visible:ring-primary/20', - itemSeparatorLine: 'h-5 w-[1.5px] shrink-0 rotate-[14deg] bg-secondary-fg/40' - } -}); + "cursor-pointer font-normal tabular-nums disabled:cursor-default disabled:opacity-100 data-focus-visible:border-primary data-focus-visible:bg-primary/10 data-focus-visible:ring-4 data-focus-visible:ring-primary/20", + itemSeparatorLine: "h-5 w-[1.5px] shrink-0 rotate-[14deg] bg-secondary-fg/40", + }, +}) const { pagination, @@ -40,62 +46,66 @@ const { itemEllipsis, itemEllipsisIcon, defaultItem, - itemSeparatorLine -} = paginationStyles(); + itemSeparatorLine, +} = paginationStyles() -type PagginationProps = React.ComponentProps<'nav'>; +type PagginationProps = React.ComponentProps<"nav"> const Pagination = ({ className, ref, ...props }: PagginationProps) => ( <nav aria-label="pagination" ref={ref} className={pagination({ className })} {...props} /> -); +) interface PaginationSectionProps<T> extends ListBoxSectionProps<T> { - ref?: React.RefObject<HTMLElement>; + ref?: React.RefObject<HTMLElement> } -const PaginationSection = <T extends object>({ className, ref, ...props }: PaginationSectionProps<T>) => ( +const PaginationSection = <T extends object>({ + className, + ref, + ...props +}: PaginationSectionProps<T>) => ( <ListBoxSection ref={ref} {...props} className={section({ className })} /> -); +) interface PaginationListProps<T> extends ListBoxProps<T> { - ref?: React.RefObject<HTMLDivElement>; + ref?: React.RefObject<HTMLDivElement> } const PaginationList = <T extends object>({ className, ref, ...props }: PaginationListProps<T>) => { return ( <ListBox ref={ref} orientation="horizontal" - aria-label={props['aria-label'] || 'Pagination'} + aria-label={props["aria-label"] || "Pagination"} layout="grid" className={composeRenderProps(className, (className) => list({ className }))} {...props} /> - ); -}; + ) +} const renderListItem = ( props: ListBoxItemProps & { - textValue?: string; - 'aria-current'?: string | undefined; - isDisabled?: boolean; - className?: string; + textValue?: string + "aria-current"?: string | undefined + isDisabled?: boolean + className?: string }, - children: React.ReactNode -) => <ListBoxItem {...props}>{children}</ListBoxItem>; + children: React.ReactNode, +) => <ListBoxItem {...props}>{children}</ListBoxItem> interface PaginationItemProps extends ListBoxItemProps { - children?: React.ReactNode; - className?: string; - intent?: 'primary' | 'secondary'; - size?: 'medium' | 'large' | 'square-petite' | 'extra-small' | 'small'; - shape?: 'square' | 'circle'; - appearance?: 'solid' | 'outline' | 'plain'; - isCurrent?: boolean; - segment?: 'label' | 'separator' | 'ellipsis' | 'default' | 'last' | 'first' | 'previous' | 'next'; + children?: React.ReactNode + className?: string + intent?: "primary" | "secondary" + size?: "medium" | "large" | "square-petite" | "extra-small" | "small" + shape?: "square" | "circle" + appearance?: "solid" | "outline" | "plain" + isCurrent?: boolean + segment?: "label" | "separator" | "ellipsis" | "default" | "last" | "first" | "previous" | "next" } const PaginationItem = ({ - segment = 'default', - size = 'small', - appearance = 'outline', + segment = "default", + size = "small", + appearance = "outline", intent, className, isCurrent, @@ -103,90 +113,94 @@ const PaginationItem = ({ ...props }: PaginationItemProps) => { const textValue = - typeof children === 'string' ? children : typeof children === 'number' ? children.toString() : undefined; + typeof children === "string" + ? children + : typeof children === "number" + ? children.toString() + : undefined const renderPaginationIndicator = (indicator: React.ReactNode) => renderListItem( { textValue: segment, - 'aria-current': isCurrent ? 'page' : undefined, + "aria-current": isCurrent ? "page" : undefined, isDisabled: isCurrent, className: cn( buttonStyles({ - appearance: 'outline', - size: 'small', - className: itemButton() + appearance: "outline", + size: "small", + className: itemButton(), }), - className + className, ), - ...props + ...props, }, - indicator - ); + indicator, + ) switch (segment) { - case 'label': + case "label": return renderListItem( { textValue: textValue, className: itemLabel({ className }), - ...props + ...props, }, - children - ); - case 'separator': + children, + ) + case "separator": return renderListItem( { - textValue: 'Separator', + textValue: "Separator", className: itemSeparator({ className }), - ...props + ...props, }, - <Separator orientation="vertical" className={itemSeparatorLine()} /> - ); - case 'ellipsis': + <Separator orientation="vertical" className={itemSeparatorLine()} />, + ) + case "ellipsis": return renderListItem( { - textValue: 'More pages', + textValue: "More pages", className: itemEllipsis({ className }), - ...props + ...props, }, <span aria-hidden className={itemEllipsisIcon({ className })}> <IconDotsHorizontal /> - </span> - ); - case 'previous': - return renderPaginationIndicator(<IconChevronLgLeft />); - case 'next': - return renderPaginationIndicator(<IconChevronLgRight />); - case 'first': - return renderPaginationIndicator(<IconChevronWallLeft />); - case 'last': - return renderPaginationIndicator(<IconChevronWallRight />); + </span>, + ) + case "previous": + return renderPaginationIndicator(<IconChevronLgLeft />) + case "next": + return renderPaginationIndicator(<IconChevronLgRight />) + case "first": + return renderPaginationIndicator(<IconChevronWallLeft />) + case "last": + return renderPaginationIndicator(<IconChevronWallRight />) default: return renderListItem( { textValue: textValue, - 'aria-current': isCurrent ? 'page' : undefined, + "aria-current": isCurrent ? "page" : undefined, isDisabled: isCurrent, className: cn( buttonStyles({ - intent: isCurrent ? 'primary' : intent, - appearance: isCurrent ? 'solid' : appearance, + intent: isCurrent ? "primary" : intent, + appearance: isCurrent ? "solid" : appearance, size, - className: defaultItem({ className }) + className: defaultItem({ className }), }), - className + className, ), - ...props + ...props, }, - children - ); + children, + ) } -}; +} -Pagination.Item = PaginationItem; -Pagination.List = PaginationList; -Pagination.Section = PaginationSection; +Pagination.Item = PaginationItem +Pagination.List = PaginationList +Pagination.Section = PaginationSection -export { Pagination }; -export type { PagginationProps, PaginationItemProps, PaginationListProps, PaginationSectionProps }; +export { Pagination } +export type { PagginationProps, PaginationItemProps, PaginationListProps, PaginationSectionProps } diff --git a/resources/js/components/ui/popover.tsx b/resources/js/components/ui/popover.tsx index 65b92b0..5cf6111 100644 --- a/resources/js/components/ui/popover.tsx +++ b/resources/js/components/ui/popover.tsx @@ -1,8 +1,8 @@ import type { DialogTriggerProps, ModalOverlayProps, - PopoverProps as PopoverPrimitiveProps -} from 'react-aria-components'; + PopoverProps as PopoverPrimitiveProps, +} from "react-aria-components" import { type DialogProps, DialogTrigger, @@ -12,96 +12,104 @@ import { PopoverContext, Popover as PopoverPrimitive, composeRenderProps, - useSlottedContext -} from 'react-aria-components'; -import { tv } from 'tailwind-variants'; + useSlottedContext, +} from "react-aria-components" +import { tv } from "tailwind-variants" -import { cn } from '@/utils/classes'; -import { useMediaQuery } from '@/utils/use-media-query'; -import { twMerge } from 'tailwind-merge'; -import type { DialogBodyProps, DialogFooterProps, DialogHeaderProps, DialogTitleProps } from './dialog'; -import { Dialog } from './dialog'; +import { cn } from "@/utils/classes" +import { useMediaQuery } from "@/utils/use-media-query" +import { twMerge } from "tailwind-merge" +import type { + DialogBodyProps, + DialogFooterProps, + DialogHeaderProps, + DialogTitleProps, +} from "./dialog" +import { Dialog } from "./dialog" -type PopoverProps = DialogTriggerProps; +type PopoverProps = DialogTriggerProps const Popover = (props: PopoverProps) => { - return <DialogTrigger {...props} />; -}; + return <DialogTrigger {...props} /> +} const PopoverTitle = ({ level = 2, className, ...props }: DialogTitleProps) => ( - <Dialog.Title className={twMerge('sm:leading-none', level === 2 && 'sm:text-lg', className)} {...props} /> -); + <Dialog.Title + className={twMerge("sm:leading-none", level === 2 && "sm:text-lg", className)} + {...props} + /> +) const PopoverHeader = ({ className, ...props }: DialogHeaderProps) => ( - <Dialog.Header className={twMerge('sm:p-4', className)} {...props} /> -); + <Dialog.Header className={twMerge("sm:p-4", className)} {...props} /> +) const PopoverFooter = ({ className, ...props }: DialogFooterProps) => ( - <Dialog.Footer className={cn('sm:p-4', className)} {...props} /> -); + <Dialog.Footer className={cn("sm:p-4", className)} {...props} /> +) const PopoverBody = ({ className, ref, ...props }: DialogBodyProps) => ( - <Dialog.Body ref={ref} className={cn('sm:px-4 sm:pt-0', className)} {...props} /> -); + <Dialog.Body ref={ref} className={cn("sm:px-4 sm:pt-0", className)} {...props} /> +) const content = tv({ base: [ - 'peer/popover-content max-w-xs rounded-xl border bg-overlay bg-clip-padding text-overlay-fg shadow-xs transition-transform [scrollbar-width:thin] sm:max-w-3xl sm:text-sm dark:backdrop-saturate-200 forced-colors:bg-[Canvas] [&::-webkit-scrollbar]:size-0.5' + "peer/popover-content max-w-xs rounded-xl border bg-overlay bg-clip-padding text-overlay-fg shadow-xs transition-transform [scrollbar-width:thin] sm:max-w-3xl sm:text-sm dark:backdrop-saturate-200 forced-colors:bg-[Canvas] [&::-webkit-scrollbar]:size-0.5", ], variants: { isPicker: { - true: 'max-h-72 min-w-(--trigger-width) overflow-y-auto', - false: 'min-w-80' + true: "max-h-72 min-w-(--trigger-width) overflow-y-auto", + false: "min-w-80", }, isMenu: { - true: '' + true: "", }, isEntering: { true: [ - 'fade-in animate-in duration-150 ease-out', - 'data-[placement=left]:slide-in-from-right-1 data-[placement=right]:slide-in-from-left-1 data-[placement=top]:slide-in-from-bottom-1 data-[placement=bottom]:slide-in-from-top-1' - ] + "fade-in animate-in duration-150 ease-out", + "data-[placement=left]:slide-in-from-right-1 data-[placement=right]:slide-in-from-left-1 data-[placement=top]:slide-in-from-bottom-1 data-[placement=bottom]:slide-in-from-top-1", + ], }, isExiting: { true: [ - 'fade-out animate-out duration-100 ease-in', - 'data-[placement=left]:slide-out-to-right-1 data-[placement=right]:slide-out-to-left-1 data-[placement=top]:slide-out-to-bottom-1 data-[placement=bottom]:slide-out-to-top-1' - ] - } - } -}); + "fade-out animate-out duration-100 ease-in", + "data-[placement=left]:slide-out-to-right-1 data-[placement=right]:slide-out-to-left-1 data-[placement=top]:slide-out-to-bottom-1 data-[placement=bottom]:slide-out-to-top-1", + ], + }, + }, +}) const drawer = tv({ base: [ - 'fixed top-auto bottom-0 z-50 max-h-full w-full max-w-2xl border border-b-transparent bg-overlay outline-hidden' + "fixed top-auto bottom-0 z-50 max-h-full w-full max-w-2xl border border-b-transparent bg-overlay outline-hidden", ], variants: { isMenu: { - true: 'rounded-t-xl p-0 [&_[role=dialog]]:*:not-has-[[data-slot=dialog-body]]:px-1', - false: 'rounded-t-2xl' + true: "rounded-t-xl p-0 [&_[role=dialog]]:*:not-has-[[data-slot=dialog-body]]:px-1", + false: "rounded-t-2xl", }, isEntering: { true: [ - '[transition:transform_0.5s_cubic-bezier(0.32,_0.72,_0,_1)] [will-change:transform]', - 'fade-in-0 slide-in-from-bottom-56 animate-in duration-200', - '[transition:translate3d(0,_100%,_0)]', - 'sm:slide-in-from-bottom-auto sm:slide-in-from-top-[20%]' - ] + "[transition:transform_0.5s_cubic-bezier(0.32,_0.72,_0,_1)] [will-change:transform]", + "fade-in-0 slide-in-from-bottom-56 animate-in duration-200", + "[transition:translate3d(0,_100%,_0)]", + "sm:slide-in-from-bottom-auto sm:slide-in-from-top-[20%]", + ], }, isExiting: { - true: 'slide-out-to-bottom-56 animate-out duration-200 ease-in' - } - } -}); + true: "slide-out-to-bottom-56 animate-out duration-200 ease-in", + }, + }, +}) interface PopoverContentProps - extends Omit<PopoverPrimitiveProps, 'children' | 'className'>, - Omit<ModalOverlayProps, 'className'>, - Pick<DialogProps, 'aria-label' | 'aria-labelledby'> { - children: React.ReactNode; - showArrow?: boolean; - style?: React.CSSProperties; - respectScreen?: boolean; - className?: string | ((values: { defaultClassName?: string }) => string); + extends Omit<PopoverPrimitiveProps, "children" | "className">, + Omit<ModalOverlayProps, "className">, + Pick<DialogProps, "aria-label" | "aria-labelledby"> { + children: React.ReactNode + showArrow?: boolean + style?: React.CSSProperties + respectScreen?: boolean + className?: string | ((values: { defaultClassName?: string }) => string) } const PopoverContent = ({ @@ -111,13 +119,13 @@ const PopoverContent = ({ className, ...props }: PopoverContentProps) => { - const isMobile = useMediaQuery('(max-width: 600px)'); - const popoverContext = useSlottedContext(PopoverContext)!; - const isMenuTrigger = popoverContext?.trigger === 'MenuTrigger'; - const isSubmenuTrigger = popoverContext?.trigger === 'SubmenuTrigger'; - const isMenu = isMenuTrigger || isSubmenuTrigger; - const offset = showArrow ? 12 : 8; - const effectiveOffset = isSubmenuTrigger ? offset - 5 : offset; + const isMobile = useMediaQuery("(max-width: 600px)") + const popoverContext = useSlottedContext(PopoverContext)! + const isMenuTrigger = popoverContext?.trigger === "MenuTrigger" + const isSubmenuTrigger = popoverContext?.trigger === "SubmenuTrigger" + const isMenu = isMenuTrigger || isSubmenuTrigger + const offset = showArrow ? 12 : 8 + const effectiveOffset = isSubmenuTrigger ? offset - 5 : offset return isMobile && respectScreen ? ( <ModalOverlay className="fixed top-0 left-0 isolate z-50 h-(--visual-viewport-height) w-full bg-overlay/10 [--visual-viewport-vertical-padding:16px]" @@ -126,10 +134,10 @@ const PopoverContent = ({ > <Modal className={composeRenderProps(className, (className, renderProps) => - drawer({ ...renderProps, isMenu, className }) + drawer({ ...renderProps, isMenu, className }), )} > - <Dialog role="dialog" aria-label={props['aria-label'] || isMenu ? 'Menu' : undefined}> + <Dialog role="dialog" aria-label={props["aria-label"] || isMenu ? "Menu" : undefined}> {children} </Dialog> </Modal> @@ -140,8 +148,8 @@ const PopoverContent = ({ className={composeRenderProps(className, (className, renderProps) => content({ ...renderProps, - className - }) + className, + }), )} {...props} > @@ -157,25 +165,25 @@ const PopoverContent = ({ </svg> </OverlayArrow> )} - <Dialog role="dialog" aria-label={props['aria-label'] || isMenu ? 'Menu' : undefined}> + <Dialog role="dialog" aria-label={props["aria-label"] || isMenu ? "Menu" : undefined}> {children} </Dialog> </PopoverPrimitive> - ); -}; + ) +} -const PopoverTrigger = Dialog.Trigger; -const PopoverClose = Dialog.Close; -const PopoverDescription = Dialog.Description; +const PopoverTrigger = Dialog.Trigger +const PopoverClose = Dialog.Close +const PopoverDescription = Dialog.Description -Popover.Trigger = PopoverTrigger; -Popover.Close = PopoverClose; -Popover.Description = PopoverDescription; -Popover.Content = PopoverContent; -Popover.Body = PopoverBody; -Popover.Footer = PopoverFooter; -Popover.Header = PopoverHeader; -Popover.Title = PopoverTitle; +Popover.Trigger = PopoverTrigger +Popover.Close = PopoverClose +Popover.Description = PopoverDescription +Popover.Content = PopoverContent +Popover.Body = PopoverBody +Popover.Footer = PopoverFooter +Popover.Header = PopoverHeader +Popover.Title = PopoverTitle -export { Popover, PopoverContent }; -export type { PopoverContentProps, PopoverProps }; +export { Popover, PopoverContent } +export type { PopoverContentProps, PopoverProps } diff --git a/resources/js/components/ui/primitive.tsx b/resources/js/components/ui/primitive.tsx index 0d07ce7..190d16b 100644 --- a/resources/js/components/ui/primitive.tsx +++ b/resources/js/components/ui/primitive.tsx @@ -1,38 +1,38 @@ -import { composeRenderProps } from 'react-aria-components'; -import { twMerge } from 'tailwind-merge'; -import { tv } from 'tailwind-variants'; +import { composeRenderProps } from "react-aria-components" +import { twMerge } from "tailwind-merge" +import { tv } from "tailwind-variants" function composeTailwindRenderProps<T>( className: string | ((v: T) => string) | undefined, - tailwind: string + tailwind: string, ): string | ((v: T) => string) { - return composeRenderProps(className, (className) => twMerge(tailwind, className)); + return composeRenderProps(className, (className) => twMerge(tailwind, className)) } const focusRing = tv({ variants: { - isFocused: { true: 'outline-hidden ring-4 ring-ring/20 data-invalid:ring-danger/20' }, - isFocusVisible: { true: 'outline-hidden ring-4 ring-ring/20' }, - isInvalid: { true: 'ring-4 ring-danger/20' } - } -}); + isFocused: { true: "outline-hidden ring-4 ring-ring/20 data-invalid:ring-danger/20" }, + isFocusVisible: { true: "outline-hidden ring-4 ring-ring/20" }, + isInvalid: { true: "ring-4 ring-danger/20" }, + }, +}) const focusStyles = tv({ extend: focusRing, variants: { - isFocused: { true: 'border-ring/70 forced-colors:border-[Highlight]' }, - isInvalid: { true: 'border-danger/70 forced-colors:border-[Mark]' } - } -}); + isFocused: { true: "border-ring/70 forced-colors:border-[Highlight]" }, + isInvalid: { true: "border-danger/70 forced-colors:border-[Mark]" }, + }, +}) const focusButtonStyles = tv({ - base: 'outline outline-ring outline-offset-2 forced-colors:outline-[Highlight]', + base: "outline outline-ring outline-offset-2 forced-colors:outline-[Highlight]", variants: { isFocusVisible: { - false: 'outline-0', - true: 'outline-2' - } - } -}); + false: "outline-0", + true: "outline-2", + }, + }, +}) -export { composeTailwindRenderProps, focusButtonStyles, focusRing, focusStyles }; +export { composeTailwindRenderProps, focusButtonStyles, focusRing, focusStyles } diff --git a/resources/js/components/ui/select.tsx b/resources/js/components/ui/select.tsx index 79574ed..d40e8a7 100644 --- a/resources/js/components/ui/select.tsx +++ b/resources/js/components/ui/select.tsx @@ -1,62 +1,81 @@ -import { cn } from '@/utils/classes'; -import { IconChevronLgDown } from 'justd-icons'; +import { cn } from "@/utils/classes" +import { IconChevronLgDown } from "justd-icons" import type { ListBoxProps, PopoverProps, SelectProps as SelectPrimitiveProps, - ValidationResult -} from 'react-aria-components'; -import { Button, Select as SelectPrimitive, SelectValue, composeRenderProps } from 'react-aria-components'; -import { tv } from 'tailwind-variants'; -import { DropdownItem, DropdownItemDetails, DropdownLabel, DropdownSection, DropdownSeparator } from './dropdown'; -import { Description, FieldError, Label } from './field'; -import { ListBox } from './list-box'; -import { PopoverContent } from './popover'; -import { composeTailwindRenderProps, focusStyles } from './primitive'; + ValidationResult, +} from "react-aria-components" +import { + Button, + Select as SelectPrimitive, + SelectValue, + composeRenderProps, +} from "react-aria-components" +import { tv } from "tailwind-variants" +import { + DropdownItem, + DropdownItemDetails, + DropdownLabel, + DropdownSection, + DropdownSeparator, +} from "./dropdown" +import { Description, FieldError, Label } from "./field" +import { ListBox } from "./list-box" +import { PopoverContent } from "./popover" +import { composeTailwindRenderProps, focusStyles } from "./primitive" const selectTriggerStyles = tv({ extend: focusStyles, base: [ - 'btr flex h-10 w-full cursor-default items-center gap-4 gap-x-2 rounded-lg border border-input py-2 pr-2 pl-3 text-start shadow-[inset_0_1px_0_0_rgba(255,255,255,0.1)] transition **:data-[slot=icon]:size-4 group-data-disabled:opacity-50 dark:shadow-none', - 'group-data-open:border-ring/70 group-data-open:ring-4 group-data-open:ring-ring/20', - 'text-fg group-data-invalid:border-danger group-data-invalid:ring-danger/20 forced-colors:group-data-invalid:border-[Mark]' + "btr flex h-10 w-full cursor-default items-center gap-4 gap-x-2 rounded-lg border border-input py-2 pr-2 pl-3 text-start shadow-[inset_0_1px_0_0_rgba(255,255,255,0.1)] transition **:data-[slot=icon]:size-4 group-data-disabled:opacity-50 dark:shadow-none", + "group-data-open:border-ring/70 group-data-open:ring-4 group-data-open:ring-ring/20", + "text-fg group-data-invalid:border-danger group-data-invalid:ring-danger/20 forced-colors:group-data-invalid:border-[Mark]", ], variants: { isDisabled: { - true: 'opacity-50 forced-colors:border-[GrayText] forced-colors:text-[GrayText]' - } - } -}); + true: "opacity-50 forced-colors:border-[GrayText] forced-colors:text-[GrayText]", + }, + }, +}) interface SelectProps<T extends object> extends SelectPrimitiveProps<T> { - label?: string; - description?: string; - errorMessage?: string | ((validation: ValidationResult) => string); - items?: Iterable<T>; - className?: string; + label?: string + description?: string + errorMessage?: string | ((validation: ValidationResult) => string) + items?: Iterable<T> + className?: string } -const Select = <T extends object>({ label, description, errorMessage, className, ...props }: SelectProps<T>) => { +const Select = <T extends object>({ + label, + description, + errorMessage, + className, + ...props +}: SelectProps<T>) => { return ( <SelectPrimitive {...props} - className={composeTailwindRenderProps(className, 'group flex w-full flex-col gap-y-1.5')} + className={composeTailwindRenderProps(className, "group flex w-full flex-col gap-y-1.5")} > {(values) => ( <> {label && <Label>{label}</Label>} - {typeof props.children === 'function' ? props.children(values) : props.children} + {typeof props.children === "function" ? props.children(values) : props.children} {description && <Description>{description}</Description>} <FieldError>{errorMessage}</FieldError> </> )} </SelectPrimitive> - ); -}; + ) +} -interface SelectListProps<T extends object> extends ListBoxProps<T>, Pick<PopoverProps, 'placement'> { - items?: Iterable<T>; - popoverClassName?: PopoverProps['className']; +interface SelectListProps<T extends object> + extends ListBoxProps<T>, + Pick<PopoverProps, "placement"> { + items?: Iterable<T> + popoverClassName?: PopoverProps["className"] } const SelectList = <T extends object>({ @@ -70,19 +89,19 @@ const SelectList = <T extends object>({ <PopoverContent showArrow={false} respectScreen={false} - className={cn('sm:min-w-(--trigger-width)', popoverClassName)} + className={cn("sm:min-w-(--trigger-width)", popoverClassName)} placement={props.placement} > - <ListBox className={cn('border-0', className)} items={items} {...props}> + <ListBox className={cn("border-0", className)} items={items} {...props}> {children} </ListBox> </PopoverContent> - ); -}; + ) +} interface SelectTriggerProps extends React.ComponentProps<typeof Button> { - prefix?: React.ReactNode; - className?: string; + prefix?: React.ReactNode + className?: string } const SelectTrigger = ({ className, ...props }: SelectTriggerProps) => { @@ -91,8 +110,8 @@ const SelectTrigger = ({ className, ...props }: SelectTriggerProps) => { className={composeRenderProps(className, (className, renderProps) => selectTriggerStyles({ ...renderProps, - className - }) + className, + }), )} > {props.prefix && <span className="-mr-1">{props.prefix}</span>} @@ -102,22 +121,22 @@ const SelectTrigger = ({ className, ...props }: SelectTriggerProps) => { className="size-4 shrink-0 text-muted-fg duration-300 group-data-open:rotate-180 group-data-open:text-fg group-data-disabled:opacity-50 forced-colors:text-[ButtonText] forced-colors:group-data-disabled:text-[GrayText]" /> </Button> - ); -}; + ) +} -const SelectSection = DropdownSection; -const SelectSeparator = DropdownSeparator; -const SelectLabel = DropdownLabel; -const SelectOptionDetails = DropdownItemDetails; -const SelectOption = DropdownItem; +const SelectSection = DropdownSection +const SelectSeparator = DropdownSeparator +const SelectLabel = DropdownLabel +const SelectOptionDetails = DropdownItemDetails +const SelectOption = DropdownItem -Select.OptionDetails = SelectOptionDetails; -Select.Option = SelectOption; -Select.Label = SelectLabel; -Select.Separator = SelectSeparator; -Select.Section = SelectSection; -Select.Trigger = SelectTrigger; -Select.List = SelectList; +Select.OptionDetails = SelectOptionDetails +Select.Option = SelectOption +Select.Label = SelectLabel +Select.Separator = SelectSeparator +Select.Section = SelectSection +Select.Trigger = SelectTrigger +Select.List = SelectList -export { Select }; -export type { SelectProps, SelectTriggerProps }; +export { Select } +export type { SelectProps, SelectTriggerProps } diff --git a/resources/js/components/ui/separator.tsx b/resources/js/components/ui/separator.tsx index ebaec4c..c8f2be0 100644 --- a/resources/js/components/ui/separator.tsx +++ b/resources/js/components/ui/separator.tsx @@ -1,21 +1,21 @@ -import { Separator as Divider, type SeparatorProps as DividerProps } from 'react-aria-components'; -import { tv } from 'tailwind-variants'; +import { Separator as Divider, type SeparatorProps as DividerProps } from "react-aria-components" +import { tv } from "tailwind-variants" const separatorStyles = tv({ - base: 'shrink-0 bg-border forced-colors:bg-[ButtonBorder]', + base: "shrink-0 bg-border forced-colors:bg-[ButtonBorder]", variants: { orientation: { - horizontal: 'h-px w-full', - vertical: 'w-px' - } + horizontal: "h-px w-full", + vertical: "w-px", + }, }, defaultVariants: { - orientation: 'horizontal' - } -}); + orientation: "horizontal", + }, +}) interface SeparatorProps extends DividerProps { - className?: string; + className?: string } const Separator = ({ className, ...props }: SeparatorProps) => { @@ -24,11 +24,11 @@ const Separator = ({ className, ...props }: SeparatorProps) => { {...props} className={separatorStyles({ orientation: props.orientation, - className: className + className: className, })} /> - ); -}; + ) +} -export { Separator }; -export type { SeparatorProps }; +export { Separator } +export type { SeparatorProps } diff --git a/resources/js/components/ui/sheet.tsx b/resources/js/components/ui/sheet.tsx index 5660077..c80415e 100644 --- a/resources/js/components/ui/sheet.tsx +++ b/resources/js/components/ui/sheet.tsx @@ -1,101 +1,101 @@ -import type { DialogProps, DialogTriggerProps, ModalOverlayProps } from 'react-aria-components'; -import { DialogTrigger, Modal, ModalOverlay, composeRenderProps } from 'react-aria-components'; -import { type VariantProps, tv } from 'tailwind-variants'; +import type { DialogProps, DialogTriggerProps, ModalOverlayProps } from "react-aria-components" +import { DialogTrigger, Modal, ModalOverlay, composeRenderProps } from "react-aria-components" +import { type VariantProps, tv } from "tailwind-variants" -import { Dialog } from './dialog'; +import { Dialog } from "./dialog" const overlayStyles = tv({ base: [ - 'fixed top-0 left-0 isolate z-50 flex h-(--visual-viewport-height) w-full items-center justify-center bg-fg/15 p-4 dark:bg-bg/40' + "fixed top-0 left-0 isolate z-50 flex h-(--visual-viewport-height) w-full items-center justify-center bg-fg/15 p-4 dark:bg-bg/40", ], variants: { isBlurred: { - true: 'bg-bg/15 backdrop-blur dark:bg-bg/40' + true: "bg-bg/15 backdrop-blur dark:bg-bg/40", }, isEntering: { - true: 'fade-in animate-in duration-300 ease-out' + true: "fade-in animate-in duration-300 ease-out", }, isExiting: { - true: 'fade-out animate-out duration-200 ease-in' - } - } -}); + true: "fade-out animate-out duration-200 ease-in", + }, + }, +}) -type Sides = 'top' | 'bottom' | 'left' | 'right'; +type Sides = "top" | "bottom" | "left" | "right" const generateCompoundVariants = (sides: Array<Sides>) => { return sides.map((side) => ({ side, isFloat: true, className: - side === 'top' - ? 'top-2 inset-x-2 rounded-xl ring-1 border-b-0' - : side === 'bottom' - ? 'bottom-2 inset-x-2 rounded-xl ring-1 border-t-0' - : side === 'left' - ? 'left-2 inset-y-2 rounded-xl ring-1 border-r-0' - : 'right-2 inset-y-2 rounded-xl ring-1 border-l-0' - })); -}; + side === "top" + ? "top-2 inset-x-2 rounded-xl ring-1 border-b-0" + : side === "bottom" + ? "bottom-2 inset-x-2 rounded-xl ring-1 border-t-0" + : side === "left" + ? "left-2 inset-y-2 rounded-xl ring-1 border-r-0" + : "right-2 inset-y-2 rounded-xl ring-1 border-l-0", + })) +} const contentStyles = tv({ - base: 'fixed z-50 grid gap-4 border-fg/5 bg-overlay text-overlay-fg shadow-lg transition ease-in-out dark:border-border', + base: "fixed z-50 grid gap-4 border-fg/5 bg-overlay text-overlay-fg shadow-lg transition ease-in-out dark:border-border", variants: { isEntering: { - true: 'animate-in duration-300 ' + true: "animate-in duration-300 ", }, isExiting: { - true: 'animate-out duration-200' + true: "animate-out duration-200", }, side: { - top: 'data-entering:slide-in-from-top data-exiting:slide-out-to-top inset-x-0 top-0 rounded-b-2xl border-b', + top: "data-entering:slide-in-from-top data-exiting:slide-out-to-top inset-x-0 top-0 rounded-b-2xl border-b", bottom: - 'data-entering:slide-in-from-bottom data-exiting:slide-out-to-bottom inset-x-0 bottom-0 rounded-t-2xl border-t', - left: 'data-entering:slide-in-from-left data-exiting:slide-out-to-left inset-y-0 left-0 h-auto w-full max-w-xs overflow-y-auto border-r', + "data-entering:slide-in-from-bottom data-exiting:slide-out-to-bottom inset-x-0 bottom-0 rounded-t-2xl border-t", + left: "data-entering:slide-in-from-left data-exiting:slide-out-to-left inset-y-0 left-0 h-auto w-full max-w-xs overflow-y-auto border-r", right: - 'data-entering:slide-in-from-right data-exiting:slide-out-to-right inset-y-0 right-0 h-auto w-full max-w-xs overflow-y-auto border-l' + "data-entering:slide-in-from-right data-exiting:slide-out-to-right inset-y-0 right-0 h-auto w-full max-w-xs overflow-y-auto border-l", }, isFloat: { - false: 'border-fg/20 dark:border-border', - true: 'ring-fg/5 dark:ring-border' - } + false: "border-fg/20 dark:border-border", + true: "ring-fg/5 dark:ring-border", + }, }, - compoundVariants: generateCompoundVariants(['top', 'bottom', 'left', 'right']) -}); + compoundVariants: generateCompoundVariants(["top", "bottom", "left", "right"]), +}) -type SheetProps = DialogTriggerProps; +type SheetProps = DialogTriggerProps const Sheet = (props: SheetProps) => { - return <DialogTrigger {...props} />; -}; + return <DialogTrigger {...props} /> +} interface SheetContentProps - extends Omit<React.ComponentProps<typeof Modal>, 'children' | 'className'>, - Omit<ModalOverlayProps, 'className'>, + extends Omit<React.ComponentProps<typeof Modal>, "children" | "className">, + Omit<ModalOverlayProps, "className">, VariantProps<typeof overlayStyles> { - 'aria-label'?: DialogProps['aria-label']; - 'aria-labelledby'?: DialogProps['aria-labelledby']; - role?: DialogProps['role']; - closeButton?: boolean; - isBlurred?: boolean; - isFloat?: boolean; - side?: Sides; + "aria-label"?: DialogProps["aria-label"] + "aria-labelledby"?: DialogProps["aria-labelledby"] + role?: DialogProps["role"] + closeButton?: boolean + isBlurred?: boolean + isFloat?: boolean + side?: Sides classNames?: { - overlay?: ModalOverlayProps['className']; - content?: ModalOverlayProps['className']; - }; + overlay?: ModalOverlayProps["className"] + content?: ModalOverlayProps["className"] + } } const SheetContent = ({ classNames, isBlurred = false, isDismissable = true, - side = 'right', - role = 'dialog', + side = "right", + role = "dialog", closeButton = true, isFloat = true, children, ...props }: SheetContentProps) => { - const _isDismissable = role === 'alertdialog' ? false : isDismissable; + const _isDismissable = role === "alertdialog" ? false : isDismissable return ( <ModalOverlay isDismissable={_isDismissable} @@ -103,8 +103,8 @@ const SheetContent = ({ return overlayStyles({ ...renderProps, isBlurred, - className - }); + className, + }) })} {...props} > @@ -114,40 +114,45 @@ const SheetContent = ({ ...renderProps, side, isFloat, - className - }) + className, + }), )} {...props} > {(values) => ( - <Dialog role={role} aria-label={props['aria-label'] ?? undefined} className="h-full"> + <Dialog role={role} aria-label={props["aria-label"] ?? undefined} className="h-full"> <> - {typeof children === 'function' ? children(values) : children} - {closeButton && <Dialog.CloseIndicator className="top-2.5 right-2.5" isDismissable={_isDismissable} />} + {typeof children === "function" ? children(values) : children} + {closeButton && ( + <Dialog.CloseIndicator + className="top-2.5 right-2.5" + isDismissable={_isDismissable} + /> + )} </> </Dialog> )} </Modal> </ModalOverlay> - ); -}; + ) +} -const SheetTrigger = Dialog.Trigger; -const SheetFooter = Dialog.Footer; -const SheetHeader = Dialog.Header; -const SheetTitle = Dialog.Title; -const SheetDescription = Dialog.Description; -const SheetBody = Dialog.Body; -const SheetClose = Dialog.Close; +const SheetTrigger = Dialog.Trigger +const SheetFooter = Dialog.Footer +const SheetHeader = Dialog.Header +const SheetTitle = Dialog.Title +const SheetDescription = Dialog.Description +const SheetBody = Dialog.Body +const SheetClose = Dialog.Close -Sheet.Trigger = SheetTrigger; -Sheet.Footer = SheetFooter; -Sheet.Header = SheetHeader; -Sheet.Title = SheetTitle; -Sheet.Description = SheetDescription; -Sheet.Body = SheetBody; -Sheet.Close = SheetClose; -Sheet.Content = SheetContent; +Sheet.Trigger = SheetTrigger +Sheet.Footer = SheetFooter +Sheet.Header = SheetHeader +Sheet.Title = SheetTitle +Sheet.Description = SheetDescription +Sheet.Body = SheetBody +Sheet.Close = SheetClose +Sheet.Content = SheetContent -export { Sheet }; -export type { SheetContentProps, SheetProps, Sides }; +export { Sheet } +export type { SheetContentProps, SheetProps, Sides } diff --git a/resources/js/components/ui/table.tsx b/resources/js/components/ui/table.tsx index 8eec09d..fc25780 100644 --- a/resources/js/components/ui/table.tsx +++ b/resources/js/components/ui/table.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React from "react" -import { IconChevronLgDown, IconHamburger } from 'justd-icons'; +import { IconChevronLgDown, IconHamburger } from "justd-icons" import type { CellProps, ColumnProps, @@ -8,8 +8,8 @@ import type { TableHeaderProps as HeaderProps, RowProps, TableBodyProps, - TableProps as TablePrimitiveProps -} from 'react-aria-components'; + TableProps as TablePrimitiveProps, +} from "react-aria-components" import { Button, Cell, @@ -22,39 +22,39 @@ import { TableHeader as TableHeaderPrimitive, Table as TablePrimitive, composeRenderProps, - useTableOptions -} from 'react-aria-components'; -import { tv } from 'tailwind-variants'; + useTableOptions, +} from "react-aria-components" +import { tv } from "tailwind-variants" -import { cn } from '@/utils/classes'; -import { Checkbox } from './checkbox'; +import { cn } from "@/utils/classes" +import { Checkbox } from "./checkbox" const table = tv({ slots: { - root: 'table w-full min-w-full caption-bottom border-spacing-0 text-sm outline-hidden [--table-selected-bg:color-mix(in_oklab,var(--color-primary)_5%,white_90%)] **:data-drop-target:border **:data-drop-target:border-primary dark:[--table-selected-bg:color-mix(in_oklab,var(--color-primary)_25%,black_70%)]', - header: 'x32 border-b', - row: 'tr group relative cursor-default border-b bg-bg text-muted-fg outline-hidden ring-primary data-selected:data-hovered:bg-(--table-selected-bg)/70 data-selected:bg-(--table-selected-bg) data-focus-visible:ring-1 data-focused:ring-0 dark:data-selected:data-hovered:bg-[color-mix(in_oklab,var(--color-primary)_30%,black_70%)]', + root: "table w-full min-w-full caption-bottom border-spacing-0 text-sm outline-hidden [--table-selected-bg:color-mix(in_oklab,var(--color-primary)_5%,white_90%)] **:data-drop-target:border **:data-drop-target:border-primary dark:[--table-selected-bg:color-mix(in_oklab,var(--color-primary)_25%,black_70%)]", + header: "x32 border-b", + row: "tr group relative cursor-default border-b bg-bg text-muted-fg outline-hidden ring-primary data-selected:data-hovered:bg-(--table-selected-bg)/70 data-selected:bg-(--table-selected-bg) data-focus-visible:ring-1 data-focused:ring-0 dark:data-selected:data-hovered:bg-[color-mix(in_oklab,var(--color-primary)_30%,black_70%)]", cellIcon: - 'grid size-[1.15rem] flex-none shrink-0 place-content-center rounded bg-secondary text-fg *:data-[slot=icon]:size-3.5 *:data-[slot=icon]:shrink-0 *:data-[slot=icon]:transition-transform *:data-[slot=icon]:duration-200', + "grid size-[1.15rem] flex-none shrink-0 place-content-center rounded bg-secondary text-fg *:data-[slot=icon]:size-3.5 *:data-[slot=icon]:shrink-0 *:data-[slot=icon]:transition-transform *:data-[slot=icon]:duration-200", columnResizer: [ - 'absolute top-0 right-0 bottom-0 grid w-px touch-none place-content-center px-1 [&[data-resizing]>div]:bg-primary', - '&[data-resizable-direction=left]:cursor-e-resize &[data-resizable-direction=right]:cursor-w-resize data-[resizable-direction=both]:cursor-ew-resize' - ] - } -}); + "absolute top-0 right-0 bottom-0 grid w-px touch-none place-content-center px-1 [&[data-resizing]>div]:bg-primary", + "&[data-resizable-direction=left]:cursor-e-resize &[data-resizable-direction=right]:cursor-w-resize data-[resizable-direction=both]:cursor-ew-resize", + ], + }, +}) -const { root, header, row, cellIcon, columnResizer } = table(); +const { root, header, row, cellIcon, columnResizer } = table() interface TableProps extends TablePrimitiveProps { - className?: string; - allowResize?: boolean; + className?: string + allowResize?: boolean } const TableContext = React.createContext<TableProps>({ - allowResize: false -}); + allowResize: false, +}) -const useTableContext = () => React.useContext(TableContext); +const useTableContext = () => React.useContext(TableContext) const Table = ({ children, className, ...props }: TableProps) => ( <TableContext.Provider value={props}> @@ -72,7 +72,7 @@ const Table = ({ children, className, ...props }: TableProps) => ( )} </div> </TableContext.Provider> -); +) const ColumnResizer = ({ className, ...props }: ColumnResizerProps) => ( <ColumnResizerPrimitive @@ -80,51 +80,55 @@ const ColumnResizer = ({ className, ...props }: ColumnResizerProps) => ( className={composeRenderProps(className, (className, renderProps) => columnResizer({ ...renderProps, - className - }) + className, + }), )} > <div className="h-full w-px bg-border py-3" /> </ColumnResizerPrimitive> -); +) const TableBody = <T extends object>(props: TableBodyProps<T>) => ( - <TableBodyPrimitive data-slot="table-body" {...props} className={cn('[&_.tr:last-child]:border-0')} /> -); + <TableBodyPrimitive + data-slot="table-body" + {...props} + className={cn("[&_.tr:last-child]:border-0")} + /> +) interface TableCellProps extends CellProps { - className?: string; + className?: string } const cellStyles = tv({ - base: 'group whitespace-nowrap px-3 py-3 outline-hidden', + base: "group whitespace-nowrap px-3 py-3 outline-hidden", variants: { allowResize: { - true: 'overflow-hidden truncate' - } - } -}); + true: "overflow-hidden truncate", + }, + }, +}) const TableCell = ({ children, className, ...props }: TableCellProps) => { - const { allowResize } = useTableContext(); + const { allowResize } = useTableContext() return ( <Cell data-slot="table-cell" {...props} className={cellStyles({ allowResize, className })}> {children} </Cell> - ); -}; + ) +} const columnStyles = tv({ - base: 'relative allows-sorting:cursor-pointer whitespace-nowrap px-3 py-3 text-left font-medium outline-hidden data-dragging:cursor-grabbing [&:has([slot=selection])]:pr-0', + base: "relative allows-sorting:cursor-pointer whitespace-nowrap px-3 py-3 text-left font-medium outline-hidden data-dragging:cursor-grabbing [&:has([slot=selection])]:pr-0", variants: { isResizable: { - true: 'overflow-hidden truncate' - } - } -}); + true: "overflow-hidden truncate", + }, + }, +}) interface TableColumnProps extends ColumnProps { - className?: string; - isResizable?: boolean; + className?: string + isResizable?: boolean } const TableColumn = ({ isResizable = false, className, ...props }: TableColumnProps) => { @@ -134,7 +138,7 @@ const TableColumn = ({ isResizable = false, className, ...props }: TableColumnPr {...props} className={columnStyles({ isResizable, - className + className, })} > {({ allowsSorting, sortDirection, isHovered }) => ( @@ -142,8 +146,8 @@ const TableColumn = ({ isResizable = false, className, ...props }: TableColumnPr <> {props.children as React.ReactNode} {allowsSorting && ( - <span className={cellIcon({ className: isHovered ? 'bg-secondary-fg/10' : '' })}> - <IconChevronLgDown className={sortDirection === 'ascending' ? 'rotate-180' : ''} /> + <span className={cellIcon({ className: isHovered ? "bg-secondary-fg/10" : "" })}> + <IconChevronLgDown className={sortDirection === "ascending" ? "rotate-180" : ""} /> </span> )} {isResizable && <ColumnResizer />} @@ -151,34 +155,54 @@ const TableColumn = ({ isResizable = false, className, ...props }: TableColumnPr </div> )} </Column> - ); -}; + ) +} interface TableHeaderProps<T extends object> extends HeaderProps<T> { - className?: string; - ref?: React.Ref<HTMLTableSectionElement>; + className?: string + ref?: React.Ref<HTMLTableSectionElement> } -const TableHeader = <T extends object>({ children, ref, className, columns, ...props }: TableHeaderProps<T>) => { - const { selectionBehavior, selectionMode, allowsDragging } = useTableOptions(); +const TableHeader = <T extends object>({ + children, + ref, + className, + columns, + ...props +}: TableHeaderProps<T>) => { + const { selectionBehavior, selectionMode, allowsDragging } = useTableOptions() return ( - <TableHeaderPrimitive data-slot="table-header" ref={ref} className={header({ className })} {...props}> + <TableHeaderPrimitive + data-slot="table-header" + ref={ref} + className={header({ className })} + {...props} + > {allowsDragging && <Column className="w-0" />} - {selectionBehavior === 'toggle' && ( - <Column className="w-0 pl-4">{selectionMode === 'multiple' && <Checkbox slot="selection" />}</Column> + {selectionBehavior === "toggle" && ( + <Column className="w-0 pl-4"> + {selectionMode === "multiple" && <Checkbox slot="selection" />} + </Column> )} <Collection items={columns}>{children}</Collection> </TableHeaderPrimitive> - ); -}; + ) +} interface TableRowProps<T extends object> extends RowProps<T> { - className?: string; - ref?: React.Ref<HTMLTableRowElement>; + className?: string + ref?: React.Ref<HTMLTableRowElement> } -const TableRow = <T extends object>({ children, className, columns, id, ref, ...props }: TableRowProps<T>) => { - const { selectionBehavior, allowsDragging } = useTableOptions(); +const TableRow = <T extends object>({ + children, + className, + columns, + id, + ref, + ...props +}: TableRowProps<T>) => { + const { selectionBehavior, allowsDragging } = useTableOptions() return ( <Row ref={ref} @@ -187,19 +211,25 @@ const TableRow = <T extends object>({ children, className, columns, id, ref, ... {...props} className={row({ className: - 'href' in props - ? cn('cursor-pointer data-hovered:bg-secondary/50 data-hovered:text-secondary-fg', className) - : '' + "href" in props + ? cn( + "cursor-pointer data-hovered:bg-secondary/50 data-hovered:text-secondary-fg", + className, + ) + : "", })} > {allowsDragging && ( <Cell className="group cursor-grab pr-0 ring-primary data-dragging:cursor-grabbing"> - <Button className="relative bg-transparent py-1.5 pl-3.5 text-muted-fg data-pressed:text-fg" slot="drag"> + <Button + className="relative bg-transparent py-1.5 pl-3.5 text-muted-fg data-pressed:text-fg" + slot="drag" + > <IconHamburger /> </Button> </Cell> )} - {selectionBehavior === 'toggle' && ( + {selectionBehavior === "toggle" && ( <Cell className="pl-4"> <span aria-hidden @@ -210,14 +240,14 @@ const TableRow = <T extends object>({ children, className, columns, id, ref, ... )} <Collection items={columns}>{children}</Collection> </Row> - ); -}; + ) +} -Table.Body = TableBody; -Table.Cell = TableCell; -Table.Column = TableColumn; -Table.Header = TableHeader; -Table.Row = TableRow; +Table.Body = TableBody +Table.Cell = TableCell +Table.Column = TableColumn +Table.Header = TableHeader +Table.Row = TableRow -export { Table }; -export type { TableBodyProps, TableCellProps, TableColumnProps, TableProps, TableRowProps }; +export { Table } +export type { TableBodyProps, TableCellProps, TableColumnProps, TableProps, TableRowProps } diff --git a/resources/js/components/ui/text-field.tsx b/resources/js/components/ui/text-field.tsx index d18c567..6d6eb7d 100644 --- a/resources/js/components/ui/text-field.tsx +++ b/resources/js/components/ui/text-field.tsx @@ -1,38 +1,38 @@ -import { useState } from 'react'; +import { useState } from "react" -import type { TextInputDOMProps } from '@react-types/shared'; -import { IconEye, IconEyeClosed } from 'justd-icons'; +import type { TextInputDOMProps } from "@react-types/shared" +import { IconEye, IconEyeClosed } from "justd-icons" import { Button as ButtonPrimitive, TextField as TextFieldPrimitive, - type TextFieldProps as TextFieldPrimitiveProps -} from 'react-aria-components'; + type TextFieldProps as TextFieldPrimitiveProps, +} from "react-aria-components" -import type { FieldProps } from './field'; -import { Description, FieldError, FieldGroup, Input, Label } from './field'; -import { Loader } from './loader'; -import { composeTailwindRenderProps } from './primitive'; +import type { FieldProps } from "./field" +import { Description, FieldError, FieldGroup, Input, Label } from "./field" +import { Loader } from "./loader" +import { composeTailwindRenderProps } from "./primitive" -type InputType = Exclude<TextInputDOMProps['type'], 'password'>; +type InputType = Exclude<TextInputDOMProps["type"], "password"> interface BaseTextFieldProps extends TextFieldPrimitiveProps, FieldProps { - prefix?: React.ReactNode; - suffix?: React.ReactNode; - isPending?: boolean; - className?: string; + prefix?: React.ReactNode + suffix?: React.ReactNode + isPending?: boolean + className?: string } interface RevealableTextFieldProps extends BaseTextFieldProps { - isRevealable: true; - type: 'password'; + isRevealable: true + type: "password" } interface NonRevealableTextFieldProps extends BaseTextFieldProps { - isRevealable?: never; - type?: InputType; + isRevealable?: never + type?: InputType } -type TextFieldProps = RevealableTextFieldProps | NonRevealableTextFieldProps; +type TextFieldProps = RevealableTextFieldProps | NonRevealableTextFieldProps const TextField = ({ placeholder, @@ -47,16 +47,16 @@ const TextField = ({ type, ...props }: TextFieldProps) => { - const [isPasswordVisible, setIsPasswordVisible] = useState(false); - const inputType = isRevealable ? (isPasswordVisible ? 'text' : 'password') : type; + const [isPasswordVisible, setIsPasswordVisible] = useState(false) + const inputType = isRevealable ? (isPasswordVisible ? "text" : "password") : type const handleTogglePasswordVisibility = () => { - setIsPasswordVisible((prev) => !prev); - }; + setIsPasswordVisible((prev) => !prev) + } return ( <TextFieldPrimitive type={inputType} {...props} - className={composeTailwindRenderProps(className, 'group flex flex-col gap-y-1')} + className={composeTailwindRenderProps(className, "group flex flex-col gap-y-1")} > {!props.children ? ( <> @@ -64,9 +64,13 @@ const TextField = ({ <FieldGroup isDisabled={props.isDisabled} isInvalid={!!errorMessage} - data-loading={isPending ? 'true' : undefined} + data-loading={isPending ? "true" : undefined} > - {prefix && typeof prefix === 'string' ? <span className="ml-2 text-muted-fg">{prefix}</span> : prefix} + {prefix && typeof prefix === "string" ? ( + <span className="ml-2 text-muted-fg">{prefix}</span> + ) : ( + prefix + )} <Input placeholder={placeholder} /> {isRevealable ? ( <ButtonPrimitive @@ -80,7 +84,7 @@ const TextField = ({ ) : isPending ? ( <Loader variant="spin" /> ) : suffix ? ( - typeof suffix === 'string' ? ( + typeof suffix === "string" ? ( <span className="mr-2 text-muted-fg">{suffix}</span> ) : ( suffix @@ -94,8 +98,8 @@ const TextField = ({ props.children )} </TextFieldPrimitive> - ); -}; + ) +} -export { TextField }; -export type { TextFieldProps }; +export { TextField } +export type { TextFieldProps } diff --git a/resources/js/components/ui/toast.tsx b/resources/js/components/ui/toast.tsx index d957583..d96910c 100644 --- a/resources/js/components/ui/toast.tsx +++ b/resources/js/components/ui/toast.tsx @@ -1,21 +1,21 @@ -import { useTheme } from '@/components/theme-provider'; -import { cn } from '@/utils/classes'; +import { useTheme } from "@/components/theme-provider" +import { cn } from "@/utils/classes" import { IconCircleCheckFill, IconCircleExclamationFill, IconCircleInfoFill, - IconTriangleExclamationFill -} from 'justd-icons'; -import { Toaster as ToasterPrimitive, type ToasterProps } from 'sonner'; + IconTriangleExclamationFill, +} from "justd-icons" +import { Toaster as ToasterPrimitive, type ToasterProps } from "sonner" -import { buttonStyles } from './button'; -import { Loader } from './loader'; +import { buttonStyles } from "./button" +import { Loader } from "./loader" const Toast = ({ ...props }: ToasterProps) => { - const { theme = 'system' } = useTheme(); + const { theme = "system" } = useTheme() return ( <ToasterPrimitive - theme={theme as ToasterProps['theme']} + theme={theme as ToasterProps["theme"]} className="toaster group" richColors icons={{ @@ -23,50 +23,50 @@ const Toast = ({ ...props }: ToasterProps) => { error: <IconTriangleExclamationFill />, warning: <IconCircleExclamationFill />, success: <IconCircleCheckFill />, - loading: <Loader variant="spin" /> + loading: <Loader variant="spin" />, }} toastOptions={{ unstyled: true, closeButton: true, classNames: { toast: cn( - 'text-[0.925rem] not-has-data-description:**:data-title:font-normal!', - 'has-data-description:**:data-title:font-medium [&:has([data-description])_[data-title]]:text-base!', - 'has-data-[slot=icon]:**:data-content:pl-0', - 'has-data-button:*:data-content:mb-10', - 'has-data-button:hover:**:data-close-button:hidden! flex w-full rounded-xl p-4', - 'inset-ring-1 inset-ring-current/10 border-transparent backdrop-blur-3xl' + "text-[0.925rem] not-has-data-description:**:data-title:font-normal!", + "has-data-description:**:data-title:font-medium [&:has([data-description])_[data-title]]:text-base!", + "has-data-[slot=icon]:**:data-content:pl-0", + "has-data-button:*:data-content:mb-10", + "has-data-button:hover:**:data-close-button:hidden! flex w-full rounded-xl p-4", + "inset-ring-1 inset-ring-current/10 border-transparent backdrop-blur-3xl", ), - icon: 'absolute top-[0.2rem] [--toast-icon-margin-end:7px] *:data-[slot=icon]:text-fg *:data-[slot=icon]:size-4.5 **:data-[slot=icon]:text-current', - title: '', - description: '', - default: 'bg-bg text-fg [--gray2:theme(--color-fg/10%)]', - content: 'pr-6 *:data-description:text-current/65! *:data-description:text-sm!', + icon: "absolute top-[0.2rem] [--toast-icon-margin-end:7px] *:data-[slot=icon]:text-fg *:data-[slot=icon]:size-4.5 **:data-[slot=icon]:text-current", + title: "", + description: "", + default: "bg-bg text-fg [--gray2:theme(--color-fg/10%)]", + content: "pr-6 *:data-description:text-current/65! *:data-description:text-sm!", error: - 'inset-ring-danger/15 dark:inset-ring-danger/20 [--error-bg:theme(--color-danger/5%)] [--error-text:var(--color-danger)]', - info: 'inset-ring-sky-600/15 dark:inset-ring-sky-500/20 [--info-bg:theme(--color-sky-500/5%)] [--info-text:var(--color-sky-700)] dark:[--info-text:var(--color-sky-400)]', + "inset-ring-danger/15 dark:inset-ring-danger/20 [--error-bg:theme(--color-danger/5%)] [--error-text:var(--color-danger)]", + info: "inset-ring-sky-600/15 dark:inset-ring-sky-500/20 [--info-bg:theme(--color-sky-500/5%)] [--info-text:var(--color-sky-700)] dark:[--info-text:var(--color-sky-400)]", warning: - 'inset-ring-warning/15 dark:inset-ring-warning/20 [--warning-bg:theme(--color-warning/7%)] [--warning-text:var(--color-warning-fg)] dark:[--warning-text:var(--color-warning)]', + "inset-ring-warning/15 dark:inset-ring-warning/20 [--warning-bg:theme(--color-warning/7%)] [--warning-text:var(--color-warning-fg)] dark:[--warning-text:var(--color-warning)]", success: - 'inset-ring-success/15 [--success-bg:theme(--color-success/5%)] [--success-text:var(--color-success)]', + "inset-ring-success/15 [--success-bg:theme(--color-success/5%)] [--success-text:var(--color-success)]", cancelButton: buttonStyles({ className: - 'hover:border-secondary-fg/10 hover:bg-secondary/90 self-start absolute bottom-4 left-4 justify-self-start', - size: 'extra-small', - appearance: 'outline' + "hover:border-secondary-fg/10 hover:bg-secondary/90 self-start absolute bottom-4 left-4 justify-self-start", + size: "extra-small", + appearance: "outline", }), actionButton: buttonStyles({ - className: 'absolute bottom-4 right-4 self-end justify-self-end', - size: 'extra-small' + className: "absolute bottom-4 right-4 self-end justify-self-end", + size: "extra-small", }), closeButton: - '*:[svg]:size-5 group-hover:block! hidden! size-6! rounded-md! [--gray1:transparent] [--gray4:transparent] [--gray5:transparent] [--gray12:current] [--toast-close-button-start:full] [--toast-close-button-end:-6px] [--toast-close-button-transform:translate(-75%,60%)] absolute' - } + "*:[svg]:size-5 group-hover:block! hidden! size-6! rounded-md! [--gray1:transparent] [--gray4:transparent] [--gray5:transparent] [--gray12:current] [--toast-close-button-start:full] [--toast-close-button-end:-6px] [--toast-close-button-transform:translate(-75%,60%)] absolute", + }, }} {...props} /> - ); -}; + ) +} -export { Toast }; -export type { ToasterProps }; +export { Toast } +export type { ToasterProps } diff --git a/resources/js/components/ui/touch-target.tsx b/resources/js/components/ui/touch-target.tsx index e9297db..e914d38 100644 --- a/resources/js/components/ui/touch-target.tsx +++ b/resources/js/components/ui/touch-target.tsx @@ -1,15 +1,15 @@ -import * as React from 'react'; +import type * as React from "react" const TouchTarget = ({ children }: { children: React.ReactNode }) => { return ( <> <span - className="absolute left-1/2 top-1/2 size-[max(100%,2.75rem)] -translate-x-1/2 -translate-y-1/2 [@media(pointer:fine)]:hidden" + className="-translate-x-1/2 -translate-y-1/2 absolute top-1/2 left-1/2 size-[max(100%,2.75rem)] [@media(pointer:fine)]:hidden" aria-hidden="true" /> {children} </> - ); -}; + ) +} -export { TouchTarget }; +export { TouchTarget } diff --git a/resources/js/components/ui/visually-hidden.tsx b/resources/js/components/ui/visually-hidden.tsx index d0cd4a0..333a9c2 100644 --- a/resources/js/components/ui/visually-hidden.tsx +++ b/resources/js/components/ui/visually-hidden.tsx @@ -1,13 +1,13 @@ -import { useVisuallyHidden } from 'react-aria'; +import { useVisuallyHidden } from "react-aria" type VisuallyHiddenSpanProps = { - children: React.ReactNode; -}; + children: React.ReactNode +} const VisuallyHidden = ({ children }: VisuallyHiddenSpanProps) => { - const { visuallyHiddenProps } = useVisuallyHidden(); + const { visuallyHiddenProps } = useVisuallyHidden() - return <span {...visuallyHiddenProps}>{children}</span>; -}; + return <span {...visuallyHiddenProps}>{children}</span> +} -export { VisuallyHidden }; +export { VisuallyHidden } diff --git a/resources/js/layouts/app-layout.tsx b/resources/js/layouts/app-layout.tsx index eb2e94d..c2639ef 100644 --- a/resources/js/layouts/app-layout.tsx +++ b/resources/js/layouts/app-layout.tsx @@ -1,7 +1,7 @@ -import { FlashMessage } from '@/components/flash-message'; -import { Footer } from '@/components/footer'; -import { AppNavbar } from '@/layouts/app-navbar'; -import { PropsWithChildren } from 'react'; +import { FlashMessage } from "@/components/flash-message" +import { Footer } from "@/components/footer" +import { AppNavbar } from "@/layouts/app-navbar" +import type { PropsWithChildren } from "react" export default function AppLayout({ children }: PropsWithChildren) { return ( @@ -10,5 +10,5 @@ export default function AppLayout({ children }: PropsWithChildren) { <AppNavbar>{children}</AppNavbar> <Footer /> </div> - ); + ) } diff --git a/resources/js/layouts/app-navbar.tsx b/resources/js/layouts/app-navbar.tsx index 83197e4..236a20b 100644 --- a/resources/js/layouts/app-navbar.tsx +++ b/resources/js/layouts/app-navbar.tsx @@ -1,7 +1,7 @@ -import { useTheme } from '@/components/theme-provider'; -import { ThemeSwitcher } from '@/components/theme-switcher'; -import { PagePropsData } from '@/types'; -import { usePage } from '@inertiajs/react'; +import { useTheme } from "@/components/theme-provider" +import { ThemeSwitcher } from "@/components/theme-switcher" +import type { PagePropsData } from "@/types" +import { usePage } from "@inertiajs/react" import { IconArrowUpRight, IconBrandJustd, @@ -11,30 +11,30 @@ import { IconColorSwatch, IconLogout, IconPackage, - IconSettings -} from 'justd-icons'; -import React from 'react'; -import { Selection } from 'react-aria-components'; -import { Avatar, buttonStyles, Link, Menu, Navbar, Separator } from 'ui'; + IconSettings, +} from "justd-icons" +import { useState, useEffect } from "react" +import type { Selection } from "react-aria-components" +import { Avatar, buttonStyles, Link, Menu, Navbar, Separator } from "ui" const navigations = [ { - name: 'Home', - textValue: 'Home', - href: '/' + name: "Home", + textValue: "Home", + href: "/", }, { - name: 'About', - textValue: 'About', - href: '/about' - } -]; + name: "About", + textValue: "About", + href: "/about", + }, +] export function AppNavbar({ children, ...props }: React.ComponentProps<typeof Navbar>) { - const page = usePage(); - const { auth } = usePage<PagePropsData>().props; - const [isOpen, setIsOpen] = React.useState(false); - React.useEffect(() => setIsOpen(false), [page.url]); + const page = usePage() + const { auth } = usePage<PagePropsData>().props + const [isOpen, setIsOpen] = useState(false) + useEffect(() => setIsOpen(false), [page.url]) return ( <Navbar isOpen={isOpen} onOpenChange={setIsOpen} {...props}> <Navbar.Nav> @@ -50,7 +50,7 @@ export function AppNavbar({ children, ...props }: React.ComponentProps<typeof Na <Menu> <Navbar.Item className="group"> Resources... - <IconChevronLgDown className="transition-transform size-4 ml-2 group-data-pressed:rotate-180" /> + <IconChevronLgDown className="ml-2 size-4 transition-transform group-data-pressed:rotate-180" /> </Navbar.Item> <Menu.Content className="sm:min-w-48"> <Menu.Submenu> @@ -59,11 +59,19 @@ export function AppNavbar({ children, ...props }: React.ComponentProps<typeof Na <Menu.Label>Blocks</Menu.Label> </Menu.Item> <Menu.Content> - <Menu.Item target="_blank" href="https://blocks.getjustd.com" className="justify-between"> + <Menu.Item + target="_blank" + href="https://blocks.getjustd.com" + className="justify-between" + > <IconBrandJustdBlocks /> <Menu.Label>Premium Blocks</Menu.Label> </Menu.Item> - <Menu.Item target="_blank" href="https://getjustd.com/blocks" className="justify-between"> + <Menu.Item + target="_blank" + href="https://getjustd.com/blocks" + className="justify-between" + > <IconBrandJustd /> <Menu.Label>Basic Blocks</Menu.Label> </Menu.Item> @@ -74,12 +82,20 @@ export function AppNavbar({ children, ...props }: React.ComponentProps<typeof Na <Menu.Label>Components</Menu.Label> <IconArrowUpRight /> </Menu.Item> - <Menu.Item target="_blank" href="https://getjustd.com/colors" className="justify-between"> + <Menu.Item + target="_blank" + href="https://getjustd.com/colors" + className="justify-between" + > <IconColorSwatch /> <Menu.Label>Colors</Menu.Label> <IconArrowUpRight /> </Menu.Item> - <Menu.Item target="_blank" href="https://getjustd.com/themes" className="justify-between"> + <Menu.Item + target="_blank" + href="https://getjustd.com/themes" + className="justify-between" + > <IconColorPalette /> <Menu.Label>Themes</Menu.Label> <IconArrowUpRight /> @@ -93,17 +109,20 @@ export function AppNavbar({ children, ...props }: React.ComponentProps<typeof Na </Menu> </Navbar.Section> - <Navbar.Section className="hidden ml-auto gap-x-1 lg:flex"> + <Navbar.Section className="ml-auto hidden gap-x-1 lg:flex"> {!auth.user && <ThemeSwitcher />} {auth.user ? ( <UserMenu /> ) : ( <> - <Separator orientation="vertical" className="h-6 mr-2" /> - <Link className={buttonStyles({ appearance: 'outline', size: 'small' })} href={route('login')}> + <Separator orientation="vertical" className="mr-2 h-6" /> + <Link + className={buttonStyles({ appearance: "outline", size: "small" })} + href={route("login")} + > Login </Link> - <Navbar.Item href={route('register')}>Register</Navbar.Item> + <Navbar.Item href={route("register")}>Register</Navbar.Item> </> )} </Navbar.Section> @@ -124,8 +143,8 @@ export function AppNavbar({ children, ...props }: React.ComponentProps<typeof Na ) : ( <> <Link - className={buttonStyles({ appearance: 'outline', size: 'small', shape: 'circle' })} - href={route('login')} + className={buttonStyles({ appearance: "outline", size: "small", shape: "circle" })} + href={route("login")} > Login </Link> @@ -136,18 +155,18 @@ export function AppNavbar({ children, ...props }: React.ComponentProps<typeof Na {children} </Navbar> - ); + ) } function UserMenu() { - const { auth } = usePage<PagePropsData>().props; - const { theme, setTheme } = useTheme(); - const currentTheme = theme || 'system'; - const [selectedTheme, setSelectedTheme] = React.useState<Selection>(new Set([currentTheme])); + const { auth } = usePage<PagePropsData>().props + const { theme, setTheme } = useTheme() + const currentTheme = theme || "system" + const [selectedTheme, setSelectedTheme] = useState<Selection>(new Set([currentTheme])) return ( <Menu> <Menu.Trigger - className="group data-hovered:bg-secondary p-1 rounded-lg flex justify-between text-left items-start" + className="group flex items-start justify-between rounded-lg p-1 text-left data-hovered:bg-secondary" aria-label="Open menu" > <Avatar src={auth.user.gravatar} shape="square" className="mr-2 size-9 *:size-9" /> @@ -155,19 +174,21 @@ function UserMenu() { <strong className="font-semibold text-sm">{auth.user.name}</strong> <span className="text-xs">{auth.user.email}</span> </div> - <IconChevronLgDown className="group-data-pressed:rotate-180 transition-transform" /> + <IconChevronLgDown className="transition-transform group-data-pressed:rotate-180" /> </Menu.Trigger> <Menu.Content placement="bottom end" className="sm:min-w-60"> <Menu.Section> <Menu.Header separator className="relative"> <div>{auth.user.name}</div> - <div className="text-muted-fg font-normal text-sm whitespace-nowrap truncate pr-6">{auth.user.email}</div> + <div className="truncate whitespace-nowrap pr-6 font-normal text-muted-fg text-sm"> + {auth.user.email} + </div> </Menu.Header> </Menu.Section> - <Menu.Item href={route('dashboard')}> + <Menu.Item href={route("dashboard")}> <Menu.Label>Dashboard</Menu.Label> </Menu.Item> - <Menu.Item href={route('profile.edit')} className="justify-between"> + <Menu.Item href={route("profile.edit")} className="justify-between"> <Menu.Label>Settings</Menu.Label> <IconSettings /> </Menu.Item> @@ -179,14 +200,14 @@ function UserMenu() { selectionMode="single" selectedKeys={selectedTheme} onSelectionChange={(keys) => { - setSelectedTheme(keys); + setSelectedTheme(keys) // @ts-ignore - setTheme(keys.has('system') ? 'system' : keys.has('dark') ? 'dark' : 'light'); + setTheme(keys.has("system") ? "system" : keys.has("dark") ? "dark" : "light") }} items={[ - { name: 'Light', value: 'light' }, - { name: 'Dark', value: 'dark' }, - { name: 'System', value: 'system' } + { name: "Light", value: "light" }, + { name: "Dark", value: "dark" }, + { name: "System", value: "system" }, ]} > {(item) => ( @@ -198,18 +219,24 @@ function UserMenu() { </Menu.Submenu> <Menu.Separator /> - <Menu.Item routerOptions={{ method: 'post' }} href={route('logout')}> + <Menu.Item routerOptions={{ method: "post" }} href={route("logout")}> <Menu.Label>Logout</Menu.Label> <IconLogout /> </Menu.Item> </Menu.Content> </Menu> - ); + ) } export function IconBrandJustdBlocks() { return ( - <svg className="size-4.5 sm:size-5" xmlns="http://www.w3.org/2000/svg" height={24} fill="none" viewBox="0 0 24 24"> + <svg + className="size-4.5 sm:size-5" + xmlns="http://www.w3.org/2000/svg" + height={24} + fill="none" + viewBox="0 0 24 24" + > <rect width={20} height={20} x={2} y={2} fill="#0D6DFD" rx="3.75" /> <g fill="#fff" filter="url(#a)" shapeRendering="crispEdges"> <path d="M5.36 6.311c0-.525.426-.952.951-.952h1.904c.526 0 .952.427.952.952v1.904a.95.95 0 0 1-.952.952H6.311a.95.95 0 0 1-.952-.952z" /> @@ -234,7 +261,11 @@ export function IconBrandJustdBlocks() { filterUnits="userSpaceOnUse" > <feFlood floodOpacity={0} result="BackgroundImageFix" /> - <feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" /> + <feColorMatrix + in="SourceAlpha" + result="hardAlpha" + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" + /> <feOffset dy=".032" /> <feGaussianBlur stdDeviation=".032" /> <feComposite in2="hardAlpha" operator="out" /> @@ -244,5 +275,5 @@ export function IconBrandJustdBlocks() { </filter> </defs> </svg> - ); + ) } diff --git a/resources/js/layouts/guest-layout.tsx b/resources/js/layouts/guest-layout.tsx index 569c57c..9dfd568 100644 --- a/resources/js/layouts/guest-layout.tsx +++ b/resources/js/layouts/guest-layout.tsx @@ -1,28 +1,28 @@ -import { FlashMessage } from '@/components/flash-message'; -import { Logo } from '@/components/logo'; -import { Card } from '@/components/ui/card'; -import { PropsWithChildren, ReactNode } from 'react'; -import { Link } from 'ui'; +import { FlashMessage } from "@/components/flash-message" +import { Logo } from "@/components/logo" +import { Card } from "@/components/ui/card" +import type { PropsWithChildren, ReactNode } from "react" +import { Link } from "ui" interface GuestLayoutProps { - header?: string | null; - description?: string | ReactNode | null; + header?: string | null + description?: string | ReactNode | null } export default function GuestLayout({ description = null, header = null, - children + children, }: PropsWithChildren<GuestLayoutProps>) { return ( <div className="flex min-h-screen flex-col items-center pt-6 sm:justify-center sm:pt-0"> <FlashMessage /> - <Link href={route('home')}> + <Link href={route("home")}> <Logo className="mx-auto size-8" /> </Link> <div className="mt-10 w-full max-w-lg"> - <Card className="rounded-none border-l-transparent border-r-transparent shadow-none sm:rounded-lg sm:border-l-border sm:border-r-border sm:shadow-sm lg:rounded-xl "> + <Card className="rounded-none border-r-transparent border-l-transparent shadow-none sm:rounded-lg sm:border-r-border sm:border-l-border sm:shadow-sm lg:rounded-xl "> <Card.Header> <Card.Title>{header}</Card.Title> <Card.Description>{description}</Card.Description> @@ -31,5 +31,5 @@ export default function GuestLayout({ </Card> </div> </div> - ); + ) } diff --git a/resources/js/pages/about.tsx b/resources/js/pages/about.tsx index 7af1231..21e8fe0 100644 --- a/resources/js/pages/about.tsx +++ b/resources/js/pages/about.tsx @@ -1,8 +1,8 @@ -import { Header } from '@/components/header'; -import AppLayout from '@/layouts/app-layout'; -import { Head } from '@inertiajs/react'; -import React from 'react'; -import { Card, Container } from 'ui'; +import { Header } from "@/components/header" +import AppLayout from "@/layouts/app-layout" +import { Head } from "@inertiajs/react" +import type React from "react" +import { Card, Container } from "ui" export default function About() { return ( @@ -13,7 +13,7 @@ export default function About() { <Card className="p-6">Your about page content goes here.</Card> </Container> </> - ); + ) } -About.layout = (page: React.ReactNode) => <AppLayout children={page} />; +About.layout = (page: React.ReactNode) => <AppLayout children={page} /> diff --git a/resources/js/pages/auth/confirm-password.tsx b/resources/js/pages/auth/confirm-password.tsx index 4a7aec7..62cadd3 100644 --- a/resources/js/pages/auth/confirm-password.tsx +++ b/resources/js/pages/auth/confirm-password.tsx @@ -1,30 +1,30 @@ -import GuestLayout from '@/layouts/guest-layout'; -import { Head, useForm } from '@inertiajs/react'; -import { useEffect } from 'react'; -import { Button, Form, TextField } from 'ui'; +import GuestLayout from "@/layouts/guest-layout" +import { Head, useForm } from "@inertiajs/react" +import { useEffect } from "react" +import { Button, Form, TextField } from "ui" export default function ConfirmPassword() { const { data, setData, post, processing, errors, reset } = useForm({ - password: '' - }); + password: "", + }) useEffect(() => { return () => { - reset('password'); - }; - }, []); + reset("password") + } + }, []) const submit = (e: { preventDefault: () => void }) => { - e.preventDefault(); + e.preventDefault() - post(route('password.confirm')); - }; + post(route("password.confirm")) + } return ( <> <Head title="Confirm Password" /> - <div className="mb-4 text-sm text-muted-fg"> + <div className="mb-4 text-muted-fg text-sm"> This is a secure area of the application. Please confirm your password before continuing. </div> @@ -37,7 +37,7 @@ export default function ConfirmPassword() { value={data.password} className="mt-1 block w-full" autoFocus - onChange={(v) => setData('password', v)} + onChange={(v) => setData("password", v)} /> <div className="mt-4 flex items-center justify-end"> @@ -45,7 +45,7 @@ export default function ConfirmPassword() { </div> </Form> </> - ); + ) } -ConfirmPassword.layout = (page: any) => <GuestLayout children={page} />; +ConfirmPassword.layout = (page: any) => <GuestLayout children={page} /> diff --git a/resources/js/pages/auth/forgot-password.tsx b/resources/js/pages/auth/forgot-password.tsx index 48a7a09..cb10043 100644 --- a/resources/js/pages/auth/forgot-password.tsx +++ b/resources/js/pages/auth/forgot-password.tsx @@ -1,25 +1,25 @@ -import GuestLayout from '@/layouts/guest-layout'; -import { Head, useForm } from '@inertiajs/react'; -import { Button, Form, TextField } from 'ui'; +import GuestLayout from "@/layouts/guest-layout" +import { Head, useForm } from "@inertiajs/react" +import { Button, Form, TextField } from "ui" interface ForgotPasswordProps { - status: string; + status: string } export default function ForgotPassword({ status }: ForgotPasswordProps) { const { data, setData, post, processing, errors } = useForm({ - email: '' - }); + email: "", + }) const submit = (e: { preventDefault: () => void }) => { - e.preventDefault(); - post('/forgot-password'); - }; + e.preventDefault() + post("/forgot-password") + } return ( <> <Head title="Forgot Password" /> - {status && <div className="mb-4 text-sm font-medium text-success">{status}</div>} + {status && <div className="mb-4 font-medium text-sm text-success">{status}</div>} <Form validationErrors={errors} onSubmit={submit}> <TextField @@ -29,7 +29,7 @@ export default function ForgotPassword({ status }: ForgotPasswordProps) { isRequired errorMessage={errors.email} autoFocus - onChange={(v) => setData('email', v)} + onChange={(v) => setData("email", v)} /> <div className="mt-4 flex items-center justify-end"> @@ -39,7 +39,7 @@ export default function ForgotPassword({ status }: ForgotPasswordProps) { </div> </Form> </> - ); + ) } ForgotPassword.layout = (page: any) => ( @@ -50,4 +50,4 @@ ForgotPassword.layout = (page: any) => ( reset link that will allow you to choose a new one." children={page} /> -); +) diff --git a/resources/js/pages/auth/login.tsx b/resources/js/pages/auth/login.tsx index e8b4389..faef491 100644 --- a/resources/js/pages/auth/login.tsx +++ b/resources/js/pages/auth/login.tsx @@ -1,38 +1,41 @@ -import GuestLayout from '@/layouts/guest-layout'; -import { Head, useForm } from '@inertiajs/react'; -import React, { useEffect } from 'react'; -import { Button, Checkbox, Form, Link, TextField } from 'ui'; +import GuestLayout from "@/layouts/guest-layout" +import { Head, useForm } from "@inertiajs/react" +import type React from "react" +import { useEffect } from "react" +import { Button, Checkbox, Form, Link, TextField } from "ui" interface LoginProps { - status: string; - canResetPassword: boolean; + status: string + canResetPassword: boolean } export default function Login(args: LoginProps) { - const { status, canResetPassword } = args; + const { status, canResetPassword } = args const { data, setData, post, processing, errors, reset } = useForm({ - email: '', - password: '', - remember: '' - }); + email: "", + password: "", + remember: "", + }) useEffect(() => { return () => { - reset('password'); - }; - }, []); + reset("password") + } + }, []) const submit = (e: { preventDefault: () => void }) => { - e.preventDefault(); + e.preventDefault() - post(route('login')); - }; + post(route("login")) + } return ( <> <Head title="Log in" /> - {status && <div className="mb-4 text-sm font-medium text-green-600 dark:text-green-400">{status}</div>} + {status && ( + <div className="mb-4 font-medium text-green-600 text-sm dark:text-green-400">{status}</div> + )} <Form validationErrors={errors} onSubmit={submit} className="flex flex-col gap-y-4"> <TextField @@ -42,7 +45,7 @@ export default function Login(args: LoginProps) { value={data.email} autoComplete="username" autoFocus - onChange={(v) => setData('email', v)} + onChange={(v) => setData("email", v)} errorMessage={errors.email} isRequired /> @@ -52,13 +55,13 @@ export default function Login(args: LoginProps) { label="Password" value={data.password} autoComplete="current-password" - onChange={(v) => setData('password', v)} + onChange={(v) => setData("password", v)} errorMessage={errors.password} isRequired /> <div className="flex items-center justify-between"> - <Checkbox name="remember" onChange={(v) => setData('remember', v as any)}> + <Checkbox name="remember" onChange={(v) => setData("remember", v as any)}> Remember me </Checkbox> {canResetPassword && ( @@ -71,15 +74,15 @@ export default function Login(args: LoginProps) { Log in </Button> <div className="text-center"> - <Link href={route('register')} className="sm:text-sm" intent="secondary"> + <Link href={route("register")} className="sm:text-sm" intent="secondary"> Dont have account? Register </Link> </div> </Form> </> - ); + ) } Login.layout = (page: React.ReactNode) => { - return <GuestLayout header="Login" description="Log in to your account." children={page} />; -}; + return <GuestLayout header="Login" description="Log in to your account." children={page} /> +} diff --git a/resources/js/pages/auth/register.tsx b/resources/js/pages/auth/register.tsx index 8717f86..6b86b77 100644 --- a/resources/js/pages/auth/register.tsx +++ b/resources/js/pages/auth/register.tsx @@ -1,28 +1,29 @@ -import GuestLayout from '@/layouts/guest-layout'; -import { Head, useForm } from '@inertiajs/react'; -import React, { useEffect } from 'react'; -import { Button, Form, Link, TextField } from 'ui'; +import GuestLayout from "@/layouts/guest-layout" +import { Head, useForm } from "@inertiajs/react" +import type React from "react" +import { useEffect } from "react" +import { Button, Form, Link, TextField } from "ui" export default function Register() { const { data, setData, post, processing, errors, reset } = useForm({ - name: '', - email: '', - password: '', - password_confirmation: '', - terms: false - }); + name: "", + email: "", + password: "", + password_confirmation: "", + terms: false, + }) useEffect(() => { return () => { - reset('password', 'password_confirmation'); - }; - }, []); + reset("password", "password_confirmation") + } + }, []) const submit = (e: { preventDefault: () => void }) => { - e.preventDefault(); + e.preventDefault() - post('/register'); - }; + post("/register") + } return ( <> <Head title="Register" /> @@ -35,7 +36,7 @@ export default function Register() { value={data.name} autoComplete="name" autoFocus - onChange={(v) => setData('name', v)} + onChange={(v) => setData("name", v)} errorMessage={errors.name} isRequired /> @@ -45,7 +46,7 @@ export default function Register() { label="Email" value={data.email} autoComplete="username" - onChange={(v) => setData('email', v)} + onChange={(v) => setData("email", v)} errorMessage={errors.email} isRequired /> @@ -56,7 +57,7 @@ export default function Register() { label="Password" value={data.password} autoComplete="current-password" - onChange={(v) => setData('password', v)} + onChange={(v) => setData("password", v)} errorMessage={errors.password} isRequired /> @@ -66,7 +67,7 @@ export default function Register() { label="Confirm Password" name="password_confirmation" value={data.password_confirmation} - onChange={(v) => setData('password_confirmation', v)} + onChange={(v) => setData("password_confirmation", v)} errorMessage={errors.password_confirmation} isRequired /> @@ -81,9 +82,11 @@ export default function Register() { </div> </Form> </> - ); + ) } Register.layout = (page: React.ReactNode) => { - return <GuestLayout header="Register" description="Register for your new account." children={page} />; -}; + return ( + <GuestLayout header="Register" description="Register for your new account." children={page} /> + ) +} diff --git a/resources/js/pages/auth/reset-password.tsx b/resources/js/pages/auth/reset-password.tsx index f0d16fa..8657de9 100644 --- a/resources/js/pages/auth/reset-password.tsx +++ b/resources/js/pages/auth/reset-password.tsx @@ -1,34 +1,32 @@ -import GuestLayout from '@/layouts/guest-layout'; -import { Head, useForm } from '@inertiajs/react'; -import { useEffect } from 'react'; -import { Button, Form, TextField } from 'ui'; +import GuestLayout from "@/layouts/guest-layout" +import { Head, useForm } from "@inertiajs/react" +import { useEffect } from "react" +import { Button, Form, TextField } from "ui" interface ResetPasswordProps { - token: string; - email: string; + token: string + email: string } -type InputTargetProps = { name: any; value: any }; - export default function ResetPassword(args: ResetPasswordProps) { - const { token, email } = args; + const { token, email } = args const { data, setData, post, processing, errors, reset } = useForm({ token: token, email: email, - password: '', - password_confirmation: '' - }); + password: "", + password_confirmation: "", + }) useEffect(() => { return () => { - reset('password', 'password_confirmation'); - }; - }, []); + reset("password", "password_confirmation") + } + }, []) const submit = (e: { preventDefault: () => void }) => { - e.preventDefault(); - post('/reset-password'); - }; + e.preventDefault() + post("/reset-password") + } return ( <> @@ -43,7 +41,7 @@ export default function ResetPassword(args: ResetPasswordProps) { name="email" value={data.email} autoComplete="username" - onChange={(v) => setData('email', v)} + onChange={(v) => setData("email", v)} /> <TextField @@ -55,7 +53,7 @@ export default function ResetPassword(args: ResetPasswordProps) { value={data.password} autoComplete="new-password" autoFocus - onChange={(v) => setData('password', v)} + onChange={(v) => setData("password", v)} /> <TextField @@ -64,7 +62,7 @@ export default function ResetPassword(args: ResetPasswordProps) { name="password_confirmation" value={data.password_confirmation} autoComplete="new-password" - onChange={(v) => setData('password_confirmation', v)} + onChange={(v) => setData("password_confirmation", v)} errorMessage={errors.password_confirmation} isRequired /> @@ -76,7 +74,7 @@ export default function ResetPassword(args: ResetPasswordProps) { </div> </Form> </> - ); + ) } -ResetPassword.layout = (page: any) => <GuestLayout children={page} />; +ResetPassword.layout = (page: any) => <GuestLayout children={page} /> diff --git a/resources/js/pages/auth/verify-email.tsx b/resources/js/pages/auth/verify-email.tsx index 0913b26..3bd483b 100644 --- a/resources/js/pages/auth/verify-email.tsx +++ b/resources/js/pages/auth/verify-email.tsx @@ -1,21 +1,22 @@ -import GuestLayout from '@/layouts/guest-layout'; -import { Head, useForm } from '@inertiajs/react'; -import { Button, Form, Link } from 'ui'; +import GuestLayout from "@/layouts/guest-layout" +import { Head, useForm } from "@inertiajs/react" +import { Button, Form, Link } from "ui" export default function VerifyEmail({ status }: { status?: any }) { - const { post, processing } = useForm(); + const { post, processing } = useForm() const submit = (e: { preventDefault: () => void }) => { - e.preventDefault(); + e.preventDefault() - post('/email/verification-notification'); - }; + post("/email/verification-notification") + } return ( <> <Head title="Email Verification" /> - {status === 'verification-link-sent' && ( - <div className="mb-4 text-sm font-medium text-green-600"> - A new verification link has been sent to the email address you provided during registration. + {status === "verification-link-sent" && ( + <div className="mb-4 font-medium text-green-600 text-sm"> + A new verification link has been sent to the email address you provided during + registration. </div> )} @@ -27,9 +28,9 @@ export default function VerifyEmail({ status }: { status?: any }) { </Form> <Link - href={'/logout'} + href={"/logout"} routerOptions={{ - method: 'post' + method: "post", }} intent="secondary" > @@ -37,7 +38,7 @@ export default function VerifyEmail({ status }: { status?: any }) { </Link> </div> </> - ); + ) } VerifyEmail.layout = (page: any) => ( @@ -48,4 +49,4 @@ VerifyEmail.layout = (page: any) => ( link we just emailed to you? If you didn't receive the email, we will gladly send you another." children={page} /> -); +) diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx index 46db103..85f1645 100644 --- a/resources/js/pages/dashboard.tsx +++ b/resources/js/pages/dashboard.tsx @@ -1,7 +1,7 @@ -import AppLayout from '@/layouts/app-layout'; -import { PagePropsData } from '@/types'; -import { Head } from '@inertiajs/react'; -import { Card, Container } from 'ui'; +import AppLayout from "@/layouts/app-layout" +import type { PagePropsData } from "@/types" +import { Head } from "@inertiajs/react" +import { Card, Container } from "ui" export default function Dashboard({ auth }: PagePropsData) { return ( @@ -9,10 +9,10 @@ export default function Dashboard({ auth }: PagePropsData) { <Head title="Dashboard" /> <Container className="py-12"> - <Card className="p-6 w-full">Hello, {auth.user.name}!</Card> + <Card className="w-full p-6">Hello, {auth.user.name}!</Card> </Container> </> - ); + ) } -Dashboard.layout = (page: any) => <AppLayout children={page} />; +Dashboard.layout = (page: any) => <AppLayout children={page} /> diff --git a/resources/js/pages/home.tsx b/resources/js/pages/home.tsx index 6b842eb..0632db1 100644 --- a/resources/js/pages/home.tsx +++ b/resources/js/pages/home.tsx @@ -1,47 +1,52 @@ -import { Header } from '@/components/header'; -import { Logo } from '@/components/logo'; -import AppLayout from '@/layouts/app-layout'; -import { IconBrandJustdBlocks } from '@/layouts/app-navbar'; -import { Head } from '@inertiajs/react'; -import { IconBrandJustd, IconBrandParanoid, IconBrandParsinta, IconWindowVisitFill } from 'justd-icons'; -import { Card, Container, Link } from 'ui'; +import { Header } from "@/components/header" +import { Logo } from "@/components/logo" +import AppLayout from "@/layouts/app-layout" +import { IconBrandJustdBlocks } from "@/layouts/app-navbar" +import { Head } from "@inertiajs/react" +import { + IconBrandJustd, + IconBrandParanoid, + IconBrandParsinta, + IconWindowVisitFill, +} from "justd-icons" +import { Card, Container, Link } from "ui" const items = [ { - name: 'Justd', - url: 'https://getjustd.com', + name: "Justd", + url: "https://getjustd.com", icon: IconBrandJustd, description: - ' Justd is a chill set of React components, built on top of React Aria Components, all about keeping the web accessible.' + " Justd is a chill set of React components, built on top of React Aria Components, all about keeping the web accessible.", }, { - name: 'Blocks', - url: 'https://blocks.getjustd.com', + name: "Blocks", + url: "https://blocks.getjustd.com", icon: IconBrandJustdBlocks, description: - 'Create stunning, professional-grade layouts that not only save time but also elevate the quality of your projects.' + "Create stunning, professional-grade layouts that not only save time but also elevate the quality of your projects.", }, { - name: 'Icons', - url: 'https://getjustd.com/icons', + name: "Icons", + url: "https://getjustd.com/icons", icon: IconBrandParanoid, description: - 'A library of beautifully crafted react icons, perfect for enhancing the visual appeal and user experience of your web applications.' + "A library of beautifully crafted react icons, perfect for enhancing the visual appeal and user experience of your web applications.", }, { - name: 'Templates', - url: 'https://blocks.getjustd.com/templates', + name: "Templates", + url: "https://blocks.getjustd.com/templates", icon: IconWindowVisitFill, - description: 'Explore the next.js templates from web apps to design systems, all here.' + description: "Explore the next.js templates from web apps to design systems, all here.", }, { - name: 'Parsinta', - url: 'https://parsinta.com', + name: "Parsinta", + url: "https://parsinta.com", icon: IconBrandParsinta, description: - 'Improve your skills with Parsinta by pushing your skills to the next level, through the series here such as Laravel, Vue, React, Tailwind CSS and Much more.' - } -]; + "Improve your skills with Parsinta by pushing your skills to the next level, through the series here such as Laravel, Vue, React, Tailwind CSS and Much more.", + }, +] export default function Home() { return ( @@ -49,31 +54,34 @@ export default function Home() { <Head title="Welcome to Laravel" /> <Header title="Inertia Typescript" /> <Container> - <div className="overflow-hidden rounded-lg border lg:border-border border-transparent"> + <div className="overflow-hidden rounded-lg border border-transparent lg:border-border"> <div> <div className="sm:p-20"> <Link href="https://getjustd.com" target="_blank" - className="grid place-content-center size-12 outline-1 outline-border rounded-full" + className="grid size-12 place-content-center rounded-full outline-1 outline-border" > <Logo className="block size-7" /> </Link> - <div className="max-w-2xl mb-8"> - <div className="mt-6 text-xl sm:text-2xl">Laravel application with Inertia and React Typescript!</div> + <div className="mb-8 max-w-2xl"> + <div className="mt-6 text-xl sm:text-2xl"> + Laravel application with Inertia and React Typescript! + </div> <div className="mt-4 text-muted-fg sm:text-lg"> - This is a Laravel application with Inertia and React Typescript. It is a work in progress. If you have - any questions or suggestions, please feel free to contact me. + This is a Laravel application with Inertia and React Typescript. It is a work in + progress. If you have any questions or suggestions, please feel free to contact + me. </div> </div> - <div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> + <div className="grid grid-cols-1 gap-4 sm:grid-cols-2"> {items.map((item) => ( <div className="relative" key={item.name}> <Link className="absolute inset-0 size-full" target="_blank" href={item.url} /> <Card> <div className="px-6 pt-6"> - <div className="size-8 *:size-5 grid place-content-center rounded-full border"> + <div className="grid size-8 place-content-center rounded-full border *:size-5"> <item.icon /> </div> </div> @@ -90,7 +98,7 @@ export default function Home() { </div> </Container> </> - ); + ) } -Home.layout = (page: any) => <AppLayout children={page} />; +Home.layout = (page: any) => <AppLayout children={page} /> diff --git a/resources/js/pages/profile/edit.tsx b/resources/js/pages/profile/edit.tsx index 8b39626..c6bf08e 100644 --- a/resources/js/pages/profile/edit.tsx +++ b/resources/js/pages/profile/edit.tsx @@ -1,15 +1,15 @@ -import { Header } from '@/components/header'; -import AppLayout from '@/layouts/app-layout'; -import { Head } from '@inertiajs/react'; -import { Container } from 'ui'; -import { DeleteUserForm, UpdatePasswordForm, UpdateProfileInformationForm } from './partials'; +import { Header } from "@/components/header" +import AppLayout from "@/layouts/app-layout" +import { Head } from "@inertiajs/react" +import { Container } from "ui" +import { DeleteUserForm, UpdatePasswordForm, UpdateProfileInformationForm } from "./partials" interface Props { - mustVerifyEmail: boolean; - status?: string; + mustVerifyEmail: boolean + status?: string } -const title = 'Profile'; +const title = "Profile" export default function Edit({ mustVerifyEmail, status }: Props) { return ( @@ -17,14 +17,14 @@ export default function Edit({ mustVerifyEmail, status }: Props) { <Head title={title} /> <Header title={title} /> <Container> - <div className="max-w-3xl flex flex-col gap-y-6"> + <div className="flex max-w-3xl flex-col gap-y-6"> <UpdateProfileInformationForm mustVerifyEmail={mustVerifyEmail} status={status} /> <UpdatePasswordForm /> <DeleteUserForm /> </div> </Container> </> - ); + ) } -Edit.layout = (page: any) => <AppLayout children={page} />; +Edit.layout = (page: any) => <AppLayout children={page} /> diff --git a/resources/js/pages/profile/partials/delete-user-form.tsx b/resources/js/pages/profile/partials/delete-user-form.tsx index 12e997c..49ec561 100644 --- a/resources/js/pages/profile/partials/delete-user-form.tsx +++ b/resources/js/pages/profile/partials/delete-user-form.tsx @@ -1,45 +1,46 @@ -import { Button } from '@/components/ui/button'; -import { useForm } from '@inertiajs/react'; -import { useState } from 'react'; -import { Card, Modal, TextField } from 'ui'; +import { Button } from "@/components/ui/button" +import { useForm } from "@inertiajs/react" +import { useState } from "react" +import { Card, Modal, TextField } from "ui" export function DeleteUserForm() { - const [confirmingUserDeletion, setConfirmingUserDeletion] = useState(false); + const [confirmingUserDeletion, setConfirmingUserDeletion] = useState(false) const { data, setData, delete: destroy, processing, reset, - errors + errors, } = useForm({ - password: '' - }); + password: "", + }) const confirmUserDeletion = () => { - setConfirmingUserDeletion(true); - }; + setConfirmingUserDeletion(true) + } const deleteUser = () => { - destroy(route('profile.destroy'), { + destroy(route("profile.destroy"), { preserveScroll: true, onSuccess: () => closeModal(), - onFinish: () => reset() - }); - }; + onFinish: () => reset(), + }) + } const closeModal = () => { - setConfirmingUserDeletion(false); - reset(); - }; + setConfirmingUserDeletion(false) + reset() + } return ( <Card> <Card.Header> <Card.Title>Delete Account</Card.Title> <Card.Description> - Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your - account, please download any data or information that you wish to retain. + Once your account is deleted, all of its resources and data will be permanently deleted. + Before deleting your account, please download any data or information that you wish to + retain. </Card.Description> </Card.Header> <Card.Content> @@ -49,9 +50,9 @@ export function DeleteUserForm() { <Modal.Header> <Modal.Title>Delete Account</Modal.Title> <Modal.Description> - Are you sure you want to delete your account? Once your account is deleted, all of its resources and - data will be permanently deleted. Please enter your password to confirm you would like to permanently - delete your account. + Are you sure you want to delete your account? Once your account is deleted, all of + its resources and data will be permanently deleted. Please enter your password to + confirm you would like to permanently delete your account. </Modal.Description> </Modal.Header> @@ -60,7 +61,7 @@ export function DeleteUserForm() { type="password" placeholder="Password" value={data.password} - onChange={(v) => setData('password', v)} + onChange={(v) => setData("password", v)} errorMessage={errors.password} isRequired /> @@ -75,5 +76,5 @@ export function DeleteUserForm() { </Modal> </Card.Content> </Card> - ); + ) } diff --git a/resources/js/pages/profile/partials/index.ts b/resources/js/pages/profile/partials/index.ts index db4bbbd..0d90590 100644 --- a/resources/js/pages/profile/partials/index.ts +++ b/resources/js/pages/profile/partials/index.ts @@ -1,3 +1,3 @@ -export * from './delete-user-form'; -export * from './update-password-form'; -export * from './update-profile-information-form'; +export * from "./delete-user-form" +export * from "./update-password-form" +export * from "./update-profile-information-form" diff --git a/resources/js/pages/profile/partials/update-password-form.tsx b/resources/js/pages/profile/partials/update-password-form.tsx index e17f338..cf6dade 100644 --- a/resources/js/pages/profile/partials/update-password-form.tsx +++ b/resources/js/pages/profile/partials/update-password-form.tsx @@ -1,44 +1,46 @@ -import { useForm } from '@inertiajs/react'; -import { useRef } from 'react'; -import { toast } from 'sonner'; -import { Button, Card, Form, TextField } from 'ui'; +import { useForm } from "@inertiajs/react" +import { useRef } from "react" +import { toast } from "sonner" +import { Button, Card, Form, TextField } from "ui" export function UpdatePasswordForm() { - const passwordInput = useRef<HTMLInputElement>(null); - const currentPasswordInput = useRef<HTMLInputElement>(null); + const passwordInput = useRef<HTMLInputElement>(null) + const currentPasswordInput = useRef<HTMLInputElement>(null) const { data, setData, put, errors, reset, processing, recentlySuccessful } = useForm({ - current_password: '', - password: '', - password_confirmation: '' - }); + current_password: "", + password: "", + password_confirmation: "", + }) const submit = (e: { preventDefault: () => void }) => { - e.preventDefault(); - put(route('password.update'), { + e.preventDefault() + put(route("password.update"), { preserveScroll: true, onSuccess: () => { - toast.success('Your profile information has been updated.'); - reset(); + toast.success("Your profile information has been updated.") + reset() }, onError: () => { if (errors.password) { - reset('password', 'password_confirmation'); - passwordInput.current?.focus(); + reset("password", "password_confirmation") + passwordInput.current?.focus() } if (errors.current_password) { - reset('current_password'); - currentPasswordInput.current?.focus(); + reset("current_password") + currentPasswordInput.current?.focus() } - } - }); - }; + }, + }) + } return ( <Card> <Card.Header> <Card.Title>Update Password</Card.Title> - <Card.Description>Ensure your account is using a long, random password to stay secure.</Card.Description> + <Card.Description> + Ensure your account is using a long, random password to stay secure. + </Card.Description> </Card.Header> <Card.Content> @@ -46,7 +48,7 @@ export function UpdatePasswordForm() { <TextField label="Current Password" value={data.current_password} - onChange={(v) => setData('current_password', v)} + onChange={(v) => setData("current_password", v)} type="password" autoComplete="current-password" isRequired @@ -58,7 +60,7 @@ export function UpdatePasswordForm() { label="Password" value={data.password} autoComplete="current-password" - onChange={(v) => setData('password', v)} + onChange={(v) => setData("password", v)} errorMessage={errors.password} isRequired /> @@ -68,7 +70,7 @@ export function UpdatePasswordForm() { label="Confirm Password" name="password_confirmation" value={data.password_confirmation} - onChange={(v) => setData('password_confirmation', v)} + onChange={(v) => setData("password_confirmation", v)} errorMessage={errors.password_confirmation} isRequired /> @@ -78,10 +80,10 @@ export function UpdatePasswordForm() { Save </Button> - {recentlySuccessful && <p className="text-sm text-muted-fg">Saved.</p>} + {recentlySuccessful && <p className="text-muted-fg text-sm">Saved.</p>} </div> </Form> </Card.Content> </Card> - ); + ) } diff --git a/resources/js/pages/profile/partials/update-profile-information-form.tsx b/resources/js/pages/profile/partials/update-profile-information-form.tsx index 0222e8e..b7dd158 100644 --- a/resources/js/pages/profile/partials/update-profile-information-form.tsx +++ b/resources/js/pages/profile/partials/update-profile-information-form.tsx @@ -1,32 +1,34 @@ -import { PagePropsData } from '@/types'; -import { useForm, usePage } from '@inertiajs/react'; -import { Button, Card, Form, Link, TextField } from 'ui'; +import type { PagePropsData } from "@/types" +import { useForm, usePage } from "@inertiajs/react" +import { Button, Card, Form, Link, TextField } from "ui" interface Props { - mustVerifyEmail: boolean; - status?: string; - className?: string; + mustVerifyEmail: boolean + status?: string + className?: string } export function UpdateProfileInformationForm({ mustVerifyEmail, status, className }: Props) { - const { auth } = usePage<PagePropsData>().props; + const { auth } = usePage<PagePropsData>().props const { data, setData, patch, errors, processing, recentlySuccessful } = useForm({ - name: auth.user.name ?? '', - email: auth.user.email ?? '' - }); + name: auth.user.name ?? "", + email: auth.user.email ?? "", + }) const submit = (e: { preventDefault: () => void }) => { - e.preventDefault(); - patch(route('profile.update'), { - preserveScroll: true - }); - }; + e.preventDefault() + patch(route("profile.update"), { + preserveScroll: true, + }) + } return ( <Card> <Card.Header> <Card.Title>Profile Information</Card.Title> - <Card.Description>Update your account's profile information and email address.</Card.Description> + <Card.Description> + Update your account's profile information and email address. + </Card.Description> </Card.Header> <Card.Content> <Form validationErrors={errors} onSubmit={submit} className="space-y-6"> @@ -35,7 +37,7 @@ export function UpdateProfileInformationForm({ mustVerifyEmail, status, classNam label="Name" type="text" value={data.name} - onChange={(v) => setData('name', v)} + onChange={(v) => setData("name", v)} isRequired errorMessage={errors.name} autoFocus @@ -46,7 +48,7 @@ export function UpdateProfileInformationForm({ mustVerifyEmail, status, classNam type="email" label="Email" value={data.email} - onChange={(v) => setData('email', v)} + onChange={(v) => setData("email", v)} isRequired errorMessage={errors.email} autoComplete="email" @@ -57,18 +59,18 @@ export function UpdateProfileInformationForm({ mustVerifyEmail, status, classNam <p className="mt-2 text-sm"> Your email address is unverified. <Link - href={route('verification.send')} + href={route("verification.send")} intent="secondary" routerOptions={{ - method: 'post' + method: "post", }} > Click here to re-send the verification email. </Link> </p> - {status === 'verification-link-sent' && ( - <div className="mt-2 text-sm font-medium text-green-600"> + {status === "verification-link-sent" && ( + <div className="mt-2 font-medium text-green-600 text-sm"> A new verification link has been sent to your email address. </div> )} @@ -79,10 +81,10 @@ export function UpdateProfileInformationForm({ mustVerifyEmail, status, classNam <Button type="submit" isDisabled={processing}> Save </Button> - {recentlySuccessful && <p className="text-sm text-muted-fg">Saved.</p>} + {recentlySuccessful && <p className="text-muted-fg text-sm">Saved.</p>} </div> </Form> </Card.Content> </Card> - ); + ) } diff --git a/resources/js/ssr.tsx b/resources/js/ssr.tsx index ebcbe28..c854484 100644 --- a/resources/js/ssr.tsx +++ b/resources/js/ssr.tsx @@ -1,28 +1,26 @@ -import { Ziggy as ziggy } from '@/ziggy'; -import { createInertiaApp } from '@inertiajs/react'; -import createServer from '@inertiajs/react/server'; -import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; -import ReactDOMServer from 'react-dom/server'; -import { route, type RouteName } from 'ziggy-js'; +import { createInertiaApp } from "@inertiajs/react" +import createServer from "@inertiajs/react/server" +import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers" +import ReactDOMServer from "react-dom/server" +import { route } from "ziggy-js" + +const appName = import.meta.env.VITE_APP_NAME || "Laravel" -const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; createServer((page) => createInertiaApp({ page, render: ReactDOMServer.renderToString, - title: (title) => (title ? `${title} / ${appName}` : appName), - resolve: (name) => resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob('./pages/**/*.tsx')), + title: (title) => `${title} - ${appName}`, + resolve: (name) => + resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob("./pages/**/*.tsx")), setup: ({ App, props }) => { - // @ts-expect-error - global.route<RouteName> = (name, params, absolute) => - // @ts-expect-error + // @ts-ignore + global.route = (name, params, absolute) => route(name, params as any, absolute, { - ...ziggy, - // @ts-expect-error - location: new URL(page.props.ziggy.location) - }); - - return <App {...props} />; - } - }) -); + ...page.props.ziggy, + location: new URL(page.props.ziggy.location), + }) + return <App {...props} /> + }, + }), +) diff --git a/resources/js/types/global.d.ts b/resources/js/types/global.d.ts index 3d6ba97..496e063 100644 --- a/resources/js/types/global.d.ts +++ b/resources/js/types/global.d.ts @@ -1,17 +1,17 @@ -import { VisitOptions } from '@inertiajs/core'; -import { type AxiosInstance } from 'axios'; -import { type route as routeFn } from 'ziggy-js'; +import type { VisitOptions } from "@inertiajs/core" +import type { AxiosInstance } from "axios" +import type { route as routeFn } from "ziggy-js" declare global { interface Window { - axios: AxiosInstance; + axios: AxiosInstance } - let route: typeof routeFn; + let route: typeof routeFn } -declare module 'react-aria-components' { +declare module "react-aria-components" { interface RouterConfig { - routerOptions: VisitOptions; + routerOptions: VisitOptions } } diff --git a/resources/js/types/index.ts b/resources/js/types/index.ts index 7a0abac..05c27e9 100644 --- a/resources/js/types/index.ts +++ b/resources/js/types/index.ts @@ -1,18 +1,18 @@ export type AuthData = { - user: AuthenticatedUserData; -}; + user: AuthenticatedUserData +} export type AuthenticatedUserData = { - id: number; - email: string; - name: string; - gravatar: string; - email_verified_at: string | null; -}; + id: number + email: string + name: string + gravatar: string + email_verified_at: string | null +} export type FlashMessageData = { - type: string; - message: string; -}; + type: string + message: string +} export type PagePropsData = { - auth: AuthData; - flashMessage: FlashMessageData; -}; + auth: AuthData + flashMessage: FlashMessageData +} diff --git a/resources/js/utils/classes.ts b/resources/js/utils/classes.ts index 80a7444..c9ac784 100644 --- a/resources/js/utils/classes.ts +++ b/resources/js/utils/classes.ts @@ -1,6 +1,6 @@ -import { ClassValue, clsx } from 'clsx'; -import { twMerge } from 'tailwind-merge'; +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" -const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs)); +const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs)) -export { cn }; +export { cn } diff --git a/resources/js/utils/use-media-query.ts b/resources/js/utils/use-media-query.ts index 110f85b..f6f9cb7 100644 --- a/resources/js/utils/use-media-query.ts +++ b/resources/js/utils/use-media-query.ts @@ -1,19 +1,19 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState } from "react" export const useMediaQuery = (query: string) => { - const [value, setValue] = useState(false); + const [value, setValue] = useState(false) useEffect(() => { const onChange = (event: MediaQueryListEvent) => { - setValue(event.matches); - }; + setValue(event.matches) + } - const result = matchMedia(query); - result.addEventListener('change', onChange); - setValue(result.matches); + const result = matchMedia(query) + result.addEventListener("change", onChange) + setValue(result.matches) - return () => result.removeEventListener('change', onChange); - }, [query]); + return () => result.removeEventListener("change", onChange) + }, [query]) - return value; -}; + return value +} From c0b0fbed0cea38a3b9566e41d8b053e58d5bffb7 Mon Sep 17 00:00:00 2001 From: "Irsyad A. Panjaitan" <irsyad@parsinta.com> Date: Fri, 14 Feb 2025 19:05:41 +0700 Subject: [PATCH 4/4] upgrade --- resources/js/ssr.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/js/ssr.tsx b/resources/js/ssr.tsx index c854484..8319441 100644 --- a/resources/js/ssr.tsx +++ b/resources/js/ssr.tsx @@ -17,7 +17,9 @@ createServer((page) => // @ts-ignore global.route = (name, params, absolute) => route(name, params as any, absolute, { + // @ts-ignore ...page.props.ziggy, + // @ts-ignore location: new URL(page.props.ziggy.location), }) return <App {...props} />