n)return!1}return!0}function s(i){let t=i.closest("[data-tui-timepicker-popup]");if(!t)return null;let u=t.closest("[id]")?.id;return u?document.getElementById(u.replace("-content","")):null}function p(i){let t=i.id+"-content",u=document.getElementById(t)?.querySelector("[data-tui-timepicker-popup]");return u?{popup:u,hourList:u.querySelector("[data-tui-timepicker-hour-list]"),minuteList:u.querySelector("[data-tui-timepicker-minute-list]"),hiddenInput:document.getElementById(i.id+"-hidden")||i.parentElement?.querySelector("[data-tui-timepicker-hidden-input]")}:null}function d(i){return{hour:i.dataset.tuiTimepickerCurrentHour?parseInt(i.dataset.tuiTimepickerCurrentHour):null,minute:i.dataset.tuiTimepickerCurrentMinute?parseInt(i.dataset.tuiTimepickerCurrentMinute):null,use12Hours:i.getAttribute("data-tui-timepicker-use12hours")==="true",step:parseInt(i.getAttribute("data-tui-timepicker-step")||"1"),minTime:m(i.getAttribute("data-tui-timepicker-min-time")),maxTime:m(i.getAttribute("data-tui-timepicker-max-time")),placeholder:i.getAttribute("data-tui-timepicker-placeholder")||"Select time"}}function l(i,t,u){t!==null?i.dataset.tuiTimepickerCurrentHour=t:delete i.dataset.tuiTimepickerCurrentHour,u!==null?i.dataset.tuiTimepickerCurrentMinute=u:delete i.dataset.tuiTimepickerCurrentMinute,k(i)}function k(i){let t=d(i),u=p(i),e=i.querySelector("[data-tui-timepicker-display]");if(e){let r=h(t.hour,t.minute,t.use12Hours);e.textContent=r||t.placeholder,e.classList.toggle("text-muted-foreground",!r)}u?.hiddenInput&&(u.hiddenInput.value=t.hour!==null&&t.minute!==null?h(t.hour,t.minute,!1):""),u?.hourList&&u?.minuteList&&A(u,t)}function A(i,t){i.hourList.querySelectorAll("[data-tui-timepicker-hour]").forEach(r=>{let n=parseInt(r.getAttribute("data-tui-timepicker-hour")),o=!1;t.hour!==null&&(t.use12Hours?o=n===t.hour||n===0&&t.hour===12||n===t.hour-12&&t.hour>12:o=n===t.hour),r.setAttribute("data-tui-timepicker-selected",o);let a=!1;for(let f=0;f<60;f++)if(c(n,f,t.minTime,t.maxTime)){a=!0;break}r.disabled=!a,r.classList.toggle("opacity-50",!a),r.classList.toggle("cursor-not-allowed",!a)}),i.minuteList.querySelectorAll("[data-tui-timepicker-minute]").forEach(r=>{let n=parseInt(r.getAttribute("data-tui-timepicker-minute")),o=n===t.minute,a=t.hour===null||c(t.hour,n,t.minTime,t.maxTime);r.setAttribute("data-tui-timepicker-selected",o),r.disabled=!a,r.classList.toggle("opacity-50",!a),r.classList.toggle("cursor-not-allowed",!a)});let u=i.popup.querySelector('[data-tui-timepicker-period="AM"]'),e=i.popup.querySelector('[data-tui-timepicker-period="PM"]');if(u&&e){let r=t.hour===null||t.hour<12;u.setAttribute("data-tui-timepicker-active",r),e.setAttribute("data-tui-timepicker-active",!r)}}document.addEventListener("click",i=>{let t=i.target;if(t.matches("[data-tui-timepicker-hour]")&&!t.disabled){let u=s(t);if(!u)return;let e=d(u),r=parseInt(t.getAttribute("data-tui-timepicker-hour"));if(e.use12Hours){let n=e.hour!==null&&e.hour>=12;r=r===0?n?12:0:n?r+12:r}if(c(r,e.minute,e.minTime,e.maxTime))l(u,r,e.minute);else for(let n=0;n<60;n+=e.step)if(c(r,n,e.minTime,e.maxTime)){l(u,r,n);return}return}if(t.matches("[data-tui-timepicker-minute]")&&!t.disabled){let u=s(t);if(!u)return;let e=d(u),r=parseInt(t.getAttribute("data-tui-timepicker-minute"));(e.hour===null||c(e.hour,r,e.minTime,e.maxTime))&&l(u,e.hour,r);return}if(t.matches("[data-tui-timepicker-period]")){let u=s(t);if(!u)return;let e=d(u);if(e.hour===null)return;let r=t.getAttribute("data-tui-timepicker-period"),n=e.hour;if(r==="AM"&&e.hour>=12?n=e.hour===12?0:e.hour-12:r==="PM"&&e.hour<12&&(n=e.hour===0?12:e.hour+12),n!==e.hour){if(c(n,e.minute,e.minTime,e.maxTime))l(u,n,e.minute);else for(let o=0;o<60;o+=e.step)if(c(n,o,e.minTime,e.maxTime)){l(u,n,o);return}}return}if(t.matches("[data-tui-timepicker-done]")){let u=s(t);u&&window.closePopover&&window.closePopover(u.id+"-content");return}}),document.addEventListener("reset",i=>{i.target.matches("form")&&i.target.querySelectorAll('[data-tui-timepicker="true"]').forEach(t=>{l(t,null,null);let u=p(t);u?.hiddenInput&&(u.hiddenInput.value="")})}),new MutationObserver(()=>{document.querySelectorAll('[data-tui-timepicker="true"]:not([data-rendered])').forEach(i=>{i.setAttribute("data-rendered","true");let t=p(i),u=t?.hiddenInput?.value||t?.popup?.getAttribute("data-tui-timepicker-value");if(u){let e=m(u);e&&l(i,e.hour,e.minute)}k(i)})}).observe(document.body,{childList:!0,subtree:!0})})();})();
diff --git a/assets/js/toast.min.js b/assets/js/toast.min.js
new file mode 100644
index 0000000..1fb13d3
--- /dev/null
+++ b/assets/js/toast.min.js
@@ -0,0 +1 @@
+(()=>{(function(){"use strict";let n=new Map;function o(e){let i=parseInt(e.dataset.tuiToastDuration||"3000"),t=e.querySelector(".toast-progress"),a={timer:null,startTime:Date.now(),remaining:i,paused:!1};n.set(e,a),t&&i>0&&(t.style.transitionDuration=i+"ms",requestAnimationFrame(()=>{requestAnimationFrame(()=>{t.style.transform="scaleX(0)"})})),i>0&&(a.timer=setTimeout(()=>r(e),i)),e.addEventListener("mouseenter",()=>{let s=n.get(e);if(!(!s||s.paused)&&(clearTimeout(s.timer),s.remaining=s.remaining-(Date.now()-s.startTime),s.paused=!0,t)){let m=getComputedStyle(t);t.style.transitionDuration="0ms",t.style.transform=m.transform}}),e.addEventListener("mouseleave",()=>{let s=n.get(e);!s||!s.paused||s.remaining<=0||(s.startTime=Date.now(),s.paused=!1,s.timer=setTimeout(()=>r(e),s.remaining),t&&(t.style.transitionDuration=s.remaining+"ms",requestAnimationFrame(()=>{requestAnimationFrame(()=>{t.style.transform="scaleX(0)"})})))})}function r(e){n.delete(e),e.style.transition="opacity 300ms, transform 300ms",e.style.opacity="0",e.style.transform="translateY(1rem)",setTimeout(()=>e.remove(),300)}document.addEventListener("click",e=>{let i=e.target.closest("[data-tui-toast-dismiss]");if(i){let t=i.closest("[data-tui-toast]");t&&r(t)}}),new MutationObserver(e=>{e.forEach(i=>{i.addedNodes.forEach(t=>{t.nodeType===1&&t.matches?.("[data-tui-toast]")&&o(t)})})}).observe(document.body,{childList:!0,subtree:!0})})();})();
diff --git a/cmd/server/main.go b/cmd/server/main.go
new file mode 100644
index 0000000..7a15c4c
--- /dev/null
+++ b/cmd/server/main.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+ "fmt"
+ "log/slog"
+ "net/http"
+
+ "git.juancwu.dev/juancwu/budgething/internal/app"
+ "git.juancwu.dev/juancwu/budgething/internal/config"
+ "git.juancwu.dev/juancwu/budgething/internal/routes"
+)
+
+func main() {
+ cfg := config.Load()
+
+ a, err := app.New(cfg)
+ if err != nil {
+ slog.Error("failed to initialize app", "error", err)
+ panic(err)
+ }
+ defer func() {
+ err := a.Close()
+ if err != nil {
+ slog.Error("failed to close app", "error", err)
+ }
+ }()
+
+ handler := routes.SetupRoutes(a)
+ slog.Info("server starting", "host", cfg.Host, "port", cfg.Port, "env", cfg.AppEnv, "url", fmt.Sprintf("http://%s:%s", cfg.Host, cfg.Port))
+
+ err = http.ListenAndServe(":"+cfg.Port, handler)
+ if err != nil {
+ slog.Error("server failed", "error", err)
+ panic(err)
+ }
+}
diff --git a/go.mod b/go.mod
index 0cbd419..c570434 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,33 @@
module git.juancwu.dev/juancwu/budgething
go 1.25.1
+
+require (
+ github.com/Oudwins/tailwind-merge-go v0.2.1
+ github.com/a-h/templ v0.3.960
+ github.com/jackc/pgx/v5 v5.7.6
+ github.com/jmoiron/sqlx v1.4.0
+ github.com/joho/godotenv v1.5.1
+ modernc.org/sqlite v1.40.1
+)
+
+require (
+ github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/go-sql-driver/mysql v1.9.3 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
+ github.com/jackc/puddle/v2 v2.2.2 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/ncruces/go-strftime v0.1.9 // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+ github.com/stretchr/testify v1.11.0 // indirect
+ golang.org/x/crypto v0.40.0 // indirect
+ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
+ golang.org/x/sync v0.16.0 // indirect
+ golang.org/x/sys v0.36.0 // indirect
+ golang.org/x/text v0.27.0 // indirect
+ modernc.org/libc v1.66.10 // indirect
+ modernc.org/mathutil v1.7.1 // indirect
+ modernc.org/memory v1.11.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..fb0d200
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,94 @@
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+github.com/Oudwins/tailwind-merge-go v0.2.1 h1:jxRaEqGtwwwF48UuFIQ8g8XT7YSualNuGzCvQ89nPFE=
+github.com/Oudwins/tailwind-merge-go v0.2.1/go.mod h1:kkZodgOPvZQ8f7SIrlWkG/w1g9JTbtnptnePIh3V72U=
+github.com/a-h/templ v0.3.960 h1:trshEpGa8clF5cdI39iY4ZrZG8Z/QixyzEyUnA7feTM=
+github.com/a-h/templ v0.3.960/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
+github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
+github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
+github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
+github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
+github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
+github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
+github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
+github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
+golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
+golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
+golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
+golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
+golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
+golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
+golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
+golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
+golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
+golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
+modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
+modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
+modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
+modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
+modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
+modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
+modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
+modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
+modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
+modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
+modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
+modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
+modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
+modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
+modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
+modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
+modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
+modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
+modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
+modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY=
+modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
+modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
+modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
+modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
diff --git a/internal/app/app.go b/internal/app/app.go
new file mode 100644
index 0000000..7f6726f
--- /dev/null
+++ b/internal/app/app.go
@@ -0,0 +1,33 @@
+package app
+
+import (
+ "fmt"
+
+ "git.juancwu.dev/juancwu/budgething/internal/config"
+ "git.juancwu.dev/juancwu/budgething/internal/db"
+ "github.com/jmoiron/sqlx"
+)
+
+type App struct {
+ Cfg *config.Config
+ DB *sqlx.DB
+}
+
+func New(cfg *config.Config) (*App, error) {
+ database, err := db.Init(cfg.DBDriver, cfg.DBConnection)
+ if err != nil {
+ return nil, fmt.Errorf("failed to initialize database: %w", err)
+ }
+
+ return &App{
+ Cfg: cfg,
+ DB: database,
+ }, nil
+}
+
+func (a *App) Close() error {
+ if a.DB != nil {
+ return a.DB.Close()
+ }
+ return nil
+}
diff --git a/internal/config/config.go b/internal/config/config.go
new file mode 100644
index 0000000..82d7e56
--- /dev/null
+++ b/internal/config/config.go
@@ -0,0 +1,76 @@
+package config
+
+import (
+ "log/slog"
+ "os"
+ "time"
+
+ "github.com/joho/godotenv"
+)
+
+type Config struct {
+ AppName string
+ AppEnv string
+ AppURL string
+ Host string
+ Port string
+
+ DBDriver string
+ DBConnection string
+
+ JWTSecret string
+ JWTExpiry time.Duration
+}
+
+func Load() *Config {
+
+ if err := godotenv.Load(); err != nil {
+ slog.Info("no .env file found, using environment variables")
+ }
+
+ cfg := &Config{
+ AppName: envString("APP_NAME", "Budgething"),
+ AppEnv: envRequired("APP_ENV"),
+ AppURL: envRequired("APP_URL"),
+ Host: envString("HOST", "127.0.0.1"),
+ Port: envString("PORT", "9000"),
+
+ DBDriver: envString("DB_DRIVER", "sqlite"),
+ DBConnection: envString("DB_CONNECTION", "./data/local.db?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)"),
+
+ JWTSecret: envRequired("JWT_SECRET"),
+ JWTExpiry: envDuration("JWT_EXPIRY", 168*time.Hour), // 7 days default
+ }
+
+ return cfg
+}
+
+func envString(key, def string) string {
+ value := os.Getenv(key)
+ if value == "" {
+ value = def
+ }
+ return value
+}
+
+func envDuration(key string, def time.Duration) time.Duration {
+ value, ok := os.LookupEnv(key)
+ if !ok || value == "" {
+ return def
+ }
+ duration, err := time.ParseDuration(value)
+ if err != nil {
+ slog.Warn("config invalid duration, using default", "key", key, "value", value, "default", def)
+ return def
+ }
+ return duration
+}
+
+func envRequired(key string) string {
+ if value := os.Getenv(key); value != "" {
+ return value
+ }
+ slog.Error("config required env var missing", "key", key)
+ os.Exit(1)
+ return ""
+}
diff --git a/internal/db/db.go b/internal/db/db.go
new file mode 100644
index 0000000..d79e732
--- /dev/null
+++ b/internal/db/db.go
@@ -0,0 +1,48 @@
+package db
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "path/filepath"
+ "time"
+
+ _ "github.com/jackc/pgx/v5/stdlib"
+ "github.com/jmoiron/sqlx"
+ _ "modernc.org/sqlite"
+)
+
+func Init(driver, connection string) (*sqlx.DB, error) {
+ if driver == "sqlite" {
+ dir := filepath.Dir(connection)
+ err := os.MkdirAll(dir, 0755)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create data directory: %w", err)
+ }
+ }
+
+ db, err := sqlx.Connect(driver, connection)
+ if err != nil {
+ return nil, fmt.Errorf("failed to connect: %w", err)
+ }
+
+ db.SetMaxOpenConns(25)
+ db.SetMaxIdleConns(5)
+ db.SetConnMaxLifetime(5 * time.Minute)
+
+ slog.Info("database connected", "driver", driver)
+
+ err = db.Ping()
+ if err != nil {
+ return nil, fmt.Errorf("failed to ping database: %w", err)
+ }
+
+ return db, nil
+}
+
+func Close(db *sqlx.DB) error {
+ if db != nil {
+ return db.Close()
+ }
+ return nil
+}
diff --git a/internal/routes/routes.go b/internal/routes/routes.go
new file mode 100644
index 0000000..035df7e
--- /dev/null
+++ b/internal/routes/routes.go
@@ -0,0 +1,19 @@
+package routes
+
+import (
+ "io/fs"
+ "net/http"
+
+ "git.juancwu.dev/juancwu/budgething/assets"
+ "git.juancwu.dev/juancwu/budgething/internal/app"
+)
+
+func SetupRoutes(a *app.App) http.Handler {
+ mux := http.NewServeMux()
+
+ // Static
+ sub, _ := fs.Sub(assets.AssetsFS, ".")
+ mux.Handle("GET /assets/", http.StripPrefix("/assets/", http.FileServer(http.FS(sub))))
+
+ return mux
+}
diff --git a/internal/ui/components/accordion/accordion.templ b/internal/ui/components/accordion/accordion.templ
new file mode 100644
index 0000000..276907e
--- /dev/null
+++ b/internal/ui/components/accordion/accordion.templ
@@ -0,0 +1,126 @@
+// templui component accordion - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/accordion
+package accordion
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ItemProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type TriggerProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+templ Accordion(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Item(props ...ItemProps) {
+ {{ var p ItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ summary>svg]:rotate-180",
+ p.Class,
+ ),
+ }
+ { p.Attributes... }
+ >
+ { children... }
+
+}
+
+templ Trigger(props ...TriggerProps) {
+ {{ var p TriggerProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+ @icon.ChevronDown(icon.Props{
+ Size: 16,
+ Class: "size-4 shrink-0 translate-y-0.5 transition-transform duration-200 text-muted-foreground pointer-events-none",
+ })
+
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
diff --git a/internal/ui/components/alert/alert.templ b/internal/ui/components/alert/alert.templ
new file mode 100644
index 0000000..34b5926
--- /dev/null
+++ b/internal/ui/components/alert/alert.templ
@@ -0,0 +1,110 @@
+// templui component alert - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/alert
+package alert
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Variant string
+
+const (
+ VariantDefault Variant = "default"
+ VariantDestructive Variant = "destructive"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Variant Variant
+}
+
+type TitleProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type DescriptionProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+templ Alert(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ svg]:grid-cols-[1rem_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start",
+ "[&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
+ variantClasses(p.Variant),
+ p.Class,
+ ),
+ }
+ role="alert"
+ { p.Attributes... }
+ >
+ { children... }
+
+}
+
+templ Title(props ...TitleProps) {
+ {{ var p TitleProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Description(props ...DescriptionProps) {
+ {{ var p DescriptionProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+func variantClasses(variant Variant) string {
+ switch variant {
+ case VariantDestructive:
+ return "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90"
+ default:
+ return "bg-card text-card-foreground"
+ }
+}
diff --git a/internal/ui/components/aspectratio/aspectratio.templ b/internal/ui/components/aspectratio/aspectratio.templ
new file mode 100644
index 0000000..60734c7
--- /dev/null
+++ b/internal/ui/components/aspectratio/aspectratio.templ
@@ -0,0 +1,63 @@
+// templui component aspectratio - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/aspect-ratio
+package aspectratio
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Ratio string
+
+const (
+ RatioAuto Ratio = "auto"
+ RatioSquare Ratio = "square"
+ RatioVideo Ratio = "video"
+ RatioPortrait Ratio = "portrait"
+ RatioWide Ratio = "wide"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Ratio Ratio
+}
+
+templ AspectRatio(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+func ratioClass(ratio Ratio) string {
+ switch ratio {
+ case RatioSquare:
+ return "aspect-square"
+ case RatioVideo:
+ return "aspect-video"
+ case RatioPortrait:
+ return "aspect-[3/4]"
+ case RatioWide:
+ return "aspect-[2/1]"
+ case RatioAuto:
+ return "aspect-auto"
+ default:
+ return "aspect-auto"
+ }
+}
diff --git a/internal/ui/components/avatar/avatar.templ b/internal/ui/components/avatar/avatar.templ
new file mode 100644
index 0000000..3a4128b
--- /dev/null
+++ b/internal/ui/components/avatar/avatar.templ
@@ -0,0 +1,97 @@
+// templui component avatar - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/avatar
+package avatar
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ImageProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Alt string
+ Src string
+}
+
+type FallbackProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+templ Avatar(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Image(props ...ImageProps) {
+ {{ var p ImageProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ Fallback(props ...FallbackProps) {
+ {{ var p FallbackProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/badge/badge.templ b/internal/ui/components/badge/badge.templ
new file mode 100644
index 0000000..6567d1e
--- /dev/null
+++ b/internal/ui/components/badge/badge.templ
@@ -0,0 +1,59 @@
+// templui component badge - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/badge
+package badge
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Variant string
+
+const (
+ VariantDefault Variant = "default"
+ VariantSecondary Variant = "secondary"
+ VariantDestructive Variant = "destructive"
+ VariantOutline Variant = "outline"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Variant Variant
+}
+
+templ Badge(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ svg]:size-3 gap-1 [&>svg]:pointer-events-none",
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ "transition-[color,box-shadow] overflow-hidden",
+ p.variantClasses(),
+ p.Class,
+ ),
+ }
+ { p.Attributes... }
+ >
+ { children... }
+
+}
+
+func (p Props) variantClasses() string {
+ switch p.Variant {
+ case VariantDestructive:
+ return "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60"
+ case VariantOutline:
+ return "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"
+ case VariantSecondary:
+ return "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90"
+ default:
+ return "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90"
+ }
+}
diff --git a/internal/ui/components/breadcrumb/breadcrumb.templ b/internal/ui/components/breadcrumb/breadcrumb.templ
new file mode 100644
index 0000000..6e87c9e
--- /dev/null
+++ b/internal/ui/components/breadcrumb/breadcrumb.templ
@@ -0,0 +1,176 @@
+// templui component breadcrumb - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/breadcrumb
+package breadcrumb
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ListProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ItemProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Current bool
+}
+
+type LinkProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Href string
+}
+
+type SeparatorProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ UseCustom bool
+}
+
+templ Breadcrumb(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ List(props ...ListProps) {
+ {{ var p ListProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Item(props ...ItemProps) {
+ {{ var p ItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Link(props ...LinkProps) {
+ {{ var p LinkProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Separator(props ...SeparatorProps) {
+ {{ var p SeparatorProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ if p.UseCustom {
+ { children... }
+ } else {
+ @icon.ChevronRight(icon.Props{Size: 14, Class: "text-muted-foreground"})
+ }
+
+}
+
+templ Page(props ...ItemProps) {
+ {{ var p ItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
diff --git a/internal/ui/components/button/button.templ b/internal/ui/components/button/button.templ
new file mode 100644
index 0000000..5883518
--- /dev/null
+++ b/internal/ui/components/button/button.templ
@@ -0,0 +1,152 @@
+// templui component button - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/button
+package button
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+ "strings"
+)
+
+type Variant string
+type Size string
+type Type string
+
+const (
+ VariantDefault Variant = "default"
+ VariantDestructive Variant = "destructive"
+ VariantOutline Variant = "outline"
+ VariantSecondary Variant = "secondary"
+ VariantGhost Variant = "ghost"
+ VariantLink Variant = "link"
+)
+
+const (
+ TypeButton Type = "button"
+ TypeReset Type = "reset"
+ TypeSubmit Type = "submit"
+)
+
+const (
+ SizeDefault Size = "default"
+ SizeSm Size = "sm"
+ SizeLg Size = "lg"
+ SizeIcon Size = "icon"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Variant Variant
+ Size Size
+ FullWidth bool
+ Href string
+ Target string
+ Disabled bool
+ Type Type
+ Form string
+}
+
+templ Button(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.Type == "" {
+ {{ p.Type = TypeButton }}
+ }
+ if p.Href != "" && !p.Disabled {
+
+ { children... }
+
+ } else {
+
+ }
+}
+
+func (b Props) variantClasses() string {
+ switch b.Variant {
+ case VariantDestructive:
+ return "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60"
+ case VariantOutline:
+ return "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50"
+ case VariantSecondary:
+ return "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80"
+ case VariantGhost:
+ return "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50"
+ case VariantLink:
+ return "text-primary underline-offset-4 hover:underline"
+ default:
+ return "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90"
+ }
+}
+
+func (b Props) sizeClasses() string {
+ switch b.Size {
+ case SizeSm:
+ return "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5"
+ case SizeLg:
+ return "h-10 rounded-md px-6 has-[>svg]:px-4"
+ case SizeIcon:
+ return "size-9"
+ default: // SizeDefault
+ return "h-9 px-4 py-2 has-[>svg]:px-3"
+ }
+}
+
+func (b Props) modifierClasses() string {
+ classes := []string{}
+ if b.FullWidth {
+ classes = append(classes, "w-full")
+ }
+ return strings.Join(classes, " ")
+}
diff --git a/internal/ui/components/calendar/calendar.templ b/internal/ui/components/calendar/calendar.templ
new file mode 100644
index 0000000..17232eb
--- /dev/null
+++ b/internal/ui/components/calendar/calendar.templ
@@ -0,0 +1,195 @@
+// templui component calendar - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/calendar
+package calendar
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+ "strconv"
+ "time"
+)
+
+type LocaleTag string
+
+var (
+ LocaleDefaultTag = LocaleTag("en-US")
+ LocaleTagChinese = LocaleTag("zh-CN")
+ LocaleTagFrench = LocaleTag("fr-FR")
+ LocaleTagGerman = LocaleTag("de-DE")
+ LocaleTagItalian = LocaleTag("it-IT")
+ LocaleTagJapanese = LocaleTag("ja-JP")
+ LocaleTagPortuguese = LocaleTag("pt-PT")
+ LocaleTagSpanish = LocaleTag("es-ES")
+)
+
+type Day int
+
+var (
+ Sunday = Day(0)
+ Monday = Day(1)
+ Tuesday = Day(2)
+ Wednesday = Day(3)
+ Thursday = Day(4)
+ Friday = Day(5)
+ Saturday = Day(6)
+)
+
+type Props struct {
+ ID string
+ Class string
+ LocaleTag LocaleTag
+ Value *time.Time
+ Name string
+ InitialMonth int // Optional: 0-11 (Default: current or from Value). Controls the initially displayed month view.
+ InitialYear int // Optional: (Default: current or from Value). Controls the initially displayed year view.
+ StartOfWeek *Day // Optional: 0-6 [Sun-Sat] (Default: 1).
+ RenderHiddenInput bool // Optional: Whether to render the hidden input (Default: true). Set to false when used inside DatePicker.
+}
+
+templ Calendar(props ...Props) {
+ {{
+ var p Props
+ if len(props) > 0 {
+ p = props[0]
+ }
+ if p.ID == "" {
+ p.ID = utils.RandomID() + "-calendar"
+ }
+ if p.Name == "" {
+ // Should be provided by parent (e.g., DatePicker or in standalone usage)
+ p.Name = p.ID + "-value" // Fallback name
+ }
+ if p.LocaleTag == "" {
+ p.LocaleTag = LocaleDefaultTag
+ }
+ // Default to rendering hidden input unless explicitly set to false
+ if p.RenderHiddenInput == false && len(props) > 0 {
+ // Only respect false if it was explicitly passed
+ p.RenderHiddenInput = props[0].RenderHiddenInput
+ } else {
+ p.RenderHiddenInput = true
+ }
+
+ initialStartOfWeek := Monday
+ if p.StartOfWeek != nil {
+ initialStartOfWeek = *p.StartOfWeek
+ }
+
+ initialView := time.Now()
+ if p.Value != nil {
+ initialView = *p.Value
+ }
+
+ initialMonth := p.InitialMonth
+ initialYear := p.InitialYear
+
+ // Use year from initialView if InitialYear prop is invalid/unset (<= 0)
+ if initialYear <= 0 {
+ initialYear = initialView.Year()
+ }
+
+ // Use month from initialView if InitialMonth prop is invalid OR
+ // if InitialMonth is default 0 AND InitialYear was also defaulted (meaning neither was likely set explicitly)
+ if (initialMonth < 0 || initialMonth > 11) || (initialMonth == 0 && p.InitialYear <= 0) {
+ initialMonth = int(initialView.Month()) - 1 // time.Month is 1-12
+ }
+
+ initialSelectedISO := ""
+ if p.Value != nil {
+ initialSelectedISO = p.Value.Format("2006-01-02")
+ }
+
+ // For SelectBox display
+ currentMonth := initialMonth
+ currentYear := initialYear
+
+ // Generate short month names (English only, JS will update with localized)
+ monthNames := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
+ }}
+
+ if p.RenderHiddenInput {
+
+ }
+
+
+
+
+
+
+
+
+
+ { monthNames[currentMonth] }
+ @icon.ChevronDown(icon.Props{Size: 14, Class: "text-muted-foreground"})
+
+
+
+
+
+
+ { strconv.Itoa(currentYear) }
+ @icon.ChevronDown(icon.Props{Size: 14, Class: "text-muted-foreground"})
+
+
+
+
+
+
+
+
+
+
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/card/card.templ b/internal/ui/components/card/card.templ
new file mode 100644
index 0000000..523c8fe
--- /dev/null
+++ b/internal/ui/components/card/card.templ
@@ -0,0 +1,167 @@
+// templui component card - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/card
+package card
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type HeaderProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type TitleProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type DescriptionProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type FooterProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+templ Card(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Header(props ...HeaderProps) {
+ {{ var p HeaderProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Title(props ...TitleProps) {
+ {{ var p TitleProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Description(props ...DescriptionProps) {
+ {{ var p DescriptionProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Footer(props ...FooterProps) {
+ {{ var p FooterProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
diff --git a/internal/ui/components/carousel/carousel.templ b/internal/ui/components/carousel/carousel.templ
new file mode 100644
index 0000000..383a510
--- /dev/null
+++ b/internal/ui/components/carousel/carousel.templ
@@ -0,0 +1,211 @@
+// templui component carousel - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/carousel
+package carousel
+
+import (
+ "fmt"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+ "strconv"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Autoplay bool
+ Interval int
+ Loop bool
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ItemProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type PreviousProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type NextProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type IndicatorsProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Count int
+}
+
+templ Carousel(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Item(props ...ItemProps) {
+ {{ var p ItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Previous(props ...PreviousProps) {
+ {{ var p PreviousProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ Next(props ...NextProps) {
+ {{ var p NextProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ Indicators(props ...IndicatorsProps) {
+ {{ var p IndicatorsProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ for i := 0; i < p.Count; i++ {
+
+ }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/chart/chart.templ b/internal/ui/components/chart/chart.templ
new file mode 100644
index 0000000..794d524
--- /dev/null
+++ b/internal/ui/components/chart/chart.templ
@@ -0,0 +1,113 @@
+// templui component chart - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/charts
+package chart
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Variant string
+
+const (
+ VariantBar Variant = "bar"
+ VariantLine Variant = "line"
+ VariantPie Variant = "pie"
+ VariantDoughnut Variant = "doughnut"
+ VariantRadar Variant = "radar"
+)
+
+type Dataset struct {
+ Label string `json:"label"`
+ Data []float64 `json:"data"`
+ BorderWidth int `json:"borderWidth,omitempty"`
+ BorderColor interface{} `json:"borderColor,omitempty"`
+ BackgroundColor interface{} `json:"backgroundColor,omitempty"`
+ Tension float64 `json:"tension,omitempty"`
+ Fill bool `json:"fill,omitempty"`
+ Stepped bool `json:"stepped,omitempty"`
+}
+
+type Options struct {
+ Responsive bool `json:"responsive,omitempty"`
+ Legend bool `json:"legend,omitempty"`
+}
+
+type Data struct {
+ Labels []string `json:"labels"`
+ Datasets []Dataset `json:"datasets"`
+}
+
+type Config struct {
+ Type Variant `json:"type"`
+ Data Data `json:"data"`
+ Options Options `json:"options,omitempty"`
+ ShowLegend bool `json:"showLegend,omitempty"`
+ ShowXAxis bool `json:"showXAxis"`
+ ShowYAxis bool `json:"showYAxis"`
+ ShowXLabels bool `json:"showXLabels"`
+ ShowYLabels bool `json:"showYLabels"`
+ ShowXGrid bool `json:"showXGrid"`
+ ShowYGrid bool `json:"showYGrid"`
+ Horizontal bool `json:"horizontal"`
+ Stacked bool `json:"stacked"`
+}
+
+type Props struct {
+ ID string
+ Variant Variant
+ Data Data
+ Options Options
+ ShowLegend bool
+ ShowXAxis bool
+ ShowYAxis bool
+ ShowXLabels bool
+ ShowYLabels bool
+ ShowXGrid bool
+ ShowYGrid bool
+ Horizontal bool
+ Stacked bool
+ Class string
+ Attributes templ.Attributes
+}
+
+templ Chart(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.ID == "" {
+ {{ p.ID = "chart-" + utils.RandomID() }}
+ }
+ {{ canvasId := p.ID + "-canvas" }}
+ {{ dataId := p.ID + "-data" }}
+
+
+
+ {{
+ chartConfig := Config{
+ Type: p.Variant,
+ Data: p.Data,
+ Options: p.Options,
+ ShowLegend: p.ShowLegend,
+ ShowXAxis: p.ShowXAxis,
+ ShowYAxis: p.ShowYAxis,
+ ShowXLabels: p.ShowXLabels,
+ ShowYLabels: p.ShowYLabels,
+ ShowXGrid: p.ShowXGrid,
+ ShowYGrid: p.ShowYGrid,
+ Horizontal: p.Horizontal,
+ Stacked: p.Stacked,
+ }
+ }}
+ @templ.JSONScript(dataId, chartConfig)
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/checkbox/checkbox.templ b/internal/ui/components/checkbox/checkbox.templ
new file mode 100644
index 0000000..6a5c85b
--- /dev/null
+++ b/internal/ui/components/checkbox/checkbox.templ
@@ -0,0 +1,69 @@
+// templui component checkbox - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/checkbox
+package checkbox
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Name string
+ Value string
+ Disabled bool
+ Checked bool
+ Form string
+ Icon templ.Component
+}
+
+templ Checkbox(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+
+
+ if p.Icon != nil {
+ @p.Icon
+ } else {
+ @icon.Check(icon.Props{Size: 14})
+ }
+
+
+}
diff --git a/internal/ui/components/code/code.templ b/internal/ui/components/code/code.templ
new file mode 100644
index 0000000..b5e406b
--- /dev/null
+++ b/internal/ui/components/code/code.templ
@@ -0,0 +1,56 @@
+// templui component code - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/code
+package code
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Props struct {
+ ID string
+ Class string
+ Attrs templ.Attributes
+ Language string
+ CodeClass string
+}
+
+templ Code(props ...Props) {
+ // Highlight.js with theme switching
+
+
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.ID == "" {
+ {{ p.ID = "code-" + utils.RandomID() }}
+ }
+
+
+
+ { children... }
+
+
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/collapsible/collapsible.templ b/internal/ui/components/collapsible/collapsible.templ
new file mode 100644
index 0000000..0e5dcba
--- /dev/null
+++ b/internal/ui/components/collapsible/collapsible.templ
@@ -0,0 +1,86 @@
+// templui component collapsible - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/collapsible
+package collapsible
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Open bool
+}
+
+type TriggerProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+templ Collapsible(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.ID == "" {
+ {{ p.ID = utils.RandomID() }}
+ }
+
+ { children... }
+
+}
+
+templ Trigger(props ...TriggerProps) {
+ {{ var p TriggerProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/copybutton/copybutton.templ b/internal/ui/components/copybutton/copybutton.templ
new file mode 100644
index 0000000..3877450
--- /dev/null
+++ b/internal/ui/components/copybutton/copybutton.templ
@@ -0,0 +1,48 @@
+// templui component copybutton - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/copy-button
+package copybutton
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/button"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Props struct {
+ ID string // Optional button ID
+ Class string // Custom CSS classes
+ Attrs templ.Attributes // Additional HTML attributes
+ TargetID string // Required - ID of element to copy from
+}
+
+templ CopyButton(props Props) {
+ {{ var p = props }}
+ if p.ID == "" {
+ {{ p.ID = "copybutton-" + utils.RandomID() }}
+ }
+
+ @button.Button(button.Props{
+ ID: p.ID,
+ Class: utils.TwMerge("h-7 w-7 text-muted-foreground hover:text-accent-foreground", p.Class),
+ Attributes: p.Attrs,
+ Size: button.SizeIcon,
+ Variant: button.VariantGhost,
+ Type: button.TypeButton,
+ }) {
+
+ @icon.Clipboard(icon.Props{Size: 16})
+
+
+ @icon.Check(icon.Props{Size: 16})
+
+ }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/datepicker/datepicker.templ b/internal/ui/components/datepicker/datepicker.templ
new file mode 100644
index 0000000..da5ef3e
--- /dev/null
+++ b/internal/ui/components/datepicker/datepicker.templ
@@ -0,0 +1,158 @@
+// templui component datepicker - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/date-picker
+package datepicker
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/button"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/calendar"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/card"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/popover"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+ "time"
+)
+
+type Format string
+type LocaleTag string
+
+const (
+ FormatLOCALE_SHORT Format = "locale-short" // Locale-specific short format (e.g., MM/DD/YY or DD.MM.YY)
+ FormatLOCALE_MEDIUM Format = "locale-medium" // Locale-specific medium format (e.g., Jan 5, 2024 or 5. Jan. 2024)
+ FormatLOCALE_LONG Format = "locale-long" // Locale-specific long format (e.g., January 5, 2024 or 5. Januar 2024)
+ FormatLOCALE_FULL Format = "locale-full" // Locale-specific full format (e.g., Monday, January 5, 2024 or Montag, 5. Januar 2024)
+)
+
+// Common Locale (BCP 47)
+var (
+ LocaleDefaultTag = LocaleTag("en-US")
+ LocaleTagChinese = LocaleTag("zh-CN")
+ LocaleTagFrench = LocaleTag("fr-FR")
+ LocaleTagGerman = LocaleTag("de-DE")
+ LocaleTagItalian = LocaleTag("it-IT")
+ LocaleTagJapanese = LocaleTag("ja-JP")
+ LocaleTagPortuguese = LocaleTag("pt-PT")
+ LocaleTagSpanish = LocaleTag("es-ES")
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Name string
+ Value time.Time
+ Form string
+ Format Format // Controls the display format using Intl dateStyle options.
+ LocaleTag LocaleTag // BCP 47 Locale Tag (e.g., "en-US", "es-ES"). Determines language and regional format defaults.
+ StartOfWeek *calendar.Day // Optional: 0-6 [Sun-Sat] (Default: 1).
+ Placeholder string
+ Disabled bool
+ HasError bool
+}
+
+templ DatePicker(props ...Props) {
+ {{
+ var p Props
+ if len(props) > 0 {
+ p = props[0]
+ }
+ if p.ID == "" {
+ p.ID = utils.RandomID()
+ }
+ if p.Name == "" {
+ p.Name = p.ID
+ }
+ if p.Placeholder == "" {
+ p.Placeholder = "Select a date"
+ }
+ if p.LocaleTag == "" {
+ p.LocaleTag = LocaleDefaultTag
+ }
+ if p.Format == "" {
+ p.Format = FormatLOCALE_MEDIUM
+ }
+
+ var contentID = p.ID + "-content"
+ var valuePtr *time.Time
+ var initialSelectedISO string
+ if !p.Value.IsZero() {
+ valuePtr = &p.Value
+ initialSelectedISO = p.Value.Format("2006-01-02")
+ }
+ }}
+
+
+ @popover.Trigger(popover.TriggerProps{For: contentID}) {
+ @button.Button(button.Props{
+ ID: p.ID,
+ Variant: button.VariantOutline,
+ Class: utils.TwMerge(
+ // Base styles matching input
+ "w-full h-9 px-3 py-1 text-base md:text-sm",
+ "flex items-center justify-between",
+ "rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] outline-none",
+ // Dark mode background
+ "dark:bg-input/30",
+ // Selection styles
+ "selection:bg-primary selection:text-primary-foreground",
+ // Focus styles
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
+ // Error/Invalid styles
+ "aria-invalid:ring-destructive/20 aria-invalid:border-destructive dark:aria-invalid:ring-destructive/40",
+ utils.If(p.HasError, "border-destructive ring-destructive/20 dark:ring-destructive/40"),
+ p.Class,
+ ),
+ Disabled: p.Disabled,
+ Attributes: utils.MergeAttributes(p.Attributes, templ.Attributes{
+ "data-tui-datepicker": "true",
+ "data-tui-datepicker-display-format": string(p.Format),
+ "data-tui-datepicker-locale-tag": string(p.LocaleTag),
+ "data-tui-datepicker-placeholder": p.Placeholder,
+ "aria-invalid": utils.If(p.HasError, "true"),
+ }),
+ }) {
+ if p.Placeholder != "" {
+
+ { p.Placeholder }
+
+ }
+
+ @icon.Calendar(icon.Props{Size: 16})
+
+ }
+ }
+ @popover.Content(popover.ContentProps{
+ ID: contentID,
+ Placement: popover.PlacementBottomStart,
+ Class: "p-0",
+ }) {
+ @card.Card(card.Props{
+ Class: "border-0 shadow-none",
+ }) {
+ @card.Content(card.ContentProps{
+ Class: "p-3",
+ }) {
+ @calendar.Calendar(calendar.Props{
+ ID: p.ID + "-calendar-instance", // Pass ID for calendar instance
+ LocaleTag: calendar.LocaleTag(p.LocaleTag), // Pass locale tag to calendar
+ StartOfWeek: p.StartOfWeek, // Pass start of week to calendar
+ Value: valuePtr, // Pass pointer to value
+ RenderHiddenInput: false, // Don't render hidden input inside popover
+ })
+ }
+ }
+ }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/dialog/dialog.templ b/internal/ui/components/dialog/dialog.templ
new file mode 100644
index 0000000..5eb6f06
--- /dev/null
+++ b/internal/ui/components/dialog/dialog.templ
@@ -0,0 +1,332 @@
+// templui component dialog - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/dialog
+package dialog
+
+import (
+ "context"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type contextKey string
+
+const (
+ instanceKey contextKey = "dialogInstance"
+ openKey contextKey = "dialogOpen"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ DisableClickAway bool
+ DisableESC bool
+ Open bool
+}
+
+type TriggerProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ For string // Reference to a specific dialog ID (for external triggers)
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ HideCloseButton bool
+ Open bool // Initial open state for standalone usage (when no context)
+ DisableAutoFocus bool
+}
+
+type CloseProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ For string // ID of the dialog to close (optional, defaults to closest dialog)
+}
+
+type HeaderProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type FooterProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type TitleProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type DescriptionProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+templ Dialog(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ {{ instanceID := p.ID }}
+ if instanceID == "" {
+ {{ instanceID = utils.RandomID() }}
+ }
+ {{ ctx = context.WithValue(ctx, instanceKey, instanceID) }}
+ {{ ctx = context.WithValue(ctx, openKey, p.Open) }}
+
+ { children... }
+
+}
+
+templ Trigger(props ...TriggerProps) {
+ {{ var p TriggerProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ {{ instanceID := "" }}
+ // Explicit For prop takes priority over inherited context
+ if p.For != "" {
+ {{ instanceID = p.For }}
+ } else if val := ctx.Value(instanceKey); val != nil {
+ {{ instanceID = val.(string) }}
+ }
+
+ { children... }
+
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ // Start with prop values as defaults
+ {{ instanceID := p.ID }}
+ {{ open := p.Open }}
+ // Override with context values if available
+ if val := ctx.Value(instanceKey); val != nil {
+ {{ instanceID = val.(string) }}
+ }
+ if val := ctx.Value(openKey); val != nil {
+ {{ open = val.(bool) }}
+ }
+ // Apply defaults if still empty
+ if instanceID == "" {
+ {{ instanceID = utils.RandomID() }}
+ }
+
+
+
+
+ { children... }
+ if !p.HideCloseButton {
+
+ }
+
+}
+
+templ Close(props ...CloseProps) {
+ {{ var p CloseProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Header(props ...HeaderProps) {
+ {{ var p HeaderProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Footer(props ...FooterProps) {
+ {{ var p FooterProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Title(props ...TitleProps) {
+ {{ var p TitleProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Description(props ...DescriptionProps) {
+ {{ var p DescriptionProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/dropdown/dropdown.templ b/internal/ui/components/dropdown/dropdown.templ
new file mode 100644
index 0000000..92b6ec3
--- /dev/null
+++ b/internal/ui/components/dropdown/dropdown.templ
@@ -0,0 +1,391 @@
+// templui component dropdown - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/dropdown
+package dropdown
+
+import (
+ "context"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/popover"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Placement = popover.Placement
+
+const (
+ PlacementTop = popover.PlacementTop
+ PlacementTopStart = popover.PlacementTopStart
+ PlacementTopEnd = popover.PlacementTopEnd
+ PlacementRight = popover.PlacementRight
+ PlacementRightStart = popover.PlacementRightStart
+ PlacementRightEnd = popover.PlacementRightEnd
+ PlacementBottom = popover.PlacementBottom
+ PlacementBottomStart = popover.PlacementBottomStart
+ PlacementBottomEnd = popover.PlacementBottomEnd
+ PlacementLeft = popover.PlacementLeft
+ PlacementLeftStart = popover.PlacementLeftStart
+ PlacementLeftEnd = popover.PlacementLeftEnd
+)
+
+type contextKey string
+
+var (
+ contentIDKey contextKey = "contentID"
+ subContentIDKey contextKey = "subContentID"
+)
+
+type Props struct {
+ ID string
+}
+
+type TriggerProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Placement Placement
+}
+
+type GroupProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type LabelProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ItemProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Disabled bool
+ Href string
+ Target string
+ PreventClose bool
+}
+
+type SeparatorProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ShortcutProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type SubProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type SubTriggerProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type SubContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type PortalProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+templ Dropdown(props ...Props) {
+ {{
+ var p Props
+ if len(props) > 0 {
+ p = props[0]
+ }
+ contentID := p.ID
+ if contentID == "" {
+ contentID = utils.RandomID()
+ }
+ ctx = context.WithValue(ctx, contentIDKey, contentID)
+ }}
+ { children... }
+}
+
+templ Trigger(props ...TriggerProps) {
+ {{
+ var p TriggerProps
+ if len(props) > 0 {
+ p = props[0]
+ }
+ contentID, ok := ctx.Value(contentIDKey).(string)
+ if !ok {
+ contentID = "fallback-content-id"
+ }
+ }}
+ @popover.Trigger(popover.TriggerProps{
+ ID: p.ID,
+ Class: p.Class,
+ Attributes: p.Attributes,
+ For: contentID,
+ TriggerType: popover.TriggerTypeClick,
+ }) {
+ { children... }
+ }
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ {{ contentID, ok := ctx.Value(contentIDKey).(string) }}
+ if !ok {
+ {{ contentID = "fallback-content-id" }} // Must match fallback in Trigger
+ }
+ {{
+ placement := p.Placement
+ if placement == "" {
+ placement = PlacementBottomStart
+ }
+ }}
+ @popover.Content(popover.ContentProps{
+ ID: contentID,
+ Placement: placement,
+ Class: utils.TwMerge(
+ "z-50 rounded-md bg-popover p-1 shadow-md focus:outline-none overflow-auto",
+ "border border-border",
+ "min-w-[8rem] max-h-[300px]",
+ p.Class,
+ ),
+ Attributes: p.Attributes,
+ Exclusive: true,
+ }) {
+ { children... }
+ }
+}
+
+templ Group(props ...GroupProps) {
+ {{ var p GroupProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Label(props ...LabelProps) {
+ {{ var p LabelProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Item(props ...ItemProps) {
+ {{ var p ItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.ID == "" {
+ {{ p.ID = utils.RandomID() }}
+ }
+ if p.Href != "" {
+
+ { children... }
+
+ } else {
+
+ }
+}
+
+templ Separator(props ...SeparatorProps) {
+ {{ var p SeparatorProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ Shortcut(props ...ShortcutProps) {
+ {{ var p ShortcutProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Sub(props ...SubProps) {
+ {{
+ var p SubProps
+ if len(props) > 0 {
+ p = props[0]
+ }
+ subContentID := p.ID
+ if subContentID == "" {
+ subContentID = utils.RandomID()
+ }
+ ctx = context.WithValue(ctx, subContentIDKey, subContentID)
+ }}
+
+ { children... }
+
+}
+
+templ SubTrigger(props ...SubTriggerProps) {
+ {{
+ var p SubTriggerProps
+ if len(props) > 0 {
+ p = props[0]
+ }
+ subContentID, ok := ctx.Value(subContentIDKey).(string)
+ if !ok {
+ subContentID = "fallback-subcontent-id"
+ }
+ }}
+ @popover.Trigger(popover.TriggerProps{
+ ID: p.ID,
+ For: subContentID,
+ TriggerType: popover.TriggerTypeHover,
+ }) {
+
+ }
+}
+
+templ SubContent(props ...SubContentProps) {
+ {{
+ var p SubContentProps
+ if len(props) > 0 {
+ p = props[0]
+ }
+ subContentID, ok := ctx.Value(subContentIDKey).(string)
+ if !ok {
+ subContentID = "fallback-subcontent-id"
+ }
+ }}
+ @popover.Content(popover.ContentProps{
+ ID: subContentID,
+ Placement: popover.PlacementRightStart,
+ Offset: -4, // Adjust as needed
+ HoverDelay: 100, // ms
+ HoverOutDelay: 200, // ms
+ Class: utils.TwMerge(
+ "z-[9999] min-w-[8rem] rounded-md border bg-popover p-1 shadow-lg",
+ p.Class,
+ ),
+ Attributes: p.Attributes,
+ }) {
+ { children... }
+ }
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/form/form.templ b/internal/ui/components/form/form.templ
new file mode 100644
index 0000000..2655b92
--- /dev/null
+++ b/internal/ui/components/form/form.templ
@@ -0,0 +1,138 @@
+// templui component form - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/form
+package form
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/label"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type MessageVariant string
+
+const (
+ MessageVariantError MessageVariant = "error"
+ MessageVariantInfo MessageVariant = "info"
+)
+
+type ItemProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type LabelProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ For string
+ DisabledClass string
+}
+
+type DescriptionProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type MessageProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Variant MessageVariant
+}
+
+templ Item(props ...ItemProps) {
+ {{ var p ItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ ItemFlex(props ...ItemProps) {
+ {{ var p ItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Label(props ...LabelProps) {
+ {{ var p LabelProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ @label.Label(label.Props{
+ ID: p.ID,
+ Class: p.Class,
+ Attributes: p.Attributes,
+ For: p.For,
+ }) {
+ { children... }
+ }
+}
+
+templ Description(props ...DescriptionProps) {
+ {{ var p DescriptionProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Message(props ...MessageProps) {
+ {{ var p MessageProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+func messageVariantClass(variant MessageVariant) string {
+ switch variant {
+ case MessageVariantError:
+ return "text-red-500"
+ case MessageVariantInfo:
+ return "text-blue-500"
+ default:
+ return ""
+ }
+}
diff --git a/internal/ui/components/icon/icon.go b/internal/ui/components/icon/icon.go
new file mode 100644
index 0000000..b955f95
--- /dev/null
+++ b/internal/ui/components/icon/icon.go
@@ -0,0 +1,118 @@
+// templui component icon - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/icon
+package icon
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "sync"
+
+ "github.com/a-h/templ"
+)
+
+// iconContents caches the fully generated SVG strings for icons that have been used,
+// keyed by a composite key of name and props to handle different stylings.
+var (
+ iconContents = make(map[string]string)
+ iconMutex sync.RWMutex
+)
+
+// Props defines the properties that can be set for an icon.
+type Props struct {
+ Size int
+ Color string
+ Fill string
+ Stroke string
+ StrokeWidth string // Stroke Width of Icon, Usage: "2.5"
+ Class string
+}
+
+// Icon returns a function that generates a templ.Component for the specified icon name.
+func Icon(name string) func(...Props) templ.Component {
+ return func(props ...Props) templ.Component {
+ var p Props
+ if len(props) > 0 {
+ p = props[0]
+ }
+
+ // Create a unique key for the cache based on icon name and all relevant props.
+ // This ensures different stylings of the same icon are cached separately.
+ cacheKey := fmt.Sprintf("%s|s:%d|c:%s|f:%s|sk:%s|sw:%s|cl:%s",
+ name, p.Size, p.Color, p.Fill, p.Stroke, p.StrokeWidth, p.Class)
+
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ iconMutex.RLock()
+ svg, cached := iconContents[cacheKey]
+ iconMutex.RUnlock()
+
+ if cached {
+ _, err = w.Write([]byte(svg))
+ return err
+ }
+
+ // Not cached, generate it
+ // The actual generation now happens once and is cached.
+ generatedSvg, err := generateSVG(name, p) // p (Props) is passed to generateSVG
+ if err != nil {
+ // Provide more context in the error message
+ return fmt.Errorf("failed to generate svg for icon '%s' with props %+v: %w", name, p, err)
+ }
+
+ iconMutex.Lock()
+ iconContents[cacheKey] = generatedSvg
+ iconMutex.Unlock()
+
+ _, err = w.Write([]byte(generatedSvg))
+ return err
+ })
+ }
+}
+
+// generateSVG creates an SVG string for the specified icon with the given properties.
+// This function is called when an icon-prop combination is not yet in the cache.
+func generateSVG(name string, props Props) (string, error) {
+ // Get the raw, inner SVG content for the icon name from our internal data map.
+ content, err := getIconContent(name) // This now reads from internalSvgData
+ if err != nil {
+ return "", err // Error from getIconContent already includes icon name
+ }
+
+ size := props.Size
+ if size <= 0 {
+ size = 24 // Default size
+ }
+
+ fill := props.Fill
+ if fill == "" {
+ fill = "none" // Default fill
+ }
+
+ stroke := props.Stroke
+ if stroke == "" {
+ stroke = props.Color // Fallback to Color if Stroke is not set
+ }
+ if stroke == "" {
+ stroke = "currentColor" // Default stroke color
+ }
+
+ strokeWidth := props.StrokeWidth
+ if strokeWidth == "" {
+ strokeWidth = "2" // Default stroke width
+ }
+
+ // Construct the final SVG string.
+ // The data-lucide attribute helps identify these as Lucide icons if needed.
+ return fmt.Sprintf("",
+ size, size, fill, stroke, strokeWidth, props.Class, content), nil
+}
+
+// getIconContent retrieves the raw inner SVG content for a given icon name.
+// It reads from the pre-generated internalSvgData map from icon_data.go.
+func getIconContent(name string) (string, error) {
+ content, exists := internalSvgData[name]
+ if !exists {
+ return "", fmt.Errorf("icon '%s' not found in internalSvgData map", name)
+ }
+ return content, nil
+}
diff --git a/internal/ui/components/icon/icon_data.go b/internal/ui/components/icon/icon_data.go
new file mode 100644
index 0000000..4cddf21
--- /dev/null
+++ b/internal/ui/components/icon/icon_data.go
@@ -0,0 +1,6494 @@
+// templui component icon - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/icon
+package icon
+
+// This file is auto generated
+// Using Lucide icons version 0.544.0
+
+const LucideVersion = "0.544.0"
+
+var internalSvgData = map[string]string{
+ "vegan": `
+
+ `,
+ "wifi-off": `
+
+
+
+
+
+ `,
+ "crosshair": `
+
+
+
+ `,
+ "gallery-horizontal-end": `
+
+ `,
+ "image-down": `
+
+
+ `,
+ "mail-warning": `
+
+
+ `,
+ "spline-pointer": `
+
+
+ `,
+ "vote": `
+
+ `,
+ "badge-indian-rupee": `
+
+
+ `,
+ "calendar-check-2": `
+
+
+
+ `,
+ "eclipse": `
+ `,
+ "file-input": `
+
+
+ `,
+ "file-minus": `
+
+ `,
+ "graduation-cap": `
+
+ `,
+ "hand-fist": `
+
+
+ `,
+ "square-check": `
+ `,
+ "arrow-right-left": `
+
+
+ `,
+ "bug": `
+
+
+
+
+
+
+
+
+
+ `,
+ "cloud-drizzle": `
+
+
+
+
+
+ `,
+ "cloud-rain": `
+
+
+ `,
+ "message-circle-plus": `
+
+ `,
+ "panel-left-close": `
+
+ `,
+ "table-columns-split": `
+
+
+
+
+
+
+
+
+
+ `,
+ "touchpad-off": `
+
+
+
+
+ `,
+ "align-horizontal-justify-start": `
+
+ `,
+ "chevron-down": ``,
+ "circle-small": ``,
+ "command": ``,
+ "id-card": `
+
+
+
+ `,
+ "list-todo": `
+
+
+
+ `,
+ "clock-2": `
+ `,
+ "file-chart-column": `
+
+
+
+ `,
+ "key-round": `
+ `,
+ "phone-outgoing": `
+
+ `,
+ "receipt-russian-ruble": `
+
+ `,
+ "signpost-big": `
+
+
+ `,
+ "tent": `
+
+
+ `,
+ "play": ``,
+ "arrow-up-from-line": `
+
+ `,
+ "move-diagonal": `
+
+ `,
+ "square-equal": `
+
+ `,
+ "step-back": `
+ `,
+ "users": `
+
+
+ `,
+ "squircle-dashed": `
+
+
+
+
+
+
+ `,
+ "cloud-sun-rain": `
+
+
+
+
+
+
+ `,
+ "file-output": `
+
+
+
+ `,
+ "move-right": `
+ `,
+ "align-start-vertical": `
+
+ `,
+ "clock-9": `
+ `,
+ "ear": `
+ `,
+ "cloud": ``,
+ "mountain-snow": `
+ `,
+ "refresh-cw-off": `
+
+
+
+
+
+ `,
+ "brain-cog": `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ "caravan": `
+
+
+ `,
+ "clover": `
+
+ `,
+ "fold-horizontal": `
+
+
+
+
+
+
+ `,
+ "git-pull-request-draft": `
+
+
+
+ `,
+ "monitor-up": `
+
+
+
+ `,
+ "moon-star": `
+
+ `,
+ "snowflake": `
+
+
+
+
+
+
+
+
+
+
+ `,
+ "badge-swiss-franc": `
+
+
+ `,
+ "cross": ``,
+ "folder-archive": `
+
+
+ `,
+ "folder-x": `
+
+ `,
+ "scan-qr-code": `
+
+
+
+
+
+
+ `,
+ "shield-check": `
+ `,
+ "squirrel": `
+
+
+ `,
+ "user-x": `
+
+
+ `,
+ "audio-lines": `
+
+
+
+
+ `,
+ "book-type": `
+
+
+ `,
+ "copy-plus": `
+
+
+ `,
+ "earth": `
+
+
+ `,
+ "disc-3": `
+
+
+ `,
+ "glass-water": `
+ `,
+ "speech": `
+
+ `,
+ "square-user": `
+
+ `,
+ "cookie": `
+
+
+
+
+ `,
+ "corner-up-left": `
+ `,
+ "footprints": `
+
+
+ `,
+ "linkedin": `
+
+ `,
+ "panel-right": `
+ `,
+ "pocket-knife": `
+
+
+
+ `,
+ "refresh-ccw-dot": `
+
+
+
+ `,
+ "square-arrow-left": `
+
+ `,
+ "cup-soda": `
+
+
+ `,
+ "map-pin-minus-inside": `
+ `,
+ "mic-vocal": `
+
+ `,
+ "phone-call": `
+
+ `,
+ "replace-all": `
+
+
+
+
+
+
+
+ `,
+ "vector-square": `
+
+
+
+
+
+
+ `,
+ "panels-left-bottom": `
+
+ `,
+ "sun": `
+
+
+
+
+
+
+
+ `,
+ "tool-case": `
+
+
+ `,
+ "trash": `
+
+ `,
+ "wallet-cards": `
+
+ `,
+ "webhook-off": `
+
+
+
+
+
+ `,
+ "columns-3-cog": `
+
+
+
+
+
+
+
+
+
+
+ `,
+ "ear-off": `
+
+
+
+ `,
+ "pause": `
+ `,
+ "signal-medium": `
+
+ `,
+ "square-function": `
+
+ `,
+ "vibrate-off": `
+
+
+
+ `,
+ "wifi-pen": `
+
+
+ `,
+ "whole-word": `
+
+
+
+ `,
+ "paint-bucket": `
+
+
+ `,
+ "align-horizontal-space-around": `
+
+ `,
+ "calendar-cog": `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ "search-slash": `
+
+ `,
+ "ship-wheel": `
+
+
+
+
+
+
+
+
+ `,
+ "square-arrow-out-down-left": `
+
+ `,
+ "timer-reset": `
+
+
+ `,
+ "turntable": `
+
+
+ `,
+ "users-round": `
+
+ `,
+ "cigarette-off": `
+
+
+
+
+ `,
+ "currency": `
+
+
+
+ `,
+ "flashlight": `
+
+ `,
+ "lightbulb": `
+
+ `,
+ "minimize": `
+
+
+ `,
+ "monitor-smartphone": `
+
+
+ `,
+ "move-up-left": `
+ `,
+ "package-2": `
+
+ `,
+ "arrow-up-z-a": `
+
+
+
+ `,
+ "badge-pound-sterling": `
+
+
+ `,
+ "monitor-check": `
+
+
+ `,
+ "wind-arrow-down": `
+
+
+ `,
+ "calendar-minus": `
+
+
+
+ `,
+ "file-chart-line": `
+
+ `,
+ "shredder": `
+
+
+
+
+
+ `,
+ "view": `
+
+
+ `,
+ "arrow-up-a-z": `
+
+
+
+ `,
+ "annoyed": `
+
+
+ `,
+ "cherry": `
+
+
+ `,
+ "briefcase-medical": `
+
+
+
+
+ `,
+ "flag-off": `
+
+
+ `,
+ "gamepad": `
+
+
+
+ `,
+ "milestone": `
+
+ `,
+ "radiation": `
+
+
+ `,
+ "chevron-up": ``,
+ "arrow-down-narrow-wide": `
+
+
+
+ `,
+ "circle-parking-off": `
+
+
+
+
+ `,
+ "circle-stop": `
+ `,
+ "pen-tool": `
+
+
+ `,
+ "plane": ``,
+ "rss": `
+
+ `,
+ "share": `
+
+ `,
+ "badge-dollar-sign": `
+
+ `,
+ "folder-symlink": `
+ `,
+ "stethoscope": `
+
+
+
+ `,
+ "brick-wall-shield": `
+
+
+
+
+
+
+ `,
+ "circle-chevron-left": `
+ `,
+ "flower": `
+
+
+
+
+
+
+
+
+ `,
+ "landmark": `
+
+
+
+
+ `,
+ "list-music": `
+
+
+
+ `,
+ "reply": `
+ `,
+ "save-off": `
+
+
+
+
+
+ `,
+ "square-split-vertical": `
+
+ `,
+ "arrow-up-down": `
+
+
+ `,
+ "circle-percent": `
+
+
+ `,
+ "bus-front": `
+
+
+
+
+
+
+
+ `,
+ "eye": `
+ `,
+ "route-off": `
+
+
+
+
+
+ `,
+ "alarm-clock": `
+
+
+
+
+ `,
+ "cable": `
+
+
+
+
+
+ `,
+ "corner-down-left": `
+ `,
+ "grid-2x2-plus": `
+
+ `,
+ "hand-metal": `
+
+
+ `,
+ "badge-info": `
+
+ `,
+ "bath": `
+
+
+
+ `,
+ "bug-play": `
+
+
+
+
+
+
+
+ `,
+ "egg-fried": `
+ `,
+ "rainbow": `
+
+ `,
+ "text-cursor-input": `
+
+
+
+ `,
+ "tree-pine": `
+ `,
+ "user-lock": `
+
+
+ `,
+ "bitcoin": ``,
+ "book-x": `
+
+ `,
+ "circle-chevron-up": `
+ `,
+ "git-pull-request-closed": `
+
+
+
+
+ `,
+ "globe-lock": `
+
+
+ `,
+ "heart-off": `
+
+ `,
+ "moon": ``,
+ "spray-can": `
+
+
+
+
+
+
+
+
+ `,
+ "bell": `
+ `,
+ "file-badge": `
+
+
+ `,
+ "lock-keyhole-open": `
+
+ `,
+ "mail-check": `
+
+ `,
+ "shield-question-mark": `
+
+ `,
+ "voicemail": `
+
+ `,
+ "blocks": `
+ `,
+ "case-upper": `
+
+ `,
+ "focus": `
+
+
+
+ `,
+ "megaphone-off": `
+
+
+
+ `,
+ "repeat": `
+
+
+ `,
+ "signal-high": `
+
+
+ `,
+ "square-star": `
+ `,
+ "barcode": `
+
+
+
+ `,
+ "book-text": `
+
+ `,
+ "clipboard-check": `
+
+ `,
+ "chart-bar-big": `
+
+ `,
+ "gitlab": ``,
+ "receipt-japanese-yen": `
+
+
+
+ `,
+ "book-alert": `
+
+ `,
+ "arrow-up-0-1": `
+
+
+
+ `,
+ "clock-plus": `
+
+
+ `,
+ "presentation": `
+
+ `,
+ "apple": `
+ `,
+ "bean-off": `
+
+
+ `,
+ "building": `
+
+
+
+
+
+
+
+
+
+ `,
+ "rotate-3d": `
+
+ `,
+ "ferris-wheel": `
+
+
+
+
+
+
+
+ `,
+ "file-code": `
+
+
+ `,
+ "file-pen": `
+
+ `,
+ "gem": `
+
+ `,
+ "case-lower": `
+
+
+ `,
+ "origami": `
+
+ `,
+ "qr-code": `
+
+
+
+
+
+
+
+
+
+
+ `,
+ "skip-back": `
+ `,
+ "clock": `
+ `,
+ "file-video-camera": `
+
+
+ `,
+ "hard-drive": `
+
+
+ `,
+ "user-round": `
+ `,
+ "book": ``,
+ "cloud-off": `
+
+ `,
+ "file-type": `
+
+
+
+ `,
+ "file-up": `
+
+
+ `,
+ "grip-vertical": `
+
+
+
+
+ `,
+ "land-plot": `
+
+
+ `,
+ "calendar-clock": `
+
+
+
+
+ `,
+ "drumstick": `
+ `,
+ "file-check": `
+
+ `,
+ "handbag": `
+ `,
+ "grip": `
+
+
+
+
+
+
+
+ `,
+ "panel-bottom-close": `
+
+ `,
+ "panel-top-close": `
+
+ `,
+ "search-check": `
+
+ `,
+ "chevrons-left-right-ellipsis": `
+
+
+
+ `,
+ "camera": `
+ `,
+ "cloud-download": `
+
+ `,
+ "database-zap": `
+
+
+
+ `,
+ "diamond": ``,
+ "laptop-minimal": `
+ `,
+ "monitor-play": `
+
+
+ `,
+ "squares-intersect": `
+
+
+
+
+
+
+
+
+
+ `,
+ "dice-6": `
+
+
+
+
+
+ `,
+ "expand": `
+
+
+
+
+
+
+ `,
+ "merge": `
+
+ `,
+ "map-pin": `
+ `,
+ "picture-in-picture-2": `
+ `,
+ "send": `
+ `,
+ "table-rows-split": `
+
+
+
+
+
+
+
+
+
+ `,
+ "volume-2": `
+
+ `,
+ "align-horizontal-distribute-center": `
+
+
+
+
+ `,
+ "house-plus": `
+
+
+ `,
+ "list-collapse": `
+
+
+
+ `,
+ "scissors": `
+
+
+
+ `,
+ "banknote-arrow-up": `
+
+
+
+
+ `,
+ "circle-dollar-sign": `
+
+ `,
+ "folder-git-2": `
+
+
+ `,
+ "layout-panel-top": `
+
+ `,
+ "square-terminal": `
+
+ `,
+ "cigarette": `
+
+
+
+ `,
+ "book-user": `
+
+ `,
+ "octagon-pause": `
+
+ `,
+ "user-plus": `
+
+
+ `,
+ "volume-off": `
+
+
+
+ `,
+ "beer-off": `
+
+
+
+
+
+
+ `,
+ "captions": `
+ `,
+ "folder-lock": `
+
+ `,
+ "hash": `
+
+
+ `,
+ "percent": `
+
+ `,
+ "radar": `
+
+
+
+
+
+
+ `,
+ "scissors-line-dashed": `
+
+
+
+
+
+ `,
+ "ticket-slash": `
+ `,
+ "clock-arrow-up": `
+
+
+ `,
+ "flask-round": `
+
+ `,
+ "frown": `
+
+
+ `,
+ "git-pull-request-arrow": `
+
+
+
+ `,
+ "video": `
+ `,
+ "circle-arrow-out-up-right": `
+
+ `,
+ "clock-6": `
+ `,
+ "contact-round": `
+
+
+
+ `,
+ "mic-off": `
+
+
+
+
+ `,
+ "package-plus": `
+
+
+
+
+ `,
+ "sliders-horizontal": `
+
+
+
+
+
+
+
+ `,
+ "text-align-end": `
+
+ `,
+ "egg-off": `
+
+ `,
+ "ethernet-port": `
+
+
+
+ `,
+ "microscope": `
+
+
+
+
+ `,
+ "scan-text": `
+
+
+
+
+
+ `,
+ "square-divide": `
+
+
+ `,
+ "chart-column-stacked": `
+
+
+
+ `,
+ "eye-closed": `
+
+
+
+ `,
+ "file-user": `
+
+
+ `,
+ "monitor-speaker": `
+
+
+
+ `,
+ "plug-2": `
+
+
+
+ `,
+ "align-horizontal-justify-end": `
+
+ `,
+ "chart-column-decreasing": `
+
+
+ `,
+ "crop": `
+ `,
+ "flip-vertical": `
+
+
+
+
+ `,
+ "message-square": ``,
+ "panel-top-bottom-dashed": `
+
+
+
+
+
+
+
+ `,
+ "arrow-down-1-0": `
+
+
+
+ `,
+ "calendar-plus-2": `
+
+
+
+
+ `,
+ "folder-heart": `
+ `,
+ "git-branch": `
+
+
+ `,
+ "globe": `
+
+ `,
+ "mouse-pointer-2": ``,
+ "move": `
+
+
+
+
+ `,
+ "panel-bottom-dashed": `
+
+
+
+ `,
+ "badge-check": `
+ `,
+ "check": ``,
+ "columns-4": `
+
+
+ `,
+ "folder-plus": `
+
+ `,
+ "folder-search": `
+
+ `,
+ "lectern": `
+
+ `,
+ "move-vertical": `
+
+ `,
+ "projector": `
+
+
+
+
+ `,
+ "arrow-up-to-line": `
+
+ `,
+ "file-x-2": `
+
+
+ `,
+ "folder-open-dot": `
+ `,
+ "octagon-minus": `
+ `,
+ "phone": ``,
+ "square-arrow-out-down-right": `
+
+ `,
+ "signature": `
+ `,
+ "underline": `
+ `,
+ "battery": `
+ `,
+ "bomb": `
+
+ `,
+ "import": `
+
+ `,
+ "power-off": `
+
+
+ `,
+ "refrigerator": `
+
+ `,
+ "shield-half": `
+ `,
+ "test-tubes": `
+
+
+
+
+ `,
+ "audio-waveform": ``,
+ "book-heart": `
+ `,
+ "columns-2": `
+ `,
+ "diff": `
+
+ `,
+ "leafy-green": `
+ `,
+ "log-in": `
+
+ `,
+ "mails": `
+
+ `,
+ "octagon": ``,
+ "award": `
+ `,
+ "chart-column": `
+
+
+ `,
+ "file-type-2": `
+
+
+
+ `,
+ "message-circle-question-mark": `
+
+ `,
+ "pi": `
+
+ `,
+ "siren": `
+
+
+
+
+
+
+ `,
+ "tractor": `
+
+
+
+
+
+
+
+ `,
+ "twitch": ``,
+ "album": `
+ `,
+ "crown": `
+ `,
+ "droplet": ``,
+ "ellipsis-vertical": `
+
+ `,
+ "maximize": `
+
+
+ `,
+ "popsicle": `
+ `,
+ "refresh-ccw": `
+
+
+ `,
+ "sofa": `
+
+
+
+ `,
+ "axe": `
+ `,
+ "beaker": `
+
+ `,
+ "boxes": `
+
+
+
+
+
+
+
+
+
+
+ `,
+ "clock-5": `
+ `,
+ "file-json-2": `
+
+
+ `,
+ "italic": `
+
+ `,
+ "library": `
+
+
+ `,
+ "monitor-x": `
+
+
+
+ `,
+ "arrow-left": `
+ `,
+ "chevrons-down": `
+ `,
+ "cable-car": `
+
+
+
+
+
+
+ `,
+ "decimals-arrow-right": `
+
+
+
+ `,
+ "arrow-down-z-a": `
+
+
+
+ `,
+ "philippine-peso": `
+
+ `,
+ "rotate-cw": `
+ `,
+ "saudi-riyal": `
+
+
+ `,
+ "book-key": `
+
+
+
+ `,
+ "ampersands": `
+ `,
+ "align-vertical-justify-end": `
+
+ `,
+ "file-x": `
+
+
+ `,
+ "folder-input": `
+
+ `,
+ "hop": `
+
+
+
+
+
+
+ `,
+ "square-chevron-left": `
+ `,
+ "user-round-cog": `
+
+
+
+
+
+
+
+
+
+ `,
+ "ampersand": `
+ `,
+ "ban": `
+ `,
+ "cone": `
+ `,
+ "handshake": `
+
+
+
+ `,
+ "lasso-select": `
+
+
+
+ `,
+ "shovel": `
+
+ `,
+ "spell-check-2": `
+
+ `,
+ "stretch-horizontal": `
+ `,
+ "battery-charging": `
+
+
+ `,
+ "calendar-fold": `
+
+
+
+ `,
+ "plane-landing": `
+ `,
+ "user-star": `
+
+ `,
+ "videotape": `
+
+
+
+ `,
+ "wifi": `
+
+
+ `,
+ "archive-x": `
+
+
+ `,
+ "flower-2": `
+
+
+
+ `,
+ "hard-hat": `
+
+
+ `,
+ "notebook-pen": `
+
+
+
+
+ `,
+ "phone-forwarded": `
+
+ `,
+ "square-arrow-out-up-left": `
+
+ `,
+ "worm": `
+
+ `,
+ "ambulance": `
+
+
+
+
+
+ `,
+ "pill": `
+ `,
+ "spade": `
+ `,
+ "user-round-pen": `
+
+ `,
+ "volume-x": `
+
+ `,
+ "baseline": `
+
+ `,
+ "closed-caption": `
+
+ `,
+ "github": `
+ `,
+ "car-front": `
+
+
+
+
+ `,
+ "cloud-hail": `
+
+
+
+
+
+ `,
+ "copy-x": `
+
+
+ `,
+ "contact": `
+
+
+
+ `,
+ "chart-no-axes-combined": `
+
+
+
+
+ `,
+ "delete": `
+
+ `,
+ "donut": `
+ `,
+ "pizza": `
+
+
+
+ `,
+ "bird": `
+
+
+
+
+ `,
+ "circle-dashed": `
+
+
+
+
+
+
+ `,
+ "hand-grab": `
+
+
+
+ `,
+ "layers-2": `
+ `,
+ "lightbulb-off": `
+
+
+
+ `,
+ "paintbrush-vertical": `
+
+
+ `,
+ "smile": `
+
+
+ `,
+ "square-check-big": `
+ `,
+ "chart-scatter": `
+
+
+
+
+ `,
+ "file-cog": `
+
+
+
+
+
+
+
+
+
+ `,
+ "folder-closed": `
+ `,
+ "flag-triangle-right": ``,
+ "funnel": ``,
+ "phone-missed": `
+
+ `,
+ "square-dashed-bottom": `
+
+ `,
+ "square-chevron-right": `
+ `,
+ "switch-camera": `
+
+
+
+ `,
+ "fan": `
+ `,
+ "file-chart-column-increasing": `
+
+
+
+ `,
+ "keyboard-off": `
+
+
+
+
+
+
+
+
+ `,
+ "panda": `
+
+
+
+
+ `,
+ "russian-ruble": `
+ `,
+ "square-m": `
+ `,
+ "app-window-mac": `
+
+
+ `,
+ "arrow-big-left-dash": `
+ `,
+ "arrow-up-right": `
+ `,
+ "circle-pause": `
+
+ `,
+ "cloud-alert": `
+
+ `,
+ "file-archive": `
+
+
+
+
+ `,
+ "list-checks": `
+
+
+
+ `,
+ "message-circle-off": `
+
+ `,
+ "axis-3d": `
+
+
+ `,
+ "list-video": `
+
+
+ `,
+ "message-square-lock": `
+
+ `,
+ "move-up": `
+ `,
+ "notebook-text": `
+
+
+
+
+
+
+ `,
+ "pin": `
+ `,
+ "turtle": `
+
+
+ `,
+ "cloud-check": `
+ `,
+ "ham": `
+
+
+ `,
+ "pentagon": ``,
+ "badge-euro": `
+
+ `,
+ "between-horizontal-end": `
+
+ `,
+ "code": `
+ `,
+ "creative-commons": `
+
+ `,
+ "database": `
+
+ `,
+ "grid-3x3": `
+
+
+
+ `,
+ "layout-list": `
+
+
+
+
+ `,
+ "mail-x": `
+
+
+ `,
+ "arrow-big-down": ``,
+ "compass": `
+ `,
+ "heart-handshake": ``,
+ "nut": `
+
+ `,
+ "panel-left-open": `
+
+ `,
+ "shrimp": `
+
+
+
+ `,
+ "x": `
+ `,
+ "binary": `
+
+
+
+
+ `,
+ "dna": `
+
+
+
+
+
+
+
+
+
+ `,
+ "dot": ``,
+ "gallery-horizontal": `
+
+ `,
+ "library-big": `
+
+ `,
+ "line-squiggle": ``,
+ "monitor-down": `
+
+
+
+ `,
+ "panels-top-left": `
+
+ `,
+ "grid-2x2": `
+
+ `,
+ "mouse-pointer-ban": `
+
+ `,
+ "move-up-right": `
+ `,
+ "panel-right-open": `
+
+ `,
+ "panel-top": `
+ `,
+ "radio-tower": `
+
+
+
+
+
+ `,
+ "soap-dispenser-droplet": `
+
+
+ `,
+ "square-pilcrow": `
+
+
+ `,
+ "circle-equal": `
+
+ `,
+ "calendar-search": `
+
+
+
+
+ `,
+ "flame": ``,
+ "file-scan": `
+
+
+
+
+ `,
+ "send-to-back": `
+
+
+ `,
+ "squares-subtract": `
+
+
+
+
+ `,
+ "tally-1": ``,
+ "tornado": `
+
+
+
+ `,
+ "arrow-down-right": `
+ `,
+ "skip-forward": `
+ `,
+ "squircle": ``,
+ "arrow-up-wide-narrow": `
+
+
+
+ `,
+ "codesandbox": `
+
+
+
+
+ `,
+ "lasso": `
+
+ `,
+ "music-4": `
+
+
+ `,
+ "notebook-tabs": `
+
+
+
+
+
+
+
+ `,
+ "pen": ``,
+ "plug-zap": `
+
+
+
+ `,
+ "server-cog": `
+
+
+
+
+
+
+
+
+
+
+ `,
+ "book-dashed": `
+
+
+
+
+
+
+
+
+
+ `,
+ "chef-hat": `
+ `,
+ "chart-bar-decreasing": `
+
+
+ `,
+ "file-json": `
+
+
+ `,
+ "panel-left-dashed": `
+
+
+
+ `,
+ "smartphone-nfc": `
+
+
+ `,
+ "tv-minimal-play": `
+
+ `,
+ "volume": ``,
+ "boom-box": `
+
+
+
+
+
+ `,
+ "diameter": `
+
+
+
+ `,
+ "eye-off": `
+
+
+ `,
+ "file-lock-2": `
+
+
+ `,
+ "map-pin-plus": `
+
+
+ `,
+ "pilcrow-right": `
+
+
+
+ `,
+ "wind": `
+
+ `,
+ "align-vertical-space-around": `
+
+ `,
+ "activity": ``,
+ "circle-alert": `
+
+ `,
+ "bookmark-x": `
+
+ `,
+ "circle-parking": `
+ `,
+ "cloud-cog": `
+
+
+
+
+
+
+
+ `,
+ "list-minus": `
+
+
+ `,
+ "sailboat": `
+
+ `,
+ "bike": `
+
+
+ `,
+ "chevrons-down-up": `
+ `,
+ "shield-x": `
+
+ `,
+ "shrub": `
+
+ `,
+ "square-arrow-right": `
+
+ `,
+ "square-dot": `
+ `,
+ "subscript": `
+
+ `,
+ "spool": `
+ `,
+ "circle-x": `
+
+ `,
+ "cloud-sun": `
+
+
+
+
+ `,
+ "highlighter": `
+ `,
+ "milk-off": `
+
+
+ `,
+ "ruler": `
+
+
+
+ `,
+ "youtube": `
+ `,
+ "chart-spline": `
+ `,
+ "locate": `
+
+
+
+ `,
+ "pen-off": `
+
+ `,
+ "battery-low": `
+
+ `,
+ "brush": `
+
+ `,
+ "clock-alert": `
+
+
+ `,
+ "check-check": `
+ `,
+ "link-2": `
+
+ `,
+ "replace": `
+
+
+
+
+
+ `,
+ "square-arrow-down": `
+
+ `,
+ "tags": `
+
+ `,
+ "brick-wall": `
+
+
+
+
+
+
+ `,
+ "columns-3": `
+
+ `,
+ "file-pen-line": `
+
+ `,
+ "folder-clock": `
+
+ `,
+ "text-align-start": `
+
+ `,
+ "bell-ring": `
+
+
+ `,
+ "bookmark": ``,
+ "cuboid": `
+
+ `,
+ "dumbbell": `
+
+
+
+ `,
+ "regex": `
+
+
+ `,
+ "camera-off": `
+
+
+ `,
+ "captions-off": `
+
+
+
+
+ `,
+ "copyleft": `
+ `,
+ "square-x": `
+
+ `,
+ "circle-question-mark": `
+
+ `,
+ "bed-single": `
+
+ `,
+ "arrow-left-from-line": `
+
+ `,
+ "grip-horizontal": `
+
+
+
+
+ `,
+ "text-select": `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ "turkish-lira": `
+
+ `,
+ "vibrate": `
+
+ `,
+ "person-standing": `
+
+
+ `,
+ "radical": ``,
+ "redo": `
+ `,
+ "user-round-search": `
+
+
+ `,
+ "weight": `
+ `,
+ "layout-dashboard": `
+
+
+ `,
+ "omega": ``,
+ "user-round-check": `
+
+ `,
+ "bottle-wine": `
+ `,
+ "chart-gantt": `
+
+
+ `,
+ "chart-no-axes-column": `
+
+ `,
+ "list-filter": `
+
+ `,
+ "map-pin-check-inside": `
+ `,
+ "shopping-cart": `
+
+ `,
+ "battery-warning": `
+
+
+
+ `,
+ "clock-7": `
+ `,
+ "hop-off": `
+
+
+
+
+
+
+
+ `,
+ "mouse-off": `
+
+
+ `,
+ "paint-roller": `
+
+ `,
+ "pound-sterling": `
+
+
+ `,
+ "washing-machine": `
+
+
+
+ `,
+ "wifi-sync": `
+
+
+
+
+
+ `,
+ "alarm-clock-minus": `
+
+
+
+
+ `,
+ "banknote": `
+
+ `,
+ "dna-off": `
+
+
+
+
+
+
+
+
+ `,
+ "grape": `
+
+
+
+
+
+
+
+ `,
+ "iteration-cw": `
+ `,
+ "lamp-floor": `
+
+ `,
+ "radius": `
+
+
+ `,
+ "square-mouse-pointer": `
+ `,
+ "arrow-down-from-line": `
+
+ `,
+ "cannabis": `
+ `,
+ "car": `
+
+
+ `,
+ "coins": `
+
+
+ `,
+ "corner-down-right": `
+ `,
+ "sliders-vertical": `
+
+
+
+
+
+
+
+ `,
+ "wand": `
+
+
+
+
+
+
+
+ `,
+ "wifi-zero": ``,
+ "arrow-big-right": ``,
+ "bell-electric": `
+
+
+
+
+ `,
+ "circle-ellipsis": `
+
+
+ `,
+ "calendar-sync": `
+
+
+
+
+
+
+ `,
+ "clock-8": `
+ `,
+ "concierge-bell": `
+
+
+ `,
+ "cloud-rain-wind": `
+
+
+ `,
+ "git-pull-request-create-arrow": `
+
+
+
+
+ `,
+ "bluetooth-connected": `
+
+ `,
+ "clock-1": `
+ `,
+ "circle-arrow-up": `
+
+ `,
+ "cloud-lightning": `
+ `,
+ "flask-conical-off": `
+
+
+
+
+ `,
+ "grid-2x2-x": `
+
+ `,
+ "package": `
+
+
+ `,
+ "repeat-1": `
+
+
+
+ `,
+ "banana": `
+ `,
+ "circle-pound-sterling": `
+
+
+ `,
+ "glasses": `
+
+
+
+ `,
+ "screen-share-off": `
+
+
+
+ `,
+ "skull": `
+
+
+ `,
+ "square-kanban": `
+
+
+ `,
+ "tangent": `
+
+
+ `,
+ "ticket-plus": `
+
+ `,
+ "fish": `
+
+
+
+
+ `,
+ "file-play": `
+
+ `,
+ "rectangle-vertical": ``,
+ "usb": `
+
+
+
+
+
+ `,
+ "wifi-low": `
+ `,
+ "unfold-vertical": `
+
+
+
+
+
+
+ `,
+ "popcorn": `
+
+
+ `,
+ "folder-kanban": `
+
+
+ `,
+ "picture-in-picture": `
+
+
+
+ `,
+ "satellite-dish": `
+
+
+ `,
+ "rows-2": `
+ `,
+ "signal-low": `
+ `,
+ "briefcase-business": `
+
+
+ `,
+ "database-backup": `
+
+
+
+
+ `,
+ "reply-all": `
+
+ `,
+ "shopping-bag": `
+
+ `,
+ "snail": `
+
+
+
+ `,
+ "sunset": `
+
+
+
+
+
+
+ `,
+ "waves-ladder": `
+
+
+
+ `,
+ "briefcase": `
+ `,
+ "clipboard-pen": `
+
+
+ `,
+ "router": `
+
+
+
+
+ `,
+ "square-plus": `
+
+ `,
+ "ticket": `
+
+
+ `,
+ "align-vertical-distribute-end": `
+
+
+ `,
+ "church": `
+
+
+
+ `,
+ "circle-dot": `
+ `,
+ "lamp-ceiling": `
+
+ `,
+ "lock": `
+ `,
+ "panel-bottom-open": `
+
+ `,
+ "zoom-in": `
+
+
+ `,
+ "battery-plus": `
+
+
+
+ `,
+ "chromium": `
+
+
+
+ `,
+ "clipboard-type": `
+
+
+
+ `,
+ "file-heart": `
+
+ `,
+ "folder-up": `
+
+ `,
+ "ribbon": `
+
+
+
+ `,
+ "arrow-down-to-dot": `
+
+ `,
+ "align-horizontal-space-between": `
+
+
+ `,
+ "arrow-left-to-line": `
+
+ `,
+ "pencil-ruler": `
+
+
+
+
+ `,
+ "amphora": `
+
+
+
+
+ `,
+ "clapperboard": `
+
+
+ `,
+ "chart-candlestick": `
+
+
+
+
+
+ `,
+ "droplets": `
+ `,
+ "japanese-yen": `
+
+ `,
+ "minimize-2": `
+
+
+ `,
+ "paintbrush": `
+
+ `,
+ "slice": ``,
+ "bot-off": `
+
+
+
+
+
+ `,
+ "circle-arrow-right": `
+
+ `,
+ "drone": `
+
+
+
+
+
+
+
+ `,
+ "receipt-cent": `
+
+ `,
+ "sword": `
+
+
+ `,
+ "tv-minimal": `
+ `,
+ "circle-fading-arrow-up": `
+
+
+
+
+
+ `,
+ "image-plus": `
+
+
+
+ `,
+ "kayak": `
+
+
+ `,
+ "train-track": `
+
+
+
+
+
+ `,
+ "user-minus": `
+
+ `,
+ "anvil": `
+
+
+
+ `,
+ "shield-alert": `
+
+ `,
+ "tram-front": `
+
+
+
+
+
+ `,
+ "aperture": `
+
+
+
+
+
+ `,
+ "folder-output": `
+
+ `,
+ "hourglass": `
+
+
+ `,
+ "meh": `
+
+
+ `,
+ "quote": `
+ `,
+ "square-slash": `
+ `,
+ "alarm-clock-off": `
+
+
+
+
+ `,
+ "angry": `
+
+
+
+
+ `,
+ "box": `
+
+ `,
+ "locate-fixed": `
+
+
+
+
+ `,
+ "clipboard-plus": `
+
+
+ `,
+ "folder-git": `
+
+
+ `,
+ "folder-root": `
+
+ `,
+ "ice-cream-bowl": `
+
+ `,
+ "tally-4": `
+
+
+ `,
+ "bluetooth-off": `
+
+ `,
+ "clock-10": `
+ `,
+ "hotel": `
+
+
+
+
+
+
+
+
+ `,
+ "square-arrow-out-up-right": `
+
+ `,
+ "file-code-2": `
+
+
+ `,
+ "folder-tree": `
+
+
+ `,
+ "hdmi-port": `
+ `,
+ "panel-left-right-dashed": `
+
+
+
+
+
+
+
+ `,
+ "satellite": `
+
+
+
+ `,
+ "thermometer": ``,
+ "align-horizontal-justify-center": `
+
+ `,
+ "archive-restore": `
+
+
+
+ `,
+ "heart-crack": `
+ `,
+ "monitor-pause": `
+
+
+
+ `,
+ "tag": `
+ `,
+ "at-sign": `
+ `,
+ "circle-arrow-out-up-left": `
+
+ `,
+ "candy-cane": `
+
+
+
+ `,
+ "copy-check": `
+
+ `,
+ "file-lock": `
+
+ `,
+ "message-square-plus": `
+
+ `,
+ "message-square-warning": `
+
+ `,
+ "scan-line": `
+
+
+
+ `,
+ "star": ``,
+ "timer-off": `
+
+
+
+ `,
+ "undo-dot": `
+
+ `,
+ "dice-3": `
+
+
+ `,
+ "package-minus": `
+
+
+
+ `,
+ "type-outline": ``,
+ "map-pinned": `
+
+ `,
+ "phone-off": `
+
+ `,
+ "square-pause": `
+
+ `,
+ "syringe": `
+
+
+
+
+ `,
+ "text-wrap": `
+
+
+ `,
+ "venetian-mask": `
+
+ `,
+ "dam": `
+
+
+
+
+
+ `,
+ "disc-album": `
+
+ `,
+ "file-check-2": `
+
+ `,
+ "heading-5": `
+
+
+
+ `,
+ "square-code": `
+
+ `,
+ "toggle-right": `
+ `,
+ "vault": `
+
+
+
+
+
+
+
+
+ `,
+ "align-horizontal-distribute-start": `
+
+
+ `,
+ "spline": `
+
+ `,
+ "chevrons-left-right": `
+ `,
+ "heading-4": `
+
+
+
+ `,
+ "rocket": `
+
+
+ `,
+ "section": `
+ `,
+ "upload": `
+
+ `,
+ "badge-turkish-lira": `
+
+ `,
+ "circle-arrow-down": `
+
+ `,
+ "chart-network": `
+
+
+
+
+
+ `,
+ "funnel-plus": `
+
+ `,
+ "key": `
+
+ `,
+ "receipt-euro": `
+
+ `,
+ "shield": ``,
+ "text-initial": `
+
+
+
+ `,
+ "clipboard-list": `
+
+
+
+
+ `,
+ "clock-3": `
+ `,
+ "image-minus": `
+
+
+ `,
+ "lamp-wall-down": `
+
+ `,
+ "panel-bottom": `
+ `,
+ "mountain": ``,
+ "spell-check": `
+
+ `,
+ "triangle-alert": `
+
+ `,
+ "circle-arrow-left": `
+
+ `,
+ "file-search-2": `
+
+
+ `,
+ "panel-left": `
+ `,
+ "cloud-moon": `
+ `,
+ "divide": `
+
+ `,
+ "gift": `
+
+
+ `,
+ "rotate-ccw-key": `
+
+
+
+ `,
+ "pc-case": `
+
+
+ `,
+ "airplay": `
+ `,
+ "calendar-arrow-up": `
+
+
+
+
+ `,
+ "grid-2x2-check": `
+ `,
+ "info": `
+
+ `,
+ "map-plus": `
+
+
+
+ `,
+ "microwave": `
+
+
+
+ `,
+ "redo-dot": `
+
+ `,
+ "circle-plus": `
+
+ `,
+ "equal-approximately": `
+ `,
+ "file-symlink": `
+
+ `,
+ "tree-palm": `
+
+
+ `,
+ "chevron-first": `
+ `,
+ "egg": ``,
+ "file-plus-2": `
+
+
+ `,
+ "heart-plus": `
+
+ `,
+ "music": `
+
+ `,
+ "sun-medium": `
+
+
+
+
+
+
+
+ `,
+ "tally-2": `
+ `,
+ "backpack": `
+
+
+
+ `,
+ "layout-grid": `
+
+
+ `,
+ "list-end": `
+
+
+
+ `,
+ "map-pin-house": `
+
+
+ `,
+ "arrow-up-from-dot": `
+
+ `,
+ "calendar-check": `
+
+
+
+ `,
+ "git-branch-plus": `
+
+
+
+
+ `,
+ "tv": `
+ `,
+ "cake-slice": `
+
+
+ `,
+ "file-digit": `
+
+
+
+ `,
+ "fire-extinguisher": `
+
+
+
+
+ `,
+ "map-pin-x": `
+
+
+ `,
+ "microchip": `
+
+
+
+
+
+
+
+
+
+ `,
+ "monitor-dot": `
+
+
+ `,
+ "plus": `
+ `,
+ "receipt-turkish-lira": `
+
+ `,
+ "book-audio": `
+
+
+ `,
+ "bring-to-front": `
+
+ `,
+ "calendar-days": `
+
+
+
+
+
+
+
+
+ `,
+ "component": `
+
+
+ `,
+ "file-spreadsheet": `
+
+
+
+
+ `,
+ "rewind": `
+ `,
+ "screen-share": `
+
+
+
+ `,
+ "search-code": `
+
+
+ `,
+ "list-filter-plus": `
+
+
+
+ `,
+ "tablets": `
+
+
+ `,
+ "torus": `
+ `,
+ "chart-no-axes-gantt": `
+
+ `,
+ "drill": `
+
+
+
+
+ `,
+ "file-minus-2": `
+
+ `,
+ "mail-open": `
+ `,
+ "map": `
+
+ `,
+ "scan-search": `
+
+
+
+
+ `,
+ "square-percent": `
+
+
+ `,
+ "test-tube-diagonal": `
+
+ `,
+ "calendar": `
+
+
+ `,
+ "calendar-1": `
+
+
+
+ `,
+ "decimals-arrow-left": `
+
+
+ `,
+ "gavel": `
+
+
+
+ `,
+ "mail-plus": `
+
+
+ `,
+ "wine-off": `
+
+
+
+ `,
+ "disc-2": `
+
+ `,
+ "file-plus": `
+
+
+ `,
+ "save": `
+
+ `,
+ "split": `
+
+
+ `,
+ "clipboard-minus": `
+
+ `,
+ "chart-no-axes-column-increasing": `
+
+ `,
+ "gauge": `
+ `,
+ "inspection-panel": `
+
+
+
+ `,
+ "arrow-big-up": ``,
+ "headphones": ``,
+ "medal": `
+
+
+
+
+ `,
+ "server-crash": `
+
+
+
+ `,
+ "squares-exclude": `
+ `,
+ "sun-snow": `
+
+
+
+
+
+
+
+
+
+ `,
+ "banknote-x": `
+
+
+
+
+ `,
+ "clock-4": `
+ `,
+ "fold-vertical": `
+
+
+
+
+
+
+ `,
+ "puzzle": ``,
+ "ticket-percent": `
+
+
+ `,
+ "lamp-desk": `
+
+
+ `,
+ "scroll": `
+ `,
+ "square-bottom-dashed-scissors": `
+
+
+
+
+
+
+ `,
+ "square-arrow-down-right": `
+
+ `,
+ "app-window": `
+
+
+ `,
+ "chevron-last": `
+ `,
+ "chart-bar-stacked": `
+
+
+
+ `,
+ "eraser": `
+ `,
+ "folder-search-2": `
+
+ `,
+ "slack": `
+
+
+
+
+
+
+ `,
+ "square-stack": `
+
+ `,
+ "badge-percent": `
+
+
+ `,
+ "brush-cleaning": `
+
+
+ `,
+ "copyright": `
+ `,
+ "headphone-off": `
+
+
+
+ `,
+ "scaling": `
+
+
+ `,
+ "shuffle": `
+
+
+
+ `,
+ "thumbs-down": `
+ `,
+ "venus-and-mars": `
+
+
+
+ `,
+ "copy-slash": `
+
+ `,
+ "file-volume": `
+
+
+ `,
+ "keyboard-music": `
+
+
+
+
+
+
+
+ `,
+ "megaphone": `
+
+ `,
+ "volleyball": `
+
+
+
+
+ `,
+ "alarm-smoke": `
+
+
+
+ `,
+ "battery-medium": `
+
+
+ `,
+ "bubbles": `
+
+
+ `,
+ "bug-off": `
+
+
+
+
+
+
+
+
+ `,
+ "monitor-stop": `
+
+
+ `,
+ "align-center-vertical": `
+
+
+
+ `,
+ "bookmark-plus": `
+
+ `,
+ "cpu": `
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ "type": `
+
+ `,
+ "clipboard-pen-line": `
+
+
+
+ `,
+ "user-round-plus": `
+
+
+ `,
+ "arrow-up-narrow-wide": `
+
+
+
+ `,
+ "blend": `
+ `,
+ "dock": `
+
+ `,
+ "flag": ``,
+ "utility-pole": `
+
+
+
+
+
+ `,
+ "calendar-x": `
+
+
+
+
+ `,
+ "car-taxi-front": `
+
+
+
+
+
+ `,
+ "folder-check": `
+ `,
+ "git-commit-horizontal": `
+
+ `,
+ "grid-3x2": `
+
+
+ `,
+ "magnet": `
+
+ `,
+ "save-all": `
+
+
+ `,
+ "square-parking-off": `
+
+
+
+ `,
+ "ligature": `
+
+
+
+ `,
+ "phone-incoming": `
+
+ `,
+ "receipt-indian-rupee": `
+
+
+ `,
+ "scan-face": `
+
+
+
+
+
+ `,
+ "touchpad": `
+
+ `,
+ "biceps-flexed": `
+
+ `,
+ "book-down": `
+
+ `,
+ "bookmark-minus": `
+ `,
+ "cake": `
+
+
+
+
+
+
+
+ `,
+ "mailbox": `
+
+
+ `,
+ "wallet": `
+ `,
+ "chart-column-big": `
+
+ `,
+ "message-circle-x": `
+
+ `,
+ "pencil-off": `
+
+
+ `,
+ "receipt-swiss-franc": `
+
+
+ `,
+ "notepad-text-dashed": `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ "tower-control": `
+
+
+
+
+
+ `,
+ "chart-column-increasing": `
+
+
+ `,
+ "book-a": `
+
+ `,
+ "arrow-big-left": ``,
+ "align-vertical-justify-center": `
+
+ `,
+ "battery-full": `
+
+
+
+ `,
+ "message-square-dashed": `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ "share-2": `
+
+
+
+ `,
+ "file-question-mark": `
+
+ `,
+ "folder-pen": `
+ `,
+ "list": `
+
+
+
+
+ `,
+ "navigation": ``,
+ "option": `
+ `,
+ "pipette": `
+
+ `,
+ "square-dashed-kanban": `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ "table-properties": `
+
+
+ `,
+ "bean": `
+ `,
+ "circle-check": `
+ `,
+ "group": `
+
+
+
+
+ `,
+ "memory-stick": `
+
+
+
+
+
+
+
+ `,
+ "message-circle-dashed": `
+
+
+
+
+
+
+ `,
+ "utensils-crossed": `
+
+
+ `,
+ "webcam": `
+
+
+ `,
+ "arrow-big-down-dash": `
+ `,
+ "book-open-text": `
+
+
+
+
+ `,
+ "feather": `
+
+ `,
+ "heart": ``,
+ "mail": `
+ `,
+ "text-quote": `
+
+
+ `,
+ "chart-bar-increasing": `
+
+
+ `,
+ "clock-fading": `
+
+
+
+
+ `,
+ "nut-off": `
+
+
+
+ `,
+ "panel-right-close": `
+
+ `,
+ "receipt": `
+
+ `,
+ "cat": `
+
+
+ `,
+ "arrow-big-up-dash": `
+ `,
+ "folder": ``,
+ "instagram": `
+
+ `,
+ "shapes": `
+
+ `,
+ "shirt": ``,
+ "arrow-down-to-line": `
+
+ `,
+ "chevrons-right-left": `
+ `,
+ "flip-horizontal": `
+
+
+
+
+ `,
+ "image": `
+
+ `,
+ "list-tree": `
+
+
+
+ `,
+ "luggage": `
+
+
+
+ `,
+ "university": `
+
+
+
+
+
+ `,
+ "wheat": `
+
+
+
+
+
+
+ `,
+ "dice-5": `
+
+
+
+
+ `,
+ "git-graph": `
+
+
+
+
+ `,
+ "image-up": `
+
+
+ `,
+ "shell": ``,
+ "arrow-right-to-line": `
+
+ `,
+ "gallery-vertical": `
+
+ `,
+ "list-chevrons-up-down": `
+
+
+
+ `,
+ "menu": `
+
+ `,
+ "panel-right-dashed": `
+
+
+
+ `,
+ "party-popper": `
+
+
+
+
+
+
+
+ `,
+ "scan-heart": `
+
+
+
+ `,
+ "settings": `
+ `,
+ "badge-plus": `
+
+ `,
+ "bell-plus": `
+
+
+ `,
+ "film": `
+
+
+
+
+
+
+ `,
+ "file-search": `
+
+
+ `,
+ "forklift": `
+
+
+ `,
+ "heading-3": `
+
+
+
+ `,
+ "iteration-ccw": `
+ `,
+ "list-chevrons-down-up": `
+
+
+
+ `,
+ "a-large-small": `
+
+
+ `,
+ "badge": ``,
+ "book-image": `
+
+ `,
+ "drum": `
+
+
+
+
+
+ `,
+ "equal-not": `
+
+ `,
+ "hand-platter": `
+
+
+
+
+ `,
+ "message-square-quote": `
+
+ `,
+ "remove-formatting": `
+
+
+
+ `,
+ "hammer": `
+
+ `,
+ "heading-1": `
+
+
+ `,
+ "layout-panel-left": `
+
+ `,
+ "notebook": `
+
+
+
+
+ `,
+ "panels-right-bottom": `
+
+ `,
+ "square-dashed": `
+
+
+
+
+
+
+
+
+
+
+ `,
+ "trello": `
+
+ `,
+ "truck": `
+
+
+
+ `,
+ "hand": `
+
+
+ `,
+ "rectangle-ellipsis": `
+
+
+ `,
+ "search": `
+ `,
+ "square-chart-gantt": `
+
+
+ `,
+ "tablet": `
+ `,
+ "tent-tree": `
+
+
+
+
+
+ `,
+ "video-off": `
+
+ `,
+ "rose": `
+
+
+
+ `,
+ "search-x": `
+
+
+ `,
+ "speaker": `
+
+
+ `,
+ "square-arrow-down-left": `
+
+ `,
+ "square-dashed-mouse-pointer": `
+
+
+
+
+
+
+
+
+ `,
+ "text-cursor": `
+
+ `,
+ "volume-1": `
+ `,
+ "arrow-up": `
+ `,
+ "chevrons-right": `
+ `,
+ "calendar-arrow-down": `
+
+
+
+
+ `,
+ "cylinder": `
+ `,
+ "flashlight-off": `
+
+
+ `,
+ "list-plus": `
+
+
+
+ `,
+ "lollipop": `
+
+ `,
+ "proportions": `
+
+ `,
+ "bow-arrow": `
+
+
+
+ `,
+ "cassette-tape": `
+
+
+
+ `,
+ "door-closed-locked": `
+
+
+
+ `,
+ "drama": `
+
+
+
+
+
+
+ `,
+ "gpu": `
+
+
+
+ `,
+ "list-check": `
+
+
+ `,
+ "nfc": `
+
+
+ `,
+ "square-chevron-down": `
+ `,
+ "atom": `
+
+ `,
+ "air-vent": `
+
+
+ `,
+ "arrow-down-0-1": `
+
+
+
+ `,
+ "citrus": `
+
+
+ `,
+ "folder-code": `
+
+ `,
+ "hat-glasses": `
+
+
+
+ `,
+ "message-circle-heart": `
+ `,
+ "message-square-reply": `
+
+ `,
+ "a-arrow-down": `
+
+
+ `,
+ "align-vertical-justify-start": `
+
+ `,
+ "droplet-off": `
+
+ `,
+ "mouse": `
+ `,
+ "move-left": `
+ `,
+ "rabbit": `
+
+
+
+ `,
+ "shield-ellipsis": `
+
+
+ `,
+ "tablet-smartphone": `
+
+ `,
+ "armchair": `
+
+
+ `,
+ "chart-line": `
+ `,
+ "calendar-heart": `
+
+
+
+ `,
+ "key-square": `
+
+ `,
+ "pencil-line": `
+
+ `,
+ "piggy-bank": `
+
+ `,
+ "user-check": `
+
+ `,
+ "sun-dim": `
+
+
+
+
+
+
+
+ `,
+ "bell-off": `
+
+
+ `,
+ "briefcase-conveyor-belt": `
+
+
+
+
+
+ `,
+ "computer": `
+
+
+ `,
+ "history": `
+
+ `,
+ "pickaxe": `
+
+
+ `,
+ "pointer": `
+
+
+
+ `,
+ "rail-symbol": `
+
+ `,
+ "signpost": `
+
+ `,
+ "align-center-horizontal": `
+
+
+
+ `,
+ "baby": `
+
+
+ `,
+ "chevron-right": ``,
+ "case-sensitive": `
+
+
+ `,
+ "list-indent-decrease": `
+
+
+ `,
+ "octagon-alert": `
+
+ `,
+ "rows-4": `
+
+
+ `,
+ "sparkle": ``,
+ "arrow-up-left": `
+ `,
+ "badge-minus": `
+ `,
+ "bluetooth-searching": `
+
+ `,
+ "book-marked": `
+ `,
+ "building-2": `
+
+
+
+
+
+ `,
+ "git-compare-arrows": `
+
+
+
+
+ `,
+ "plug": `
+
+
+ `,
+ "redo-2": `
+ `,
+ "between-vertical-start": `
+
+ `,
+ "clipboard-copy": `
+
+
+
+ `,
+ "files": `
+
+ `,
+ "fullscreen": `
+
+
+
+ `,
+ "heart-pulse": `
+ `,
+ "square-scissors": `
+
+
+
+
+ `,
+ "swords": `
+
+
+
+
+
+
+ `,
+ "timer": `
+
+ `,
+ "badge-x": `
+
+ `,
+ "chevron-left": ``,
+ "brain-circuit": `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ "corner-left-up": `
+ `,
+ "fish-symbol": ``,
+ "list-start": `
+
+
+
+ `,
+ "square-arrow-up-left": `
+
+ `,
+ "umbrella-off": `
+
+
+
+ `,
+ "biohazard": `
+
+
+
+
+
+
+
+
+ `,
+ "calculator": `
+
+
+
+
+
+
+
+
+ `,
+ "combine": `
+
+
+
+
+ `,
+ "dribbble": `
+
+
+ `,
+ "earth-lock": `
+
+
+
+
+ `,
+ "folder-sync": `
+
+
+
+ `,
+ "house-plug": `
+
+
+ `,
+ "slash": ``,
+ "square-library": `
+
+
+ `,
+ "square-menu": `
+
+
+ `,
+ "square-power": `
+
+ `,
+ "arrow-big-right-dash": `
+ `,
+ "roller-coaster": `
+
+
+
+
+
+ `,
+ "square-radical": `
+ `,
+ "square-square": `
+ `,
+ "tickets": `
+
+
+
+ `,
+ "wallpaper": `
+
+
+
+ `,
+ "message-square-code": `
+
+ `,
+ "move-horizontal": `
+
+ `,
+ "non-binary": `
+
+
+ `,
+ "receipt-text": `
+
+
+ `,
+ "square-arrow-up-right": `
+
+ `,
+ "bone": ``,
+ "heading-6": `
+
+
+
+ `,
+ "map-pin-x-inside": `
+
+ `,
+ "move-down-right": `
+ `,
+ "square-arrow-up": `
+
+ `,
+ "square-chevron-up": `
+ `,
+ "clipboard": `
+ `,
+ "diamond-plus": `
+
+ `,
+ "folder-down": `
+
+ `,
+ "file-terminal": `
+
+
+ `,
+ "git-compare": `
+
+
+ `,
+ "hamburger": `
+
+
+ `,
+ "list-ordered": `
+
+
+
+
+ `,
+ "salad": `
+
+
+
+ `,
+ "file-down": `
+
+
+ `,
+ "list-restart": `
+
+
+
+ `,
+ "message-square-heart": `
+ `,
+ "circle-power": `
+
+ `,
+ "shield-ban": `
+ `,
+ "user-pen": `
+
+ `,
+ "brick-wall-fire": `
+
+
+
+
+
+ `,
+ "circle-chevron-down": `
+ `,
+ "calendar-x-2": `
+
+
+
+
+ `,
+ "cast": `
+
+
+ `,
+ "croissant": `
+
+
+
+ `,
+ "list-indent-increase": `
+
+
+ `,
+ "mars-stroke": `
+
+
+ `,
+ "rectangle-goggles": ``,
+ "bed-double": `
+
+
+ `,
+ "arrow-up-1-0": `
+
+
+
+ `,
+ "circle-arrow-out-down-right": `
+
+ `,
+ "cooking-pot": `
+
+
+ `,
+ "equal": `
+ `,
+ "heading": `
+
+ `,
+ "map-pin-off": `
+
+
+
+ `,
+ "message-square-more": `
+
+
+ `,
+ "card-sim": `
+
+
+ `,
+ "castle": `
+
+
+
+
+
+
+ `,
+ "mouse-pointer-click": `
+
+
+
+ `,
+ "move-down": `
+ `,
+ "package-check": `
+
+
+
+ `,
+ "tickets-plane": `
+
+
+
+
+
+ `,
+ "webhook": `
+
+ `,
+ "bed": `
+
+
+ `,
+ "bold": ``,
+ "file-key": `
+
+
+ `,
+ "server": `
+
+
+ `,
+ "parentheses": `
+ `,
+ "signal-zero": ``,
+ "tally-5": `
+
+
+
+ `,
+ "ungroup": `
+ `,
+ "align-vertical-distribute-center": `
+
+
+
+
+ `,
+ "circle-divide": `
+
+
+ `,
+ "copy-minus": `
+
+ `,
+ "corner-left-down": `
+ `,
+ "frame": `
+
+
+ `,
+ "georgian-lari": `
+
+
+ `,
+ "hexagon": ``,
+ "laptop-minimal-check": `
+
+ `,
+ "arrows-up-from-line": `
+
+
+
+ `,
+ "cog": `
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ "file-sliders": `
+
+
+
+
+ `,
+ "hospital": `
+
+
+
+ `,
+ "logs": `
+
+
+
+
+
+
+
+ `,
+ "message-circle-code": `
+
+ `,
+ "palette": `
+
+
+
+ `,
+ "rotate-ccw-square": `
+
+ `,
+ "book-open-check": `
+
+ `,
+ "club": `
+ `,
+ "external-link": `
+
+ `,
+ "list-x": `
+
+
+
+ `,
+ "map-pin-plus-inside": `
+
+ `,
+ "message-square-dot": `
+ `,
+ "rotate-ccw": `
+ `,
+ "shield-off": `
+
+ `,
+ "chevrons-up-down": `
+ `,
+ "disc": `
+ `,
+ "ellipsis": `
+
+ `,
+ "monitor-cog": `
+
+
+
+
+
+
+
+
+
+
+ `,
+ "monitor-off": `
+
+
+
+ `,
+ "rectangle-horizontal": ``,
+ "school": `
+
+
+
+
+ `,
+ "shopping-basket": `
+
+
+
+
+
+ `,
+ "images": `
+
+
+ `,
+ "pointer-off": `
+
+
+
+
+ `,
+ "scale": `
+
+
+
+ `,
+ "wand-sparkles": `
+
+
+
+
+
+
+ `,
+ "waypoints": `
+
+
+
+
+
+ `,
+ "badge-alert": `
+
+ `,
+ "chart-area": `
+ `,
+ "folder-cog": `
+
+
+
+
+
+
+
+
+ `,
+ "message-square-share": `
+
+ `,
+ "music-2": `
+ `,
+ "panel-top-dashed": `
+
+
+
+ `,
+ "pyramid": `
+ `,
+ "stretch-vertical": `
+ `,
+ "chart-pie": `
+ `,
+ "file-diff": `
+
+
+ `,
+ "flip-horizontal-2": `
+
+
+
+
+ `,
+ "text-align-justify": `
+
+ `,
+ "wifi-high": `
+
+ `,
+ "zap": ``,
+ "swatch-book": `
+
+
+ `,
+ "undo": `
+ `,
+ "flame-kindling": `
+
+ `,
+ "message-circle-warning": `
+
+ `,
+ "minus": ``,
+ "strikethrough": `
+
+ `,
+ "zoom-out": `
+
+ `,
+ "badge-japanese-yen": `
+
+
+
+ `,
+ "bookmark-check": `
+ `,
+ "repeat-2": `
+
+
+ `,
+ "terminal": `
+ `,
+ "tree-deciduous": `
+ `,
+ "book-open": `
+ `,
+ "clock-12": `
+ `,
+ "corner-right-up": `
+ `,
+ "calendar-minus-2": `
+
+
+
+ `,
+ "ev-charger": `
+
+
+
+ `,
+ "folder-minus": `
+ `,
+ "infinity": ``,
+ "move-diagonal-2": `
+
+ `,
+ "book-up": `
+
+ `,
+ "file-text": `
+
+
+
+ `,
+ "loader-pinwheel": `
+
+
+ `,
+ "parking-meter": `
+
+
+
+ `,
+ "smartphone": `
+ `,
+ "unfold-horizontal": `
+
+
+
+
+
+
+ `,
+ "unlink": `
+
+
+
+
+ `,
+ "user": `
+ `,
+ "book-minus": `
+ `,
+ "printer-check": `
+
+
+ `,
+ "text-search": `
+
+
+
+ `,
+ "traffic-cone": `
+
+
+ `,
+ "variable": `
+
+
+ `,
+ "watch": `
+
+
+ `,
+ "file-key-2": `
+
+
+
+ `,
+ "joystick": `
+
+
+ `,
+ "square-sigma": `
+ `,
+ "table-cells-split": `
+
+
+ `,
+ "table": `
+
+
+ `,
+ "barrel": `
+
+
+
+ `,
+ "bluetooth": ``,
+ "clock-arrow-down": `
+
+
+ `,
+ "gallery-thumbnails": `
+
+
+
+ `,
+ "music-3": `
+ `,
+ "shrink": `
+
+
+ `,
+ "thumbs-up": `
+ `,
+ "braces": `
+ `,
+ "brain": `
+
+
+
+
+
+
+ `,
+ "link": `
+ `,
+ "paw-print": `
+
+
+ `,
+ "workflow": `
+
+ `,
+ "circle": ``,
+ "circle-slash": `
+ `,
+ "flask-conical": `
+
+ `,
+ "hand-helping": `
+
+ `,
+ "loader-circle": ``,
+ "smile-plus": `
+
+
+
+
+ `,
+ "sparkles": `
+
+
+ `,
+ "square-stop": `
+ `,
+ "triangle-right": ``,
+ "triangle": ``,
+ "wallet-minimal": `
+ `,
+ "dessert": `
+
+ `,
+ "calendar-plus": `
+
+
+
+
+ `,
+ "mail-question-mark": `
+
+
+ `,
+ "map-pin-minus": `
+
+ `,
+ "beef": `
+
+ `,
+ "languages": `
+
+
+
+
+ `,
+ "mail-search": `
+
+
+
+ `,
+ "maximize-2": `
+
+
+ `,
+ "message-circle-more": `
+
+
+ `,
+ "pin-off": `
+
+
+ `,
+ "arrow-down-wide-narrow": `
+
+
+
+ `,
+ "archive": `
+
+ `,
+ "carrot": `
+
+ `,
+ "fingerprint": `
+
+
+
+
+
+
+
+ `,
+ "panel-top-open": `
+
+ `,
+ "receipt-pound-sterling": `
+
+
+ `,
+ "square-activity": `
+ `,
+ "door-closed": `
+
+ `,
+ "facebook": ``,
+ "goal": `
+
+ `,
+ "network": `
+
+
+
+ `,
+ "toilet": `
+ `,
+ "cloud-snow": `
+
+
+
+
+
+ `,
+ "notepad-text": `
+
+
+
+
+
+ `,
+ "refresh-cw": `
+
+
+ `,
+ "alarm-clock-check": `
+
+
+
+
+ `,
+ "arrow-down": `
+ `,
+ "lamp": `
+
+ `,
+ "theater": `
+
+
+
+
+
+
+
+ `,
+ "truck-electric": `
+
+
+
+
+
+ `,
+ "user-round-minus": `
+
+ `,
+ "anchor": `
+
+ `,
+ "circle-off": `
+
+ `,
+ "dog": `
+
+
+
+ `,
+ "laugh": `
+
+
+ `,
+ "lock-keyhole": `
+
+ `,
+ "map-pin-check": `
+
+ `,
+ "mouse-pointer": `
+ `,
+ "package-search": `
+
+
+
+
+ `,
+ "align-end-horizontal": `
+
+ `,
+ "badge-cent": `
+
+ `,
+ "book-copy": `
+
+ `,
+ "bot": `
+
+
+
+
+ `,
+ "clipboard-clock": `
+
+
+
+ `,
+ "cloud-moon-rain": `
+
+
+ `,
+ "factory": `
+
+
+ `,
+ "settings-2": `
+
+
+ `,
+ "git-pull-request-create": `
+
+
+
+ `,
+ "piano": `
+
+
+
+
+ `,
+ "power": `
+ `,
+ "scroll-text": `
+
+
+ `,
+ "square-play": `
+ `,
+ "text-align-center": `
+
+ `,
+ "ticket-check": `
+ `,
+ "thermometer-sun": `
+
+
+
+
+ `,
+ "container": `
+
+
+
+ `,
+ "git-pull-request": `
+
+
+ `,
+ "house-heart": `
+ `,
+ "separator-horizontal": `
+
+ `,
+ "shield-minus": `
+ `,
+ "shower-head": `
+
+
+
+
+
+
+
+
+ `,
+ "signal": `
+
+
+
+ `,
+ "sunrise": `
+
+
+
+
+
+
+ `,
+ "arrow-right": `
+ `,
+ "file-badge-2": `
+
+ `,
+ "house-wifi": `
+
+
+ `,
+ "square-dashed-bottom-code": `
+
+
+
+ `,
+ "user-cog": `
+
+
+
+
+
+
+
+
+
+ `,
+ "chevrons-left": `
+ `,
+ "arrow-left-right": `
+
+
+ `,
+ "brackets": `
+ `,
+ "flip-vertical-2": `
+
+
+
+
+ `,
+ "file-stack": `
+
+ `,
+ "folder-key": `
+
+
+ `,
+ "locate-off": `
+
+
+
+
+
+ `,
+ "move-3d": `
+
+
+ `,
+ "arrow-down-a-z": `
+
+
+
+ `,
+ "file-image": `
+
+
+ `,
+ "fish-off": `
+
+ `,
+ "heading-2": `
+
+
+ `,
+ "headset": `
+ `,
+ "navigation-2-off": `
+
+ `,
+ "package-x": `
+
+
+
+ `,
+ "pilcrow-left": `
+
+
+
+ `,
+ "circle-play": `
+ `,
+ "git-fork": `
+
+
+
+ `,
+ "guitar": `
+
+
+ `,
+ "heart-minus": `
+ `,
+ "house": `
+ `,
+ "target": `
+
+ `,
+ "test-tube": `
+
+ `,
+ "trash-2": `
+
+
+
+ `,
+ "arrow-right-from-line": `
+
+ `,
+ "candy-off": `
+
+
+
+
+
+ `,
+ "clipboard-x": `
+
+
+ `,
+ "dice-1": `
+ `,
+ "file-warning": `
+
+ `,
+ "fuel": `
+
+
+ `,
+ "git-merge": `
+
+ `,
+ "mic": `
+
+ `,
+ "file-volume-2": `
+
+
+
+ `,
+ "sandwich": `
+
+
+
+ `,
+ "ship": `
+
+
+
+ `,
+ "step-forward": `
+ `,
+ "telescope": `
+
+
+
+
+
+ `,
+ "train-front": `
+
+
+
+
+ `,
+ "umbrella": `
+
+ `,
+ "undo-2": `
+ `,
+ "circle-user": `
+
+ `,
+ "scan-eye": `
+
+
+
+
+ `,
+ "server-off": `
+
+
+
+
+ `,
+ "superscript": `
+
+ `,
+ "toggle-left": `
+ `,
+ "triangle-dashed": `
+
+
+
+
+
+
+
+ `,
+ "user-round-x": `
+
+
+ `,
+ "user-search": `
+
+
+ `,
+ "calendar-range": `
+
+
+
+
+
+
+ `,
+ "blinds": `
+
+
+
+
+
+ `,
+ "circle-user-round": `
+
+ `,
+ "framer": ``,
+ "image-upscale": `
+
+
+
+
+
+
+ `,
+ "kanban": `
+
+ `,
+ "map-pin-pen": `
+
+ `,
+ "mars": `
+
+ `,
+ "asterisk": `
+
+ `,
+ "codepen": `
+
+
+
+ `,
+ "file-box": `
+
+
+
+ `,
+ "loader": `
+
+
+
+
+
+
+ `,
+ "orbit": `
+
+
+
+ `,
+ "ruler-dimension-line": `
+
+
+
+
+
+ `,
+ "square-split-horizontal": `
+
+ `,
+ "sun-moon": `
+
+
+
+ `,
+ "align-vertical-distribute-start": `
+
+
+ `,
+ "bot-message-square": `
+
+
+
+
+ `,
+ "circle-dot-dashed": `
+
+
+
+
+
+
+
+ `,
+ "life-buoy": `
+
+
+
+
+ `,
+ "message-square-x": `
+
+ `,
+ "printer": `
+
+ `,
+ "route": `
+
+ `,
+ "stamp": `
+
+ `,
+ "dice-2": `
+
+ `,
+ "fence": `
+
+
+
+
+
+ `,
+ "file-chart-pie": `
+
+
+ `,
+ "layout-template": `
+
+ `,
+ "pill-bottle": `
+
+ `,
+ "ticket-minus": `
+ `,
+ "trending-down": `
+ `,
+ "unplug": `
+
+
+
+
+ `,
+ "baggage-claim": `
+
+
+
+ `,
+ "clipboard-paste": `
+
+
+
+ `,
+ "calendar-off": `
+
+
+
+
+ `,
+ "heater": `
+
+
+
+
+
+
+
+
+ `,
+ "mail-minus": `
+
+ `,
+ "plane-takeoff": `
+ `,
+ "recycle": `
+
+
+
+
+ `,
+ "scan-barcode": `
+
+
+
+
+
+ `,
+ "drafting-compass": `
+
+
+
+ `,
+ "scale-3d": `
+
+
+ `,
+ "square-pi": `
+
+
+ `,
+ "squares-unite": ``,
+ "wifi-cog": `
+
+
+
+
+
+
+
+
+
+
+ `,
+ "wrench": ``,
+ "arrow-down-left": `
+ `,
+ "forward": `
+ `,
+ "pencil": `
+ `,
+ "pilcrow": `
+
+ `,
+ "rows-3": `
+
+ `,
+ "shield-plus": `
+
+ `,
+ "smartphone-charging": `
+ `,
+ "sticker": `
+
+
+
+ `,
+ "bell-minus": `
+
+ `,
+ "circle-gauge": `
+
+ `,
+ "clock-11": `
+ `,
+ "lock-open": `
+ `,
+ "rocking-chair": `
+
+
+ `,
+ "zap-off": `
+
+
+ `,
+ "wheat-off": `
+
+
+
+
+
+
+
+
+ `,
+ "beer": `
+
+
+
+ `,
+ "bus": `
+
+
+
+
+
+ `,
+ "corner-up-right": `
+ `,
+ "euro": `
+
+ `,
+ "file-audio-2": `
+
+
+
+ `,
+ "flag-triangle-left": ``,
+ "message-square-text": `
+
+
+ `,
+ "milk": `
+
+ `,
+ "book-check": `
+ `,
+ "cloud-fog": `
+
+ `,
+ "download": `
+
+ `,
+ "file": `
+ `,
+ "folders": `
+ `,
+ "gallery-vertical-end": `
+
+ `,
+ "hard-drive-upload": `
+
+
+
+ `,
+ "lamp-wall-up": `
+
+ `,
+ "badge-russian-ruble": `
+
+ `,
+ "circuit-board": `
+
+
+
+ `,
+ "contrast": `
+ `,
+ "book-up-2": `
+
+
+
+ `,
+ "git-commit-vertical": `
+
+ `,
+ "ice-cream-cone": `
+
+ `,
+ "image-play": `
+
+
+ `,
+ "paperclip": ``,
+ "circle-minus": `
+ `,
+ "fast-forward": `
+ `,
+ "inbox": `
+ `,
+ "podcast": `
+
+
+ `,
+ "radio-receiver": `
+
+
+ `,
+ "ratio": `
+ `,
+ "sigma": ``,
+ "sprout": `
+
+ `,
+ "alarm-clock-plus": `
+
+
+
+
+
+ `,
+ "message-circle": ``,
+ "square-asterisk": `
+
+
+ `,
+ "square-round-corner": `
+ `,
+ "square-user-round": `
+
+ `,
+ "trees": `
+
+
+ `,
+ "venus": `
+
+ `,
+ "bell-dot": `
+
+ `,
+ "binoculars": `
+
+
+
+
+ `,
+ "file-axis-3d": `
+
+
+ `,
+ "message-square-diff": `
+
+
+ `,
+ "star-half": ``,
+ "table-cells-merge": `
+
+
+
+ `,
+ "table-of-contents": `
+
+
+
+
+ `,
+ "align-start-horizontal": `
+
+ `,
+ "train-front-tunnel": `
+
+
+
+
+
+ `,
+ "trending-up": `
+ `,
+ "accessibility": `
+
+
+
+ `,
+ "bolt": `
+ `,
+ "chart-bar": `
+
+
+ `,
+ "hand-heart": `
+
+
+ `,
+ "image-off": `
+
+
+
+
+ `,
+ "octagon-x": `
+
+ `,
+ "soup": `
+
+
+
+
+ `,
+ "square-minus": `
+ `,
+ "align-vertical-space-between": `
+
+
+ `,
+ "candy": `
+
+
+
+ `,
+ "link-2-off": `
+
+
+ `,
+ "monitor": `
+
+ `,
+ "radio": `
+
+
+
+ `,
+ "scan": `
+
+
+ `,
+ "sheet": `
+
+
+
+ `,
+ "spotlight": `
+
+
+
+ `,
+ "coffee": `
+
+
+ `,
+ "ghost": `
+
+ `,
+ "laptop": `
+ `,
+ "navigation-2": ``,
+ "twitter": ``,
+ "wine": `
+
+
+ `,
+ "badge-question-mark": `
+
+ `,
+ "check-line": `
+
+ `,
+ "door-open": `
+
+
+
+ `,
+ "pocket": `
+ `,
+ "separator-vertical": `
+
+ `,
+ "square": ``,
+ "store": `
+
+ `,
+ "toy-brick": `
+
+ `,
+ "align-horizontal-distribute-end": `
+
+
+ `,
+ "book-plus": `
+
+ `,
+ "circle-fading-plus": `
+
+
+
+
+
+ `,
+ "credit-card": `
+ `,
+ "cloudy": `
+ `,
+ "folder-open": ``,
+ "hand-coins": `
+
+
+
+ `,
+ "rat": `
+
+
+
+ `,
+ "between-vertical-end": `
+
+ `,
+ "book-headphones": `
+
+
+ `,
+ "chart-no-axes-column-decreasing": `
+
+ `,
+ "code-xml": `
+
+ `,
+ "copy": `
+ `,
+ "dices": `
+
+
+
+
+ `,
+ "haze": `
+
+
+
+
+
+
+ `,
+ "package-open": `
+
+
+ `,
+ "hard-drive-download": `
+
+
+
+ `,
+ "map-minus": `
+
+
+ `,
+ "rectangle-circle": `
+ `,
+ "square-parking": `
+ `,
+ "table-2": ``,
+ "tally-3": `
+
+ `,
+ "trending-up-down": `
+
+
+ `,
+ "waves": `
+
+ `,
+ "chevrons-up": `
+ `,
+ "circle-chevron-right": `
+ `,
+ "file-clock": `
+
+
+ `,
+ "pen-line": `
+ `,
+ "send-horizontal": `
+ `,
+ "square-dashed-top-solid": `
+
+
+
+
+
+
+
+ `,
+ "star-off": `
+
+ `,
+ "circle-star": `
+ `,
+ "file-music": `
+
+
+ `,
+ "message-circle-reply": `
+
+ `,
+ "messages-square": `
+ `,
+ "navigation-off": `
+
+ `,
+ "utensils": `
+
+ `,
+ "a-arrow-up": `
+
+
+ `,
+ "circle-arrow-out-down-left": `
+
+ `,
+ "folder-dot": `
+ `,
+ "log-out": `
+
+ `,
+ "message-square-off": `
+
+ `,
+ "transgender": `
+
+
+
+
+
+
+ `,
+ "warehouse": `
+
+
+ `,
+ "diamond-percent": `
+
+
+ `,
+ "diamond-minus": `
+ `,
+ "keyboard": `
+
+
+
+
+
+
+
+ `,
+ "thermometer-snowflake": `
+
+
+
+
+
+
+ `,
+ "between-horizontal-start": `
+
+ `,
+ "align-end-vertical": `
+
+ `,
+ "figma": `
+
+
+
+ `,
+ "layers": `
+
+ `,
+ "martini": `
+
+ `,
+ "rotate-cw-square": `
+
+ `,
+ "square-pen": `
+ `,
+ "ticket-x": `
+
+ `,
+ "antenna": `
+
+
+
+
+ `,
+ "cloud-upload": `
+
+ `,
+ "indian-rupee": `
+
+
+
+ `,
+ "leaf": `
+ `,
+ "cctv": `
+
+
+
+ `,
+ "space": ``,
+ "sticky-note": `
+ `,
+ "dice-4": `
+
+
+
+ `,
+ "file-audio": `
+
+ `,
+ "id-card-lanyard": `
+
+
+
+ `,
+ "move-down-left": `
+ `,
+ "swiss-franc": `
+
+ `,
+ "banknote-arrow-down": `
+
+
+
+
+ `,
+ "bandage": `
+
+
+
+
+
+ `,
+ "circle-slash-2": `
+ `,
+ "corner-right-down": `
+ `,
+ "dollar-sign": `
+ `,
+ "gamepad-2": `
+
+
+
+ `,
+ "construction": `
+
+
+
+
+
+
+ `,
+ "funnel-x": `
+
+ `,
+ "newspaper": `
+
+
+ `,
+ "shield-user": `
+
+ `,
+ "trophy": `
+
+
+
+
+ `,
+ "unlink-2": ``,
+ "arrow-down-up": `
+
+
+ `,
+ "book-lock": `
+
+
+ `,
+ "circle-check-big": `
+ `,
+}
diff --git a/internal/ui/components/icon/icon_defs.go b/internal/ui/components/icon/icon_defs.go
new file mode 100644
index 0000000..f6c82d9
--- /dev/null
+++ b/internal/ui/components/icon/icon_defs.go
@@ -0,0 +1,1641 @@
+// templui component icon - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/icon
+package icon
+// This file is auto generated
+// Using Lucide icons version 0.544.0
+var AlarmClockOff = Icon("alarm-clock-off")
+var AArrowDown = Icon("a-arrow-down")
+var ALargeSmall = Icon("a-large-small")
+var AArrowUp = Icon("a-arrow-up")
+var AlarmClock = Icon("alarm-clock")
+var AirVent = Icon("air-vent")
+var Activity = Icon("activity")
+var AlarmClockCheck = Icon("alarm-clock-check")
+var Airplay = Icon("airplay")
+var AlarmClockMinus = Icon("alarm-clock-minus")
+var Accessibility = Icon("accessibility")
+var ArrowUp = Icon("arrow-up")
+var ArrowDownNarrowWide = Icon("arrow-down-narrow-wide")
+var ArrowDownRight = Icon("arrow-down-right")
+var ArrowDownToDot = Icon("arrow-down-to-dot")
+var ArrowDownToLine = Icon("arrow-down-to-line")
+var Ampersands = Icon("ampersands")
+var AlarmSmoke = Icon("alarm-smoke")
+var Album = Icon("album")
+var AlignCenterHorizontal = Icon("align-center-horizontal")
+var AlignCenterVertical = Icon("align-center-vertical")
+var AlignEndHorizontal = Icon("align-end-horizontal")
+var ArrowDownLeft = Icon("arrow-down-left")
+var AlignEndVertical = Icon("align-end-vertical")
+var AlignHorizontalDistributeCenter = Icon("align-horizontal-distribute-center")
+var AlignHorizontalDistributeEnd = Icon("align-horizontal-distribute-end")
+var AlignHorizontalDistributeStart = Icon("align-horizontal-distribute-start")
+var AlignHorizontalJustifyCenter = Icon("align-horizontal-justify-center")
+var AlignHorizontalJustifyEnd = Icon("align-horizontal-justify-end")
+var AlignHorizontalJustifyStart = Icon("align-horizontal-justify-start")
+var AlignHorizontalSpaceAround = Icon("align-horizontal-space-around")
+var AlignHorizontalSpaceBetween = Icon("align-horizontal-space-between")
+var AlignStartHorizontal = Icon("align-start-horizontal")
+var AlignStartVertical = Icon("align-start-vertical")
+var AlignVerticalDistributeCenter = Icon("align-vertical-distribute-center")
+var AlignVerticalDistributeEnd = Icon("align-vertical-distribute-end")
+var AlignVerticalDistributeStart = Icon("align-vertical-distribute-start")
+var AlignVerticalJustifyCenter = Icon("align-vertical-justify-center")
+var AlignVerticalJustifyEnd = Icon("align-vertical-justify-end")
+var AlignVerticalJustifyStart = Icon("align-vertical-justify-start")
+var Amphora = Icon("amphora")
+var Anchor = Icon("anchor")
+var AlignVerticalSpaceAround = Icon("align-vertical-space-around")
+var AlignVerticalSpaceBetween = Icon("align-vertical-space-between")
+var Ambulance = Icon("ambulance")
+var Ampersand = Icon("ampersand")
+var ArrowDownUp = Icon("arrow-down-up")
+var AlarmClockPlus = Icon("alarm-clock-plus")
+var ArrowUp10 = Icon("arrow-up-1-0")
+var ArrowLeftToLine = Icon("arrow-left-to-line")
+var Angry = Icon("angry")
+var ArrowDownWideNarrow = Icon("arrow-down-wide-narrow")
+var ArrowUpAZ = Icon("arrow-up-a-z")
+var ArrowUpDown = Icon("arrow-up-down")
+var ArrowUpFromDot = Icon("arrow-up-from-dot")
+var ArrowUpFromLine = Icon("arrow-up-from-line")
+var ArrowBigDownDash = Icon("arrow-big-down-dash")
+var Annoyed = Icon("annoyed")
+var Antenna = Icon("antenna")
+var Anvil = Icon("anvil")
+var Aperture = Icon("aperture")
+var AppWindowMac = Icon("app-window-mac")
+var AppWindow = Icon("app-window")
+var Apple = Icon("apple")
+var ArchiveRestore = Icon("archive-restore")
+var ArchiveX = Icon("archive-x")
+var Archive = Icon("archive")
+var Armchair = Icon("armchair")
+var ArrowRightLeft = Icon("arrow-right-left")
+var ArrowLeft = Icon("arrow-left")
+var ArrowRightFromLine = Icon("arrow-right-from-line")
+var ArrowBigDown = Icon("arrow-big-down")
+var ArrowBigUp = Icon("arrow-big-up")
+var ArrowDown01 = Icon("arrow-down-0-1")
+var ArrowDown10 = Icon("arrow-down-1-0")
+var ArrowDownAZ = Icon("arrow-down-a-z")
+var ArrowDownFromLine = Icon("arrow-down-from-line")
+var ArrowBigLeft = Icon("arrow-big-left")
+var ArrowBigLeftDash = Icon("arrow-big-left-dash")
+var ArrowUpLeft = Icon("arrow-up-left")
+var ArrowUpNarrowWide = Icon("arrow-up-narrow-wide")
+var ArrowUpRight = Icon("arrow-up-right")
+var ArrowUpToLine = Icon("arrow-up-to-line")
+var ArrowUpWideNarrow = Icon("arrow-up-wide-narrow")
+var ArrowUpZA = Icon("arrow-up-z-a")
+var ArrowBigRightDash = Icon("arrow-big-right-dash")
+var ArrowLeftFromLine = Icon("arrow-left-from-line")
+var ArrowDown = Icon("arrow-down")
+var ArrowBigRight = Icon("arrow-big-right")
+var ArrowLeftRight = Icon("arrow-left-right")
+var BookOpenCheck = Icon("book-open-check")
+var ArrowsUpFromLine = Icon("arrows-up-from-line")
+var Asterisk = Icon("asterisk")
+var AtSign = Icon("at-sign")
+var Atom = Icon("atom")
+var AudioLines = Icon("audio-lines")
+var AudioWaveform = Icon("audio-waveform")
+var Award = Icon("award")
+var Axe = Icon("axe")
+var Axis3d = Icon("axis-3d")
+var Baby = Icon("baby")
+var Beef = Icon("beef")
+var Backpack = Icon("backpack")
+var BadgeAlert = Icon("badge-alert")
+var BadgeCent = Icon("badge-cent")
+var BadgeCheck = Icon("badge-check")
+var BadgeDollarSign = Icon("badge-dollar-sign")
+var BadgeEuro = Icon("badge-euro")
+var BadgeIndianRupee = Icon("badge-indian-rupee")
+var BadgeInfo = Icon("badge-info")
+var BadgeJapaneseYen = Icon("badge-japanese-yen")
+var BadgeMinus = Icon("badge-minus")
+var BadgePercent = Icon("badge-percent")
+var BadgePlus = Icon("badge-plus")
+var BadgePoundSterling = Icon("badge-pound-sterling")
+var BadgeQuestionMark = Icon("badge-question-mark")
+var BadgeRussianRuble = Icon("badge-russian-ruble")
+var BadgeSwissFranc = Icon("badge-swiss-franc")
+var BadgeTurkishLira = Icon("badge-turkish-lira")
+var BadgeX = Icon("badge-x")
+var Badge = Icon("badge")
+var BaggageClaim = Icon("baggage-claim")
+var Ban = Icon("ban")
+var Banana = Icon("banana")
+var Bandage = Icon("bandage")
+var BanknoteArrowDown = Icon("banknote-arrow-down")
+var BanknoteArrowUp = Icon("banknote-arrow-up")
+var BanknoteX = Icon("banknote-x")
+var Banknote = Icon("banknote")
+var Barcode = Icon("barcode")
+var Barrel = Icon("barrel")
+var Baseline = Icon("baseline")
+var Bath = Icon("bath")
+var BatteryCharging = Icon("battery-charging")
+var BatteryFull = Icon("battery-full")
+var BatteryLow = Icon("battery-low")
+var BatteryMedium = Icon("battery-medium")
+var BatteryPlus = Icon("battery-plus")
+var BatteryWarning = Icon("battery-warning")
+var Battery = Icon("battery")
+var Beaker = Icon("beaker")
+var BeanOff = Icon("bean-off")
+var Bean = Icon("bean")
+var BedDouble = Icon("bed-double")
+var BedSingle = Icon("bed-single")
+var Bed = Icon("bed")
+var CalendarRange = Icon("calendar-range")
+var BookOpenText = Icon("book-open-text")
+var BookOpen = Icon("book-open")
+var BookPlus = Icon("book-plus")
+var BookText = Icon("book-text")
+var BookType = Icon("book-type")
+var BeerOff = Icon("beer-off")
+var Beer = Icon("beer")
+var BellDot = Icon("bell-dot")
+var BellElectric = Icon("bell-electric")
+var BellMinus = Icon("bell-minus")
+var BellOff = Icon("bell-off")
+var BellPlus = Icon("bell-plus")
+var BellRing = Icon("bell-ring")
+var Bell = Icon("bell")
+var BetweenHorizontalEnd = Icon("between-horizontal-end")
+var BetweenHorizontalStart = Icon("between-horizontal-start")
+var BetweenVerticalEnd = Icon("between-vertical-end")
+var BetweenVerticalStart = Icon("between-vertical-start")
+var BicepsFlexed = Icon("biceps-flexed")
+var Bike = Icon("bike")
+var Binary = Icon("binary")
+var Binoculars = Icon("binoculars")
+var Biohazard = Icon("biohazard")
+var Bird = Icon("bird")
+var Bitcoin = Icon("bitcoin")
+var Blend = Icon("blend")
+var Blinds = Icon("blinds")
+var Blocks = Icon("blocks")
+var BluetoothConnected = Icon("bluetooth-connected")
+var BluetoothOff = Icon("bluetooth-off")
+var BluetoothSearching = Icon("bluetooth-searching")
+var Bluetooth = Icon("bluetooth")
+var Bold = Icon("bold")
+var Bolt = Icon("bolt")
+var Bomb = Icon("bomb")
+var Bone = Icon("bone")
+var BookA = Icon("book-a")
+var BookAlert = Icon("book-alert")
+var BookAudio = Icon("book-audio")
+var BookCheck = Icon("book-check")
+var BookCopy = Icon("book-copy")
+var BookDashed = Icon("book-dashed")
+var BookDown = Icon("book-down")
+var BookHeadphones = Icon("book-headphones")
+var BookHeart = Icon("book-heart")
+var BookImage = Icon("book-image")
+var BookKey = Icon("book-key")
+var BookLock = Icon("book-lock")
+var BookMarked = Icon("book-marked")
+var BookMinus = Icon("book-minus")
+var BringToFront = Icon("bring-to-front")
+var BookUp = Icon("book-up")
+var ArrowUp01 = Icon("arrow-up-0-1")
+var ArrowRightToLine = Icon("arrow-right-to-line")
+var ArrowRight = Icon("arrow-right")
+var CircleDivide = Icon("circle-divide")
+var Check = Icon("check")
+var ChefHat = Icon("chef-hat")
+var Cherry = Icon("cherry")
+var ChevronDown = Icon("chevron-down")
+var ChevronFirst = Icon("chevron-first")
+var ChevronLast = Icon("chevron-last")
+var ChevronLeft = Icon("chevron-left")
+var ChevronRight = Icon("chevron-right")
+var ChevronUp = Icon("chevron-up")
+var ChevronsDownUp = Icon("chevrons-down-up")
+var ChevronsDown = Icon("chevrons-down")
+var ChevronsLeftRightEllipsis = Icon("chevrons-left-right-ellipsis")
+var ChevronsLeftRight = Icon("chevrons-left-right")
+var ChevronsLeft = Icon("chevrons-left")
+var ChevronsRightLeft = Icon("chevrons-right-left")
+var ChevronsRight = Icon("chevrons-right")
+var ChevronsUpDown = Icon("chevrons-up-down")
+var ChevronsUp = Icon("chevrons-up")
+var Chromium = Icon("chromium")
+var Church = Icon("church")
+var CigaretteOff = Icon("cigarette-off")
+var Cigarette = Icon("cigarette")
+var CircleAlert = Icon("circle-alert")
+var CircleArrowDown = Icon("circle-arrow-down")
+var CircleArrowLeft = Icon("circle-arrow-left")
+var CircleArrowOutDownLeft = Icon("circle-arrow-out-down-left")
+var BookUser = Icon("book-user")
+var BookX = Icon("book-x")
+var Book = Icon("book")
+var BookmarkCheck = Icon("bookmark-check")
+var BookmarkMinus = Icon("bookmark-minus")
+var BookmarkPlus = Icon("bookmark-plus")
+var BookmarkX = Icon("bookmark-x")
+var Bookmark = Icon("bookmark")
+var BoomBox = Icon("boom-box")
+var BotMessageSquare = Icon("bot-message-square")
+var BotOff = Icon("bot-off")
+var Bot = Icon("bot")
+var BottleWine = Icon("bottle-wine")
+var BowArrow = Icon("bow-arrow")
+var Box = Icon("box")
+var Boxes = Icon("boxes")
+var Braces = Icon("braces")
+var Brackets = Icon("brackets")
+var BrainCircuit = Icon("brain-circuit")
+var BrainCog = Icon("brain-cog")
+var Brain = Icon("brain")
+var BrickWallFire = Icon("brick-wall-fire")
+var BrickWallShield = Icon("brick-wall-shield")
+var BrickWall = Icon("brick-wall")
+var BriefcaseBusiness = Icon("briefcase-business")
+var BriefcaseConveyorBelt = Icon("briefcase-conveyor-belt")
+var BriefcaseMedical = Icon("briefcase-medical")
+var Briefcase = Icon("briefcase")
+var Clock1 = Icon("clock-1")
+var CircleArrowOutDownRight = Icon("circle-arrow-out-down-right")
+var CircleArrowOutUpLeft = Icon("circle-arrow-out-up-left")
+var CircleArrowOutUpRight = Icon("circle-arrow-out-up-right")
+var CircleArrowRight = Icon("circle-arrow-right")
+var CircleArrowUp = Icon("circle-arrow-up")
+var CircleCheckBig = Icon("circle-check-big")
+var CircleCheck = Icon("circle-check")
+var CircleChevronDown = Icon("circle-chevron-down")
+var CircleChevronLeft = Icon("circle-chevron-left")
+var CircleChevronRight = Icon("circle-chevron-right")
+var CircleChevronUp = Icon("circle-chevron-up")
+var CircleDashed = Icon("circle-dashed")
+var CircleSmall = Icon("circle-small")
+var CircleDotDashed = Icon("circle-dot-dashed")
+var CircleDot = Icon("circle-dot")
+var CircleEllipsis = Icon("circle-ellipsis")
+var CircleEqual = Icon("circle-equal")
+var CircleFadingArrowUp = Icon("circle-fading-arrow-up")
+var CircleFadingPlus = Icon("circle-fading-plus")
+var CircleGauge = Icon("circle-gauge")
+var CircleMinus = Icon("circle-minus")
+var CircleOff = Icon("circle-off")
+var CircleParkingOff = Icon("circle-parking-off")
+var CircleParking = Icon("circle-parking")
+var CirclePause = Icon("circle-pause")
+var CirclePercent = Icon("circle-percent")
+var CirclePlay = Icon("circle-play")
+var CirclePlus = Icon("circle-plus")
+var CirclePoundSterling = Icon("circle-pound-sterling")
+var CheckLine = Icon("check-line")
+var CalendarSearch = Icon("calendar-search")
+var CalendarSync = Icon("calendar-sync")
+var CalendarX2 = Icon("calendar-x-2")
+var CalendarX = Icon("calendar-x")
+var Calendar = Icon("calendar")
+var CameraOff = Icon("camera-off")
+var Camera = Icon("camera")
+var CandyCane = Icon("candy-cane")
+var CandyOff = Icon("candy-off")
+var Candy = Icon("candy")
+var Cannabis = Icon("cannabis")
+var CaptionsOff = Icon("captions-off")
+var Captions = Icon("captions")
+var CarFront = Icon("car-front")
+var CarTaxiFront = Icon("car-taxi-front")
+var Car = Icon("car")
+var Caravan = Icon("caravan")
+var CardSim = Icon("card-sim")
+var Calculator = Icon("calculator")
+var BrushCleaning = Icon("brush-cleaning")
+var Brush = Icon("brush")
+var Bubbles = Icon("bubbles")
+var BugOff = Icon("bug-off")
+var BugPlay = Icon("bug-play")
+var Bug = Icon("bug")
+var Bus = Icon("bus")
+var Building2 = Icon("building-2")
+var Building = Icon("building")
+var CableCar = Icon("cable-car")
+var BusFront = Icon("bus-front")
+var Cable = Icon("cable")
+var CakeSlice = Icon("cake-slice")
+var Club = Icon("club")
+var Clock10 = Icon("clock-10")
+var Carrot = Icon("carrot")
+var CaseLower = Icon("case-lower")
+var CaseSensitive = Icon("case-sensitive")
+var CaseUpper = Icon("case-upper")
+var CassetteTape = Icon("cassette-tape")
+var Cake = Icon("cake")
+var ChartColumnStacked = Icon("chart-column-stacked")
+var Castle = Icon("castle")
+var Cast = Icon("cast")
+var Cat = Icon("cat")
+var ChartColumn = Icon("chart-column")
+var ChartGantt = Icon("chart-gantt")
+var ChartLine = Icon("chart-line")
+var ChartNetwork = Icon("chart-network")
+var ChartNoAxesColumnDecreasing = Icon("chart-no-axes-column-decreasing")
+var CircleDollarSign = Icon("circle-dollar-sign")
+var CircleStar = Icon("circle-star")
+var CircleStop = Icon("circle-stop")
+var CircleUserRound = Icon("circle-user-round")
+var CircleUser = Icon("circle-user")
+var CircleX = Icon("circle-x")
+var Circle = Icon("circle")
+var CircuitBoard = Icon("circuit-board")
+var Citrus = Icon("citrus")
+var Clapperboard = Icon("clapperboard")
+var ClipboardCheck = Icon("clipboard-check")
+var ClipboardClock = Icon("clipboard-clock")
+var ClipboardCopy = Icon("clipboard-copy")
+var ClipboardList = Icon("clipboard-list")
+var ClipboardMinus = Icon("clipboard-minus")
+var ClipboardPaste = Icon("clipboard-paste")
+var ClipboardPenLine = Icon("clipboard-pen-line")
+var ClipboardPen = Icon("clipboard-pen")
+var ClipboardPlus = Icon("clipboard-plus")
+var ClipboardType = Icon("clipboard-type")
+var ClipboardX = Icon("clipboard-x")
+var Clipboard = Icon("clipboard")
+var ChartBarStacked = Icon("chart-bar-stacked")
+var ChartArea = Icon("chart-area")
+var ChartBarBig = Icon("chart-bar-big")
+var ChartBarDecreasing = Icon("chart-bar-decreasing")
+var ChartBarIncreasing = Icon("chart-bar-increasing")
+var ChartColumnBig = Icon("chart-column-big")
+var ChartBar = Icon("chart-bar")
+var ChartCandlestick = Icon("chart-candlestick")
+var ChartColumnDecreasing = Icon("chart-column-decreasing")
+var CloudCheck = Icon("cloud-check")
+var Clock12 = Icon("clock-12")
+var Clock2 = Icon("clock-2")
+var Clock3 = Icon("clock-3")
+var Clock4 = Icon("clock-4")
+var Clock5 = Icon("clock-5")
+var Clock6 = Icon("clock-6")
+var Clock7 = Icon("clock-7")
+var Clock8 = Icon("clock-8")
+var Clock9 = Icon("clock-9")
+var ClockAlert = Icon("clock-alert")
+var ClockArrowDown = Icon("clock-arrow-down")
+var ClockArrowUp = Icon("clock-arrow-up")
+var ClockFading = Icon("clock-fading")
+var ClockPlus = Icon("clock-plus")
+var Clock = Icon("clock")
+var ClosedCaption = Icon("closed-caption")
+var CloudAlert = Icon("cloud-alert")
+var CalendarDays = Icon("calendar-days")
+var Calendar1 = Icon("calendar-1")
+var CalendarArrowDown = Icon("calendar-arrow-down")
+var CalendarArrowUp = Icon("calendar-arrow-up")
+var CalendarCheck2 = Icon("calendar-check-2")
+var CalendarCheck = Icon("calendar-check")
+var CalendarClock = Icon("calendar-clock")
+var CalendarCog = Icon("calendar-cog")
+var CircleSlash2 = Icon("circle-slash-2")
+var CircleQuestionMark = Icon("circle-question-mark")
+var ChartColumnIncreasing = Icon("chart-column-increasing")
+var CloudOff = Icon("cloud-off")
+var CloudCog = Icon("cloud-cog")
+var CloudDownload = Icon("cloud-download")
+var CloudDrizzle = Icon("cloud-drizzle")
+var CloudFog = Icon("cloud-fog")
+var CloudHail = Icon("cloud-hail")
+var CloudLightning = Icon("cloud-lightning")
+var CloudMoonRain = Icon("cloud-moon-rain")
+var CloudMoon = Icon("cloud-moon")
+var DatabaseBackup = Icon("database-backup")
+var CodeXml = Icon("code-xml")
+var Code = Icon("code")
+var Codepen = Icon("codepen")
+var Codesandbox = Icon("codesandbox")
+var Coffee = Icon("coffee")
+var Cog = Icon("cog")
+var Coins = Icon("coins")
+var Columns2 = Icon("columns-2")
+var Columns3Cog = Icon("columns-3-cog")
+var Columns3 = Icon("columns-3")
+var Columns4 = Icon("columns-4")
+var Combine = Icon("combine")
+var Command = Icon("command")
+var Compass = Icon("compass")
+var Component = Icon("component")
+var Computer = Icon("computer")
+var ConciergeBell = Icon("concierge-bell")
+var Cone = Icon("cone")
+var Construction = Icon("construction")
+var ContactRound = Icon("contact-round")
+var Contact = Icon("contact")
+var Container = Icon("container")
+var Contrast = Icon("contrast")
+var Cookie = Icon("cookie")
+var CookingPot = Icon("cooking-pot")
+var CopyCheck = Icon("copy-check")
+var CopyMinus = Icon("copy-minus")
+var CopyPlus = Icon("copy-plus")
+var CopySlash = Icon("copy-slash")
+var CopyX = Icon("copy-x")
+var Copy = Icon("copy")
+var Copyleft = Icon("copyleft")
+var Copyright = Icon("copyright")
+var CornerDownLeft = Icon("corner-down-left")
+var CornerDownRight = Icon("corner-down-right")
+var CornerLeftDown = Icon("corner-left-down")
+var CornerLeftUp = Icon("corner-left-up")
+var CornerRightDown = Icon("corner-right-down")
+var CornerRightUp = Icon("corner-right-up")
+var CornerUpLeft = Icon("corner-up-left")
+var CornerUpRight = Icon("corner-up-right")
+var Cpu = Icon("cpu")
+var CreativeCommons = Icon("creative-commons")
+var CreditCard = Icon("credit-card")
+var Croissant = Icon("croissant")
+var Crop = Icon("crop")
+var Cross = Icon("cross")
+var Crosshair = Icon("crosshair")
+var Crown = Icon("crown")
+var Cuboid = Icon("cuboid")
+var CupSoda = Icon("cup-soda")
+var Currency = Icon("currency")
+var Cylinder = Icon("cylinder")
+var Dam = Icon("dam")
+var CloudSun = Icon("cloud-sun")
+var CloudRainWind = Icon("cloud-rain-wind")
+var CloudRain = Icon("cloud-rain")
+var CloudSnow = Icon("cloud-snow")
+var CloudSunRain = Icon("cloud-sun-rain")
+var ChartPie = Icon("chart-pie")
+var ChartNoAxesColumn = Icon("chart-no-axes-column")
+var ChartNoAxesCombined = Icon("chart-no-axes-combined")
+var ChartNoAxesGantt = Icon("chart-no-axes-gantt")
+var CalendarMinus = Icon("calendar-minus")
+var CalendarFold = Icon("calendar-fold")
+var CalendarHeart = Icon("calendar-heart")
+var CalendarMinus2 = Icon("calendar-minus-2")
+var CircleSlash = Icon("circle-slash")
+var ChartSpline = Icon("chart-spline")
+var ChartScatter = Icon("chart-scatter")
+var CheckCheck = Icon("check-check")
+var Earth = Icon("earth")
+var DatabaseZap = Icon("database-zap")
+var Database = Icon("database")
+var DecimalsArrowLeft = Icon("decimals-arrow-left")
+var DecimalsArrowRight = Icon("decimals-arrow-right")
+var Delete = Icon("delete")
+var Dessert = Icon("dessert")
+var Diameter = Icon("diameter")
+var DiamondMinus = Icon("diamond-minus")
+var DiamondPercent = Icon("diamond-percent")
+var DiamondPlus = Icon("diamond-plus")
+var Diamond = Icon("diamond")
+var Dice1 = Icon("dice-1")
+var Dice2 = Icon("dice-2")
+var Dice3 = Icon("dice-3")
+var Dice4 = Icon("dice-4")
+var Dice5 = Icon("dice-5")
+var Dice6 = Icon("dice-6")
+var Dices = Icon("dices")
+var Diff = Icon("diff")
+var Disc2 = Icon("disc-2")
+var Disc3 = Icon("disc-3")
+var DiscAlbum = Icon("disc-album")
+var Disc = Icon("disc")
+var Divide = Icon("divide")
+var DnaOff = Icon("dna-off")
+var Dna = Icon("dna")
+var Dock = Icon("dock")
+var Dog = Icon("dog")
+var DollarSign = Icon("dollar-sign")
+var Donut = Icon("donut")
+var DoorClosedLocked = Icon("door-closed-locked")
+var DoorClosed = Icon("door-closed")
+var DoorOpen = Icon("door-open")
+var Dot = Icon("dot")
+var Download = Icon("download")
+var DraftingCompass = Icon("drafting-compass")
+var Drama = Icon("drama")
+var Dribbble = Icon("dribbble")
+var Drill = Icon("drill")
+var Drone = Icon("drone")
+var DropletOff = Icon("droplet-off")
+var Droplet = Icon("droplet")
+var Droplets = Icon("droplets")
+var Drum = Icon("drum")
+var Drumstick = Icon("drumstick")
+var Dumbbell = Icon("dumbbell")
+var EarOff = Icon("ear-off")
+var Ear = Icon("ear")
+var EarthLock = Icon("earth-lock")
+var CalendarPlus2 = Icon("calendar-plus-2")
+var CalendarOff = Icon("calendar-off")
+var FileCheck2 = Icon("file-check-2")
+var Eclipse = Icon("eclipse")
+var EggFried = Icon("egg-fried")
+var EggOff = Icon("egg-off")
+var Egg = Icon("egg")
+var EllipsisVertical = Icon("ellipsis-vertical")
+var Ellipsis = Icon("ellipsis")
+var EqualApproximately = Icon("equal-approximately")
+var EqualNot = Icon("equal-not")
+var Equal = Icon("equal")
+var Eraser = Icon("eraser")
+var EthernetPort = Icon("ethernet-port")
+var Euro = Icon("euro")
+var EvCharger = Icon("ev-charger")
+var Expand = Icon("expand")
+var ExternalLink = Icon("external-link")
+var EyeClosed = Icon("eye-closed")
+var EyeOff = Icon("eye-off")
+var Eye = Icon("eye")
+var Facebook = Icon("facebook")
+var Factory = Icon("factory")
+var Fan = Icon("fan")
+var FastForward = Icon("fast-forward")
+var Feather = Icon("feather")
+var Fence = Icon("fence")
+var FerrisWheel = Icon("ferris-wheel")
+var Figma = Icon("figma")
+var FileArchive = Icon("file-archive")
+var FileAudio2 = Icon("file-audio-2")
+var FileAudio = Icon("file-audio")
+var FileAxis3d = Icon("file-axis-3d")
+var FileBadge2 = Icon("file-badge-2")
+var FileBadge = Icon("file-badge")
+var FileBox = Icon("file-box")
+var FileChartColumnIncreasing = Icon("file-chart-column-increasing")
+var FileChartColumn = Icon("file-chart-column")
+var FileChartLine = Icon("file-chart-line")
+var FileChartPie = Icon("file-chart-pie")
+var Cloud = Icon("cloud")
+var CloudUpload = Icon("cloud-upload")
+var Cloudy = Icon("cloudy")
+var FileText = Icon("file-text")
+var FileCheck = Icon("file-check")
+var FileClock = Icon("file-clock")
+var FileCode2 = Icon("file-code-2")
+var FileCode = Icon("file-code")
+var FileCog = Icon("file-cog")
+var FileDiff = Icon("file-diff")
+var FileDigit = Icon("file-digit")
+var FileDown = Icon("file-down")
+var FileHeart = Icon("file-heart")
+var FileImage = Icon("file-image")
+var FileInput = Icon("file-input")
+var FileJson2 = Icon("file-json-2")
+var FileJson = Icon("file-json")
+var Clock11 = Icon("clock-11")
+var Clover = Icon("clover")
+var FlashlightOff = Icon("flashlight-off")
+var FileType2 = Icon("file-type-2")
+var FileType = Icon("file-type")
+var FileUp = Icon("file-up")
+var FileUser = Icon("file-user")
+var FileVideoCamera = Icon("file-video-camera")
+var FileVolume2 = Icon("file-volume-2")
+var FileVolume = Icon("file-volume")
+var FileWarning = Icon("file-warning")
+var FileX2 = Icon("file-x-2")
+var FileX = Icon("file-x")
+var File = Icon("file")
+var Files = Icon("files")
+var Film = Icon("film")
+var Fingerprint = Icon("fingerprint")
+var FireExtinguisher = Icon("fire-extinguisher")
+var FishOff = Icon("fish-off")
+var FishSymbol = Icon("fish-symbol")
+var Fish = Icon("fish")
+var FlagOff = Icon("flag-off")
+var FlagTriangleLeft = Icon("flag-triangle-left")
+var FlagTriangleRight = Icon("flag-triangle-right")
+var Flag = Icon("flag")
+var FlameKindling = Icon("flame-kindling")
+var Flame = Icon("flame")
+var FoldHorizontal = Icon("fold-horizontal")
+var Flashlight = Icon("flashlight")
+var FlaskConicalOff = Icon("flask-conical-off")
+var FlaskConical = Icon("flask-conical")
+var FlaskRound = Icon("flask-round")
+var FlipHorizontal2 = Icon("flip-horizontal-2")
+var FlipHorizontal = Icon("flip-horizontal")
+var FlipVertical2 = Icon("flip-vertical-2")
+var FlipVertical = Icon("flip-vertical")
+var Flower2 = Icon("flower-2")
+var Flower = Icon("flower")
+var Focus = Icon("focus")
+var FilePlus2 = Icon("file-plus-2")
+var FileKey = Icon("file-key")
+var FileLock2 = Icon("file-lock-2")
+var FileLock = Icon("file-lock")
+var FileMinus2 = Icon("file-minus-2")
+var FileMinus = Icon("file-minus")
+var FileMusic = Icon("file-music")
+var FileOutput = Icon("file-output")
+var FilePenLine = Icon("file-pen-line")
+var FilePen = Icon("file-pen")
+var FilePlay = Icon("file-play")
+var FolderCode = Icon("folder-code")
+var FoldVertical = Icon("fold-vertical")
+var FolderArchive = Icon("folder-archive")
+var FolderCheck = Icon("folder-check")
+var FolderClock = Icon("folder-clock")
+var FolderClosed = Icon("folder-closed")
+var FileSearch = Icon("file-search")
+var FilePlus = Icon("file-plus")
+var FileQuestionMark = Icon("file-question-mark")
+var FileScan = Icon("file-scan")
+var FileSearch2 = Icon("file-search-2")
+var FolderDown = Icon("folder-down")
+var FolderCog = Icon("folder-cog")
+var FolderDot = Icon("folder-dot")
+var FileKey2 = Icon("file-key-2")
+var FileSliders = Icon("file-sliders")
+var FileSpreadsheet = Icon("file-spreadsheet")
+var FileStack = Icon("file-stack")
+var FileSymlink = Icon("file-symlink")
+var FileTerminal = Icon("file-terminal")
+var CalendarPlus = Icon("calendar-plus")
+var FolderHeart = Icon("folder-heart")
+var FolderGit2 = Icon("folder-git-2")
+var FolderGit = Icon("folder-git")
+var ArrowDownZA = Icon("arrow-down-z-a")
+var FolderInput = Icon("folder-input")
+var ChartNoAxesColumnIncreasing = Icon("chart-no-axes-column-increasing")
+var ArrowBigUpDash = Icon("arrow-big-up-dash")
+var BookUp2 = Icon("book-up-2")
+var FolderMinus = Icon("folder-minus")
+var FolderKanban = Icon("folder-kanban")
+var FolderKey = Icon("folder-key")
+var FolderLock = Icon("folder-lock")
+var FolderPlus = Icon("folder-plus")
+var FolderOpenDot = Icon("folder-open-dot")
+var FolderSearch2 = Icon("folder-search-2")
+var FolderOpen = Icon("folder-open")
+var FolderOutput = Icon("folder-output")
+var FolderPen = Icon("folder-pen")
+var FolderTree = Icon("folder-tree")
+var FolderSync = Icon("folder-sync")
+var FolderUp = Icon("folder-up")
+var FolderSymlink = Icon("folder-symlink")
+var FolderSearch = Icon("folder-search")
+var Folder = Icon("folder")
+var Footprints = Icon("footprints")
+var Folders = Icon("folders")
+var Forklift = Icon("forklift")
+var FolderRoot = Icon("folder-root")
+var Framer = Icon("framer")
+var FunnelPlus = Icon("funnel-plus")
+var Frame = Icon("frame")
+var Fuel = Icon("fuel")
+var Fullscreen = Icon("fullscreen")
+var Funnel = Icon("funnel")
+var FunnelX = Icon("funnel-x")
+var GalleryHorizontalEnd = Icon("gallery-horizontal-end")
+var GalleryThumbnails = Icon("gallery-thumbnails")
+var GalleryHorizontal = Icon("gallery-horizontal")
+var Frown = Icon("frown")
+var GalleryVertical = Icon("gallery-vertical")
+var GalleryVerticalEnd = Icon("gallery-vertical-end")
+var GeorgianLari = Icon("georgian-lari")
+var Gavel = Icon("gavel")
+var Gem = Icon("gem")
+var Ghost = Icon("ghost")
+var Gamepad2 = Icon("gamepad-2")
+var GitBranch = Icon("git-branch")
+var GitBranchPlus = Icon("git-branch-plus")
+var Gift = Icon("gift")
+var GitCommitHorizontal = Icon("git-commit-horizontal")
+var GitCommitVertical = Icon("git-commit-vertical")
+var Gauge = Icon("gauge")
+var GitCompare = Icon("git-compare")
+var GitCompareArrows = Icon("git-compare-arrows")
+var GitMerge = Icon("git-merge")
+var GitFork = Icon("git-fork")
+var GitGraph = Icon("git-graph")
+var GitPullRequestCreateArrow = Icon("git-pull-request-create-arrow")
+var GitPullRequestArrow = Icon("git-pull-request-arrow")
+var GitPullRequestClosed = Icon("git-pull-request-closed")
+var GitPullRequestCreate = Icon("git-pull-request-create")
+var Gitlab = Icon("gitlab")
+var GitPullRequest = Icon("git-pull-request")
+var Github = Icon("github")
+var Glasses = Icon("glasses")
+var GlassWater = Icon("glass-water")
+var GlobeLock = Icon("globe-lock")
+var Globe = Icon("globe")
+var GitPullRequestDraft = Icon("git-pull-request-draft")
+var GraduationCap = Icon("graduation-cap")
+var Gpu = Icon("gpu")
+var Grid2x2X = Icon("grid-2x2-x")
+var Grape = Icon("grape")
+var Grid2x2Check = Icon("grid-2x2-check")
+var Grid2x2Plus = Icon("grid-2x2-plus")
+var GripHorizontal = Icon("grip-horizontal")
+var Grid2x2 = Icon("grid-2x2")
+var Grid3x2 = Icon("grid-3x2")
+var Grid3x3 = Icon("grid-3x3")
+var GripVertical = Icon("grip-vertical")
+var Group = Icon("group")
+var Guitar = Icon("guitar")
+var Hamburger = Icon("hamburger")
+var Ham = Icon("ham")
+var Goal = Icon("goal")
+var HandFist = Icon("hand-fist")
+var Hammer = Icon("hammer")
+var HandCoins = Icon("hand-coins")
+var HandPlatter = Icon("hand-platter")
+var HandGrab = Icon("hand-grab")
+var HandMetal = Icon("hand-metal")
+var HandHeart = Icon("hand-heart")
+var Handshake = Icon("handshake")
+var Hand = Icon("hand")
+var Handbag = Icon("handbag")
+var HardDriveUpload = Icon("hard-drive-upload")
+var HardDriveDownload = Icon("hard-drive-download")
+var Grip = Icon("grip")
+var HardHat = Icon("hard-hat")
+var Hash = Icon("hash")
+var HandHelping = Icon("hand-helping")
+var Heading4 = Icon("heading-4")
+var HatGlasses = Icon("hat-glasses")
+var Haze = Icon("haze")
+var HdmiPort = Icon("hdmi-port")
+var Heading1 = Icon("heading-1")
+var Heading2 = Icon("heading-2")
+var HeadphoneOff = Icon("headphone-off")
+var Heading5 = Icon("heading-5")
+var Heading6 = Icon("heading-6")
+var Heading = Icon("heading")
+var History = Icon("history")
+var Headphones = Icon("headphones")
+var Headset = Icon("headset")
+var Heading3 = Icon("heading-3")
+var IceCreamBowl = Icon("ice-cream-bowl")
+var HopOff = Icon("hop-off")
+var Hop = Icon("hop")
+var Hospital = Icon("hospital")
+var Hotel = Icon("hotel")
+var Hourglass = Icon("hourglass")
+var HouseHeart = Icon("house-heart")
+var HousePlug = Icon("house-plug")
+var HeartCrack = Icon("heart-crack")
+var HousePlus = Icon("house-plus")
+var HeartHandshake = Icon("heart-handshake")
+var HeartMinus = Icon("heart-minus")
+var HeartOff = Icon("heart-off")
+var HeartPlus = Icon("heart-plus")
+var HeartPulse = Icon("heart-pulse")
+var HouseWifi = Icon("house-wifi")
+var House = Icon("house")
+var ImageUpscale = Icon("image-upscale")
+var IceCreamCone = Icon("ice-cream-cone")
+var IdCardLanyard = Icon("id-card-lanyard")
+var IdCard = Icon("id-card")
+var ImageDown = Icon("image-down")
+var ImageMinus = Icon("image-minus")
+var ImageOff = Icon("image-off")
+var ImagePlay = Icon("image-play")
+var ImagePlus = Icon("image-plus")
+var ImageUp = Icon("image-up")
+var Heart = Icon("heart")
+var Instagram = Icon("instagram")
+var Image = Icon("image")
+var Images = Icon("images")
+var Import = Icon("import")
+var Inbox = Icon("inbox")
+var IndianRupee = Icon("indian-rupee")
+var Infinity = Icon("infinity")
+var Info = Icon("info")
+var InspectionPanel = Icon("inspection-panel")
+var JapaneseYen = Icon("japanese-yen")
+var Italic = Icon("italic")
+var IterationCcw = Icon("iteration-ccw")
+var IterationCw = Icon("iteration-cw")
+var HardDrive = Icon("hard-drive")
+var Kayak = Icon("kayak")
+var Joystick = Icon("joystick")
+var Kanban = Icon("kanban")
+var Heater = Icon("heater")
+var Hexagon = Icon("hexagon")
+var KeyboardMusic = Icon("keyboard-music")
+var KeySquare = Icon("key-square")
+var Key = Icon("key")
+var LampWallUp = Icon("lamp-wall-up")
+var KeyboardOff = Icon("keyboard-off")
+var Keyboard = Icon("keyboard")
+var LampCeiling = Icon("lamp-ceiling")
+var LampDesk = Icon("lamp-desk")
+var LampFloor = Icon("lamp-floor")
+var KeyRound = Icon("key-round")
+var LampWallDown = Icon("lamp-wall-down")
+var LassoSelect = Icon("lasso-select")
+var Lamp = Icon("lamp")
+var LandPlot = Icon("land-plot")
+var Landmark = Icon("landmark")
+var Languages = Icon("languages")
+var LaptopMinimalCheck = Icon("laptop-minimal-check")
+var LaptopMinimal = Icon("laptop-minimal")
+var Laptop = Icon("laptop")
+var Laugh = Icon("laugh")
+var Lasso = Icon("lasso")
+var Highlighter = Icon("highlighter")
+var LayoutGrid = Icon("layout-grid")
+var LayoutPanelLeft = Icon("layout-panel-left")
+var LayoutList = Icon("layout-list")
+var Layers2 = Icon("layers-2")
+var Layers = Icon("layers")
+var Leaf = Icon("leaf")
+var Lectern = Icon("lectern")
+var LeafyGreen = Icon("leafy-green")
+var LifeBuoy = Icon("life-buoy")
+var LibraryBig = Icon("library-big")
+var LayoutDashboard = Icon("layout-dashboard")
+var LayoutTemplate = Icon("layout-template")
+var Link2Off = Icon("link-2-off")
+var Ligature = Icon("ligature")
+var LightbulbOff = Icon("lightbulb-off")
+var Lightbulb = Icon("lightbulb")
+var Library = Icon("library")
+var LineSquiggle = Icon("line-squiggle")
+var ListCheck = Icon("list-check")
+var Link2 = Icon("link-2")
+var Link = Icon("link")
+var Linkedin = Icon("linkedin")
+var ListChevronsDownUp = Icon("list-chevrons-down-up")
+var ListChecks = Icon("list-checks")
+var ListChevronsUpDown = Icon("list-chevrons-up-down")
+var LayoutPanelTop = Icon("layout-panel-top")
+var ListFilter = Icon("list-filter")
+var ListCollapse = Icon("list-collapse")
+var ListEnd = Icon("list-end")
+var ListMusic = Icon("list-music")
+var ListIndentDecrease = Icon("list-indent-decrease")
+var ListIndentIncrease = Icon("list-indent-increase")
+var ListMinus = Icon("list-minus")
+var ListRestart = Icon("list-restart")
+var ListOrdered = Icon("list-ordered")
+var ListTree = Icon("list-tree")
+var ListPlus = Icon("list-plus")
+var ListStart = Icon("list-start")
+var ListTodo = Icon("list-todo")
+var ListX = Icon("list-x")
+var ListVideo = Icon("list-video")
+var LoaderCircle = Icon("loader-circle")
+var ListFilterPlus = Icon("list-filter-plus")
+var LoaderPinwheel = Icon("loader-pinwheel")
+var Loader = Icon("loader")
+var LocateFixed = Icon("locate-fixed")
+var LocateOff = Icon("locate-off")
+var Locate = Icon("locate")
+var LockKeyhole = Icon("lock-keyhole")
+var LockKeyholeOpen = Icon("lock-keyhole-open")
+var Lock = Icon("lock")
+var LockOpen = Icon("lock-open")
+var Lollipop = Icon("lollipop")
+var LogIn = Icon("log-in")
+var LogOut = Icon("log-out")
+var Logs = Icon("logs")
+var MailCheck = Icon("mail-check")
+var Luggage = Icon("luggage")
+var Magnet = Icon("magnet")
+var MailMinus = Icon("mail-minus")
+var MailOpen = Icon("mail-open")
+var List = Icon("list")
+var Mail = Icon("mail")
+var MailPlus = Icon("mail-plus")
+var MailQuestionMark = Icon("mail-question-mark")
+var MailSearch = Icon("mail-search")
+var MailWarning = Icon("mail-warning")
+var MailX = Icon("mail-x")
+var Mails = Icon("mails")
+var Mailbox = Icon("mailbox")
+var Gamepad = Icon("gamepad")
+var MapPinHouse = Icon("map-pin-house")
+var MapPinCheck = Icon("map-pin-check")
+var MapPinMinusInside = Icon("map-pin-minus-inside")
+var MapPinMinus = Icon("map-pin-minus")
+var MapPinPen = Icon("map-pin-pen")
+var MapPinOff = Icon("map-pin-off")
+var MapPinPlus = Icon("map-pin-plus")
+var MapPinPlusInside = Icon("map-pin-plus-inside")
+var MapPinX = Icon("map-pin-x")
+var MapPinned = Icon("map-pinned")
+var MapPinXInside = Icon("map-pin-x-inside")
+var MapPinCheckInside = Icon("map-pin-check-inside")
+var MapPlus = Icon("map-plus")
+var Map = Icon("map")
+var Maximize2 = Icon("maximize-2")
+var Mars = Icon("mars")
+var Martini = Icon("martini")
+var Maximize = Icon("maximize")
+var Medal = Icon("medal")
+var MarsStroke = Icon("mars-stroke")
+var MessageCircleHeart = Icon("message-circle-heart")
+var MegaphoneOff = Icon("megaphone-off")
+var Megaphone = Icon("megaphone")
+var Meh = Icon("meh")
+var MemoryStick = Icon("memory-stick")
+var Menu = Icon("menu")
+var Merge = Icon("merge")
+var MessageCircleCode = Icon("message-circle-code")
+var MessageCircleDashed = Icon("message-circle-dashed")
+var MessageSquareDiff = Icon("message-square-diff")
+var MessageCircleMore = Icon("message-circle-more")
+var MessageCircleOff = Icon("message-circle-off")
+var MessageCirclePlus = Icon("message-circle-plus")
+var MessageCircleQuestionMark = Icon("message-circle-question-mark")
+var MessageCircleReply = Icon("message-circle-reply")
+var MessageCircleWarning = Icon("message-circle-warning")
+var MessageCircleX = Icon("message-circle-x")
+var MessageCircle = Icon("message-circle")
+var MessageSquareCode = Icon("message-square-code")
+var MessageSquareDashed = Icon("message-square-dashed")
+var MessageSquareQuote = Icon("message-square-quote")
+var MessageSquareDot = Icon("message-square-dot")
+var MessageSquareHeart = Icon("message-square-heart")
+var MessageSquareLock = Icon("message-square-lock")
+var MessageSquareMore = Icon("message-square-more")
+var MessageSquareOff = Icon("message-square-off")
+var MessageSquarePlus = Icon("message-square-plus")
+var MessagesSquare = Icon("messages-square")
+var CirclePower = Icon("circle-power")
+var Microchip = Icon("microchip")
+var MicOff = Icon("mic-off")
+var Mic = Icon("mic")
+var MicVocal = Icon("mic-vocal")
+var MessageSquareWarning = Icon("message-square-warning")
+var Milestone = Icon("milestone")
+var MessageSquareShare = Icon("message-square-share")
+var MessageSquareText = Icon("message-square-text")
+var Microscope = Icon("microscope")
+var Microwave = Icon("microwave")
+var MessageSquare = Icon("message-square")
+var MessageSquareReply = Icon("message-square-reply")
+var Minimize = Icon("minimize")
+var MilkOff = Icon("milk-off")
+var Milk = Icon("milk")
+var Minimize2 = Icon("minimize-2")
+var MonitorCheck = Icon("monitor-check")
+var MonitorCog = Icon("monitor-cog")
+var Minus = Icon("minus")
+var MonitorDot = Icon("monitor-dot")
+var MessageSquareX = Icon("message-square-x")
+var MonitorDown = Icon("monitor-down")
+var MonitorOff = Icon("monitor-off")
+var MonitorPause = Icon("monitor-pause")
+var MonitorPlay = Icon("monitor-play")
+var MonitorSmartphone = Icon("monitor-smartphone")
+var MonitorSpeaker = Icon("monitor-speaker")
+var MapPin = Icon("map-pin")
+var MapMinus = Icon("map-minus")
+var MonitorX = Icon("monitor-x")
+var Forward = Icon("forward")
+var Monitor = Icon("monitor")
+var MoonStar = Icon("moon-star")
+var MonitorUp = Icon("monitor-up")
+var MountainSnow = Icon("mountain-snow")
+var Moon = Icon("moon")
+var FolderX = Icon("folder-x")
+var MouseOff = Icon("mouse-off")
+var MousePointer2 = Icon("mouse-pointer-2")
+var MousePointerClick = Icon("mouse-pointer-click")
+var MousePointerBan = Icon("mouse-pointer-ban")
+var MoveDiagonal2 = Icon("move-diagonal-2")
+var MousePointer = Icon("mouse-pointer")
+var Mouse = Icon("mouse")
+var Move3d = Icon("move-3d")
+var MoveDown = Icon("move-down")
+var MoveDiagonal = Icon("move-diagonal")
+var MoveDownLeft = Icon("move-down-left")
+var MoveLeft = Icon("move-left")
+var MoveHorizontal = Icon("move-horizontal")
+var MoveUpLeft = Icon("move-up-left")
+var MoveRight = Icon("move-right")
+var MoveUpRight = Icon("move-up-right")
+var Music3 = Icon("music-3")
+var MonitorStop = Icon("monitor-stop")
+var MoveUp = Icon("move-up")
+var Move = Icon("move")
+var MoveVertical = Icon("move-vertical")
+var Music2 = Icon("music-2")
+var Navigation = Icon("navigation")
+var Music4 = Icon("music-4")
+var Music = Icon("music")
+var Navigation2Off = Icon("navigation-2-off")
+var Navigation2 = Icon("navigation-2")
+var NavigationOff = Icon("navigation-off")
+var Cctv = Icon("cctv")
+var NotebookPen = Icon("notebook-pen")
+var NotebookText = Icon("notebook-text")
+var NotebookTabs = Icon("notebook-tabs")
+var Network = Icon("network")
+var OctagonAlert = Icon("octagon-alert")
+var Notebook = Icon("notebook")
+var Octagon = Icon("octagon")
+var MoveDownRight = Icon("move-down-right")
+var OctagonMinus = Icon("octagon-minus")
+var NutOff = Icon("nut-off")
+var Option = Icon("option")
+var NotepadText = Icon("notepad-text")
+var Omega = Icon("omega")
+var Nut = Icon("nut")
+var Newspaper = Icon("newspaper")
+var OctagonX = Icon("octagon-x")
+var Nfc = Icon("nfc")
+var Origami = Icon("origami")
+var PackageSearch = Icon("package-search")
+var PaintBucket = Icon("paint-bucket")
+var PackageX = Icon("package-x")
+var Package = Icon("package")
+var PackageOpen = Icon("package-open")
+var PackageMinus = Icon("package-minus")
+var PaintRoller = Icon("paint-roller")
+var PaintbrushVertical = Icon("paintbrush-vertical")
+var PackagePlus = Icon("package-plus")
+var Package2 = Icon("package-2")
+var Paintbrush = Icon("paintbrush")
+var PackageCheck = Icon("package-check")
+var Panda = Icon("panda")
+var Palette = Icon("palette")
+var PanelBottomDashed = Icon("panel-bottom-dashed")
+var Orbit = Icon("orbit")
+var PanelBottom = Icon("panel-bottom")
+var PanelBottomOpen = Icon("panel-bottom-open")
+var PanelLeftRightDashed = Icon("panel-left-right-dashed")
+var PanelLeftClose = Icon("panel-left-close")
+var PanelLeftDashed = Icon("panel-left-dashed")
+var PanelLeftOpen = Icon("panel-left-open")
+var PanelRight = Icon("panel-right")
+var PanelLeft = Icon("panel-left")
+var PanelRightClose = Icon("panel-right-close")
+var PanelRightDashed = Icon("panel-right-dashed")
+var PanelRightOpen = Icon("panel-right-open")
+var PanelTopOpen = Icon("panel-top-open")
+var PanelBottomClose = Icon("panel-bottom-close")
+var PanelTopBottomDashed = Icon("panel-top-bottom-dashed")
+var PanelTop = Icon("panel-top")
+var PanelsLeftBottom = Icon("panels-left-bottom")
+var PanelsRightBottom = Icon("panels-right-bottom")
+var PanelTopClose = Icon("panel-top-close")
+var Paperclip = Icon("paperclip")
+var PanelsTopLeft = Icon("panels-top-left")
+var PenTool = Icon("pen-tool")
+var PanelTopDashed = Icon("panel-top-dashed")
+var PencilLine = Icon("pencil-line")
+var Pen = Icon("pen")
+var PenOff = Icon("pen-off")
+var PartyPopper = Icon("party-popper")
+var ParkingMeter = Icon("parking-meter")
+var Pause = Icon("pause")
+var PawPrint = Icon("paw-print")
+var Percent = Icon("percent")
+var PencilOff = Icon("pencil-off")
+var PencilRuler = Icon("pencil-ruler")
+var Pencil = Icon("pencil")
+var Pentagon = Icon("pentagon")
+var PhoneIncoming = Icon("phone-incoming")
+var PersonStanding = Icon("person-standing")
+var PhilippinePeso = Icon("philippine-peso")
+var PhoneCall = Icon("phone-call")
+var PhoneForwarded = Icon("phone-forwarded")
+var Phone = Icon("phone")
+var PhoneMissed = Icon("phone-missed")
+var PhoneOff = Icon("phone-off")
+var PhoneOutgoing = Icon("phone-outgoing")
+var Piano = Icon("piano")
+var Pi = Icon("pi")
+var Pickaxe = Icon("pickaxe")
+var PictureInPicture2 = Icon("picture-in-picture-2")
+var PiggyBank = Icon("piggy-bank")
+var PictureInPicture = Icon("picture-in-picture")
+var Pilcrow = Icon("pilcrow")
+var PilcrowLeft = Icon("pilcrow-left")
+var PilcrowRight = Icon("pilcrow-right")
+var Pill = Icon("pill")
+var PillBottle = Icon("pill-bottle")
+var PinOff = Icon("pin-off")
+var Pin = Icon("pin")
+var PenLine = Icon("pen-line")
+var PlugZap = Icon("plug-zap")
+var Pipette = Icon("pipette")
+var Pizza = Icon("pizza")
+var PlaneLanding = Icon("plane-landing")
+var PlaneTakeoff = Icon("plane-takeoff")
+var Plane = Icon("plane")
+var OctagonPause = Icon("octagon-pause")
+var Pocket = Icon("pocket")
+var PocketKnife = Icon("pocket-knife")
+var Plug2 = Icon("plug-2")
+var Mountain = Icon("mountain")
+var PrinterCheck = Icon("printer-check")
+var Plus = Icon("plus")
+var PowerOff = Icon("power-off")
+var Power = Icon("power")
+var Presentation = Icon("presentation")
+var Plug = Icon("plug")
+var Rabbit = Icon("rabbit")
+var QrCode = Icon("qr-code")
+var Quote = Icon("quote")
+var PoundSterling = Icon("pound-sterling")
+var Pointer = Icon("pointer")
+var Popcorn = Icon("popcorn")
+var Podcast = Icon("podcast")
+var PointerOff = Icon("pointer-off")
+var Projector = Icon("projector")
+var Printer = Icon("printer")
+var Pyramid = Icon("pyramid")
+var Proportions = Icon("proportions")
+var RadioReceiver = Icon("radio-receiver")
+var Radar = Icon("radar")
+var Radiation = Icon("radiation")
+var Radical = Icon("radical")
+var Puzzle = Icon("puzzle")
+var RailSymbol = Icon("rail-symbol")
+var Radius = Icon("radius")
+var NonBinary = Icon("non-binary")
+var RadioTower = Icon("radio-tower")
+var Rat = Icon("rat")
+var Radio = Icon("radio")
+var ReceiptEuro = Icon("receipt-euro")
+var ReceiptCent = Icon("receipt-cent")
+var ReceiptJapaneseYen = Icon("receipt-japanese-yen")
+var ReceiptIndianRupee = Icon("receipt-indian-rupee")
+var ReceiptRussianRuble = Icon("receipt-russian-ruble")
+var ReceiptPoundSterling = Icon("receipt-pound-sterling")
+var Rainbow = Icon("rainbow")
+var ReceiptTurkishLira = Icon("receipt-turkish-lira")
+var ReceiptSwissFranc = Icon("receipt-swiss-franc")
+var ReceiptText = Icon("receipt-text")
+var RectangleCircle = Icon("rectangle-circle")
+var RectangleEllipsis = Icon("rectangle-ellipsis")
+var Receipt = Icon("receipt")
+var RectangleGoggles = Icon("rectangle-goggles")
+var Ratio = Icon("ratio")
+var Popsicle = Icon("popsicle")
+var RefreshCcw = Icon("refresh-ccw")
+var RectangleHorizontal = Icon("rectangle-horizontal")
+var Repeat1 = Icon("repeat-1")
+var RemoveFormatting = Icon("remove-formatting")
+var RefreshCw = Icon("refresh-cw")
+var RefreshCwOff = Icon("refresh-cw-off")
+var RedoDot = Icon("redo-dot")
+var Recycle = Icon("recycle")
+var Redo2 = Icon("redo-2")
+var Repeat2 = Icon("repeat-2")
+var Repeat = Icon("repeat")
+var Redo = Icon("redo")
+var ReplaceAll = Icon("replace-all")
+var RollerCoaster = Icon("roller-coaster")
+var ReplyAll = Icon("reply-all")
+var Reply = Icon("reply")
+var Rewind = Icon("rewind")
+var Ribbon = Icon("ribbon")
+var Rocket = Icon("rocket")
+var RockingChair = Icon("rocking-chair")
+var Refrigerator = Icon("refrigerator")
+var RotateCcwSquare = Icon("rotate-ccw-square")
+var Rose = Icon("rose")
+var Rotate3d = Icon("rotate-3d")
+var RotateCcwKey = Icon("rotate-ccw-key")
+var RefreshCcwDot = Icon("refresh-ccw-dot")
+var Router = Icon("router")
+var RotateCcw = Icon("rotate-ccw")
+var RotateCwSquare = Icon("rotate-cw-square")
+var RotateCw = Icon("rotate-cw")
+var RouteOff = Icon("route-off")
+var NotepadTextDashed = Icon("notepad-text-dashed")
+var RussianRuble = Icon("russian-ruble")
+var Replace = Icon("replace")
+var SaudiRiyal = Icon("saudi-riyal")
+var Sailboat = Icon("sailboat")
+var Salad = Icon("salad")
+var Sandwich = Icon("sandwich")
+var SatelliteDish = Icon("satellite-dish")
+var Satellite = Icon("satellite")
+var Rss = Icon("rss")
+var Rows3 = Icon("rows-3")
+var Rows4 = Icon("rows-4")
+var Ruler = Icon("ruler")
+var RulerDimensionLine = Icon("ruler-dimension-line")
+var ScanBarcode = Icon("scan-barcode")
+var SaveAll = Icon("save-all")
+var SaveOff = Icon("save-off")
+var Route = Icon("route")
+var Scale = Icon("scale")
+var Scale3d = Icon("scale-3d")
+var ScanQrCode = Icon("scan-qr-code")
+var ScanEye = Icon("scan-eye")
+var ScanFace = Icon("scan-face")
+var ScanHeart = Icon("scan-heart")
+var ScanLine = Icon("scan-line")
+var Scaling = Icon("scaling")
+var ScanText = Icon("scan-text")
+var ScanSearch = Icon("scan-search")
+var ScissorsLineDashed = Icon("scissors-line-dashed")
+var Scan = Icon("scan")
+var School = Icon("school")
+var Save = Icon("save")
+var ScreenShare = Icon("screen-share")
+var Scissors = Icon("scissors")
+var ScreenShareOff = Icon("screen-share-off")
+var ScrollText = Icon("scroll-text")
+var SearchCheck = Icon("search-check")
+var SearchCode = Icon("search-code")
+var Rows2 = Icon("rows-2")
+var Section = Icon("section")
+var SearchSlash = Icon("search-slash")
+var SearchX = Icon("search-x")
+var Scroll = Icon("scroll")
+var SendToBack = Icon("send-to-back")
+var SendHorizontal = Icon("send-horizontal")
+var Send = Icon("send")
+var SeparatorHorizontal = Icon("separator-horizontal")
+var SeparatorVertical = Icon("separator-vertical")
+var ServerCog = Icon("server-cog")
+var ServerCrash = Icon("server-crash")
+var ServerOff = Icon("server-off")
+var Server = Icon("server")
+var Settings2 = Icon("settings-2")
+var Settings = Icon("settings")
+var Shapes = Icon("shapes")
+var Share2 = Icon("share-2")
+var Parentheses = Icon("parentheses")
+var Share = Icon("share")
+var Sheet = Icon("sheet")
+var Shell = Icon("shell")
+var ShieldCheck = Icon("shield-check")
+var ShieldBan = Icon("shield-ban")
+var ShieldMinus = Icon("shield-minus")
+var ShieldAlert = Icon("shield-alert")
+var ShieldPlus = Icon("shield-plus")
+var ShieldOff = Icon("shield-off")
+var ShieldUser = Icon("shield-user")
+var ShieldQuestionMark = Icon("shield-question-mark")
+var Shield = Icon("shield")
+var ShieldX = Icon("shield-x")
+var Shirt = Icon("shirt")
+var ShipWheel = Icon("ship-wheel")
+var Ship = Icon("ship")
+var ShoppingBag = Icon("shopping-bag")
+var ShieldEllipsis = Icon("shield-ellipsis")
+var Shovel = Icon("shovel")
+var ShoppingBasket = Icon("shopping-basket")
+var ShoppingCart = Icon("shopping-cart")
+var ShowerHead = Icon("shower-head")
+var ShieldHalf = Icon("shield-half")
+var Shredder = Icon("shredder")
+var Shrink = Icon("shrink")
+var Shrimp = Icon("shrimp")
+var Shrub = Icon("shrub")
+var Shuffle = Icon("shuffle")
+var Sigma = Icon("sigma")
+var SignalLow = Icon("signal-low")
+var SignalHigh = Icon("signal-high")
+var SignalZero = Icon("signal-zero")
+var SignalMedium = Icon("signal-medium")
+var Signpost = Icon("signpost")
+var SignpostBig = Icon("signpost-big")
+var Signal = Icon("signal")
+var SkipBack = Icon("skip-back")
+var SkipForward = Icon("skip-forward")
+var Regex = Icon("regex")
+var Slack = Icon("slack")
+var Skull = Icon("skull")
+var Slice = Icon("slice")
+var Slash = Icon("slash")
+var SlidersHorizontal = Icon("sliders-horizontal")
+var SlidersVertical = Icon("sliders-vertical")
+var SmartphoneCharging = Icon("smartphone-charging")
+var Smartphone = Icon("smartphone")
+var SmartphoneNfc = Icon("smartphone-nfc")
+var Snail = Icon("snail")
+var SmilePlus = Icon("smile-plus")
+var Smile = Icon("smile")
+var SoapDispenserDroplet = Icon("soap-dispenser-droplet")
+var Snowflake = Icon("snowflake")
+var Sofa = Icon("sofa")
+var Search = Icon("search")
+var Space = Icon("space")
+var Soup = Icon("soup")
+var Sparkles = Icon("sparkles")
+var Speech = Icon("speech")
+var Speaker = Icon("speaker")
+var Spade = Icon("spade")
+var Sparkle = Icon("sparkle")
+var Split = Icon("split")
+var SpellCheck2 = Icon("spell-check-2")
+var SpellCheck = Icon("spell-check")
+var Siren = Icon("siren")
+var Spotlight = Icon("spotlight")
+var Spline = Icon("spline")
+var Sprout = Icon("sprout")
+var SprayCan = Icon("spray-can")
+var SquareActivity = Icon("square-activity")
+var SquareArrowDownLeft = Icon("square-arrow-down-left")
+var Signature = Icon("signature")
+var SquareArrowDown = Icon("square-arrow-down")
+var SquareArrowOutDownLeft = Icon("square-arrow-out-down-left")
+var SquareArrowLeft = Icon("square-arrow-left")
+var SquareArrowRight = Icon("square-arrow-right")
+var SquareArrowOutDownRight = Icon("square-arrow-out-down-right")
+var SquareBottomDashedScissors = Icon("square-bottom-dashed-scissors")
+var SquareArrowUpLeft = Icon("square-arrow-up-left")
+var SquareArrowUpRight = Icon("square-arrow-up-right")
+var SquareArrowUp = Icon("square-arrow-up")
+var SquareAsterisk = Icon("square-asterisk")
+var SquareArrowOutUpLeft = Icon("square-arrow-out-up-left")
+var SquareArrowOutUpRight = Icon("square-arrow-out-up-right")
+var SquareDashedMousePointer = Icon("square-dashed-mouse-pointer")
+var SquareCode = Icon("square-code")
+var SquareDashedBottomCode = Icon("square-dashed-bottom-code")
+var SquareDashedBottom = Icon("square-dashed-bottom")
+var SquareDashedKanban = Icon("square-dashed-kanban")
+var SquareDivide = Icon("square-divide")
+var SquareDashedTopSolid = Icon("square-dashed-top-solid")
+var SquareDashed = Icon("square-dashed")
+var SquareDot = Icon("square-dot")
+var SplinePointer = Icon("spline-pointer")
+var SquareCheck = Icon("square-check")
+var SquareChevronLeft = Icon("square-chevron-left")
+var SquareChartGantt = Icon("square-chart-gantt")
+var SquareChevronUp = Icon("square-chevron-up")
+var SquareChevronRight = Icon("square-chevron-right")
+var SquareM = Icon("square-m")
+var SquareEqual = Icon("square-equal")
+var SquareKanban = Icon("square-kanban")
+var SquareLibrary = Icon("square-library")
+var SquareParking = Icon("square-parking")
+var SquareMenu = Icon("square-menu")
+var SquareFunction = Icon("square-function")
+var SquareMinus = Icon("square-minus")
+var SquareMousePointer = Icon("square-mouse-pointer")
+var SquareParkingOff = Icon("square-parking-off")
+var SquarePi = Icon("square-pi")
+var SquarePause = Icon("square-pause")
+var SquarePen = Icon("square-pen")
+var SquareCheckBig = Icon("square-check-big")
+var SquarePercent = Icon("square-percent")
+var SquarePlay = Icon("square-play")
+var SquarePilcrow = Icon("square-pilcrow")
+var SquarePower = Icon("square-power")
+var SquarePlus = Icon("square-plus")
+var SquareScissors = Icon("square-scissors")
+var SquareRadical = Icon("square-radical")
+var SquareRoundCorner = Icon("square-round-corner")
+var SquareSplitHorizontal = Icon("square-split-horizontal")
+var SquareSlash = Icon("square-slash")
+var SquareSplitVertical = Icon("square-split-vertical")
+var SquareChevronDown = Icon("square-chevron-down")
+var SquareSquare = Icon("square-square")
+var SquareStack = Icon("square-stack")
+var SquareSigma = Icon("square-sigma")
+var SquareStop = Icon("square-stop")
+var SquareStar = Icon("square-star")
+var SquareUserRound = Icon("square-user-round")
+var SquareTerminal = Icon("square-terminal")
+var SquareUser = Icon("square-user")
+var SquareX = Icon("square-x")
+var SquareArrowDownRight = Icon("square-arrow-down-right")
+var Squirrel = Icon("squirrel")
+var Square = Icon("square")
+var SquaresExclude = Icon("squares-exclude")
+var StretchVertical = Icon("stretch-vertical")
+var SquaresIntersect = Icon("squares-intersect")
+var Sticker = Icon("sticker")
+var SquaresSubtract = Icon("squares-subtract")
+var StickyNote = Icon("sticky-note")
+var SquaresUnite = Icon("squares-unite")
+var Store = Icon("store")
+var StretchHorizontal = Icon("stretch-horizontal")
+var SunMedium = Icon("sun-medium")
+var Strikethrough = Icon("strikethrough")
+var Subscript = Icon("subscript")
+var SunSnow = Icon("sun-snow")
+var SunMoon = Icon("sun-moon")
+var Squircle = Icon("squircle")
+var StarOff = Icon("star-off")
+var Stamp = Icon("stamp")
+var StarHalf = Icon("star-half")
+var SwissFranc = Icon("swiss-franc")
+var Sun = Icon("sun")
+var Sunrise = Icon("sunrise")
+var Sunset = Icon("sunset")
+var Superscript = Icon("superscript")
+var SwatchBook = Icon("swatch-book")
+var StepBack = Icon("step-back")
+var Star = Icon("star")
+var TableCellsSplit = Icon("table-cells-split")
+var SwitchCamera = Icon("switch-camera")
+var Sword = Icon("sword")
+var Swords = Icon("swords")
+var Syringe = Icon("syringe")
+var Table2 = Icon("table-2")
+var TableCellsMerge = Icon("table-cells-merge")
+var StepForward = Icon("step-forward")
+var Table = Icon("table")
+var TableColumnsSplit = Icon("table-columns-split")
+var Stethoscope = Icon("stethoscope")
+var TableProperties = Icon("table-properties")
+var TableRowsSplit = Icon("table-rows-split")
+var Tablet = Icon("tablet")
+var TabletSmartphone = Icon("tablet-smartphone")
+var Tags = Icon("tags")
+var Tablets = Icon("tablets")
+var Tag = Icon("tag")
+var Tally3 = Icon("tally-3")
+var Tally1 = Icon("tally-1")
+var Tally2 = Icon("tally-2")
+var Target = Icon("target")
+var Tally4 = Icon("tally-4")
+var Tally5 = Icon("tally-5")
+var Tangent = Icon("tangent")
+var Terminal = Icon("terminal")
+var Telescope = Icon("telescope")
+var Tent = Icon("tent")
+var TentTree = Icon("tent-tree")
+var TestTube = Icon("test-tube")
+var TestTubeDiagonal = Icon("test-tube-diagonal")
+var TestTubes = Icon("test-tubes")
+var TextAlignCenter = Icon("text-align-center")
+var TextAlignEnd = Icon("text-align-end")
+var TextAlignJustify = Icon("text-align-justify")
+var TextAlignStart = Icon("text-align-start")
+var TextQuote = Icon("text-quote")
+var TextCursorInput = Icon("text-cursor-input")
+var TextCursor = Icon("text-cursor")
+var TextInitial = Icon("text-initial")
+var ThermometerSnowflake = Icon("thermometer-snowflake")
+var TextSearch = Icon("text-search")
+var TextSelect = Icon("text-select")
+var TextWrap = Icon("text-wrap")
+var Theater = Icon("theater")
+var Thermometer = Icon("thermometer")
+var ThermometerSun = Icon("thermometer-sun")
+var TableOfContents = Icon("table-of-contents")
+var ThumbsUp = Icon("thumbs-up")
+var TicketCheck = Icon("ticket-check")
+var TicketMinus = Icon("ticket-minus")
+var ThumbsDown = Icon("thumbs-down")
+var TicketSlash = Icon("ticket-slash")
+var TicketPercent = Icon("ticket-percent")
+var TicketPlus = Icon("ticket-plus")
+var TimerOff = Icon("timer-off")
+var TicketX = Icon("ticket-x")
+var Ticket = Icon("ticket")
+var RectangleVertical = Icon("rectangle-vertical")
+var ToggleLeft = Icon("toggle-left")
+var TimerReset = Icon("timer-reset")
+var Timer = Icon("timer")
+var Toilet = Icon("toilet")
+var ToggleRight = Icon("toggle-right")
+var Tickets = Icon("tickets")
+var ToolCase = Icon("tool-case")
+var Tornado = Icon("tornado")
+var Torus = Icon("torus")
+var TouchpadOff = Icon("touchpad-off")
+var Touchpad = Icon("touchpad")
+var TowerControl = Icon("tower-control")
+var ToyBrick = Icon("toy-brick")
+var Tractor = Icon("tractor")
+var TicketsPlane = Icon("tickets-plane")
+var TrainFront = Icon("train-front")
+var TrafficCone = Icon("traffic-cone")
+var TrainFrontTunnel = Icon("train-front-tunnel")
+var Transgender = Icon("transgender")
+var TrainTrack = Icon("train-track")
+var Trash = Icon("trash")
+var TramFront = Icon("tram-front")
+var Trash2 = Icon("trash-2")
+var Trees = Icon("trees")
+var TreePalm = Icon("tree-palm")
+var TreePine = Icon("tree-pine")
+var TrendingUp = Icon("trending-up")
+var Trello = Icon("trello")
+var TrendingDown = Icon("trending-down")
+var TrendingUpDown = Icon("trending-up-down")
+var TriangleRight = Icon("triangle-right")
+var Trophy = Icon("trophy")
+var TriangleAlert = Icon("triangle-alert")
+var Triangle = Icon("triangle")
+var TriangleDashed = Icon("triangle-dashed")
+var Turntable = Icon("turntable")
+var TruckElectric = Icon("truck-electric")
+var Truck = Icon("truck")
+var TurkishLira = Icon("turkish-lira")
+var Tv = Icon("tv")
+var Turtle = Icon("turtle")
+var TvMinimal = Icon("tv-minimal")
+var UmbrellaOff = Icon("umbrella-off")
+var Twitch = Icon("twitch")
+var Twitter = Icon("twitter")
+var TypeOutline = Icon("type-outline")
+var Type = Icon("type")
+var UndoDot = Icon("undo-dot")
+var Umbrella = Icon("umbrella")
+var Underline = Icon("underline")
+var Undo2 = Icon("undo-2")
+var UnfoldHorizontal = Icon("unfold-horizontal")
+var Undo = Icon("undo")
+var TreeDeciduous = Icon("tree-deciduous")
+var Ungroup = Icon("ungroup")
+var University = Icon("university")
+var Unlink2 = Icon("unlink-2")
+var Unlink = Icon("unlink")
+var Unplug = Icon("unplug")
+var UserCheck = Icon("user-check")
+var Play = Icon("play")
+var UserCog = Icon("user-cog")
+var Usb = Icon("usb")
+var TvMinimalPlay = Icon("tv-minimal-play")
+var UserRoundCog = Icon("user-round-cog")
+var UserRoundCheck = Icon("user-round-check")
+var UserRoundPen = Icon("user-round-pen")
+var UserRoundMinus = Icon("user-round-minus")
+var UserRoundSearch = Icon("user-round-search")
+var UserRoundPlus = Icon("user-round-plus")
+var UserMinus = Icon("user-minus")
+var UserLock = Icon("user-lock")
+var UserPen = Icon("user-pen")
+var UserX = Icon("user-x")
+var UserRoundX = Icon("user-round-x")
+var UserRound = Icon("user-round")
+var UserSearch = Icon("user-search")
+var UserStar = Icon("user-star")
+var UsersRound = Icon("users-round")
+var User = Icon("user")
+var UtensilsCrossed = Icon("utensils-crossed")
+var Users = Icon("users")
+var Utensils = Icon("utensils")
+var UtilityPole = Icon("utility-pole")
+var Variable = Icon("variable")
+var Vault = Icon("vault")
+var UserPlus = Icon("user-plus")
+var VolumeOff = Icon("volume-off")
+var VectorSquare = Icon("vector-square")
+var Vegan = Icon("vegan")
+var VenetianMask = Icon("venetian-mask")
+var VenusAndMars = Icon("venus-and-mars")
+var Venus = Icon("venus")
+var VibrateOff = Icon("vibrate-off")
+var Vibrate = Icon("vibrate")
+var VideoOff = Icon("video-off")
+var Video = Icon("video")
+var Videotape = Icon("videotape")
+var View = Icon("view")
+var Voicemail = Icon("voicemail")
+var Volleyball = Icon("volleyball")
+var Volume1 = Icon("volume-1")
+var Volume2 = Icon("volume-2")
+var Wheat = Icon("wheat")
+var Upload = Icon("upload")
+var WifiPen = Icon("wifi-pen")
+var WashingMachine = Icon("washing-machine")
+var VolumeX = Icon("volume-x")
+var Volume = Icon("volume")
+var Vote = Icon("vote")
+var WalletCards = Icon("wallet-cards")
+var WalletMinimal = Icon("wallet-minimal")
+var Wallet = Icon("wallet")
+var Wallpaper = Icon("wallpaper")
+var WandSparkles = Icon("wand-sparkles")
+var Wand = Icon("wand")
+var Warehouse = Icon("warehouse")
+var Webcam = Icon("webcam")
+var WavesLadder = Icon("waves-ladder")
+var Waves = Icon("waves")
+var Waypoints = Icon("waypoints")
+var WifiHigh = Icon("wifi-high")
+var WifiCog = Icon("wifi-cog")
+var WifiLow = Icon("wifi-low")
+var ZoomOut = Icon("zoom-out")
+var Worm = Icon("worm")
+var Wrench = Icon("wrench")
+var X = Icon("x")
+var Youtube = Icon("youtube")
+var ZapOff = Icon("zap-off")
+var Zap = Icon("zap")
+var ZoomIn = Icon("zoom-in")
+var Webhook = Icon("webhook")
+var WebhookOff = Icon("webhook-off")
+var WindArrowDown = Icon("wind-arrow-down")
+var WifiSync = Icon("wifi-sync")
+var WifiZero = Icon("wifi-zero")
+var Wifi = Icon("wifi")
+var Weight = Icon("weight")
+var WineOff = Icon("wine-off")
+var Wind = Icon("wind")
+var Wine = Icon("wine")
+var WifiOff = Icon("wifi-off")
+var WheatOff = Icon("wheat-off")
+var WholeWord = Icon("whole-word")
+var Workflow = Icon("workflow")
+var Watch = Icon("watch")
+var SunDim = Icon("sun-dim")
+var SquircleDashed = Icon("squircle-dashed")
+var Spool = Icon("spool")
+var UnfoldVertical = Icon("unfold-vertical")
+var PcCase = Icon("pc-case")
diff --git a/internal/ui/components/input/input.templ b/internal/ui/components/input/input.templ
new file mode 100644
index 0000000..adae3e4
--- /dev/null
+++ b/internal/ui/components/input/input.templ
@@ -0,0 +1,130 @@
+// templui component input - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/input
+package input
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/button"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Type string
+
+const (
+ TypeText Type = "text"
+ TypePassword Type = "password"
+ TypeEmail Type = "email"
+ TypeNumber Type = "number"
+ TypeTel Type = "tel"
+ TypeURL Type = "url"
+ TypeSearch Type = "search"
+ TypeDate Type = "date"
+ TypeDateTime Type = "datetime-local"
+ TypeTime Type = "time"
+ TypeFile Type = "file"
+ TypeColor Type = "color"
+ TypeWeek Type = "week"
+ TypeMonth Type = "month"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Name string
+ Type Type
+ Form string
+ Placeholder string
+ Value string
+ Disabled bool
+ Readonly bool
+ FileAccept string
+ HasError bool
+ NoTogglePassword bool
+}
+
+templ Input(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.Type == "" {
+ {{ p.Type = TypeText }}
+ }
+ if p.ID == "" {
+ {{ p.ID = utils.RandomID() }}
+ }
+
+
+ if p.Type == TypePassword && !p.NoTogglePassword {
+ @button.Button(button.Props{
+ Size: button.SizeIcon,
+ Variant: button.VariantGhost,
+ Class: "absolute right-0 top-1/2 -translate-y-1/2 opacity-50 cursor-pointer",
+ Attributes: templ.Attributes{"data-tui-input-toggle-password": p.ID},
+ }) {
+
+ @icon.Eye(icon.Props{
+ Size: 18,
+ })
+
+
+ @icon.EyeOff(icon.Props{
+ Size: 18,
+ })
+
+ }
+ }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/inputotp/inputotp.templ b/internal/ui/components/inputotp/inputotp.templ
new file mode 100644
index 0000000..f898183
--- /dev/null
+++ b/internal/ui/components/inputotp/inputotp.templ
@@ -0,0 +1,181 @@
+// templui component inputotp - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/input-otp
+package inputotp
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+ "strconv"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Value string
+ Name string
+ Form string
+ HasError bool
+}
+
+type GroupProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type SlotProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Index int
+ Type string
+ Placeholder string
+ Disabled bool
+ HasError bool
+}
+
+type SeparatorProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+templ InputOTP(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+
+ { children... }
+
+}
+
+templ Group(props ...GroupProps) {
+ {{ var p GroupProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Slot(props ...SlotProps) {
+ {{ var p SlotProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.Type == "" {
+ {{ p.Type = "text" }}
+ }
+
+
+
+}
+
+templ Separator(props ...SeparatorProps) {
+ {{ var p SeparatorProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ -
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/label/label.templ b/internal/ui/components/label/label.templ
new file mode 100644
index 0000000..5a87dbe
--- /dev/null
+++ b/internal/ui/components/label/label.templ
@@ -0,0 +1,43 @@
+// templui component label - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/label
+package label
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ For string
+ Error string
+}
+
+templ Label(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/pagination/pagination.templ b/internal/ui/components/pagination/pagination.templ
new file mode 100644
index 0000000..91926df
--- /dev/null
+++ b/internal/ui/components/pagination/pagination.templ
@@ -0,0 +1,250 @@
+// templui component pagination - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/pagination
+package pagination
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/button"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ItemProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type LinkProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Href string
+ IsActive bool
+ Disabled bool
+}
+
+type PreviousProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Href string
+ Disabled bool
+ Label string
+}
+
+type NextProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Href string
+ Disabled bool
+ Label string
+}
+
+templ Pagination(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ Item(props ...ItemProps) {
+ {{ var p ItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Link(props ...LinkProps) {
+ {{ var p LinkProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.Disabled {
+ @button.Button(button.Props{
+ ID: p.ID,
+ Disabled: true,
+ Size: button.SizeIcon,
+ Variant: button.VariantGhost,
+ Class: p.Class,
+ Attributes: p.Attributes,
+ }) {
+ { children... }
+ }
+ } else {
+ @button.Button(button.Props{
+ ID: p.ID,
+ Href: p.Href,
+ Size: button.SizeIcon,
+ Variant: button.Variant(buttonVariant(p.IsActive)),
+ Class: p.Class,
+ Attributes: p.Attributes,
+ }) {
+ { children... }
+ }
+ }
+}
+
+templ Previous(props ...PreviousProps) {
+ {{ var p PreviousProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ @button.Button(button.Props{
+ ID: p.ID,
+ Href: p.Href,
+ Disabled: p.Disabled,
+ Variant: button.VariantGhost,
+ Class: utils.TwMerge("gap-1", p.Class),
+ Attributes: p.Attributes,
+ }) {
+ @icon.ChevronLeft(icon.Props{Size: 16})
+ if p.Label != "" {
+ { p.Label }
+ }
+ }
+}
+
+templ Next(props ...NextProps) {
+ {{ var p NextProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ @button.Button(button.Props{
+ ID: p.ID,
+ Href: p.Href,
+ Disabled: p.Disabled,
+ Variant: button.VariantGhost,
+ Class: utils.TwMerge("gap-1", p.Class),
+ Attributes: p.Attributes,
+ }) {
+ if p.Label != "" {
+ { p.Label }
+ }
+ @icon.ChevronRight(icon.Props{Size: 16})
+ }
+}
+
+templ Ellipsis() {
+ @icon.Ellipsis(icon.Props{Size: 16})
+}
+
+func CreatePagination(currentPage, totalPages, maxVisible int) struct {
+ CurrentPage int
+ TotalPages int
+ Pages []int
+ HasPrevious bool
+ HasNext bool
+} {
+ if currentPage < 1 {
+ currentPage = 1
+ }
+ if totalPages < 1 {
+ totalPages = 1
+ }
+ if currentPage > totalPages {
+ currentPage = totalPages
+ }
+ if maxVisible < 1 {
+ maxVisible = 5
+ }
+
+ start, end := calculateVisibleRange(currentPage, totalPages, maxVisible)
+ pages := make([]int, 0, end-start+1)
+ for i := start; i <= end; i++ {
+ pages = append(pages, i)
+ }
+
+ return struct {
+ CurrentPage int
+ TotalPages int
+ Pages []int
+ HasPrevious bool
+ HasNext bool
+ }{
+ CurrentPage: currentPage,
+ TotalPages: totalPages,
+ Pages: pages,
+ HasPrevious: currentPage > 1,
+ HasNext: currentPage < totalPages,
+ }
+}
+
+func calculateVisibleRange(currentPage, totalPages, maxVisible int) (int, int) {
+ if totalPages <= maxVisible {
+ return 1, totalPages
+ }
+
+ half := maxVisible / 2
+ start := currentPage - half
+ end := currentPage + half
+
+ if start < 1 {
+ end += (1 - start)
+ start = 1
+ }
+
+ if end > totalPages {
+ start -= (end - totalPages)
+ if start < 1 {
+ start = 1
+ }
+ end = totalPages
+ }
+
+ return start, end
+}
+
+func buttonVariant(isActive bool) button.Variant {
+ if isActive {
+ return button.VariantOutline
+ }
+ return button.VariantGhost
+}
diff --git a/internal/ui/components/popover/popover.templ b/internal/ui/components/popover/popover.templ
new file mode 100644
index 0000000..2f7ba4f
--- /dev/null
+++ b/internal/ui/components/popover/popover.templ
@@ -0,0 +1,135 @@
+// templui component popover - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/popover
+package popover
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+ "strconv"
+)
+
+type Placement string
+
+const (
+ PlacementTop Placement = "top"
+ PlacementTopStart Placement = "top-start"
+ PlacementTopEnd Placement = "top-end"
+ PlacementRight Placement = "right"
+ PlacementRightStart Placement = "right-start"
+ PlacementRightEnd Placement = "right-end"
+ PlacementBottom Placement = "bottom"
+ PlacementBottomStart Placement = "bottom-start"
+ PlacementBottomEnd Placement = "bottom-end"
+ PlacementLeft Placement = "left"
+ PlacementLeftStart Placement = "left-start"
+ PlacementLeftEnd Placement = "left-end"
+)
+
+type TriggerType string
+
+const (
+ TriggerTypeHover TriggerType = "hover"
+ TriggerTypeClick TriggerType = "click"
+)
+
+type TriggerProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ For string
+ TriggerType TriggerType
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Placement Placement
+ Offset int
+ DisableClickAway bool
+ DisableESC bool
+ ShowArrow bool
+ HoverDelay int
+ HoverOutDelay int
+ MatchWidth bool
+ Exclusive bool
+}
+
+templ Trigger(props ...TriggerProps) {
+ {{ var p TriggerProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.TriggerType == "" {
+ {{ p.TriggerType = TriggerTypeClick }}
+ }
+
+ { children... }
+
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.Placement == "" {
+ {{ p.Placement = PlacementBottom }}
+ }
+ if p.Offset == 0 {
+ if p.ShowArrow {
+ {{ p.Offset = 8 }}
+ } else {
+ {{ p.Offset = 4 }}
+ }
+ }
+
+
+ { children... }
+
+ if p.ShowArrow {
+
+ }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/progress/progress.templ b/internal/ui/components/progress/progress.templ
new file mode 100644
index 0000000..0ef10c7
--- /dev/null
+++ b/internal/ui/components/progress/progress.templ
@@ -0,0 +1,127 @@
+// templui component progress - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/progress
+package progress
+
+import (
+ "fmt"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Size string
+type Variant string
+
+const (
+ SizeSm Size = "sm"
+ SizeLg Size = "lg"
+)
+
+const (
+ VariantDefault Variant = "default"
+ VariantSuccess Variant = "success"
+ VariantDanger Variant = "danger"
+ VariantWarning Variant = "warning"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Max int
+ Value int
+ Label string
+ ShowValue bool
+ Size Size
+ Variant Variant
+ BarClass string
+}
+
+templ Progress(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.ID == "" {
+ {{ p.ID = utils.RandomID() }}
+ }
+
+ if p.Label != "" || p.ShowValue {
+
+ if p.Label != "" {
+ { p.Label }
+ }
+ if p.ShowValue {
+
+ { fmt.Sprintf("%d%%", percentage(p.Value, p)) }
+
+ }
+
+ }
+
+
+}
+
+func maxValue(max int) int {
+ if max <= 0 {
+ return 100
+ }
+ return max
+}
+
+func percentage(value int, props Props) int {
+ max := maxValue(props.Max)
+ if value < 0 {
+ value = 0
+ }
+ if value > max {
+ value = max
+ }
+ return (value * 100) / max
+}
+
+func sizeClasses(size Size) string {
+ switch size {
+ case SizeSm:
+ return "h-1"
+ case SizeLg:
+ return "h-4"
+ default:
+ return "h-2.5"
+ }
+}
+
+func variantClasses(variant Variant) string {
+ switch variant {
+ case VariantSuccess:
+ return "bg-green-500"
+ case VariantDanger:
+ return "bg-destructive"
+ case VariantWarning:
+ return "bg-yellow-500"
+ default:
+ return "bg-primary"
+ }
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/radio/radio.templ b/internal/ui/components/radio/radio.templ
new file mode 100644
index 0000000..5f73b70
--- /dev/null
+++ b/internal/ui/components/radio/radio.templ
@@ -0,0 +1,57 @@
+// templui component radio - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/radio
+package radio
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Name string
+ Value string
+ Form string
+ Disabled bool
+ Checked bool
+}
+
+templ Radio(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
diff --git a/internal/ui/components/rating/rating.templ b/internal/ui/components/rating/rating.templ
new file mode 100644
index 0000000..f3a4966
--- /dev/null
+++ b/internal/ui/components/rating/rating.templ
@@ -0,0 +1,193 @@
+// templui component rating - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/rating
+package rating
+
+import (
+ "fmt"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+ "strconv"
+)
+
+type Style string
+
+const (
+ StyleStar Style = "star"
+ StyleHeart Style = "heart"
+ StyleEmoji Style = "emoji"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Value float64
+ ReadOnly bool
+ Precision float64
+ Name string
+ Form string
+ OnlyInteger bool
+}
+
+type GroupProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ItemProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Value int
+ Style Style
+}
+
+templ Rating(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ {{ p.setDefaults() }}
+
+ { children... }
+ if p.Name != "" {
+
+ }
+
+}
+
+templ Group(props ...GroupProps) {
+ {{ var p GroupProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Item(props ...ItemProps) {
+ {{ var p ItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ {{ p.setDefaults() }}
+
+
+ @ratingIcon(p.Style, false, float64(p.Value))
+
+
+ @ratingIcon(p.Style, true, float64(p.Value))
+
+
+}
+
+func colorClass(style Style) string {
+ switch style {
+ case StyleHeart:
+ return "text-destructive"
+ case StyleEmoji:
+ return "text-yellow-500"
+ default:
+ return "text-yellow-400"
+ }
+}
+
+func ratingIcon(style Style, filled bool, value float64) templ.Component {
+ if style == StyleEmoji {
+ if filled {
+ switch {
+ case value <= 1:
+ return icon.Angry()
+ case value <= 2:
+ return icon.Frown()
+ case value <= 3:
+ return icon.Meh()
+ case value <= 4:
+ return icon.Smile()
+ default:
+ return icon.Laugh()
+ }
+ }
+ return icon.Meh()
+ }
+ iconProps := icon.Props{}
+ if filled {
+ iconProps.Fill = "currentColor"
+ }
+ switch style {
+ case StyleHeart:
+ return icon.Heart(iconProps)
+ default:
+ return icon.Star(iconProps)
+ }
+}
+
+func (p *ItemProps) setDefaults() {
+ if p.Style == "" {
+ p.Style = StyleStar
+ }
+}
+
+func (p *Props) setDefaults() {
+ if p.Precision <= 0 {
+ p.Precision = 1.0
+ }
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/selectbox/selectbox.templ b/internal/ui/components/selectbox/selectbox.templ
new file mode 100644
index 0000000..82b7079
--- /dev/null
+++ b/internal/ui/components/selectbox/selectbox.templ
@@ -0,0 +1,325 @@
+// templui component selectbox - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/select-box
+package selectbox
+
+import (
+ "context"
+ "fmt"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/button"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/input"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/popover"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+ "strconv"
+)
+
+type contextKey string
+
+var contentIDKey contextKey = "contentID"
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Multiple bool
+}
+
+type TriggerProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Name string
+ Form string
+ Disabled bool
+ HasError bool
+ Multiple bool
+ ShowPills bool
+ SelectedCountText string
+}
+
+type ValueProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Placeholder string
+ Multiple bool
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ NoSearch bool
+ SearchPlaceholder string
+}
+
+type GroupProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type LabelProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ItemProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Value string
+ Selected bool
+ Disabled bool
+}
+
+templ SelectBox(props ...Props) {
+ {{
+ var p Props
+ if len(props) > 0 {
+ p = props[0]
+ }
+ wrapperID := p.ID
+ if wrapperID == "" {
+ wrapperID = utils.RandomID()
+ }
+ contentID := fmt.Sprintf("%s-content", wrapperID)
+ ctx = context.WithValue(ctx, contentIDKey, contentID)
+ }}
+
+ { children... }
+
+}
+
+templ Trigger(props ...TriggerProps) {
+ {{
+ var p TriggerProps
+ if len(props) > 0 {
+ p = props[0]
+ }
+ contentID, ok := ctx.Value(contentIDKey).(string)
+ if !ok {
+ contentID = "fallback-select-content-id"
+ }
+ if p.ShowPills {
+ p.Multiple = true
+ }
+ }}
+ @popover.Trigger(popover.TriggerProps{
+ For: contentID,
+ TriggerType: popover.TriggerTypeClick,
+ }) {
+ @button.Button(button.Props{
+ ID: p.ID,
+ Type: "button",
+ Variant: button.VariantOutline,
+ Class: utils.TwMerge(
+ // Required class for JavaScript
+ "select-trigger",
+ // Base styles matching input
+ "w-full h-9 px-3 py-1 text-base md:text-sm",
+ "flex items-center justify-between",
+ "rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] outline-none",
+ // Dark mode background
+ "dark:bg-input/30",
+ // Selection styles
+ "selection:bg-primary selection:text-primary-foreground",
+ // Focus styles
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
+ // Error/Invalid styles
+ "aria-invalid:ring-destructive/20 aria-invalid:border-destructive dark:aria-invalid:ring-destructive/40",
+ utils.If(p.HasError, "border-destructive ring-destructive/20 dark:ring-destructive/40"),
+ p.Class,
+ ),
+ Disabled: p.Disabled,
+ Attributes: utils.MergeAttributes(
+ templ.Attributes{
+ "data-tui-selectbox-content-id": contentID,
+ "data-tui-selectbox-multiple": strconv.FormatBool(p.Multiple),
+ "data-tui-selectbox-show-pills": strconv.FormatBool(p.ShowPills),
+ "data-tui-selectbox-selected-count-text": p.SelectedCountText,
+ "tabindex": "0",
+ "aria-invalid": utils.If(p.HasError, "true"),
+ },
+ ),
+ }) {
+
+ { children... }
+
+ @icon.ChevronDown(icon.Props{
+ Size: 16,
+ Class: "text-muted-foreground",
+ })
+
+ }
+ }
+}
+
+templ Value(props ...ValueProps) {
+ {{ var p ValueProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ if p.Placeholder != "" {
+ { p.Placeholder }
+ }
+ { children... }
+
+}
+
+templ Content(props ...ContentProps) {
+ {{
+ var p ContentProps
+ if len(props) > 0 {
+ p = props[0]
+ }
+ contentID, ok := ctx.Value(contentIDKey).(string)
+ if !ok {
+ contentID = "fallback-select-content-id"
+ }
+ }}
+ @popover.Content(popover.ContentProps{
+ ID: contentID,
+ Placement: popover.PlacementBottomStart,
+ Offset: 4,
+ MatchWidth: true,
+ DisableESC: !p.NoSearch,
+ Class: utils.TwMerge(
+ "p-1 select-content z-50 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
+ "min-w-[var(--popover-trigger-width)] w-[var(--popover-trigger-width)]",
+ p.Class,
+ ),
+ Attributes: utils.MergeAttributes(
+ templ.Attributes{
+ "role": "listbox",
+ "tabindex": "-1",
+ },
+ p.Attributes,
+ ),
+ Exclusive: true,
+ }) {
+ if !p.NoSearch {
+
+
+
+ @icon.Search(icon.Props{Size: 16})
+
+ @input.Input(input.Props{
+ Type: input.TypeSearch,
+ Class: "pl-8",
+ Placeholder: utils.IfElse(p.SearchPlaceholder != "", p.SearchPlaceholder, "Search..."),
+ Attributes: templ.Attributes{
+ "data-tui-selectbox-search": "",
+ },
+ })
+
+
+ }
+
+ { children... }
+
+ }
+}
+
+templ Group(props ...GroupProps) {
+ {{ var p GroupProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Label(props ...LabelProps) {
+ {{ var p LabelProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Item(props ...ItemProps) {
+ {{ var p ItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+
+ { children... }
+
+
+ @icon.Check(icon.Props{Size: 16})
+
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/separator/separator.templ b/internal/ui/components/separator/separator.templ
new file mode 100644
index 0000000..15f0c05
--- /dev/null
+++ b/internal/ui/components/separator/separator.templ
@@ -0,0 +1,98 @@
+// templui component separator - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/separator
+package separator
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Orientation string
+type Decoration string
+
+const (
+ OrientationHorizontal Orientation = "horizontal"
+ OrientationVertical Orientation = "vertical"
+)
+
+const (
+ DecorationDashed Decoration = "dashed"
+ DecorationDotted Decoration = "dotted"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Orientation Orientation
+ Decoration Decoration
+}
+
+templ Separator(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.Orientation == "" {
+ {{ p.Orientation = OrientationHorizontal }}
+ }
+ if p.Orientation == OrientationHorizontal {
+
+
+
+
+ { children... }
+
+
+
+ } else {
+
+
+
+
+ { children... }
+
+
+
+ }
+}
+
+func decorationClasses(decoration Decoration) string {
+ switch decoration {
+ case DecorationDashed:
+ return "border-dashed"
+ case DecorationDotted:
+ return "border-dotted"
+ default:
+ return ""
+ }
+}
diff --git a/internal/ui/components/sheet/sheet.templ b/internal/ui/components/sheet/sheet.templ
new file mode 100644
index 0000000..42f2186
--- /dev/null
+++ b/internal/ui/components/sheet/sheet.templ
@@ -0,0 +1,318 @@
+// templui component sheet - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/sheet
+package sheet
+
+import (
+ "context"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/dialog"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type contextKey string
+
+const (
+ sideKey contextKey = "sheetSide"
+)
+
+type Side string
+
+const (
+ SideTop Side = "top"
+ SideRight Side = "right"
+ SideBottom Side = "bottom"
+ SideLeft Side = "left"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Side Side
+ Open bool
+ DisableClickAway bool
+ DisableESC bool
+}
+
+type TriggerProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ For string // Reference to a specific sheet ID (for external triggers)
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ HideCloseButton bool
+ Side Side //
+ Open bool // Initial open state for standalone usage
+}
+
+type HeaderProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type FooterProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type TitleProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type DescriptionProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type CloseProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ For string
+}
+
+templ Sheet(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.Side == "" {
+ {{ p.Side = SideRight }}
+ }
+ // Pass the Side through context to child components
+ {{ ctx = context.WithValue(ctx, sideKey, p.Side) }}
+ // Sheet uses Dialog internally with sheet-specific attributes
+ @dialog.Dialog(dialog.Props{
+ ID: p.ID,
+ Open: p.Open,
+ DisableClickAway: p.DisableClickAway,
+ DisableESC: p.DisableESC,
+ Class: p.Class,
+ Attributes: utils.MergeAttributes(
+ templ.Attributes{
+ "data-tui-sheet": "true",
+ "data-tui-sheet-side": string(p.Side),
+ },
+ p.Attributes,
+ ),
+ }) {
+ { children... }
+ }
+}
+
+templ Trigger(props ...TriggerProps) {
+ {{ var p TriggerProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ // Sheet trigger is just a Dialog trigger
+ @dialog.Trigger(dialog.TriggerProps{
+ ID: p.ID,
+ For: p.For,
+ Class: p.Class,
+ Attributes: p.Attributes,
+ }) {
+ { children... }
+ }
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ // Get Side from context if not explicitly provided
+ if p.Side == "" {
+ if val := ctx.Value(sideKey); val != nil {
+ {{ p.Side = val.(Side) }}
+ } else {
+ {{ p.Side = SideRight }}
+ }
+ }
+ // Sheet content uses Dialog content with sheet-specific styles
+ @dialog.Content(dialog.ContentProps{
+ ID: p.ID,
+ Open: p.Open,
+ HideCloseButton: p.HideCloseButton,
+ Class: utils.TwMerge(
+ // First apply side-specific positioning and animations
+ getSideClasses(p.Side),
+ // Default gap matching shadcn (no padding in content)
+ "gap-4 !p-0", // Remove Dialog's p-6 padding
+ // Override Dialog styles
+ "!scale-100", // Reset Dialog's scale animation
+ "!rounded-none", // Remove dialog rounded corners
+ "!opacity-100", // Keep fully opaque - no fade, only slide
+ // Remove pointer-events control during animation
+ "!pointer-events-auto data-[tui-dialog-hidden=true]:!pointer-events-none",
+ // User-provided classes last
+ p.Class,
+ ),
+ Attributes: utils.MergeAttributes(
+ templ.Attributes{
+ "data-tui-sheet-content": "true",
+ "data-tui-sheet-side": string(p.Side),
+ },
+ p.Attributes,
+ ),
+ }) {
+ { children... }
+ }
+}
+
+templ Header(props ...HeaderProps) {
+ {{ var p HeaderProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ // Sheet header uses Dialog header but overrides styles
+ @dialog.Header(dialog.HeaderProps{
+ ID: p.ID,
+ Class: utils.TwMerge("gap-1.5 p-4 text-left", p.Class),
+ Attributes: p.Attributes,
+ }) {
+ { children... }
+ }
+}
+
+templ Title(props ...TitleProps) {
+ {{ var p TitleProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ // Sheet title uses Dialog title but overrides styles
+ @dialog.Title(dialog.TitleProps{
+ ID: p.ID,
+ Class: utils.TwMerge("text-base leading-normal", p.Class),
+ Attributes: p.Attributes,
+ }) {
+ { children... }
+ }
+}
+
+templ Description(props ...DescriptionProps) {
+ {{ var p DescriptionProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ // Sheet description uses Dialog description
+ @dialog.Description(dialog.DescriptionProps{
+ ID: p.ID,
+ Class: p.Class,
+ Attributes: p.Attributes,
+ }) {
+ { children... }
+ }
+}
+
+templ Footer(props ...FooterProps) {
+ {{ var p FooterProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ // Sheet footer uses Dialog footer but overrides styles
+ @dialog.Footer(dialog.FooterProps{
+ ID: p.ID,
+ Class: utils.TwMerge("mt-auto flex flex-col gap-2 p-4", p.Class),
+ Attributes: p.Attributes,
+ }) {
+ { children... }
+ }
+}
+
+templ Close(props ...CloseProps) {
+ {{ var p CloseProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ // Sheet close uses Dialog close
+ @dialog.Close(dialog.CloseProps{
+ ID: p.ID,
+ For: p.For,
+ Class: p.Class,
+ Attributes: p.Attributes,
+ }) {
+ { children... }
+ }
+}
+
+func getSideClasses(side Side) string {
+ // Base classes for all sheets - matching shadcn
+ // Duration varies: 300ms when closing, 500ms when opening
+ // Use !transition-transform to override Dialog's transition-all
+ baseClasses := "fixed z-50 flex flex-col bg-background shadow-lg !transition-transform ease-in-out " +
+ "data-[tui-dialog-open=false]:duration-300 data-[tui-dialog-open=true]:duration-500 "
+
+ switch side {
+ case SideRight:
+ return baseClasses +
+ // Positioning
+ "!inset-y-0 !right-0 !left-auto !top-auto " +
+ // Size
+ "h-full w-3/4 sm:max-w-sm " +
+ // Border
+ "border-l border-t-0 border-r-0 border-b-0 " +
+ // Reset Dialog transforms
+ "!translate-y-0 " +
+ // Slide animation
+ "data-[tui-dialog-open=false]:!translate-x-full " +
+ "data-[tui-dialog-open=true]:!translate-x-0"
+ case SideLeft:
+ return baseClasses +
+ // Positioning
+ "!inset-y-0 !left-0 !right-auto !top-auto " +
+ // Size
+ "h-full w-3/4 sm:max-w-sm " +
+ // Border
+ "border-r border-t-0 border-l-0 border-b-0 " +
+ // Reset Dialog transforms
+ "!translate-y-0 " +
+ // Slide animation
+ "data-[tui-dialog-open=false]:!-translate-x-full " +
+ "data-[tui-dialog-open=true]:!translate-x-0"
+ case SideTop:
+ return baseClasses +
+ // Positioning - full width at top
+ "!inset-x-0 !top-0 !bottom-auto !left-0 !right-0 " +
+ // Size - full width, auto height
+ "!w-full !max-w-full h-auto " +
+ // Border
+ "border-b border-t-0 border-l-0 border-r-0 " +
+ // Reset Dialog transforms - IMPORTANT: reset the centering from Dialog
+ "!translate-x-0 !translate-y-0 " +
+ // Slide animation - top slides up when closing
+ "data-[tui-dialog-open=false]:!-translate-y-full " +
+ "data-[tui-dialog-open=true]:!translate-y-0"
+ case SideBottom:
+ return baseClasses +
+ // Positioning - full width at bottom
+ "!inset-x-0 !bottom-0 !top-auto !left-0 !right-0 " +
+ // Size - full width, auto height
+ "!w-full !max-w-full h-auto " +
+ // Border
+ "border-t border-b-0 border-l-0 border-r-0 " +
+ // Reset Dialog transforms - IMPORTANT: reset the centering from Dialog
+ "!translate-x-0 !translate-y-0 " +
+ // Slide animation
+ "data-[tui-dialog-open=false]:!translate-y-full " +
+ "data-[tui-dialog-open=true]:!translate-y-0"
+ default:
+ return baseClasses +
+ // Default to right side
+ "!inset-y-0 !right-0 !left-auto !top-auto " +
+ "h-full w-3/4 " +
+ "border-l border-t-0 border-r-0 border-b-0 " +
+ "!translate-y-0 " +
+ "data-[tui-dialog-open=false]:!translate-x-full " +
+ "data-[tui-dialog-open=true]:!translate-x-0"
+ }
+}
diff --git a/internal/ui/components/sidebar/sidebar.templ b/internal/ui/components/sidebar/sidebar.templ
new file mode 100644
index 0000000..2677b71
--- /dev/null
+++ b/internal/ui/components/sidebar/sidebar.templ
@@ -0,0 +1,753 @@
+// templui component sidebar - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/sidebar
+package sidebar
+
+import "context"
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+import "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+import "git.juancwu.dev/juancwu/budgething/internal/ui/components/button"
+import "git.juancwu.dev/juancwu/budgething/internal/ui/components/sheet"
+import "git.juancwu.dev/juancwu/budgething/internal/ui/components/tooltip"
+
+type contextKey string
+
+const sidebarIDKey contextKey = "sidebar-id"
+
+type Side string
+
+const (
+ SideLeft Side = "left" // default
+ SideRight Side = "right"
+)
+
+type Variant string
+
+const (
+ VariantSidebar Variant = "sidebar" // default
+ VariantFloating Variant = "floating"
+ VariantInset Variant = "inset"
+)
+
+type Collapsible string
+
+const (
+ CollapsibleOffcanvas Collapsible = "offcanvas" // default
+ CollapsibleIcon Collapsible = "icon"
+ CollapsibleNone Collapsible = "none"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Side Side // default: "left"
+ Variant Variant // default: "sidebar"
+ Collapsible Collapsible // default: "offcanvas"
+ Collapsed bool // default: false (sidebar open)
+ KeyboardShortcut string // default: "b"
+}
+
+type TriggerProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Target string // Target sidebar ID to toggle
+}
+
+type HeaderProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type FooterProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type InsetProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type GroupProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type GroupLabelProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type MenuProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type MenuItemProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type MenuButtonSize string
+
+const (
+ MenuButtonSizeDefault MenuButtonSize = "default" // default
+ MenuButtonSizeSm MenuButtonSize = "sm"
+ MenuButtonSizeLg MenuButtonSize = "lg"
+)
+
+type MenuButtonProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Href string
+ IsActive bool
+ Size MenuButtonSize // default: "default"
+ Tooltip string // Tooltip text to show when sidebar is collapsed
+}
+
+type MenuBadgeProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type MenuSubProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type MenuSubItemProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type MenuSubButtonProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Href string
+ IsActive bool
+}
+
+type SeparatorProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type LayoutProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+templ Layout(props ...LayoutProps) {
+ {{ var p LayoutProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ // Generate ID for context (children components can use it for targeting)
+ {{ var sidebarId string = p.ID }}
+ if sidebarId == "" {
+ {{ sidebarId = utils.RandomID() }}
+ }
+ // Set sidebar ID in context for children to access
+ {{ ctx = context.WithValue(ctx, sidebarIDKey, sidebarId) }}
+
+
+ { children... }
+
+}
+
+templ Sidebar(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ // Get sidebar ID from context or use provided/generate new
+ if p.ID == "" {
+ if ctxId := ctx.Value(sidebarIDKey); ctxId != nil {
+ {{ p.ID = ctxId.(string) }}
+ } else {
+ {{ p.ID = utils.RandomID() }}
+ }
+ }
+ if p.Side == "" {
+ {{ p.Side = SideLeft }}
+ }
+ if p.Variant == "" {
+ {{ p.Variant = VariantSidebar }}
+ }
+ if p.Collapsible == "" {
+ {{ p.Collapsible = CollapsibleOffcanvas }}
+ }
+ if p.KeyboardShortcut == "" {
+ {{ p.KeyboardShortcut = "b" }}
+ }
+ // Use the sidebar's ID for mobile sheet and trigger targeting
+ {{ var sidebarId string = p.ID }}
+ {{ sidebarState := "expanded" }}
+ if p.Collapsed {
+ {{ sidebarState = "collapsed" }}
+ }
+
+ {{ var sheetSide sheet.Side }}
+ if p.Side == SideRight {
+ {{ sheetSide = sheet.SideRight }}
+ } else {
+ {{ sheetSide = sheet.SideLeft }}
+ }
+
+ @sheet.Sheet(sheet.Props{
+ ID: sidebarId + "-mobile",
+ Side: sheetSide,
+ Open: false,
+ }) {
+ @sheet.Content(sheet.ContentProps{
+ Class: "md:hidden bg-sidebar text-sidebar-foreground !p-0 !gap-0 flex flex-col h-full",
+ HideCloseButton: true,
+ }) {
+
+
+ }
+ }
+
+
+}
+
+templ Trigger(props ...TriggerProps) {
+ {{ var p TriggerProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ // Get sidebar ID from: 1. Target prop, 2. Context
+ {{ var sidebarId string }}
+ if p.Target != "" {
+ {{ sidebarId = p.Target }}
+ } else if ctxId := ctx.Value(sidebarIDKey); ctxId != nil {
+ {{ sidebarId = ctxId.(string) }}
+ }
+
+ @sheet.Trigger(sheet.TriggerProps{
+ For: sidebarId + "-mobile",
+ Class: "md:hidden",
+ }) {
+ @button.Button(button.Props{
+ Size: button.SizeIcon,
+ Variant: button.VariantGhost,
+ Class: "size-7",
+ }) {
+ @icon.PanelLeft(icon.Props{Class: "size-4"})
+ Toggle Sidebar
+ }
+ }
+
+
+ @button.Button(button.Props{
+ Size: button.SizeIcon,
+ Variant: button.VariantGhost,
+ Class: utils.TwMerge(
+ "size-7",
+ p.Class,
+ ),
+ Attributes: utils.MergeAttributes(
+ templ.Attributes{
+ "data-tui-sidebar-trigger": true,
+ "data-tui-sidebar-target": sidebarId,
+ },
+ p.Attributes,
+ )},
+ ) {
+ @icon.PanelLeft(icon.Props{Class: "size-4"})
+ Toggle Sidebar
+ }
+
+}
+
+templ Header(props ...HeaderProps) {
+ {{ var p HeaderProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Footer(props ...FooterProps) {
+ {{ var p FooterProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Menu(props ...MenuProps) {
+ {{ var p MenuProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ MenuItem(props ...MenuItemProps) {
+ {{ var p MenuItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ MenuButton(props ...MenuButtonProps) {
+ {{ var p MenuButtonProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.Size == "" {
+ {{ p.Size = MenuButtonSizeDefault }}
+ }
+ if p.Tooltip != "" {
+ {{ tooltipID := utils.RandomID() }}
+ // When collapsed to icon mode - show with tooltip
+
+ // When expanded - show without tooltip
+
+ } else {
+ @menuButtonContent(p, "") {
+ { children... }
+ }
+ }
+}
+
+templ menuButtonContent(p MenuButtonProps, buttonID string) {
+ if p.Href != "" {
+ span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ p.Class,
+ ) }
+ data-tui-sidebar="menu-button"
+ data-tui-sidebar-size={ string(p.Size) }
+ if p.IsActive {
+ data-tui-sidebar-active="true"
+ }
+ { p.Attributes... }
+ >
+ { children... }
+
+ } else {
+
+ }
+}
+
+templ MenuSub(props ...MenuSubProps) {
+ {{ var p MenuSubProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ MenuSubItem(props ...MenuSubItemProps) {
+ {{ var p MenuSubItemProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ MenuSubButton(props ...MenuSubButtonProps) {
+ {{ var p MenuSubButtonProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.Href != "" {
+
+ { children... }
+
+ } else {
+
+ }
+}
+
+templ Inset(props ...InsetProps) {
+ {{ var p InsetProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Group(props ...GroupProps) {
+ {{ var p GroupProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ GroupLabel(props ...GroupLabelProps) {
+ {{ var p GroupLabelProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ svg]:size-4 [&>svg]:shrink-0",
+ "group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:-mt-8 group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:opacity-0",
+ p.Class,
+ ) }
+ data-tui-sidebar="group-label"
+ { p.Attributes... }
+ >
+ { children... }
+
+}
+
+templ MenuBadge(props ...MenuBadgeProps) {
+ {{ var p MenuBadgeProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Separator(props ...SeparatorProps) {
+ {{ var p SeparatorProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/skeleton/skeleton.templ b/internal/ui/components/skeleton/skeleton.templ
new file mode 100644
index 0000000..cf5803d
--- /dev/null
+++ b/internal/ui/components/skeleton/skeleton.templ
@@ -0,0 +1,30 @@
+// templui component skeleton - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/skeleton
+package skeleton
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+templ Skeleton(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
diff --git a/internal/ui/components/slider/slider.templ b/internal/ui/components/slider/slider.templ
new file mode 100644
index 0000000..5c7db5b
--- /dev/null
+++ b/internal/ui/components/slider/slider.templ
@@ -0,0 +1,121 @@
+// templui component slider - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/slider
+package slider
+
+import (
+ "fmt"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type InputProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Name string
+ Min int
+ Max int
+ Step int
+ Value int
+ Disabled bool
+}
+
+type ValueProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ For string // Corresponds to the ID of the Slider Input
+}
+
+templ Slider(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Input(props ...InputProps) {
+ {{ var p InputProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.ID == "" {
+ {{ p.ID = utils.RandomID() }}
+ }
+
+}
+
+templ Value(props ...ValueProps) {
+ {{ var p ValueProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.For == "" {
+ Error: SliderValue missing 'For' attribute.
+ }
+
+
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/switch/switch.templ b/internal/ui/components/switch/switch.templ
new file mode 100644
index 0000000..9971fb2
--- /dev/null
+++ b/internal/ui/components/switch/switch.templ
@@ -0,0 +1,86 @@
+// templui component switch - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/switch
+package switchcomp
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Name string
+ Value string
+ Disabled bool
+ Checked bool
+ Form string
+}
+
+templ Switch(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.ID == "" {
+ {{ p.ID = utils.RandomID() }}
+ }
+
+}
diff --git a/internal/ui/components/table/table.templ b/internal/ui/components/table/table.templ
new file mode 100644
index 0000000..9df312c
--- /dev/null
+++ b/internal/ui/components/table/table.templ
@@ -0,0 +1,205 @@
+// templui component table - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/table
+package table
+
+import "git.juancwu.dev/juancwu/budgething/internal/utils"
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type HeaderProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type BodyProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type FooterProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type RowProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Selected bool
+}
+
+type HeadProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type CellProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type CaptionProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+templ Table(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+}
+
+templ Header(props ...HeaderProps) {
+ {{ var p HeaderProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Body(props ...BodyProps) {
+ {{ var p BodyProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Footer(props ...FooterProps) {
+ {{ var p FooterProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ tr]:last:border-b-0", p.Class) }
+ { p.Attributes... }
+ >
+ { children... }
+
+}
+
+templ Row(props ...RowProps) {
+ {{ var p RowProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
+
+templ Head(props ...HeadProps) {
+ {{ var p HeadProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ [role=checkbox]]:translate-y-[2px]",
+ p.Class,
+ ),
+ }
+ { p.Attributes... }
+ >
+ { children... }
+ |
+}
+
+templ Cell(props ...CellProps) {
+ {{ var p CellProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ [role=checkbox]]:translate-y-[2px]",
+ p.Class,
+ ),
+ }
+ { p.Attributes... }
+ >
+ { children... }
+ |
+}
+
+templ Caption(props ...CaptionProps) {
+ {{ var p CaptionProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+ { children... }
+
+}
diff --git a/internal/ui/components/tabs/tabs.templ b/internal/ui/components/tabs/tabs.templ
new file mode 100644
index 0000000..7e62d88
--- /dev/null
+++ b/internal/ui/components/tabs/tabs.templ
@@ -0,0 +1,163 @@
+// templui component tabs - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/tabs
+package tabs
+
+import (
+ "context"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type ListProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type TriggerProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Value string
+ IsActive bool
+ TabsID string
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Value string
+ IsActive bool
+ TabsID string
+}
+
+templ Tabs(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ {{ tabsID := p.ID }}
+ if tabsID == "" {
+ {{ tabsID = utils.RandomID() }}
+ }
+
+ {{ ctx = context.WithValue(ctx, "tabsId", tabsID) }}
+ { children... }
+
+}
+
+templ List(props ...ListProps) {
+ {{ var p ListProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ {{ tabsID := IDFromContext(ctx) }}
+
+ { children... }
+
+}
+
+templ Trigger(props ...TriggerProps) {
+ {{ var p TriggerProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ {{ tabsID := p.TabsID }}
+ if tabsID == "" {
+ {{ tabsID = IDFromContext(ctx) }}
+ }
+ if p.Value == "" {
+ Error: Tab Trigger missing required 'Value' attribute.
+ }
+
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ {{ tabsID := p.TabsID }}
+ if tabsID == "" {
+ {{ tabsID = IDFromContext(ctx) }}
+ }
+ if p.Value == "" {
+ Error: Tab Content missing required 'Value' attribute.
+ return templ.NopComponent
+ }
+
+ { children... }
+
+}
+
+func IDFromContext(ctx context.Context) string {
+ if tabsID, ok := ctx.Value("tabsId").(string); ok {
+ return tabsID
+ }
+ return ""
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/tagsinput/tagsinput.templ b/internal/ui/components/tagsinput/tagsinput.templ
new file mode 100644
index 0000000..5d85274
--- /dev/null
+++ b/internal/ui/components/tagsinput/tagsinput.templ
@@ -0,0 +1,94 @@
+// templui component tagsinput - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/tags-input
+package tagsinput
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/badge"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/input"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Props struct {
+ ID string
+ Name string
+ Value []string
+ Form string
+ Placeholder string
+ Class string
+ HasError bool
+ Attributes templ.Attributes
+ Disabled bool
+ Readonly bool
+}
+
+templ TagsInput(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+
+
+ for _, tag := range p.Value {
+ @badge.Badge(badge.Props{
+ Attributes: templ.Attributes{"data-tui-tagsinput-chip": ""},
+ }) {
+
{ tag }
+
+ }
+ }
+
+ @input.Input(input.Props{
+ ID: p.ID,
+ Class: "border-0 shadow-none focus-visible:ring-0 h-auto py-0 px-0 bg-transparent rounded-none min-h-0 disabled:opacity-100 dark:bg-transparent",
+ Type: input.TypeText,
+ Placeholder: p.Placeholder,
+ Disabled: p.Disabled,
+ Readonly: p.Readonly,
+ Attributes: utils.MergeAttributes(
+ templ.Attributes{"data-tui-tagsinput-text-input": ""},
+ p.Attributes,
+ ),
+ })
+
+ for _, tag := range p.Value {
+
+ }
+
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/textarea/textarea.templ b/internal/ui/components/textarea/textarea.templ
new file mode 100644
index 0000000..ee8908e
--- /dev/null
+++ b/internal/ui/components/textarea/textarea.templ
@@ -0,0 +1,85 @@
+// templui component textarea - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/textarea
+package textarea
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+ "strconv"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Name string
+ Value string
+ Form string
+ Placeholder string
+ Rows int
+ AutoResize bool
+ Disabled bool
+ Readonly bool
+ HasError bool
+}
+
+templ Textarea(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.ID == "" {
+ {{ p.ID = utils.RandomID() }}
+ }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/timepicker/timepicker.templ b/internal/ui/components/timepicker/timepicker.templ
new file mode 100644
index 0000000..10c1ea2
--- /dev/null
+++ b/internal/ui/components/timepicker/timepicker.templ
@@ -0,0 +1,250 @@
+// templui component timepicker - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/time-picker
+package timepicker
+
+import (
+ "fmt"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/button"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/card"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/popover"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+ "strconv"
+ "time"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Name string
+ Form string
+ Value time.Time
+ MinTime time.Time
+ MaxTime time.Time
+ Step int
+ Use12Hours bool
+ AMLabel string
+ PMLabel string
+ Placeholder string
+ Disabled bool
+ HasError bool
+}
+
+templ TimePicker(props ...Props) {
+ {{
+ var p Props
+ if len(props) > 0 {
+ p = props[0]
+ }
+ if p.ID == "" {
+ p.ID = utils.RandomID()
+ }
+ if p.Name == "" {
+ p.Name = p.ID
+ }
+ if p.Placeholder == "" {
+ p.Placeholder = "Select time"
+ }
+ if p.AMLabel == "" {
+ p.AMLabel = "AM"
+ }
+ if p.PMLabel == "" {
+ p.PMLabel = "PM"
+ }
+ if p.Step <= 0 {
+ p.Step = 1
+ }
+
+ var contentID = p.ID + "-content"
+ var valueString string
+ if p.Value != (time.Time{}) {
+ valueString = p.Value.Format("15:04")
+ }
+ var minTimeString string
+ if p.MinTime != (time.Time{}) {
+ minTimeString = p.MinTime.Format("15:04")
+ }
+ var maxTimeString string
+ if p.MaxTime != (time.Time{}) {
+ maxTimeString = p.MaxTime.Format("15:04")
+ }
+ }}
+
+
+ @popover.Trigger(popover.TriggerProps{For: contentID}) {
+ @button.Button(button.Props{
+ ID: p.ID,
+ Variant: button.VariantOutline,
+ Class: utils.TwMerge(
+ // Base styles matching input
+ "w-full h-9 px-3 py-1 text-base md:text-sm",
+ "flex items-center justify-between",
+ "rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] outline-none",
+ // Dark mode background
+ "dark:bg-input/30",
+ // Selection styles
+ "selection:bg-primary selection:text-primary-foreground",
+ // Focus styles
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
+ // Error/Invalid styles
+ "aria-invalid:ring-destructive/20 aria-invalid:border-destructive dark:aria-invalid:ring-destructive/40",
+ utils.If(p.HasError, "border-destructive ring-destructive/20 dark:ring-destructive/40"),
+ p.Class,
+ ),
+ Disabled: p.Disabled,
+ Attributes: utils.MergeAttributes(p.Attributes, templ.Attributes{
+ "data-tui-timepicker": "true",
+ "data-tui-timepicker-use12hours": fmt.Sprintf("%t", p.Use12Hours),
+ "data-tui-timepicker-am-label": p.AMLabel,
+ "data-tui-timepicker-pm-label": p.PMLabel,
+ "data-tui-timepicker-placeholder": p.Placeholder,
+ "data-tui-timepicker-step": fmt.Sprintf("%d", p.Step),
+ "data-tui-timepicker-min-time": minTimeString,
+ "data-tui-timepicker-max-time": maxTimeString,
+ "aria-invalid": utils.If(p.HasError, "true"),
+ }),
+ }) {
+
+ { p.Placeholder }
+
+
+ @icon.Clock(icon.Props{Size: 16})
+
+ }
+ }
+ @popover.Content(popover.ContentProps{
+ ID: contentID,
+ Placement: popover.PlacementBottomStart,
+ Class: "p-0 w-80",
+ }) {
+ @card.Card(card.Props{
+ Class: "border-0 shadow-none",
+ }) {
+ @card.Content(card.ContentProps{
+ Class: "p-4",
+ }) {
+
+ // Time selection grid
+
+ // Hour selection
+
+
+
+
+ if p.Use12Hours {
+ // 12-hour format: 12, 01-11
+
+ for hour := 1; hour <= 11; hour++ {
+
+ }
+ } else {
+ // 24-hour format: 00-23
+ for hour := 0; hour < 24; hour++ {
+
+ }
+ }
+
+
+
+ // Minute selection
+
+
+
+
+ for minute := 0; minute < 60; minute += p.Step {
+
+ }
+
+
+
+
+ // AM/PM selector and action buttons
+
+ if p.Use12Hours {
+
+
+
+
+ } else {
+
+ }
+ @button.Button(button.Props{
+ Type: "button",
+ Variant: button.VariantSecondary,
+ Size: button.SizeSm,
+ Attributes: templ.Attributes{
+ "data-tui-timepicker-done": "true",
+ },
+ }) {
+ Done
+ }
+
+
+ }
+ }
+ }
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/toast/toast.templ b/internal/ui/components/toast/toast.templ
new file mode 100644
index 0000000..24ee568
--- /dev/null
+++ b/internal/ui/components/toast/toast.templ
@@ -0,0 +1,153 @@
+// templui component toast - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/toast
+package toast
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/button"
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/icon"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+ "strconv"
+)
+
+type Variant string
+type Position string
+
+const (
+ VariantDefault Variant = "default"
+ VariantSuccess Variant = "success"
+ VariantError Variant = "error"
+ VariantWarning Variant = "warning"
+ VariantInfo Variant = "info"
+)
+
+const (
+ PositionTopRight Position = "top-right"
+ PositionTopLeft Position = "top-left"
+ PositionTopCenter Position = "top-center"
+ PositionBottomRight Position = "bottom-right"
+ PositionBottomLeft Position = "bottom-left"
+ PositionBottomCenter Position = "bottom-center"
+)
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ Title string
+ Description string
+ Variant Variant
+ Position Position
+ Duration int
+ Dismissible bool
+ ShowIndicator bool
+ Icon bool
+}
+
+templ Toast(props ...Props) {
+ {{ var p Props }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ if p.ID == "" {
+ {{ p.ID = utils.RandomID() }}
+ }
+ // Set defaults
+ if p.Variant == "" {
+ {{ p.Variant = VariantDefault }}
+ }
+ if p.Position == "" {
+ {{ p.Position = PositionBottomRight }}
+ }
+ if p.Duration == 0 {
+ {{ p.Duration = 3000 }}
+ }
+
+
+ // Progress indicator
+ if p.ShowIndicator && p.Duration > 0 {
+
+ }
+ // Icon
+ if p.Icon {
+ switch p.Variant {
+ case VariantSuccess:
+ @icon.CircleCheck(icon.Props{Size: 22, Class: "text-green-500 mr-3 flex-shrink-0"})
+ case VariantError:
+ @icon.CircleX(icon.Props{Size: 22, Class: "text-red-500 mr-3 flex-shrink-0"})
+ case VariantWarning:
+ @icon.TriangleAlert(icon.Props{Size: 22, Class: "text-yellow-500 mr-3 flex-shrink-0"})
+ case VariantInfo:
+ @icon.Info(icon.Props{Size: 22, Class: "text-blue-500 mr-3 flex-shrink-0"})
+ }
+ }
+ // Content
+
+ if p.Title != "" {
+ { p.Title }
+ }
+ if p.Description != "" {
+ { p.Description }
+ }
+
+ // Dismiss button
+ if p.Dismissible {
+ @button.Button(button.Props{
+ Size: button.SizeIcon,
+ Variant: button.VariantGhost,
+ Attributes: templ.Attributes{
+ "aria-label": "Close",
+ "data-tui-toast-dismiss": "",
+ "type": "button",
+ },
+ }) {
+ @icon.X(icon.Props{
+ Size: 18,
+ Class: "opacity-75 hover:opacity-100",
+ })
+ }
+ }
+
+
+}
+
+templ Script() {
+
+}
diff --git a/internal/ui/components/tooltip/tooltip.templ b/internal/ui/components/tooltip/tooltip.templ
new file mode 100644
index 0000000..53c5b81
--- /dev/null
+++ b/internal/ui/components/tooltip/tooltip.templ
@@ -0,0 +1,94 @@
+// templui component tooltip - version: v0.101.0 installed by templui v0.101.0
+// 📚 Documentation: https://templui.io/docs/components/tooltip
+package tooltip
+
+import (
+ "git.juancwu.dev/juancwu/budgething/internal/ui/components/popover"
+ "git.juancwu.dev/juancwu/budgething/internal/utils"
+)
+
+type Position string
+
+const (
+ PositionTop Position = "top"
+ PositionRight Position = "right"
+ PositionBottom Position = "bottom"
+ PositionLeft Position = "left"
+)
+
+// Map tooltip positions to popover positions
+func mapTooltipPositionToPopover(position Position) popover.Placement {
+ switch position {
+ case PositionTop:
+ return popover.PlacementTop
+ case PositionRight:
+ return popover.PlacementRight
+ case PositionBottom:
+ return popover.PlacementBottom
+ case PositionLeft:
+ return popover.PlacementLeft
+ default:
+ return popover.PlacementTop
+ }
+}
+
+type Props struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+}
+
+type TriggerProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ For string
+}
+
+type ContentProps struct {
+ ID string
+ Class string
+ Attributes templ.Attributes
+ ShowArrow bool
+ Position Position
+ HoverDelay int
+ HoverOutDelay int
+}
+
+templ Tooltip(props ...Props) {
+ { children... }
+}
+
+templ Trigger(props ...TriggerProps) {
+ {{ var p TriggerProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ @popover.Trigger(popover.TriggerProps{
+ ID: p.ID,
+ Class: p.Class,
+ Attributes: p.Attributes,
+ TriggerType: popover.TriggerTypeHover,
+ For: p.For,
+ }) {
+ { children... }
+ }
+}
+
+templ Content(props ...ContentProps) {
+ {{ var p ContentProps }}
+ if len(props) > 0 {
+ {{ p = props[0] }}
+ }
+ @popover.Content(popover.ContentProps{
+ ID: p.ID,
+ Class: utils.TwMerge("px-4 py-1 bg-foreground text-background [&_[data-tui-popover-arrow]]:!bg-foreground [&_[data-tui-popover-arrow]]:!border-0", p.Class),
+ Attributes: p.Attributes,
+ Placement: mapTooltipPositionToPopover(p.Position),
+ ShowArrow: p.ShowArrow,
+ HoverDelay: p.HoverDelay,
+ HoverOutDelay: p.HoverOutDelay,
+ }) {
+ { children... }
+ }
+}
diff --git a/internal/utils/templui.go b/internal/utils/templui.go
new file mode 100644
index 0000000..93e0c9d
--- /dev/null
+++ b/internal/utils/templui.go
@@ -0,0 +1,60 @@
+// templui util templui.go - version: v0.101.0 installed by templui v0.101.0
+package utils
+
+import (
+ "fmt"
+ "time"
+
+ "crypto/rand"
+
+ "github.com/a-h/templ"
+
+ twmerge "github.com/Oudwins/tailwind-merge-go"
+)
+
+// TwMerge combines Tailwind classes and resolves conflicts.
+// Example: "bg-red-500 hover:bg-blue-500", "bg-green-500" → "hover:bg-blue-500 bg-green-500"
+func TwMerge(classes ...string) string {
+ return twmerge.Merge(classes...)
+}
+
+// TwIf returns value if condition is true, otherwise an empty value of type T.
+// Example: true, "bg-red-500" → "bg-red-500"
+func If[T comparable](condition bool, value T) T {
+ var empty T
+ if condition {
+ return value
+ }
+ return empty
+}
+
+// TwIfElse returns trueValue if condition is true, otherwise falseValue.
+// Example: true, "bg-red-500", "bg-gray-300" → "bg-red-500"
+func IfElse[T any](condition bool, trueValue T, falseValue T) T {
+ if condition {
+ return trueValue
+ }
+ return falseValue
+}
+
+// MergeAttributes combines multiple Attributes into one.
+// Example: MergeAttributes(attr1, attr2) → combined attributes
+func MergeAttributes(attrs ...templ.Attributes) templ.Attributes {
+ merged := templ.Attributes{}
+ for _, attr := range attrs {
+ for k, v := range attr {
+ merged[k] = v
+ }
+ }
+ return merged
+}
+
+// RandomID generates a random ID string.
+// Example: RandomID() → "id-1a2b3c"
+func RandomID() string {
+ return fmt.Sprintf("id-%s", rand.Text())
+}
+
+// ScriptVersion is a timestamp generated at app start for cache busting.
+// Used in Script() templates to append ?v= to script URLs.
+var ScriptVersion = fmt.Sprintf("%d", time.Now().Unix())
diff --git a/main.go b/main.go
deleted file mode 100644
index 67d7299..0000000
--- a/main.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package main
-
-import "fmt"
-
-func main() {
- fmt.Println("BUDGETHING!!")
-}