168 Commits

Author SHA1 Message Date
32c55f39e2 build(nix): updated nix hashes 2025-06-19 00:08:21 +00:00
826c6537b4 build(client): updated npm dependencies 2025-06-19 00:06:28 +00:00
e6f7ab860d build(nix): updated nix dependencies 2025-06-19 00:05:57 +00:00
3a33275185 Merge pull request 'update' (#8) from update into main
All checks were successful
Check / check (push) Has been skipped
Check / update (push) Successful in 13s
Check / push (push) Has been skipped
Check / check (pull_request) Successful in 1m8s
Check / update (pull_request) Has been skipped
Check / push (pull_request) Has been skipped
Update / update (push) Successful in 3m59s
2025-06-16 18:40:16 +00:00
238860053d build(nix): updated nix hashes
All checks were successful
Check / check (pull_request) Successful in 2m4s
Check / update (pull_request) Has been skipped
Check / push (pull_request) Has been skipped
2025-06-16 18:37:37 +00:00
21debac8ad build(client): updated npm dependencies 2025-06-16 18:35:45 +00:00
632648ca8c build(buf): updated buf dependencies 2025-06-16 18:35:12 +00:00
c86eb849f3 build(nix): updated nix dependencies 2025-06-16 18:35:12 +00:00
3412be9484 fix: set home
All checks were successful
Check / check (push) Successful in 1m22s
Check / update (push) Has been skipped
Check / push (push) Successful in 13s
2025-06-16 14:26:28 -04:00
5bb6311fea fix: create pull request
All checks were successful
Check / check (push) Successful in 1m5s
Check / update (push) Has been skipped
Check / push (push) Successful in 13s
2025-06-16 14:01:05 -04:00
8cc54fb466 test: create pull request
All checks were successful
Check / check (push) Successful in 1m5s
Check / update (push) Has been skipped
Check / push (push) Successful in 13s
2025-06-16 13:53:20 -04:00
6f87b1f4da fix: gitea update
All checks were successful
Check / check (push) Successful in 1m5s
Check / update (push) Has been skipped
Check / push (push) Successful in 13s
2025-06-16 13:45:13 -04:00
99379efa15 fix: rename util
All checks were successful
Check / check (push) Successful in 1m22s
Check / update (push) Has been skipped
Check / push (push) Successful in 13s
2025-06-16 12:50:23 -04:00
534a6e2bf9 ci: on all pull request actions
All checks were successful
Check / check (push) Successful in 1m6s
Check / update (push) Has been skipped
Check / push (push) Successful in 13s
2025-06-16 12:21:56 -04:00
c01fc59271 ci: push to prod
All checks were successful
Check / check (push) Successful in 1m7s
Check / update (push) Has been skipped
Check / push (push) Successful in 13s
2025-06-16 12:12:15 -04:00
1030363896 merge gitea & github
All checks were successful
Check / check (push) Successful in 1m27s
2025-06-16 12:06:16 -04:00
633629ca91 Merge pull request #26 from spotdemo4/update
update
2025-05-31 20:17:00 -04:00
26eb08d37c build(nix): updated nix hashes 2025-06-01 00:14:38 +00:00
156c804d1a build(client): updated npm dependencies 2025-06-01 00:12:53 +00:00
78ece703ef Merge pull request #25 from spotdemo4/update
update
2025-05-30 20:14:37 -04:00
185649671f build(nix): updated nix hashes 2025-05-31 00:12:19 +00:00
25da3aea19 build(client): updated npm dependencies 2025-05-31 00:10:34 +00:00
a0206c3287 build(nix): updated nix dependencies 2025-05-31 00:09:58 +00:00
bd0a8e223e Merge pull request #24 from spotdemo4/update
update
2025-05-29 20:15:14 -04:00
eeca47790b build(nix): updated nix hashes 2025-05-30 00:12:50 +00:00
1db7805031 build(client): updated npm dependencies 2025-05-30 00:11:03 +00:00
66630d84fb Merge pull request #23 from spotdemo4/update
update
2025-05-28 20:14:52 -04:00
44b495a282 build(nix): updated nix hashes 2025-05-29 00:12:33 +00:00
dfe30ecade build(client): updated npm dependencies 2025-05-29 00:10:48 +00:00
f95156febb build(nix): updated nix dependencies 2025-05-29 00:10:18 +00:00
8896100e3d Merge pull request #22 from spotdemo4/update
update
2025-05-27 20:15:19 -04:00
1ed8493bb7 build(nix): updated nix hashes 2025-05-28 00:12:57 +00:00
3e61e45655 build(client): updated npm dependencies 2025-05-28 00:11:07 +00:00
e10c14b3e0 Merge pull request #21 from spotdemo4/update
update
2025-05-26 20:14:30 -04:00
77ef734baa build(nix): updated nix hashes 2025-05-27 00:12:26 +00:00
b3584332c5 build(client): updated npm dependencies 2025-05-27 00:10:39 +00:00
6fa6704f1c build(nix): updated nix dependencies 2025-05-27 00:10:03 +00:00
26c678a3e5 Merge pull request #20 from spotdemo4/update
update
2025-05-25 20:14:38 -04:00
3a142ad595 build(nix): updated nix hashes 2025-05-26 00:12:39 +00:00
1c317b3154 build(client): updated npm dependencies 2025-05-26 00:10:50 +00:00
ade88cf810 Merge pull request #19 from spotdemo4/update
update
2025-05-24 20:15:29 -04:00
f4ae33c00d build(nix): updated nix hashes 2025-05-25 00:13:32 +00:00
13c1eaf77f build(client): updated npm dependencies 2025-05-25 00:11:48 +00:00
f475f6e64d build(nix): updated nix dependencies 2025-05-25 00:11:09 +00:00
a93ad92ab9 Merge pull request #18 from spotdemo4/update
update
2025-05-23 20:13:58 -04:00
23c807d508 build(nix): updated nix hashes 2025-05-24 00:11:59 +00:00
3d01527e92 build(client): updated npm dependencies 2025-05-24 00:10:07 +00:00
b19fdce370 Merge pull request #17 from spotdemo4/update
update
2025-05-22 20:14:39 -04:00
a69e2ffa40 build(nix): updated nix hashes 2025-05-23 00:12:33 +00:00
35df911072 build(client): updated npm dependencies 2025-05-23 00:10:41 +00:00
bd24a3c84b Merge pull request #16 from spotdemo4/update
update
2025-05-21 20:14:25 -04:00
0cdbfeaca9 build(nix): updated nix hashes 2025-05-22 00:12:20 +00:00
afa88b6274 build(client): updated npm dependencies 2025-05-22 00:10:29 +00:00
89651cdbf6 build(nix): updated nix dependencies 2025-05-22 00:09:59 +00:00
dabb473e0f Merge pull request #15 from spotdemo4/update
update
2025-05-20 20:14:32 -04:00
5a3c4e7369 build(nix): updated nix hashes 2025-05-21 00:12:29 +00:00
f7070590b8 build(client): updated npm dependencies 2025-05-21 00:10:43 +00:00
e6b378c170 style: remove unnecessary .dockerignore
All checks were successful
Check / check (push) Successful in 1m2s
Update / update (push) Successful in 2m59s
2025-05-20 10:07:29 -04:00
fe05a64eb0 Merge pull request #14 from spotdemo4/update
update
2025-05-19 20:15:12 -04:00
7b3d66886d build(nix): updated nix hashes 2025-05-20 00:12:53 +00:00
bc74994ac4 build(client): updated npm dependencies 2025-05-20 00:11:03 +00:00
e20a67f7a4 Merge pull request #13 from spotdemo4/update
update
2025-05-18 20:14:57 -04:00
01e2f3eca3 build(nix): updated nix hashes 2025-05-19 00:12:51 +00:00
95a2a00cec build(client): updated npm dependencies 2025-05-19 00:11:04 +00:00
b6058aa434 build(nix): updated nix dependencies 2025-05-19 00:10:26 +00:00
0adbbc3f06 Merge pull request #12 from spotdemo4/update
update
2025-05-17 20:15:32 -04:00
46058ae5d6 build(nix): updated nix hashes 2025-05-18 00:13:30 +00:00
94b367c2fb build(client): updated npm dependencies 2025-05-18 00:11:36 +00:00
548efa254c build(nix): updated nix dependencies 2025-05-18 00:10:57 +00:00
68166c8d3a fix: don't prepend https. I honestly don't know how it was working before
All checks were successful
Check / check (push) Successful in 52s
Update / update (push) Successful in 3m41s
2025-05-17 04:55:04 -04:00
3a5fa69bf6 bump: v0.0.46 -> v0.0.47
All checks were successful
Check / check (push) Has been skipped
Release / check (push) Successful in 51s
Release / release (push) Successful in 2m42s
Release / package (push) Successful in 49s
2025-05-17 04:50:43 -04:00
23be247cdb fix: correct name
All checks were successful
Check / check (push) Successful in 56s
2025-05-17 04:49:14 -04:00
9a204d3808 fix: use ghcr for github 2025-05-17 04:47:55 -04:00
00e36b6c77 fix: change update path for protobuf deps
All checks were successful
Check / check (push) Successful in 54s
2025-05-17 04:34:06 -04:00
3b34d50120 bump: v0.0.45 -> v0.0.46
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Successful in 54s
Release / release (push) Successful in 2m47s
Release / package (push) Failing after 48s
2025-05-17 04:31:17 -04:00
13b652d425 fix: use pat, use shell
All checks were successful
Check / check (push) Successful in 55s
2025-05-17 04:29:39 -04:00
3bdef16173 bump: v0.0.44 -> v0.0.45
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Successful in 52s
Release / release (push) Successful in 2m52s
Release / package (push) Failing after 19s
2025-05-17 04:11:56 -04:00
968378e8bb style: rename
All checks were successful
Check / check (push) Successful in 55s
2025-05-17 04:09:13 -04:00
6767df7f91 style: rename
Some checks failed
Check / check (push) Has been cancelled
2025-05-17 04:08:10 -04:00
f9245c4145 style: move push action to new dir so github and gitea can share it
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 04:03:37 -04:00
e20156a2de style: rename github steps
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 03:08:52 -04:00
4f9dee1e27 bump: v0.0.43 -> v0.0.44
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Failing after 0s
Release / release (push) Has been skipped
Release / package (push) Has been skipped
2025-05-17 03:05:37 -04:00
fe8a1376fa fix: pass token
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 03:02:13 -04:00
7619be6d11 fix: checkout first
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 02:57:37 -04:00
1062595d7f fix: action path
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 02:53:39 -04:00
d829c1efb2 fix: init path
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 02:50:34 -04:00
a1f22433a0 style: move init to composite action
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 02:49:01 -04:00
43fc67ded6 bump: v0.0.42 -> v0.0.43
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Failing after 0s
Release / release (push) Has been skipped
Release / package (push) Has been skipped
2025-05-17 02:30:43 -04:00
8e7781a346 style: use better env
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 02:29:04 -04:00
68dd90048f bump: v0.0.41 -> v0.0.42
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Failing after 0s
Release / release (push) Has been skipped
Release / package (push) Has been skipped
2025-05-17 02:07:27 -04:00
7bf54bbd8c fix: use version
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 02:05:54 -04:00
9fa5818860 bump: v0.0.40 -> v0.0.41
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Failing after 0s
Release / release (push) Has been skipped
Release / package (push) Has been skipped
2025-05-17 01:56:26 -04:00
77859b3d94 fix: don't use repo for name
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 01:54:56 -04:00
9e26479f67 bump: v0.0.39 -> v0.0.40
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Failing after 0s
Release / release (push) Has been skipped
Release / package (push) Has been skipped
2025-05-17 01:50:01 -04:00
000797f930 fix: explicitly name registry
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 01:48:32 -04:00
1e8e06738b bump: v0.0.38 -> v0.0.39
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Failing after 0s
Release / release (push) Has been skipped
Release / package (push) Has been skipped
2025-05-17 01:45:32 -04:00
28dbf76789 fix: use repo name as name
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 01:43:58 -04:00
93aa1ebd3b bump: v0.0.37 -> v0.0.38
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Failing after 0s
Release / release (push) Has been skipped
Release / package (push) Has been skipped
2025-05-17 01:22:25 -04:00
bf13344cbe fix: needs check
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 01:20:56 -04:00
62358e100c bump: v0.0.36 -> v0.0.37
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Failing after 0s
Release / release (push) Has been skipped
Release / package (push) Has been skipped
2025-05-17 01:20:13 -04:00
7ee1cd94dc fix: use ref_name
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 01:18:40 -04:00
893aa4db51 bump: v0.0.35 -> v0.0.36
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Failing after 0s
Release / release (push) Has been skipped
Release / package (push) Has been skipped
2025-05-17 01:06:22 -04:00
6b9da9dc15 feat: build images with nix
Some checks failed
Check / check (push) Failing after 0s
2025-05-17 01:04:39 -04:00
44e08b62fd bump: v0.0.34 -> v0.0.35
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Successful in 1m32s
Release / release (push) Successful in 3m47s
Release / package (push) Successful in 1m37s
Update / update (push) Failing after 59s
2025-05-16 18:47:11 -04:00
3feb35ea7b fix: formatting
All checks were successful
Check / check (push) Successful in 52s
2025-05-16 18:45:39 -04:00
849fec6f01 fix: bump real openapi.yaml too
All checks were successful
Check / check (push) Successful in 51s
2025-05-16 18:42:24 -04:00
d27ee1202b bump: v0.0.33 -> v0.0.34
All checks were successful
Check / check (push) Has been skipped
2025-05-16 18:38:32 -04:00
32ac21afd2 fix: move buf to proto dir so submodules get the same deps
All checks were successful
Check / check (push) Successful in 1m35s
2025-05-16 18:35:48 -04:00
39959f041d bump: v0.0.32 -> v0.0.33
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Successful in 1m34s
Release / release (push) Successful in 3m34s
Release / package (push) Failing after 13s
2025-05-16 18:04:34 -04:00
124d702ec4 fix: there was no space
All checks were successful
Check / check (push) Successful in 51s
2025-05-16 18:03:02 -04:00
2587483733 bump: v0.0.31 -> v0.0.32
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Successful in 1m32s
Release / release (push) Failing after 1m49s
Release / package (push) Has been skipped
2025-05-16 17:57:30 -04:00
575ec574dd fix: add dot to build
All checks were successful
Check / check (push) Successful in 52s
2025-05-16 17:55:48 -04:00
815cf96374 bump: v0.0.30 -> v0.0.31
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Successful in 1m32s
Release / release (push) Successful in 1m47s
Release / package (push) Failing after 5s
2025-05-16 17:34:54 -04:00
2b6c24bc86 fix: remove label, as it should be generated by docker/metadata-action
All checks were successful
Check / check (push) Successful in 52s
2025-05-16 17:32:37 -04:00
632774d051 style: rename github to gitea for gitea actions
All checks were successful
Check / check (push) Successful in 52s
2025-05-16 17:27:53 -04:00
1d6b419a15 fix: git automerge
All checks were successful
Check / check (push) Successful in 52s
2025-05-16 15:04:39 -04:00
2da7526265 fix: switch the protobuf extension
All checks were successful
Check / check (push) Successful in 52s
2025-05-16 14:58:43 -04:00
92877b669e fix: remove renovate, it doesn't support nix well
All checks were successful
Check / check (push) Successful in 51s
2025-05-14 12:15:17 -04:00
10168843e1 fix: specify renovate repos
All checks were successful
Check / check (push) Successful in 51s
2025-05-14 11:48:30 -04:00
0889f9c7b1 fix: autodiscover
All checks were successful
Check / check (push) Successful in 51s
2025-05-14 11:40:11 -04:00
084010e38c feat: renovate
All checks were successful
Check / check (push) Successful in 51s
2025-05-14 11:36:54 -04:00
8158c195f5 fix: delete branch after merge
All checks were successful
Check / check (push) Successful in 50s
2025-05-14 10:35:30 -04:00
174d15de5b fix: merge when checks succeed
All checks were successful
Check / check (push) Successful in 51s
2025-05-14 10:02:08 -04:00
56523795d5 fix: force the push
All checks were successful
Check / check (push) Successful in 50s
2025-05-14 09:17:28 -04:00
32bdb3d709 fix: actually fetch
All checks were successful
Check / check (push) Successful in 1m6s
2025-05-14 08:47:46 -04:00
b30d14af9a fix: push to new pr
All checks were successful
Check / check (push) Successful in 52s
2025-05-14 08:24:33 -04:00
1220a37b60 fix: use gitea api
All checks were successful
Check / check (push) Successful in 52s
2025-05-14 08:08:13 -04:00
a3e008c317 fix: create gitea pr
All checks were successful
Check / check (push) Successful in 49s
2025-05-14 07:44:11 -04:00
58498c87af bump: v0.0.29 -> v0.0.30
All checks were successful
Check / check (push) Has been skipped
Release / check (push) Successful in 1m29s
Release / release (push) Successful in 3m21s
Release / package (push) Successful in 1m35s
2025-05-14 07:12:08 -04:00
fd9abb948a fix: set gitea url
All checks were successful
Check / check (push) Successful in 52s
2025-05-14 07:10:29 -04:00
2b07f74cc1 bump: v0.0.28 -> v0.0.29
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Successful in 1m36s
Release / release (push) Successful in 3m23s
Release / package (push) Failing after 3m7s
2025-05-14 06:58:32 -04:00
ee4d2984dd fix: remove unused repo
All checks were successful
Check / check (push) Successful in 1m19s
2025-05-14 06:56:12 -04:00
dd80776bb1 bump: v0.0.27 -> v0.0.28
Some checks are pending
Release / package (push) Blocked by required conditions
Release / check (push) Successful in 52s
Release / release (push) Successful in 6m37s
Check / check (push) Has been skipped
2025-05-14 06:31:13 -04:00
d0e7ae9284 fix: set HOME
All checks were successful
Check / check (push) Successful in 53s
2025-05-14 06:29:21 -04:00
63433be0bb bump: v0.0.26 -> v0.0.27
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Successful in 1m48s
Release / release (push) Failing after 1m27s
Release / package (push) Has been skipped
2025-05-14 06:11:54 -04:00
1a856e575e fix: use single user mode for gitea
All checks were successful
Check / check (push) Successful in 52s
2025-05-14 06:08:40 -04:00
a3e4154fb6 bump: v0.0.25 -> v0.0.26
Some checks failed
Check / check (push) Has been skipped
Release / check (push) Successful in 55s
Release / release (push) Failing after 1m30s
Release / package (push) Has been skipped
2025-05-14 05:55:36 -04:00
4839b74bf7 feat: gitea workflows
All checks were successful
Check / check (push) Successful in 52s
2025-05-14 05:48:08 -04:00
dcf5a16c3f fix: import buf 2025-05-14 05:13:11 -04:00
932d82c1fc fix: update buf deps 2025-05-14 05:11:19 -04:00
db509ffa8a feat: auto updates 2025-05-14 05:00:06 -04:00
c4392601b1 fix: bump correctly 2025-05-14 04:39:13 -04:00
ca421b313d bump: v0.0.24 -> v0.0.25 2025-05-14 04:38:55 -04:00
71df7b4711 fix: assign git root 2025-05-14 04:32:02 -04:00
106a43aaf1 style: rename release 2025-05-14 04:30:28 -04:00
5077682fa5 style: break out check from release 2025-05-14 04:28:27 -04:00
634bff4411 feat: readme 2025-05-14 04:15:58 -04:00
bce4d598fb build(nix): updated nix hashes 2025-05-14 01:45:17 -04:00
3fd1e1f4a3 build(client): updated npm dependencies 2025-05-14 01:43:45 -04:00
de1baa4517 build(nix): updated nix hashes 2025-05-14 00:11:08 -04:00
be309409ad build(client): updated npm dependencies 2025-05-14 00:09:42 -04:00
b6ef0cab53 license 2025-05-13 21:31:58 -04:00
e8d9a4adff feat: bump openapi version 2025-05-13 18:19:47 -04:00
fc90905dcf bump: v0.0.23 -> v0.0.24 2025-05-13 18:12:26 -04:00
6494d74ab2 style: rename workflows 2025-05-13 18:09:44 -04:00
ca313960c4 style: rename openapi.yaml 2025-05-13 18:01:54 -04:00
0cb262524c fix: format sql and proto 2025-05-13 17:59:22 -04:00
05aff14703 feat: generate release notes 2025-05-13 17:41:38 -04:00
73cf074d6d bump: v0.0.22 -> v0.0.23 2025-05-13 17:27:05 -04:00
3e545d4fb1 build(nix): updated nix hashes 2025-05-13 17:22:51 -04:00
b07364f146 build(client): updated npm dependencies 2025-05-13 17:21:24 -04:00
c9dd6d9061 style: pretty imports 2025-05-13 17:19:12 -04:00
06dc437033 fix: better mobile support 2025-05-13 10:07:35 -04:00
d91c90a5c2 fix: display version number 2025-05-12 13:42:23 -04:00
e6ab5700de fix: check lock and not nix 2025-05-12 12:26:12 -04:00
20726c55d5 bump: v0.0.21 -> v0.0.22 2025-05-12 12:24:09 -04:00
95ce559ff3 fix: remove checking buf until I can figure out how to not use buf.lock 2025-05-12 12:19:03 -04:00
bfc1580218 fix: add flake.lock on update 2025-05-12 11:39:06 -04:00
149 changed files with 2564 additions and 2743 deletions

View File

@ -1,14 +0,0 @@
.env
/docker-compose.*
/result*
/.direnv/
/build/
# Client
/client/node_modules/
/client/.svelte-kit/
# Server
/server/client/
/server/tmp/
/server/build/

View File

@ -0,0 +1,66 @@
name: Check
on:
push:
branches:
- main
pull_request:
types: [opened, reopened, edited, auto_merge_enabled, synchronize]
jobs:
check:
name: check
runs-on: ubuntu-latest
if: |
contains(github.event.head_commit.message, 'bump:') == false &&
contains(github.event.head_commit.message, 'Merge pull request') == false
steps:
- uses: actions/checkout@v4
- name: Install nix
uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: Use cachix
uses: cachix/cachix-action@v16
with:
name: trevstack
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: nix flake check --accept-flake-config
push:
name: push
runs-on: ubuntu-latest
needs: check
if: ${{ github.event_name != 'pull_request' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: "0"
# https://github.com/actions/checkout/issues/13
- name: Push to Production
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git push origin main:production
update:
name: update
runs-on: ubuntu-latest
if: ${{ contains(github.event.head_commit.message, 'Merge pull request') }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: "0"
# https://github.com/actions/checkout/issues/13
- name: Push to Production
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git push origin main:production

View File

@ -0,0 +1,126 @@
name: Release
on:
push:
tags:
- "*"
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.actions/init
with:
token: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: nix flake check
release:
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v4
- name: Install nix
uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: Use cachix
uses: cachix/cachix-action@v16
with:
name: trevstack
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: >
nix build
.#trevstack-linux-amd64
.#trevstack-linux-arm64
.#trevstack-linux-arm
.#trevstack-windows-amd64
.#trevstack-darwin-amd64
.#trevstack-darwin-arm64
- uses: akkuman/gitea-release-action@v1
with:
files: |-
result*/bin/*
package:
runs-on: ubuntu-latest
needs: release
steps:
- uses: actions/checkout@v4
- name: Install nix
uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: Use cachix
uses: cachix/cachix-action@v16
with:
name: trevstack
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- uses: docker/login-action@v3
with:
registry: ${{ github.server_url }}
username: ${{ github.actor }}
password: ${{ secrets.PAT }}
- name: Build & load images
run: |
nix build .#trevstack-linux-amd64-image && ./result | docker load
nix build .#trevstack-linux-arm64-image && ./result | docker load
nix build .#trevstack-linux-arm-image && ./result | docker load
- name: Set env
shell: bash
run: |
REGISTRY=$(basename ${{ github.server_url }})
NR=${{ github.repository }}
NAMESPACE="${NR%%/*}"
REPOSITORY="${NR##*/}"
TAG=${{ github.ref_name }}
VERSION=${TAG#v}
echo "REGISTRY=${REGISTRY}" >> $GITHUB_ENV
echo "NAMESPACE=${NAMESPACE}" >> $GITHUB_ENV
echo "REPOSITORY=${REPOSITORY}" >> $GITHUB_ENV
echo "VERSION=${VERSION}" >> $GITHUB_ENV
- name: Push images
shell: bash
run: |
docker image tag $REPOSITORY:$VERSION-amd64 $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64
docker push $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64
docker image tag $REPOSITORY:$VERSION-arm64 $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64
docker push $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64
docker image tag $REPOSITORY:$VERSION-arm $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm
docker push $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm
- name: Push manifest
shell: bash
run: |
docker manifest create $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64 \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64 \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64 --arch amd64
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64 --arch arm64
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm --arch arm
docker manifest push $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION
docker manifest create $REGISTRY/$NAMESPACE/$REPOSITORY:latest \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64 \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64 \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:latest $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64 --arch amd64
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:latest $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64 --arch arm64
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:latest $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm --arch arm

View File

@ -0,0 +1,61 @@
name: Update
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install nix
uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: Use cachix
uses: cachix/cachix-action@v16
with:
name: trevstack
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
# https://github.com/actions/checkout/issues/13
- name: Set git config
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B update
- run: nix run .#update
- name: Create pull request
env:
PAT: ${{ secrets.PAT }}
run: |
git push origin update --force
URL="${{ gitea.server_url }}"
REPO_OWNER_SLASH_NAME="${{ gitea.repository }}"
PRS=$(curl -s -X GET -H "Authorization: token $PAT" \
-H "Content-Type: application/json" \
"$URL/api/v1/repos/$REPO_OWNER_SLASH_NAME/pulls?state=open")
PR_UPDATE=$(echo "$PRS" | jq -cr '.[] | select( .title | contains("update") )')
if [ -z "$PR_UPDATE" ]; then
echo "Creating pull request"
PR_CREATE=$(curl -s -X POST -H "Authorization: token $PAT" \
-H "Content-Type: application/json" \
-d '{"title":"update","body":"automatic update","head":"update","base":"main"}' \
"$URL/api/v1/repos/$REPO_OWNER_SLASH_NAME/pulls")
PR_NUMBER=$(echo "$PR_CREATE" | jq -r '.number')
curl -s -X POST -H "Authorization: token $PAT" \
-H "Content-Type: application/json" \
-d '{"Do":"merge","merge_when_checks_succeed":true,"delete_branch_after_merge":true}' \
"$URL/api/v1/repos/$REPO_OWNER_SLASH_NAME/pulls/$PR_NUMBER/merge"
fi

66
.github/workflows/check.yaml vendored Normal file
View File

@ -0,0 +1,66 @@
name: Check
on:
push:
branches:
- main
pull_request:
types: [opened, reopened, edited, auto_merge_enabled, synchronize]
jobs:
check:
name: check
runs-on: ubuntu-latest
if: |
contains(github.event.head_commit.message, 'bump:') == false &&
contains(github.event.head_commit.message, 'Merge pull request') == false
steps:
- uses: actions/checkout@v4
- name: Install nix
uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: Use cachix
uses: cachix/cachix-action@v16
with:
name: trevstack
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: nix flake check --accept-flake-config
push:
name: push
runs-on: ubuntu-latest
needs: check
if: ${{ github.event_name != 'pull_request' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: "0"
# https://github.com/actions/checkout/issues/13
- name: Push to Production
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git push origin main:production
update:
name: update
runs-on: ubuntu-latest
if: ${{ contains(github.event.head_commit.message, 'Merge pull request') }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: "0"
# https://github.com/actions/checkout/issues/13
- name: Push to Production
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git push origin main:production

View File

@ -1,28 +0,0 @@
name: Lint Workflow
on:
push:
branches:
- main
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: Use Cachix
uses: cachix/cachix-action@v16
with:
name: trevstack
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Run checks
run: nix flake check

View File

@ -1,34 +1,43 @@
name: Release Workflow name: Release
on: on:
push: push:
tags: tags:
- '*' - "*"
permissions: permissions:
contents: write contents: write
packages: write packages: write
jobs: jobs:
release: check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - uses: actions/checkout@v4
uses: actions/checkout@v4
- name: Install Nix - name: Install nix
uses: cachix/install-nix-action@v31 uses: cachix/install-nix-action@v31
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- name: Use Cachix - name: Use cachix
uses: cachix/cachix-action@v16 uses: cachix/cachix-action@v16
with: with:
name: trevstack name: trevstack
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Build - run: nix flake check
run: >
release:
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v4
- uses: ./.actions/init
with:
token: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: >
nix build nix build
.#trevstack-linux-amd64 .#trevstack-linux-amd64
.#trevstack-linux-arm64 .#trevstack-linux-arm64
@ -37,52 +46,86 @@ jobs:
.#trevstack-darwin-amd64 .#trevstack-darwin-amd64
.#trevstack-darwin-arm64 .#trevstack-darwin-arm64
- name: Create Release - uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v2
with: with:
generate_release_notes: true
files: |- files: |-
result*/bin/* result*/bin/*
# https://docs.docker.com/build/ci/github-actions/manage-tags-labels/
package: package:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: release # Wait for binary cache to propagate needs: release
steps: steps:
- name: Docker meta - uses: actions/checkout@v4
id: meta
uses: docker/metadata-action@v5
with:
# list of Docker images to use as base name for tags
images: |
${{ github.repository }}
ghcr.io/${{ github.repository }}
# generate Docker tags based on the following events/attributes
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
- name: Login to Docker Hub - name: Install nix
uses: docker/login-action@v3 uses: cachix/install-nix-action@v31
with: with:
username: ${{ vars.DOCKERHUB_USERNAME }} nix_path: nixpkgs=channel:nixos-unstable
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Use cachix
uses: docker/login-action@v3 uses: cachix/cachix-action@v16
with:
name: trevstack
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU - name: Build & load images
uses: docker/setup-qemu-action@v3 run: |
nix build .#trevstack-linux-amd64-image && ./result | docker load
nix build .#trevstack-linux-arm64-image && ./result | docker load
nix build .#trevstack-linux-arm-image && ./result | docker load
- name: Set up Docker Buildx - name: Set env
uses: docker/setup-buildx-action@v3 shell: bash
run: |
REGISTRY=$(basename ${{ github.server_url }})
- name: Build and push NR=${{ github.repository }}
uses: docker/build-push-action@v6 NAMESPACE="${NR%%/*}"
with: REPOSITORY="${NR##*/}"
push: true
tags: ${{ steps.meta.outputs.tags }} TAG=${{ github.ref_name }}
labels: ${{ steps.meta.outputs.labels }} VERSION=${TAG#v}
echo "REGISTRY=${REGISTRY}" >> $GITHUB_ENV
echo "NAMESPACE=${NAMESPACE}" >> $GITHUB_ENV
echo "REPOSITORY=${REPOSITORY}" >> $GITHUB_ENV
echo "VERSION=${VERSION}" >> $GITHUB_ENV
- name: Push images
shell: bash
run: |
docker image tag $REPOSITORY:$VERSION-amd64 $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64
docker push $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64
docker image tag $REPOSITORY:$VERSION-arm64 $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64
docker push $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64
docker image tag $REPOSITORY:$VERSION-arm $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm
docker push $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm
- name: Push manifest
shell: bash
run: |
docker manifest create $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64 \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64 \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64 --arch amd64
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64 --arch arm64
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm --arch arm
docker manifest push $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION
docker manifest create $REGISTRY/$NAMESPACE/$REPOSITORY:latest \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64 \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64 \
$REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:latest $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-amd64 --arch amd64
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:latest $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm64 --arch arm64
docker manifest annotate $REGISTRY/$NAMESPACE/$REPOSITORY:latest $REGISTRY/$NAMESPACE/$REPOSITORY:$VERSION-arm --arch arm

View File

@ -1,41 +1,48 @@
name: Update Workflow name: Update
on: on:
schedule: schedule:
- cron: "0 0 * * *" - cron: "0 0 * * *"
workflow_dispatch: workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs: jobs:
update: update:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - uses: actions/checkout@v4
uses: actions/checkout@v4
- name: Install Nix - name: Install nix
uses: cachix/install-nix-action@v31 uses: cachix/install-nix-action@v31
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- name: Use Cachix - name: Use cachix
uses: cachix/cachix-action@v16 uses: cachix/cachix-action@v16
with: with:
name: trevstack name: trevstack
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
# https://github.com/actions/checkout/issues/13 # https://github.com/actions/checkout/issues/13
- name: Set Git Config - name: Set git config
run: | run: |
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Update
run: nix run .#update
- name: Create Pull Request - run: nix run .#update
- name: Create pull request
id: cpr
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v7
with: with:
delete-branch: true branch: update
title: Bump deps title: update
body: automatic update
- name: Enable automerge
run: gh pr merge --merge --auto "${{ steps.cpr.outputs.pull-request-number }}"
env:
GH_TOKEN: ${{ secrets.PAT }}

6
.gitignore vendored
View File

@ -1,5 +1,7 @@
.env .env
/docker-compose.* /docker-compose.*
/build/
# Nix
/result* /result*
/.direnv/ /.direnv/
/build/

View File

@ -3,7 +3,25 @@
git_root=$(git rev-parse --show-toplevel) git_root=$(git rev-parse --show-toplevel)
git_version=$(git describe --tags --abbrev=0) git_version=$(git describe --tags --abbrev=0)
version=${git_version#v} version=${git_version#v}
next_version=$(echo "${version}" | awk -F. -v OFS=. '{$NF += 1 ; print}')
major=$(echo "${version}" | cut -d . -f1)
minor=$(echo "${version}" | cut -d . -f2)
patch=$(echo "${version}" | cut -d . -f3)
case "${1-patch}" in
major) major=$((major + 1)) ;;
minor) minor=$((minor + 1)) ;;
*) patch=$((patch + 1)) ;;
esac
next_version="${major}.${minor}.${patch}"
echo "${version} -> ${next_version}"
echo "bumping openapi"
cd "${git_root}"
sed -i -e "s/${version}/${next_version}/g" openapi.yaml
sed -i -e "s/${version}/${next_version}/g" client/static/openapi/openapi.yaml
git add openapi.yaml
git add client/static/openapi/openapi.yaml
echo "bumping client" echo "bumping client"
cd "${git_root}/client" cd "${git_root}/client"

View File

@ -5,12 +5,20 @@ updated=false
echo "updating nix flake" echo "updating nix flake"
cd "${git_root}" cd "${git_root}"
nix flake update nix flake update --accept-flake-config
if ! git diff --exit-code flake.nix; then if ! git diff --exit-code flake.lock; then
git add flake.nix git add flake.lock
git commit -m "build(nix): updated nix dependencies" git commit -m "build(nix): updated nix dependencies"
fi fi
echo "updating protobuf deps"
cd "${git_root}/proto"
buf dep update
if ! git diff --exit-code buf.lock; then
git add buf.lock
git commit -m "build(buf): updated buf dependencies"
fi
echo "updating client" echo "updating client"
cd "${git_root}/client" cd "${git_root}/client"
npm update --save && npm i npm update --save && npm i
@ -28,7 +36,7 @@ go mod tidy
if ! git diff --exit-code go.mod go.sum; then if ! git diff --exit-code go.mod go.sum; then
git add go.mod git add go.mod
git add go.sum git add go.sum
git commit -m "build(go): updated go dependencies" git commit -m "build(server): updated go dependencies"
updated=true updated=true
fi fi

View File

@ -39,7 +39,7 @@ apps:
- ts - ts
- svelte - svelte
onstart: npx prettier --check . onstart: npx prettier --check .
onchange: npx prettier --check . || npx prettier --write . onchange: npx prettier --check .
revive: revive:
color: "#89dceb" color: "#89dceb"

10
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"recommendations": [
"golang.go",
"dorzey.vscode-sqlfluff",
"bufbuild.vscode-buf",
"dbaeumer.vscode-eslint",
"svelte.svelte-vscode",
"esbenp.prettier-vscode"
]
}

51
.vscode/settings.json vendored
View File

@ -1,7 +1,46 @@
{ {
"go.lintTool": "revive", "editor.formatOnSave": true,
"go.lintFlags": [
"--config=server/revive.toml" // Go
], "go.lintTool": "revive",
"sqlfluff.config": "server/db/.sqlfluff", "go.formatTool": "goimports",
} "go.buildTags": "dev",
"go.lintFlags": ["--config=server/revive.toml"],
"gopls": { "ui.semanticTokens": true },
"[go]": {
"editor.defaultFormatter": "golang.go"
},
// SQLFluff
"sqlfluff.config": "server/db/.sqlfluff",
"[sql]": {
"editor.defaultFormatter": "dorzey.vscode-sqlfluff"
},
// Proto
"[proto]": {
"editor.defaultFormatter": "bufbuild.vscode-buf"
},
// ESLint
"eslint.workingDirectories": ["./client"],
"eslint.validate": ["svelte", "javascript", "typescript"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
// Svelte
"svelte.enable-ts-plugin": true,
"[svelte]": {
"editor.defaultFormatter": "svelte.svelte-vscode"
},
// Prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

View File

@ -1,29 +0,0 @@
# Nix builder
FROM nixos/nix:latest AS builder
# Copy our source and setup our working dir.
COPY . /tmp/build
WORKDIR /tmp/build
# Build our Nix environment
RUN nix \
--extra-experimental-features "nix-command flakes" \
--option filter-syscalls false \
--accept-flake-config \
build
# Copy the Nix store closure into a directory. The Nix store closure is the
# entire set of Nix store values that we need for our build.
RUN mkdir /tmp/nix-store-closure
RUN cp -R $(nix-store -qR result/) /tmp/nix-store-closure
# Final image is based on scratch. We copy a bunch of Nix dependencies
# but they're fully self-contained so we don't need Nix anymore.
FROM scratch
WORKDIR /app
# Copy /nix/store
COPY --from=builder /tmp/nix-store-closure /nix/store
COPY --from=builder /tmp/build/result /app
CMD ["/app/bin/trevstack"]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) Trev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

125
README.md Normal file
View File

@ -0,0 +1,125 @@
## TrevStack
This is a CRUD app to use as a template for starting projects
### Features
- **Communicate anywhere**. Define a [protocol buffer](https://protobuf.dev/), and [Connect](https://connectrpc.com/) generates type-safe code to facilitate communication between the server and any client (web, mobile, embedded, etc). The protocol buffers can contain annotations to validate fields on the client and server. For clients that cannot use Connect, an OpenAPI spec is also generated
- **Build anywhere**. The dev environment, testing and building is all declared in a single [Nix](https://nixos.org/) flake. Every developer and server can use the same environment
- **Deploy anywhere**. CI/CD is already set up using github actions. New versions are automatically released for every major platform, along with a docker image. The binaries created require zero run-time dependencies and are relatively small (this app is 26 MiB)
- Can be entirely self-hosted
- Authentication is rolled in, including API key, fingerprint & passkey
- Automatic database migration on startup
- Light & dark modes with the [catppuccin](https://catppuccin.com/palette/) color palette
- Really good at running as a [progressive web app](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps)
While I personally prefer using a Svelte frontend and Go backend, feel free to swap them out with whatever you like, you'll be surprised how easy it is.
## Getting Started
1. [Install Nix](https://nixos.org/download/)
2. Run `nix develop`
3. Create a `server/.env` file that looks something like
```env
KEY=changeme
PORT=8080
URL=http://localhost:5173
DATABASE_URL=sqlite:/home/trev/.config/trevstack/sqlite.db
```
4. Run `treli` to start the server & client
It's that simple. If you're feeling fancy, install [direnv](https://direnv.net/) and the dev environment will load automatically.
### Useful Commands
- `nix run #update`: updates all of the dependencies
- `nix run #bump [major | minor]`: bumps the current version up one. Defaults to "patch" (0.0.1 -> 0.0.2)
- `nix build [#trevstack-(GOOS)-(GOARCH)]`: builds the application. Defaults to building for your current platform, but can be built to many by specifying the GOOS and GOARCH values
- `nix flake check`: runs all validations
- `buf lint proto` & `buf generate`: lints and generates code from protocol buffers
- `sqlc vet` & `sqlc generate`: verifies and generates code from SQL files
- `dbmate new` & `dbmate up`: creates a new migration file and runs pending migrations
### Github Actions
To use github actions for CI/CD, you'll need to create a fine-grained personal access token for the repository with the permissions:
- Contents (read and write)
- Pull requests (read and write)
And change some settings for the repository:
- General -> Allow auto-merge: true
- Rules -> Rulesets -> New ruleset
- Branch targeting criteria: Default
- Branch rules
- Require status checks to pass -> Add checks -> "check"
- Actions -> General -> Workflow permissions
- Read and write permissions: true
- Allow GitHub Actions to create and approve pull requests: true
- Secrets and variables -> Actions -> Repository secrets
- PAT: (personal access token)
### Gitea Actions
To use gitea actions for CI/CD, you'll need to create an [API token](https://docs.gitea.com/development/api-usage) with the scopes:
- write:repository
- write:package
And change some settings for the repository:
- Repository -> Delete pull request branch after merge by default: true
- Branches -> Add New Rule
- Protected Branch Name Pattern: main
- Enable Status Check: true
- Status check patterns: Check / check\*
- Actions -> Secrets
- PAT: (API token)
## Components
### Client
- **svelte 5** [[docs](https://svelte.dev/docs/svelte)] UI framework
- **tailwind 4** [[docs](https://tailwindcss.com/)] CSS framework
- **bits ui** [[docs](https://bits-ui.com/docs/)] headless components
- [components](https://github.com/spotdemo4/trevstack/tree/main/client/src/lib/ui) from **shadcn-svelte** [[docs](https://www.shadcn-svelte.com/docs)] altered to work with tailwind 4, fit the [catppuccin](https://catppuccin.com/palette/) color palette, and use [shallow routing](https://svelte.dev/docs/kit/shallow-routing)
- **connect rpc** [[docs](https://connectrpc.com/docs/web/)] to communicating with the server
- **protovalidate-es** [[docs](https://github.com/bufbuild/protovalidate-es)], along with a function [coolforms](https://github.com/spotdemo4/trevstack/blob/main/client/src/lib/coolforms/) to emulate the library [sveltekit-superforms](https://superforms.rocks/)
- **simplewebauthn** [[docs](https://simplewebauthn.dev/docs/packages/browser)] for passkey authentication
- **scalar** [[docs](https://github.com/scalar/scalar)] for displaying openapi specs
- **tw-animate-css** [[docs](https://github.com/Wombosvideo/tw-animate-css)] to animate with just tailwind classes
- vite [[docs](https://vite.dev/)] for dev server and bundling
- eslint [[docs](https://eslint.org/)] for linting
- prettier [[docs](https://prettier.io/)] for formatting
- prettier-plugin-svelte [[docs](https://github.com/sveltejs/prettier-plugin-svelte)]
- prettier-plugin-tailwindcss [[docs](https://github.com/tailwindlabs/prettier-plugin-tailwindcss)]
- prettier-plugin-sort-imports [[docs](https://github.com/IanVS/prettier-plugin-sort-imports)]
### Server
- **go** [[docs](https://go.dev/doc/)]
- **connect rpc** [[docs](https://connectrpc.com/docs/go/)] to serve gRPC & HTTP requests
- **protovalidate-go** [[docs](https://github.com/bufbuild/protovalidate-go)] for validating those requests
- **sqlc** [[docs](https://docs.sqlc.dev/en/latest/)] because writing the SQL yourself is better than an ORM
- **go-webauthn** [[docs](https://github.com/go-webauthn/webauthn)] because webauthn is hard
- **dbmate** [[docs](https://github.com/amacneil/dbmate)] for database migrations
- revive [[docs](https://github.com/mgechev/revive)] for linting
### Protocol Buffers / gRPC
- **buf** [[docs](https://buf.build/docs/)] CLI for linting & code generation
- **protovalidate** [[docs](https://buf.build/docs/protovalidate/)] provides annotations to validate proto messages & fields
- **protoc-gen-connect-openapi** [[docs](https://github.com/sudorandom/protoc-gen-connect-openapi)] generates openapi specs
- protoc-gen-go [[docs](https://pkg.go.dev/google.golang.org/protobuf)]
- protoc-gen-connect-go [[docs](https://connectrpc.com/docs/go)]
- protoc-gen-es [[docs](https://connectrpc.com/docs/web/)]

View File

@ -1,5 +1,8 @@
version: v2 version: v2
clean: true clean: true
inputs:
- directory: proto
managed: managed:
enabled: true enabled: true
override: override:
@ -27,5 +30,5 @@ plugins:
out: client/static/openapi out: client/static/openapi
strategy: all strategy: all
opt: opt:
- base=openapi.base.yaml - base=openapi.yaml
- path=openapi.yaml - path=openapi.yaml

View File

@ -1,6 +0,0 @@
# Generated by buf. DO NOT EDIT.
version: v2
deps:
- name: buf.build/bufbuild/protovalidate
commit: 8976f5be98c146529b1cc15cd2012b60
digest: b5:5d513af91a439d9e78cacac0c9455c7cb885a8737d30405d0b91974fe05276d19c07a876a51a107213a3d01b83ecc912996cdad4cddf7231f91379079cf7488d

View File

@ -3,8 +3,21 @@
"singleQuote": true, "singleQuote": true,
"trailingComma": "none", "trailingComma": "none",
"printWidth": 100, "printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], "plugins": [
"prettier-plugin-svelte",
"prettier-plugin-tailwindcss",
"@ianvs/prettier-plugin-sort-imports"
],
"tailwindFunctions": ["tv", "cn", "clsx"], "tailwindFunctions": ["tv", "cn", "clsx"],
"importOrder": [
"<TYPES>^(node:)",
"<TYPES>",
"<TYPES>^[.]",
"<BUILTIN_MODULES>",
"^(\\$lib/ui)(/.*)$",
"<THIRD_PARTY_MODULES>",
"^[.]"
],
"overrides": [ "overrides": [
{ {
"files": "*.svelte", "files": "*.svelte",

View File

@ -1,11 +1,12 @@
import prettier from 'eslint-config-prettier'; import { fileURLToPath } from 'node:url';
import js from '@eslint/js';
import { includeIgnoreFile } from '@eslint/compat'; import { includeIgnoreFile } from '@eslint/compat';
import js from '@eslint/js';
import prettier from 'eslint-config-prettier';
import svelte from 'eslint-plugin-svelte'; import svelte from 'eslint-plugin-svelte';
import globals from 'globals'; import globals from 'globals';
import { fileURLToPath } from 'node:url';
import ts from 'typescript-eslint'; import ts from 'typescript-eslint';
import svelteConfig from './svelte.config.js'; import svelteConfig from './svelte.config.js';
const gitignorePath = fileURLToPath(new URL('./.prettierignore', import.meta.url)); const gitignorePath = fileURLToPath(new URL('./.prettierignore', import.meta.url));
export default ts.config( export default ts.config(

2718
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "trevstack", "name": "trevstack",
"private": true, "private": true,
"version": "0.0.21", "version": "0.0.47",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
@ -17,35 +17,35 @@
"@bufbuild/protovalidate": "^0.1.1", "@bufbuild/protovalidate": "^0.1.1",
"@connectrpc/connect": "^2.0.2", "@connectrpc/connect": "^2.0.2",
"@connectrpc/connect-web": "^2.0.2", "@connectrpc/connect-web": "^2.0.2",
"@eslint/compat": "^1.2.9", "@eslint/compat": "^1.3.0",
"@eslint/js": "^9.18.0", "@eslint/js": "^9.18.0",
"@ianvs/prettier-plugin-sort-imports": "^4.4.2",
"@lucide/svelte": "^0.479.0", "@lucide/svelte": "^0.479.0",
"@scalar/api-reference": "^1.28.32", "@scalar/api-reference": "^1.31.14",
"@simplewebauthn/browser": "^13.1.0", "@simplewebauthn/browser": "^13.1.0",
"@sveltejs/adapter-static": "^3.0.8", "@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.20.8", "@sveltejs/kit": "^2.21.5",
"@sveltejs/vite-plugin-svelte": "^5.0.3", "@sveltejs/vite-plugin-svelte": "^5.1.0",
"@tailwindcss/vite": "^4.1.6", "@tailwindcss/vite": "^4.1.10",
"bits-ui": "^1.4.8", "bits-ui": "^1.8.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"eslint": "^9.26.0", "eslint": "^9.29.0",
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^10.1.5",
"eslint-plugin-svelte": "^3.5.1", "eslint-plugin-svelte": "^3.9.2",
"fast-deep-equal": "^3.1.3", "globals": "^16.2.0",
"globals": "^16.1.0", "mode-watcher": "^1.0.8",
"mode-watcher": "^1.0.7",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.6.11", "prettier-plugin-tailwindcss": "^0.6.12",
"svelte": "^5.28.2", "svelte": "^5.34.6",
"svelte-check": "^4.1.7", "svelte-check": "^4.2.1",
"svelte-sonner": "^0.3.28", "svelte-sonner": "^0.3.28",
"tailwind-merge": "^3.3.0", "tailwind-merge": "^3.3.1",
"tailwind-variants": "^1.0.0", "tailwind-variants": "^1.0.0",
"tailwindcss": "^4.0.13", "tailwindcss": "^4.0.13",
"tw-animate-css": "^1.2.9", "tw-animate-css": "^1.3.4",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.32.0", "typescript-eslint": "^8.34.1",
"vite": "^6.3.5" "vite": "^6.3.5"
} }
} }

View File

@ -1,99 +1,99 @@
@import 'tailwindcss'; @import 'tailwindcss';
@import "tw-animate-css"; @import 'tw-animate-css';
@custom-variant dark (&:where(.dark, .dark *)); @custom-variant dark (&:where(.dark, .dark *));
@theme inline { @theme inline {
--spacing-body: calc(100vh - 180px); --spacing-body: calc(100vh - 180px);
--color-accent: var(--sky); --color-accent: var(--sky);
--color-rosewater: var(--rosewater); --color-rosewater: var(--rosewater);
--color-flamingo: var(--flamingo); --color-flamingo: var(--flamingo);
--color-pink: var(--pink); --color-pink: var(--pink);
--color-mauve: var(--mauve); --color-mauve: var(--mauve);
--color-red: var(--red); --color-red: var(--red);
--color-maroon: var(--maroon); --color-maroon: var(--maroon);
--color-peach: var(--peach); --color-peach: var(--peach);
--color-yellow: var(--yellow); --color-yellow: var(--yellow);
--color-green: var(--green); --color-green: var(--green);
--color-teal: var(--teal); --color-teal: var(--teal);
--color-sky: var(--sky); --color-sky: var(--sky);
--color-sapphire: var(--sapphire); --color-sapphire: var(--sapphire);
--color-blue: var(--blue); --color-blue: var(--blue);
--color-lavender: var(--lavender); --color-lavender: var(--lavender);
--color-text: var(--text); --color-text: var(--text);
--color-subtext-1: var(--subtext-1); --color-subtext-1: var(--subtext-1);
--color-subtext: var(--subtext); --color-subtext: var(--subtext);
--color-overlay-2: var(--overlay-2); --color-overlay-2: var(--overlay-2);
--color-overlay-1: var(--overlay-1); --color-overlay-1: var(--overlay-1);
--color-overlay: var(--overlay); --color-overlay: var(--overlay);
--color-surface-2: var(--surface-2); --color-surface-2: var(--surface-2);
--color-surface-1: var(--surface-1); --color-surface-1: var(--surface-1);
--color-surface: var(--surface); --color-surface: var(--surface);
--color-based: var(--based); --color-based: var(--based);
--color-mantle: var(--mantle); --color-mantle: var(--mantle);
--color-crust: var(--crust); --color-crust: var(--crust);
} }
@layer base { @layer base {
:root { :root {
--rosewater: hsl(11deg, 59%, 67%); --rosewater: hsl(11deg, 59%, 67%);
--flamingo: hsl(0deg, 60%, 67%); --flamingo: hsl(0deg, 60%, 67%);
--pink: hsl(316deg, 73%, 69%); --pink: hsl(316deg, 73%, 69%);
--mauve: hsl(266deg, 85%, 58%); --mauve: hsl(266deg, 85%, 58%);
--red: hsl(347deg, 87%, 44%); --red: hsl(347deg, 87%, 44%);
--maroon: hsl(355deg, 76%, 59%); --maroon: hsl(355deg, 76%, 59%);
--peach: hsl(22deg, 99%, 52%); --peach: hsl(22deg, 99%, 52%);
--yellow: hsl(35deg, 77%, 49%); --yellow: hsl(35deg, 77%, 49%);
--green: hsl(109deg, 58%, 40%); --green: hsl(109deg, 58%, 40%);
--teal: hsl(183deg, 74%, 35%); --teal: hsl(183deg, 74%, 35%);
--sky: hsl(197deg, 97%, 46%); --sky: hsl(197deg, 97%, 46%);
--sapphire: hsl(189deg, 70%, 42%); --sapphire: hsl(189deg, 70%, 42%);
--blue: hsl(220deg, 91%, 54%); --blue: hsl(220deg, 91%, 54%);
--lavender: hsl(231deg, 97%, 72%); --lavender: hsl(231deg, 97%, 72%);
--text: hsl(234deg, 16%, 35%); --text: hsl(234deg, 16%, 35%);
--subtext-1: hsl(233deg, 13%, 41%); --subtext-1: hsl(233deg, 13%, 41%);
--subtext: hsl(233deg, 10%, 47%); --subtext: hsl(233deg, 10%, 47%);
--overlay-2: hsl(232deg, 10%, 53%); --overlay-2: hsl(232deg, 10%, 53%);
--overlay-1: hsl(231deg, 10%, 59%); --overlay-1: hsl(231deg, 10%, 59%);
--overlay: hsl(228deg, 11%, 65%); --overlay: hsl(228deg, 11%, 65%);
--surface-2: hsl(227deg, 12%, 71%); --surface-2: hsl(227deg, 12%, 71%);
--surface-1: hsl(225deg, 14%, 77%); --surface-1: hsl(225deg, 14%, 77%);
--surface: hsl(223deg, 16%, 83%); --surface: hsl(223deg, 16%, 83%);
--based: hsl(220deg, 23%, 95%); --based: hsl(220deg, 23%, 95%);
--mantle: hsl(220deg, 22%, 92%); --mantle: hsl(220deg, 22%, 92%);
--crust: hsl(220deg, 21%, 89%); --crust: hsl(220deg, 21%, 89%);
} }
.dark { .dark {
--rosewater: hsl(10deg, 56%, 91%); --rosewater: hsl(10deg, 56%, 91%);
--flamingo: hsl(0deg, 59%, 88%); --flamingo: hsl(0deg, 59%, 88%);
--pink: hsl(316deg, 72%, 86%); --pink: hsl(316deg, 72%, 86%);
--mauve: hsl(267deg, 84%, 81%); --mauve: hsl(267deg, 84%, 81%);
--red: hsl(343deg, 81%, 75%); --red: hsl(343deg, 81%, 75%);
--maroon: hsl(350deg, 65%, 77%); --maroon: hsl(350deg, 65%, 77%);
--peach: hsl(23deg, 92%, 75%); --peach: hsl(23deg, 92%, 75%);
--yellow: hsl(41deg, 86%, 83%); --yellow: hsl(41deg, 86%, 83%);
--green: hsl(115deg, 54%, 76%); --green: hsl(115deg, 54%, 76%);
--teal: hsl(170deg, 57%, 73%); --teal: hsl(170deg, 57%, 73%);
--sky: hsl(189deg, 71%, 73%); --sky: hsl(189deg, 71%, 73%);
--sapphire: hsl(199deg, 76%, 69%); --sapphire: hsl(199deg, 76%, 69%);
--blue: hsl(217deg, 92%, 76%); --blue: hsl(217deg, 92%, 76%);
--lavender: hsl(232deg, 97%, 85%); --lavender: hsl(232deg, 97%, 85%);
--text: hsl(226deg, 64%, 88%); --text: hsl(226deg, 64%, 88%);
--subtext-1: hsl(227deg, 35%, 80%); --subtext-1: hsl(227deg, 35%, 80%);
--subtext: hsl(228deg, 24%, 72%); --subtext: hsl(228deg, 24%, 72%);
--overlay-2: hsl(228deg, 17%, 64%); --overlay-2: hsl(228deg, 17%, 64%);
--overlay-1: hsl(230deg, 13%, 55%); --overlay-1: hsl(230deg, 13%, 55%);
--overlay: hsl(231deg, 11%, 47%); --overlay: hsl(231deg, 11%, 47%);
--surface-2: hsl(233deg, 12%, 39%); --surface-2: hsl(233deg, 12%, 39%);
--surface-1: hsl(234deg, 13%, 31%); --surface-1: hsl(234deg, 13%, 31%);
--surface: hsl(237deg, 16%, 23%); --surface: hsl(237deg, 16%, 23%);
--based: hsl(240deg, 21%, 15%); --based: hsl(240deg, 21%, 15%);
--mantle: hsl(240deg, 21%, 12%); --mantle: hsl(240deg, 21%, 12%);
--crust: hsl(240deg, 23%, 9%); --crust: hsl(240deg, 23%, 9%);
} }
} }
/* /*
@ -102,4 +102,4 @@
Focus Outline: blue Focus Outline: blue
Border: surface-1 Border: surface-1
Hover: bump color by 2 (eg crust -> based), if accent color drop opacity (eg blue -> blue/90) Hover: bump color by 2 (eg crust -> based), if accent color drop opacity (eg blue -> blue/90)
*/ */

View File

@ -8,7 +8,7 @@
<title>TrevStack</title> <title>TrevStack</title>
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="tap" class="bg-crust text-text min-h-screen"> <body data-sveltekit-preload-data="tap" class="bg-crust text-text min-h-dvh">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

View File

@ -1,7 +1,5 @@
export function newState<T>(s: T) { export function newState<T>(s: T) {
const state = $state(s); const state = $state(s);
return state; return state;
} }

View File

@ -1,175 +1,176 @@
import { create, type DescMessage, type DescService, type MessageShape, type MessageInitShape } from "@bufbuild/protobuf"; import type { DescMessage, DescService, MessageInitShape, MessageShape } from '@bufbuild/protobuf';
import { ValidationError, type Violation } from "@bufbuild/protovalidate"; import type { Violation } from '@bufbuild/protovalidate';
import { Validator } from "../transport"; import type { Client } from '@connectrpc/connect';
import { ConnectError, type Client } from "@connectrpc/connect"; import type { Action } from 'svelte/action';
import type { Action } from "svelte/action"; import { create } from '@bufbuild/protobuf';
import { ValidationError } from '@bufbuild/protovalidate';
import { ConnectError } from '@connectrpc/connect';
import { Validator } from '../transport';
type Options<Input extends DescMessage, Output extends DescMessage> = { type Options<Input extends DescMessage, Output extends DescMessage> = {
init?: MessageInitShape<Input>, init?: MessageInitShape<Input>;
start?: boolean, start?: boolean;
reset?: boolean, reset?: boolean;
onSubmit?: (formData: FormData, input: MessageShape<Input>) => Promise<MessageShape<Input>>, onSubmit?: (formData: FormData, input: MessageShape<Input>) => Promise<MessageShape<Input>>;
onResult?: (result: MessageShape<Output>) => void, onResult?: (result: MessageShape<Output>) => void;
onError?: (err: Violation[] | ConnectError) => void onError?: (err: Violation[] | ConnectError) => void;
}
type Violations<Field> = {
[field in keyof Field]?: Violation[];
}; };
export function coolForm< type Violations<Field> = {
Service extends DescService, [field in keyof Field]?: Violation[];
Method extends Service['methods'][number] };
>(
client: Client<Service>, export function coolForm<Service extends DescService, Method extends Service['methods'][number]>(
method: Method, client: Client<Service>,
options?: Options<Method['input'], Method['output']> method: Method,
options?: Options<Method['input'], Method['output']>
) { ) {
const input = $state(create(method.input as Method['input'], options?.init)); const input = $state(create(method.input as Method['input'], options?.init));
const output = $state(create(method.output as Method['output'])); const output = $state(create(method.output as Method['output']));
const errors: Violations<Method['input']['field']> & { const errors: Violations<Method['input']['field']> & { form?: ConnectError } = $state({});
form?: ConnectError let loading = $state(false);
} = $state({});
let loading = $state(false);
const validate = () => { const validate = () => {
// Delete existing errors // Delete existing errors
for (const key in errors) { for (const key in errors) {
delete errors[key]; delete errors[key];
} }
try { try {
Validator.validate(method['input'], input); Validator.validate(method['input'], input);
} catch (e) { } catch (e) {
if (!(e instanceof ValidationError)) { if (!(e instanceof ValidationError)) {
throw e; throw e;
} }
// Map violation errors to errors rune // Map violation errors to errors rune
for (const violation of e.violations) { for (const violation of e.violations) {
for (const field of violation.field) { for (const field of violation.field) {
if (!("localName" in field)) { if (!('localName' in field)) {
continue; continue;
} }
// Create localName property if it doesn't exist // Create localName property if it doesn't exist
if (!errors[field.localName]) { if (!errors[field.localName]) {
Object.assign(errors, { Object.assign(errors, {
[field.localName]: [violation] [field.localName]: [violation]
}) });
} else { } else {
errors[field.localName]?.push(violation); errors[field.localName]?.push(violation);
} }
} }
} }
return e.violations; return e.violations;
} }
return []; return [];
}; };
// When a request is successful // When a request is successful
const success = (response: MessageShape<Method['output']>) => { const success = (response: MessageShape<Method['output']>) => {
loading = false; loading = false;
// Send the response up // Send the response up
options?.onResult?.(response); options?.onResult?.(response);
// Set the response // Set the response
Object.assign(output, response); Object.assign(output, response);
// If we want to reset the input // If we want to reset the input
if (options && (options.reset == undefined || options.reset)) { if (options && (options.reset == undefined || options.reset)) {
const cleared = create(method.input as Method['input'], options?.init) const cleared = create(method.input as Method['input'], options?.init);
Object.assign(input, cleared); Object.assign(input, cleared);
} }
} };
// When a request fails // When a request fails
const fail = (err: Violation[] | ConnectError | any) => { const fail = (err: Violation[] | ConnectError | Error) => {
loading = false; loading = false;
// It's a Violation[] // It's a Violation[]
if (Array.isArray(err)) { if (Array.isArray(err)) {
// Send the error up // Send the error up
options?.onError?.(err); options?.onError?.(err);
return; return;
} }
// It's a ConnectError // It's a ConnectError
if (err instanceof ConnectError) { if (err instanceof ConnectError) {
// Assign it to the form // Assign it to the form
errors.form = err; errors.form = err;
// Send the error up // Send the error up
options?.onError?.(err); options?.onError?.(err);
return; return;
} }
throw err; throw err;
} };
const submit = () => { const submit = () => {
loading = true; loading = true;
// Validate // Validate
const validationErrors = validate(); const validationErrors = validate();
if (validationErrors.length > 0) { if (validationErrors.length > 0) {
fail(validationErrors); fail(validationErrors);
return; return;
} }
// Send response // Send response
if (method.methodKind == "unary") { if (method.methodKind == 'unary') {
// @ts-ignore I can't figure out how to make this typescript compliant // @ts-expect-error I can't figure out how to make this typescript compliant
const response = client[method.localName]($state.snapshot(input)) as Promise<MessageShape<Method['output']>> const response = client[method.localName]($state.snapshot(input)) as Promise<
MessageShape<Method['output']>
>;
response response
.then((resp) => { .then((resp) => {
success(resp); success(resp);
}).catch(err => { })
fail(err); .catch((err) => {
}); fail(err);
} });
} }
};
// A nice action to give to forms to run submit() on submit // A nice action to give to forms to run submit() on submit
const impair: Action<HTMLFormElement> = (form) => { const impair: Action<HTMLFormElement> = (form) => {
$effect(() => { $effect(() => {
form.onsubmit = (event) => { form.onsubmit = (event) => {
event.preventDefault(); event.preventDefault();
if (options?.onSubmit) { if (options?.onSubmit) {
const formData = new FormData(form); const formData = new FormData(form);
options.onSubmit(formData, input).then((i) => { options.onSubmit(formData, input).then((i) => {
Object.assign(input, i); Object.assign(input, i);
submit(); submit();
}); });
return; return;
} }
submit(); submit();
} };
return () => { return () => {
form.onsubmit = () => { }; form.onsubmit = () => {};
}; };
}); });
}; };
if (options?.start) { if (options?.start) {
submit(); submit();
} }
return { return {
input, input,
output, output,
errors, errors,
loading: () => loading, loading: () => loading,
submit, submit,
validate, validate,
impair impair
} };
} }

View File

@ -1,7 +1,4 @@
import { coolForm } from "./coolforms.svelte"; import { newState } from './conststate.svelte';
import { newState } from "./conststate.svelte"; import { coolForm } from './coolforms.svelte';
export { export { coolForm, newState };
coolForm,
newState
};

View File

@ -1,11 +1,12 @@
import type { Interceptor } from '@connectrpc/connect';
import { createValidator } from '@bufbuild/protovalidate';
import { Code, ConnectError, createClient } from '@connectrpc/connect';
import { createConnectTransport } from '@connectrpc/connect-web'; import { createConnectTransport } from '@connectrpc/connect-web';
import { createValidator } from "@bufbuild/protovalidate";
import { Code, ConnectError, createClient, type Interceptor } from '@connectrpc/connect';
import { AuthService } from '$lib/connect/user/v1/auth_pb';
import { UserService } from '$lib/connect/user/v1/user_pb';
import { ItemService } from '$lib/connect/item/v1/item_pb';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/state'; import { page } from '$app/state';
import { ItemService } from '$lib/connect/item/v1/item_pb';
import { AuthService } from '$lib/connect/user/v1/auth_pb';
import { UserService } from '$lib/connect/user/v1/user_pb';
const redirector: Interceptor = (next) => async (req) => { const redirector: Interceptor = (next) => async (req) => {
try { try {
@ -15,7 +16,10 @@ const redirector: Interceptor = (next) => async (req) => {
if (error.code === Code.Unauthenticated) { if (error.code === Code.Unauthenticated) {
const redirectURL = new URL(page.url); const redirectURL = new URL(page.url);
redirectURL.pathname = '/auth'; redirectURL.pathname = '/auth';
redirectURL.searchParams.append('redir', encodeURIComponent(page.url.pathname + page.url.search)); redirectURL.searchParams.append(
'redir',
encodeURIComponent(page.url.pathname + page.url.search)
);
await goto(redirectURL); await goto(redirectURL);
} }

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Avatar as AvatarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import { Avatar as AvatarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -13,7 +13,7 @@
bind:ref bind:ref
data-slot="avatar-fallback" data-slot="avatar-fallback"
class={cn( class={cn(
'bg-surface outline-surface-2 flex size-full select-none items-center justify-center rounded-full text-sm transition-all', 'bg-surface flex size-full items-center justify-center rounded-full text-sm transition-all select-none',
className className
)} )}
{...restProps} {...restProps}

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { Avatar as AvatarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -12,6 +12,6 @@
<AvatarPrimitive.Image <AvatarPrimitive.Image
bind:ref bind:ref
data-slot="avatar-image" data-slot="avatar-image"
class={cn("aspect-square size-full", className)} class={cn('aspect-square size-full', className)}
{...restProps} {...restProps}
/> />

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { Avatar as AvatarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -12,6 +12,10 @@
<AvatarPrimitive.Root <AvatarPrimitive.Root
bind:ref bind:ref
data-slot="avatar" data-slot="avatar"
class={cn("relative outline outline-offset-2 outline-surface-1 flex size-9 shrink-0 overflow-hidden rounded-full", className)} class={cn(
'outline-surface-1 relative flex size-9 shrink-0 overflow-hidden rounded-full shadow-xs outline outline-offset-2',
className
)}
{...restProps} {...restProps}
/> />

View File

@ -1,6 +1,6 @@
import Root from "./avatar.svelte"; import Fallback from './avatar-fallback.svelte';
import Image from "./avatar-image.svelte"; import Image from './avatar-image.svelte';
import Fallback from "./avatar-fallback.svelte"; import Root from './avatar.svelte';
export { export {
Root, Root,
@ -9,5 +9,5 @@ export {
// //
Root as Avatar, Root as Avatar,
Image as AvatarImage, Image as AvatarImage,
Fallback as AvatarFallback, Fallback as AvatarFallback
}; };

View File

@ -1,13 +1,14 @@
<script lang="ts" module> <script lang="ts" module>
import type { WithElementRef } from 'bits-ui'; import type { WithElementRef } from 'bits-ui';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements'; import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, tv } from 'tailwind-variants'; import type { VariantProps } from 'tailwind-variants';
import { cn } from '$lib/utils';
import { LoaderCircle } from '@lucide/svelte'; import { LoaderCircle } from '@lucide/svelte';
import { cn } from '$lib/utils';
import { tv } from 'tailwind-variants';
export const buttonVariants = tv({ export const buttonVariants = tv({
base: cn( base: cn(
'shadow-xs inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all', 'inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all',
// Focus // Focus
'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2', 'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2',
@ -16,15 +17,15 @@
'disabled:pointer-events-none disabled:opacity-50', 'disabled:pointer-events-none disabled:opacity-50',
// Images // Images
"[&_svg:not([class*='size-'])]:size-5 [&_svg]:pointer-events-none [&_svg]:shrink-0" "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5"
), ),
variants: { variants: {
variant: { variant: {
default: 'text-crust bg-accent hover:bg-accent/90 shadow-xs', default: 'text-crust bg-accent hover:bg-accent/90 shadow-xs',
red: 'text-crust bg-red hover:bg-red/90 shadow-xs', red: 'text-crust bg-red hover:bg-red/90 shadow-xs',
outline: 'text-text border-surface-1 hover:bg-surface shadow-xs border bg-transparent', outline: 'text-text border-surface-1 hover:bg-surface border bg-transparent shadow-xs',
input: 'text-text border-surface-1 hover:border-overlay shadow-xs border bg-transparent', input: 'text-text border-surface-1 hover:border-overlay border bg-transparent shadow-xs',
ghost: 'text-text hover:bg-surface shadow-xs' ghost: 'text-text hover:bg-surface'
}, },
size: { size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3', default: 'h-9 px-4 py-2 has-[>svg]:px-3',
@ -47,7 +48,6 @@
variant?: ButtonVariant; variant?: ButtonVariant;
size?: ButtonSize; size?: ButtonSize;
loading?: boolean; loading?: boolean;
scan?: boolean;
}; };
</script> </script>
@ -60,7 +60,6 @@
href = undefined, href = undefined,
type = 'button', type = 'button',
loading, loading,
scan,
children, children,
...restProps ...restProps
}: ButtonProps = $props(); }: ButtonProps = $props();

View File

@ -1,9 +1,5 @@
import Root, { import type { ButtonProps, ButtonSize, ButtonVariant } from './button.svelte';
type ButtonProps, import Root, { buttonVariants } from './button.svelte';
type ButtonSize,
type ButtonVariant,
buttonVariants,
} from "./button.svelte";
export { export {
Root, Root,
@ -13,5 +9,5 @@ export {
buttonVariants, buttonVariants,
type ButtonProps, type ButtonProps,
type ButtonSize, type ButtonSize,
type ButtonVariant, type ButtonVariant
}; };

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib/utils';
import type { WithElementRef } from 'bits-ui'; import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils';
type Props = WithElementRef<HTMLAttributes<HTMLDivElement>>; type Props = WithElementRef<HTMLAttributes<HTMLDivElement>>;

View File

@ -1,7 +1,7 @@
import Root from "./card.svelte"; import Root from './card.svelte';
export { export {
Root, Root,
// //
Root as Card, Root as Card
}; };

View File

@ -1,16 +1,12 @@
<script lang="ts"> <script lang="ts">
import CalendarIcon from '@lucide/svelte/icons/calendar'; import type { DateValue } from '@internationalized/date';
import type { DateRange } from 'bits-ui'; import type { DateRange } from 'bits-ui';
import {
CalendarDate,
DateFormatter,
type DateValue,
getLocalTimeZone
} from '@internationalized/date';
import { cn } from '$lib/utils.js';
import { buttonVariants } from '$lib/ui/button'; import { buttonVariants } from '$lib/ui/button';
import { RangeCalendar } from '$lib/ui/range-calendar';
import * as Popover from '$lib/ui/popover'; import * as Popover from '$lib/ui/popover';
import { RangeCalendar } from '$lib/ui/range-calendar';
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date';
import CalendarIcon from '@lucide/svelte/icons/calendar';
import { cn } from '$lib/utils.js';
let { let {
className, className,
@ -41,7 +37,11 @@
<div class={cn('grid gap-2', className)}> <div class={cn('grid gap-2', className)}>
<Popover.Root> <Popover.Root>
<Popover.Trigger <Popover.Trigger
class={cn(buttonVariants({ variant: 'input' }), 'bg-based', !value && 'text-subtext')} class={cn(
buttonVariants({ variant: 'input' }),
'bg-based text-md md:text-sm',
!value && 'text-subtext'
)}
> >
<CalendarIcon class="mr-2 size-4" /> <CalendarIcon class="mr-2 size-4" />
{#if value && value.start} {#if value && value.start}
@ -58,7 +58,7 @@
Pick a date range Pick a date range
{/if} {/if}
</Popover.Trigger> </Popover.Trigger>
<Popover.Content class="w-auto p-0 bg-based" align="start"> <Popover.Content class="bg-based w-auto p-0" align="start">
<RangeCalendar <RangeCalendar
bind:value bind:value
onStartValueChange={(v) => { onStartValueChange={(v) => {

View File

@ -1,6 +1,3 @@
import Root from "./daterangepicker.svelte"; import Root from './daterangepicker.svelte';
export { export { Root, Root as DateRangePicker };
Root,
Root as DateRangePicker
};

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui"; import { Dialog as DialogPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props(); let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props();
</script> </script>

View File

@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive, type WithoutChildrenOrChild } from 'bits-ui'; import type { WithoutChildrenOrChild } from 'bits-ui';
import X from '@lucide/svelte/icons/x';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import * as Dialog from './index.js'; import X from '@lucide/svelte/icons/x';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import { Dialog as DialogPrimitive } from 'bits-ui';
import * as Dialog from './index.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -34,7 +35,7 @@
{@render children?.()} {@render children?.()}
<DialogPrimitive.Close <DialogPrimitive.Close
class={cn( class={cn(
'text-text absolute top-4 right-4 cursor-pointer p-1 rounded hover:bg-crust transition-all disabled:pointer-events-none', 'text-text hover:bg-crust absolute top-4 right-4 cursor-pointer rounded p-1 transition-all disabled:pointer-events-none',
// Focus // Focus
'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2', 'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2',

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { Dialog as DialogPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -12,6 +12,6 @@
<DialogPrimitive.Description <DialogPrimitive.Description
bind:ref bind:ref
data-slot="dialog-description" data-slot="dialog-description"
class={cn("text-muted-foreground text-sm", className)} class={cn('text-muted-foreground text-sm', className)}
{...restProps} {...restProps}
/> />

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { WithElementRef } from "bits-ui"; import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from 'svelte/elements';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -14,7 +14,7 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="dialog-footer" data-slot="dialog-footer"
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} class={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { HTMLAttributes } from "svelte/elements"; import type { WithElementRef } from 'bits-ui';
import type { WithElementRef } from "bits-ui"; import type { HTMLAttributes } from 'svelte/elements';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -14,7 +14,7 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="dialog-header" data-slot="dialog-header"
class={cn("flex flex-col gap-2 text-center sm:text-left", className)} class={cn('flex flex-col gap-2 text-center sm:text-left', className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { Dialog as DialogPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -13,7 +13,7 @@
bind:ref bind:ref
data-slot="dialog-overlay" data-slot="dialog-overlay"
class={cn( class={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className className
)} )}
{...restProps} {...restProps}

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { Dialog as DialogPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -12,6 +12,6 @@
<DialogPrimitive.Title <DialogPrimitive.Title
bind:ref bind:ref
data-slot="dialog-title" data-slot="dialog-title"
class={cn("text-lg font-semibold leading-none", className)} class={cn('text-lg leading-none font-semibold', className)}
{...restProps} {...restProps}
/> />

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui"; import { Dialog as DialogPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props(); let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props();
</script> </script>

View File

@ -1,14 +1,13 @@
import { Dialog as DialogPrimitive } from "bits-ui"; import { Dialog as DialogPrimitive } from 'bits-ui';
import Close from './dialog-close.svelte';
import Content from './dialog-content.svelte';
import Description from './dialog-description.svelte';
import Footer from './dialog-footer.svelte';
import Header from './dialog-header.svelte';
import Overlay from './dialog-overlay.svelte';
import Title from './dialog-title.svelte';
import Trigger from './dialog-trigger.svelte';
import Root from './dialog.svelte'; import Root from './dialog.svelte';
import Title from "./dialog-title.svelte";
import Footer from "./dialog-footer.svelte";
import Header from "./dialog-header.svelte";
import Overlay from "./dialog-overlay.svelte";
import Content from "./dialog-content.svelte";
import Description from "./dialog-description.svelte";
import Trigger from "./dialog-trigger.svelte";
import Close from "./dialog-close.svelte";
const Portal = DialogPrimitive.Portal; const Portal = DialogPrimitive.Portal;
@ -33,5 +32,5 @@ export {
Overlay as DialogOverlay, Overlay as DialogOverlay,
Content as DialogContent, Content as DialogContent,
Description as DialogDescription, Description as DialogDescription,
Close as DialogClose, Close as DialogClose
}; };

View File

@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChildrenOrChild } from "bits-ui"; import type { WithoutChildrenOrChild } from 'bits-ui';
import Check from "@lucide/svelte/icons/check"; import type { Snippet } from 'svelte';
import Minus from "@lucide/svelte/icons/minus"; import Check from '@lucide/svelte/icons/check';
import { cn } from "$lib/utils.js"; import Minus from '@lucide/svelte/icons/minus';
import type { Snippet } from "svelte"; import { cn } from '$lib/utils.js';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -23,13 +24,13 @@
bind:indeterminate bind:indeterminate
data-slot="dropdown-menu-checkbox-item" data-slot="dropdown-menu-checkbox-item"
class={cn( class={cn(
"focus:bg-surface outline-hidden relative flex cursor-pointer select-none items-center gap-2 rounded-sm py-2 pl-8 pr-2 text-sm", 'focus:bg-surface relative flex cursor-pointer items-center gap-2 rounded-sm py-2 pr-2 pl-8 text-sm outline-hidden select-none',
// Disabled // Disabled
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
// Images // Images
"[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className className
)} )}
{...restProps} {...restProps}
@ -39,7 +40,7 @@
{#if indeterminate} {#if indeterminate}
<Minus class="size-4" /> <Minus class="size-4" />
{:else} {:else}
<Check class={cn("size-4", !checked && "text-transparent")} /> <Check class={cn('size-4', !checked && 'text-transparent')} />
{/if} {/if}
</span> </span>
{@render childrenProp?.()} {@render childrenProp?.()}

View File

@ -12,6 +12,6 @@
<DropdownMenuPrimitive.Group <DropdownMenuPrimitive.Group
bind:ref bind:ref
data-slot="dropdown-menu-group" data-slot="dropdown-menu-group"
class={cn('border-b border-surface first:pt-0 last:pb-0 last:border-none', className)} class={cn('border-surface border-b first:pt-0 last:border-none last:pb-0', className)}
{...restProps} {...restProps}
/> />

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from 'svelte/elements';
import { type WithElementRef } from "bits-ui"; import { cn } from '$lib/utils.js';
import type { HTMLAttributes } from "svelte/elements"; import { type WithElementRef } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -17,7 +17,7 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="dropdown-menu-label" data-slot="dropdown-menu-label"
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)} class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from 'bits-ui'; import type { WithoutChild } from 'bits-ui';
import Circle from '@lucide/svelte/icons/circle'; import Circle from '@lucide/svelte/icons/circle';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -15,10 +16,10 @@
bind:ref bind:ref
data-slot="dropdown-menu-radio-item" data-slot="dropdown-menu-radio-item"
class={cn( class={cn(
"focus:bg-surface text-text relative flex cursor-pointer items-center gap-2 py-2 pr-2 pl-8 text-sm select-none", 'focus:bg-surface text-text relative flex cursor-pointer items-center gap-2 py-2 pr-2 pl-8 text-sm select-none',
// Disabled // Disabled
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
// Images // Images
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -12,6 +12,6 @@
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
bind:ref bind:ref
data-slot="dropdown-menu-separator" data-slot="dropdown-menu-separator"
class={cn("bg-surface-1 -mx-1 my-1 h-px", className)} class={cn('bg-surface-1 -mx-1 my-1 h-px', className)}
{...restProps} {...restProps}
/> />

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from 'svelte/elements';
import { type WithElementRef } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { type WithElementRef } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -14,7 +14,7 @@
<span <span
bind:this={ref} bind:this={ref}
data-slot="dropdown-menu-shortcut" data-slot="dropdown-menu-shortcut"
class={cn("text-text ml-auto text-xs tracking-widest", className)} class={cn('text-text ml-auto text-xs tracking-widest', className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -13,7 +13,7 @@
bind:ref bind:ref
data-slot="dropdown-menu-sub-content" data-slot="dropdown-menu-sub-content"
class={cn( class={cn(
'bg-based text-text origin-(--radix-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg', 'bg-based text-text z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
// Animations // Animations
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import ChevronRight from '@lucide/svelte/icons/chevron-right';
import ChevronRight from "@lucide/svelte/icons/chevron-right"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -18,8 +18,8 @@
bind:ref bind:ref
data-slot="dropdown-menu-sub-trigger" data-slot="dropdown-menu-sub-trigger"
class={cn( class={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground outline-hidden flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm", 'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none',
inset && "pl-8", inset && 'pl-8',
className className
)} )}
{...restProps} {...restProps}

View File

@ -14,7 +14,7 @@
data-slot="dropdown-menu-trigger" data-slot="dropdown-menu-trigger"
class={cn( class={cn(
'flex cursor-pointer items-center gap-1 transition-all', 'flex cursor-pointer items-center gap-1 transition-all',
// Focus // Focus
'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2', 'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2',
className className

View File

@ -1,17 +1,17 @@
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; import CheckboxItem from './dropdown-menu-checkbox-item.svelte';
import Content from "./dropdown-menu-content.svelte"; import Content from './dropdown-menu-content.svelte';
import Group from "./dropdown-menu-group.svelte"; import Group from './dropdown-menu-group.svelte';
import Item from "./dropdown-menu-item.svelte"; import Item from './dropdown-menu-item.svelte';
import Label from './dropdown-menu-label.svelte';
import Link from './dropdown-menu-link.svelte'; import Link from './dropdown-menu-link.svelte';
import Label from "./dropdown-menu-label.svelte"; import RadioGroup from './dropdown-menu-radio-group.svelte';
import RadioGroup from "./dropdown-menu-radio-group.svelte"; import RadioItem from './dropdown-menu-radio-item.svelte';
import RadioItem from "./dropdown-menu-radio-item.svelte"; import Separator from './dropdown-menu-separator.svelte';
import Separator from "./dropdown-menu-separator.svelte"; import Shortcut from './dropdown-menu-shortcut.svelte';
import Shortcut from "./dropdown-menu-shortcut.svelte"; import SubContent from './dropdown-menu-sub-content.svelte';
import Trigger from "./dropdown-menu-trigger.svelte"; import SubTrigger from './dropdown-menu-sub-trigger.svelte';
import SubContent from "./dropdown-menu-sub-content.svelte"; import Trigger from './dropdown-menu-trigger.svelte';
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
const Sub = DropdownMenuPrimitive.Sub; const Sub = DropdownMenuPrimitive.Sub;
const Root = DropdownMenuPrimitive.Root; const Root = DropdownMenuPrimitive.Root;
@ -46,5 +46,5 @@ export {
Sub, Sub,
SubContent, SubContent,
SubTrigger, SubTrigger,
Trigger, Trigger
}; };

View File

@ -1,31 +1,31 @@
import { getContext, hasContext, setContext } from 'svelte'; import { getContext, hasContext, setContext } from 'svelte';
type Item = { type Item = {
id: string; id: string;
name: string; name: string;
} };
const key = 'form'; const key = 'form';
export function setFormContext(id: string, name: string) { export function setFormContext(id: string, name: string) {
const item = getFormContext(); const item = getFormContext();
if (!item) { if (!item) {
const item: Item = $state({ const item: Item = $state({
id, id,
name, name
}); });
setContext(key, item); setContext(key, item);
return; return;
} }
item.id = id; item.id = id;
item.name = name; item.name = name;
} }
export function getFormContext() { export function getFormContext() {
if (!hasContext(key)) { if (!hasContext(key)) {
return null; return null;
} }
return getContext(key) as Item; return getContext(key) as Item;
} }

View File

@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib/utils';
import { getFormContext } from './context.svelte';
import type { WithElementRef, WithoutChildren } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import type { Violation } from '@bufbuild/protovalidate'; import type { Violation } from '@bufbuild/protovalidate';
import type { ConnectError } from '@connectrpc/connect'; import type { ConnectError } from '@connectrpc/connect';
import type { WithElementRef, WithoutChildren } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils';
import { getFormContext } from './context.svelte';
type Props = WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> & { type Props = WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> & {
errors?: Violation[] | ConnectError; errors?: Violation[] | ConnectError;
@ -21,7 +21,7 @@
<div bind:this={ref} class={cn('text-red text-sm', className)} {...restProps}> <div bind:this={ref} class={cn('text-red text-sm', className)} {...restProps}>
{#if errors && Array.isArray(errors)} {#if errors && Array.isArray(errors)}
{#each errors as error} {#each errors as error (error)}
<label for={item?.id}>{error.message}</label> <label for={item?.id}>{error.message}</label>
{/each} {/each}
{:else if errors} {:else if errors}

View File

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib/utils';
import { setFormContext } from './context.svelte';
import type { WithElementRef } from 'bits-ui'; import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils';
import { setFormContext } from './context.svelte';
type Props = WithElementRef<HTMLAttributes<HTMLDivElement>> & { type Props = WithElementRef<HTMLAttributes<HTMLDivElement>> & {
name?: string; name?: string;
@ -16,6 +16,6 @@
} }
</script> </script>
<div bind:this={ref} class={cn('flex flex-col gap-1')} {...restProps}> <div bind:this={ref} class={cn('flex flex-col gap-1', className)} {...restProps}>
{@render children?.()} {@render children?.()}
</div> </div>

View File

@ -1,9 +1,5 @@
import Field from './field.svelte';
import Errors from './errors.svelte'; import Errors from './errors.svelte';
import Field from './field.svelte';
import Label from './label.svelte'; import Label from './label.svelte';
export { export { Field, Errors, Label };
Field,
Errors,
Label
};

View File

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib/utils';
import { getFormContext } from './context.svelte';
import type { WithElementRef } from 'bits-ui'; import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils';
import { getFormContext } from './context.svelte';
type Props = WithElementRef<HTMLAttributes<HTMLLabelElement>>; type Props = WithElementRef<HTMLAttributes<HTMLLabelElement>>;
let { ref = $bindable(null), class: className, children, ...restProps }: Props = $props(); let { ref = $bindable(null), class: className, children, ...restProps }: Props = $props();

View File

@ -1,7 +1,7 @@
import Root from "./input.svelte"; import Root from './input.svelte';
export { export {
Root, Root,
// //
Root as Input, Root as Input
}; };

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
import type { WithElementRef } from 'bits-ui'; import type { WithElementRef } from 'bits-ui';
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import { getFormContext } from '../form/context.svelte'; import { getFormContext } from '../form/context.svelte';
@ -8,9 +8,7 @@
type Props = WithElementRef< type Props = WithElementRef<
Omit<HTMLInputAttributes, 'type'> & Omit<HTMLInputAttributes, 'type'> &
({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined }) & { ({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
scan?: boolean;
}
>; >;
let { let {
@ -19,7 +17,6 @@
type, type,
files = $bindable(), files = $bindable(),
class: className, class: className,
scan,
id, id,
name, name,
...restProps ...restProps
@ -40,7 +37,7 @@
{name} {name}
bind:this={ref} bind:this={ref}
class={cn( class={cn(
'border-surface-1 file:bg-surface hover:border-overlay placeholder:text-subtext text-text shadow-xs flex h-9 w-full min-w-0 cursor-pointer rounded-md border text-sm font-medium transition-all file:mr-2 file:px-3 file:py-2 md:text-sm', 'border-surface-1 file:bg-surface hover:border-overlay placeholder:text-subtext text-text flex h-9 w-full min-w-0 cursor-pointer rounded-md border text-sm font-medium shadow-xs transition-all file:mr-2 file:px-3 file:py-2 md:text-sm',
// Focus // Focus
'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2', 'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2',
@ -61,7 +58,7 @@
{name} {name}
bind:this={ref} bind:this={ref}
class={cn( class={cn(
'border-surface-1 hover:border-overlay placeholder:text-subtext text-text shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 transition-all md:text-sm', 'border-surface-1 hover:border-overlay placeholder:text-subtext text-text flex h-9 w-full min-w-0 rounded-md border px-3 py-1 shadow-xs transition-all md:text-sm',
// Focus // Focus
'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2', 'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2',

View File

@ -1,7 +1,7 @@
import Root from "./label.svelte"; import Root from './label.svelte';
export { export {
Root, Root,
// //
Root as Label, Root as Label
}; };

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Label as LabelPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import { Label as LabelPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -13,7 +13,7 @@
bind:ref bind:ref
data-slot="label" data-slot="label"
class={cn( class={cn(
'flex items-center gap-2 text-sm text-text leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50', 'text-text flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
className className
)} )}
{...restProps} {...restProps}

View File

@ -1,5 +1,3 @@
import Pager from './pager.svelte' import Pager from './pager.svelte';
export { export { Pager };
Pager
}

View File

@ -1,10 +1,10 @@
import Root from "./pagination.svelte"; import Content from './pagination-content.svelte';
import Content from "./pagination-content.svelte"; import Ellipsis from './pagination-ellipsis.svelte';
import Item from "./pagination-item.svelte"; import Item from './pagination-item.svelte';
import Link from "./pagination-link.svelte"; import Link from './pagination-link.svelte';
import PrevButton from "./pagination-prev-button.svelte"; import NextButton from './pagination-next-button.svelte';
import NextButton from "./pagination-next-button.svelte"; import PrevButton from './pagination-prev-button.svelte';
import Ellipsis from "./pagination-ellipsis.svelte"; import Root from './pagination.svelte';
export { export {
Root, Root,
@ -21,5 +21,5 @@ export {
Link as PaginationLink, Link as PaginationLink,
PrevButton as PaginationPrevButton, PrevButton as PaginationPrevButton,
NextButton as PaginationNextButton, NextButton as PaginationNextButton,
Ellipsis as PaginationEllipsis, Ellipsis as PaginationEllipsis
}; };

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { HTMLAttributes } from "svelte/elements"; import type { WithElementRef } from 'bits-ui';
import type { WithElementRef } from "bits-ui"; import type { HTMLAttributes } from 'svelte/elements';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -14,7 +14,7 @@
<ul <ul
bind:this={ref} bind:this={ref}
data-slot="pagination-content" data-slot="pagination-content"
class={cn("flex flex-row items-center gap-1", className)} class={cn('flex flex-row items-center gap-1', className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import Ellipsis from "@lucide/svelte/icons/ellipsis"; import type { WithElementRef, WithoutChildren } from 'bits-ui';
import type { WithElementRef, WithoutChildren } from "bits-ui"; import type { HTMLAttributes } from 'svelte/elements';
import type { HTMLAttributes } from "svelte/elements"; import Ellipsis from '@lucide/svelte/icons/ellipsis';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -15,7 +15,7 @@
bind:this={ref} bind:this={ref}
aria-hidden="true" aria-hidden="true"
data-slot="pagination-ellipsis" data-slot="pagination-ellipsis"
class={cn("flex size-9 items-center justify-center text-text", className)} class={cn('text-text flex size-9 items-center justify-center', className)}
{...restProps} {...restProps}
> >
<Ellipsis class="size-4" /> <Ellipsis class="size-4" />

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { HTMLLiAttributes } from "svelte/elements"; import type { WithElementRef } from 'bits-ui';
import type { WithElementRef } from "bits-ui"; import type { HTMLLiAttributes } from 'svelte/elements';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@ -1,12 +1,13 @@
<script lang="ts"> <script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui"; import type { Props } from '$lib/ui/button';
import { cn } from "$lib/utils.js"; import { buttonVariants } from '$lib/ui/button';
import { type Props, buttonVariants } from "$lib/ui/button"; import { cn } from '$lib/utils.js';
import { Pagination as PaginationPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
size = "icon", size = 'icon',
isActive = false, isActive = false,
page, page,
children, children,
@ -24,16 +25,16 @@
<PaginationPrimitive.Page <PaginationPrimitive.Page
bind:ref bind:ref
{page} {page}
aria-current={isActive ? "page" : undefined} aria-current={isActive ? 'page' : undefined}
data-slot="pagination-link" data-slot="pagination-link"
data-active={isActive} data-active={isActive}
class={cn( class={cn(
buttonVariants({ buttonVariants({
variant: "ghost", variant: 'ghost',
size, size
}), }),
'text-text', 'text-text',
isActive && 'bg-surface-1', isActive && 'bg-surface',
className className
)} )}
children={children || Fallback} children={children || Fallback}

View File

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui"; import { buttonVariants } from '$lib/ui/button/index.js';
import ChevronRight from "@lucide/svelte/icons/chevron-right"; import ChevronRight from '@lucide/svelte/icons/chevron-right';
import { buttonVariants } from "$lib/ui/button/index.js"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { Pagination as PaginationPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -23,9 +23,9 @@
aria-label="Go to next page" aria-label="Go to next page"
class={cn( class={cn(
buttonVariants({ buttonVariants({
size: "default", size: 'default',
variant: "ghost", variant: 'ghost',
class: "gap-1 px-2.5 sm:pr-2.5", class: 'gap-1 px-2.5 sm:pr-2.5'
}), }),
className className
)} )}

View File

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui"; import { buttonVariants } from '$lib/ui/button/index.js';
import ChevronLeft from "@lucide/svelte/icons/chevron-left"; import ChevronLeft from '@lucide/svelte/icons/chevron-left';
import { buttonVariants } from "$lib/ui/button/index.js"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { Pagination as PaginationPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -22,9 +22,9 @@
aria-label="Go to previous page" aria-label="Go to previous page"
class={cn( class={cn(
buttonVariants({ buttonVariants({
size: "default", size: 'default',
variant: "ghost", variant: 'ghost',
class: "gap-1 px-2.5 sm:pl-2.5", class: 'gap-1 px-2.5 sm:pl-2.5'
}), }),
className className
)} )}

View File

@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Pagination as PaginationPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
import { pushState } from '$app/navigation'; import { pushState } from '$app/navigation';
import { cn } from '$lib/utils.js';
import { Pagination as PaginationPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -18,7 +17,7 @@
<svelte:window <svelte:window
onpopstate={(state) => { onpopstate={(state) => {
const sks = state.state['sveltekit:states'] as {} | string; const sks = state.state['sveltekit:states'] as object | string;
if (typeof sks === 'string' && sks.includes('#pagination-')) { if (typeof sks === 'string' && sks.includes('#pagination-')) {
page = Number(sks.split('#pagination-')[1]); page = Number(sks.split('#pagination-')[1]);
onPageChange?.(page); onPageChange?.(page);

View File

@ -1,6 +1,7 @@
import { Popover as PopoverPrimitive } from "bits-ui"; import { Popover as PopoverPrimitive } from 'bits-ui';
import Content from "./popover-content.svelte"; import Content from './popover-content.svelte';
import Trigger from "./popover-trigger.svelte"; import Trigger from './popover-trigger.svelte';
const Root = PopoverPrimitive.Root; const Root = PopoverPrimitive.Root;
const Close = PopoverPrimitive.Close; const Close = PopoverPrimitive.Close;
@ -13,5 +14,5 @@ export {
Root as Popover, Root as Popover,
Content as PopoverContent, Content as PopoverContent,
Trigger as PopoverTrigger, Trigger as PopoverTrigger,
Close as PopoverClose, Close as PopoverClose
}; };

View File

@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
import { Popover as PopoverPrimitive } from "bits-ui"; import { Popover as PopoverPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
sideOffset = 4, sideOffset = 4,
align = "center", align = 'center',
portalProps, portalProps,
...restProps ...restProps
}: PopoverPrimitive.ContentProps & { }: PopoverPrimitive.ContentProps & {
@ -21,10 +21,10 @@
{sideOffset} {sideOffset}
{align} {align}
class={cn( class={cn(
"bg-based text-text outline-hidden z-50 w-72 rounded-md border border-surface-1 p-4 shadow-md", 'bg-based text-text border-surface-1 z-50 w-72 rounded-md border p-4 shadow-md outline-hidden',
// Animation // Animation
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-popover-content-transform-origin)", 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-popover-content-transform-origin)',
className className
)} )}
{...restProps} {...restProps}

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
import { Popover as PopoverPrimitive } from "bits-ui"; import { Popover as PopoverPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -12,6 +12,6 @@
<PopoverPrimitive.Trigger <PopoverPrimitive.Trigger
bind:ref bind:ref
data-slot="popover-trigger" data-slot="popover-trigger"
class={cn("", className)} class={cn('', className)}
{...restProps} {...restProps}
/> />

View File

@ -1,15 +1,15 @@
import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui"; import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
import Root from "./range-calendar.svelte"; import Cell from './range-calendar-cell.svelte';
import Cell from "./range-calendar-cell.svelte"; import Day from './range-calendar-day.svelte';
import Day from "./range-calendar-day.svelte"; import GridRow from './range-calendar-grid-row.svelte';
import Grid from "./range-calendar-grid.svelte"; import Grid from './range-calendar-grid.svelte';
import Header from "./range-calendar-header.svelte"; import HeadCell from './range-calendar-head-cell.svelte';
import Months from "./range-calendar-months.svelte"; import Header from './range-calendar-header.svelte';
import GridRow from "./range-calendar-grid-row.svelte"; import Heading from './range-calendar-heading.svelte';
import Heading from "./range-calendar-heading.svelte"; import Months from './range-calendar-months.svelte';
import HeadCell from "./range-calendar-head-cell.svelte"; import NextButton from './range-calendar-next-button.svelte';
import NextButton from "./range-calendar-next-button.svelte"; import PrevButton from './range-calendar-prev-button.svelte';
import PrevButton from "./range-calendar-prev-button.svelte"; import Root from './range-calendar.svelte';
const GridHead = RangeCalendarPrimitive.GridHead; const GridHead = RangeCalendarPrimitive.GridHead;
const GridBody = RangeCalendarPrimitive.GridBody; const GridBody = RangeCalendarPrimitive.GridBody;
@ -28,5 +28,5 @@ export {
NextButton, NextButton,
PrevButton, PrevButton,
// //
Root as RangeCalendar, Root as RangeCalendar
}; };

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -12,7 +12,7 @@
<RangeCalendarPrimitive.Cell <RangeCalendarPrimitive.Cell
bind:ref bind:ref
class={cn( class={cn(
"[&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50 relative size-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 first:[&:has([data-selected])]:rounded-l-md last:[&:has([data-selected])]:rounded-r-md [&:has([data-selected][data-selection-end])]:rounded-r-md [&:has([data-selected][data-selection-start])]:rounded-l-md", '[&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50 relative size-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 first:[&:has([data-selected])]:rounded-l-md last:[&:has([data-selected])]:rounded-r-md [&:has([data-selected][data-selection-end])]:rounded-r-md [&:has([data-selected][data-selection-start])]:rounded-l-md',
className className
)} )}
{...restProps} {...restProps}

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
import { buttonVariants } from '$lib/ui/button/index.js'; import { buttonVariants } from '$lib/ui/button/index.js';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -17,7 +17,7 @@
class={cn( class={cn(
buttonVariants({ variant: 'ghost' }), buttonVariants({ variant: 'ghost' }),
'size-9 p-0 font-normal', 'size-9 p-0 font-normal',
'[&[data-today]:not([data-selected])]:bg-blue [&[data-today]:not([data-selected])]:text-crust [&[data-today]:not([data-selected])]:rounded-md', '[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-crust [&[data-today]:not([data-selected])]:rounded-md',
// Selected // Selected
'data-[selected]:bg-surface data-[selected]:hover:bg-surface-1 data-[selected]:rounded-none data-[selected]:opacity-100', 'data-[selected]:bg-surface data-[selected]:hover:bg-surface-1 data-[selected]:rounded-none data-[selected]:opacity-100',
// Selection Start // Selection Start

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -9,4 +9,4 @@
}: RangeCalendarPrimitive.GridRowProps = $props(); }: RangeCalendarPrimitive.GridRowProps = $props();
</script> </script>
<RangeCalendarPrimitive.GridRow bind:ref class={cn("flex", className)} {...restProps} /> <RangeCalendarPrimitive.GridRow bind:ref class={cn('flex', className)} {...restProps} />

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -11,6 +11,6 @@
<RangeCalendarPrimitive.Grid <RangeCalendarPrimitive.Grid
bind:ref bind:ref
class={cn("w-full border-collapse space-y-1", className)} class={cn('w-full border-collapse space-y-1', className)}
{...restProps} {...restProps}
/> />

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -11,6 +11,6 @@
<RangeCalendarPrimitive.HeadCell <RangeCalendarPrimitive.HeadCell
bind:ref bind:ref
class={cn("text-muted-foreground w-9 rounded-md text-[0.8rem] font-normal", className)} class={cn('text-muted-foreground w-9 rounded-md text-[0.8rem] font-normal', className)}
{...restProps} {...restProps}
/> />

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -11,6 +11,6 @@
<RangeCalendarPrimitive.Header <RangeCalendarPrimitive.Header
bind:ref bind:ref
class={cn("relative flex w-full items-center justify-between pt-1", className)} class={cn('relative flex w-full items-center justify-between pt-1', className)}
{...restProps} {...restProps}
/> />

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -11,6 +11,6 @@
<RangeCalendarPrimitive.Heading <RangeCalendarPrimitive.Heading
bind:ref bind:ref
class={cn("text-sm font-medium", className)} class={cn('text-sm font-medium', className)}
{...restProps} {...restProps}
/> />

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { WithElementRef } from "bits-ui"; import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from 'svelte/elements';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -13,7 +13,7 @@
<div <div
bind:this={ref} bind:this={ref}
class={cn("mt-4 flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0", className)} class={cn('mt-4 flex flex-col space-y-4 sm:flex-row sm:space-y-0 sm:space-x-4', className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui"; import { buttonVariants } from '$lib/ui/button/index.js';
import ChevronRight from "@lucide/svelte/icons/chevron-right"; import ChevronRight from '@lucide/svelte/icons/chevron-right';
import { buttonVariants } from "$lib/ui/button/index.js"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -19,8 +19,8 @@
<RangeCalendarPrimitive.NextButton <RangeCalendarPrimitive.NextButton
bind:ref bind:ref
class={cn( class={cn(
buttonVariants({ variant: "outline" }), buttonVariants({ variant: 'outline' }),
"size-7 bg-transparent p-0 opacity-50 hover:opacity-100", 'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
className className
)} )}
children={children || Fallback} children={children || Fallback}

View File

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui"; import { buttonVariants } from '$lib/ui/button/index.js';
import ChevronLeft from "@lucide/svelte/icons/chevron-left"; import ChevronLeft from '@lucide/svelte/icons/chevron-left';
import { buttonVariants } from "$lib/ui/button/index.js"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -19,8 +19,8 @@
<RangeCalendarPrimitive.PrevButton <RangeCalendarPrimitive.PrevButton
bind:ref bind:ref
class={cn( class={cn(
buttonVariants({ variant: "outline" }), buttonVariants({ variant: 'outline' }),
"size-7 bg-transparent p-0 opacity-50 hover:opacity-100", 'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
className className
)} )}
children={children || Fallback} children={children || Fallback}

View File

@ -1,13 +1,14 @@
<script lang="ts"> <script lang="ts">
import { RangeCalendar as RangeCalendarPrimitive, type WithoutChildrenOrChild } from "bits-ui"; import type { WithoutChildrenOrChild } from 'bits-ui';
import * as RangeCalendar from "./index.js"; import { cn } from '$lib/utils.js';
import { cn } from "$lib/utils.js"; import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui';
import * as RangeCalendar from './index.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
value = $bindable(), value = $bindable(),
placeholder = $bindable(), placeholder = $bindable(),
weekdayFormat = "short", weekdayFormat = 'short',
class: className, class: className,
...restProps ...restProps
}: WithoutChildrenOrChild<RangeCalendarPrimitive.RootProps> = $props(); }: WithoutChildrenOrChild<RangeCalendarPrimitive.RootProps> = $props();
@ -18,7 +19,7 @@
bind:value bind:value
bind:placeholder bind:placeholder
{weekdayFormat} {weekdayFormat}
class={cn("p-3", className)} class={cn('p-3', className)}
{...restProps} {...restProps}
> >
{#snippet children({ months, weekdays })} {#snippet children({ months, weekdays })}

View File

@ -1,11 +1,10 @@
import { Select as SelectPrimitive } from "bits-ui"; import { Select as SelectPrimitive } from 'bits-ui';
import Content from './select-content.svelte';
import Group from "./select-group.svelte"; import Group from './select-group.svelte';
import Label from "./select-label.svelte"; import Item from './select-item.svelte';
import Item from "./select-item.svelte"; import Label from './select-label.svelte';
import Content from "./select-content.svelte"; import Separator from './select-separator.svelte';
import Trigger from "./select-trigger.svelte"; import Trigger from './select-trigger.svelte';
import Separator from "./select-separator.svelte";
const Root = SelectPrimitive.Root; const Root = SelectPrimitive.Root;
@ -24,5 +23,5 @@ export {
Item as SelectItem, Item as SelectItem,
Content as SelectContent, Content as SelectContent,
Trigger as SelectTrigger, Trigger as SelectTrigger,
Separator as SelectSeparator, Separator as SelectSeparator
}; };

View File

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Select as SelectPrimitive, type WithoutChild } from 'bits-ui'; import type { WithoutChild } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import { Select as SelectPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Select as SelectPrimitive } from "bits-ui"; import { Select as SelectPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: SelectPrimitive.GroupProps = $props(); let { ref = $bindable(null), ...restProps }: SelectPrimitive.GroupProps = $props();
</script> </script>

View File

@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import type { WithoutChild } from 'bits-ui';
import Check from '@lucide/svelte/icons/check'; import Check from '@lucide/svelte/icons/check';
import { Select as SelectPrimitive, type WithoutChild } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import { Select as SelectPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from 'svelte/elements';
import { type WithElementRef } from "bits-ui"; import { cn } from '$lib/utils.js';
import type { HTMLAttributes } from "svelte/elements"; import { type WithElementRef } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -14,7 +14,7 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="select-label" data-slot="select-label"
class={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} class={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { Separator as SeparatorPrimitive } from "bits-ui"; import type { Separator as SeparatorPrimitive } from 'bits-ui';
import { Separator } from "$lib/ui/separator/index.js"; import { Separator } from '$lib/ui/separator/index.js';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -13,6 +13,6 @@
<Separator <Separator
bind:ref bind:ref
data-slot="select-separator" data-slot="select-separator"
class={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)} class={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
{...restProps} {...restProps}
/> />

View File

@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Select as SelectPrimitive, type WithoutChild } from 'bits-ui'; import type { WithoutChild } from 'bits-ui';
import ChevronDown from '@lucide/svelte/icons/chevron-down'; import ChevronDown from '@lucide/svelte/icons/chevron-down';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import { Select as SelectPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -19,7 +20,7 @@
data-slot="select-trigger" data-slot="select-trigger"
data-size={size} data-size={size}
class={cn( class={cn(
'border-surface-1 bg-based hover:border-overlay text-text flex flex-row-reverse w-full cursor-pointer items-center justify-between gap-2 rounded-md border px-3 py-2 md:text-sm whitespace-nowrap shadow-xs transition-all', 'border-surface-1 bg-based hover:border-overlay text-text flex w-full cursor-pointer flex-row-reverse items-center justify-between gap-2 rounded-md border px-3 py-2 whitespace-nowrap shadow-xs transition-all md:text-sm',
// Focus // Focus
'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2', 'focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2',

View File

@ -1,7 +1,7 @@
import Root from "./separator.svelte"; import Root from './separator.svelte';
export { export {
Root, Root,
// //
Root as Separator, Root as Separator
}; };

Some files were not shown because too many files have changed in this diff Show More