mirror of
https://github.com/axzilla/templui.git
synced 2025-02-21 00:12:48 +00:00
feat: implement nonce generation and enhance modal functionality with new handlers (first CSP on some components)
This commit is contained in:
parent
1a4db2aab7
commit
d682d5f443
@ -735,10 +735,6 @@ body {
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.z-20 {
|
||||
z-index: 20;
|
||||
}
|
||||
@ -862,10 +858,6 @@ body {
|
||||
height: 0.25rem;
|
||||
}
|
||||
|
||||
.h-1\/2 {
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.h-10 {
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ func main() {
|
||||
config.LoadConfig()
|
||||
SetupAssetsRoutes(mux)
|
||||
|
||||
wrappedMux := middleware.WithPreviewCheck(mux)
|
||||
wrappedMux := middleware.WithPreviewCheck(middleware.WithNonce(mux))
|
||||
|
||||
mux.Handle("GET /", templ.Handler(pages.Landing()))
|
||||
mux.Handle("GET /docs/components", http.RedirectHandler("/docs/components/accordion", http.StatusSeeOther))
|
||||
|
34
internals/middleware/middleware.go
Normal file
34
internals/middleware/middleware.go
Normal file
@ -0,0 +1,34 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/axzilla/templui/internals/config"
|
||||
"github.com/axzilla/templui/internals/utils"
|
||||
)
|
||||
|
||||
func WithPreviewCheck(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
isPreview := strings.HasPrefix(r.Host, "preview.")
|
||||
ctx := context.WithValue(r.Context(), config.PreviewContextKey, isPreview)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func WithNonce(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nonce := utils.GenerateNonce()
|
||||
csp := fmt.Sprintf(
|
||||
"script-src 'self' 'nonce-%s' cdn.jsdelivr.net unpkg.com cdnjs.cloudflare.com; "+
|
||||
"style-src 'self' 'unsafe-inline' cdnjs.cloudflare.com;", // highlight.js CSS erlauben
|
||||
nonce,
|
||||
)
|
||||
w.Header().Set("Content-Security-Policy", csp)
|
||||
ctx := templ.WithNonce(r.Context(), nonce)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/axzilla/templui/internals/config"
|
||||
)
|
||||
|
||||
func WithPreviewCheck(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
isPreview := strings.HasPrefix(r.Host, "preview.")
|
||||
ctx := context.WithValue(r.Context(), config.PreviewContextKey, isPreview)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
@ -5,56 +5,70 @@ import (
|
||||
"github.com/axzilla/templui/internals/ui/modules"
|
||||
)
|
||||
|
||||
templ themeHandler() {
|
||||
<script nonce={ templ.GetNonce(ctx) }>
|
||||
// Initial theme setup
|
||||
document.documentElement.classList.toggle('dark', localStorage.getItem('appTheme') === 'dark');
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('themeHandler', () => ({
|
||||
state: {
|
||||
isDark: localStorage.getItem('appTheme') === 'dark',
|
||||
},
|
||||
actions: {
|
||||
themeClasses() {
|
||||
return this.state.isDark ? 'text-white' : 'bg-white text-black'
|
||||
},
|
||||
toggleTheme() {
|
||||
this.state.isDark = !this.state.isDark;
|
||||
localStorage.setItem('appTheme', this.state.isDark ? 'dark' : 'light');
|
||||
document.documentElement.classList.toggle('dark', this.state.isDark);
|
||||
}
|
||||
}
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
}
|
||||
|
||||
templ BaseLayout() {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full">
|
||||
<head>
|
||||
// Theme Handler Script
|
||||
@themeHandler()
|
||||
// Meta Tags
|
||||
<title>TemplUI - Modern UI Components for Go & Templ</title>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<style>
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
<script>
|
||||
// Minimal initial theme setup to prevent flash
|
||||
document.documentElement.classList.toggle('dark', localStorage.getItem('appTheme') === 'dark');
|
||||
</script>
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
// Plausible Analytics
|
||||
if config.AppConfig.GoEnv == "production" {
|
||||
<!-- Plausible Analytics -->
|
||||
<script defer data-domain="templui.io" src="https://plausible.axeladrian.com/js/script.js"></script>
|
||||
<script defer data-domain="templui.io" src="https://plausible.axeladrian.com/js/script.js" nonce={ templ.GetNonce(ctx) }></script>
|
||||
}
|
||||
<!-- Favicon -->
|
||||
// Favicon
|
||||
<link rel="icon" href="/assets/img/favicon.svg" type="image/x-icon"/>
|
||||
<!-- Tailwind CSS (local) -->
|
||||
// Tailwind CSS
|
||||
<link href="/assets/css/output.css" rel="stylesheet"/>
|
||||
<!-- Alpine.js -->
|
||||
// <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/focus@3.x.x/dist/cdn.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
// HTMX
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/htmx.org@1.7.0/dist/htmx.min.js"></script>
|
||||
// Theme Customizer
|
||||
<script src="/assets/js/theme-customizer.js"></script>
|
||||
<!-- Themes CSS -->
|
||||
// Custom CSS
|
||||
<link href="/assets/css/themes.css" rel="stylesheet"/>
|
||||
<!-- Highlight.js -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/base16/woodland.min.css"/>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
// <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
// Custom JS
|
||||
<script nonce={ templ.GetNonce(ctx) } src="/assets/js/theme-customizer.js"></script>
|
||||
// Alpine.js
|
||||
<script nonce={ templ.GetNonce(ctx) } defer src="https://cdn.jsdelivr.net/npm/@alpinejs/csp@3.x.x/dist/cdn.min.js"></script>
|
||||
// htmx.js
|
||||
<script nonce={ templ.GetNonce(ctx) } defer src="https://cdn.jsdelivr.net/npm/htmx.org@1.7.0/dist/htmx.min.js"></script>
|
||||
// Highlight.js
|
||||
<link nonce={ templ.GetNonce(ctx) } rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/base16/woodland.min.css"/>
|
||||
<script nonce={ templ.GetNonce(ctx) } src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script nonce={ templ.GetNonce(ctx) }>hljs.highlightAll();</script>
|
||||
</head>
|
||||
<body
|
||||
x-data="{
|
||||
isDark: localStorage.getItem('appTheme') === 'dark',
|
||||
sidebarOpen: false,
|
||||
toggleTheme() {
|
||||
this.isDark = !this.isDark;
|
||||
localStorage.setItem('appTheme', this.isDark ? 'dark' : 'light');
|
||||
document.documentElement.classList.toggle('dark', this.isDark);
|
||||
}
|
||||
}"
|
||||
x-data="themeHandler"
|
||||
x-cloak
|
||||
class="h-full flex flex-col transition-colors duration-200"
|
||||
:class="{ 'bg-white text-black': !isDark, 'text-white': isDark }"
|
||||
x-bind:class="actions.themeClasses"
|
||||
>
|
||||
<div class="flex flex-col min-h-screen">
|
||||
{ children... }
|
||||
|
@ -1,20 +1,44 @@
|
||||
package modules
|
||||
|
||||
templ snippetHandler() {
|
||||
<script nonce={ templ.GetNonce(ctx) }>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('snippet', () => ({
|
||||
state: {
|
||||
copied: false,
|
||||
buttonText: 'Copy',
|
||||
},
|
||||
actions: {
|
||||
copyCode() {
|
||||
navigator.clipboard.writeText(this.$refs.code.textContent)
|
||||
this.state.copied = true
|
||||
this.state.buttonText = 'Copied!'
|
||||
setTimeout(() => {
|
||||
this.state.copied = false
|
||||
this.state.buttonText = 'Copy'
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
}
|
||||
|
||||
templ CodeSnippet(content, language string) {
|
||||
<div class="relative" x-data="{ copied: false }">
|
||||
@snippetHandler()
|
||||
<div class="relative" x-data="snippet">
|
||||
<pre class="!overflow-hidden">
|
||||
<code x-ref="code" class={ "language-" + language, "!max-h-[501px] !overflow-y-auto rounded-md block" }>
|
||||
<code
|
||||
x-ref="code"
|
||||
class={ "language-" + language, "!max-h-[501px] !overflow-y-auto rounded-md block" }
|
||||
>
|
||||
{ content }
|
||||
</code>
|
||||
</pre>
|
||||
<button
|
||||
class="absolute top-2 right-2 bg-gray-700 hover:bg-gray-600 text-white font-bold py-1 px-2 rounded text-xs"
|
||||
x-on:click="
|
||||
navigator.clipboard.writeText($refs.code.textContent);
|
||||
copied = true;
|
||||
setTimeout(() => copied = false, 2001);
|
||||
"
|
||||
x-text="copied ? 'Copied!' : 'Copy'"
|
||||
@click="actions.copyCode"
|
||||
x-text="state.buttonText"
|
||||
></button>
|
||||
</div>
|
||||
}
|
||||
|
@ -65,7 +65,6 @@ templ NavbarMobileMenu() {
|
||||
}
|
||||
@components.SheetTrigger(string(components.SheetSideLeft), components.SheetSideLeft) {
|
||||
<button
|
||||
@click="sidebarOpen = !sidebarOpen"
|
||||
class="mr-2 lg:hidden p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
|
||||
>
|
||||
@icons.SquareLibrary(icons.IconProps{})
|
||||
|
@ -5,24 +5,42 @@ import (
|
||||
"github.com/axzilla/templui/pkg/icons"
|
||||
)
|
||||
|
||||
templ themeIconHandler() {
|
||||
<script nonce={ templ.GetNonce(ctx) }>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('themeIconHandler', () => ({
|
||||
isDarkMode() {
|
||||
return this.state.isDark
|
||||
},
|
||||
isLightMode() {
|
||||
return !this.state.isDark
|
||||
}
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
}
|
||||
|
||||
templ ThemeSwitcher() {
|
||||
@themeIconHandler()
|
||||
@components.Button(components.ButtonProps{
|
||||
Size: components.ButtonSizeIcon,
|
||||
Variant: components.ButtonVariantGhost,
|
||||
IconLeft: templ.Component(DynamicThemeIcon()),
|
||||
Attributes: templ.Attributes{
|
||||
"@click": "toggleTheme()",
|
||||
"@click": "actions.toggleTheme",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
templ DynamicThemeIcon() {
|
||||
<span x-show="isDark" class="block">
|
||||
@LightIcon()
|
||||
</span>
|
||||
<span x-show="!isDark" class="block">
|
||||
@DarkIcon()
|
||||
</span>
|
||||
<div x-data="themeIconHandler">
|
||||
<span x-show="isDarkMode" class="block">
|
||||
@LightIcon()
|
||||
</span>
|
||||
<span x-show="isLightMode" class="block">
|
||||
@DarkIcon()
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ DarkIcon() {
|
||||
|
16
internals/utils/utils.go
Normal file
16
internals/utils/utils.go
Normal file
@ -0,0 +1,16 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
func GenerateNonce() string {
|
||||
nonceBytes := make([]byte, 16)
|
||||
_, err := rand.Read(nonceBytes)
|
||||
if err != nil {
|
||||
// TODO: Handle the error appropriately
|
||||
return ""
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(nonceBytes)
|
||||
}
|
@ -195,7 +195,7 @@ templ Datepicker(props DatepickerProps) {
|
||||
const rect = trigger.getBoundingClientRect();
|
||||
const popupRect = popup.getBoundingClientRect();
|
||||
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
|
||||
|
||||
if (rect.bottom + popupRect.height > viewportHeight && rect.top > popupRect.height) {
|
||||
this.position = 'top';
|
||||
} else {
|
||||
@ -206,7 +206,7 @@ templ Datepicker(props DatepickerProps) {
|
||||
calculateDays() {
|
||||
const firstDay = new Date(this.currentYear, this.currentMonth, 1).getDay();
|
||||
const daysInMonth = new Date(this.currentYear, this.currentMonth + 1, 0).getDate();
|
||||
|
||||
|
||||
this.blankDays = Array.from({ length: firstDay }, (_, i) => i);
|
||||
this.monthDays = Array.from({ length: daysInMonth }, (_, i) => i + 1);
|
||||
},
|
||||
@ -225,7 +225,7 @@ templ Datepicker(props DatepickerProps) {
|
||||
default:
|
||||
return dateStr; // Für ISO und long Format das Datum unverändert lassen
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
formatDate(date) {
|
||||
const d = date.getDate().toString().padStart(2, '0');
|
||||
|
@ -270,7 +270,7 @@ func Datepicker(props DatepickerProps) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" x-data=\"{\n open: false,\n value: null,\n format: $el.dataset.format,\n currentMonth: 5,\n currentYear: new Date().getFullYear(),\n monthDays: [],\n blankDays: [],\n months: JSON.parse($el.dataset.monthnames) || ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],\n days: JSON.parse($el.dataset.daynames) || ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],\n\n position: 'bottom',\n\n init() {\n const initialDate = $el.dataset.value ? new Date(this.parseDate($el.dataset.value)) : new Date();\n this.currentMonth = initialDate.getMonth();\n this.currentYear = initialDate.getFullYear();\n this.calculateDays();\n // Format the initial value using the correct locale\n if ($el.dataset.value) {\n this.value = this.formatDate(initialDate);\n }\n },\n\n toggleDatePicker() {\n this.open = !this.open;\n if (this.open) {\n this.$nextTick(() => this.updatePosition());\n }\n },\n\n updatePosition() {\n const trigger = document.getElementById($el.dataset.inputId);\n const popup = this.$refs.datePickerPopup;\n const rect = trigger.getBoundingClientRect();\n const popupRect = popup.getBoundingClientRect();\n const viewportHeight = window.innerHeight || document.documentElement.clientHeight;\n \n if (rect.bottom + popupRect.height > viewportHeight && rect.top > popupRect.height) {\n this.position = 'top';\n } else {\n this.position = 'bottom';\n }\n },\n\n calculateDays() {\n const firstDay = new Date(this.currentYear, this.currentMonth, 1).getDay();\n const daysInMonth = new Date(this.currentYear, this.currentMonth + 1, 0).getDate();\n \n this.blankDays = Array.from({ length: firstDay }, (_, i) => i);\n this.monthDays = Array.from({ length: daysInMonth }, (_, i) => i + 1);\n },\n\n\t\t\tparseDate(dateStr) {\n\t\t\t\tconst parts = dateStr.split(/[-/.]/);\n\t\t\t\tswitch(this.format) {\n\t\t\t\t\tcase 'eu':\n\t\t\t\t\t\treturn `${parts[2]}-${parts[1]}-${parts[0]}`;\n\t\t\t\t\tcase 'us':\n\t\t\t\t\t\treturn `${parts[2]}-${parts[0]}-${parts[1]}`;\n\t\t\t\t\tcase 'uk':\n\t\t\t\t\t\treturn `${parts[2]}-${parts[1]}-${parts[0]}`;\n\t\t\t\t\tcase 'long':\n\t\t\t\t\tcase 'iso':\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn dateStr; // Für ISO und long Format das Datum unverändert lassen\n\t\t\t\t}\n\t\t\t}, \n\n\t\t\tformatDate(date) {\n const d = date.getDate().toString().padStart(2, '0');\n const m = (date.getMonth() + 1).toString().padStart(2, '0');\n const y = date.getFullYear();\n\n switch(this.format) {\n case 'eu':\n return `${d}.${m}.${y}`;\n\t\t\t\t\t case 'uk':\n\t\t\t\t\t\t return `${d}/${m}/${y}`;\n case 'us':\n return `${m}/${d}/${y}`;\n case 'long':\n // Use the months array from the provided locale\n return `${this.months[date.getMonth()]} ${d}, ${y}`;\n default: // iso\n return `${y}-${m}-${d}`;\n }\n },\n\n isToday(day) {\n const today = new Date();\n const date = new Date(this.currentYear, this.currentMonth, day);\n return date.toDateString() === today.toDateString();\n },\n\n isSelected(day) {\n if (!this.value) return false;\n const date = new Date(this.currentYear, this.currentMonth, day);\n const selected = new Date(this.parseDate(this.value));\n return date.toDateString() === selected.toDateString();\n },\n\n selectDate(day) {\n const date = new Date(this.currentYear, this.currentMonth, day);\n this.value = this.formatDate(date);\n this.open = false;\n }\n }\" @resize.window=\"if (open) updatePosition()\"><div class=\"relative\">")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" x-data=\"{\n open: false,\n value: null,\n format: $el.dataset.format,\n currentMonth: 5,\n currentYear: new Date().getFullYear(),\n monthDays: [],\n blankDays: [],\n months: JSON.parse($el.dataset.monthnames) || ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],\n days: JSON.parse($el.dataset.daynames) || ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],\n\n position: 'bottom',\n\n init() {\n const initialDate = $el.dataset.value ? new Date(this.parseDate($el.dataset.value)) : new Date();\n this.currentMonth = initialDate.getMonth();\n this.currentYear = initialDate.getFullYear();\n this.calculateDays();\n // Format the initial value using the correct locale\n if ($el.dataset.value) {\n this.value = this.formatDate(initialDate);\n }\n },\n\n toggleDatePicker() {\n this.open = !this.open;\n if (this.open) {\n this.$nextTick(() => this.updatePosition());\n }\n },\n\n updatePosition() {\n const trigger = document.getElementById($el.dataset.inputId);\n const popup = this.$refs.datePickerPopup;\n const rect = trigger.getBoundingClientRect();\n const popupRect = popup.getBoundingClientRect();\n const viewportHeight = window.innerHeight || document.documentElement.clientHeight;\n\n if (rect.bottom + popupRect.height > viewportHeight && rect.top > popupRect.height) {\n this.position = 'top';\n } else {\n this.position = 'bottom';\n }\n },\n\n calculateDays() {\n const firstDay = new Date(this.currentYear, this.currentMonth, 1).getDay();\n const daysInMonth = new Date(this.currentYear, this.currentMonth + 1, 0).getDate();\n\n this.blankDays = Array.from({ length: firstDay }, (_, i) => i);\n this.monthDays = Array.from({ length: daysInMonth }, (_, i) => i + 1);\n },\n\n\t\t\tparseDate(dateStr) {\n\t\t\t\tconst parts = dateStr.split(/[-/.]/);\n\t\t\t\tswitch(this.format) {\n\t\t\t\t\tcase 'eu':\n\t\t\t\t\t\treturn `${parts[2]}-${parts[1]}-${parts[0]}`;\n\t\t\t\t\tcase 'us':\n\t\t\t\t\t\treturn `${parts[2]}-${parts[0]}-${parts[1]}`;\n\t\t\t\t\tcase 'uk':\n\t\t\t\t\t\treturn `${parts[2]}-${parts[1]}-${parts[0]}`;\n\t\t\t\t\tcase 'long':\n\t\t\t\t\tcase 'iso':\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn dateStr; // Für ISO und long Format das Datum unverändert lassen\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tformatDate(date) {\n const d = date.getDate().toString().padStart(2, '0');\n const m = (date.getMonth() + 1).toString().padStart(2, '0');\n const y = date.getFullYear();\n\n switch(this.format) {\n case 'eu':\n return `${d}.${m}.${y}`;\n\t\t\t\t\t case 'uk':\n\t\t\t\t\t\t return `${d}/${m}/${y}`;\n case 'us':\n return `${m}/${d}/${y}`;\n case 'long':\n // Use the months array from the provided locale\n return `${this.months[date.getMonth()]} ${d}, ${y}`;\n default: // iso\n return `${y}-${m}-${d}`;\n }\n },\n\n isToday(day) {\n const today = new Date();\n const date = new Date(this.currentYear, this.currentMonth, day);\n return date.toDateString() === today.toDateString();\n },\n\n isSelected(day) {\n if (!this.value) return false;\n const date = new Date(this.currentYear, this.currentMonth, day);\n const selected = new Date(this.parseDate(this.value));\n return date.toDateString() === selected.toDateString();\n },\n\n selectDate(day) {\n const date = new Date(this.currentYear, this.currentMonth, day);\n this.value = this.formatDate(date);\n this.open = false;\n }\n }\" @resize.window=\"if (open) updatePosition()\"><div class=\"relative\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
@ -9,12 +9,13 @@ type ModalProps struct {
|
||||
|
||||
// Dialog overlay that requires user attention or interaction.
|
||||
templ Modal(props ModalProps) {
|
||||
@initModalHandlers()
|
||||
<div
|
||||
x-data="{ open: false }"
|
||||
x-on:open-modal.window="if ($event.detail.id === $el.dataset.modalId) open = true"
|
||||
x-on:close-modal.window="if ($event.detail.id === $el.dataset.modalId) open = false"
|
||||
x-data="modalHandler"
|
||||
x-on:open-modal.window="actions.handleOpenModal"
|
||||
x-on:close-modal.window="actions.handleCloseModal"
|
||||
data-modal-id={ props.ID }
|
||||
x-show="open"
|
||||
x-show="state.open"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
@ -33,7 +34,7 @@ templ Modal(props ModalProps) {
|
||||
"relative bg-background rounded-lg border text-left overflow-hidden shadow-xl transform transition-all sm:my-8 w-full",
|
||||
props.Class),
|
||||
}
|
||||
@click.away="open = false"
|
||||
@click.away="actions.handleClickAway"
|
||||
>
|
||||
{ children... }
|
||||
</div>
|
||||
@ -41,22 +42,24 @@ templ Modal(props ModalProps) {
|
||||
}
|
||||
|
||||
// ModalTrigger creates clickable elements that open a modal
|
||||
// ID parameter must match the target modal's ID
|
||||
templ ModalTrigger(id string) {
|
||||
@initModalHandlers()
|
||||
<span
|
||||
x-data="modalTriggers"
|
||||
data-modal-id={ id }
|
||||
@click="$dispatch('open-modal', { id: $el.dataset.modalId })"
|
||||
@click="openModal"
|
||||
>
|
||||
{ children... }
|
||||
</span>
|
||||
}
|
||||
|
||||
// ModalClose creates clickable elements that close a modal
|
||||
// ID parameter must match the target modal's ID
|
||||
templ ModalClose(id string) {
|
||||
@initModalHandlers()
|
||||
<span
|
||||
x-data="modalTriggers"
|
||||
data-modal-id={ id }
|
||||
@click="$dispatch('close-modal', { id: $el.dataset.modalId })"
|
||||
@click="closeModal"
|
||||
>
|
||||
{ children... }
|
||||
</span>
|
||||
@ -84,3 +87,50 @@ templ ModalFooter() {
|
||||
{ children... }
|
||||
</div>
|
||||
}
|
||||
|
||||
var modalHandle = templ.NewOnceHandle()
|
||||
|
||||
templ initModalHandlers() {
|
||||
@modalHandle.Once() {
|
||||
<script defer nonce={ templ.GetNonce(ctx) }>
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
// Modal selbst
|
||||
Alpine.data('modalHandler', () => ({
|
||||
state: {
|
||||
open: false,
|
||||
},
|
||||
actions: {
|
||||
handleOpenModal(event) {
|
||||
if (event.detail.id === this.$el.dataset.modalId) {
|
||||
this.state.open = true
|
||||
}
|
||||
},
|
||||
handleCloseModal(event) {
|
||||
if (event.detail.id === this.$el.dataset.modalId) {
|
||||
this.state.open = false
|
||||
}
|
||||
},
|
||||
handleClickAway() {
|
||||
this.state.open = false;
|
||||
},
|
||||
}
|
||||
}))
|
||||
|
||||
// Trigger & Close in einem
|
||||
Alpine.data('modalTriggers', () => ({
|
||||
openModal() {
|
||||
this.$dispatch('open-modal', {
|
||||
id: this.$el.dataset.modalId
|
||||
})
|
||||
},
|
||||
closeModal() {
|
||||
this.$dispatch('close-modal', {
|
||||
id: this.$el.dataset.modalId
|
||||
})
|
||||
}
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
}
|
||||
}
|
||||
|
@ -37,20 +37,24 @@ func Modal(props ModalProps) templ.Component {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-data=\"{ open: false }\" x-on:open-modal.window=\"if ($event.detail.id === $el.dataset.modalId) open = true\" x-on:close-modal.window=\"if ($event.detail.id === $el.dataset.modalId) open = false\" data-modal-id=\"")
|
||||
templ_7745c5c3_Err = initModalHandlers().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-data=\"modalHandler\" x-on:open-modal.window=\"actions.handleOpenModal\" x-on:close-modal.window=\"actions.handleCloseModal\" data-modal-id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(props.ID)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/modal.templ`, Line: 16, Col: 26}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/modal.templ`, Line: 17, Col: 26}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" x-show=\"open\" x-transition:enter=\"transition ease-out duration-300\" x-transition:enter-start=\"opacity-0\" x-transition:enter-end=\"opacity-100\" x-transition:leave=\"transition ease-in duration-200\" x-transition:leave-start=\"opacity-100\" x-transition:leave-end=\"opacity-0\" class=\"fixed inset-0 z-50 flex items-center justify-center overflow-y-auto\" aria-labelledby=\"modal-title\" role=\"dialog\" aria-modal=\"true\"><div class=\"fixed inset-0 bg-black bg-opacity-50 transition-opacity\" aria-hidden=\"true\"></div>")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" x-show=\"state.open\" x-transition:enter=\"transition ease-out duration-300\" x-transition:enter-start=\"opacity-0\" x-transition:enter-end=\"opacity-100\" x-transition:leave=\"transition ease-in duration-200\" x-transition:leave-start=\"opacity-100\" x-transition:leave-end=\"opacity-0\" class=\"fixed inset-0 z-50 flex items-center justify-center overflow-y-auto\" aria-labelledby=\"modal-title\" role=\"dialog\" aria-modal=\"true\"><div class=\"fixed inset-0 bg-black bg-opacity-50 transition-opacity\" aria-hidden=\"true\"></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -76,7 +80,7 @@ func Modal(props ModalProps) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" @click.away=\"open = false\">")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" @click.away=\"actions.handleClickAway\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -93,7 +97,6 @@ func Modal(props ModalProps) templ.Component {
|
||||
}
|
||||
|
||||
// ModalTrigger creates clickable elements that open a modal
|
||||
// ID parameter must match the target modal's ID
|
||||
func ModalTrigger(id string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
@ -115,20 +118,24 @@ func ModalTrigger(id string) templ.Component {
|
||||
templ_7745c5c3_Var5 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span data-modal-id=\"")
|
||||
templ_7745c5c3_Err = initModalHandlers().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span x-data=\"modalTriggers\" data-modal-id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(id)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/modal.templ`, Line: 47, Col: 20}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/modal.templ`, Line: 49, Col: 20}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" @click=\"$dispatch('open-modal', { id: $el.dataset.modalId })\">")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" @click=\"openModal\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -145,7 +152,6 @@ func ModalTrigger(id string) templ.Component {
|
||||
}
|
||||
|
||||
// ModalClose creates clickable elements that close a modal
|
||||
// ID parameter must match the target modal's ID
|
||||
func ModalClose(id string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
@ -167,20 +173,24 @@ func ModalClose(id string) templ.Component {
|
||||
templ_7745c5c3_Var7 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span data-modal-id=\"")
|
||||
templ_7745c5c3_Err = initModalHandlers().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span x-data=\"modalTriggers\" data-modal-id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(id)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/modal.templ`, Line: 58, Col: 20}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/modal.templ`, Line: 61, Col: 20}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" @click=\"$dispatch('close-modal', { id: $el.dataset.modalId })\">")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" @click=\"closeModal\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -310,4 +320,66 @@ func ModalFooter() templ.Component {
|
||||
})
|
||||
}
|
||||
|
||||
var modalHandle = templ.NewOnceHandle()
|
||||
|
||||
func initModalHandlers() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var12 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var12 == nil {
|
||||
templ_7745c5c3_Var12 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var13 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script defer nonce=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(templ.GetNonce(ctx))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/modal.templ`, Line: 95, Col: 43}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">\n\n document.addEventListener('alpine:init', () => {\n // Modal selbst\n Alpine.data('modalHandler', () => ({\n\t\t\t\t\tstate: {\n \topen: false,\n\t\t\t\t\t},\n\t\t\t\t\tactions: {\n\t\t\t\t\t\thandleOpenModal(event) {\n\t\t\t\t\t\t\tif (event.detail.id === this.$el.dataset.modalId) {\n\t\t\t\t\t\t\t\tthis.state.open = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\thandleCloseModal(event) {\n\t\t\t\t\t\t\tif (event.detail.id === this.$el.dataset.modalId) {\n\t\t\t\t\t\t\t\tthis.state.open = false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\thandleClickAway() {\n\t\t\t\t\t\t\tthis.state.open = false;\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n }))\n\n\t\t\t\t // Trigger & Close in einem\n Alpine.data('modalTriggers', () => ({\n openModal() {\n this.$dispatch('open-modal', { \n id: this.$el.dataset.modalId \n })\n },\n closeModal() {\n this.$dispatch('close-modal', { \n id: this.$el.dataset.modalId \n })\n }\n }))\n })\n </script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = modalHandle.Once().Render(templ.WithChildren(ctx, templ_7745c5c3_Var13), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
|
@ -15,22 +15,33 @@ type SheetProps struct {
|
||||
Side SheetSide // Slide-in direction
|
||||
}
|
||||
|
||||
templ sheetHandler() {
|
||||
<script nonce={ templ.GetNonce(ctx) }>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('sheetHandler', () => ({
|
||||
state: {
|
||||
isOpen: false,
|
||||
},
|
||||
actions: {
|
||||
open() {
|
||||
this.state.isOpen = true
|
||||
},
|
||||
close() {
|
||||
this.state.isOpen = false
|
||||
},
|
||||
}
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
}
|
||||
|
||||
// SheetRoot initializes Alpine.js state and event handlers
|
||||
// Must wrap Sheet components and triggers
|
||||
templ SheetRoot() {
|
||||
@sheetHandler()
|
||||
<div
|
||||
x-data="{
|
||||
isOpen: false,
|
||||
side: '',
|
||||
open(newSide) {
|
||||
this.side = newSide;
|
||||
this.isOpen = true;
|
||||
},
|
||||
close() {
|
||||
this.isOpen = false;
|
||||
}
|
||||
}"
|
||||
@keydown.escape.window="close()"
|
||||
x-data="sheetHandler"
|
||||
@keydown.escape.window="actions.close"
|
||||
>
|
||||
{ children... }
|
||||
</div>
|
||||
@ -40,9 +51,9 @@ templ SheetRoot() {
|
||||
templ Sheet(props SheetProps) {
|
||||
<!-- Backdrop -->
|
||||
<div
|
||||
x-show="isOpen"
|
||||
x-show="state.isOpen"
|
||||
class="fixed inset-0 bg-background/80 backdrop-blur-sm"
|
||||
@click="close()"
|
||||
@click="actions.close"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
@ -52,14 +63,14 @@ templ Sheet(props SheetProps) {
|
||||
></div>
|
||||
<!-- Sheet -->
|
||||
<div
|
||||
x-show="isOpen"
|
||||
class="z-50"
|
||||
:class="{
|
||||
'fixed inset-y-0 right-0 w-3/4 md:w-1/2 lg:w-1/3': side === 'right',
|
||||
'fixed inset-y-0 left-0 w-3/4 md:w-1/2 lg:w-1/3': side === 'left',
|
||||
'fixed inset-x-0 top-0 h-auto sm:h-1/2': side === 'top',
|
||||
'fixed inset-x-0 bottom-0 h-auto sm:h-1/2': side === 'bottom'
|
||||
}"
|
||||
x-show="state.isOpen"
|
||||
class={
|
||||
"z-50",
|
||||
templ.KV("fixed inset-y-0 right-0 w-3/4 md:w-1/2 lg:w-1/3", props.Side == SheetSideRight),
|
||||
templ.KV("fixed inset-y-0 left-0 w-3/4 md:w-1/2 lg:w-1/3", props.Side == SheetSideLeft),
|
||||
templ.KV("fixed inset-x-0 top-0 h-auto sm:h-1/2", props.Side == SheetSideTop),
|
||||
templ.KV("fixed inset-x-0 bottom-0 h-auto sm:h-1/2", props.Side == SheetSideBottom),
|
||||
}
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:leave="transition ease-in duration-300"
|
||||
if props.Side == SheetSideLeft {
|
||||
@ -89,10 +100,10 @@ templ Sheet(props SheetProps) {
|
||||
>
|
||||
<div
|
||||
class={ "h-full overflow-y-auto bg-background p-6 shadow-lg",
|
||||
templ.KV("border-l", props.Side == SheetSideRight),
|
||||
templ.KV("border-r", props.Side == SheetSideLeft),
|
||||
templ.KV("border-t", props.Side == SheetSideBottom),
|
||||
templ.KV("border-b", props.Side == SheetSideTop) }
|
||||
templ.KV("border-l", props.Side == SheetSideRight),
|
||||
templ.KV("border-r", props.Side == SheetSideLeft),
|
||||
templ.KV("border-t", props.Side == SheetSideBottom),
|
||||
templ.KV("border-b", props.Side == SheetSideTop) }
|
||||
>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<h2 class="text-lg font-semibold">{ props.Title }</h2>
|
||||
@ -108,9 +119,7 @@ templ Sheet(props SheetProps) {
|
||||
// SheetTrigger creates elements that open the sheet
|
||||
// Must be used within SheetRoot
|
||||
templ SheetTrigger(text string, side SheetSide) {
|
||||
<span
|
||||
@click={ "open('" + string(side) + "')" }
|
||||
>
|
||||
<span @click="actions.open">
|
||||
{ children... }
|
||||
</span>
|
||||
}
|
||||
@ -119,11 +128,11 @@ templ SheetTrigger(text string, side SheetSide) {
|
||||
// Must be used within Sheet
|
||||
templ SheetClose(text string) {
|
||||
<button
|
||||
@click="close()"
|
||||
@click="actions.close"
|
||||
class={ "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background",
|
||||
"transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
"disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent",
|
||||
"hover:text-accent-foreground h-10 px-4 py-2" }
|
||||
"transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
"disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent",
|
||||
"hover:text-accent-foreground h-10 px-4 py-2" }
|
||||
>
|
||||
{ text }
|
||||
</button>
|
||||
|
@ -23,9 +23,7 @@ type SheetProps struct {
|
||||
Side SheetSide // Slide-in direction
|
||||
}
|
||||
|
||||
// SheetRoot initializes Alpine.js state and event handlers
|
||||
// Must wrap Sheet components and triggers
|
||||
func SheetRoot() templ.Component {
|
||||
func sheetHandler() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
@ -46,11 +44,59 @@ func SheetRoot() templ.Component {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-data=\"{ \n isOpen: false,\n side: '',\n open(newSide) { \n this.side = newSide; \n this.isOpen = true; \n },\n close() { \n this.isOpen = false; \n }\n }\" @keydown.escape.window=\"close()\">")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script nonce=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(templ.GetNonce(ctx))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/sheet.templ`, Line: 19, Col: 36}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">\n document.addEventListener('alpine:init', () => {\n Alpine.data('sheetHandler', () => ({\n\t\t\t\tstate: {\n \tisOpen: false,\n\t\t\t\t},\n\t\t\t\tactions: {\n\t\t\t\t\topen() {\n\t\t\t\t\t\tthis.state.isOpen = true\n\t\t\t\t\t},\n\t\t\t\t\tclose() {\n\t\t\t\t\t\tthis.state.isOpen = false\n\t\t\t\t\t},\n\t\t\t\t}\n }))\n })\n </script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
// SheetRoot initializes Alpine.js state and event handlers
|
||||
// Must wrap Sheet components and triggers
|
||||
func SheetRoot() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var3 == nil {
|
||||
templ_7745c5c3_Var3 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = sheetHandler().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-data=\"sheetHandler\" @keydown.escape.window=\"actions.close\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var3.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -79,12 +125,40 @@ func Sheet(props SheetProps) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var2 == nil {
|
||||
templ_7745c5c3_Var2 = templ.NopComponent
|
||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!-- Backdrop --><div x-show=\"isOpen\" class=\"fixed inset-0 bg-background/80 backdrop-blur-sm\" @click=\"close()\" x-transition:enter=\"transition ease-out duration-300\" x-transition:enter-start=\"opacity-0\" x-transition:enter-end=\"opacity-100\" x-transition:leave=\"transition ease-in duration-300\" x-transition:leave-start=\"opacity-100\" x-transition:leave-end=\"opacity-0\"></div><!-- Sheet --><div x-show=\"isOpen\" class=\"z-50\" :class=\"{\n 'fixed inset-y-0 right-0 w-3/4 md:w-1/2 lg:w-1/3': side === 'right',\n 'fixed inset-y-0 left-0 w-3/4 md:w-1/2 lg:w-1/3': side === 'left',\n 'fixed inset-x-0 top-0 h-auto sm:h-1/2': side === 'top',\n 'fixed inset-x-0 bottom-0 h-auto sm:h-1/2': side === 'bottom'\n }\" x-transition:enter=\"transition ease-out duration-300\" x-transition:leave=\"transition ease-in duration-300\"")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!-- Backdrop --><div x-show=\"state.isOpen\" class=\"fixed inset-0 bg-background/80 backdrop-blur-sm\" @click=\"actions.close\" x-transition:enter=\"transition ease-out duration-300\" x-transition:enter-start=\"opacity-0\" x-transition:enter-end=\"opacity-100\" x-transition:leave=\"transition ease-in duration-300\" x-transition:leave-start=\"opacity-100\" x-transition:leave-end=\"opacity-0\"></div><!-- Sheet -->")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 = []any{
|
||||
"z-50",
|
||||
templ.KV("fixed inset-y-0 right-0 w-3/4 md:w-1/2 lg:w-1/3", props.Side == SheetSideRight),
|
||||
templ.KV("fixed inset-y-0 left-0 w-3/4 md:w-1/2 lg:w-1/3", props.Side == SheetSideLeft),
|
||||
templ.KV("fixed inset-x-0 top-0 h-auto sm:h-1/2", props.Side == SheetSideTop),
|
||||
templ.KV("fixed inset-x-0 bottom-0 h-auto sm:h-1/2", props.Side == SheetSideBottom),
|
||||
}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-show=\"state.isOpen\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var5).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/sheet.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" x-transition:enter=\"transition ease-out duration-300\" x-transition:leave=\"transition ease-in duration-300\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -116,12 +190,12 @@ func Sheet(props SheetProps) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 = []any{"h-full overflow-y-auto bg-background p-6 shadow-lg",
|
||||
var templ_7745c5c3_Var7 = []any{"h-full overflow-y-auto bg-background p-6 shadow-lg",
|
||||
templ.KV("border-l", props.Side == SheetSideRight),
|
||||
templ.KV("border-r", props.Side == SheetSideLeft),
|
||||
templ.KV("border-t", props.Side == SheetSideBottom),
|
||||
templ.KV("border-b", props.Side == SheetSideTop)}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -129,12 +203,12 @@ func Sheet(props SheetProps) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String())
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var7).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/sheet.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -142,12 +216,12 @@ func Sheet(props SheetProps) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(props.Title)
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(props.Title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/sheet.templ`, Line: 98, Col: 51}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/sheet.templ`, Line: 109, Col: 51}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -155,12 +229,12 @@ func Sheet(props SheetProps) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(props.Description)
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(props.Description)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/sheet.templ`, Line: 99, Col: 64}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/sheet.templ`, Line: 110, Col: 64}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -168,7 +242,7 @@ func Sheet(props SheetProps) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var2.Render(ctx, templ_7745c5c3_Buffer)
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var4.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -198,29 +272,16 @@ func SheetTrigger(text string, side SheetSide) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var7 == nil {
|
||||
templ_7745c5c3_Var7 = templ.NopComponent
|
||||
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var11 == nil {
|
||||
templ_7745c5c3_Var11 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span @click=\"")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span @click=\"actions.open\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs("open('" + string(side) + "')")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/sheet.templ`, Line: 112, Col: 41}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var7.Render(ctx, templ_7745c5c3_Buffer)
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var11.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -250,29 +311,29 @@ func SheetClose(text string) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var9 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var9 == nil {
|
||||
templ_7745c5c3_Var9 = templ.NopComponent
|
||||
templ_7745c5c3_Var12 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var12 == nil {
|
||||
templ_7745c5c3_Var12 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
var templ_7745c5c3_Var10 = []any{"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background",
|
||||
var templ_7745c5c3_Var13 = []any{"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background",
|
||||
"transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
"disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent",
|
||||
"hover:text-accent-foreground h-10 px-4 py-2"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...)
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var13...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button @click=\"close()\" class=\"")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button @click=\"actions.close\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var10).String())
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var13).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/sheet.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -280,12 +341,12 @@ func SheetClose(text string) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/sheet.templ`, Line: 128, Col: 8}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/sheet.templ`, Line: 137, Col: 8}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package components
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Tab struct {
|
||||
ID string // Unique identifier for the tab
|
||||
Title string // Tab title
|
||||
@ -12,65 +14,58 @@ type TabsProps struct {
|
||||
ContentContainerClass string // Additional CSS classes for the content container
|
||||
}
|
||||
|
||||
// Navigation interface that organizes content into sections.
|
||||
templ Tabs(props TabsProps) {
|
||||
<div
|
||||
x-data="{
|
||||
tabSelected: '1',
|
||||
tabId: $id('tabs'),
|
||||
tabButtonClicked(tabButton) {
|
||||
this.tabSelected = tabButton.id.replace(this.tabId + '-', '');
|
||||
this.tabRepositionMarker(tabButton);
|
||||
},
|
||||
tabRepositionMarker(tabButton) {
|
||||
this.$refs.tabMarker.style.width = tabButton.offsetWidth + 'px';
|
||||
this.$refs.tabMarker.style.height = tabButton.offsetHeight + 'px';
|
||||
this.$refs.tabMarker.style.left = tabButton.offsetLeft + 'px';
|
||||
},
|
||||
tabContentActive(tabContent) {
|
||||
return this.tabSelected === tabContent.id.replace(this.tabId + '-content-', '');
|
||||
}
|
||||
}"
|
||||
x-init="$nextTick(() => tabRepositionMarker($refs.tabButtons.firstElementChild));"
|
||||
class="relative"
|
||||
>
|
||||
<!-- Tabs buttons container -->
|
||||
<div
|
||||
x-ref="tabButtons"
|
||||
class={ "relative flex items-center justify-center h-10 p-1 rounded-lg select-none",
|
||||
"bg-muted text-muted-foreground",
|
||||
props.TabsContainerClass }
|
||||
>
|
||||
<!-- Individual tab buttons -->
|
||||
for _, tab := range props.Tabs {
|
||||
<div class="tabs-component">
|
||||
<div class={ "relative flex items-center justify-center h-10 p-1 rounded-lg select-none bg-muted text-muted-foreground", props.TabsContainerClass }>
|
||||
for i, tab := range props.Tabs {
|
||||
<button
|
||||
:id="$id(tabId)"
|
||||
@click="tabButtonClicked($el);"
|
||||
type="button"
|
||||
class="relative z-20 flex-1 inline-flex items-center justify-center h-8 px-3 text-sm font-medium transition-all rounded-md cursor-pointer whitespace-nowrap"
|
||||
:class="{'text-foreground bg-background shadow-sm': tabSelected === '{tab.ID}', 'hover:text-foreground': tabSelected !== '{tab.ID}'}"
|
||||
data-tab-target={ fmt.Sprintf("tab-%d", i) }
|
||||
class="tab-button relative z-20 flex-1 inline-flex items-center justify-center h-8 px-3 text-sm font-medium transition-all rounded-md cursor-pointer whitespace-nowrap hover:text-foreground"
|
||||
>
|
||||
{ tab.Title }
|
||||
</button>
|
||||
}
|
||||
<!-- Active tab marker -->
|
||||
<div x-ref="tabMarker" class="absolute left-0 z-10 h-full duration-300 ease-out" x-cloak>
|
||||
<div class="w-full h-full bg-background rounded-md shadow-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tab content container -->
|
||||
<div class={ "relative mt-2 content", props.ContentContainerClass }>
|
||||
<!-- Individual tab content -->
|
||||
for _, tab := range props.Tabs {
|
||||
<div class={ "relative mt-2", props.ContentContainerClass }>
|
||||
for i, tab := range props.Tabs {
|
||||
<div
|
||||
:id="$id(tabId + '-content')"
|
||||
x-show="tabContentActive($el)"
|
||||
class="relative"
|
||||
x-cloak
|
||||
id={ fmt.Sprintf("tab-%d", i) }
|
||||
class="tab-content relative hidden"
|
||||
>
|
||||
@tab.Content
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<script nonce={ templ.GetNonce(ctx) }>
|
||||
// Initialize tabs
|
||||
function initTabs(container) {
|
||||
const tabs = container.querySelectorAll('.tab-button')
|
||||
const contents = container.querySelectorAll('.tab-content')
|
||||
|
||||
// Activate the first tab
|
||||
if (tabs.length > 0) {
|
||||
tabs[0].classList.add('text-foreground', 'bg-background', 'shadow-sm')
|
||||
contents[0].classList.remove('hidden')
|
||||
}
|
||||
|
||||
// Click handler for all tabs
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
// Remove active classes
|
||||
tabs.forEach(t => t.classList.remove('text-foreground', 'bg-background', 'shadow-sm'))
|
||||
contents.forEach(c => c.classList.add('hidden'))
|
||||
|
||||
// Active tab
|
||||
tab.classList.add('text-foreground', 'bg-background', 'shadow-sm')
|
||||
const target = tab.dataset.tabTarget
|
||||
container.querySelector(`#${target}`).classList.remove('hidden')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Initialize all tab components
|
||||
document.querySelectorAll('.tabs-component').forEach(initTabs)
|
||||
</script>
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ package components
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Tab struct {
|
||||
ID string // Unique identifier for the tab
|
||||
Title string // Tab title
|
||||
@ -20,7 +22,6 @@ type TabsProps struct {
|
||||
ContentContainerClass string // Additional CSS classes for the content container
|
||||
}
|
||||
|
||||
// Navigation interface that organizes content into sections.
|
||||
func Tabs(props TabsProps) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
@ -42,18 +43,16 @@ func Tabs(props TabsProps) templ.Component {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-data=\"{ \n tabSelected: '1',\n tabId: $id('tabs'),\n tabButtonClicked(tabButton) {\n this.tabSelected = tabButton.id.replace(this.tabId + '-', '');\n this.tabRepositionMarker(tabButton);\n },\n tabRepositionMarker(tabButton) {\n this.$refs.tabMarker.style.width = tabButton.offsetWidth + 'px';\n this.$refs.tabMarker.style.height = tabButton.offsetHeight + 'px';\n this.$refs.tabMarker.style.left = tabButton.offsetLeft + 'px';\n },\n tabContentActive(tabContent) {\n return this.tabSelected === tabContent.id.replace(this.tabId + '-content-', '');\n }\n }\" x-init=\"$nextTick(() => tabRepositionMarker($refs.tabButtons.firstElementChild));\" class=\"relative\"><!-- Tabs buttons container -->")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"tabs-component\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 = []any{"relative flex items-center justify-center h-10 p-1 rounded-lg select-none",
|
||||
"bg-muted text-muted-foreground",
|
||||
props.TabsContainerClass}
|
||||
var templ_7745c5c3_Var2 = []any{"relative flex items-center justify-center h-10 p-1 rounded-lg select-none bg-muted text-muted-foreground", props.TabsContainerClass}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-ref=\"tabButtons\" class=\"")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -66,35 +65,48 @@ func Tabs(props TabsProps) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><!-- Individual tab buttons -->")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, tab := range props.Tabs {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button :id=\"$id(tabId)\" @click=\"tabButtonClicked($el);\" type=\"button\" class=\"relative z-20 flex-1 inline-flex items-center justify-center h-8 px-3 text-sm font-medium transition-all rounded-md cursor-pointer whitespace-nowrap\" :class=\"{'text-foreground bg-background shadow-sm': tabSelected === '{tab.ID}', 'hover:text-foreground': tabSelected !== '{tab.ID}'}\">")
|
||||
for i, tab := range props.Tabs {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button type=\"button\" data-tab-target=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(tab.Title)
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("tab-%d", i))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/tabs.templ`, Line: 53, Col: 16}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/tabs.templ`, Line: 23, Col: 47}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"tab-button relative z-20 flex-1 inline-flex items-center justify-center h-8 px-3 text-sm font-medium transition-all rounded-md cursor-pointer whitespace-nowrap hover:text-foreground\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(tab.Title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/tabs.templ`, Line: 26, Col: 16}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</button>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!-- Active tab marker --><div x-ref=\"tabMarker\" class=\"absolute left-0 z-10 h-full duration-300 ease-out\" x-cloak><div class=\"w-full h-full bg-background rounded-md shadow-sm\"></div></div></div><!-- Tab content container -->")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 = []any{"relative mt-2 content", props.ContentContainerClass}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
|
||||
var templ_7745c5c3_Var6 = []any{"relative mt-2", props.ContentContainerClass}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -102,21 +114,34 @@ func Tabs(props TabsProps) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var5).String())
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/tabs.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><!-- Individual tab content -->")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, tab := range props.Tabs {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div :id=\"$id(tabId + '-content')\" x-show=\"tabContentActive($el)\" class=\"relative\" x-cloak>")
|
||||
for i, tab := range props.Tabs {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("tab-%d", i))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/tabs.templ`, Line: 33, Col: 34}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"tab-content relative hidden\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -129,7 +154,20 @@ func Tabs(props TabsProps) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><script nonce=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.GetNonce(ctx))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/tabs.templ`, Line: 41, Col: 36}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">\n // Initialize tabs\n function initTabs(container) {\n const tabs = container.querySelectorAll('.tab-button')\n const contents = container.querySelectorAll('.tab-content')\n\n // Activate the first tab\n if (tabs.length > 0) {\n tabs[0].classList.add('text-foreground', 'bg-background', 'shadow-sm')\n contents[0].classList.remove('hidden')\n }\n\n // Click handler for all tabs\n tabs.forEach(tab => {\n tab.addEventListener('click', () => {\n // Remove active classes\n tabs.forEach(t => t.classList.remove('text-foreground', 'bg-background', 'shadow-sm'))\n contents.forEach(c => c.classList.add('hidden'))\n\n // Active tab\n tab.classList.add('text-foreground', 'bg-background', 'shadow-sm')\n const target = tab.dataset.tabTarget\n container.querySelector(`#${target}`).classList.remove('hidden')\n })\n })\n }\n\n // Initialize all tab components\n document.querySelectorAll('.tabs-component').forEach(initTabs)\n </script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user