From b037cec1002c10bfa35a4742ad018781fe09e625 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Mon, 9 Dec 2024 16:36:17 +0100 Subject: [PATCH] First Commit --- .npmrc | 1 + .vscode/settings.json | 3 + Dockerfile | 27 +++ README.md | 41 +--- bun.lockb | Bin 62855 -> 75735 bytes components/general/Logo.tsx | 22 ++ components/lib/editors/AceEditor.tsx | 136 ++++++++++++ components/lib/elements/Border.tsx | 36 ++++ components/lib/elements/Breadcrumbs.tsx | 94 ++++++++ components/lib/elements/Card.tsx | 45 ++++ .../lib/elements/ColorSchemeSelector.tsx | 39 ++++ components/lib/elements/Loading.tsx | 58 +++++ components/lib/elements/LoadingBlock.tsx | 24 +++ components/lib/elements/Modal.tsx | 71 ++++++ components/lib/elements/Paper.tsx | 32 +++ components/lib/elements/Search.tsx | 110 ++++++++++ components/lib/elements/Toggle.tsx | 52 +++++ components/lib/form/Form.tsx | 23 ++ components/lib/form/ImageUpload.tsx | 109 ++++++++++ components/lib/form/Input.tsx | 136 ++++++++++++ components/lib/form/Select.tsx | 117 ++++++++++ components/lib/form/Textarea.tsx | 9 + components/lib/layout/Button.tsx | 204 ++++++++++++++++++ components/lib/layout/Center.tsx | 23 ++ components/lib/layout/Container.tsx | 24 +++ components/lib/layout/Divider.tsx | 30 +++ components/lib/layout/Footer.tsx | 25 +++ components/lib/layout/H1.tsx | 19 ++ components/lib/layout/H2.tsx | 19 ++ components/lib/layout/H3.tsx | 19 ++ components/lib/layout/H4.tsx | 19 ++ components/lib/layout/H5.tsx | 19 ++ components/lib/layout/HR.tsx | 21 ++ components/lib/layout/Header.tsx | 25 +++ components/lib/layout/Link.tsx | 30 +++ components/lib/layout/List.tsx | 54 +++++ .../lib/layout/LoadingRectangleBlock.tsx | 28 +++ components/lib/layout/Main.tsx | 23 ++ components/lib/layout/P.tsx | 24 +++ components/lib/layout/Row.tsx | 23 ++ components/lib/layout/Section.tsx | 24 +++ components/lib/layout/Span.tsx | 33 +++ components/lib/layout/Stack.tsx | 23 ++ components/lib/utils/fetch/fetchApi.ts | 106 +++++++++ .../lib/utils/form/imageInputToBase64.ts | 93 ++++++++ components/pages/Home/(data)/skills.ts | 167 ++++++++++++++ components/pages/Home/(data)/work.ts | 92 ++++++++ .../pages/Home/(sections)/AboutSection.tsx | 18 ++ .../pages/Home/(sections)/MySkillsSection.tsx | 159 ++++++++++++++ .../pages/Home/(sections)/MyWorkSection.tsx | 154 +++++++++++++ components/pages/Home/index.tsx | 42 ++++ components/pages/about/index.tsx | 22 ++ components/pages/contact/index.tsx | 28 +++ components/pages/skills/index.tsx | 18 ++ components/pages/work/index.tsx | 15 ++ layouts/main/(data)/links.tsx | 70 ++++++ layouts/main/(partials)/HeaderLink.tsx | 13 ++ layouts/main/(partials)/SocialLink.tsx | 18 ++ layouts/main/(sections)/MobileMenu.tsx | 56 +++++ layouts/main/Aside.tsx | 40 ++++ layouts/main/Footer.tsx | 22 ++ layouts/main/Header.tsx | 37 ++++ layouts/main/index.tsx | 30 +++ package.json | 5 +- pages/404.tsx | 51 +++++ pages/_app.tsx | 2 +- pages/_document.tsx | 18 +- pages/about.tsx | 10 + pages/contact.tsx | 10 + pages/index.tsx | 130 ++--------- pages/skills.tsx | 14 ++ pages/work.tsx | 14 ++ .../Resume-Benjamin-Toby-Linkedin.pdf | Bin 0 -> 87368 bytes public/documents/Resume-Benjamin-Toby.pdf | Bin 0 -> 113317 bytes public/favicon.ico | Bin 25931 -> 320 bytes public/favicon.png | Bin 0 -> 320 bytes public/file.svg | 1 - public/globe.svg | 1 - public/icons/android-chrome-192x192.png | Bin 0 -> 1103 bytes public/icons/apple-touch-icon.png | Bin 0 -> 984 bytes public/icons/icon-192x192.png | Bin 0 -> 1103 bytes public/icons/icon-256x256.png | Bin 0 -> 1441 bytes public/icons/icon-384x384.png | Bin 0 -> 2372 bytes public/icons/icon-512x512.png | Bin 0 -> 3361 bytes public/icons/touch-icon-ipad-retina.png | Bin 0 -> 1441 bytes public/icons/touch-icon-ipad.png | Bin 0 -> 1441 bytes public/icons/touch-icon-iphone-retina.png | Bin 0 -> 1441 bytes public/icons/touch-icon-iphone.png | Bin 0 -> 1441 bytes public/images/Base-Fitness-Screenshot.webp | Bin 0 -> 21942 bytes public/images/Homeruntoken-graphic.jpg | Bin 0 -> 52315 bytes public/images/Module-Trace-Usage.gif | Bin 0 -> 1688375 bytes public/images/Module-trace-image.png | Bin 0 -> 60388 bytes public/images/castcord-graphic.jpg | Bin 0 -> 35510 bytes public/images/datasquirel-img.jpg | Bin 0 -> 29749 bytes public/images/external-link-dark.png | Bin 0 -> 983 bytes public/images/external-link.png | Bin 0 -> 785 bytes public/images/github-white.png | Bin 0 -> 1938 bytes public/images/guaranteed.jpg | Bin 0 -> 76884 bytes public/images/linkedin-white.png | Bin 0 -> 919 bytes public/images/logo-icon-white.svg | 1 + public/images/logo-icon.svg | 15 ++ public/images/logo-v3-cropped.png | Bin 0 -> 6667 bytes public/images/logo-v3.png | Bin 0 -> 31834 bytes public/images/logo-v3.svg | 1 + public/images/logo-white.svg | 14 ++ public/images/mattermost-logo.webp | Bin 0 -> 1016 bytes public/images/my-photo-2.png | Bin 0 -> 235667 bytes public/images/my-photo-3.png | Bin 0 -> 278457 bytes public/images/my-photo-stroked.png | Bin 0 -> 243131 bytes public/images/my-photo.png | Bin 0 -> 411972 bytes public/images/next-7-graphic-min.jpg | Bin 0 -> 67238 bytes public/images/next7screenshot.png | Bin 0 -> 496553 bytes public/images/programming-laptop.png | Bin 0 -> 158039 bytes public/images/projects-section-image.png | Bin 0 -> 172214 bytes public/images/renition-graphic.jpg | Bin 0 -> 30085 bytes public/images/rm378-07c-min.png | Bin 0 -> 184227 bytes public/images/showmerebates.jpg | Bin 0 -> 51322 bytes public/images/showmerebates.png | Bin 0 -> 479666 bytes public/images/stirrmediascreenshot.png | Bin 0 -> 478281 bytes public/images/summit-lending.jpg | Bin 0 -> 48872 bytes public/images/trader-hub-graphic-min.jpg | Bin 0 -> 81612 bytes public/images/traderhubscreenshot.png | Bin 0 -> 1446970 bytes public/images/why-so-serious.png | Bin 0 -> 132249 bytes public/next.svg | 1 - public/vercel.svg | 1 - public/window.svg | 1 - styles/globals.css | 62 ++++-- tailwind.config.ts | 28 +-- 128 files changed, 3281 insertions(+), 193 deletions(-) create mode 100644 .npmrc create mode 100644 .vscode/settings.json create mode 100644 Dockerfile create mode 100644 components/general/Logo.tsx create mode 100755 components/lib/editors/AceEditor.tsx create mode 100644 components/lib/elements/Border.tsx create mode 100755 components/lib/elements/Breadcrumbs.tsx create mode 100644 components/lib/elements/Card.tsx create mode 100644 components/lib/elements/ColorSchemeSelector.tsx create mode 100644 components/lib/elements/Loading.tsx create mode 100644 components/lib/elements/LoadingBlock.tsx create mode 100644 components/lib/elements/Modal.tsx create mode 100644 components/lib/elements/Paper.tsx create mode 100644 components/lib/elements/Search.tsx create mode 100644 components/lib/elements/Toggle.tsx create mode 100644 components/lib/form/Form.tsx create mode 100644 components/lib/form/ImageUpload.tsx create mode 100644 components/lib/form/Input.tsx create mode 100644 components/lib/form/Select.tsx create mode 100644 components/lib/form/Textarea.tsx create mode 100644 components/lib/layout/Button.tsx create mode 100644 components/lib/layout/Center.tsx create mode 100644 components/lib/layout/Container.tsx create mode 100644 components/lib/layout/Divider.tsx create mode 100644 components/lib/layout/Footer.tsx create mode 100644 components/lib/layout/H1.tsx create mode 100644 components/lib/layout/H2.tsx create mode 100644 components/lib/layout/H3.tsx create mode 100644 components/lib/layout/H4.tsx create mode 100644 components/lib/layout/H5.tsx create mode 100644 components/lib/layout/HR.tsx create mode 100644 components/lib/layout/Header.tsx create mode 100644 components/lib/layout/Link.tsx create mode 100644 components/lib/layout/List.tsx create mode 100644 components/lib/layout/LoadingRectangleBlock.tsx create mode 100644 components/lib/layout/Main.tsx create mode 100644 components/lib/layout/P.tsx create mode 100644 components/lib/layout/Row.tsx create mode 100644 components/lib/layout/Section.tsx create mode 100644 components/lib/layout/Span.tsx create mode 100644 components/lib/layout/Stack.tsx create mode 100755 components/lib/utils/fetch/fetchApi.ts create mode 100644 components/lib/utils/form/imageInputToBase64.ts create mode 100644 components/pages/Home/(data)/skills.ts create mode 100644 components/pages/Home/(data)/work.ts create mode 100644 components/pages/Home/(sections)/AboutSection.tsx create mode 100644 components/pages/Home/(sections)/MySkillsSection.tsx create mode 100644 components/pages/Home/(sections)/MyWorkSection.tsx create mode 100644 components/pages/Home/index.tsx create mode 100644 components/pages/about/index.tsx create mode 100644 components/pages/contact/index.tsx create mode 100644 components/pages/skills/index.tsx create mode 100644 components/pages/work/index.tsx create mode 100644 layouts/main/(data)/links.tsx create mode 100644 layouts/main/(partials)/HeaderLink.tsx create mode 100644 layouts/main/(partials)/SocialLink.tsx create mode 100644 layouts/main/(sections)/MobileMenu.tsx create mode 100644 layouts/main/Aside.tsx create mode 100644 layouts/main/Footer.tsx create mode 100644 layouts/main/Header.tsx create mode 100644 layouts/main/index.tsx create mode 100644 pages/404.tsx create mode 100644 pages/about.tsx create mode 100644 pages/contact.tsx create mode 100644 pages/skills.tsx create mode 100644 pages/work.tsx create mode 100644 public/documents/Resume-Benjamin-Toby-Linkedin.pdf create mode 100644 public/documents/Resume-Benjamin-Toby.pdf create mode 100644 public/favicon.png delete mode 100644 public/file.svg delete mode 100644 public/globe.svg create mode 100644 public/icons/android-chrome-192x192.png create mode 100644 public/icons/apple-touch-icon.png create mode 100644 public/icons/icon-192x192.png create mode 100644 public/icons/icon-256x256.png create mode 100644 public/icons/icon-384x384.png create mode 100644 public/icons/icon-512x512.png create mode 100644 public/icons/touch-icon-ipad-retina.png create mode 100644 public/icons/touch-icon-ipad.png create mode 100644 public/icons/touch-icon-iphone-retina.png create mode 100644 public/icons/touch-icon-iphone.png create mode 100644 public/images/Base-Fitness-Screenshot.webp create mode 100644 public/images/Homeruntoken-graphic.jpg create mode 100644 public/images/Module-Trace-Usage.gif create mode 100644 public/images/Module-trace-image.png create mode 100644 public/images/castcord-graphic.jpg create mode 100644 public/images/datasquirel-img.jpg create mode 100644 public/images/external-link-dark.png create mode 100644 public/images/external-link.png create mode 100644 public/images/github-white.png create mode 100644 public/images/guaranteed.jpg create mode 100644 public/images/linkedin-white.png create mode 100644 public/images/logo-icon-white.svg create mode 100644 public/images/logo-icon.svg create mode 100644 public/images/logo-v3-cropped.png create mode 100644 public/images/logo-v3.png create mode 100644 public/images/logo-v3.svg create mode 100644 public/images/logo-white.svg create mode 100644 public/images/mattermost-logo.webp create mode 100644 public/images/my-photo-2.png create mode 100644 public/images/my-photo-3.png create mode 100644 public/images/my-photo-stroked.png create mode 100644 public/images/my-photo.png create mode 100644 public/images/next-7-graphic-min.jpg create mode 100644 public/images/next7screenshot.png create mode 100644 public/images/programming-laptop.png create mode 100644 public/images/projects-section-image.png create mode 100644 public/images/renition-graphic.jpg create mode 100644 public/images/rm378-07c-min.png create mode 100644 public/images/showmerebates.jpg create mode 100644 public/images/showmerebates.png create mode 100644 public/images/stirrmediascreenshot.png create mode 100644 public/images/summit-lending.jpg create mode 100644 public/images/trader-hub-graphic-min.jpg create mode 100644 public/images/traderhubscreenshot.png create mode 100644 public/images/why-so-serious.png delete mode 100644 public/next.svg delete mode 100644 public/vercel.svg delete mode 100644 public/window.svg diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..1b4726a --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@moduletrace:registry=https://git.tben.me/api/packages/moduletrace/npm/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fd9d94a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "css.lint.unknownAtRules": "ignore" +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a79b60a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# Set Node.js version +FROM node:20-alpine + +RUN apk update && apk add --no-cache curl bash nano + +SHELL ["/bin/bash", "-c"] + +RUN curl -fsSL https://bun.sh/install | bash +ENV PATH="/root/.bun/bin:${PATH}" + +RUN mkdir /app + +# Set working directory +WORKDIR /app + +RUN touch /root/.bashrc +RUN echo 'alias ll="ls -laF"' >/root/.bashrc + +COPY . /app/. + +# Install dependencies +RUN bun install + +RUN bun run build + +# Run the app +CMD ["bun", "start"] diff --git a/README.md b/README.md index ef0e47e..a34b2ff 100644 --- a/README.md +++ b/README.md @@ -1,40 +1 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/pages/api-reference/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. - -[API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. - -The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) instead of React pages. - -This project uses [`next/font`](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn-pages-router) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/pages/building-your-application/deploying) for more details. +# Welcome to Tben diff --git a/bun.lockb b/bun.lockb index daeb4c5e4e074fd14861b84e05477c6eadc18033..a1f31799b8c2431623a65e8e24b7bdb94c8beca1 100755 GIT binary patch delta 18895 zcmeHvcUV-{*7uwPD1$*kDKm%y0wU2u9g#i)UfR#4DC`|@b6dY`^$FeoH*kW&> zBKCqtjTns@HTDvtvA1Y!vAn-^W{ysB&He84z286I^W3e!z4qE=?X}k~XU}2QjK6Po z?u7YtU+>1QBkWyo_lwIsI_LF6kMK8F^Br20KK?%VQ2n^%O}CsMb@JjQ6b{c&yXbun z3Tb0xE0L7WM!GiW6i_K>z9u=f0O@qJB0fhmj97`nOd@dtU(ZP2@Dhm}{0&eU=usoB z0Cfbv5Yz#5BB&jxE;BhlPY*$1mJ&%LWXM2WL0{LDNL)ZKfi?u)W28$!8-O1R>I|A` zq+LKskq}Vo*aeh2l!8)+&uSRxRZx5I)kazgswYjBB0+SjkrsoJC3eCLbwCSrS=tmR zIvDx2!0R;GLp1pkNosCZE{NnMDt&?cSQ~@o7K2i~cA!?E_fT#PT7r>Ny)+|D;3aw? zqYE;qVp3g0!w%rd)OoqOf>fPO5>wA0pe-oX%Sq13)uu@#DTTU}+#-o27TS|w4U`cS z0VPXZ$8e!TX&B5v`52U-*mfhIU8L6y&O&B?*o!*s4oaH02c?mBhZI^19gH*plq{vy zCFkcS7fG}^nVNiUfzA{8)Ik{95j~BLX~g0HmSTP=eF|dC9-nUl69F9$#(EGWofwunw%jL$y)H# zZWk61DPoQVMHJ!$MaF}t`g)kRpeRoxSz;8}4i!jnn9)?(ZidB#dKz7-MA92PW});~ zSA!x&$=PH;Ni_1w)Ga}&V_zf9*XVMyhG-;pJq(N55|re>aTn_8OEZ&oxkGa#k{C~e zhGi(A3VGUe%&bH*A3TlR)hN&&luT!3q*|RKBP%yW66ufY;|+Wh1K3?X|sdPS)AGgzb`QQBL&#)Xt}cq2s@|I|+-Bf;)U&4T9H3NF>;Cr87Y33{P)q$S;my z-7Fg0nAgkeDorZrRHm6z``y%^+$vUEY~=I@C3n03J~g9hN%w)@9c|)T_enjc#E#tx z!*)EaY~*t0tEd@%y{9&=Y!xoeZF%@!{QOLcVSObC<;{UWe}b>9>^Cx-H{>i0BwGXT*xQdRI&KolXs}Ydim*qwLz5 zjIHCBZtsvgLBFynX=ik2Wm>PEe>D7lU#A5J`zHT*^g)c}s4;J^bnY0}y|ruNQJq~@ zp9tNyuSO|(H)XBNSK6SpC%^4->!!nuMwM4S{Os4AzcWu6)j#9Tm}TZi$5$u3b$4(1 z?5m}ld%i0<(X;)}-_5(ZPgXwp`>S1st-tP9bz?}Yh2dS!gdh94Ngtd9)+09cov9yj z_R-_^J^X;PwJRcBc|Ue@Da+0eFix%MO2u$ulA8*AR-S(R01>t$GK z?J+5MC`c^W4F9GxYgx+w1k)9nIE(%gZZo%9Uh-T{BI*2(rQ1+SZK)w<`(qH=@^(Bs zT1#oM)=VN%v*WcQEI4y9m4H-FA#Vdx-G!9ILQHi>DqhH2j#M0_q|cC2p{qK~)-{Z) z!K`X4`QDZ+y0+3{za^faEVg!pg$LGRGCN+Ur8yD{dfw z0a6{Ql&faadXaq1IxM=LQW}oqL5X4U>{P8#ZY-;=7s+jBQoBgr%!WnVDfvD&Y?7Ul zTg$5LBDp(EY9Gma*s^GQC7*7~CfO^w4XoNelDo^K4k)S1q8*gdxpfUfB`n@P)Z%Aw z!DK_eRy}6rsN@stv1msnw}4G_jO1>yYR5=kZpW--D6wPFGNtuAJSN&2r}amy99k#V z!Fa9>B26*NHQD}=Lgo`>((=GMFsvw6CI7-2dta!Au}PfRek(7u=k*ROy19}+=)fj5 z40U}9pBLouxS!I_f&|Fx29B&^PFAs<0c=ao%i?pn&wnE~XtSaTN<}ar22l0WAnBlo|{87#B7vP|44AVMha%(krM&N|`ZR zhfv#kt_BYEg%Bh*>gmcRVFfO5Wk*5Ax-l!clE3N3CdrjjStIN*h%gt7n+}eQ0fF*R zen}%1?WE*SHe!>Ulu}!FgAr@68BU?np5Qv5W*ss-|D8LVR9`6#@W3Y!WD2W-pX0%- zoR$1(4;Jl=jyxq2Jc4PB!0L(M+6pyo{lYB4kQq1@?;XZ@vgig%Y0btTC&?+)LJbZ} zP#$5i04XJr#$E+GF`Eh)Z9E}On z*dSCo6CBMjc9~-+f5x9p@>24?0qm%kQu;0KSUn6iVagZa=%B+MM!O!k*O3%V7K}I3 z$kk-FjYFl^z;#5Ku#YVQal*9~mO=qi2Hk184}(KR<4Sw=k;6psK0z$nN6AkKVv~H7 z((f_X)DF@yi66kxluF5z7M-zwJJMOle-li{p!7O240~gSPbeQ6!mOGprA1hClxaAd zT7?=o!=e}sjur(LC`Nh2$O+5G!W`YUX4ZBQ7U@X!5=OBbDH^$@u%zA?IoJVK2@C(& z53G_8&ag1aMrB40{eZg%juwlsvZP^9j!r&_uyl&SQ4ctav2=DCIkbb-UV+1Z<9r21 zuxNiJe<*@Y0~j`}O5CFqlmI^lG%_#PY#E|1nPo zJcLS_|z5uDyAE5jJMw$dl zAqg-C(f|skv?h4mYy|UALJ*KewGjRjS_Ad+0UAIdK*5w!$3;S}fl|FVFGRy_EoIxdxyBYOot+ z(0JAw={isfL@9qgF$n*TQoRiT(K3JnQBq(dF$hG7-%Jd`X6m0BQ~*S`7-=Oah0jq7 zqLj~3 z=D$#yf0F{LXl~?*(uisp`Tv9(s|x4-7t|X9QIcbN=2JfL4mAHB#3B4|=-&>1L0$_X z{`b!SH>zV=kw);}!yiUS0w^Q_ba8g(+DJ8CUG7I*lgV4FIZHMe zv6NM}Rqwso4>5B`Ou? z&8oo-i@``@R9sV56r*NMVy)Q~a0=!ft7hlHO^sD?e(VCc30>;@3EIdxl?tohur{aRyU2t=|TC>=$DlU}G>#Am*4IRB>EzIrv?wy&KtZJlMn zdpspM$@@A)!wruGAvW(*(T5v&i}&e2zy6&U5jm%1=bMbmI@-wg@X0=1b|1bLa`(W# zYv$&)I#gvW0KUix*BQjB9ZDz?3#WeHnD?$X<=7Z+UgEmN~7y zNGmtdjvnP=Ft=`WAbeQbp65FfPRC`xe&PKS&ud%G+NON_Ecct&haQ^-yvq){Qrn zy@r&%9oJAZ$wb#kQBhFyZ%dnxe}CoRs2y*v-(Y<|bhwiHwL*FANJYABMbw5@j^-oW zwykKDG+OTM-g(ZETYgVR*w+93iR+Ll=cb&AOL?I=#<6$ZWqSFQS!s3K%_vHEkY0W0 zPxEK5ANzDL?;gMH>G7-ySEom=+|w#DW^P`w)9|dp-cvk%ES^+MD7)r6^lhE36MnB4 zweyXMc5O_xv)!Kgq|DuIQ`O-Sn3|C{=bEJ}SG8Y1Ykp^yLpkp}Su20{ zcIUVnIq9h%78lR^qic_xMa@sVp7XnWZY0~#L&hw+sbbriie3DC>7pk$?!;fpRzIG! zQ`vjM4abQ072nnI&+B_!J*_0VoyYDbidJsh5{IQq+UEFqd(^sW(k$dD&ZeNsY@*oLVeSnrPFn*^i_4eQ28WI&j6H6Rj1@x|dAP z{yeN58JW^+Tc4Iwte&3?AJWiu#J#X}8LJ(0jtAWRahCj8EsrKg63>^9{YEyk`Q^jW zqb-IVRX26mUO0Z9o7JNICfao})lMVHo!s^E@dUd6EO7Z!#5A!4Q&49lc`Gv-EH3PW!AZuPGyPh zt2>s@d09T~_!ATDqW+@Yi0E}EGIu<(joiAx@3QxYv$mc`rw2*e+)SQ$VU@$r-*+GB>i{-yIF?%?^mF8iq9yO8|U8xvebJkBkAK!GYRtn2j3iB$_;ftBVD7QnqbKL|X+6D1c-y^aYkM4$7bn;k-r1k<^4X?E zveTnC{_-a9*p^dn^Dg%;{$)?Bc7t~FYG=z)b#9rAIo4D;`S{d?B{#F{-prWWaQlex z;Hlc}i>D1R`}6hF6P;SMsO$3}_tFyms$%)f4j1<=F&p>Yn6dG3{^Nc>9Mf(4_b=-# zYiq}jLpycnRG0A`l`gH9%`XTXn{dZ|;5Ny!@yc@dnWvxdMK|@^$6R~0XVcR&Ukxi! zIPR!cjSZ+;|D2z2Re`j zIQCua54|hDTsM1lV4K?}+KDfxNJaCoP4#LhH=exjRO;NIzwSzCC$E5~CEv2)e;idl zoPVkK>AE58Y>>UX{PZC2$x8}Dck^SnG*XV_JL{h{{^QB*)s0=4qPI+6HBnODWbL-s zXK%K*T4R-Y#p6!rTP0qtZ)eTl*tD|zU5m>vvyO0I4s+=4`-&av)AgFHPx0AV2b?4a zwcD=syYYKt6YaWxv>deSa9ZG>vMbQ;>LH)3{mzdo@2xo9}hPGFQ6&{rd83y-t3=b>`rV zS0>u^G}Ufpm9OT^k@ikBJ`c!Q@+5gz}k}iu<4s`u_ru_C& zZL0HvOY47DG@I*qx+32tI%!y^;k6otv#EV#`h8k6+eag2->$c_*O*4Xc8^q?`e8yX z*`j%89zL#@*mTs8*s_DlW<3r!=-YGZp%WGTe;Bv5r~G#7#vdFr`s7ZVksEEIo!V5p zKPnSa9}Ztoxl4ZRlzEFe^=|dt?)g4Dbx_IZ(H{m5Y1}bqdaoB7*LRoSiG7w)G_k(& zaKNLK_8TPc(l>VA^J|;mbAnl`zA}A!hU((3Z(m;@l4_aNC+x^Vms+zgB|0bFY1e0o z%a7XS`5RZX`QefBerWp}&rhy$@~A&Tue?0RLdkF1v&tB4(E)QJ3<-(}9$0Tb*`|5@|hUPtbd8_6QZp$o(tgj}n zD9t`PaQxX-gKl~J^h#p&#-`!yF=GZEE+~?<+%bBhiFSQVwfjEs;E^Ror+r3u{X;r^ za?@UK9D<+r$ojJL>-ye`LB+f7ckc6~|CXvm`BsY|ifJ#F-A{1&K7LP~MN3}33_jmg zdf?zCwy>X!?I}=k{g|{+%@X?Kj#{XS9bno?($f;vwr49JtUJ7G@5bEw8(Jhz_g%6) zFV3;Q!?lLbXRhc{=Werm;WEYAv|qg%jvu*cxbo=rApyE(@6v)oW=A|3#oqOov3Iy_ zCbOt`+=B;LvkmboE|tZ1!#z03npJjFaT>;TS99qs5pf3F)E&3rWNT*ML&a&?jvlxL zr{KQaQ^gHp+Mc)tr{ca0E}O}F;T8<8q?d}zW!2y&q~X4-R&j$_ks7yP4era}bj&*e zw_tEn6I5Iwy8v!(I_}GfDsCv7n21|&2JXw?idcAWHG2haVQ&>Tg53qTIurNhJ}Rz+ z&FcdzX|35iaC#QiSIsO3Vmy6S+$i=M+;(vN`l+}vY)wCmZjd#z?yus;vBdtkbSO=>UumT=oDJH<9fCH!K_DOHy%@nKlXI%fa}-O=a?Aj1OE%vWlC=s=-aj#rRTG z+zeKfg7M{HeBc=KPR01ZO-)sCv)Ki3a|dI5X)10Go0x|2uLk@qt^+qB1bPLX0m%#Vuv8!EFcEFH^-WV{0Qm8~`YfUhqEzD0~M9FM&ZHQwF{*Bsqkb zD$?t}rvQb+0Chkw^XXMJ2@zlBOObvCP<`=*J_av&Zp@>X`y_7|2}W3bgza6{QBTdN zS}lN%sL{X}fc{#f0}6n#z&PMbU_3Apm;}(+X{=L$uYhU53}7a}fLXw7;A>zGKsJ~M zbOU+-c%rh<<+jq^NYfcyn-wkh)7MAJ8E62|YwZodN?;YR8aNL82%G>;0;hnVfYZQ! zfUHS#Nya(?R03Op?Z76W4EPpU2GAdu<^v0WQXq-`_L_`D3Xlq90ogzfkPGAi{eZ53 z3W#RjE8_H_NQD9PF?|g{Z~J+`47dRPB5(<~3|s-O0%w4;z|X*Dpc>c(>;~2WqkuF( z1Ed2PfEFkOh5$o>VL*HO`$h*O+5&3<`jf&v(E9-Wf#f!D4Y&@R11bPo(R+X*APVRN zJOCa7j{sVQPXJnYcY$Ak8^Eu?7N82~415429Om*B=sVyIa0|Ez)Bz3w+kl0@3*aU2 z2k<-a40vwj#r*R$V+hZWVRbg{*%HaV0EvkPl7S?E3{3{N0L%a$z_5j#Mtlem2m}Cr zfG?l`ngO-hyOr+DW0j3PwW8XM0Z+gKaAO@-so1dPuKGsc+<~Tm7vKXl0lbZAD)R?O zLNE{nGzUTf%A?&xx+ek&fEwrpD1n{;*}a94j{v2ywgSkmZD>+kBM}MEV;bg{bO&OA7(iH;pr%U2gQqnc2Xq0t0b(9eF->K?fj;y{%l=680}cY;0S5qD zdNdOQftkP*U^FlaCcDg+KwI1GGRUkO8CtDL^VfgP`TD0gOlP2P%t3|H2`Z z2TGk$r?eBZfI&bGkP8e3NU5O!EoZ7%42%FufRO;nnhcBs#sXsiQk2?F0KNpq15{@s zFbU9;5UMZ(m<~|GuK*g&RA3rFoe(H*53n0p0L%ww1G4}I%mwBFUjuWD{3_6Oz-nLx zun_nLSPU#8otGi;EwBVw3M>bx(Mn(quol<=tOvFOn}DrAIZz3d0b2mdr@W0uS^>Hl zXbS8Cs4cbIW~4hochF|okHkJ;FHmKqeLp0|?`=?qr*y8o_ z5YeBgT(7@VSNeGFr*hHbsPmVS=w_=D*ryVO8GE!o(7BVD;S2VAS5zG8ykSdcJFb6# zZ%}}5phRuPT5gajw5UL)4%)in>+3Ey`F5NSMoi)F#<#(@dq-`MBVMo}qQ2+>l=-^2 z)I74XlQ03o$couh)SAj_mC5Ui{zRir@Lt`=@FnIjS@Y&JO_eONOdc#cARTzs>{a=Z zhu?lG7hRO{Rw}oT9&vy1r;=xEYMDGlbZL4sxsChC)AI^Hm5WYKYj!2wTQ)j<$fuH! zn(T5JCc37db?2}?%2hxIF$bC;L`>DO#Vxty0- zvi0RMKFpF`|3nO#h8o7&hL#2?f<)dZekUv6*u1{z3Y9Y0qgCr#b;67W48TK>z1t`c z7G0)JC9FLe^?dOylm}qx_~RhAF|7TXb_482+-+Nq5`W)7OsQlsQ*Dy-?QB^3ra(Ta zE}Or}$8u0z;n`NoT30%TV>OGOQ@V+r-)~vrQT$1+=v&qF?6Uk*gEsdymOyT-9dq6+ zW1BbC)nBp`PKHv^OGx|zzn}#6L!fXuqs<_G<)6fa`8M|r2oznBL?;211o?*e;%O~9 zD~WCd)Fy=5d~|0rcpA9rt*Hpd2kazzJc-T-g1CTC-@t(EB7-xR=x2eFKwl^kAQ8Q# zL`MdcKmp%?Ac^RxCAv@;+K>vO?-+S?=*g`EMFH6PSjGR`k?3e9x`3DqDg@&^vBlo1 z1^b9jA^gW3Cpx2vZX!H4GQ>9^95X9=lNITWjvhQ0ZdeQw(Lqi09pSk^7%mi@h%RiR zcZuOZ!*qUhaufYe3~gXii6>5KnmnUt$x5hTTy+KqH+_LlbUr~*py4bNec(j*luwOB zFFDbH#n=Xm_+OP9Nyja8=l~VyXcwJd1ZhGEeDL%_k?0>|>#~{URW8xo7bhTPMLv&1uShb!Dy@l1{!a}ikW4kM4&aP-m6JA`S--1?CR^*@pO&*5o>&Biy zw&+@!xX%5a^|&Fs1qp&JqS4d_O`V(94f8zdu@EJ}P#8^nyD{Y!8K3RO61D{L-DPYF z2!>g;C5AFg)Z82vI!mkk_JSoFS61Bk+Mq;)5HZjRZgIpGr? zw*w`#(sAb0^DAM3@+w#xVuUhK%$7-!M#vS-qR4{xR?zYyfn`3eV2 z=D$rABD!CiCj|C5USsVcL3hDoqC=*SU+1^4`q=%Bl2C&|`uebu+hoq72j;$J3ob_V zltl{~VUu7A+}P%Af&2?s_8W+zDRbH$6CyfbPEMP(Y4^qIYjpnM*@g2@bk!VR(|b|u zvdyamK>_rLYSD~+v%QpuyoNhu&Z4Jg`#Kv}6-+%jNjTVG1c}7ejdj}*=q$Q$rcK$} zOM7|IBM72CXd;@hB|BsxpEZX+U5o#D`gi!UM>}NvNnd8UGmyXF%UbS?aTc9H@5QH% z=sLLxUZCMRAgH{_kA1OA#_#rHdw0tCYCm>ZmbRu2*#n10Qv^yIlR1iigdXaV= znYH`!ri%3_F-}`lAiKOP##wYPZEb&TZi{`ExHpSN@@&r9?{4p*g#tKMKl+tEIareS z&ADTzsSmpI_)Efr+4|iw=dUn%v}M%sXJY&PUj54_C7eRo_1!YP=xMsT=zVVUCH33j zc?>JjjESzL@3Od!d5zj%L{oYaVUPce>yizGzIrnv^FYF^6+E{OIgZHr4V2D>2917k zuaY*R9Y7<)TDHJ0AVty=zdNv~&JqirdxE<57Rag>%8K{Moc&S58f<{o)B8Rh?aX=3 z89BIC!I_n9+#?J5j6y+B2=n%tZt3Je$=*=*9!>wD0`zK+lZkE}57K(~ywb9&Jv25x z(&EBc%wC!EKi@~8R_Wf)szpye$o@>PMr%N=AJO!mD||*R+;_`t0s4_ z!4aC=WlgGDx2|DtIQ+l2SY7$`BUZ2+F;_&|p2WX?C4pv~e3UDjU)7MG;mURz$i=FLo%;A1Y<;Qch~?^auJUy7wB<%Q_N@KE zF6e4Lcv@G7!UuGZ9Ma-k!d-)6kzV+uieS~UFihzh!2H+Bn0kK~kJj0_X@yyu0`gb) zOG_?D)(tMy=4-OD3RAUd8inw=mJL2wpH=Ob)-FiaW)0Qmq$vb1bryf1zV~0V{1kqF z%}@l=W!ASKV>laiz^>xT{zdhe_Hli^sdzLpWfeFYLg+k0Bf6<$!?+d~yY|QJ8_Us< z6qc7EDcn^R`Kf8RmMhWE^E*wMsV7=rNL!PJ5nI^IOk zC|c8RAL#sGVPmR5sfE{lswP8|qaiPQ1^KfN&CO5C$xYK_!^oO^U2+b7vSGMJky((P zrPJi&r!cZKI-NrJR6v#w|9d8M_ypzoij>0ibc|zg z7Q1%R%@Gu3+MEnhQ%oxY*)J#D zLaWQm&rKzR(O-bHse=^B`5E{T3esRenkE%?&Szho>&6z`$I^aW!$r&_1^!YCHpD+R zu=Dlq=A2kbE5sJn})`Y1JJp@l!= zesybLYIW2xWfkEhf6!l64yycxr&*?x3&u**X!3-8j9=VH&J(5%#=(xy&leea-tT-GW7hHsx$6; zy8lfSs{AcS5llk}GwB_5OgIH?{6Inb+-yyjCL1?*+FbZZl9`;7#=0D6=qO(1(Nat* zf_#Hn^0h`yjK6<^BJo3vLO92b5)~M)HaSZ>oW&hzATtWZW;E2N9>6GAz#$KJ6q@vJ zs9=&oYm$0&&CUKORFq?qfr-z3=zQ%C)ZX%t~IHT-mhpzsr; z5MD*l?sjoByxc&sApz?MZ#Jl`>AxIFsP->;SdDl~LiOzoZ&yeU;WuFvIIx45|M5n2 zgfuBI9CIi&rqC7X@##z)IJi)gUnHy}oJd{<7WEAYOir+Gb78)XC#Oz3e0Y{dtZ#2% uQQwddPJ(db33l9u9%%|XcJWA|*GCmY&(7v5`k!0KRRmthb7JvDbN>g?EaQ{_ delta 11302 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 diff --git a/components/general/Logo.tsx b/components/general/Logo.tsx new file mode 100644 index 0000000..f2ef82a --- /dev/null +++ b/components/general/Logo.tsx @@ -0,0 +1,22 @@ +type Props = { + size?: number; +}; +export default function Logo({ size }: Props) { + const sizeRatio = 50 / 100; + const width = size || 50; + const height = width * sizeRatio; + + return ( + + Main Logo + + ); +} diff --git a/components/lib/editors/AceEditor.tsx b/components/lib/editors/AceEditor.tsx new file mode 100755 index 0000000..f64cde0 --- /dev/null +++ b/components/lib/editors/AceEditor.tsx @@ -0,0 +1,136 @@ +import { AceEditorAcceptedModes } from "@/components/general/data/partials/EditDataListButton"; +import React, { MutableRefObject } from "react"; +import { twMerge } from "tailwind-merge"; + +export type AceEditorComponentType = { + editorRef?: MutableRefObject; + readOnly?: boolean; + /** Function to call when Ctrl+Enter is pressed */ + ctrlEnterFn?: (editor: AceAjax.Editor) => void; + content?: string; + placeholder?: string; + mode?: AceEditorAcceptedModes; + fontSize?: string; + previewMode?: boolean; + onChange?: (value: string) => void; + delay?: number; +}; + +let timeout: any; + +/** + * # Powerful Ace Editor + * @note **NOTE** head scripts required + * @script `https://cdnjs.cloudflare.com/ajax/libs/ace/1.22.0/ace.min.js` + * @script `https://cdnjs.cloudflare.com/ajax/libs/ace/1.22.0/ext-language_tools.min.js` + */ +export default function AceEditor({ + editorRef, + readOnly, + ctrlEnterFn, + content = "", + placeholder, + mode, + fontSize, + previewMode, + onChange, + delay = 500, +}: AceEditorComponentType) { + try { + const editorElementRef = React.useRef(); + const editorRefInstance = React.useRef(); + + const [refresh, setRefresh] = React.useState(0); + const [darkMode, setDarkMode] = React.useState(false); + const [ready, setReady] = React.useState(false); + + React.useEffect(() => { + if (!ready) return; + + if (!ace?.edit || !editorElementRef.current) { + setTimeout(() => { + setRefresh((prev) => prev + 1); + }, 1000); + return; + } + + const editor = ace.edit(editorElementRef.current); + + editor.setOptions({ + mode: `ace/mode/${mode ? mode : "javascript"}`, + theme: darkMode + ? "ace/theme/tomorrow_night_bright" + : "ace/theme/ace_light", + value: content, + placeholder: placeholder ? placeholder : "", + enableBasicAutocompletion: true, + enableLiveAutocompletion: true, + readOnly: readOnly ? true : false, + fontSize: fontSize ? fontSize : null, + showLineNumbers: previewMode ? false : true, + wrap: true, + wrapMethod: "code", + // onchange: (e) => { + // console.log(e); + // }, + }); + + editor.commands.addCommand({ + name: "myCommand", + bindKey: { win: "Ctrl-Enter", mac: "Command-Enter" }, + exec: function (editor) { + if (ctrlEnterFn) ctrlEnterFn(editor); + }, + readOnly: true, + }); + + editor.getSession().on("change", function (e) { + if (onChange) { + clearTimeout(timeout); + + setTimeout(() => { + onChange(editor.getValue()); + console.log(editor.getValue()); + }, delay); + } + }); + + editorRefInstance.current = editor; + if (editorRef) editorRef.current = editor; + }, [refresh, darkMode, ready]); + + React.useEffect(() => { + const htmlClassName = document.documentElement.className; + if (htmlClassName.match(/dark/i)) setDarkMode(true); + setTimeout(() => { + setReady(true); + }, 200); + }, []); + + return ( + +
+
+
+
+ ); + } catch (error: any) { + return ( + + + Editor Error:{" "} + {error.message} + + + ); + } +} diff --git a/components/lib/elements/Border.tsx b/components/lib/elements/Border.tsx new file mode 100644 index 0000000..0e98ff4 --- /dev/null +++ b/components/lib/elements/Border.tsx @@ -0,0 +1,36 @@ +import { DetailedHTMLProps, HTMLAttributes } from "react"; +import { twMerge } from "tailwind-merge"; + +export type TWUI_BORDER_PROPS = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + spacing?: "normal" | "loose" | "tight" | "wide" | "tightest"; +}; + +/** + * # Toggle Component + * @className_wrapper twui-border + */ +export default function Border({ spacing, ...props }: TWUI_BORDER_PROPS) { + return ( +
+ {props.children} +
+ ); +} diff --git a/components/lib/elements/Breadcrumbs.tsx b/components/lib/elements/Breadcrumbs.tsx new file mode 100755 index 0000000..4af8e57 --- /dev/null +++ b/components/lib/elements/Breadcrumbs.tsx @@ -0,0 +1,94 @@ +import React from "react"; +import Link from "../layout/Link"; +import Divider from "../layout/Divider"; +import Row from "../layout/Row"; +import lowerToTitleCase from "@/server-client-shared/utils/lower-to-title-case"; + +type LinkObject = { + title: string; + path: string; +}; +export default function Breadcrumbs() { + const [links, setLinks] = React.useState(null); + + React.useEffect(() => { + let pathname = window.location.pathname; + let pathLinks = pathname.split("/"); + + let validPathLinks = []; + + validPathLinks.push({ + title: "Home", + path: pathname.match(/admin/) ? "/admin" : "/", + }); + + pathLinks.forEach((linkText, index, array) => { + if (!linkText?.match(/./) || index == 1) { + return; + } + + validPathLinks.push({ + title: lowerToTitleCase(linkText), + path: (() => { + let path = ""; + + for (let i = 0; i < array.length; i++) { + const lnText = array[i]; + if (i > index || !lnText.match(/./)) continue; + + path += `/${lnText}`; + } + + return path; + })(), + }); + }); + + setLinks(validPathLinks); + + return function () { + setLinks(null); + }; + }, []); + + if (!links?.[1]) { + return ; + } + + return ( + + {links.map((linkObject, index, array) => { + if (index === links.length - 1) { + return ( + + {linkObject.title} + + ); + } else { + return ( + + + {linkObject.title} + + + + ); + } + })} + + ); + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// +} + +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ diff --git a/components/lib/elements/Card.tsx b/components/lib/elements/Card.tsx new file mode 100644 index 0000000..7e4996e --- /dev/null +++ b/components/lib/elements/Card.tsx @@ -0,0 +1,45 @@ +import React, { DetailedHTMLProps, HTMLAttributes } from "react"; +import { twMerge } from "tailwind-merge"; + +/** + * # General Card + * @className_wrapper twui-card + */ +export default function Card({ + href, + variant, + linkProps, + ...props +}: DetailedHTMLProps, HTMLDivElement> & { + variant?: "normal"; + href?: string; + linkProps?: DetailedHTMLProps< + React.AnchorHTMLAttributes, + HTMLAnchorElement + >; +}) { + const component = ( +
+ {props.children} +
+ ); + + if (href) { + return ( + + {component} + + ); + } + + return component; +} diff --git a/components/lib/elements/ColorSchemeSelector.tsx b/components/lib/elements/ColorSchemeSelector.tsx new file mode 100644 index 0000000..40c6f66 --- /dev/null +++ b/components/lib/elements/ColorSchemeSelector.tsx @@ -0,0 +1,39 @@ +import React, { DetailedHTMLProps, HTMLAttributes } from "react"; +import { twMerge } from "tailwind-merge"; +import Toggle, { TWUI_TOGGLE_PROPS } from "./Toggle"; + +/** + * # Color Scheme Loader + * @className_wrapper twui-color-scheme-selector + */ +export default function ColorSchemeSelector({ + active, + setActive, + toggleProps, + ...props +}: DetailedHTMLProps, HTMLDivElement> & { + toggleProps?: TWUI_TOGGLE_PROPS; + active: boolean; + setActive: React.Dispatch>; +}) { + React.useEffect(() => { + if (active) { + document.documentElement.className = "dark"; + } else { + document.documentElement.className = ""; + } + }, [active]); + + return ( +
+ +
+ ); +} diff --git a/components/lib/elements/Loading.tsx b/components/lib/elements/Loading.tsx new file mode 100644 index 0000000..602be92 --- /dev/null +++ b/components/lib/elements/Loading.tsx @@ -0,0 +1,58 @@ +import { DetailedHTMLProps, HTMLAttributes } from "react"; +import { twMerge } from "tailwind-merge"; + +type Props = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + size?: "small" | "normal" | "medium" | "large" | "smaller"; + svgClassName?: string; +}; + +/** + * # Loading Component + * @className_wrapper twui-loading + */ +export default function Loading({ size, svgClassName, ...props }: Props) { + const sizeClassName = (() => { + switch (size) { + case "smaller": + return "w-4 h-4"; + case "small": + return "w-5 h-5"; + case "normal": + return "w-6 h-6"; + case "large": + return "w-7 h-7"; + + default: + return "w-6 h-6"; + } + })(); + + return ( +
+ +
+ ); +} diff --git a/components/lib/elements/LoadingBlock.tsx b/components/lib/elements/LoadingBlock.tsx new file mode 100644 index 0000000..9698a30 --- /dev/null +++ b/components/lib/elements/LoadingBlock.tsx @@ -0,0 +1,24 @@ +import React, { DetailedHTMLProps, HTMLAttributes } from "react"; +import { twMerge } from "tailwind-merge"; + +/** + * # General paper + * @className_wrapper twui-loading-block + */ +export default function LoadingBlock({ + ...props +}: DetailedHTMLProps, HTMLDivElement>) { + return ( +
+ {props.children} +
+ ); +} diff --git a/components/lib/elements/Modal.tsx b/components/lib/elements/Modal.tsx new file mode 100644 index 0000000..f48277d --- /dev/null +++ b/components/lib/elements/Modal.tsx @@ -0,0 +1,71 @@ +import React, { DetailedHTMLProps, HTMLAttributes } from "react"; +import { twMerge } from "tailwind-merge"; +import { createRoot } from "react-dom/client"; +import Paper from "./Paper"; + +type Props = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + target: React.ReactNode; +}; + +/** + * # Modal Component + * @className_wrapper twui-modal-root + * @className_wrapper twui-modal + */ +export default function Modal({ target, ...props }: Props) { + const [wrapper, setWrapper] = React.useState(null); + + React.useEffect(() => { + const wrapperEl = document.createElement("div"); + wrapperEl.className = twMerge( + "fixed z-[200000] top-0 left-0 w-screen h-screen", + "flex flex-col items-center justify-center", + "twui-modal-root" + ); + + setWrapper(wrapperEl); + }, []); + + const modalEl = ( + +
{ + closeModal({ wrapperEl: wrapper }); + }} + >
+ + {props.children} + +
+ ); + + const targetEl = ( +
{ + if (!wrapper) return; + document.body.appendChild(wrapper); + const root = createRoot(wrapper); + root.render(modalEl); + }} + > + {target} +
+ ); + + return targetEl; +} + +function closeModal({ wrapperEl }: { wrapperEl: HTMLDivElement | null }) { + if (!wrapperEl) return; + wrapperEl.parentElement?.removeChild(wrapperEl); +} diff --git a/components/lib/elements/Paper.tsx b/components/lib/elements/Paper.tsx new file mode 100644 index 0000000..3346ba0 --- /dev/null +++ b/components/lib/elements/Paper.tsx @@ -0,0 +1,32 @@ +import React, { DetailedHTMLProps, HTMLAttributes } from "react"; +import { twMerge } from "tailwind-merge"; + +/** + * # General paper + * @className_wrapper twui-paper + */ +export default function Paper({ + variant, + linkProps, + ...props +}: DetailedHTMLProps, HTMLDivElement> & { + variant?: "normal"; + linkProps?: DetailedHTMLProps< + React.AnchorHTMLAttributes, + HTMLAnchorElement + >; +}) { + return ( +
+ {props.children} +
+ ); +} diff --git a/components/lib/elements/Search.tsx b/components/lib/elements/Search.tsx new file mode 100644 index 0000000..587681a --- /dev/null +++ b/components/lib/elements/Search.tsx @@ -0,0 +1,110 @@ +import { twMerge } from "tailwind-merge"; +import Input from "../form/Input"; +import Button from "../layout/Button"; +import Row from "../layout/Row"; +import { Search as SearchIcon } from "lucide-react"; +import React, { + DetailedHTMLProps, + InputHTMLAttributes, + TextareaHTMLAttributes, +} from "react"; + +let timeout: any; + +export type SearchProps = DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement +> & { + dispatch?: (value?: string) => void; + delay?: number; + inputProps?: DetailedHTMLProps< + InputHTMLAttributes, + HTMLInputElement + > & + DetailedHTMLProps< + TextareaHTMLAttributes, + HTMLTextAreaElement + >; + buttonProps?: DetailedHTMLProps< + React.ButtonHTMLAttributes, + HTMLButtonElement + >; +}; + +/** + * # Search Component + * @className_wrapper twui-search-wrapper + * @className_circle twui-search-input + * @className_circle twui-search-button + */ +export default function Search({ + dispatch, + delay = 500, + inputProps, + buttonProps, + ...props +}: SearchProps) { + const [input, setInput] = React.useState(""); + + React.useEffect(() => { + clearTimeout(timeout); + + timeout = setTimeout(() => { + dispatch?.(input); + }, delay); + }, [input]); + + const inputRef = React.useRef(); + + React.useEffect(() => { + if (props.autoFocus) { + inputRef.current?.focus(); + } + }, []); + + return ( + + setInput(e.target.value)} + className={twMerge( + "rounded-r-none", + "twui-search-input", + inputProps?.className + )} + wrapperProps={{ + className: "rounded-r-none", + }} + componentRef={inputRef} + /> + + + ); +} diff --git a/components/lib/elements/Toggle.tsx b/components/lib/elements/Toggle.tsx new file mode 100644 index 0000000..ad276c4 --- /dev/null +++ b/components/lib/elements/Toggle.tsx @@ -0,0 +1,52 @@ +import { DetailedHTMLProps, HTMLAttributes } from "react"; +import { twMerge } from "tailwind-merge"; + +export type TWUI_TOGGLE_PROPS = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + active?: boolean; + setActive?: React.Dispatch>; + circleProps?: DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement + >; +}; + +/** + * # Toggle Component + * @className_wrapper twui-toggle-wrapper + * @className_circle twui-toggle-circle + */ +export default function Toggle({ + circleProps, + active, + setActive, + ...props +}: TWUI_TOGGLE_PROPS) { + return ( +
setActive?.(!active)} + > +
+
+ ); +} diff --git a/components/lib/form/Form.tsx b/components/lib/form/Form.tsx new file mode 100644 index 0000000..b0d92eb --- /dev/null +++ b/components/lib/form/Form.tsx @@ -0,0 +1,23 @@ +import { DetailedHTMLProps, FormHTMLAttributes } from "react"; +import { twMerge } from "tailwind-merge"; + +/** + * # Form Element + * @className twui-form + */ +export default function Form({ + ...props +}: DetailedHTMLProps, HTMLFormElement>) { + return ( +
+ {props.children} +
+ ); +} diff --git a/components/lib/form/ImageUpload.tsx b/components/lib/form/ImageUpload.tsx new file mode 100644 index 0000000..9869aad --- /dev/null +++ b/components/lib/form/ImageUpload.tsx @@ -0,0 +1,109 @@ +import Button from "@/components/lib/layout/Button"; +import Stack from "@/components/lib/layout/Stack"; +import { ImagePlus, X } from "lucide-react"; +import React, { DetailedHTMLProps } from "react"; +import Card from "@/components/lib/elements/Card"; +import Span from "@/components/lib/layout/Span"; +import Center from "@/components/lib/layout/Center"; +import imageInputToBase64, { + ImageInputToBase64FunctionReturn, +} from "../utils/form/imageInputToBase64"; +import { twMerge } from "tailwind-merge"; + +type ImageUploadProps = { + onChange?: (imgData: ImageInputToBase64FunctionReturn | undefined) => any; + fileInputProps?: DetailedHTMLProps< + React.InputHTMLAttributes, + HTMLInputElement + >; + wrapperProps?: DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + >; + placeHolderWrapper?: DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + >; + previewImageWrapperProps?: DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + >; + previewImageProps?: DetailedHTMLProps< + React.ImgHTMLAttributes, + HTMLImageElement + >; +}; + +export default function ImageUpload({ + onChange, + fileInputProps, + wrapperProps, + placeHolderWrapper, + previewImageWrapperProps, + previewImageProps, +}: ImageUploadProps) { + const [src, setSrc] = React.useState(undefined); + const inputRef = React.useRef(); + + return ( + + { + imageInputToBase64({ imageInput: e.target }).then((res) => { + setSrc(res.imageBase64Full); + onChange?.(res); + fileInputProps?.onChange?.(e); + }); + }} + ref={inputRef as any} + /> + + {src ? ( + + + + + ) : ( + { + inputRef.current?.click(); + placeHolderWrapper?.onClick?.(e); + }} + {...placeHolderWrapper} + > +
+ + + + Click to Upload Image + + +
+
+ )} +
+ ); +} diff --git a/components/lib/form/Input.tsx b/components/lib/form/Input.tsx new file mode 100644 index 0000000..e02050f --- /dev/null +++ b/components/lib/form/Input.tsx @@ -0,0 +1,136 @@ +import React, { + DetailedHTMLProps, + InputHTMLAttributes, + LabelHTMLAttributes, + RefObject, + TextareaHTMLAttributes, +} from "react"; +import { twMerge } from "tailwind-merge"; + +export type InputProps = DetailedHTMLProps< + InputHTMLAttributes, + HTMLInputElement +> & + DetailedHTMLProps< + TextareaHTMLAttributes, + HTMLTextAreaElement + > & { + label?: string; + variant?: "normal" | "warning" | "error" | "inactive"; + prefix?: string | React.ReactNode; + suffix?: string | React.ReactNode; + showLabel?: boolean; + istextarea?: boolean; + wrapperProps?: DetailedHTMLProps< + InputHTMLAttributes, + HTMLDivElement + >; + labelProps?: DetailedHTMLProps< + LabelHTMLAttributes, + HTMLLabelElement + >; + componentRef?: RefObject; + }; + +/** + * # Input Element + * @className twui-input + */ +export default function Input({ + label, + variant, + prefix, + suffix, + componentRef, + labelProps, + wrapperProps, + showLabel, + istextarea, + ...props +}: InputProps) { + const [focus, setFocus] = React.useState(false); + + const targetComponent = istextarea ? ( +