From 8254a28baa00845c84e128e82388a3e316fb9bd1 Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Wed, 8 Apr 2026 04:29:35 +0200 Subject: [PATCH] init --- .devenv.flake.nix | 513 ++++++++++++++++++ .gitignore | 36 ++ .helix/languages.toml | 4 + .ignore | 2 + CLAUDE.md | 106 ++++ README.md | 15 + bun.lock | 47 ++ devenv.nix | 7 + index.ts | 56 ++ package.json | 20 + template/.envrc | 12 + template/.gitignore | 12 + template/.ignore | 10 + template/apps/web/.gitignore | 26 + template/apps/web/.ignore | 26 + template/apps/web/.npmrc | 1 + template/apps/web/.prettierignore | 9 + template/apps/web/.prettierrc | 16 + template/apps/web/.vscode/extensions.json | 3 + template/apps/web/.vscode/settings.json | 5 + template/apps/web/README.md | 42 ++ template/apps/web/components.json | 20 + template/apps/web/messages/de-de.json | 4 + template/apps/web/messages/en.json | 4 + template/apps/web/package.json | 59 ++ .../apps/web/project.inlang/settings.json | 12 + template/apps/web/src/app.html | 16 + template/apps/web/src/hooks.server.ts | 17 + template/apps/web/src/hooks.ts | 4 + template/apps/web/src/lib/assets/favicon.svg | 1 + .../lib/components/todos/CreateTodo.svelte | 26 + .../src/lib/components/todos/ListTodos.svelte | 50 ++ .../web/src/lib/components/todos/Todo.svelte | 70 +++ .../ui/accordion/accordion-content.svelte | 27 + .../ui/accordion/accordion-item.svelte | 17 + .../ui/accordion/accordion-trigger.svelte | 32 ++ .../components/ui/accordion/accordion.svelte | 19 + .../src/lib/components/ui/accordion/index.ts | 16 + .../alert-dialog/alert-dialog-action.svelte | 27 + .../alert-dialog/alert-dialog-cancel.svelte | 27 + .../alert-dialog/alert-dialog-content.svelte | 32 ++ .../alert-dialog-description.svelte | 17 + .../alert-dialog/alert-dialog-footer.svelte | 23 + .../alert-dialog/alert-dialog-header.svelte | 20 + .../ui/alert-dialog/alert-dialog-media.svelte | 20 + .../alert-dialog/alert-dialog-overlay.svelte | 17 + .../alert-dialog/alert-dialog-portal.svelte | 7 + .../ui/alert-dialog/alert-dialog-title.svelte | 17 + .../alert-dialog/alert-dialog-trigger.svelte | 7 + .../ui/alert-dialog/alert-dialog.svelte | 7 + .../lib/components/ui/alert-dialog/index.ts | 40 ++ .../components/ui/alert/alert-action.svelte | 20 + .../ui/alert/alert-description.svelte | 23 + .../components/ui/alert/alert-title.svelte | 23 + .../src/lib/components/ui/alert/alert.svelte | 43 ++ .../web/src/lib/components/ui/alert/index.ts | 17 + .../ui/aspect-ratio/aspect-ratio.svelte | 7 + .../lib/components/ui/aspect-ratio/index.ts | 3 + .../components/ui/avatar/avatar-badge.svelte | 26 + .../ui/avatar/avatar-fallback.svelte | 20 + .../ui/avatar/avatar-group-count.svelte | 23 + .../components/ui/avatar/avatar-group.svelte | 23 + .../components/ui/avatar/avatar-image.svelte | 17 + .../lib/components/ui/avatar/avatar.svelte | 26 + .../web/src/lib/components/ui/avatar/index.ts | 22 + .../src/lib/components/ui/badge/badge.svelte | 49 ++ .../web/src/lib/components/ui/badge/index.ts | 2 + .../ui/breadcrumb/breadcrumb-ellipsis.svelte | 23 + .../ui/breadcrumb/breadcrumb-item.svelte | 20 + .../ui/breadcrumb/breadcrumb-link.svelte | 31 ++ .../ui/breadcrumb/breadcrumb-list.svelte | 20 + .../ui/breadcrumb/breadcrumb-page.svelte | 23 + .../ui/breadcrumb/breadcrumb-separator.svelte | 27 + .../ui/breadcrumb/breadcrumb.svelte | 22 + .../src/lib/components/ui/breadcrumb/index.ts | 25 + .../button-group-separator.svelte | 23 + .../ui/button-group/button-group-text.svelte | 28 + .../ui/button-group/button-group.svelte | 46 ++ .../lib/components/ui/button-group/index.ts | 15 + .../lib/components/ui/button/button.svelte | 82 +++ .../web/src/lib/components/ui/button/index.ts | 17 + .../ui/calendar/calendar-caption.svelte | 76 +++ .../ui/calendar/calendar-cell.svelte | 19 + .../ui/calendar/calendar-day.svelte | 33 ++ .../ui/calendar/calendar-grid-body.svelte | 12 + .../ui/calendar/calendar-grid-head.svelte | 12 + .../ui/calendar/calendar-grid-row.svelte | 12 + .../ui/calendar/calendar-grid.svelte | 16 + .../ui/calendar/calendar-head-cell.svelte | 19 + .../ui/calendar/calendar-header.svelte | 19 + .../ui/calendar/calendar-heading.svelte | 16 + .../ui/calendar/calendar-month-select.svelte | 48 ++ .../ui/calendar/calendar-month.svelte | 15 + .../ui/calendar/calendar-months.svelte | 19 + .../ui/calendar/calendar-nav.svelte | 19 + .../ui/calendar/calendar-next-button.svelte | 36 ++ .../ui/calendar/calendar-prev-button.svelte | 36 ++ .../ui/calendar/calendar-year-select.svelte | 47 ++ .../components/ui/calendar/calendar.svelte | 115 ++++ .../src/lib/components/ui/calendar/index.ts | 40 ++ .../lib/components/ui/card/card-action.svelte | 23 + .../components/ui/card/card-content.svelte | 20 + .../ui/card/card-description.svelte | 20 + .../lib/components/ui/card/card-footer.svelte | 20 + .../lib/components/ui/card/card-header.svelte | 23 + .../lib/components/ui/card/card-title.svelte | 20 + .../src/lib/components/ui/card/card.svelte | 22 + .../web/src/lib/components/ui/card/index.ts | 25 + .../ui/carousel/carousel-content.svelte | 43 ++ .../ui/carousel/carousel-item.svelte | 30 + .../ui/carousel/carousel-next.svelte | 39 ++ .../ui/carousel/carousel-previous.svelte | 39 ++ .../components/ui/carousel/carousel.svelte | 94 ++++ .../src/lib/components/ui/carousel/context.ts | 58 ++ .../src/lib/components/ui/carousel/index.ts | 19 + .../ui/chart/chart-container.svelte | 80 +++ .../components/ui/chart/chart-style.svelte | 37 ++ .../components/ui/chart/chart-tooltip.svelte | 184 +++++++ .../lib/components/ui/chart/chart-utils.ts | 68 +++ .../web/src/lib/components/ui/chart/index.ts | 6 + .../components/ui/checkbox/checkbox.svelte | 39 ++ .../src/lib/components/ui/checkbox/index.ts | 6 + .../ui/collapsible/collapsible-content.svelte | 7 + .../ui/collapsible/collapsible-trigger.svelte | 7 + .../ui/collapsible/collapsible.svelte | 11 + .../lib/components/ui/collapsible/index.ts | 13 + .../ui/command/command-dialog.svelte | 42 ++ .../ui/command/command-empty.svelte | 17 + .../ui/command/command-group.svelte | 32 ++ .../ui/command/command-input.svelte | 31 ++ .../components/ui/command/command-item.svelte | 25 + .../ui/command/command-link-item.svelte | 20 + .../components/ui/command/command-list.svelte | 17 + .../ui/command/command-loading.svelte | 7 + .../ui/command/command-separator.svelte | 17 + .../ui/command/command-shortcut.svelte | 20 + .../lib/components/ui/command/command.svelte | 25 + .../src/lib/components/ui/command/index.ts | 37 ++ .../context-menu-checkbox-item.svelte | 41 ++ .../context-menu/context-menu-content.svelte | 28 + .../context-menu-group-heading.svelte | 21 + .../ui/context-menu/context-menu-group.svelte | 7 + .../ui/context-menu/context-menu-item.svelte | 27 + .../ui/context-menu/context-menu-label.svelte | 24 + .../context-menu/context-menu-portal.svelte | 7 + .../context-menu-radio-group.svelte | 16 + .../context-menu-radio-item.svelte | 35 ++ .../context-menu-separator.svelte | 17 + .../context-menu/context-menu-shortcut.svelte | 20 + .../context-menu-sub-content.svelte | 17 + .../context-menu-sub-trigger.svelte | 29 + .../ui/context-menu/context-menu-sub.svelte | 7 + .../context-menu/context-menu-trigger.svelte | 17 + .../ui/context-menu/context-menu.svelte | 7 + .../lib/components/ui/context-menu/index.ts | 52 ++ .../ui/data-table/data-table.svelte.ts | 142 +++++ .../ui/data-table/flex-render.svelte | 40 ++ .../src/lib/components/ui/data-table/index.ts | 3 + .../ui/data-table/render-helpers.ts | 111 ++++ .../components/ui/dialog/dialog-close.svelte | 11 + .../ui/dialog/dialog-content.svelte | 48 ++ .../ui/dialog/dialog-description.svelte | 17 + .../components/ui/dialog/dialog-footer.svelte | 32 ++ .../components/ui/dialog/dialog-header.svelte | 20 + .../ui/dialog/dialog-overlay.svelte | 17 + .../components/ui/dialog/dialog-portal.svelte | 7 + .../components/ui/dialog/dialog-title.svelte | 17 + .../ui/dialog/dialog-trigger.svelte | 11 + .../lib/components/ui/dialog/dialog.svelte | 7 + .../web/src/lib/components/ui/dialog/index.ts | 34 ++ .../components/ui/drawer/drawer-close.svelte | 7 + .../ui/drawer/drawer-content.svelte | 33 ++ .../ui/drawer/drawer-description.svelte | 17 + .../components/ui/drawer/drawer-footer.svelte | 20 + .../components/ui/drawer/drawer-header.svelte | 20 + .../components/ui/drawer/drawer-nested.svelte | 12 + .../ui/drawer/drawer-overlay.svelte | 17 + .../components/ui/drawer/drawer-portal.svelte | 7 + .../components/ui/drawer/drawer-title.svelte | 17 + .../ui/drawer/drawer-trigger.svelte | 7 + .../lib/components/ui/drawer/drawer.svelte | 12 + .../web/src/lib/components/ui/drawer/index.ts | 38 ++ .../dropdown-menu-checkbox-group.svelte | 16 + .../dropdown-menu-checkbox-item.svelte | 44 ++ .../dropdown-menu-content.svelte | 31 ++ .../dropdown-menu-group-heading.svelte | 22 + .../dropdown-menu/dropdown-menu-group.svelte | 7 + .../dropdown-menu/dropdown-menu-item.svelte | 27 + .../dropdown-menu/dropdown-menu-label.svelte | 24 + .../dropdown-menu/dropdown-menu-portal.svelte | 7 + .../dropdown-menu-radio-group.svelte | 16 + .../dropdown-menu-radio-item.svelte | 34 ++ .../dropdown-menu-separator.svelte | 17 + .../dropdown-menu-shortcut.svelte | 20 + .../dropdown-menu-sub-content.svelte | 17 + .../dropdown-menu-sub-trigger.svelte | 29 + .../ui/dropdown-menu/dropdown-menu-sub.svelte | 7 + .../dropdown-menu-trigger.svelte | 7 + .../ui/dropdown-menu/dropdown-menu.svelte | 7 + .../lib/components/ui/dropdown-menu/index.ts | 54 ++ .../components/ui/empty/empty-content.svelte | 23 + .../ui/empty/empty-description.svelte | 23 + .../components/ui/empty/empty-header.svelte | 20 + .../components/ui/empty/empty-media.svelte | 41 ++ .../components/ui/empty/empty-title.svelte | 20 + .../src/lib/components/ui/empty/empty.svelte | 23 + .../web/src/lib/components/ui/empty/index.ts | 22 + .../components/ui/field/field-content.svelte | 20 + .../ui/field/field-description.svelte | 25 + .../components/ui/field/field-error.svelte | 58 ++ .../components/ui/field/field-group.svelte | 23 + .../components/ui/field/field-label.svelte | 25 + .../components/ui/field/field-legend.svelte | 24 + .../ui/field/field-separator.svelte | 35 ++ .../lib/components/ui/field/field-set.svelte | 20 + .../components/ui/field/field-title.svelte | 20 + .../src/lib/components/ui/field/field.svelte | 47 ++ .../web/src/lib/components/ui/field/index.ts | 33 ++ .../lib/components/ui/form/form-button.svelte | 7 + .../ui/form/form-description.svelte | 17 + .../ui/form/form-element-field.svelte | 24 + .../ui/form/form-field-errors.svelte | 30 + .../lib/components/ui/form/form-field.svelte | 29 + .../components/ui/form/form-fieldset.svelte | 15 + .../lib/components/ui/form/form-label.svelte | 24 + .../lib/components/ui/form/form-legend.svelte | 16 + .../web/src/lib/components/ui/form/index.ts | 33 ++ .../ui/hover-card/hover-card-content.svelte | 31 ++ .../ui/hover-card/hover-card-portal.svelte | 7 + .../ui/hover-card/hover-card-trigger.svelte | 7 + .../ui/hover-card/hover-card.svelte | 7 + .../src/lib/components/ui/hover-card/index.ts | 15 + .../lib/components/ui/input-group/index.ts | 22 + .../ui/input-group/input-group-addon.svelte | 52 ++ .../ui/input-group/input-group-button.svelte | 49 ++ .../ui/input-group/input-group-input.svelte | 20 + .../ui/input-group/input-group-text.svelte | 19 + .../input-group/input-group-textarea.svelte | 20 + .../ui/input-group/input-group.svelte | 24 + .../src/lib/components/ui/input-otp/index.ts | 15 + .../ui/input-otp/input-otp-group.svelte | 20 + .../ui/input-otp/input-otp-separator.svelte | 27 + .../ui/input-otp/input-otp-slot.svelte | 31 ++ .../components/ui/input-otp/input-otp.svelte | 23 + .../web/src/lib/components/ui/input/index.ts | 7 + .../src/lib/components/ui/input/input.svelte | 48 ++ .../web/src/lib/components/ui/item/index.ts | 34 ++ .../components/ui/item/item-actions.svelte | 20 + .../components/ui/item/item-content.svelte | 23 + .../ui/item/item-description.svelte | 23 + .../lib/components/ui/item/item-footer.svelte | 20 + .../lib/components/ui/item/item-group.svelte | 21 + .../lib/components/ui/item/item-header.svelte | 20 + .../lib/components/ui/item/item-media.svelte | 42 ++ .../components/ui/item/item-separator.svelte | 19 + .../lib/components/ui/item/item-title.svelte | 20 + .../src/lib/components/ui/item/item.svelte | 61 +++ .../web/src/lib/components/ui/kbd/index.ts | 10 + .../lib/components/ui/kbd/kbd-group.svelte | 20 + .../web/src/lib/components/ui/kbd/kbd.svelte | 23 + .../web/src/lib/components/ui/label/index.ts | 7 + .../src/lib/components/ui/label/label.svelte | 20 + .../src/lib/components/ui/menubar/index.ts | 55 ++ .../ui/menubar/menubar-checkbox-item.svelte | 46 ++ .../ui/menubar/menubar-content.svelte | 35 ++ .../ui/menubar/menubar-group-heading.svelte | 22 + .../ui/menubar/menubar-group.svelte | 12 + .../components/ui/menubar/menubar-item.svelte | 24 + .../ui/menubar/menubar-label.svelte | 25 + .../components/ui/menubar/menubar-menu.svelte | 7 + .../ui/menubar/menubar-portal.svelte | 7 + .../ui/menubar/menubar-radio-group.svelte | 11 + .../ui/menubar/menubar-radio-item.svelte | 37 ++ .../ui/menubar/menubar-separator.svelte | 17 + .../ui/menubar/menubar-shortcut.svelte | 20 + .../ui/menubar/menubar-sub-content.svelte | 17 + .../ui/menubar/menubar-sub-trigger.svelte | 29 + .../components/ui/menubar/menubar-sub.svelte | 7 + .../ui/menubar/menubar-trigger.svelte | 17 + .../lib/components/ui/menubar/menubar.svelte | 17 + .../lib/components/ui/native-select/index.ts | 12 + .../native-select-opt-group.svelte | 14 + .../native-select/native-select-option.svelte | 14 + .../ui/native-select/native-select.svelte | 39 ++ .../components/ui/navigation-menu/index.ts | 28 + .../navigation-menu-content.svelte | 20 + .../navigation-menu-indicator.svelte | 22 + .../navigation-menu-item.svelte | 17 + .../navigation-menu-link.svelte | 17 + .../navigation-menu-list.svelte | 20 + .../navigation-menu-trigger.svelte | 29 + .../navigation-menu-viewport.svelte | 22 + .../ui/navigation-menu/navigation-menu.svelte | 31 ++ .../src/lib/components/ui/pagination/index.ts | 31 ++ .../ui/pagination/pagination-content.svelte | 20 + .../ui/pagination/pagination-ellipsis.svelte | 22 + .../ui/pagination/pagination-item.svelte | 14 + .../ui/pagination/pagination-link.svelte | 42 ++ .../pagination/pagination-next-button.svelte | 31 ++ .../ui/pagination/pagination-next.svelte | 20 + .../pagination/pagination-prev-button.svelte | 31 ++ .../ui/pagination/pagination-previous.svelte | 20 + .../ui/pagination/pagination.svelte | 28 + .../src/lib/components/ui/popover/index.ts | 28 + .../ui/popover/popover-close.svelte | 7 + .../ui/popover/popover-content.svelte | 31 ++ .../ui/popover/popover-description.svelte | 20 + .../ui/popover/popover-header.svelte | 20 + .../ui/popover/popover-portal.svelte | 7 + .../ui/popover/popover-title.svelte | 20 + .../ui/popover/popover-trigger.svelte | 17 + .../lib/components/ui/popover/popover.svelte | 7 + .../src/lib/components/ui/progress/index.ts | 7 + .../components/ui/progress/progress.svelte | 27 + .../lib/components/ui/radio-group/index.ts | 10 + .../ui/radio-group/radio-group-item.svelte | 29 + .../ui/radio-group/radio-group.svelte | 19 + .../lib/components/ui/range-calendar/index.ts | 40 ++ .../range-calendar-caption.svelte | 76 +++ .../range-calendar/range-calendar-cell.svelte | 19 + .../range-calendar/range-calendar-day.svelte | 36 ++ .../range-calendar-grid-body.svelte | 7 + .../range-calendar-grid-head.svelte | 7 + .../range-calendar-grid-row.svelte | 12 + .../range-calendar/range-calendar-grid.svelte | 16 + .../range-calendar-head-cell.svelte | 19 + .../range-calendar-header.svelte | 19 + .../range-calendar-heading.svelte | 16 + .../range-calendar-month-select.svelte | 44 ++ .../range-calendar-month.svelte | 15 + .../range-calendar-months.svelte | 19 + .../range-calendar/range-calendar-nav.svelte | 19 + .../range-calendar-next-button.svelte | 36 ++ .../range-calendar-prev-button.svelte | 36 ++ .../range-calendar-year-select.svelte | 43 ++ .../ui/range-calendar/range-calendar.svelte | 112 ++++ .../src/lib/components/ui/resizable/index.ts | 13 + .../ui/resizable/resizable-handle.svelte | 27 + .../ui/resizable/resizable-pane-group.svelte | 24 + .../lib/components/ui/scroll-area/index.ts | 10 + .../scroll-area/scroll-area-scrollbar.svelte | 30 + .../ui/scroll-area/scroll-area.svelte | 43 ++ .../web/src/lib/components/ui/select/index.ts | 37 ++ .../ui/select/select-content.svelte | 45 ++ .../ui/select/select-group-heading.svelte | 21 + .../components/ui/select/select-group.svelte | 17 + .../components/ui/select/select-item.svelte | 38 ++ .../components/ui/select/select-label.svelte | 20 + .../components/ui/select/select-portal.svelte | 7 + .../select/select-scroll-down-button.svelte | 20 + .../ui/select/select-scroll-up-button.svelte | 20 + .../ui/select/select-separator.svelte | 18 + .../ui/select/select-trigger.svelte | 29 + .../lib/components/ui/select/select.svelte | 11 + .../src/lib/components/ui/separator/index.ts | 7 + .../components/ui/separator/separator.svelte | 23 + .../web/src/lib/components/ui/sheet/index.ts | 34 ++ .../components/ui/sheet/sheet-close.svelte | 7 + .../components/ui/sheet/sheet-content.svelte | 55 ++ .../ui/sheet/sheet-description.svelte | 17 + .../components/ui/sheet/sheet-footer.svelte | 20 + .../components/ui/sheet/sheet-header.svelte | 20 + .../components/ui/sheet/sheet-overlay.svelte | 17 + .../components/ui/sheet/sheet-portal.svelte | 7 + .../components/ui/sheet/sheet-title.svelte | 17 + .../components/ui/sheet/sheet-trigger.svelte | 7 + .../src/lib/components/ui/sheet/sheet.svelte | 7 + .../lib/components/ui/sidebar/constants.ts | 6 + .../components/ui/sidebar/context.svelte.ts | 81 +++ .../src/lib/components/ui/sidebar/index.ts | 75 +++ .../ui/sidebar/sidebar-content.svelte | 24 + .../ui/sidebar/sidebar-footer.svelte | 21 + .../ui/sidebar/sidebar-group-action.svelte | 33 ++ .../ui/sidebar/sidebar-group-content.svelte | 21 + .../ui/sidebar/sidebar-group-label.svelte | 33 ++ .../ui/sidebar/sidebar-group.svelte | 21 + .../ui/sidebar/sidebar-header.svelte | 21 + .../ui/sidebar/sidebar-input.svelte | 21 + .../ui/sidebar/sidebar-inset.svelte | 20 + .../ui/sidebar/sidebar-menu-action.svelte | 37 ++ .../ui/sidebar/sidebar-menu-badge.svelte | 24 + .../ui/sidebar/sidebar-menu-button.svelte | 102 ++++ .../ui/sidebar/sidebar-menu-item.svelte | 21 + .../ui/sidebar/sidebar-menu-skeleton.svelte | 36 ++ .../ui/sidebar/sidebar-menu-sub-button.svelte | 39 ++ .../ui/sidebar/sidebar-menu-sub-item.svelte | 21 + .../ui/sidebar/sidebar-menu-sub.svelte | 21 + .../components/ui/sidebar/sidebar-menu.svelte | 21 + .../ui/sidebar/sidebar-provider.svelte | 53 ++ .../components/ui/sidebar/sidebar-rail.svelte | 36 ++ .../ui/sidebar/sidebar-separator.svelte | 19 + .../ui/sidebar/sidebar-trigger.svelte | 36 ++ .../lib/components/ui/sidebar/sidebar.svelte | 108 ++++ .../src/lib/components/ui/skeleton/index.ts | 7 + .../components/ui/skeleton/skeleton.svelte | 17 + .../web/src/lib/components/ui/slider/index.ts | 7 + .../lib/components/ui/slider/slider.svelte | 52 ++ .../web/src/lib/components/ui/sonner/index.ts | 1 + .../lib/components/ui/sonner/sonner.svelte | 34 ++ .../src/lib/components/ui/spinner/index.ts | 1 + .../lib/components/ui/spinner/spinner.svelte | 18 + .../web/src/lib/components/ui/switch/index.ts | 7 + .../lib/components/ui/switch/switch.svelte | 31 ++ .../web/src/lib/components/ui/table/index.ts | 28 + .../lib/components/ui/table/table-body.svelte | 15 + .../components/ui/table/table-caption.svelte | 20 + .../lib/components/ui/table/table-cell.svelte | 15 + .../components/ui/table/table-footer.svelte | 20 + .../lib/components/ui/table/table-head.svelte | 15 + .../components/ui/table/table-header.svelte | 20 + .../lib/components/ui/table/table-row.svelte | 15 + .../src/lib/components/ui/table/table.svelte | 17 + .../web/src/lib/components/ui/tabs/index.ts | 18 + .../components/ui/tabs/tabs-content.svelte | 17 + .../lib/components/ui/tabs/tabs-list.svelte | 40 ++ .../components/ui/tabs/tabs-trigger.svelte | 23 + .../src/lib/components/ui/tabs/tabs.svelte | 19 + .../src/lib/components/ui/textarea/index.ts | 7 + .../components/ui/textarea/textarea.svelte | 23 + .../lib/components/ui/toggle-group/index.ts | 10 + .../ui/toggle-group/toggle-group-item.svelte | 35 ++ .../ui/toggle-group/toggle-group.svelte | 75 +++ .../web/src/lib/components/ui/toggle/index.ts | 13 + .../lib/components/ui/toggle/toggle.svelte | 51 ++ .../src/lib/components/ui/tooltip/index.ts | 19 + .../ui/tooltip/tooltip-content.svelte | 52 ++ .../ui/tooltip/tooltip-portal.svelte | 7 + .../ui/tooltip/tooltip-provider.svelte | 7 + .../ui/tooltip/tooltip-trigger.svelte | 7 + .../lib/components/ui/tooltip/tooltip.svelte | 10 + template/apps/web/src/lib/getconnectrouter.ts | 8 + .../web/src/lib/hooks/is-mobile.svelte.ts | 9 + template/apps/web/src/lib/index.ts | 1 + .../web/src/lib/todocollectionscontext.ts | 5 + template/apps/web/src/lib/utils.ts | 14 + template/apps/web/src/routes/+layout.svelte | 63 +++ template/apps/web/src/routes/+page.svelte | 11 + template/apps/web/src/routes/+page.ts | 7 + template/apps/web/src/routes/layout.css | 130 +++++ template/apps/web/static/robots.txt | 3 + template/apps/web/svelte.config.js | 24 + template/apps/web/tsconfig.json | 20 + template/apps/web/vite.config.ts | 12 + template/devenv.nix | 52 ++ template/devenv.yaml | 15 + template/package.json | 12 + template/packages/proto/buf.gen.yaml | 15 + template/packages/proto/buf.yaml | 9 + template/packages/proto/todo/v1/todo.proto | 49 ++ template/packages/rpc/.gitignore | 34 ++ template/packages/rpc/CLAUDE.md | 106 ++++ template/packages/rpc/README.md | 15 + template/packages/rpc/package.json | 22 + template/packages/rpc/tsconfig.json | 29 + template/scripts/.helix/languages.toml | 4 + template/scripts/gen-rpc-index.ts | 324 +++++++++++ template/scripts/package.json | 11 + template/services/api/.air.toml | 58 ++ template/services/api/.gitignore | 1 + template/services/api/LICENSE | 0 template/services/api/cmd/configInit.go | 45 ++ template/services/api/cmd/migrate.go | 37 ++ template/services/api/cmd/root.go | 45 ++ template/services/api/cmd/serve.go | 81 +++ template/services/api/config/config.go | 81 +++ template/services/api/db/db.go | 32 ++ template/services/api/db/dbfs.go | 6 + template/services/api/db/lib.go | 60 ++ .../api/db/migrations/20260404121052_init.sql | 10 + template/services/api/db/models.go | 17 + template/services/api/db/query/todo.sql | 14 + template/services/api/db/schema.sql | 86 +++ template/services/api/db/todo.sql.go | 113 ++++ template/services/api/go.mod | 34 ++ template/services/api/go.sum | 79 +++ template/services/api/main.go | 11 + template/services/api/server/todo/todo.go | 116 ++++ template/services/api/sqlc.yaml | 10 + template/services/api/utils/utils.go | 9 + tsconfig.json | 29 + 480 files changed, 13386 insertions(+) create mode 100644 .devenv.flake.nix create mode 100644 .gitignore create mode 100644 .helix/languages.toml create mode 100644 .ignore create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 bun.lock create mode 100644 devenv.nix create mode 100644 index.ts create mode 100644 package.json create mode 100644 template/.envrc create mode 100644 template/.gitignore create mode 100644 template/.ignore create mode 100644 template/apps/web/.gitignore create mode 100644 template/apps/web/.ignore create mode 100644 template/apps/web/.npmrc create mode 100644 template/apps/web/.prettierignore create mode 100644 template/apps/web/.prettierrc create mode 100644 template/apps/web/.vscode/extensions.json create mode 100644 template/apps/web/.vscode/settings.json create mode 100644 template/apps/web/README.md create mode 100644 template/apps/web/components.json create mode 100644 template/apps/web/messages/de-de.json create mode 100644 template/apps/web/messages/en.json create mode 100644 template/apps/web/package.json create mode 100644 template/apps/web/project.inlang/settings.json create mode 100644 template/apps/web/src/app.html create mode 100644 template/apps/web/src/hooks.server.ts create mode 100644 template/apps/web/src/hooks.ts create mode 100644 template/apps/web/src/lib/assets/favicon.svg create mode 100644 template/apps/web/src/lib/components/todos/CreateTodo.svelte create mode 100644 template/apps/web/src/lib/components/todos/ListTodos.svelte create mode 100644 template/apps/web/src/lib/components/todos/Todo.svelte create mode 100644 template/apps/web/src/lib/components/ui/accordion/accordion-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/accordion/accordion-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/accordion/accordion-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/accordion/accordion.svelte create mode 100644 template/apps/web/src/lib/components/ui/accordion/index.ts create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert-dialog/index.ts create mode 100644 template/apps/web/src/lib/components/ui/alert/alert-action.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert/alert-description.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert/alert-title.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert/alert.svelte create mode 100644 template/apps/web/src/lib/components/ui/alert/index.ts create mode 100644 template/apps/web/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte create mode 100644 template/apps/web/src/lib/components/ui/aspect-ratio/index.ts create mode 100644 template/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte create mode 100644 template/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte create mode 100644 template/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte create mode 100644 template/apps/web/src/lib/components/ui/avatar/avatar-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/avatar/avatar-image.svelte create mode 100644 template/apps/web/src/lib/components/ui/avatar/avatar.svelte create mode 100644 template/apps/web/src/lib/components/ui/avatar/index.ts create mode 100644 template/apps/web/src/lib/components/ui/badge/badge.svelte create mode 100644 template/apps/web/src/lib/components/ui/badge/index.ts create mode 100644 template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte create mode 100644 template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte create mode 100644 template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte create mode 100644 template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte create mode 100644 template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb.svelte create mode 100644 template/apps/web/src/lib/components/ui/breadcrumb/index.ts create mode 100644 template/apps/web/src/lib/components/ui/button-group/button-group-separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/button-group/button-group-text.svelte create mode 100644 template/apps/web/src/lib/components/ui/button-group/button-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/button-group/index.ts create mode 100644 template/apps/web/src/lib/components/ui/button/button.svelte create mode 100644 template/apps/web/src/lib/components/ui/button/index.ts create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-caption.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-cell.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-day.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-grid-body.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-grid-head.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-grid-row.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-grid.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-head-cell.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-heading.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-month-select.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-month.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-months.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-nav.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-next-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-prev-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar-year-select.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/calendar.svelte create mode 100644 template/apps/web/src/lib/components/ui/calendar/index.ts create mode 100644 template/apps/web/src/lib/components/ui/card/card-action.svelte create mode 100644 template/apps/web/src/lib/components/ui/card/card-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/card/card-description.svelte create mode 100644 template/apps/web/src/lib/components/ui/card/card-footer.svelte create mode 100644 template/apps/web/src/lib/components/ui/card/card-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/card/card-title.svelte create mode 100644 template/apps/web/src/lib/components/ui/card/card.svelte create mode 100644 template/apps/web/src/lib/components/ui/card/index.ts create mode 100644 template/apps/web/src/lib/components/ui/carousel/carousel-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/carousel/carousel-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/carousel/carousel-next.svelte create mode 100644 template/apps/web/src/lib/components/ui/carousel/carousel-previous.svelte create mode 100644 template/apps/web/src/lib/components/ui/carousel/carousel.svelte create mode 100644 template/apps/web/src/lib/components/ui/carousel/context.ts create mode 100644 template/apps/web/src/lib/components/ui/carousel/index.ts create mode 100644 template/apps/web/src/lib/components/ui/chart/chart-container.svelte create mode 100644 template/apps/web/src/lib/components/ui/chart/chart-style.svelte create mode 100644 template/apps/web/src/lib/components/ui/chart/chart-tooltip.svelte create mode 100644 template/apps/web/src/lib/components/ui/chart/chart-utils.ts create mode 100644 template/apps/web/src/lib/components/ui/chart/index.ts create mode 100644 template/apps/web/src/lib/components/ui/checkbox/checkbox.svelte create mode 100644 template/apps/web/src/lib/components/ui/checkbox/index.ts create mode 100644 template/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/collapsible/collapsible.svelte create mode 100644 template/apps/web/src/lib/components/ui/collapsible/index.ts create mode 100644 template/apps/web/src/lib/components/ui/command/command-dialog.svelte create mode 100644 template/apps/web/src/lib/components/ui/command/command-empty.svelte create mode 100644 template/apps/web/src/lib/components/ui/command/command-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/command/command-input.svelte create mode 100644 template/apps/web/src/lib/components/ui/command/command-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/command/command-link-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/command/command-list.svelte create mode 100644 template/apps/web/src/lib/components/ui/command/command-loading.svelte create mode 100644 template/apps/web/src/lib/components/ui/command/command-separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/command/command-shortcut.svelte create mode 100644 template/apps/web/src/lib/components/ui/command/command.svelte create mode 100644 template/apps/web/src/lib/components/ui/command/index.ts create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-group-heading.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-label.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-portal.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-radio-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-radio-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-shortcut.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-sub-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-sub.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/context-menu.svelte create mode 100644 template/apps/web/src/lib/components/ui/context-menu/index.ts create mode 100644 template/apps/web/src/lib/components/ui/data-table/data-table.svelte.ts create mode 100644 template/apps/web/src/lib/components/ui/data-table/flex-render.svelte create mode 100644 template/apps/web/src/lib/components/ui/data-table/index.ts create mode 100644 template/apps/web/src/lib/components/ui/data-table/render-helpers.ts create mode 100644 template/apps/web/src/lib/components/ui/dialog/dialog-close.svelte create mode 100644 template/apps/web/src/lib/components/ui/dialog/dialog-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/dialog/dialog-description.svelte create mode 100644 template/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte create mode 100644 template/apps/web/src/lib/components/ui/dialog/dialog-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte create mode 100644 template/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte create mode 100644 template/apps/web/src/lib/components/ui/dialog/dialog-title.svelte create mode 100644 template/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/dialog/dialog.svelte create mode 100644 template/apps/web/src/lib/components/ui/dialog/index.ts create mode 100644 template/apps/web/src/lib/components/ui/drawer/drawer-close.svelte create mode 100644 template/apps/web/src/lib/components/ui/drawer/drawer-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/drawer/drawer-description.svelte create mode 100644 template/apps/web/src/lib/components/ui/drawer/drawer-footer.svelte create mode 100644 template/apps/web/src/lib/components/ui/drawer/drawer-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/drawer/drawer-nested.svelte create mode 100644 template/apps/web/src/lib/components/ui/drawer/drawer-overlay.svelte create mode 100644 template/apps/web/src/lib/components/ui/drawer/drawer-portal.svelte create mode 100644 template/apps/web/src/lib/components/ui/drawer/drawer-title.svelte create mode 100644 template/apps/web/src/lib/components/ui/drawer/drawer-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/drawer/drawer.svelte create mode 100644 template/apps/web/src/lib/components/ui/drawer/index.ts create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte create mode 100644 template/apps/web/src/lib/components/ui/dropdown-menu/index.ts create mode 100644 template/apps/web/src/lib/components/ui/empty/empty-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/empty/empty-description.svelte create mode 100644 template/apps/web/src/lib/components/ui/empty/empty-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/empty/empty-media.svelte create mode 100644 template/apps/web/src/lib/components/ui/empty/empty-title.svelte create mode 100644 template/apps/web/src/lib/components/ui/empty/empty.svelte create mode 100644 template/apps/web/src/lib/components/ui/empty/index.ts create mode 100644 template/apps/web/src/lib/components/ui/field/field-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/field/field-description.svelte create mode 100644 template/apps/web/src/lib/components/ui/field/field-error.svelte create mode 100644 template/apps/web/src/lib/components/ui/field/field-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/field/field-label.svelte create mode 100644 template/apps/web/src/lib/components/ui/field/field-legend.svelte create mode 100644 template/apps/web/src/lib/components/ui/field/field-separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/field/field-set.svelte create mode 100644 template/apps/web/src/lib/components/ui/field/field-title.svelte create mode 100644 template/apps/web/src/lib/components/ui/field/field.svelte create mode 100644 template/apps/web/src/lib/components/ui/field/index.ts create mode 100644 template/apps/web/src/lib/components/ui/form/form-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/form/form-description.svelte create mode 100644 template/apps/web/src/lib/components/ui/form/form-element-field.svelte create mode 100644 template/apps/web/src/lib/components/ui/form/form-field-errors.svelte create mode 100644 template/apps/web/src/lib/components/ui/form/form-field.svelte create mode 100644 template/apps/web/src/lib/components/ui/form/form-fieldset.svelte create mode 100644 template/apps/web/src/lib/components/ui/form/form-label.svelte create mode 100644 template/apps/web/src/lib/components/ui/form/form-legend.svelte create mode 100644 template/apps/web/src/lib/components/ui/form/index.ts create mode 100644 template/apps/web/src/lib/components/ui/hover-card/hover-card-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/hover-card/hover-card-portal.svelte create mode 100644 template/apps/web/src/lib/components/ui/hover-card/hover-card-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/hover-card/hover-card.svelte create mode 100644 template/apps/web/src/lib/components/ui/hover-card/index.ts create mode 100644 template/apps/web/src/lib/components/ui/input-group/index.ts create mode 100644 template/apps/web/src/lib/components/ui/input-group/input-group-addon.svelte create mode 100644 template/apps/web/src/lib/components/ui/input-group/input-group-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/input-group/input-group-input.svelte create mode 100644 template/apps/web/src/lib/components/ui/input-group/input-group-text.svelte create mode 100644 template/apps/web/src/lib/components/ui/input-group/input-group-textarea.svelte create mode 100644 template/apps/web/src/lib/components/ui/input-group/input-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/input-otp/index.ts create mode 100644 template/apps/web/src/lib/components/ui/input-otp/input-otp-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/input-otp/input-otp-separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/input-otp/input-otp-slot.svelte create mode 100644 template/apps/web/src/lib/components/ui/input-otp/input-otp.svelte create mode 100644 template/apps/web/src/lib/components/ui/input/index.ts create mode 100644 template/apps/web/src/lib/components/ui/input/input.svelte create mode 100644 template/apps/web/src/lib/components/ui/item/index.ts create mode 100644 template/apps/web/src/lib/components/ui/item/item-actions.svelte create mode 100644 template/apps/web/src/lib/components/ui/item/item-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/item/item-description.svelte create mode 100644 template/apps/web/src/lib/components/ui/item/item-footer.svelte create mode 100644 template/apps/web/src/lib/components/ui/item/item-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/item/item-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/item/item-media.svelte create mode 100644 template/apps/web/src/lib/components/ui/item/item-separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/item/item-title.svelte create mode 100644 template/apps/web/src/lib/components/ui/item/item.svelte create mode 100644 template/apps/web/src/lib/components/ui/kbd/index.ts create mode 100644 template/apps/web/src/lib/components/ui/kbd/kbd-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/kbd/kbd.svelte create mode 100644 template/apps/web/src/lib/components/ui/label/index.ts create mode 100644 template/apps/web/src/lib/components/ui/label/label.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/index.ts create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-checkbox-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-group-heading.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-label.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-menu.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-portal.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-radio-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-radio-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-shortcut.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-sub-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-sub-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-sub.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/menubar/menubar.svelte create mode 100644 template/apps/web/src/lib/components/ui/native-select/index.ts create mode 100644 template/apps/web/src/lib/components/ui/native-select/native-select-opt-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/native-select/native-select-option.svelte create mode 100644 template/apps/web/src/lib/components/ui/native-select/native-select.svelte create mode 100644 template/apps/web/src/lib/components/ui/navigation-menu/index.ts create mode 100644 template/apps/web/src/lib/components/ui/navigation-menu/navigation-menu-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/navigation-menu/navigation-menu-indicator.svelte create mode 100644 template/apps/web/src/lib/components/ui/navigation-menu/navigation-menu-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/navigation-menu/navigation-menu-link.svelte create mode 100644 template/apps/web/src/lib/components/ui/navigation-menu/navigation-menu-list.svelte create mode 100644 template/apps/web/src/lib/components/ui/navigation-menu/navigation-menu-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/navigation-menu/navigation-menu-viewport.svelte create mode 100644 template/apps/web/src/lib/components/ui/navigation-menu/navigation-menu.svelte create mode 100644 template/apps/web/src/lib/components/ui/pagination/index.ts create mode 100644 template/apps/web/src/lib/components/ui/pagination/pagination-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/pagination/pagination-ellipsis.svelte create mode 100644 template/apps/web/src/lib/components/ui/pagination/pagination-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/pagination/pagination-link.svelte create mode 100644 template/apps/web/src/lib/components/ui/pagination/pagination-next-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/pagination/pagination-next.svelte create mode 100644 template/apps/web/src/lib/components/ui/pagination/pagination-prev-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/pagination/pagination-previous.svelte create mode 100644 template/apps/web/src/lib/components/ui/pagination/pagination.svelte create mode 100644 template/apps/web/src/lib/components/ui/popover/index.ts create mode 100644 template/apps/web/src/lib/components/ui/popover/popover-close.svelte create mode 100644 template/apps/web/src/lib/components/ui/popover/popover-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/popover/popover-description.svelte create mode 100644 template/apps/web/src/lib/components/ui/popover/popover-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/popover/popover-portal.svelte create mode 100644 template/apps/web/src/lib/components/ui/popover/popover-title.svelte create mode 100644 template/apps/web/src/lib/components/ui/popover/popover-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/popover/popover.svelte create mode 100644 template/apps/web/src/lib/components/ui/progress/index.ts create mode 100644 template/apps/web/src/lib/components/ui/progress/progress.svelte create mode 100644 template/apps/web/src/lib/components/ui/radio-group/index.ts create mode 100644 template/apps/web/src/lib/components/ui/radio-group/radio-group-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/radio-group/radio-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/index.ts create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-caption.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-cell.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-day.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-grid-body.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-grid-head.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-grid.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-heading.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-month-select.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-month.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-months.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-nav.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar-year-select.svelte create mode 100644 template/apps/web/src/lib/components/ui/range-calendar/range-calendar.svelte create mode 100644 template/apps/web/src/lib/components/ui/resizable/index.ts create mode 100644 template/apps/web/src/lib/components/ui/resizable/resizable-handle.svelte create mode 100644 template/apps/web/src/lib/components/ui/resizable/resizable-pane-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/scroll-area/index.ts create mode 100644 template/apps/web/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte create mode 100644 template/apps/web/src/lib/components/ui/scroll-area/scroll-area.svelte create mode 100644 template/apps/web/src/lib/components/ui/select/index.ts create mode 100644 template/apps/web/src/lib/components/ui/select/select-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/select/select-group-heading.svelte create mode 100644 template/apps/web/src/lib/components/ui/select/select-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/select/select-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/select/select-label.svelte create mode 100644 template/apps/web/src/lib/components/ui/select/select-portal.svelte create mode 100644 template/apps/web/src/lib/components/ui/select/select-scroll-down-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/select/select-scroll-up-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/select/select-separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/select/select-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/select/select.svelte create mode 100644 template/apps/web/src/lib/components/ui/separator/index.ts create mode 100644 template/apps/web/src/lib/components/ui/separator/separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/sheet/index.ts create mode 100644 template/apps/web/src/lib/components/ui/sheet/sheet-close.svelte create mode 100644 template/apps/web/src/lib/components/ui/sheet/sheet-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/sheet/sheet-description.svelte create mode 100644 template/apps/web/src/lib/components/ui/sheet/sheet-footer.svelte create mode 100644 template/apps/web/src/lib/components/ui/sheet/sheet-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/sheet/sheet-overlay.svelte create mode 100644 template/apps/web/src/lib/components/ui/sheet/sheet-portal.svelte create mode 100644 template/apps/web/src/lib/components/ui/sheet/sheet-title.svelte create mode 100644 template/apps/web/src/lib/components/ui/sheet/sheet-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/sheet/sheet.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/constants.ts create mode 100644 template/apps/web/src/lib/components/ui/sidebar/context.svelte.ts create mode 100644 template/apps/web/src/lib/components/ui/sidebar/index.ts create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-footer.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-input.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-inset.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-menu.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-provider.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-rail.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-separator.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/sidebar/sidebar.svelte create mode 100644 template/apps/web/src/lib/components/ui/skeleton/index.ts create mode 100644 template/apps/web/src/lib/components/ui/skeleton/skeleton.svelte create mode 100644 template/apps/web/src/lib/components/ui/slider/index.ts create mode 100644 template/apps/web/src/lib/components/ui/slider/slider.svelte create mode 100644 template/apps/web/src/lib/components/ui/sonner/index.ts create mode 100644 template/apps/web/src/lib/components/ui/sonner/sonner.svelte create mode 100644 template/apps/web/src/lib/components/ui/spinner/index.ts create mode 100644 template/apps/web/src/lib/components/ui/spinner/spinner.svelte create mode 100644 template/apps/web/src/lib/components/ui/switch/index.ts create mode 100644 template/apps/web/src/lib/components/ui/switch/switch.svelte create mode 100644 template/apps/web/src/lib/components/ui/table/index.ts create mode 100644 template/apps/web/src/lib/components/ui/table/table-body.svelte create mode 100644 template/apps/web/src/lib/components/ui/table/table-caption.svelte create mode 100644 template/apps/web/src/lib/components/ui/table/table-cell.svelte create mode 100644 template/apps/web/src/lib/components/ui/table/table-footer.svelte create mode 100644 template/apps/web/src/lib/components/ui/table/table-head.svelte create mode 100644 template/apps/web/src/lib/components/ui/table/table-header.svelte create mode 100644 template/apps/web/src/lib/components/ui/table/table-row.svelte create mode 100644 template/apps/web/src/lib/components/ui/table/table.svelte create mode 100644 template/apps/web/src/lib/components/ui/tabs/index.ts create mode 100644 template/apps/web/src/lib/components/ui/tabs/tabs-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/tabs/tabs-list.svelte create mode 100644 template/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/tabs/tabs.svelte create mode 100644 template/apps/web/src/lib/components/ui/textarea/index.ts create mode 100644 template/apps/web/src/lib/components/ui/textarea/textarea.svelte create mode 100644 template/apps/web/src/lib/components/ui/toggle-group/index.ts create mode 100644 template/apps/web/src/lib/components/ui/toggle-group/toggle-group-item.svelte create mode 100644 template/apps/web/src/lib/components/ui/toggle-group/toggle-group.svelte create mode 100644 template/apps/web/src/lib/components/ui/toggle/index.ts create mode 100644 template/apps/web/src/lib/components/ui/toggle/toggle.svelte create mode 100644 template/apps/web/src/lib/components/ui/tooltip/index.ts create mode 100644 template/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte create mode 100644 template/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte create mode 100644 template/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte create mode 100644 template/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte create mode 100644 template/apps/web/src/lib/components/ui/tooltip/tooltip.svelte create mode 100644 template/apps/web/src/lib/getconnectrouter.ts create mode 100644 template/apps/web/src/lib/hooks/is-mobile.svelte.ts create mode 100644 template/apps/web/src/lib/index.ts create mode 100644 template/apps/web/src/lib/todocollectionscontext.ts create mode 100644 template/apps/web/src/lib/utils.ts create mode 100644 template/apps/web/src/routes/+layout.svelte create mode 100644 template/apps/web/src/routes/+page.svelte create mode 100644 template/apps/web/src/routes/+page.ts create mode 100644 template/apps/web/src/routes/layout.css create mode 100644 template/apps/web/static/robots.txt create mode 100644 template/apps/web/svelte.config.js create mode 100644 template/apps/web/tsconfig.json create mode 100644 template/apps/web/vite.config.ts create mode 100644 template/devenv.nix create mode 100644 template/devenv.yaml create mode 100644 template/package.json create mode 100644 template/packages/proto/buf.gen.yaml create mode 100644 template/packages/proto/buf.yaml create mode 100644 template/packages/proto/todo/v1/todo.proto create mode 100644 template/packages/rpc/.gitignore create mode 100644 template/packages/rpc/CLAUDE.md create mode 100644 template/packages/rpc/README.md create mode 100644 template/packages/rpc/package.json create mode 100644 template/packages/rpc/tsconfig.json create mode 100644 template/scripts/.helix/languages.toml create mode 100644 template/scripts/gen-rpc-index.ts create mode 100644 template/scripts/package.json create mode 100644 template/services/api/.air.toml create mode 100644 template/services/api/.gitignore create mode 100644 template/services/api/LICENSE create mode 100644 template/services/api/cmd/configInit.go create mode 100644 template/services/api/cmd/migrate.go create mode 100644 template/services/api/cmd/root.go create mode 100644 template/services/api/cmd/serve.go create mode 100644 template/services/api/config/config.go create mode 100644 template/services/api/db/db.go create mode 100644 template/services/api/db/dbfs.go create mode 100644 template/services/api/db/lib.go create mode 100644 template/services/api/db/migrations/20260404121052_init.sql create mode 100644 template/services/api/db/models.go create mode 100644 template/services/api/db/query/todo.sql create mode 100644 template/services/api/db/schema.sql create mode 100644 template/services/api/db/todo.sql.go create mode 100644 template/services/api/go.mod create mode 100644 template/services/api/go.sum create mode 100644 template/services/api/main.go create mode 100644 template/services/api/server/todo/todo.go create mode 100644 template/services/api/sqlc.yaml create mode 100644 template/services/api/utils/utils.go create mode 100644 tsconfig.json diff --git a/.devenv.flake.nix b/.devenv.flake.nix new file mode 100644 index 0000000..38ae300 --- /dev/null +++ b/.devenv.flake.nix @@ -0,0 +1,513 @@ +{ + inputs = + let + vars = { + version = "1.11.2"; + system = "x86_64-linux"; + devenv_root = "/home/anon/Dev/glstack"; + project_input_ref = "path:/home/anon/Dev/glstack"; + devenv_dotfile = "/home/anon/Dev/glstack/.devenv"; + devenv_dotfile_path = ./.devenv; + devenv_tmpdir = "/run/user/1000"; + devenv_runtime = "/run/user/1000/devenv-5149e7a"; + devenv_istesting = false; + devenv_direnvrc_latest_version = 1; + container_name = null; + active_profiles = [ + ]; + hostname = "debian"; + username = "anon"; + git_root = null; + secretspec = null; +}; + in + { + git-hooks.url = "github:cachix/git-hooks.nix"; + git-hooks.inputs.nixpkgs.follows = "nixpkgs"; + pre-commit-hooks.follows = "git-hooks"; + nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling"; + devenv.url = "github:cachix/devenv?dir=src/modules"; + } + // ( + if builtins.pathExists (vars.devenv_dotfile_path + "/flake.json") then + builtins.fromJSON (builtins.readFile (vars.devenv_dotfile_path + "/flake.json")) + else + { } + ); + + outputs = + { nixpkgs, ... }@inputs: + let + vars = { + version = "1.11.2"; + system = "x86_64-linux"; + devenv_root = "/home/anon/Dev/glstack"; + project_input_ref = "path:/home/anon/Dev/glstack"; + devenv_dotfile = "/home/anon/Dev/glstack/.devenv"; + devenv_dotfile_path = ./.devenv; + devenv_tmpdir = "/run/user/1000"; + devenv_runtime = "/run/user/1000/devenv-5149e7a"; + devenv_istesting = false; + devenv_direnvrc_latest_version = 1; + container_name = null; + active_profiles = [ + ]; + hostname = "debian"; + username = "anon"; + git_root = null; + secretspec = null; +}; + devenv = + if builtins.pathExists (vars.devenv_dotfile_path + "/devenv.json") then + builtins.fromJSON (builtins.readFile (vars.devenv_dotfile_path + "/devenv.json")) + else + { }; + + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + # Function to create devenv configuration for a specific system with profiles support + mkDevenvForSystem = + targetSystem: + let + getOverlays = + inputName: inputAttrs: + map ( + overlay: + let + input = + inputs.${inputName} or (throw "No such input `${inputName}` while trying to configure overlays."); + in + input.overlays.${overlay} + or (throw "Input `${inputName}` has no overlay called `${overlay}`. Supported overlays: ${nixpkgs.lib.concatStringsSep ", " (builtins.attrNames input.overlays)}") + ) inputAttrs.overlays or [ ]; + overlays = nixpkgs.lib.flatten (nixpkgs.lib.mapAttrsToList getOverlays (devenv.inputs or { })); + permittedUnfreePackages = + devenv.nixpkgs.per-platform."${targetSystem}".permittedUnfreePackages + or devenv.nixpkgs.permittedUnfreePackages or [ ]; + pkgs = import nixpkgs { + system = targetSystem; + config = { + allowUnfree = + devenv.nixpkgs.per-platform."${targetSystem}".allowUnfree or devenv.nixpkgs.allowUnfree + or devenv.allowUnfree or false; + allowBroken = + devenv.nixpkgs.per-platform."${targetSystem}".allowBroken or devenv.nixpkgs.allowBroken + or devenv.allowBroken or false; + cudaSupport = + devenv.nixpkgs.per-platform."${targetSystem}".cudaSupport or devenv.nixpkgs.cudaSupport or false; + cudaCapabilities = + devenv.nixpkgs.per-platform."${targetSystem}".cudaCapabilities or devenv.nixpkgs.cudaCapabilities + or [ ]; + permittedInsecurePackages = + devenv.nixpkgs.per-platform."${targetSystem}".permittedInsecurePackages + or devenv.nixpkgs.permittedInsecurePackages or devenv.permittedInsecurePackages or [ ]; + allowUnfreePredicate = + if (permittedUnfreePackages != [ ]) then + (pkg: builtins.elem (nixpkgs.lib.getName pkg) permittedUnfreePackages) + else + (_: false); + }; + inherit overlays; + }; + inherit (pkgs) lib; + importModule = + path: + if lib.hasPrefix "./" path then + if lib.hasSuffix ".nix" path then + ./. + (builtins.substring 1 255 path) + else + ./. + (builtins.substring 1 255 path) + "/devenv.nix" + else if lib.hasPrefix "../" path then + # For parent directory paths, concatenate with /. + # ./. refers to the directory containing this file (project root) + # So ./. + "/../shared" = /../shared + if lib.hasSuffix ".nix" path then ./. + "/${path}" else ./. + "/${path}/devenv.nix" + else + let + paths = lib.splitString "/" path; + name = builtins.head paths; + input = inputs.${name} or (throw "Unknown input ${name}"); + subpath = "/${lib.concatStringsSep "/" (builtins.tail paths)}"; + devenvpath = "${input}" + subpath; + devenvdefaultpath = devenvpath + "/devenv.nix"; + in + if lib.hasSuffix ".nix" devenvpath then + devenvpath + else if builtins.pathExists devenvdefaultpath then + devenvdefaultpath + else + throw (devenvdefaultpath + " file does not exist for input ${name}."); + + # Phase 1: Base evaluation to extract profile definitions + baseProject = pkgs.lib.evalModules { + specialArgs = inputs // { + inherit inputs; + }; + modules = [ + ( + { config, ... }: + { + _module.args.pkgs = pkgs.appendOverlays (config.overlays or [ ]); + } + ) + (inputs.devenv.modules + /top-level.nix) + ( + { options, ... }: + { + config.devenv = lib.mkMerge [ + { + cliVersion = vars.version; + root = vars.devenv_root; + dotfile = vars.devenv_dotfile; + } + (pkgs.lib.optionalAttrs (builtins.hasAttr "tmpdir" options.devenv) { + tmpdir = vars.devenv_tmpdir; + }) + (pkgs.lib.optionalAttrs (builtins.hasAttr "isTesting" options.devenv) { + isTesting = vars.devenv_istesting; + }) + (pkgs.lib.optionalAttrs (builtins.hasAttr "runtime" options.devenv) { + runtime = vars.devenv_runtime; + }) + (pkgs.lib.optionalAttrs (builtins.hasAttr "direnvrcLatestVersion" options.devenv) { + direnvrcLatestVersion = vars.devenv_direnvrc_latest_version; + }) + ]; + } + ) + ( + { options, ... }: + { + config = lib.mkMerge [ + (pkgs.lib.optionalAttrs (builtins.hasAttr "git" options) { + git.root = vars.git_root; + }) + ]; + } + ) + (pkgs.lib.optionalAttrs (vars.container_name != null) { + container.isBuilding = pkgs.lib.mkForce true; + containers.${vars.container_name}.isBuilding = true; + }) + ] + ++ (map importModule (devenv.imports or [ ])) + ++ [ + (if builtins.pathExists ./devenv.nix then ./devenv.nix else { }) + (devenv.devenv or { }) + (if builtins.pathExists ./devenv.local.nix then ./devenv.local.nix else { }) + ( + if builtins.pathExists (vars.devenv_dotfile_path + "/cli-options.nix") then + import (vars.devenv_dotfile_path + "/cli-options.nix") + else + { } + ) + ]; + }; + + # Phase 2: Extract and apply profiles using extendModules with priority overrides + project = + let + # Build ordered list of profile names: hostname -> user -> manual + manualProfiles = vars.active_profiles; + currentHostname = vars.hostname; + currentUsername = vars.username; + hostnameProfiles = lib.optional ( + currentHostname != "" + && builtins.hasAttr currentHostname (baseProject.config.profiles.hostname or { }) + ) "hostname.${currentHostname}"; + userProfiles = lib.optional ( + currentUsername != "" && builtins.hasAttr currentUsername (baseProject.config.profiles.user or { }) + ) "user.${currentUsername}"; + + # Ordered list of profiles to activate + orderedProfiles = hostnameProfiles ++ userProfiles ++ manualProfiles; + + # Resolve profile extends with cycle detection + resolveProfileExtends = + profileName: visited: + if builtins.elem profileName visited then + throw "Circular dependency detected in profile extends: ${lib.concatStringsSep " -> " visited} -> ${profileName}" + else + let + profile = getProfileConfig profileName; + extends = profile.extends or [ ]; + newVisited = visited ++ [ profileName ]; + extendedProfiles = lib.flatten (map (name: resolveProfileExtends name newVisited) extends); + in + extendedProfiles ++ [ profileName ]; + + # Get profile configuration by name from baseProject + getProfileConfig = + profileName: + if lib.hasPrefix "hostname." profileName then + let + name = lib.removePrefix "hostname." profileName; + in + baseProject.config.profiles.hostname.${name} + else if lib.hasPrefix "user." profileName then + let + name = lib.removePrefix "user." profileName; + in + baseProject.config.profiles.user.${name} + else + let + availableProfiles = builtins.attrNames (baseProject.config.profiles or { }); + hostnameProfiles = map (n: "hostname.${n}") ( + builtins.attrNames (baseProject.config.profiles.hostname or { }) + ); + userProfiles = map (n: "user.${n}") (builtins.attrNames (baseProject.config.profiles.user or { })); + allAvailableProfiles = availableProfiles ++ hostnameProfiles ++ userProfiles; + in + baseProject.config.profiles.${profileName} + or (throw "Profile '${profileName}' not found. Available profiles: ${lib.concatStringsSep ", " allAvailableProfiles}"); + + # Fold over ordered profiles to build final list with extends + expandedProfiles = lib.foldl' ( + acc: profileName: + let + allProfileNames = resolveProfileExtends profileName [ ]; + in + acc ++ allProfileNames + ) [ ] orderedProfiles; + + # Map over expanded profiles and apply priorities + allPrioritizedModules = lib.imap0 ( + index: profileName: + let + # Decrement priority for each profile (lower = higher precedence) + # Start with the next lowest priority after the default priority for values (100) + profilePriority = (lib.modules.defaultOverridePriority - 1) - index; + profileConfig = getProfileConfig profileName; + + # Check if an option type needs explicit override to resolve conflicts + # Only apply overrides to LEAF values (scalars), not collection types that can merge + typeNeedsOverride = + type: + if type == null then + false + else + let + typeName = type.name or type._type or ""; + + # True leaf types that need priority resolution when they conflict + isLeafType = builtins.elem typeName [ + "str" + "int" + "bool" + "enum" + "path" + "package" + "float" + "anything" + ]; + in + if isLeafType then + true + else if typeName == "nullOr" then + # For nullOr, check the wrapped type recursively + let + innerType = + type.elemType + or (if type ? nestedTypes && type.nestedTypes ? elemType then type.nestedTypes.elemType else null); + in + if innerType != null then typeNeedsOverride innerType else false + else + # Everything else (collections, submodules, etc.) should merge naturally + false; + + # Check if a config path needs explicit override + pathNeedsOverride = + optionPath: + let + # Try direct option first + directOption = lib.attrByPath optionPath null baseProject.options; + in + if directOption != null && lib.isOption directOption then + typeNeedsOverride directOption.type + else if optionPath != [ ] then + # Check parent for freeform type + let + parentPath = lib.init optionPath; + parentOption = lib.attrByPath parentPath null baseProject.options; + in + if parentOption != null && lib.isOption parentOption then + let + # Look for freeform type: + # 1. Standard location: type.freeformType (primary) + # 2. Nested location: type.nestedTypes.freeformType (evaluated form) + freeformType = parentOption.type.freeformType or parentOption.type.nestedTypes.freeformType or null; + elementType = + if freeformType ? elemType then + freeformType.elemType + else if freeformType ? nestedTypes && freeformType.nestedTypes ? elemType then + freeformType.nestedTypes.elemType + else + freeformType; + in + typeNeedsOverride elementType + else + false + else + false; + + # Support overriding both plain attrset modules and functions + applyModuleOverride = + config: + if builtins.isFunction config then + let + wrapper = args: applyOverrideRecursive (config args) [ ]; + in + lib.mirrorFunctionArgs config wrapper + else + applyOverrideRecursive config [ ]; + + # Apply overrides recursively based on option types + applyOverrideRecursive = + config: optionPath: + if lib.isAttrs config && config ? _type then + config # Don't touch values with existing type metadata + else if lib.isAttrs config then + lib.mapAttrs (name: value: applyOverrideRecursive value (optionPath ++ [ name ])) config + else if pathNeedsOverride optionPath then + lib.mkOverride profilePriority config + else + config; + + # Apply priority overrides recursively to the deferredModule imports structure + prioritizedConfig = ( + profileConfig.module + // { + imports = lib.map ( + importItem: + importItem + // { + imports = lib.map (nestedImport: applyModuleOverride nestedImport) (importItem.imports or [ ]); + } + ) (profileConfig.module.imports or [ ]); + } + ); + in + prioritizedConfig + ) expandedProfiles; + in + if allPrioritizedModules == [ ] then + baseProject + else + baseProject.extendModules { modules = allPrioritizedModules; }; + + config = project.config; + + options = pkgs.nixosOptionsDoc { + options = builtins.removeAttrs project.options [ "_module" ]; + warningsAreErrors = false; + # Unpack Nix types, e.g. literalExpression, mDoc. + transformOptions = + let + isDocType = + v: + builtins.elem v [ + "literalDocBook" + "literalExpression" + "literalMD" + "mdDoc" + ]; + in + lib.attrsets.mapAttrs ( + _: v: + if v ? _type && isDocType v._type then + v.text + else if v ? _type && v._type == "derivation" then + v.name + else + v + ); + }; + + # Recursively search for outputs in the config. + # This is used when not building a specific output by attrpath. + build = + options: config: + lib.concatMapAttrs ( + name: option: + if lib.isOption option then + let + typeName = option.type.name or ""; + in + if + builtins.elem typeName [ + "output" + "outputOf" + ] + then + { ${name} = config.${name}; } + else + { } + else if builtins.isAttrs option && !lib.isDerivation option then + let + v = build option config.${name}; + in + if v != { } then + { + ${name} = v; + } + else + { } + else + { } + ) options; + in + { + inherit + config + options + build + project + ; + shell = config.shell; + packages = { + optionsJSON = options.optionsJSON; + # deprecated + inherit (config) + info + procfileScript + procfileEnv + procfile + ; + ci = config.ciDerivation; + }; + }; + + # Generate per-system devenv configurations + perSystem = nixpkgs.lib.genAttrs systems mkDevenvForSystem; + + # Default devenv for the current system + currentSystemDevenv = perSystem.${vars.system}; + in + { + devShell = nixpkgs.lib.genAttrs systems (s: perSystem.${s}.shell); + packages = nixpkgs.lib.genAttrs systems (s: perSystem.${s}.packages); + + # Per-system devenv configurations + devenv = { + # Default devenv for the current system + inherit (currentSystemDevenv) + config + options + build + shell + packages + project + ; + # Per-system devenv configurations + inherit perSystem; + }; + + # Legacy build output + build = currentSystemDevenv.build currentSystemDevenv.options currentSystemDevenv.config; + }; +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64650e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store +.devenv +devenv.lock diff --git a/.helix/languages.toml b/.helix/languages.toml new file mode 100644 index 0000000..1813968 --- /dev/null +++ b/.helix/languages.toml @@ -0,0 +1,4 @@ +[[language]] +name="typescript" +roots = ["package.json"] +language-servers = ["typescript-language-server"] diff --git a/.ignore b/.ignore new file mode 100644 index 0000000..2c78c9a --- /dev/null +++ b/.ignore @@ -0,0 +1,2 @@ +node_modules +.devenv diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..764c1dd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,106 @@ + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; +import { createRoot } from "react-dom/client"; + +// import .css files directly and it works +import './index.css'; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f2f951 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# glstack + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.10. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..03321de --- /dev/null +++ b/bun.lock @@ -0,0 +1,47 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "glstack", + "dependencies": { + "@clack/prompts": "^1.2.0", + "@gregorlohaus/tdir": "^0.1.1", + "zod": "^4.3.6", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@clack/core": ["@clack/core@1.2.0", "", { "dependencies": { "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg=="], + + "@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="], + + "@gregorlohaus/tdir": ["@gregorlohaus/tdir@0.1.1", "", { "peerDependencies": { "zod": "^4" } }, "sha512-4NlHif5Pn6Vh1TzCj8B1d+pz8ab5/CodC2Cq9HVr1wHdFlgXM/yjtZEDLdBMtwqz1n1oCCEjzV7XYbin2ywsjQ=="], + + "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], + + "@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="], + + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + + "fast-string-truncated-width": ["fast-string-truncated-width@1.2.1", "", {}, "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow=="], + + "fast-string-width": ["fast-string-width@1.1.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ=="], + + "fast-wrap-ansi": ["fast-wrap-ansi@0.1.6", "", { "dependencies": { "fast-string-width": "^1.1.0" } }, "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + } +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..ed38107 --- /dev/null +++ b/devenv.nix @@ -0,0 +1,7 @@ +{ pkgs, lib, config, inputs, ... }: + +{ + packages = [ pkgs.bun pkgs.nodejs_24]; + languages.typescript.enable = true; + +} diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..f562a76 --- /dev/null +++ b/index.ts @@ -0,0 +1,56 @@ +#!/usr/bin/env bun + +import * as p from "@clack/prompts"; +import { initRenderer } from '@gregorlohaus/tdir' +import { z } from 'zod' +import join from "node:path"; + +p.intro("create-glstack"); + +const project = await p.group( + { + name: () => + p.text({ + message: "What would you like to name your project?", + placeholder: "glstack-test", + defaultValue: "glstack-test", + validate: (value) => { + return undefined + }, + }), + goprefix: () => + p.text({ + message: "What would you like to use as a go package prefix?", + placeholder: "github.com/glstack-test", + defaultValue: "github.com/glstack-test", + validate: (value) => { + return undefined + }, + }), + }, + { + onCancel: () => { + p.cancel("Operation cancelled."); + process.exit(0); + }, + } +); +const createRenderer = initRenderer('./template') +const render = createRenderer(z.object({ + project: z.object({ + name: z.string(), + goprefix: z.string() + }) +})) +const destDir = join.join("./",project.name); +render(destDir,{project}) +// TODO: template rendering — copy/generate files into destDir + +const s = p.spinner(); +s.start("Installing dependencies"); +await Bun.$`bun install`.cwd(join.join(destDir)).quiet(); +await Bun.$`bun install`.cwd(join.join(destDir,'packages','rpc')).quiet(); +await Bun.$`bun install`.cwd(join.join(destDir,'apps','web')).quiet(); +s.stop("Dependencies installed."); + +p.outro("You're all set!"); diff --git a/package.json b/package.json new file mode 100644 index 0000000..280a5ff --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "create-glstack", + "version": "0.0.1", + "module": "index.ts", + "type": "module", + "bin": { + "create-glstack": "./index.ts" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "@clack/prompts": "^1.2.0", + "@gregorlohaus/tdir": "^0.1.1", + "zod": "^4.3.6" + } +} diff --git a/template/.envrc b/template/.envrc new file mode 100644 index 0000000..cc5c18b --- /dev/null +++ b/template/.envrc @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +export DIRENV_WARN_TIMEOUT=20s + +eval "$(devenv direnvrc)" + +# `use devenv` supports the same options as the `devenv shell` command. +# +# To silence all output, use `--quiet`. +# +# Example usage: use devenv --quiet --impure --option services.postgres.enable:bool true +use devenv diff --git a/template/.gitignore b/template/.gitignore new file mode 100644 index 0000000..f58ea61 --- /dev/null +++ b/template/.gitignore @@ -0,0 +1,12 @@ +# Devenv +.devenv* +devenv.local.nix +devenv.local.yaml + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml +node_modules +scripts/node_modules diff --git a/template/.ignore b/template/.ignore new file mode 100644 index 0000000..ae49879 --- /dev/null +++ b/template/.ignore @@ -0,0 +1,10 @@ +# Devenv +.devenv* +devenv.local.nix +devenv.local.yaml + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/template/apps/web/.gitignore b/template/apps/web/.gitignore new file mode 100644 index 0000000..cd8e1d7 --- /dev/null +++ b/template/apps/web/.gitignore @@ -0,0 +1,26 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +# Paraglide +src/lib/paraglide +project.inlang/cache/ diff --git a/template/apps/web/.ignore b/template/apps/web/.ignore new file mode 100644 index 0000000..cd8e1d7 --- /dev/null +++ b/template/apps/web/.ignore @@ -0,0 +1,26 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +# Paraglide +src/lib/paraglide +project.inlang/cache/ diff --git a/template/apps/web/.npmrc b/template/apps/web/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/template/apps/web/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/template/apps/web/.prettierignore b/template/apps/web/.prettierignore new file mode 100644 index 0000000..7d74fe2 --- /dev/null +++ b/template/apps/web/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/template/apps/web/.prettierrc b/template/apps/web/.prettierrc new file mode 100644 index 0000000..819fa57 --- /dev/null +++ b/template/apps/web/.prettierrc @@ -0,0 +1,16 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ], + "tailwindStylesheet": "./src/routes/layout.css" +} diff --git a/template/apps/web/.vscode/extensions.json b/template/apps/web/.vscode/extensions.json new file mode 100644 index 0000000..252fc41 --- /dev/null +++ b/template/apps/web/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode", "esbenp.prettier-vscode", "bradlc.vscode-tailwindcss"] +} diff --git a/template/apps/web/.vscode/settings.json b/template/apps/web/.vscode/settings.json new file mode 100644 index 0000000..bc31e15 --- /dev/null +++ b/template/apps/web/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "*.css": "tailwindcss" + } +} diff --git a/template/apps/web/README.md b/template/apps/web/README.md new file mode 100644 index 0000000..db53934 --- /dev/null +++ b/template/apps/web/README.md @@ -0,0 +1,42 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project +npx sv create my-app +``` + +To recreate this project with the same configuration: + +```sh +# recreate this project +bun x sv@0.14.0 create --template minimal --types ts --add prettier tailwindcss="plugins:typography" paraglide="languageTags:en, de-De+demo:no" --install bun web +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/template/apps/web/components.json b/template/apps/web/components.json new file mode 100644 index 0000000..0eaa3b2 --- /dev/null +++ b/template/apps/web/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/routes/layout.css", + "baseColor": "mist" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": true, + "registry": "https://shadcn-svelte.com/registry", + "style": "lyra", + "iconLibrary": "lucide", + "menuColor": "default", + "menuAccent": "subtle" +} diff --git a/template/apps/web/messages/de-de.json b/template/apps/web/messages/de-de.json new file mode 100644 index 0000000..d713555 --- /dev/null +++ b/template/apps/web/messages/de-de.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "hello_world": "Hello, {name} from de-de!" +} diff --git a/template/apps/web/messages/en.json b/template/apps/web/messages/en.json new file mode 100644 index 0000000..37a9894 --- /dev/null +++ b/template/apps/web/messages/en.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "hello_world": "Hello, {name} from en!" +} diff --git a/template/apps/web/package.json b/template/apps/web/package.json new file mode 100644 index 0000000..9f8ab80 --- /dev/null +++ b/template/apps/web/package.json @@ -0,0 +1,59 @@ +{ + "name": "web", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --check .", + "format": "prettier --write ." + }, + "devDependencies": { + "@fontsource-variable/roboto": "^5.2.10", + "@inlang/paraglide-js": "^2.10.0", + "@internationalized/date": "^3.12.0", + "@lucide/svelte": "^0.577.0", + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.50.2", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.18", + "@tanstack/table-core": "^8.21.3", + "bits-ui": "^2.16.3", + "clsx": "^2.1.1", + "embla-carousel-svelte": "^8.6.0", + "formsnap": "^2.0.1", + "layerchart": "2.0.0-next.48", + "mode-watcher": "^1.1.0", + "paneforge": "^1.0.2", + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.4.1", + "prettier-plugin-tailwindcss": "^0.7.2", + "shadcn-svelte": "^1.2.7", + "svelte": "^5.54.0", + "svelte-check": "^4.4.2", + "svelte-sonner": "^1.1.0", + "sveltekit-superforms": "^2.30.0", + "tailwind-merge": "^3.5.0", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.1.18", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "vaul-svelte": "^1.0.0-next.7", + "vite": "^7.3.1" + }, + "dependencies": { + "@bufbuild/protobuf": "^2.11.0", + "@connectrpc/connect": "^2.1.1", + "@connectrpc/connect-web": "^2.1.1", + "@<@var(context.project.name)>/rpc": "workspace:*", + "@tanstack/query-db-collection": "^1.0.33", + "@tanstack/svelte-db": "^0.1.79", + "@tanstack/svelte-query": "^6.1.13" + } +} diff --git a/template/apps/web/project.inlang/settings.json b/template/apps/web/project.inlang/settings.json new file mode 100644 index 0000000..e9eac7e --- /dev/null +++ b/template/apps/web/project.inlang/settings.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://inlang.com/schema/project-settings", + "modules": [ + "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js", + "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js" + ], + "plugin.inlang.messageFormat": { + "pathPattern": "./messages/{locale}.json" + }, + "baseLocale": "en", + "locales": ["en", "de-de"] +} diff --git a/template/apps/web/src/app.html b/template/apps/web/src/app.html new file mode 100644 index 0000000..639cbb8 --- /dev/null +++ b/template/apps/web/src/app.html @@ -0,0 +1,16 @@ + + + + + + + + + + %sveltekit.head% + + + +
%sveltekit.body%
+ + diff --git a/template/apps/web/src/hooks.server.ts b/template/apps/web/src/hooks.server.ts new file mode 100644 index 0000000..ac5e591 --- /dev/null +++ b/template/apps/web/src/hooks.server.ts @@ -0,0 +1,17 @@ +import type { Handle } from '@sveltejs/kit'; +import { getTextDirection } from '$lib/paraglide/runtime'; +import { paraglideMiddleware } from '$lib/paraglide/server'; + +const handleParaglide: Handle = ({ event, resolve }) => + paraglideMiddleware(event.request, ({ request, locale }) => { + event.request = request; + + return resolve(event, { + transformPageChunk: ({ html }) => + html + .replace('%paraglide.lang%', locale) + .replace('%paraglide.dir%', getTextDirection(locale)) + }); + }); + +export const handle: Handle = handleParaglide; diff --git a/template/apps/web/src/hooks.ts b/template/apps/web/src/hooks.ts new file mode 100644 index 0000000..392875d --- /dev/null +++ b/template/apps/web/src/hooks.ts @@ -0,0 +1,4 @@ +import type { Reroute } from '@sveltejs/kit'; +import { deLocalizeUrl } from '$lib/paraglide/runtime'; + +export const reroute: Reroute = (request) => deLocalizeUrl(request.url).pathname; diff --git a/template/apps/web/src/lib/assets/favicon.svg b/template/apps/web/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/template/apps/web/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/template/apps/web/src/lib/components/todos/CreateTodo.svelte b/template/apps/web/src/lib/components/todos/CreateTodo.svelte new file mode 100644 index 0000000..1edb1ed --- /dev/null +++ b/template/apps/web/src/lib/components/todos/CreateTodo.svelte @@ -0,0 +1,26 @@ + + +
+ + + + Todo + + + + + +
diff --git a/template/apps/web/src/lib/components/todos/ListTodos.svelte b/template/apps/web/src/lib/components/todos/ListTodos.svelte new file mode 100644 index 0000000..a530e8f --- /dev/null +++ b/template/apps/web/src/lib/components/todos/ListTodos.svelte @@ -0,0 +1,50 @@ + + + + {#if browser} + {#each todos as todo (todo.id)} + + {/each} + {:else} + {#each initialTodos as todo} + + {/each} + {/if} + diff --git a/template/apps/web/src/lib/components/todos/Todo.svelte b/template/apps/web/src/lib/components/todos/Todo.svelte new file mode 100644 index 0000000..79e566c --- /dev/null +++ b/template/apps/web/src/lib/components/todos/Todo.svelte @@ -0,0 +1,70 @@ + + +update()}> + + + + + task + + + + + + + + done + + update()}> + + + +
+ + + Created + + + {(new Date(todoState.createdAt||"now")).toLocaleString()} + + + + + Updated + + + {(new Date(todoState.updatesAt||"now")).toLocaleString()} + + + +
+
+
+
+ diff --git a/template/apps/web/src/lib/components/ui/accordion/accordion-content.svelte b/template/apps/web/src/lib/components/ui/accordion/accordion-content.svelte new file mode 100644 index 0000000..24f1f41 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/accordion/accordion-content.svelte @@ -0,0 +1,27 @@ + + + +
+ {@render children?.()} +
+
diff --git a/template/apps/web/src/lib/components/ui/accordion/accordion-item.svelte b/template/apps/web/src/lib/components/ui/accordion/accordion-item.svelte new file mode 100644 index 0000000..9d3919d --- /dev/null +++ b/template/apps/web/src/lib/components/ui/accordion/accordion-item.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/accordion/accordion-trigger.svelte b/template/apps/web/src/lib/components/ui/accordion/accordion-trigger.svelte new file mode 100644 index 0000000..383272d --- /dev/null +++ b/template/apps/web/src/lib/components/ui/accordion/accordion-trigger.svelte @@ -0,0 +1,32 @@ + + + + + {@render children?.()} + + + diff --git a/template/apps/web/src/lib/components/ui/accordion/accordion.svelte b/template/apps/web/src/lib/components/ui/accordion/accordion.svelte new file mode 100644 index 0000000..b19a3c5 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/accordion/accordion.svelte @@ -0,0 +1,19 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/accordion/index.ts b/template/apps/web/src/lib/components/ui/accordion/index.ts new file mode 100644 index 0000000..ac343a1 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/accordion/index.ts @@ -0,0 +1,16 @@ +import Root from "./accordion.svelte"; +import Content from "./accordion-content.svelte"; +import Item from "./accordion-item.svelte"; +import Trigger from "./accordion-trigger.svelte"; + +export { + Root, + Content, + Item, + Trigger, + // + Root as Accordion, + Content as AccordionContent, + Item as AccordionItem, + Trigger as AccordionTrigger, +}; diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte new file mode 100644 index 0000000..7e63004 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte @@ -0,0 +1,27 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte new file mode 100644 index 0000000..a4ce03c --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte @@ -0,0 +1,27 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte new file mode 100644 index 0000000..710727d --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte @@ -0,0 +1,32 @@ + + + + + + diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte new file mode 100644 index 0000000..7b26f8a --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte new file mode 100644 index 0000000..4d133b7 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte new file mode 100644 index 0000000..d6df4d3 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte new file mode 100644 index 0000000..4db6d7e --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte new file mode 100644 index 0000000..9ffcc85 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte new file mode 100644 index 0000000..f0a19a8 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte new file mode 100644 index 0000000..5cb7e7f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte new file mode 100644 index 0000000..b22d1d5 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog.svelte b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog.svelte new file mode 100644 index 0000000..7ea78bb --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/alert-dialog.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/alert-dialog/index.ts b/template/apps/web/src/lib/components/ui/alert-dialog/index.ts new file mode 100644 index 0000000..ca81c2a --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert-dialog/index.ts @@ -0,0 +1,40 @@ +import Root from "./alert-dialog.svelte"; +import Portal from "./alert-dialog-portal.svelte"; +import Trigger from "./alert-dialog-trigger.svelte"; +import Title from "./alert-dialog-title.svelte"; +import Action from "./alert-dialog-action.svelte"; +import Cancel from "./alert-dialog-cancel.svelte"; +import Footer from "./alert-dialog-footer.svelte"; +import Header from "./alert-dialog-header.svelte"; +import Overlay from "./alert-dialog-overlay.svelte"; +import Content from "./alert-dialog-content.svelte"; +import Description from "./alert-dialog-description.svelte"; +import Media from "./alert-dialog-media.svelte"; + +export { + Root, + Title, + Action, + Cancel, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + Media, + // + Root as AlertDialog, + Title as AlertDialogTitle, + Action as AlertDialogAction, + Cancel as AlertDialogCancel, + Portal as AlertDialogPortal, + Footer as AlertDialogFooter, + Header as AlertDialogHeader, + Trigger as AlertDialogTrigger, + Overlay as AlertDialogOverlay, + Content as AlertDialogContent, + Description as AlertDialogDescription, + Media as AlertDialogMedia, +}; diff --git a/template/apps/web/src/lib/components/ui/alert/alert-action.svelte b/template/apps/web/src/lib/components/ui/alert/alert-action.svelte new file mode 100644 index 0000000..0f90609 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert/alert-action.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/template/apps/web/src/lib/components/ui/alert/alert-description.svelte b/template/apps/web/src/lib/components/ui/alert/alert-description.svelte new file mode 100644 index 0000000..e62a009 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert/alert-description.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/template/apps/web/src/lib/components/ui/alert/alert-title.svelte b/template/apps/web/src/lib/components/ui/alert/alert-title.svelte new file mode 100644 index 0000000..3e339a3 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert/alert-title.svelte @@ -0,0 +1,23 @@ + + +
svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3", + className + )} + {...restProps} +> + {@render children?.()} +
diff --git a/template/apps/web/src/lib/components/ui/alert/alert.svelte b/template/apps/web/src/lib/components/ui/alert/alert.svelte new file mode 100644 index 0000000..b2eecfa --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert/alert.svelte @@ -0,0 +1,43 @@ + + + + + diff --git a/template/apps/web/src/lib/components/ui/alert/index.ts b/template/apps/web/src/lib/components/ui/alert/index.ts new file mode 100644 index 0000000..071b113 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/alert/index.ts @@ -0,0 +1,17 @@ +import Root from "./alert.svelte"; +import Description from "./alert-description.svelte"; +import Title from "./alert-title.svelte"; +import Action from "./alert-action.svelte"; +export { alertVariants, type AlertVariant } from "./alert.svelte"; + +export { + Root, + Description, + Title, + Action, + // + Root as Alert, + Description as AlertDescription, + Title as AlertTitle, + Action as AlertAction, +}; diff --git a/template/apps/web/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte b/template/apps/web/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte new file mode 100644 index 0000000..815aab0 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/aspect-ratio/index.ts b/template/apps/web/src/lib/components/ui/aspect-ratio/index.ts new file mode 100644 index 0000000..985c75f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/aspect-ratio/index.ts @@ -0,0 +1,3 @@ +import Root from "./aspect-ratio.svelte"; + +export { Root, Root as AspectRatio }; diff --git a/template/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte b/template/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte new file mode 100644 index 0000000..ca9d9ed --- /dev/null +++ b/template/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte @@ -0,0 +1,26 @@ + + +svg]:hidden", + "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", + "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", + className + )} + {...restProps} +> + {@render children?.()} + diff --git a/template/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte b/template/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte new file mode 100644 index 0000000..63c8b02 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte @@ -0,0 +1,20 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte b/template/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte new file mode 100644 index 0000000..536c308 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte @@ -0,0 +1,23 @@ + + +
svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2", + className + )} + {...restProps} +> + {@render children?.()} +
diff --git a/template/apps/web/src/lib/components/ui/avatar/avatar-group.svelte b/template/apps/web/src/lib/components/ui/avatar/avatar-group.svelte new file mode 100644 index 0000000..78714fc --- /dev/null +++ b/template/apps/web/src/lib/components/ui/avatar/avatar-group.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/template/apps/web/src/lib/components/ui/avatar/avatar-image.svelte b/template/apps/web/src/lib/components/ui/avatar/avatar-image.svelte new file mode 100644 index 0000000..f41cf8f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/avatar/avatar-image.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/avatar/avatar.svelte b/template/apps/web/src/lib/components/ui/avatar/avatar.svelte new file mode 100644 index 0000000..ea6fde5 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/avatar/avatar.svelte @@ -0,0 +1,26 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/avatar/index.ts b/template/apps/web/src/lib/components/ui/avatar/index.ts new file mode 100644 index 0000000..38ccef8 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/avatar/index.ts @@ -0,0 +1,22 @@ +import Root from "./avatar.svelte"; +import Image from "./avatar-image.svelte"; +import Fallback from "./avatar-fallback.svelte"; +import Badge from "./avatar-badge.svelte"; +import Group from "./avatar-group.svelte"; +import GroupCount from "./avatar-group-count.svelte"; + +export { + Root, + Image, + Fallback, + Badge, + Group, + GroupCount, + // + Root as Avatar, + Image as AvatarImage, + Fallback as AvatarFallback, + Badge as AvatarBadge, + Group as AvatarGroup, + GroupCount as AvatarGroupCount, +}; diff --git a/template/apps/web/src/lib/components/ui/badge/badge.svelte b/template/apps/web/src/lib/components/ui/badge/badge.svelte new file mode 100644 index 0000000..2cfe06f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/badge/badge.svelte @@ -0,0 +1,49 @@ + + + + + + {@render children?.()} + diff --git a/template/apps/web/src/lib/components/ui/badge/index.ts b/template/apps/web/src/lib/components/ui/badge/index.ts new file mode 100644 index 0000000..64e0aa9 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/badge/index.ts @@ -0,0 +1,2 @@ +export { default as Badge } from "./badge.svelte"; +export { badgeVariants, type BadgeVariant } from "./badge.svelte"; diff --git a/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte new file mode 100644 index 0000000..9e1e22c --- /dev/null +++ b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte @@ -0,0 +1,23 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte new file mode 100644 index 0000000..e9c77ea --- /dev/null +++ b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte @@ -0,0 +1,20 @@ + + +
  • + {@render children?.()} +
  • diff --git a/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte new file mode 100644 index 0000000..e6bc17d --- /dev/null +++ b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte @@ -0,0 +1,31 @@ + + +{#if child} + {@render child({ props: attrs })} +{:else} + + {@render children?.()} + +{/if} diff --git a/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte new file mode 100644 index 0000000..44fd50b --- /dev/null +++ b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte @@ -0,0 +1,20 @@ + + +
      + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte new file mode 100644 index 0000000..5fb6979 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte new file mode 100644 index 0000000..0f62fba --- /dev/null +++ b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte @@ -0,0 +1,27 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb.svelte b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb.svelte new file mode 100644 index 0000000..29ea3f5 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/breadcrumb/breadcrumb.svelte @@ -0,0 +1,22 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/breadcrumb/index.ts b/template/apps/web/src/lib/components/ui/breadcrumb/index.ts new file mode 100644 index 0000000..dc914ec --- /dev/null +++ b/template/apps/web/src/lib/components/ui/breadcrumb/index.ts @@ -0,0 +1,25 @@ +import Root from "./breadcrumb.svelte"; +import Ellipsis from "./breadcrumb-ellipsis.svelte"; +import Item from "./breadcrumb-item.svelte"; +import Separator from "./breadcrumb-separator.svelte"; +import Link from "./breadcrumb-link.svelte"; +import List from "./breadcrumb-list.svelte"; +import Page from "./breadcrumb-page.svelte"; + +export { + Root, + Ellipsis, + Item, + Separator, + Link, + List, + Page, + // + Root as Breadcrumb, + Ellipsis as BreadcrumbEllipsis, + Item as BreadcrumbItem, + Separator as BreadcrumbSeparator, + Link as BreadcrumbLink, + List as BreadcrumbList, + Page as BreadcrumbPage, +}; diff --git a/template/apps/web/src/lib/components/ui/button-group/button-group-separator.svelte b/template/apps/web/src/lib/components/ui/button-group/button-group-separator.svelte new file mode 100644 index 0000000..a13f023 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/button-group/button-group-separator.svelte @@ -0,0 +1,23 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/button-group/button-group-text.svelte b/template/apps/web/src/lib/components/ui/button-group/button-group-text.svelte new file mode 100644 index 0000000..d5b607f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/button-group/button-group-text.svelte @@ -0,0 +1,28 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} +
    + {@render mergedProps.children?.()} +
    +{/if} diff --git a/template/apps/web/src/lib/components/ui/button-group/button-group.svelte b/template/apps/web/src/lib/components/ui/button-group/button-group.svelte new file mode 100644 index 0000000..6137433 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/button-group/button-group.svelte @@ -0,0 +1,46 @@ + + + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/button-group/index.ts b/template/apps/web/src/lib/components/ui/button-group/index.ts new file mode 100644 index 0000000..664167f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/button-group/index.ts @@ -0,0 +1,15 @@ +import Root, { buttonGroupVariants, type ButtonGroupOrientation } from "./button-group.svelte"; +import Text from "./button-group-text.svelte"; +import Separator from "./button-group-separator.svelte"; + +export { + Root, + Text, + Separator, + buttonGroupVariants, + type ButtonGroupOrientation, + // + Root as ButtonGroup, + Text as ButtonGroupText, + Separator as ButtonGroupSeparator, +}; diff --git a/template/apps/web/src/lib/components/ui/button/button.svelte b/template/apps/web/src/lib/components/ui/button/button.svelte new file mode 100644 index 0000000..b8fd3bc --- /dev/null +++ b/template/apps/web/src/lib/components/ui/button/button.svelte @@ -0,0 +1,82 @@ + + + + +{#if href} + + {@render children?.()} + +{:else} + +{/if} diff --git a/template/apps/web/src/lib/components/ui/button/index.ts b/template/apps/web/src/lib/components/ui/button/index.ts new file mode 100644 index 0000000..fb585d7 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/button/index.ts @@ -0,0 +1,17 @@ +import Root, { + type ButtonProps, + type ButtonSize, + type ButtonVariant, + buttonVariants, +} from "./button.svelte"; + +export { + Root, + type ButtonProps as Props, + // + Root as Button, + buttonVariants, + type ButtonProps, + type ButtonSize, + type ButtonVariant, +}; diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-caption.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-caption.svelte new file mode 100644 index 0000000..5c93037 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-caption.svelte @@ -0,0 +1,76 @@ + + +{#snippet MonthSelect()} + { + if (!placeholder) return; + const v = Number.parseInt(e.currentTarget.value); + const newPlaceholder = placeholder.set({ month: v }); + placeholder = newPlaceholder.subtract({ months: monthIndex }); + }} + /> +{/snippet} + +{#snippet YearSelect()} + +{/snippet} + +{#if captionLayout === "dropdown"} + {@render MonthSelect()} + {@render YearSelect()} +{:else if captionLayout === "dropdown-months"} + {@render MonthSelect()} + {#if placeholder} + {formatYear(placeholder)} + {/if} +{:else if captionLayout === "dropdown-years"} + {#if placeholder} + {formatMonth(placeholder)} + {/if} + {@render YearSelect()} +{:else} + {formatMonth(month)} {formatYear(month)} +{/if} diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-cell.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-cell.svelte new file mode 100644 index 0000000..7ceb5e1 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-day.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-day.svelte new file mode 100644 index 0000000..edc8c9d --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-day.svelte @@ -0,0 +1,33 @@ + + +span]:text-xs [&>span]:opacity-70", + className + )} + {...restProps} +/> diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-grid-body.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-grid-body.svelte new file mode 100644 index 0000000..8cd86de --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-grid-body.svelte @@ -0,0 +1,12 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-grid-head.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-grid-head.svelte new file mode 100644 index 0000000..333edc4 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-grid-head.svelte @@ -0,0 +1,12 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-grid-row.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-grid-row.svelte new file mode 100644 index 0000000..9032236 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-grid-row.svelte @@ -0,0 +1,12 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-grid.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-grid.svelte new file mode 100644 index 0000000..5fbb18f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-grid.svelte @@ -0,0 +1,16 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-head-cell.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-head-cell.svelte new file mode 100644 index 0000000..131807e --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-head-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-header.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-header.svelte new file mode 100644 index 0000000..c39e955 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-header.svelte @@ -0,0 +1,19 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-heading.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-heading.svelte new file mode 100644 index 0000000..a9b9810 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-heading.svelte @@ -0,0 +1,16 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-month-select.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-month-select.svelte new file mode 100644 index 0000000..ed75b1d --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-month-select.svelte @@ -0,0 +1,48 @@ + + + + + {#snippet child({ props, monthItems, selectedMonthItem })} + + + {/snippet} + + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-month.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-month.svelte new file mode 100644 index 0000000..7bdafe7 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-month.svelte @@ -0,0 +1,15 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-months.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-months.svelte new file mode 100644 index 0000000..f717a9d --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-months.svelte @@ -0,0 +1,19 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-nav.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-nav.svelte new file mode 100644 index 0000000..27f33d7 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-nav.svelte @@ -0,0 +1,19 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-next-button.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-next-button.svelte new file mode 100644 index 0000000..82dd739 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-next-button.svelte @@ -0,0 +1,36 @@ + + +{#snippet Fallback()} + +{/snippet} + + + {#if children} + {@render children?.()} + {:else} + {@render Fallback()} + {/if} + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-prev-button.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-prev-button.svelte new file mode 100644 index 0000000..383312e --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-prev-button.svelte @@ -0,0 +1,36 @@ + + +{#snippet Fallback()} + +{/snippet} + + + {#if children} + {@render children?.()} + {:else} + {@render Fallback()} + {/if} + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar-year-select.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar-year-select.svelte new file mode 100644 index 0000000..898c2a4 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar-year-select.svelte @@ -0,0 +1,47 @@ + + + + + {#snippet child({ props, yearItems, selectedYearItem })} + + + {/snippet} + + diff --git a/template/apps/web/src/lib/components/ui/calendar/calendar.svelte b/template/apps/web/src/lib/components/ui/calendar/calendar.svelte new file mode 100644 index 0000000..74abd6a --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/calendar.svelte @@ -0,0 +1,115 @@ + + + + + {#snippet children({ months, weekdays })} + + + + + + {#each months as month, monthIndex (month)} + + + + + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + + {/each} + + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + {#if day} + {@render day({ + day: date, + outsideMonth: !isEqualMonth(date, month.value), + })} + {:else} + + {/if} + + {/each} + + {/each} + + + + {/each} + + {/snippet} + diff --git a/template/apps/web/src/lib/components/ui/calendar/index.ts b/template/apps/web/src/lib/components/ui/calendar/index.ts new file mode 100644 index 0000000..f3a16d2 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/calendar/index.ts @@ -0,0 +1,40 @@ +import Root from "./calendar.svelte"; +import Cell from "./calendar-cell.svelte"; +import Day from "./calendar-day.svelte"; +import Grid from "./calendar-grid.svelte"; +import Header from "./calendar-header.svelte"; +import Months from "./calendar-months.svelte"; +import GridRow from "./calendar-grid-row.svelte"; +import Heading from "./calendar-heading.svelte"; +import GridBody from "./calendar-grid-body.svelte"; +import GridHead from "./calendar-grid-head.svelte"; +import HeadCell from "./calendar-head-cell.svelte"; +import NextButton from "./calendar-next-button.svelte"; +import PrevButton from "./calendar-prev-button.svelte"; +import MonthSelect from "./calendar-month-select.svelte"; +import YearSelect from "./calendar-year-select.svelte"; +import Month from "./calendar-month.svelte"; +import Nav from "./calendar-nav.svelte"; +import Caption from "./calendar-caption.svelte"; + +export { + Day, + Cell, + Grid, + Header, + Months, + GridRow, + Heading, + GridBody, + GridHead, + HeadCell, + NextButton, + PrevButton, + Nav, + Month, + YearSelect, + MonthSelect, + Caption, + // + Root as Calendar, +}; diff --git a/template/apps/web/src/lib/components/ui/card/card-action.svelte b/template/apps/web/src/lib/components/ui/card/card-action.svelte new file mode 100644 index 0000000..7c48844 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/card/card-action.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/card/card-content.svelte b/template/apps/web/src/lib/components/ui/card/card-content.svelte new file mode 100644 index 0000000..4f60ee3 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/card/card-content.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/card/card-description.svelte b/template/apps/web/src/lib/components/ui/card/card-description.svelte new file mode 100644 index 0000000..8366534 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/card/card-description.svelte @@ -0,0 +1,20 @@ + + +

    + {@render children?.()} +

    diff --git a/template/apps/web/src/lib/components/ui/card/card-footer.svelte b/template/apps/web/src/lib/components/ui/card/card-footer.svelte new file mode 100644 index 0000000..17aa09a --- /dev/null +++ b/template/apps/web/src/lib/components/ui/card/card-footer.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/card/card-header.svelte b/template/apps/web/src/lib/components/ui/card/card-header.svelte new file mode 100644 index 0000000..4ec42c5 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/card/card-header.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/card/card-title.svelte b/template/apps/web/src/lib/components/ui/card/card-title.svelte new file mode 100644 index 0000000..b2a4368 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/card/card-title.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/card/card.svelte b/template/apps/web/src/lib/components/ui/card/card.svelte new file mode 100644 index 0000000..86226bb --- /dev/null +++ b/template/apps/web/src/lib/components/ui/card/card.svelte @@ -0,0 +1,22 @@ + + +
    img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none group/card flex flex-col", className)} + {...restProps} +> + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/card/index.ts b/template/apps/web/src/lib/components/ui/card/index.ts new file mode 100644 index 0000000..4d3fce4 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/card/index.ts @@ -0,0 +1,25 @@ +import Root from "./card.svelte"; +import Content from "./card-content.svelte"; +import Description from "./card-description.svelte"; +import Footer from "./card-footer.svelte"; +import Header from "./card-header.svelte"; +import Title from "./card-title.svelte"; +import Action from "./card-action.svelte"; + +export { + Root, + Content, + Description, + Footer, + Header, + Title, + Action, + // + Root as Card, + Content as CardContent, + Description as CardDescription, + Footer as CardFooter, + Header as CardHeader, + Title as CardTitle, + Action as CardAction, +}; diff --git a/template/apps/web/src/lib/components/ui/carousel/carousel-content.svelte b/template/apps/web/src/lib/components/ui/carousel/carousel-content.svelte new file mode 100644 index 0000000..84c71f8 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/carousel/carousel-content.svelte @@ -0,0 +1,43 @@ + + +
    +
    + {@render children?.()} +
    +
    diff --git a/template/apps/web/src/lib/components/ui/carousel/carousel-item.svelte b/template/apps/web/src/lib/components/ui/carousel/carousel-item.svelte new file mode 100644 index 0000000..ebf1649 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/carousel/carousel-item.svelte @@ -0,0 +1,30 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/carousel/carousel-next.svelte b/template/apps/web/src/lib/components/ui/carousel/carousel-next.svelte new file mode 100644 index 0000000..7a3fdb3 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/carousel/carousel-next.svelte @@ -0,0 +1,39 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/carousel/carousel-previous.svelte b/template/apps/web/src/lib/components/ui/carousel/carousel-previous.svelte new file mode 100644 index 0000000..29cd858 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/carousel/carousel-previous.svelte @@ -0,0 +1,39 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/carousel/carousel.svelte b/template/apps/web/src/lib/components/ui/carousel/carousel.svelte new file mode 100644 index 0000000..b3933b8 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/carousel/carousel.svelte @@ -0,0 +1,94 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/carousel/context.ts b/template/apps/web/src/lib/components/ui/carousel/context.ts new file mode 100644 index 0000000..a5fd74f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/carousel/context.ts @@ -0,0 +1,58 @@ +import type { WithElementRef } from "$lib/utils.js"; +import type { + EmblaCarouselSvelteType, + default as emblaCarouselSvelte, +} from "embla-carousel-svelte"; +import { getContext, hasContext, setContext } from "svelte"; +import type { HTMLAttributes } from "svelte/elements"; + +export type CarouselAPI = + NonNullable["on:emblaInit"]> extends ( + evt: CustomEvent + ) => void + ? CarouselAPI + : never; + +type EmblaCarouselConfig = NonNullable[1]>; + +export type CarouselOptions = EmblaCarouselConfig["options"]; +export type CarouselPlugins = EmblaCarouselConfig["plugins"]; + +//// + +export type CarouselProps = { + opts?: CarouselOptions; + plugins?: CarouselPlugins; + setApi?: (api: CarouselAPI | undefined) => void; + orientation?: "horizontal" | "vertical"; +} & WithElementRef>; + +const EMBLA_CAROUSEL_CONTEXT = Symbol("EMBLA_CAROUSEL_CONTEXT"); + +export type EmblaContext = { + api: CarouselAPI | undefined; + orientation: "horizontal" | "vertical"; + scrollNext: () => void; + scrollPrev: () => void; + canScrollNext: boolean; + canScrollPrev: boolean; + handleKeyDown: (e: KeyboardEvent) => void; + options: CarouselOptions; + plugins: CarouselPlugins; + onInit: (e: CustomEvent) => void; + scrollTo: (index: number, jump?: boolean) => void; + scrollSnaps: number[]; + selectedIndex: number; +}; + +export function setEmblaContext(config: EmblaContext): EmblaContext { + setContext(EMBLA_CAROUSEL_CONTEXT, config); + return config; +} + +export function getEmblaContext(name = "This component") { + if (!hasContext(EMBLA_CAROUSEL_CONTEXT)) { + throw new Error(`${name} must be used within a component`); + } + return getContext>(EMBLA_CAROUSEL_CONTEXT); +} diff --git a/template/apps/web/src/lib/components/ui/carousel/index.ts b/template/apps/web/src/lib/components/ui/carousel/index.ts new file mode 100644 index 0000000..957fc74 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/carousel/index.ts @@ -0,0 +1,19 @@ +import Root from "./carousel.svelte"; +import Content from "./carousel-content.svelte"; +import Item from "./carousel-item.svelte"; +import Previous from "./carousel-previous.svelte"; +import Next from "./carousel-next.svelte"; + +export { + Root, + Content, + Item, + Previous, + Next, + // + Root as Carousel, + Content as CarouselContent, + Item as CarouselItem, + Previous as CarouselPrevious, + Next as CarouselNext, +}; diff --git a/template/apps/web/src/lib/components/ui/chart/chart-container.svelte b/template/apps/web/src/lib/components/ui/chart/chart-container.svelte new file mode 100644 index 0000000..1eb8e39 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/chart/chart-container.svelte @@ -0,0 +1,80 @@ + + +
    + + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/chart/chart-style.svelte b/template/apps/web/src/lib/components/ui/chart/chart-style.svelte new file mode 100644 index 0000000..ea1b3b2 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/chart/chart-style.svelte @@ -0,0 +1,37 @@ + + +{#if themeContents} + {#key id} + + {themeContents} + + {/key} +{/if} diff --git a/template/apps/web/src/lib/components/ui/chart/chart-tooltip.svelte b/template/apps/web/src/lib/components/ui/chart/chart-tooltip.svelte new file mode 100644 index 0000000..e0da9ca --- /dev/null +++ b/template/apps/web/src/lib/components/ui/chart/chart-tooltip.svelte @@ -0,0 +1,184 @@ + + +{#snippet TooltipLabel()} + {#if formattedLabel} +
    + {#if typeof formattedLabel === "function"} + {@render formattedLabel()} + {:else} + {formattedLabel} + {/if} +
    + {/if} +{/snippet} + + +
    + {#if !nestLabel} + {@render TooltipLabel()} + {/if} +
    + {#each visibleSeries as item, i (item.key + i)} + {@const key = `${nameKey || item.key || item.label || "value"}`} + {@const itemConfig = getPayloadConfigFromPayload( + chart.config, + item, + key, + chartCtx.tooltip.data + )} + {@const indicatorColor = color || item.config?.color || item.color} +
    svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:size-2.5", + indicator === "dot" && "items-center" + )} + > + {#if formatter && item.value !== undefined && item.label} + {@render formatter({ + value: item.value, + name: item.label, + item, + index: i, + payload: visibleSeries, + })} + {:else} + {#if itemConfig?.icon} + + {:else if !hideIndicator} +
    + {/if} +
    +
    + {#if nestLabel} + {@render TooltipLabel()} + {/if} + + {itemConfig?.label || item.label} + +
    + {#if item.value !== undefined} + + {item.value.toLocaleString()} + + {/if} +
    + {/if} +
    + {/each} +
    +
    +
    diff --git a/template/apps/web/src/lib/components/ui/chart/chart-utils.ts b/template/apps/web/src/lib/components/ui/chart/chart-utils.ts new file mode 100644 index 0000000..a289e35 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/chart/chart-utils.ts @@ -0,0 +1,68 @@ +import type { Tooltip } from "layerchart"; +import { getContext, setContext, type Component, type Snippet } from "svelte"; + +export const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + [k in string]: { + label?: string; + icon?: Component; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +export type ExtractSnippetParams = T extends Snippet<[infer P]> ? P : never; + +export type TooltipPayload = Tooltip.TooltipSeries; + +// Helper to extract item config from a payload. +export function getPayloadConfigFromPayload( + config: ChartConfig, + payload: TooltipPayload, + key: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data?: Record | null +) { + if (typeof payload !== "object" || payload === null) return undefined; + + const payloadConfig = + "config" in payload && typeof payload.config === "object" && payload.config !== null + ? payload.config + : undefined; + + let configLabelKey: string = key; + + if (payload.key === key) { + configLabelKey = payload.key; + } else if (payload.label === key) { + configLabelKey = payload.label; + } else if (key in payload && typeof payload[key as keyof typeof payload] === "string") { + configLabelKey = payload[key as keyof typeof payload] as string; + } else if ( + payloadConfig !== undefined && + key in payloadConfig && + typeof payloadConfig[key as keyof typeof payloadConfig] === "string" + ) { + configLabelKey = payloadConfig[key as keyof typeof payloadConfig] as string; + } else if (data != null && key in data && typeof data[key] === "string") { + configLabelKey = data[key] as string; + } + + return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]; +} + +type ChartContextValue = { + config: ChartConfig; +}; + +const chartContextKey = Symbol("chart-context"); + +export function setChartContext(value: ChartContextValue) { + return setContext(chartContextKey, value); +} + +export function useChart() { + return getContext(chartContextKey); +} diff --git a/template/apps/web/src/lib/components/ui/chart/index.ts b/template/apps/web/src/lib/components/ui/chart/index.ts new file mode 100644 index 0000000..f22375e --- /dev/null +++ b/template/apps/web/src/lib/components/ui/chart/index.ts @@ -0,0 +1,6 @@ +import ChartContainer from "./chart-container.svelte"; +import ChartTooltip from "./chart-tooltip.svelte"; + +export { getPayloadConfigFromPayload, type ChartConfig } from "./chart-utils.js"; + +export { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip }; diff --git a/template/apps/web/src/lib/components/ui/checkbox/checkbox.svelte b/template/apps/web/src/lib/components/ui/checkbox/checkbox.svelte new file mode 100644 index 0000000..39af8e1 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/checkbox/checkbox.svelte @@ -0,0 +1,39 @@ + + + + {#snippet children({ checked, indeterminate })} +
    + {#if checked} + + {:else if indeterminate} + + {/if} +
    + {/snippet} +
    diff --git a/template/apps/web/src/lib/components/ui/checkbox/index.ts b/template/apps/web/src/lib/components/ui/checkbox/index.ts new file mode 100644 index 0000000..6d92d94 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/checkbox/index.ts @@ -0,0 +1,6 @@ +import Root from "./checkbox.svelte"; +export { + Root, + // + Root as Checkbox, +}; diff --git a/template/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte b/template/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte new file mode 100644 index 0000000..bdabb55 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte b/template/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte new file mode 100644 index 0000000..ece7ad6 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/collapsible/collapsible.svelte b/template/apps/web/src/lib/components/ui/collapsible/collapsible.svelte new file mode 100644 index 0000000..39cdd4e --- /dev/null +++ b/template/apps/web/src/lib/components/ui/collapsible/collapsible.svelte @@ -0,0 +1,11 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/collapsible/index.ts b/template/apps/web/src/lib/components/ui/collapsible/index.ts new file mode 100644 index 0000000..169b479 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/collapsible/index.ts @@ -0,0 +1,13 @@ +import Root from "./collapsible.svelte"; +import Trigger from "./collapsible-trigger.svelte"; +import Content from "./collapsible-content.svelte"; + +export { + Root, + Content, + Trigger, + // + Root as Collapsible, + Content as CollapsibleContent, + Trigger as CollapsibleTrigger, +}; diff --git a/template/apps/web/src/lib/components/ui/command/command-dialog.svelte b/template/apps/web/src/lib/components/ui/command/command-dialog.svelte new file mode 100644 index 0000000..024c76c --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/command-dialog.svelte @@ -0,0 +1,42 @@ + + + + + {title} + {description} + + + + + diff --git a/template/apps/web/src/lib/components/ui/command/command-empty.svelte b/template/apps/web/src/lib/components/ui/command/command-empty.svelte new file mode 100644 index 0000000..4cfd990 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/command-empty.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/command/command-group.svelte b/template/apps/web/src/lib/components/ui/command/command-group.svelte new file mode 100644 index 0000000..0712eee --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/command-group.svelte @@ -0,0 +1,32 @@ + + + + {#if heading} + + {heading} + + {/if} + + diff --git a/template/apps/web/src/lib/components/ui/command/command-input.svelte b/template/apps/web/src/lib/components/ui/command/command-input.svelte new file mode 100644 index 0000000..2881f6f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/command-input.svelte @@ -0,0 +1,31 @@ + + +
    + + + + + + +
    diff --git a/template/apps/web/src/lib/components/ui/command/command-item.svelte b/template/apps/web/src/lib/components/ui/command/command-item.svelte new file mode 100644 index 0000000..4246840 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/command-item.svelte @@ -0,0 +1,25 @@ + + + + {@render children?.()} + + diff --git a/template/apps/web/src/lib/components/ui/command/command-link-item.svelte b/template/apps/web/src/lib/components/ui/command/command-link-item.svelte new file mode 100644 index 0000000..ada6d2c --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/command-link-item.svelte @@ -0,0 +1,20 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/command/command-list.svelte b/template/apps/web/src/lib/components/ui/command/command-list.svelte new file mode 100644 index 0000000..e97747f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/command-list.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/command/command-loading.svelte b/template/apps/web/src/lib/components/ui/command/command-loading.svelte new file mode 100644 index 0000000..19dd298 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/command-loading.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/command/command-separator.svelte b/template/apps/web/src/lib/components/ui/command/command-separator.svelte new file mode 100644 index 0000000..35c4c95 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/command-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/command/command-shortcut.svelte b/template/apps/web/src/lib/components/ui/command/command-shortcut.svelte new file mode 100644 index 0000000..5322e6a --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/command-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/template/apps/web/src/lib/components/ui/command/command.svelte b/template/apps/web/src/lib/components/ui/command/command.svelte new file mode 100644 index 0000000..74a415b --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/command.svelte @@ -0,0 +1,25 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/command/index.ts b/template/apps/web/src/lib/components/ui/command/index.ts new file mode 100644 index 0000000..5435fbe --- /dev/null +++ b/template/apps/web/src/lib/components/ui/command/index.ts @@ -0,0 +1,37 @@ +import Root from "./command.svelte"; +import Loading from "./command-loading.svelte"; +import Dialog from "./command-dialog.svelte"; +import Empty from "./command-empty.svelte"; +import Group from "./command-group.svelte"; +import Item from "./command-item.svelte"; +import Input from "./command-input.svelte"; +import List from "./command-list.svelte"; +import Separator from "./command-separator.svelte"; +import Shortcut from "./command-shortcut.svelte"; +import LinkItem from "./command-link-item.svelte"; + +export { + Root, + Dialog, + Empty, + Group, + Item, + LinkItem, + Input, + List, + Separator, + Shortcut, + Loading, + // + Root as Command, + Dialog as CommandDialog, + Empty as CommandEmpty, + Group as CommandGroup, + Item as CommandItem, + LinkItem as CommandLinkItem, + Input as CommandInput, + List as CommandList, + Separator as CommandSeparator, + Shortcut as CommandShortcut, + Loading as CommandLoading, +}; diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte new file mode 100644 index 0000000..da5bd30 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte @@ -0,0 +1,41 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.()} + {/snippet} + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-content.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-content.svelte new file mode 100644 index 0000000..fb34a60 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-content.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-group-heading.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-group-heading.svelte new file mode 100644 index 0000000..0c0f459 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-group-heading.svelte @@ -0,0 +1,21 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-group.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-group.svelte new file mode 100644 index 0000000..c7c1e06 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-item.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-item.svelte new file mode 100644 index 0000000..d1601b6 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-item.svelte @@ -0,0 +1,27 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-label.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-label.svelte new file mode 100644 index 0000000..4b3b1fe --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-label.svelte @@ -0,0 +1,24 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-portal.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-portal.svelte new file mode 100644 index 0000000..96b1e3e --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-radio-group.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-radio-group.svelte new file mode 100644 index 0000000..964cb55 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-radio-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-radio-item.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-radio-item.svelte new file mode 100644 index 0000000..24b7380 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-radio-item.svelte @@ -0,0 +1,35 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.({ checked })} + {/snippet} + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-separator.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-separator.svelte new file mode 100644 index 0000000..e675367 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-shortcut.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-shortcut.svelte new file mode 100644 index 0000000..3305abc --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-sub-content.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-sub-content.svelte new file mode 100644 index 0000000..fd85c70 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-sub-content.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte new file mode 100644 index 0000000..b1e9370 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-sub.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-sub.svelte new file mode 100644 index 0000000..a03827b --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-sub.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu-trigger.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu-trigger.svelte new file mode 100644 index 0000000..f6a1542 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu-trigger.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/context-menu.svelte b/template/apps/web/src/lib/components/ui/context-menu/context-menu.svelte new file mode 100644 index 0000000..cfaefb3 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/context-menu.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/context-menu/index.ts b/template/apps/web/src/lib/components/ui/context-menu/index.ts new file mode 100644 index 0000000..cbeaee1 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/context-menu/index.ts @@ -0,0 +1,52 @@ +import Root from "./context-menu.svelte"; +import Sub from "./context-menu-sub.svelte"; +import Portal from "./context-menu-portal.svelte"; +import Trigger from "./context-menu-trigger.svelte"; +import Group from "./context-menu-group.svelte"; +import RadioGroup from "./context-menu-radio-group.svelte"; +import Item from "./context-menu-item.svelte"; +import GroupHeading from "./context-menu-group-heading.svelte"; +import Content from "./context-menu-content.svelte"; +import Shortcut from "./context-menu-shortcut.svelte"; +import RadioItem from "./context-menu-radio-item.svelte"; +import Separator from "./context-menu-separator.svelte"; +import SubContent from "./context-menu-sub-content.svelte"; +import SubTrigger from "./context-menu-sub-trigger.svelte"; +import CheckboxItem from "./context-menu-checkbox-item.svelte"; +import Label from "./context-menu-label.svelte"; + +export { + Root, + Sub, + Portal, + Item, + GroupHeading, + Label, + Group, + Trigger, + Content, + Shortcut, + Separator, + RadioItem, + SubContent, + SubTrigger, + RadioGroup, + CheckboxItem, + // + Root as ContextMenu, + Sub as ContextMenuSub, + Portal as ContextMenuPortal, + Item as ContextMenuItem, + GroupHeading as ContextMenuGroupHeading, + Group as ContextMenuGroup, + Content as ContextMenuContent, + Trigger as ContextMenuTrigger, + Shortcut as ContextMenuShortcut, + RadioItem as ContextMenuRadioItem, + Separator as ContextMenuSeparator, + RadioGroup as ContextMenuRadioGroup, + SubContent as ContextMenuSubContent, + SubTrigger as ContextMenuSubTrigger, + CheckboxItem as ContextMenuCheckboxItem, + Label as ContextMenuLabel, +}; diff --git a/template/apps/web/src/lib/components/ui/data-table/data-table.svelte.ts b/template/apps/web/src/lib/components/ui/data-table/data-table.svelte.ts new file mode 100644 index 0000000..f3c30a7 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/data-table/data-table.svelte.ts @@ -0,0 +1,142 @@ +import { + type RowData, + type TableOptions, + type TableOptionsResolved, + type TableState, + type Updater, + createTable, +} from "@tanstack/table-core"; + +/** + * Creates a reactive TanStack table object for Svelte. + * @param options Table options to create the table with. + * @returns A reactive table object. + * @example + * ```svelte + * + * + * + * + * {#each table.getHeaderGroups() as headerGroup} + * + * {#each headerGroup.headers as header} + * + * {/each} + * + * {/each} + * + * + *
    + * + *
    + * ``` + */ +export function createSvelteTable(options: TableOptions) { + const resolvedOptions: TableOptionsResolved = mergeObjects( + { + state: {}, + onStateChange() {}, + renderFallbackValue: null, + mergeOptions: ( + defaultOptions: TableOptions, + options: Partial> + ) => { + return mergeObjects(defaultOptions, options); + }, + }, + options + ); + + const table = createTable(resolvedOptions); + let state = $state(table.initialState); + + function updateOptions() { + table.setOptions(() => { + return mergeObjects(resolvedOptions, options, { + state: mergeObjects(state, options.state || {}), + + onStateChange: (updater: Updater) => { + if (updater instanceof Function) state = updater(state); + else state = mergeObjects(state, updater); + + options.onStateChange?.(updater); + }, + }); + }); + } + + updateOptions(); + + $effect.pre(() => { + updateOptions(); + }); + + return table; +} + +type MaybeThunk = T | (() => T | null | undefined); +type Intersection = (T extends [infer H, ...infer R] + ? H & Intersection + : unknown) & {}; + +/** + * Lazily merges several objects (or thunks) while preserving + * getter semantics from every source. + * + * Proxy-based to avoid known WebKit recursion issue. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function mergeObjects[]>( + ...sources: Sources +): Intersection<{ [K in keyof Sources]: Sources[K] }> { + const resolve = (src: MaybeThunk): T | undefined => + typeof src === "function" ? (src() ?? undefined) : src; + + const findSourceWithKey = (key: PropertyKey) => { + for (let i = sources.length - 1; i >= 0; i--) { + const obj = resolve(sources[i]); + if (obj && key in obj) return obj; + } + return undefined; + }; + + return new Proxy(Object.create(null), { + get(_, key) { + const src = findSourceWithKey(key); + + return src?.[key as never]; + }, + + has(_, key) { + return !!findSourceWithKey(key); + }, + + ownKeys(): (string | symbol)[] { + // eslint-disable-next-line svelte/prefer-svelte-reactivity + const all = new Set(); + for (const s of sources) { + const obj = resolve(s); + if (obj) { + for (const k of Reflect.ownKeys(obj) as (string | symbol)[]) { + all.add(k); + } + } + } + return [...all]; + }, + + getOwnPropertyDescriptor(_, key) { + const src = findSourceWithKey(key); + if (!src) return undefined; + return { + configurable: true, + enumerable: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: (src as any)[key], + writable: true, + }; + }, + }) as Intersection<{ [K in keyof Sources]: Sources[K] }>; +} diff --git a/template/apps/web/src/lib/components/ui/data-table/flex-render.svelte b/template/apps/web/src/lib/components/ui/data-table/flex-render.svelte new file mode 100644 index 0000000..ac82a58 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/data-table/flex-render.svelte @@ -0,0 +1,40 @@ + + +{#if typeof content === "string"} + {content} +{:else if content instanceof Function} + + + {@const result = content(context as any)} + {#if result instanceof RenderComponentConfig} + {@const { component: Component, props } = result} + + {:else if result instanceof RenderSnippetConfig} + {@const { snippet, params } = result} + {@render snippet({ ...params, attach })} + {:else} + {result} + {/if} +{/if} diff --git a/template/apps/web/src/lib/components/ui/data-table/index.ts b/template/apps/web/src/lib/components/ui/data-table/index.ts new file mode 100644 index 0000000..5f4e77e --- /dev/null +++ b/template/apps/web/src/lib/components/ui/data-table/index.ts @@ -0,0 +1,3 @@ +export { default as FlexRender } from "./flex-render.svelte"; +export { renderComponent, renderSnippet } from "./render-helpers.js"; +export { createSvelteTable } from "./data-table.svelte.js"; diff --git a/template/apps/web/src/lib/components/ui/data-table/render-helpers.ts b/template/apps/web/src/lib/components/ui/data-table/render-helpers.ts new file mode 100644 index 0000000..fa036d6 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/data-table/render-helpers.ts @@ -0,0 +1,111 @@ +import type { Component, ComponentProps, Snippet } from "svelte"; + +/** + * A helper class to make it easy to identify Svelte components in + * `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderComponentConfig} + * {@const { component: Component, props } = result} + * + * {/if} + * ``` + */ +export class RenderComponentConfig { + component: TComponent; + props: ComponentProps | Record; + constructor( + component: TComponent, + props: ComponentProps | Record = {} + ) { + this.component = component; + this.props = props; + } +} + +/** + * A helper class to make it easy to identify Svelte Snippets in `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderSnippetConfig} + * {@const { snippet, params } = result} + * {@render snippet(params)} + * {/if} + * ``` + */ +export class RenderSnippetConfig { + snippet: Snippet<[TProps]>; + params: TProps; + constructor(snippet: Snippet<[TProps]>, params: TProps) { + this.snippet = snippet; + this.params = params; + } +} + +/** + * A helper function to help create cells from Svelte components through ColumnDef's `cell` and `header` properties. + * + * This is only to be used with Svelte Components - use `renderSnippet` for Svelte Snippets. + * + * @param component A Svelte component + * @param props The props to pass to `component` + * @returns A `RenderComponentConfig` object that helps svelte-table know how to render the header/cell component. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * header: header => renderComponent(SortHeader, { label: 'Name', header }), + * }), + * columnHelper.accessor('state', { + * header: header => renderComponent(SortHeader, { label: 'State', header }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderComponent< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T extends Component, + Props extends ComponentProps, +>(component: T, props: Props = {} as Props) { + return new RenderComponentConfig(component, props); +} + +/** + * A helper function to help create cells from Svelte Snippets through ColumnDef's `cell` and `header` properties. + * + * The snippet must only take one parameter. + * + * This is only to be used with Snippets - use `renderComponent` for Svelte Components. + * + * @param snippet + * @param params + * @returns - A `RenderSnippetConfig` object that helps svelte-table know how to render the header/cell snippet. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * cell: cell => renderSnippet(nameSnippet, { name: cell.row.name }), + * }), + * columnHelper.accessor('state', { + * cell: cell => renderSnippet(stateSnippet, { state: cell.row.state }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderSnippet(snippet: Snippet<[TProps]>, params: TProps = {} as TProps) { + return new RenderSnippetConfig(snippet, params); +} diff --git a/template/apps/web/src/lib/components/ui/dialog/dialog-close.svelte b/template/apps/web/src/lib/components/ui/dialog/dialog-close.svelte new file mode 100644 index 0000000..de68f2f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dialog/dialog-close.svelte @@ -0,0 +1,11 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dialog/dialog-content.svelte b/template/apps/web/src/lib/components/ui/dialog/dialog-content.svelte new file mode 100644 index 0000000..6c963e4 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dialog/dialog-content.svelte @@ -0,0 +1,48 @@ + + + + + + {@render children?.()} + {#if showCloseButton} + + {#snippet child({ props })} + + {/snippet} + + {/if} + + diff --git a/template/apps/web/src/lib/components/ui/dialog/dialog-description.svelte b/template/apps/web/src/lib/components/ui/dialog/dialog-description.svelte new file mode 100644 index 0000000..bd220ac --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dialog/dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte b/template/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte new file mode 100644 index 0000000..2f54b2c --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte @@ -0,0 +1,32 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dialog/dialog-header.svelte b/template/apps/web/src/lib/components/ui/dialog/dialog-header.svelte new file mode 100644 index 0000000..8164f0f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dialog/dialog-header.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte b/template/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte new file mode 100644 index 0000000..19f69f0 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte b/template/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte new file mode 100644 index 0000000..ccfa79c --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dialog/dialog-title.svelte b/template/apps/web/src/lib/components/ui/dialog/dialog-title.svelte new file mode 100644 index 0000000..493f497 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dialog/dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte b/template/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte new file mode 100644 index 0000000..589ee0c --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte @@ -0,0 +1,11 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dialog/dialog.svelte b/template/apps/web/src/lib/components/ui/dialog/dialog.svelte new file mode 100644 index 0000000..211672c --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dialog/dialog.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dialog/index.ts b/template/apps/web/src/lib/components/ui/dialog/index.ts new file mode 100644 index 0000000..076cef5 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dialog/index.ts @@ -0,0 +1,34 @@ +import Root from "./dialog.svelte"; +import Portal from "./dialog-portal.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"; + +export { + Root, + Title, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + Close, + // + Root as Dialog, + Title as DialogTitle, + Portal as DialogPortal, + Footer as DialogFooter, + Header as DialogHeader, + Trigger as DialogTrigger, + Overlay as DialogOverlay, + Content as DialogContent, + Description as DialogDescription, + Close as DialogClose, +}; diff --git a/template/apps/web/src/lib/components/ui/drawer/drawer-close.svelte b/template/apps/web/src/lib/components/ui/drawer/drawer-close.svelte new file mode 100644 index 0000000..95c2479 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/drawer-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/drawer/drawer-content.svelte b/template/apps/web/src/lib/components/ui/drawer/drawer-content.svelte new file mode 100644 index 0000000..c389dd6 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/drawer-content.svelte @@ -0,0 +1,33 @@ + + + + + + + {@render children?.()} + + diff --git a/template/apps/web/src/lib/components/ui/drawer/drawer-description.svelte b/template/apps/web/src/lib/components/ui/drawer/drawer-description.svelte new file mode 100644 index 0000000..ded26d2 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/drawer-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/drawer/drawer-footer.svelte b/template/apps/web/src/lib/components/ui/drawer/drawer-footer.svelte new file mode 100644 index 0000000..56a1fc6 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/drawer-footer.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/drawer/drawer-header.svelte b/template/apps/web/src/lib/components/ui/drawer/drawer-header.svelte new file mode 100644 index 0000000..861f956 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/drawer-header.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/drawer/drawer-nested.svelte b/template/apps/web/src/lib/components/ui/drawer/drawer-nested.svelte new file mode 100644 index 0000000..834af94 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/drawer-nested.svelte @@ -0,0 +1,12 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/drawer/drawer-overlay.svelte b/template/apps/web/src/lib/components/ui/drawer/drawer-overlay.svelte new file mode 100644 index 0000000..84df1e8 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/drawer-overlay.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/drawer/drawer-portal.svelte b/template/apps/web/src/lib/components/ui/drawer/drawer-portal.svelte new file mode 100644 index 0000000..5a0dd74 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/drawer-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/drawer/drawer-title.svelte b/template/apps/web/src/lib/components/ui/drawer/drawer-title.svelte new file mode 100644 index 0000000..ba99287 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/drawer-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/drawer/drawer-trigger.svelte b/template/apps/web/src/lib/components/ui/drawer/drawer-trigger.svelte new file mode 100644 index 0000000..f1877d8 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/drawer-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/drawer/drawer.svelte b/template/apps/web/src/lib/components/ui/drawer/drawer.svelte new file mode 100644 index 0000000..0cb57ff --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/drawer.svelte @@ -0,0 +1,12 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/drawer/index.ts b/template/apps/web/src/lib/components/ui/drawer/index.ts new file mode 100644 index 0000000..1656cac --- /dev/null +++ b/template/apps/web/src/lib/components/ui/drawer/index.ts @@ -0,0 +1,38 @@ +import Root from "./drawer.svelte"; +import Content from "./drawer-content.svelte"; +import Description from "./drawer-description.svelte"; +import Overlay from "./drawer-overlay.svelte"; +import Footer from "./drawer-footer.svelte"; +import Header from "./drawer-header.svelte"; +import Title from "./drawer-title.svelte"; +import NestedRoot from "./drawer-nested.svelte"; +import Close from "./drawer-close.svelte"; +import Trigger from "./drawer-trigger.svelte"; +import Portal from "./drawer-portal.svelte"; + +export { + Root, + NestedRoot, + Content, + Description, + Overlay, + Footer, + Header, + Title, + Trigger, + Portal, + Close, + + // + Root as Drawer, + NestedRoot as DrawerNestedRoot, + Content as DrawerContent, + Description as DrawerDescription, + Overlay as DrawerOverlay, + Footer as DrawerFooter, + Header as DrawerHeader, + Title as DrawerTitle, + Trigger as DrawerTrigger, + Portal as DrawerPortal, + Close as DrawerClose, +}; diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte new file mode 100644 index 0000000..e0e1971 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte new file mode 100644 index 0000000..e12c9a4 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte @@ -0,0 +1,44 @@ + + + + {#snippet children({ checked, indeterminate })} + + {#if indeterminate} + + {:else if checked} + + {/if} + + {@render childrenProp?.()} + {/snippet} + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte new file mode 100644 index 0000000..1442e33 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte @@ -0,0 +1,31 @@ + + + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte new file mode 100644 index 0000000..433540f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte @@ -0,0 +1,22 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte new file mode 100644 index 0000000..aca1f7b --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte new file mode 100644 index 0000000..20713df --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte @@ -0,0 +1,27 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte new file mode 100644 index 0000000..05a9e64 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte @@ -0,0 +1,24 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte new file mode 100644 index 0000000..274cfef --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte new file mode 100644 index 0000000..189aef4 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte new file mode 100644 index 0000000..f7e4dbf --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte @@ -0,0 +1,34 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.({ checked })} + {/snippet} + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte new file mode 100644 index 0000000..45f82af --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte new file mode 100644 index 0000000..ed7cc85 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte new file mode 100644 index 0000000..8de5cf1 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte @@ -0,0 +1,17 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte new file mode 100644 index 0000000..5e16f67 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte new file mode 100644 index 0000000..f044581 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte new file mode 100644 index 0000000..cb05344 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte new file mode 100644 index 0000000..cb4bc62 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/dropdown-menu/index.ts b/template/apps/web/src/lib/components/ui/dropdown-menu/index.ts new file mode 100644 index 0000000..7850c6a --- /dev/null +++ b/template/apps/web/src/lib/components/ui/dropdown-menu/index.ts @@ -0,0 +1,54 @@ +import Root from "./dropdown-menu.svelte"; +import Sub from "./dropdown-menu-sub.svelte"; +import CheckboxGroup from "./dropdown-menu-checkbox-group.svelte"; +import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; +import Content from "./dropdown-menu-content.svelte"; +import Group from "./dropdown-menu-group.svelte"; +import Item from "./dropdown-menu-item.svelte"; +import Label from "./dropdown-menu-label.svelte"; +import RadioGroup from "./dropdown-menu-radio-group.svelte"; +import RadioItem from "./dropdown-menu-radio-item.svelte"; +import Separator from "./dropdown-menu-separator.svelte"; +import Shortcut from "./dropdown-menu-shortcut.svelte"; +import Trigger from "./dropdown-menu-trigger.svelte"; +import SubContent from "./dropdown-menu-sub-content.svelte"; +import SubTrigger from "./dropdown-menu-sub-trigger.svelte"; +import GroupHeading from "./dropdown-menu-group-heading.svelte"; +import Portal from "./dropdown-menu-portal.svelte"; + +export { + CheckboxGroup, + CheckboxItem, + Content, + Portal, + Root as DropdownMenu, + CheckboxGroup as DropdownMenuCheckboxGroup, + CheckboxItem as DropdownMenuCheckboxItem, + Content as DropdownMenuContent, + Portal as DropdownMenuPortal, + Group as DropdownMenuGroup, + Item as DropdownMenuItem, + Label as DropdownMenuLabel, + RadioGroup as DropdownMenuRadioGroup, + RadioItem as DropdownMenuRadioItem, + Separator as DropdownMenuSeparator, + Shortcut as DropdownMenuShortcut, + Sub as DropdownMenuSub, + SubContent as DropdownMenuSubContent, + SubTrigger as DropdownMenuSubTrigger, + Trigger as DropdownMenuTrigger, + GroupHeading as DropdownMenuGroupHeading, + Group, + GroupHeading, + Item, + Label, + RadioGroup, + RadioItem, + Root, + Separator, + Shortcut, + Sub, + SubContent, + SubTrigger, + Trigger, +}; diff --git a/template/apps/web/src/lib/components/ui/empty/empty-content.svelte b/template/apps/web/src/lib/components/ui/empty/empty-content.svelte new file mode 100644 index 0000000..cc17823 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/empty/empty-content.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/empty/empty-description.svelte b/template/apps/web/src/lib/components/ui/empty/empty-description.svelte new file mode 100644 index 0000000..9e8cd78 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/empty/empty-description.svelte @@ -0,0 +1,23 @@ + + +
    a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4", + className + )} + {...restProps} +> + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/empty/empty-header.svelte b/template/apps/web/src/lib/components/ui/empty/empty-header.svelte new file mode 100644 index 0000000..7112f66 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/empty/empty-header.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/empty/empty-media.svelte b/template/apps/web/src/lib/components/ui/empty/empty-media.svelte new file mode 100644 index 0000000..24fe55d --- /dev/null +++ b/template/apps/web/src/lib/components/ui/empty/empty-media.svelte @@ -0,0 +1,41 @@ + + + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/empty/empty-title.svelte b/template/apps/web/src/lib/components/ui/empty/empty-title.svelte new file mode 100644 index 0000000..346c2ec --- /dev/null +++ b/template/apps/web/src/lib/components/ui/empty/empty-title.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/empty/empty.svelte b/template/apps/web/src/lib/components/ui/empty/empty.svelte new file mode 100644 index 0000000..e16c45c --- /dev/null +++ b/template/apps/web/src/lib/components/ui/empty/empty.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/empty/index.ts b/template/apps/web/src/lib/components/ui/empty/index.ts new file mode 100644 index 0000000..ae4c106 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/empty/index.ts @@ -0,0 +1,22 @@ +import Root from "./empty.svelte"; +import Header from "./empty-header.svelte"; +import Media from "./empty-media.svelte"; +import Title from "./empty-title.svelte"; +import Description from "./empty-description.svelte"; +import Content from "./empty-content.svelte"; + +export { + Root, + Header, + Media, + Title, + Description, + Content, + // + Root as Empty, + Header as EmptyHeader, + Media as EmptyMedia, + Title as EmptyTitle, + Description as EmptyDescription, + Content as EmptyContent, +}; diff --git a/template/apps/web/src/lib/components/ui/field/field-content.svelte b/template/apps/web/src/lib/components/ui/field/field-content.svelte new file mode 100644 index 0000000..1545c43 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/field/field-content.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/field/field-description.svelte b/template/apps/web/src/lib/components/ui/field/field-description.svelte new file mode 100644 index 0000000..f033c10 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/field/field-description.svelte @@ -0,0 +1,25 @@ + + +

    a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", + className + )} + {...restProps} +> + {@render children?.()} +

    diff --git a/template/apps/web/src/lib/components/ui/field/field-error.svelte b/template/apps/web/src/lib/components/ui/field/field-error.svelte new file mode 100644 index 0000000..326cf59 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/field/field-error.svelte @@ -0,0 +1,58 @@ + + +{#if hasContent} + +{/if} diff --git a/template/apps/web/src/lib/components/ui/field/field-group.svelte b/template/apps/web/src/lib/components/ui/field/field-group.svelte new file mode 100644 index 0000000..3795195 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/field/field-group.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/field/field-label.svelte b/template/apps/web/src/lib/components/ui/field/field-label.svelte new file mode 100644 index 0000000..6d9ec73 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/field/field-label.svelte @@ -0,0 +1,25 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/field/field-legend.svelte b/template/apps/web/src/lib/components/ui/field/field-legend.svelte new file mode 100644 index 0000000..e3d8deb --- /dev/null +++ b/template/apps/web/src/lib/components/ui/field/field-legend.svelte @@ -0,0 +1,24 @@ + + + + {@render children?.()} + diff --git a/template/apps/web/src/lib/components/ui/field/field-separator.svelte b/template/apps/web/src/lib/components/ui/field/field-separator.svelte new file mode 100644 index 0000000..a1ab858 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/field/field-separator.svelte @@ -0,0 +1,35 @@ + + +
    + + {#if children} + + {@render children()} + + {/if} +
    diff --git a/template/apps/web/src/lib/components/ui/field/field-set.svelte b/template/apps/web/src/lib/components/ui/field/field-set.svelte new file mode 100644 index 0000000..27ee330 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/field/field-set.svelte @@ -0,0 +1,20 @@ + + +
    [data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col", className)} + {...restProps} +> + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/field/field-title.svelte b/template/apps/web/src/lib/components/ui/field/field-title.svelte new file mode 100644 index 0000000..f7e2f08 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/field/field-title.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/field/field.svelte b/template/apps/web/src/lib/components/ui/field/field.svelte new file mode 100644 index 0000000..03d23c0 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/field/field.svelte @@ -0,0 +1,47 @@ + + + + +
    + {@render children?.()} +
    diff --git a/template/apps/web/src/lib/components/ui/field/index.ts b/template/apps/web/src/lib/components/ui/field/index.ts new file mode 100644 index 0000000..a644a95 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/field/index.ts @@ -0,0 +1,33 @@ +import Field from "./field.svelte"; +import Set from "./field-set.svelte"; +import Legend from "./field-legend.svelte"; +import Group from "./field-group.svelte"; +import Content from "./field-content.svelte"; +import Label from "./field-label.svelte"; +import Title from "./field-title.svelte"; +import Description from "./field-description.svelte"; +import Separator from "./field-separator.svelte"; +import Error from "./field-error.svelte"; + +export { + Field, + Set, + Legend, + Group, + Content, + Label, + Title, + Description, + Separator, + Error, + // + Set as FieldSet, + Legend as FieldLegend, + Group as FieldGroup, + Content as FieldContent, + Label as FieldLabel, + Title as FieldTitle, + Description as FieldDescription, + Separator as FieldSeparator, + Error as FieldError, +}; diff --git a/template/apps/web/src/lib/components/ui/form/form-button.svelte b/template/apps/web/src/lib/components/ui/form/form-button.svelte new file mode 100644 index 0000000..48d3936 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/form/form-button.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/input-group/input-group-input.svelte b/template/apps/web/src/lib/components/ui/input-group/input-group-input.svelte new file mode 100644 index 0000000..316c0cb --- /dev/null +++ b/template/apps/web/src/lib/components/ui/input-group/input-group-input.svelte @@ -0,0 +1,20 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/input-group/input-group-text.svelte b/template/apps/web/src/lib/components/ui/input-group/input-group-text.svelte new file mode 100644 index 0000000..d6ab315 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/input-group/input-group-text.svelte @@ -0,0 +1,19 @@ + + + + {@render children?.()} + diff --git a/template/apps/web/src/lib/components/ui/input-group/input-group-textarea.svelte b/template/apps/web/src/lib/components/ui/input-group/input-group-textarea.svelte new file mode 100644 index 0000000..9fbabe6 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/input-group/input-group-textarea.svelte @@ -0,0 +1,20 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/toggle-group/index.ts b/template/apps/web/src/lib/components/ui/toggle-group/index.ts new file mode 100644 index 0000000..12b14b9 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/toggle-group/index.ts @@ -0,0 +1,10 @@ +import Root from "./toggle-group.svelte"; +import Item from "./toggle-group-item.svelte"; + +export { + Root, + Item, + // + Root as ToggleGroup, + Item as ToggleGroupItem, +}; diff --git a/template/apps/web/src/lib/components/ui/toggle-group/toggle-group-item.svelte b/template/apps/web/src/lib/components/ui/toggle-group/toggle-group-item.svelte new file mode 100644 index 0000000..904bf70 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/toggle-group/toggle-group-item.svelte @@ -0,0 +1,35 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/toggle-group/toggle-group.svelte b/template/apps/web/src/lib/components/ui/toggle-group/toggle-group.svelte new file mode 100644 index 0000000..3d67cb8 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/toggle-group/toggle-group.svelte @@ -0,0 +1,75 @@ + + + + + + diff --git a/template/apps/web/src/lib/components/ui/toggle/index.ts b/template/apps/web/src/lib/components/ui/toggle/index.ts new file mode 100644 index 0000000..8cb2936 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/toggle/index.ts @@ -0,0 +1,13 @@ +import Root from "./toggle.svelte"; +export { + toggleVariants, + type ToggleSize, + type ToggleVariant, + type ToggleVariants, +} from "./toggle.svelte"; + +export { + Root, + // + Root as Toggle, +}; diff --git a/template/apps/web/src/lib/components/ui/toggle/toggle.svelte b/template/apps/web/src/lib/components/ui/toggle/toggle.svelte new file mode 100644 index 0000000..eca68b1 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/toggle/toggle.svelte @@ -0,0 +1,51 @@ + + + + + diff --git a/template/apps/web/src/lib/components/ui/tooltip/index.ts b/template/apps/web/src/lib/components/ui/tooltip/index.ts new file mode 100644 index 0000000..1718604 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/tooltip/index.ts @@ -0,0 +1,19 @@ +import Root from "./tooltip.svelte"; +import Trigger from "./tooltip-trigger.svelte"; +import Content from "./tooltip-content.svelte"; +import Provider from "./tooltip-provider.svelte"; +import Portal from "./tooltip-portal.svelte"; + +export { + Root, + Trigger, + Content, + Provider, + Portal, + // + Root as Tooltip, + Content as TooltipContent, + Trigger as TooltipTrigger, + Provider as TooltipProvider, + Portal as TooltipPortal, +}; diff --git a/template/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte b/template/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte new file mode 100644 index 0000000..4f27fa3 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,52 @@ + + + + + {@render children?.()} + + {#snippet child({ props })} +
    + {/snippet} +
    +
    +
    diff --git a/template/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte b/template/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte new file mode 100644 index 0000000..d234f7d --- /dev/null +++ b/template/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte b/template/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte new file mode 100644 index 0000000..6dba9a6 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte b/template/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte new file mode 100644 index 0000000..1acdaa4 --- /dev/null +++ b/template/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/template/apps/web/src/lib/components/ui/tooltip/tooltip.svelte b/template/apps/web/src/lib/components/ui/tooltip/tooltip.svelte new file mode 100644 index 0000000..44bee9f --- /dev/null +++ b/template/apps/web/src/lib/components/ui/tooltip/tooltip.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/template/apps/web/src/lib/getconnectrouter.ts b/template/apps/web/src/lib/getconnectrouter.ts new file mode 100644 index 0000000..8ee292c --- /dev/null +++ b/template/apps/web/src/lib/getconnectrouter.ts @@ -0,0 +1,8 @@ +import {createRouter} from '@<@var(context.project.name)>/rpc' + +const router = createRouter("http://127.0.0.1:8080") + +export const getRouter = () => { + return router; +} + diff --git a/template/apps/web/src/lib/hooks/is-mobile.svelte.ts b/template/apps/web/src/lib/hooks/is-mobile.svelte.ts new file mode 100644 index 0000000..4829c00 --- /dev/null +++ b/template/apps/web/src/lib/hooks/is-mobile.svelte.ts @@ -0,0 +1,9 @@ +import { MediaQuery } from "svelte/reactivity"; + +const DEFAULT_MOBILE_BREAKPOINT = 768; + +export class IsMobile extends MediaQuery { + constructor(breakpoint: number = DEFAULT_MOBILE_BREAKPOINT) { + super(`max-width: ${breakpoint - 1}px`); + } +} diff --git a/template/apps/web/src/lib/index.ts b/template/apps/web/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/template/apps/web/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/template/apps/web/src/lib/todocollectionscontext.ts b/template/apps/web/src/lib/todocollectionscontext.ts new file mode 100644 index 0000000..479b6b3 --- /dev/null +++ b/template/apps/web/src/lib/todocollectionscontext.ts @@ -0,0 +1,5 @@ +import { createContext } from "svelte"; +import { type CollectionImpl } from '@tanstack/svelte-db' +import type { Todo } from "@<@var(context.project.name)>/rpc"; +import { type ExtractPayload } from "./utils"; +export const [getTodoCollection,setTodoCollection] = createContext,string>>() diff --git a/template/apps/web/src/lib/utils.ts b/template/apps/web/src/lib/utils.ts new file mode 100644 index 0000000..93ccb61 --- /dev/null +++ b/template/apps/web/src/lib/utils.ts @@ -0,0 +1,14 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; +import {type Message} from "@bufbuild/protobuf" +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null }; +export type ExtractPayload = Omit>; diff --git a/template/apps/web/src/routes/+layout.svelte b/template/apps/web/src/routes/+layout.svelte new file mode 100644 index 0000000..e3dbf37 --- /dev/null +++ b/template/apps/web/src/routes/+layout.svelte @@ -0,0 +1,63 @@ + + + + + + {@render children()} + + +
    + {#each locales as locale} + {locale} + {/each} +
    diff --git a/template/apps/web/src/routes/+page.svelte b/template/apps/web/src/routes/+page.svelte new file mode 100644 index 0000000..ac76563 --- /dev/null +++ b/template/apps/web/src/routes/+page.svelte @@ -0,0 +1,11 @@ + + +
    + + +
    diff --git a/template/apps/web/src/routes/+page.ts b/template/apps/web/src/routes/+page.ts new file mode 100644 index 0000000..0b79dc8 --- /dev/null +++ b/template/apps/web/src/routes/+page.ts @@ -0,0 +1,7 @@ +import type { PageLoad } from './$types' +import { getRouter } from "$lib/getconnectrouter" +export const load: PageLoad = async () => { + const router = getRouter(); + const todos = await router.todos.listTodos({}) + return {todos: todos.todos}; +} diff --git a/template/apps/web/src/routes/layout.css b/template/apps/web/src/routes/layout.css new file mode 100644 index 0000000..00bce42 --- /dev/null +++ b/template/apps/web/src/routes/layout.css @@ -0,0 +1,130 @@ +@import 'tailwindcss'; +@import "tw-animate-css"; +@import "shadcn-svelte/tailwind.css"; +@import "@fontsource-variable/roboto"; + +@custom-variant dark (&:is(.dark *)); +@plugin '@tailwindcss/typography'; + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.148 0.004 228.8); + --card: oklch(1 0 0); + --card-foreground: oklch(0.148 0.004 228.8); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.148 0.004 228.8); + --primary: oklch(0.511 0.096 186.391); + --primary-foreground: oklch(0.984 0.014 180.72); + --secondary: oklch(0.967 0.001 286.375); + --secondary-foreground: oklch(0.21 0.006 285.885); + --muted: oklch(0.963 0.002 197.1); + --muted-foreground: oklch(0.56 0.021 213.5); + --accent: oklch(0.963 0.002 197.1); + --accent-foreground: oklch(0.218 0.008 223.9); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.925 0.005 214.3); + --input: oklch(0.925 0.005 214.3); + --ring: oklch(0.723 0.014 214.4); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --radius: 0.625rem; + --sidebar: oklch(0.987 0.002 197.1); + --sidebar-foreground: oklch(0.148 0.004 228.8); + --sidebar-primary: oklch(0.6 0.118 184.704); + --sidebar-primary-foreground: oklch(0.984 0.014 180.72); + --sidebar-accent: oklch(0.963 0.002 197.1); + --sidebar-accent-foreground: oklch(0.218 0.008 223.9); + --sidebar-border: oklch(0.925 0.005 214.3); + --sidebar-ring: oklch(0.723 0.014 214.4); +} + +.dark { + --background: oklch(0.148 0.004 228.8); + --foreground: oklch(0.987 0.002 197.1); + --card: oklch(0.218 0.008 223.9); + --card-foreground: oklch(0.987 0.002 197.1); + --popover: oklch(0.218 0.008 223.9); + --popover-foreground: oklch(0.987 0.002 197.1); + --primary: oklch(0.437 0.078 188.216); + --primary-foreground: oklch(0.984 0.014 180.72); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.275 0.011 216.9); + --muted-foreground: oklch(0.723 0.014 214.4); + --accent: oklch(0.275 0.011 216.9); + --accent-foreground: oklch(0.987 0.002 197.1); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.56 0.021 213.5); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --sidebar: oklch(0.218 0.008 223.9); + --sidebar-foreground: oklch(0.987 0.002 197.1); + --sidebar-primary: oklch(0.704 0.14 182.503); + --sidebar-primary-foreground: oklch(0.277 0.046 192.524); + --sidebar-accent: oklch(0.275 0.011 216.9); + --sidebar-accent-foreground: oklch(0.987 0.002 197.1); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.56 0.021 213.5); +} + +@theme inline { + --font-sans: 'Roboto Variable', sans-serif; + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-foreground: var(--foreground); + --color-background: var(--background); + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + html { + @apply font-sans; + } +} diff --git a/template/apps/web/static/robots.txt b/template/apps/web/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/template/apps/web/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/template/apps/web/svelte.config.js b/template/apps/web/svelte.config.js new file mode 100644 index 0000000..58d330b --- /dev/null +++ b/template/apps/web/svelte.config.js @@ -0,0 +1,24 @@ +import adapter from '@sveltejs/adapter-auto'; +import { relative, sep } from 'node:path'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + compilerOptions: { + // defaults to rune mode for the project, except for `node_modules`. Can be removed in svelte 6. + runes: ({ filename }) => { + const relativePath = relative(import.meta.dirname, filename); + const pathSegments = relativePath.toLowerCase().split(sep); + const isExternalLibrary = pathSegments.includes('node_modules'); + + return isExternalLibrary ? undefined : true; + } + }, + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter() + } +}; + +export default config; diff --git a/template/apps/web/tsconfig.json b/template/apps/web/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/template/apps/web/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/template/apps/web/vite.config.ts b/template/apps/web/vite.config.ts new file mode 100644 index 0000000..0fbc1a4 --- /dev/null +++ b/template/apps/web/vite.config.ts @@ -0,0 +1,12 @@ +import { paraglideVitePlugin } from '@inlang/paraglide-js'; +import tailwindcss from '@tailwindcss/vite'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ + tailwindcss(), + sveltekit(), + paraglideVitePlugin({ project: './project.inlang', outdir: './src/lib/paraglide' }) + ] +}); diff --git a/template/devenv.nix b/template/devenv.nix new file mode 100644 index 0000000..a33efe6 --- /dev/null +++ b/template/devenv.nix @@ -0,0 +1,52 @@ +{ pkgs, lib, config, inputs, ... }: + +{ + packages = [ + pkgs.bun + pkgs.watchexec + pkgs.sqlc + pkgs.dbmate + pkgs.air + pkgs.buf + pkgs.protoc-gen-go + pkgs.protoc-gen-connect-go + pkgs.protoc-gen-es + pkgs.cobra-cli + ]; + + languages.go.enable = true; + languages.typescript.enable = true; + services.postgres = { + enable = true; + listen_addresses = "127.0.0.1"; + initialDatabases = [ + { + name = "<@var(context.project.name)>"; + user = "pp"; + pass = "<@var(context.project.name)>"; + } + ]; + }; + processes = { + air = { + exec = "air"; + cwd = "./services/api"; + }; + protowatcher = { + exec = "watchexec -r -e proto buf generate"; + cwd = "./packages/proto"; + }; + protojswatcher = { + exec = "watchexec -e js,ts -w ./packages/rpc/src -r bun run ./scripts/gen-rpc-index.ts"; + cwd = "./"; + }; + sqlwatcher = { + exec = "watchexec -w -r db -e sql sqlc generate"; + cwd = "./services/api"; + }; + bundev = { + exec = "bun dev"; + cwd = "./apps/web"; + }; + }; +} diff --git a/template/devenv.yaml b/template/devenv.yaml new file mode 100644 index 0000000..116a2ad --- /dev/null +++ b/template/devenv.yaml @@ -0,0 +1,15 @@ +# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json +inputs: + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling + +# If you're using non-OSS software, you can set allowUnfree to true. +# allowUnfree: true + +# If you're willing to use a package that's vulnerable +# permittedInsecurePackages: +# - "openssl-1.1.1w" + +# If you have more than one devenv you can merge them +#imports: +# - ./backend diff --git a/template/package.json b/template/package.json new file mode 100644 index 0000000..8c3b883 --- /dev/null +++ b/template/package.json @@ -0,0 +1,12 @@ +{ + "name": "<@var(context.project.name)>", + "private": true, + "workspaces": [ + "apps/*", + "packages/*" + ], + "scripts": { + "dev": "bun run --filter '*' dev", + "build": "bun run --filter '*' build" + } +} diff --git a/template/packages/proto/buf.gen.yaml b/template/packages/proto/buf.gen.yaml new file mode 100644 index 0000000..21a4a30 --- /dev/null +++ b/template/packages/proto/buf.gen.yaml @@ -0,0 +1,15 @@ +version: v2 +plugins: + - local: protoc-gen-go + out: ../../services/api/gen + opt: paths=source_relative + - local: protoc-gen-connect-go + opt: + - paths=source_relative + out: ../../services/api/gen + + # TypeScript + - local: protoc-gen-es + out: ../rpc/src + include_imports: true + opt: target=ts diff --git a/template/packages/proto/buf.yaml b/template/packages/proto/buf.yaml new file mode 100644 index 0000000..00c96c2 --- /dev/null +++ b/template/packages/proto/buf.yaml @@ -0,0 +1,9 @@ +version: v2 +deps: + - buf.build/bufbuild/protovalidate +lint: + use: + - STANDARD +breaking: + use: + - FILE diff --git a/template/packages/proto/todo/v1/todo.proto b/template/packages/proto/todo/v1/todo.proto new file mode 100644 index 0000000..af80c1b --- /dev/null +++ b/template/packages/proto/todo/v1/todo.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; +package todo.v1; + +option go_package = "<@var(context.project.goprefix)>/<@var(context.project.name)>/gen/todo/v1;todov1"; + +service TodoService { + rpc CreateTodo (CreateTodoRequest) returns (CreateTodoResponse); + rpc ListTodos (ListTodosRequest) returns (ListTodosResponse); + rpc UpdateTodo (UpdateTodoRequest) returns (UpdateTodoResponse); + rpc DeleteTodo (DeleteTodoRequest) returns (DeleteTodoResponse); +} + +message Todo { + optional string id = 1; + string task = 2; + optional string created_at = 3; + optional string updates_at = 4; + optional bool done = 5; + +} + +message DeleteTodoRequest { + Todo todo = 1; +} + +message DeleteTodoResponse {} + +message CreateTodoRequest { + Todo todo = 1; +} + +message CreateTodoResponse { + Todo todo = 1; +} +message ListTodosRequest { +} + +message ListTodosResponse { + repeated Todo todos = 1; +} + +message UpdateTodoRequest { + Todo todo = 1; +} + +message UpdateTodoResponse { + Todo todo = 1; +} + diff --git a/template/packages/rpc/.gitignore b/template/packages/rpc/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/template/packages/rpc/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/template/packages/rpc/CLAUDE.md b/template/packages/rpc/CLAUDE.md new file mode 100644 index 0000000..764c1dd --- /dev/null +++ b/template/packages/rpc/CLAUDE.md @@ -0,0 +1,106 @@ + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; +import { createRoot } from "react-dom/client"; + +// import .css files directly and it works +import './index.css'; + +const root = createRoot(document.body); + +export default function Frontend() { + return

    Hello, world!

    ; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`. diff --git a/template/packages/rpc/README.md b/template/packages/rpc/README.md new file mode 100644 index 0000000..12949a3 --- /dev/null +++ b/template/packages/rpc/README.md @@ -0,0 +1,15 @@ +# rpc + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.10. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/template/packages/rpc/package.json b/template/packages/rpc/package.json new file mode 100644 index 0000000..d2e1902 --- /dev/null +++ b/template/packages/rpc/package.json @@ -0,0 +1,22 @@ +{ + "name": "@<@var(context.project.name)>/rpc", + "type": "module", + "main": "index.ts", + "exports": { + ".": { + "import": "./index.ts", + "types": "./index.ts" + } + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "@bufbuild/protobuf": "^2.11.0", + "@connectrpc/connect": "^2.1.1", + "@connectrpc/connect-web": "^2.1.1" + } +} diff --git a/template/packages/rpc/tsconfig.json b/template/packages/rpc/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/template/packages/rpc/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/template/scripts/.helix/languages.toml b/template/scripts/.helix/languages.toml new file mode 100644 index 0000000..1813968 --- /dev/null +++ b/template/scripts/.helix/languages.toml @@ -0,0 +1,4 @@ +[[language]] +name="typescript" +roots = ["package.json"] +language-servers = ["typescript-language-server"] diff --git a/template/scripts/gen-rpc-index.ts b/template/scripts/gen-rpc-index.ts new file mode 100644 index 0000000..0bdb137 --- /dev/null +++ b/template/scripts/gen-rpc-index.ts @@ -0,0 +1,324 @@ +import ts, { StringLiteral } from "typescript"; +import { resolve, relative, dirname, join } from "path"; +import { Glob } from "bun"; + +const RPC_PKG = resolve(import.meta.dirname!, "../packages/rpc"); +const SRC_DIR = join(RPC_PKG, "src"); +const INDEX_FILE = join(RPC_PKG, "index.ts"); + +const glob = new Glob("**/*_pb.ts"); +const files = Array.from(glob.scanSync({ cwd: SRC_DIR, absolute: true })).sort(); + +if (files.length === 0) { + console.error("No _pb.ts files found in", SRC_DIR); + process.exit(1); +} + +// Read the rpc package's tsconfig so module resolution finds @bufbuild/protobuf +const tsconfigPath = join(RPC_PKG, "tsconfig.json"); +const { config } = ts.readConfigFile(tsconfigPath, ts.sys.readFile); +const parsed = ts.parseJsonConfigFileContent(config, ts.sys, RPC_PKG); + +const program = ts.createProgram(files, { + ...parsed.options, + noEmit: true, +}); + +const checker = program.getTypeChecker(); +const factory = ts.factory; + +function hasTypeNamed(type: ts.Type, name: string): boolean { + if (type.aliasSymbol?.name === name) return true; + if (type.symbol?.name === name) return true; + const target = (type as any).target as ts.Type | undefined; + if (target?.symbol?.name === name) return true; + if (type.isIntersection()) { + return type.types.some((t) => hasTypeNamed(t, name)); + } + if (type.isUnion()) { + return type.types.some((t) => hasTypeNamed(t, name)); + } + return false; +} + +const isStringLiteral = (s: string | StringLiteral | undefined): s is StringLiteral => { + if (s === undefined) { + return false; + } + return (s as StringLiteral).forEachChild != undefined; +} + +const serviceNameToRouterPropertyName = (n:string) : string => { + return n.toLowerCase().replace('service','') + 's' +} + +const exportDecl = (exp: Map, typesOnly:boolean = false, from?: string | StringLiteral) => { + return factory.createExportDeclaration( + undefined, + typesOnly, + factory.createNamedExports( + Array.from(exp.entries()).map((exp) => { + return factory.createExportSpecifier( + exp[1], + undefined, + exp[0] + ) + }) + ), + ( + isStringLiteral(from) ? + from : + ( + from != undefined ? + factory.createStringLiteral(from) : + from + ) + ) + ) +} + +const importDecl = (imp: Map, from: string | StringLiteral, typesOnly: boolean = false) => { + return factory.createImportDeclaration( + undefined, + factory.createImportClause( + typesOnly, + undefined, + factory.createNamedImports( + Array.from(imp.entries()).map((k) => { + return factory.createImportSpecifier( + k[1], + undefined, + factory.createIdentifier(k[0]) + ) + }) + ) + ), + (isStringLiteral(from) ? from : factory.createStringLiteral(from)) + ) +} + +const statements: ts.Statement[] = []; +const serviceNames: string[] = []; +const typeNames: string[] = []; + +for (const filePath of files) { + const sourceFile = program.getSourceFile(filePath); + if (!sourceFile) continue; + + const symbol = checker.getSymbolAtLocation(sourceFile); + if (!symbol) continue; + + const exports = checker.getExportsOfModule(symbol); + + const fileServiceNames: string[] = []; + const fileTypeNames: string[] = []; + + for (const exp of exports) { + const declarations = exp.getDeclarations(); + if (!declarations || declarations.length === 0) continue; + + const decl = declarations[0]; + + if (ts.isTypeAliasDeclaration(decl) || ts.isInterfaceDeclaration(decl)) { + const type = checker.getDeclaredTypeOfSymbol(exp); + if (hasTypeNamed(type, "Message")) { + fileTypeNames.push(exp.getName()); + } + } else { + const type = checker.getTypeOfSymbolAtLocation(exp, decl); + if (hasTypeNamed(type, "GenService")) { + fileServiceNames.push(exp.getName()); + } + } + } + + let modulePath = "./" + relative(dirname(INDEX_FILE), filePath); + modulePath = modulePath.replace(/\.ts$/, ""); + + const moduleSpecifier = factory.createStringLiteral(modulePath); + statements.push( + importDecl( + new Map([ + ["Client", true], + ["createClient", false] + ]), + "@connectrpc/connect" + ) + ) + statements.push( + importDecl( + new Map([ + ["createConnectTransport", false] + ]), + "@connectrpc/connect-web" + ) + ) + if (fileServiceNames.length > 0) { + serviceNames.push(...fileServiceNames) + statements.push(importDecl( + new Map( + fileServiceNames.map((v) => { + return [v, false] as const; + }) + ), + moduleSpecifier + )) + } + + if (fileTypeNames.length > 0) { + typeNames.push(...fileTypeNames) + statements.push(importDecl( + new Map( + fileTypeNames.map((v) => { + return [v, false] as const; + }) + ), + moduleSpecifier, + true + )) + } +} + +statements.push( + exportDecl( + new Map(serviceNames.map((service) => { + return [service, false] as const; + })) + ) +) + +statements.push( + exportDecl( + new Map(typeNames.map((type) => { + return [type, false] as const; + })), + true + ) +) + +statements.push( + factory.createInterfaceDeclaration( + undefined, + factory.createIdentifier("Router"), + undefined, + undefined, + serviceNames.map((service) => { + return factory.createPropertySignature( + undefined, + serviceNameToRouterPropertyName(service), + undefined, + factory.createTypeReferenceNode( + factory.createIdentifier("Client"), + [ + factory.createTypeQueryNode( + factory.createIdentifier(service) + ) + ] + ) + ) + }) + ) +) + +statements.push(exportDecl( + new Map([ + ['Router', true] + ]) +)) + +statements.push(factory.createFunctionDeclaration( + undefined, + undefined, + 'createRouter', + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + 'baseUrl', + undefined, + factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + undefined + ) + ], + factory.createTypeReferenceNode(factory.createIdentifier("Router"), undefined), + factory.createBlock( + [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + 'transport', + undefined, + undefined, + factory.createCallExpression( + factory.createIdentifier("createConnectTransport"), + undefined, + [ + factory.createObjectLiteralExpression([ + factory.createPropertyAssignment( + 'baseUrl', + factory.createIdentifier('baseUrl') + ) + ]) + ] + ) + ), + ], + ts.NodeFlags.Const + ), + ), + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + 'router', + undefined, + undefined, + factory.createObjectLiteralExpression( + [ + ...serviceNames.map((service) => { + return factory.createPropertyAssignment( + serviceNameToRouterPropertyName(service), + + factory.createCallExpression( + factory.createIdentifier('createClient'), + undefined, + [ + factory.createIdentifier(service), + factory.createIdentifier('transport') + ] + ) + ) + }) + ] + ) + ) + ], + ts.NodeFlags.Const + ) + ), + factory.createReturnStatement( + factory.createIdentifier('router') + ) + ], + true + ) +)) + +statements.push(exportDecl(new Map([ + ['createRouter', false] +]))) + +const outputFile = ts.createSourceFile(INDEX_FILE, "", ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS); +const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + +const header = "// @generated by scripts/gen-rpc-index.ts — do not edit manually\n\n"; +const body = statements + .map((s) => printer.printNode(ts.EmitHint.Unspecified, s, outputFile)) + .join("\n"); + +await Bun.write(INDEX_FILE, header + body + "\n"); +console.log(`Wrote ${INDEX_FILE}`); diff --git a/template/scripts/package.json b/template/scripts/package.json new file mode 100644 index 0000000..6d062bb --- /dev/null +++ b/template/scripts/package.json @@ -0,0 +1,11 @@ +{ + "name": "@<@var(context.project.name)>/scripts", + "private": true, + "type": "module", + "dependencies": { + "typescript": "^5" + }, + "devDependencies": { + "@types/bun": "^1.3.11" + } +} diff --git a/template/services/api/.air.toml b/template/services/api/.air.toml new file mode 100644 index 0000000..9a2e4c2 --- /dev/null +++ b/template/services/api/.air.toml @@ -0,0 +1,58 @@ +#:schema https://json.schemastore.org/any.json + +env_files = [] +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ." + delay = 1000 + entrypoint = ["./tmp/main","serve"] + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + ignore_dangerous_root_dir = false + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + silent = false + time = false + +[misc] + clean_on_exit = false + +[proxy] + app_port = 0 + app_start_timeout = 0 + enabled = false + proxy_port = 0 + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/template/services/api/.gitignore b/template/services/api/.gitignore new file mode 100644 index 0000000..a9a5aec --- /dev/null +++ b/template/services/api/.gitignore @@ -0,0 +1 @@ +tmp diff --git a/template/services/api/LICENSE b/template/services/api/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/template/services/api/cmd/configInit.go b/template/services/api/cmd/configInit.go new file mode 100644 index 0000000..540d2a1 --- /dev/null +++ b/template/services/api/cmd/configInit.go @@ -0,0 +1,45 @@ +/* +Copyright © 2026 NAME HERE +*/ +package cmd + +import ( + "fmt" + "os" + + "<@var(context.project.goprefix)>/<@var(context.project.name)>/config" + "github.com/spf13/cobra" +) + +// configInitCmd represents the configInit command +var configInitCmd = &cobra.Command{ + Use: "configInit", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + err := config.Write(*config.DefaultConf) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + }, +} + +func init() { + rootCmd.AddCommand(configInitCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // configInitCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // configInitCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/template/services/api/cmd/migrate.go b/template/services/api/cmd/migrate.go new file mode 100644 index 0000000..beee0d0 --- /dev/null +++ b/template/services/api/cmd/migrate.go @@ -0,0 +1,37 @@ +/* +Copyright © 2026 NAME HERE +*/ +package cmd + +import ( + "<@var(context.project.goprefix)>/<@var(context.project.name)>/db" + "github.com/spf13/cobra" +) + +// migrateCmd represents the migrate command +var migrateCmd = &cobra.Command{ + Use: "migrate", + Short: "run database migrations", + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + err := db.RunDbMigrations() + if err != nil { + return err + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(migrateCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // migrateCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // migrateCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/template/services/api/cmd/root.go b/template/services/api/cmd/root.go new file mode 100644 index 0000000..6895c57 --- /dev/null +++ b/template/services/api/cmd/root.go @@ -0,0 +1,45 @@ +/* +Copyright © 2026 NAME HERE +*/ +package cmd + +import ( + "github.com/spf13/cobra" + "os" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "<@var(context.project.name)>", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.<@var(context.project.name)>.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/template/services/api/cmd/serve.go b/template/services/api/cmd/serve.go new file mode 100644 index 0000000..42e0d3e --- /dev/null +++ b/template/services/api/cmd/serve.go @@ -0,0 +1,81 @@ +/* +Copyright © 2026 NAME HERE +*/ +package cmd + +import ( + "<@var(context.project.goprefix)>/<@var(context.project.name)>/config" + "<@var(context.project.goprefix)>/<@var(context.project.name)>/db" + "<@var(context.project.goprefix)>/<@var(context.project.name)>/server/todo" + connectors "connectrpc.com/cors" + "context" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/rs/cors" + "github.com/spf13/cobra" + "net/http" + "strconv" + "strings" +) + +// serveCmd represents the serve command +var serveCmd = &cobra.Command{ + Use: "serve", + Short: "start server", + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + err := config.Load() + if err != nil { + err := config.Write(*config.DefaultConf) + if err != nil { + return err + } + } + conf := *config.Conf + ctx := context.Background() + db_url := "postgres://" + conf.Database.User + ":" + conf.Database.Password + "@" + conf.Database.Host + ":" + strconv.Itoa(conf.Database.Port) + "/" + conf.Database.Name + db_url = db_url + "?sslmode=disable" + conn, err := pgxpool.New(ctx, db_url) + if err != nil { + panic(err.Error()) + } + err = db.RunDbMigrations() + if err != nil { + return err + } + db.Q = db.New(conn) + middleware := cors.New(cors.Options{ + AllowedOrigins: conf.Server.FrontendUrls, + AllowedMethods: connectors.AllowedMethods(), + AllowedHeaders: connectors.AllowedHeaders(), + ExposedHeaders: connectors.ExposedHeaders(), + Debug: true, + }) + mux := http.NewServeMux() + todoPath, todoHandler := todo.GetPathHandler() + mux.Handle(todoPath, todoHandler) + p := new(http.Protocols) + p.SetHTTP1(true) + p.SetUnencryptedHTTP2(true) + s := http.Server{ + Addr: strings.Join([]string{conf.Server.Host, strconv.Itoa(conf.Server.Port)}, ":"), + Handler: middleware.Handler(mux), + Protocols: p, + } + s.ListenAndServe() + return nil + }, +} + +func init() { + rootCmd.AddCommand(serveCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // serveCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // serveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/template/services/api/config/config.go b/template/services/api/config/config.go new file mode 100644 index 0000000..3bb7e9d --- /dev/null +++ b/template/services/api/config/config.go @@ -0,0 +1,81 @@ +package config + +import ( + "github.com/adrg/xdg" + "github.com/pelletier/go-toml" + "os" + "path/filepath" +) + +var confPath string +var Conf *Config +var DefaultConf *Config + +func init() { + confPath = filepath.Join(xdg.ConfigHome, "<@var(context.project.name)>", "config.toml") + Conf = &Config{} + DefaultConf = &Config{ + Server: Server{ + Host: "127.0.0.1", + Port: 8080, + FrontendUrls: []string{"http://localhost:5173"}, + }, + Database: Database{ + User: "pp", + Password: "<@var(context.project.name)>", + Host: "127.0.0.1", + Port: 5432, + Name: "<@var(context.project.name)>", + }, + } +} + +func Load() error { + confContent, err := os.ReadFile(confPath) + if err != nil { + return err + } + return toml.Unmarshal(confContent, Conf) + +} + +func Write(config Config) error { + confContent, err := toml.Marshal(config) + if err != nil { + return err + } + + dir := filepath.Dir(confPath) + + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + + if err := os.WriteFile(confPath, confContent, 0644); err != nil { + return err + } + + if err := Load(); err != nil { + return err + } + return nil +} + +type Server struct { + Host string + Port int + FrontendUrls []string +} + +type Database struct { + User string + Password string + Host string + Port int + Name string +} + +type Config struct { + Server Server + Database Database +} diff --git a/template/services/api/db/db.go b/template/services/api/db/db.go new file mode 100644 index 0000000..9d485b5 --- /dev/null +++ b/template/services/api/db/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/template/services/api/db/dbfs.go b/template/services/api/db/dbfs.go new file mode 100644 index 0000000..6078ddf --- /dev/null +++ b/template/services/api/db/dbfs.go @@ -0,0 +1,6 @@ +package db + +import "embed" + +//go:embed migrations/*.sql +var MigrationsFs embed.FS diff --git a/template/services/api/db/lib.go b/template/services/api/db/lib.go new file mode 100644 index 0000000..c50948e --- /dev/null +++ b/template/services/api/db/lib.go @@ -0,0 +1,60 @@ +package db + +import ( + "<@var(context.project.goprefix)>/<@var(context.project.name)>/config" + "github.com/amacneil/dbmate/v2/pkg/dbmate" + _ "github.com/amacneil/dbmate/v2/pkg/driver/postgres" + "log" + "net/url" + "strconv" + "strings" +) + +var Q *Queries + +func RunDbMigrations() error { + + err := config.Load() + if err != nil { + return err + } + conf := *config.Conf + dbUrl, err := url.Parse(strings.Join([]string{ + "postgresql://", + conf.Database.User, + ":", + conf.Database.Password, + "@", + conf.Database.Host, + ":", + strconv.Itoa(conf.Database.Port), + "/", + conf.Database.Name, + }, "")) + if err != nil { + return err + } + db := dbmate.New(dbUrl) + db.FS = MigrationsFs + db.MigrationsDir = []string{"migrations"} + migrations, err := db.FindMigrations() + if err != nil { + return err + } + for _, m := range migrations { + log.Default().Println(m.Version, m.FilePath) + } + db.AutoDumpSchema = false + log.Default().Println("\nApplying...") + err = db.CreateAndMigrate() + if err != nil { + log.Default().Println(err.Error()) + } + err = db.DumpSchema() + if err != nil { + return err + } + + return nil + +} diff --git a/template/services/api/db/migrations/20260404121052_init.sql b/template/services/api/db/migrations/20260404121052_init.sql new file mode 100644 index 0000000..eaf1dc6 --- /dev/null +++ b/template/services/api/db/migrations/20260404121052_init.sql @@ -0,0 +1,10 @@ +-- migrate:up +CREATE TABLE todo ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + task VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + done bool DEFAULT false +); +-- migrate:down +DROP TABLE todo; diff --git a/template/services/api/db/models.go b/template/services/api/db/models.go new file mode 100644 index 0000000..f098474 --- /dev/null +++ b/template/services/api/db/models.go @@ -0,0 +1,17 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package db + +import ( + "github.com/jackc/pgx/v5/pgtype" +) + +type Todo struct { + ID pgtype.UUID + Task string + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp + Done pgtype.Bool +} diff --git a/template/services/api/db/query/todo.sql b/template/services/api/db/query/todo.sql new file mode 100644 index 0000000..027107d --- /dev/null +++ b/template/services/api/db/query/todo.sql @@ -0,0 +1,14 @@ +-- name: CreateTodo :one +insert into todo (id,task) values ($1,$2) returning *; + +-- name: ListTodos :many +select * from todo; + +-- name: GetTodo :one +select * from todo where id = $1 limit 1; + +-- name: UpdateTodo :one +update todo set task = $1, done = $2 where id = $3 returning *; + +-- name: DeleteTodo :exec +delete from todo where id = $1; diff --git a/template/services/api/db/schema.sql b/template/services/api/db/schema.sql new file mode 100644 index 0000000..9c3f874 --- /dev/null +++ b/template/services/api/db/schema.sql @@ -0,0 +1,86 @@ +\restrict dbmate + +-- Dumped from database version 17.9 +-- Dumped by pg_dump version 17.9 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public; + + +-- +-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions'; + + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.schema_migrations ( + version character varying NOT NULL +); + + +-- +-- Name: todo; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.todo ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + task character varying(255) NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + done boolean DEFAULT false +); + + +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: todo todo_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.todo + ADD CONSTRAINT todo_pkey PRIMARY KEY (id); + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict dbmate + + +-- +-- Dbmate schema migrations +-- + +INSERT INTO public.schema_migrations (version) VALUES + ('20260404121052'); diff --git a/template/services/api/db/todo.sql.go b/template/services/api/db/todo.sql.go new file mode 100644 index 0000000..9f52618 --- /dev/null +++ b/template/services/api/db/todo.sql.go @@ -0,0 +1,113 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: todo.sql + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createTodo = `-- name: CreateTodo :one +insert into todo (id,task) values ($1,$2) returning id, task, created_at, updated_at, done +` + +type CreateTodoParams struct { + ID pgtype.UUID + Task string +} + +func (q *Queries) CreateTodo(ctx context.Context, arg CreateTodoParams) (Todo, error) { + row := q.db.QueryRow(ctx, createTodo, arg.ID, arg.Task) + var i Todo + err := row.Scan( + &i.ID, + &i.Task, + &i.CreatedAt, + &i.UpdatedAt, + &i.Done, + ) + return i, err +} + +const deleteTodo = `-- name: DeleteTodo :exec +delete from todo where id = $1 +` + +func (q *Queries) DeleteTodo(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, deleteTodo, id) + return err +} + +const getTodo = `-- name: GetTodo :one +select id, task, created_at, updated_at, done from todo where id = $1 limit 1 +` + +func (q *Queries) GetTodo(ctx context.Context, id pgtype.UUID) (Todo, error) { + row := q.db.QueryRow(ctx, getTodo, id) + var i Todo + err := row.Scan( + &i.ID, + &i.Task, + &i.CreatedAt, + &i.UpdatedAt, + &i.Done, + ) + return i, err +} + +const listTodos = `-- name: ListTodos :many +select id, task, created_at, updated_at, done from todo +` + +func (q *Queries) ListTodos(ctx context.Context) ([]Todo, error) { + rows, err := q.db.Query(ctx, listTodos) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Todo + for rows.Next() { + var i Todo + if err := rows.Scan( + &i.ID, + &i.Task, + &i.CreatedAt, + &i.UpdatedAt, + &i.Done, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateTodo = `-- name: UpdateTodo :one +update todo set task = $1, done = $2 where id = $3 returning id, task, created_at, updated_at, done +` + +type UpdateTodoParams struct { + Task string + Done pgtype.Bool + ID pgtype.UUID +} + +func (q *Queries) UpdateTodo(ctx context.Context, arg UpdateTodoParams) (Todo, error) { + row := q.db.QueryRow(ctx, updateTodo, arg.Task, arg.Done, arg.ID) + var i Todo + err := row.Scan( + &i.ID, + &i.Task, + &i.CreatedAt, + &i.UpdatedAt, + &i.Done, + ) + return i, err +} diff --git a/template/services/api/go.mod b/template/services/api/go.mod new file mode 100644 index 0000000..d8f71d5 --- /dev/null +++ b/template/services/api/go.mod @@ -0,0 +1,34 @@ +module <@var(context.project.goprefix)>/<@var(context.project.name)> + +go 1.25.7 + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.9-20250912141014-52f32327d4b0.1 // indirect + buf.build/go/protovalidate v1.0.0 // indirect + cel.dev/expr v0.24.0 // indirect + connectrpc.com/connect v1.19.1 // indirect + connectrpc.com/cors v0.1.0 // indirect + connectrpc.com/validate v0.6.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/amacneil/dbmate/v2 v2.32.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/google/cel-go v0.26.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.9.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/lib/pq v1.12.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/stoewer/go-strcase v1.3.1 // indirect + golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/template/services/api/go.sum b/template/services/api/go.sum new file mode 100644 index 0000000..331b399 --- /dev/null +++ b/template/services/api/go.sum @@ -0,0 +1,79 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.9-20250912141014-52f32327d4b0.1 h1:DQLS/rRxLHuugVzjJU5AvOwD57pdFl9he/0O7e5P294= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.9-20250912141014-52f32327d4b0.1/go.mod h1:aY3zbkNan5F+cGm9lITDP6oxJIwu0dn9KjJuJjWaHkg= +buf.build/go/protovalidate v1.0.0 h1:IAG1etULddAy93fiBsFVhpj7es5zL53AfB/79CVGtyY= +buf.build/go/protovalidate v1.0.0/go.mod h1:KQmEUrcQuC99hAw+juzOEAmILScQiKBP1Oc36vvCLW8= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= +connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= +connectrpc.com/cors v0.1.0 h1:f3gTXJyDZPrDIZCQ567jxfD9PAIpopHiRDnJRt3QuOQ= +connectrpc.com/cors v0.1.0/go.mod h1:v8SJZCPfHtGH1zsm+Ttajpozd4cYIUryl4dFB6QEpfg= +connectrpc.com/validate v0.6.0 h1:DcrgDKt2ZScrUs/d/mh9itD2yeEa0UbBBa+i0mwzx+4= +connectrpc.com/validate v0.6.0/go.mod h1:ihrpI+8gVbLH1fvVWJL1I3j0CfWnF8P/90LsmluRiZs= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/amacneil/dbmate/v2 v2.32.0 h1:DJWt+NRWlphTv1HXWWcAU7gsJKm/Ov8/+8k6yqLz8vA= +github.com/amacneil/dbmate/v2 v2.32.0/go.mod h1:Kwax92bT+ZgqIXll9M+0QGuHVuu7O7AhsE+5p6PTqDc= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= +github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= +github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= +github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/template/services/api/main.go b/template/services/api/main.go new file mode 100644 index 0000000..3dbb805 --- /dev/null +++ b/template/services/api/main.go @@ -0,0 +1,11 @@ +/* +Copyright © 2026 NAME HERE + +*/ +package main + +import "<@var(context.project.goprefix)>/<@var(context.project.name)>/cmd" + +func main() { + cmd.Execute() +} diff --git a/template/services/api/server/todo/todo.go b/template/services/api/server/todo/todo.go new file mode 100644 index 0000000..d27d690 --- /dev/null +++ b/template/services/api/server/todo/todo.go @@ -0,0 +1,116 @@ +package todo + +import ( + "connectrpc.com/connect" + "connectrpc.com/validate" + "context" + "<@var(context.project.goprefix)>/<@var(context.project.name)>/db" + todov1 "<@var(context.project.goprefix)>/<@var(context.project.name)>/gen/todo/v1" + "<@var(context.project.goprefix)>/<@var(context.project.name)>/gen/todo/v1/todov1connect" + . "<@var(context.project.goprefix)>/<@var(context.project.name)>/utils" + "github.com/jackc/pgx/v5/pgtype" + "net/http" + "time" +) + +type TodoServer struct{} + +func (srv *TodoServer) CreateTodo(ctx context.Context, req *connect.Request[todov1.CreateTodoRequest]) (*connect.Response[todov1.CreateTodoResponse], error) { + var id pgtype.UUID + err := id.Scan(*req.Msg.Todo.Id) + if err != nil { + return nil, err + } + todo, err := db.Q.CreateTodo(ctx, db.CreateTodoParams{ + ID: id, + Task: req.Msg.Todo.Task, + }) + if err != nil { + return nil, err + } + return &connect.Response[todov1.CreateTodoResponse]{ + Msg: &todov1.CreateTodoResponse{ + Todo: &todov1.Todo{ + Id: StrPtr(todo.ID.String()), + Task: todo.Task, + CreatedAt: StrPtr(todo.CreatedAt.Time.Format(time.RFC3339)), + UpdatesAt: StrPtr(todo.UpdatedAt.Time.Format(time.RFC3339)), + Done: BoolPtr(todo.Done.Bool), + }, + }, + }, nil +} + +func (srv *TodoServer) ListTodos(ctx context.Context, req *connect.Request[todov1.ListTodosRequest]) (*connect.Response[todov1.ListTodosResponse], error) { + todos, err := db.Q.ListTodos(ctx) + if err != nil { + return nil, err + } + reponseTodos := []*todov1.Todo{} + for _, todo := range todos { + reponseTodos = append(reponseTodos, &todov1.Todo{ + Id: StrPtr(todo.ID.String()), + Task: todo.Task, + CreatedAt: StrPtr(todo.CreatedAt.Time.Format(time.RFC3339)), + UpdatesAt: StrPtr(todo.UpdatedAt.Time.Format(time.RFC3339)), + Done: BoolPtr(todo.Done.Bool), + }) + } + + return &connect.Response[todov1.ListTodosResponse]{ + Msg: &todov1.ListTodosResponse{ + Todos: reponseTodos, + }, + }, nil +} + +func (srv *TodoServer) UpdateTodo(ctx context.Context, req *connect.Request[todov1.UpdateTodoRequest]) (*connect.Response[todov1.UpdateTodoResponse], error) { + var id pgtype.UUID + err := id.Scan(*req.Msg.Todo.Id) + if err != nil { + return nil, err + } + todo, err := db.Q.UpdateTodo(ctx, db.UpdateTodoParams{ + Task: req.Msg.Todo.Task, + Done: pgtype.Bool{ + Bool: *req.Msg.Todo.Done, + Valid: true, + }, + ID: id, + }) + if err != nil { + return nil, err + } + return &connect.Response[todov1.UpdateTodoResponse]{ + Msg: &todov1.UpdateTodoResponse{ + Todo: &todov1.Todo{ + Id: StrPtr(todo.ID.String()), + Task: todo.Task, + CreatedAt: StrPtr(todo.CreatedAt.Time.Format(time.RFC3339)), + UpdatesAt: StrPtr(todo.UpdatedAt.Time.Format(time.RFC3339)), + Done: BoolPtr(todo.Done.Bool), + }, + }, + }, nil + +} + +func (srv *TodoServer) DeleteTodo(ctx context.Context, req *connect.Request[todov1.DeleteTodoRequest]) (*connect.Response[todov1.DeleteTodoResponse], error) { + var id pgtype.UUID + err := id.Scan(*req.Msg.Todo.Id) + if err != nil { + return nil, err + } + err = db.Q.DeleteTodo(ctx, id) + if err != nil { + return nil, err + } + return &connect.Response[todov1.DeleteTodoResponse]{ + Msg: &todov1.DeleteTodoResponse{}, + }, nil +} + +func GetPathHandler() (path string, handler http.Handler) { + path, handler = todov1connect.NewTodoServiceHandler(&TodoServer{}, connect.WithInterceptors(validate.NewInterceptor())) + return +} diff --git a/template/services/api/sqlc.yaml b/template/services/api/sqlc.yaml new file mode 100644 index 0000000..6e40552 --- /dev/null +++ b/template/services/api/sqlc.yaml @@ -0,0 +1,10 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "db/query" + schema: "db/migrations" + gen: + go: + package: "db" + out: "db" + sql_package: "pgx/v5" diff --git a/template/services/api/utils/utils.go b/template/services/api/utils/utils.go new file mode 100644 index 0000000..8954b4b --- /dev/null +++ b/template/services/api/utils/utils.go @@ -0,0 +1,9 @@ +package utils + +func StrPtr(v string) *string { + return &v +} + +func BoolPtr(v bool) *bool { + return &v +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}