1
0
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:
axzilla 2024-12-14 12:26:57 +07:00
parent 1a4db2aab7
commit d682d5f443
17 changed files with 559 additions and 254 deletions

View File

@ -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;
}

View File

@ -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))

View 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))
})
}

View File

@ -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))
})
}

View File

@ -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... }

View File

@ -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>
}

View File

@ -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{})

View File

@ -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
View 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)
}

View File

@ -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');

View File

@ -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) || [&#39;January&#39;, &#39;February&#39;, &#39;March&#39;, &#39;April&#39;, &#39;May&#39;, &#39;June&#39;, &#39;July&#39;, &#39;August&#39;, &#39;September&#39;, &#39;October&#39;, &#39;November&#39;, &#39;December&#39;],\n days: JSON.parse($el.dataset.daynames) || [&#39;Mo&#39;, &#39;Tu&#39;, &#39;We&#39;, &#39;Th&#39;, &#39;Fr&#39;, &#39;Sa&#39;, &#39;Su&#39;],\n\n position: &#39;bottom&#39;,\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(() =&gt; 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 &gt; viewportHeight &amp;&amp; rect.top &gt; popupRect.height) {\n this.position = &#39;top&#39;;\n } else {\n this.position = &#39;bottom&#39;;\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) =&gt; i);\n this.monthDays = Array.from({ length: daysInMonth }, (_, i) =&gt; 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 &#39;eu&#39;:\n\t\t\t\t\t\treturn `${parts[2]}-${parts[1]}-${parts[0]}`;\n\t\t\t\t\tcase &#39;us&#39;:\n\t\t\t\t\t\treturn `${parts[2]}-${parts[0]}-${parts[1]}`;\n\t\t\t\t\tcase &#39;uk&#39;:\n\t\t\t\t\t\treturn `${parts[2]}-${parts[1]}-${parts[0]}`;\n\t\t\t\t\tcase &#39;long&#39;:\n\t\t\t\t\tcase &#39;iso&#39;:\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, &#39;0&#39;);\n const m = (date.getMonth() + 1).toString().padStart(2, &#39;0&#39;);\n const y = date.getFullYear();\n\n switch(this.format) {\n case &#39;eu&#39;:\n return `${d}.${m}.${y}`;\n\t\t\t\t\t case &#39;uk&#39;:\n\t\t\t\t\t\t return `${d}/${m}/${y}`;\n case &#39;us&#39;:\n return `${m}/${d}/${y}`;\n case &#39;long&#39;:\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) || [&#39;January&#39;, &#39;February&#39;, &#39;March&#39;, &#39;April&#39;, &#39;May&#39;, &#39;June&#39;, &#39;July&#39;, &#39;August&#39;, &#39;September&#39;, &#39;October&#39;, &#39;November&#39;, &#39;December&#39;],\n days: JSON.parse($el.dataset.daynames) || [&#39;Mo&#39;, &#39;Tu&#39;, &#39;We&#39;, &#39;Th&#39;, &#39;Fr&#39;, &#39;Sa&#39;, &#39;Su&#39;],\n\n position: &#39;bottom&#39;,\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(() =&gt; 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 &gt; viewportHeight &amp;&amp; rect.top &gt; popupRect.height) {\n this.position = &#39;top&#39;;\n } else {\n this.position = &#39;bottom&#39;;\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) =&gt; i);\n this.monthDays = Array.from({ length: daysInMonth }, (_, i) =&gt; 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 &#39;eu&#39;:\n\t\t\t\t\t\treturn `${parts[2]}-${parts[1]}-${parts[0]}`;\n\t\t\t\t\tcase &#39;us&#39;:\n\t\t\t\t\t\treturn `${parts[2]}-${parts[0]}-${parts[1]}`;\n\t\t\t\t\tcase &#39;uk&#39;:\n\t\t\t\t\t\treturn `${parts[2]}-${parts[1]}-${parts[0]}`;\n\t\t\t\t\tcase &#39;long&#39;:\n\t\t\t\t\tcase &#39;iso&#39;:\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, &#39;0&#39;);\n const m = (date.getMonth() + 1).toString().padStart(2, &#39;0&#39;);\n const y = date.getFullYear();\n\n switch(this.format) {\n case &#39;eu&#39;:\n return `${d}.${m}.${y}`;\n\t\t\t\t\t case &#39;uk&#39;:\n\t\t\t\t\t\t return `${d}/${m}/${y}`;\n case &#39;us&#39;:\n return `${m}/${d}/${y}`;\n case &#39;long&#39;:\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
}

View File

@ -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>
}
}

View File

@ -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(&#39;open-modal&#39;, { 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(&#39;close-modal&#39;, { 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

View File

@ -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>

View File

@ -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: &#39;&#39;,\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 &#39;fixed inset-y-0 right-0 w-3/4 md:w-1/2 lg:w-1/3&#39;: side === &#39;right&#39;,\n &#39;fixed inset-y-0 left-0 w-3/4 md:w-1/2 lg:w-1/3&#39;: side === &#39;left&#39;,\n &#39;fixed inset-x-0 top-0 h-auto sm:h-1/2&#39;: side === &#39;top&#39;,\n &#39;fixed inset-x-0 bottom-0 h-auto sm:h-1/2&#39;: side === &#39;bottom&#39;\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
}

View File

@ -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>
}

View File

@ -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: &#39;1&#39;,\n tabId: $id(&#39;tabs&#39;),\n tabButtonClicked(tabButton) {\n this.tabSelected = tabButton.id.replace(this.tabId + &#39;-&#39;, &#39;&#39;);\n this.tabRepositionMarker(tabButton);\n },\n tabRepositionMarker(tabButton) {\n this.$refs.tabMarker.style.width = tabButton.offsetWidth + &#39;px&#39;;\n this.$refs.tabMarker.style.height = tabButton.offsetHeight + &#39;px&#39;;\n this.$refs.tabMarker.style.left = tabButton.offsetLeft + &#39;px&#39;;\n },\n tabContentActive(tabContent) {\n return this.tabSelected === tabContent.id.replace(this.tabId + &#39;-content-&#39;, &#39;&#39;);\n }\n }\" x-init=\"$nextTick(() =&gt; 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=\"{&#39;text-foreground bg-background shadow-sm&#39;: tabSelected === &#39;{tab.ID}&#39;, &#39;hover:text-foreground&#39;: tabSelected !== &#39;{tab.ID}&#39;}\">")
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 + &#39;-content&#39;)\" 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
}