Clean, modern building blocks. Copy and paste into your apps. Built with Go and templ. Open Source. Free forever.
Registry reference: these templ ports track the current shadcn/ui Base UI blocks. The public shadcn block previews can still show legacy new-york-v4/Radix variants, so their block-level details may differ.
package dashboard01 import "github.com/axadrn/shadcn-templ/v2/components/sidebar" templ Page() { @sidebar.Provider(sidebar.ProviderProps{ Attributes: templ.Attributes{ "style": "--sidebar-width: calc(var(--spacing) * 72); --sidebar-width-icon: 3rem; --header-height: calc(var(--spacing) * 12);", }, }) { @AppSidebar() @sidebar.Inset() { @SiteHeader() <div class="flex flex-1 flex-col"> <div class="@container/main flex flex-1 flex-col gap-2"> <div class="flex flex-col gap-4 py-4 md:gap-6 md:py-6"> @SectionCards() <div class="px-4 lg:px-6"> @ChartAreaInteractive() </div> @DataTable(Data) </div> </div> </div> } }}package dashboard01 // Item represents one row of data.json.type Item struct { ID int Header string SectionType string Status string Target string Limit string Reviewer string} // Data holds all rows of data.json verbatim.var Data = []Item{ {ID: 1, Header: "Cover page", SectionType: "Cover page", Status: "In Process", Target: "18", Limit: "5", Reviewer: "Eddie Lake"}, {ID: 2, Header: "Table of contents", SectionType: "Table of contents", Status: "Done", Target: "29", Limit: "24", Reviewer: "Eddie Lake"}, {ID: 3, Header: "Executive summary", SectionType: "Narrative", Status: "Done", Target: "10", Limit: "13", Reviewer: "Eddie Lake"}, {ID: 4, Header: "Technical approach", SectionType: "Narrative", Status: "Done", Target: "27", Limit: "23", Reviewer: "Jamik Tashpulatov"}, {ID: 5, Header: "Design", SectionType: "Narrative", Status: "In Process", Target: "2", Limit: "16", Reviewer: "Jamik Tashpulatov"}, {ID: 6, Header: "Capabilities", SectionType: "Narrative", Status: "In Process", Target: "20", Limit: "8", Reviewer: "Jamik Tashpulatov"}, {ID: 7, Header: "Integration with existing systems", SectionType: "Narrative", Status: "In Process", Target: "19", Limit: "21", Reviewer: "Jamik Tashpulatov"}, {ID: 8, Header: "Innovation and Advantages", SectionType: "Narrative", Status: "Done", Target: "25", Limit: "26", Reviewer: "Assign reviewer"}, {ID: 9, Header: "Overview of EMR's Innovative Solutions", SectionType: "Technical content", Status: "Done", Target: "7", Limit: "23", Reviewer: "Assign reviewer"}, {ID: 10, Header: "Advanced Algorithms and Machine Learning", SectionType: "Narrative", Status: "Done", Target: "30", Limit: "28", Reviewer: "Assign reviewer"}, {ID: 11, Header: "Adaptive Communication Protocols", SectionType: "Narrative", Status: "Done", Target: "9", Limit: "31", Reviewer: "Assign reviewer"}, {ID: 12, Header: "Advantages Over Current Technologies", SectionType: "Narrative", Status: "Done", Target: "12", Limit: "0", Reviewer: "Assign reviewer"}, {ID: 13, Header: "Past Performance", SectionType: "Narrative", Status: "Done", Target: "22", Limit: "33", Reviewer: "Assign reviewer"}, {ID: 14, Header: "Customer Feedback and Satisfaction Levels", SectionType: "Narrative", Status: "Done", Target: "15", Limit: "34", Reviewer: "Assign reviewer"}, {ID: 15, Header: "Implementation Challenges and Solutions", SectionType: "Narrative", Status: "Done", Target: "3", Limit: "35", Reviewer: "Assign reviewer"}, {ID: 16, Header: "Security Measures and Data Protection Policies", SectionType: "Narrative", Status: "In Process", Target: "6", Limit: "36", Reviewer: "Assign reviewer"}, {ID: 17, Header: "Scalability and Future Proofing", SectionType: "Narrative", Status: "Done", Target: "4", Limit: "37", Reviewer: "Assign reviewer"}, {ID: 18, Header: "Cost-Benefit Analysis", SectionType: "Plain language", Status: "Done", Target: "14", Limit: "38", Reviewer: "Assign reviewer"}, {ID: 19, Header: "User Training and Onboarding Experience", SectionType: "Narrative", Status: "Done", Target: "17", Limit: "39", Reviewer: "Assign reviewer"}, {ID: 20, Header: "Future Development Roadmap", SectionType: "Narrative", Status: "Done", Target: "11", Limit: "40", Reviewer: "Assign reviewer"}, {ID: 21, Header: "System Architecture Overview", SectionType: "Technical content", Status: "In Process", Target: "24", Limit: "18", Reviewer: "Maya Johnson"}, {ID: 22, Header: "Risk Management Plan", SectionType: "Narrative", Status: "Done", Target: "15", Limit: "22", Reviewer: "Carlos Rodriguez"}, {ID: 23, Header: "Compliance Documentation", SectionType: "Legal", Status: "In Process", Target: "31", Limit: "27", Reviewer: "Sarah Chen"}, {ID: 24, Header: "API Documentation", SectionType: "Technical content", Status: "Done", Target: "8", Limit: "12", Reviewer: "Raj Patel"}, {ID: 25, Header: "User Interface Mockups", SectionType: "Visual", Status: "In Process", Target: "19", Limit: "25", Reviewer: "Leila Ahmadi"}, {ID: 26, Header: "Database Schema", SectionType: "Technical content", Status: "Done", Target: "22", Limit: "20", Reviewer: "Thomas Wilson"}, {ID: 27, Header: "Testing Methodology", SectionType: "Technical content", Status: "In Process", Target: "17", Limit: "14", Reviewer: "Assign reviewer"}, {ID: 28, Header: "Deployment Strategy", SectionType: "Narrative", Status: "Done", Target: "26", Limit: "30", Reviewer: "Eddie Lake"}, {ID: 29, Header: "Budget Breakdown", SectionType: "Financial", Status: "In Process", Target: "13", Limit: "16", Reviewer: "Jamik Tashpulatov"}, {ID: 30, Header: "Market Analysis", SectionType: "Research", Status: "Done", Target: "29", Limit: "32", Reviewer: "Sophia Martinez"}, {ID: 31, Header: "Competitor Comparison", SectionType: "Research", Status: "In Process", Target: "21", Limit: "19", Reviewer: "Assign reviewer"}, {ID: 32, Header: "Maintenance Plan", SectionType: "Technical content", Status: "Done", Target: "16", Limit: "23", Reviewer: "Alex Thompson"}, {ID: 33, Header: "User Personas", SectionType: "Research", Status: "In Process", Target: "27", Limit: "24", Reviewer: "Nina Patel"}, {ID: 34, Header: "Accessibility Compliance", SectionType: "Legal", Status: "Done", Target: "18", Limit: "21", Reviewer: "Assign reviewer"}, {ID: 35, Header: "Performance Metrics", SectionType: "Technical content", Status: "In Process", Target: "23", Limit: "26", Reviewer: "David Kim"}, {ID: 36, Header: "Disaster Recovery Plan", SectionType: "Technical content", Status: "Done", Target: "14", Limit: "17", Reviewer: "Jamik Tashpulatov"}, {ID: 37, Header: "Third-party Integrations", SectionType: "Technical content", Status: "In Process", Target: "25", Limit: "28", Reviewer: "Eddie Lake"}, {ID: 38, Header: "User Feedback Summary", SectionType: "Research", Status: "Done", Target: "20", Limit: "15", Reviewer: "Assign reviewer"}, {ID: 39, Header: "Localization Strategy", SectionType: "Narrative", Status: "In Process", Target: "12", Limit: "19", Reviewer: "Maria Garcia"}, {ID: 40, Header: "Mobile Compatibility", SectionType: "Technical content", Status: "Done", Target: "28", Limit: "31", Reviewer: "James Wilson"}, {ID: 41, Header: "Data Migration Plan", SectionType: "Technical content", Status: "In Process", Target: "19", Limit: "22", Reviewer: "Assign reviewer"}, {ID: 42, Header: "Quality Assurance Protocols", SectionType: "Technical content", Status: "Done", Target: "30", Limit: "33", Reviewer: "Priya Singh"}, {ID: 43, Header: "Stakeholder Analysis", SectionType: "Research", Status: "In Process", Target: "11", Limit: "14", Reviewer: "Eddie Lake"}, {ID: 44, Header: "Environmental Impact Assessment", SectionType: "Research", Status: "Done", Target: "24", Limit: "27", Reviewer: "Assign reviewer"}, {ID: 45, Header: "Intellectual Property Rights", SectionType: "Legal", Status: "In Process", Target: "17", Limit: "20", Reviewer: "Sarah Johnson"}, {ID: 46, Header: "Customer Support Framework", SectionType: "Narrative", Status: "Done", Target: "22", Limit: "25", Reviewer: "Jamik Tashpulatov"}, {ID: 47, Header: "Version Control Strategy", SectionType: "Technical content", Status: "In Process", Target: "15", Limit: "18", Reviewer: "Assign reviewer"}, {ID: 48, Header: "Continuous Integration Pipeline", SectionType: "Technical content", Status: "Done", Target: "26", Limit: "29", Reviewer: "Michael Chen"}, {ID: 49, Header: "Regulatory Compliance", SectionType: "Legal", Status: "In Process", Target: "13", Limit: "16", Reviewer: "Assign reviewer"}, {ID: 50, Header: "User Authentication System", SectionType: "Technical content", Status: "Done", Target: "28", Limit: "31", Reviewer: "Eddie Lake"}, {ID: 51, Header: "Data Analytics Framework", SectionType: "Technical content", Status: "In Process", Target: "21", Limit: "24", Reviewer: "Jamik Tashpulatov"}, {ID: 52, Header: "Cloud Infrastructure", SectionType: "Technical content", Status: "Done", Target: "16", Limit: "19", Reviewer: "Assign reviewer"}, {ID: 53, Header: "Network Security Measures", SectionType: "Technical content", Status: "In Process", Target: "29", Limit: "32", Reviewer: "Lisa Wong"}, {ID: 54, Header: "Project Timeline", SectionType: "Planning", Status: "Done", Target: "14", Limit: "17", Reviewer: "Eddie Lake"}, {ID: 55, Header: "Resource Allocation", SectionType: "Planning", Status: "In Process", Target: "27", Limit: "30", Reviewer: "Assign reviewer"}, {ID: 56, Header: "Team Structure and Roles", SectionType: "Planning", Status: "Done", Target: "20", Limit: "23", Reviewer: "Jamik Tashpulatov"}, {ID: 57, Header: "Communication Protocols", SectionType: "Planning", Status: "In Process", Target: "15", Limit: "18", Reviewer: "Assign reviewer"}, {ID: 58, Header: "Success Metrics", SectionType: "Planning", Status: "Done", Target: "30", Limit: "33", Reviewer: "Eddie Lake"}, {ID: 59, Header: "Internationalization Support", SectionType: "Technical content", Status: "In Process", Target: "23", Limit: "26", Reviewer: "Jamik Tashpulatov"}, {ID: 60, Header: "Backup and Recovery Procedures", SectionType: "Technical content", Status: "Done", Target: "18", Limit: "21", Reviewer: "Assign reviewer"}, {ID: 61, Header: "Monitoring and Alerting System", SectionType: "Technical content", Status: "In Process", Target: "25", Limit: "28", Reviewer: "Daniel Park"}, {ID: 62, Header: "Code Review Guidelines", SectionType: "Technical content", Status: "Done", Target: "12", Limit: "15", Reviewer: "Eddie Lake"}, {ID: 63, Header: "Documentation Standards", SectionType: "Technical content", Status: "In Process", Target: "27", Limit: "30", Reviewer: "Jamik Tashpulatov"}, {ID: 64, Header: "Release Management Process", SectionType: "Planning", Status: "Done", Target: "22", Limit: "25", Reviewer: "Assign reviewer"}, {ID: 65, Header: "Feature Prioritization Matrix", SectionType: "Planning", Status: "In Process", Target: "19", Limit: "22", Reviewer: "Emma Davis"}, {ID: 66, Header: "Technical Debt Assessment", SectionType: "Technical content", Status: "Done", Target: "24", Limit: "27", Reviewer: "Eddie Lake"}, {ID: 67, Header: "Capacity Planning", SectionType: "Planning", Status: "In Process", Target: "21", Limit: "24", Reviewer: "Jamik Tashpulatov"}, {ID: 68, Header: "Service Level Agreements", SectionType: "Legal", Status: "Done", Target: "26", Limit: "29", Reviewer: "Assign reviewer"},}package dashboard01 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type User struct { Name string Email string Avatar string} type NavItem struct { Title string URL string Icon func(...icon.Props) templ.Component} type NavCloudSubItem struct { Title string URL string} type NavCloudItem struct { Title string Icon func(...icon.Props) templ.Component IsActive bool URL string Items []NavCloudSubItem} type DocumentItem struct { Name string URL string Icon func(...icon.Props) templ.Component} var appSidebarUser = User{ Name: "Axel Adrian", Email: "[email protected]", Avatar: "https://github.com/axadrn.png",} var appSidebarNavMain = []NavItem{ {Title: "Dashboard", URL: "#", Icon: icon.LayoutDashboard}, {Title: "Lifecycle", URL: "#", Icon: icon.List}, {Title: "Analytics", URL: "#", Icon: icon.ChartBar}, {Title: "Projects", URL: "#", Icon: icon.Folder}, {Title: "Team", URL: "#", Icon: icon.Users},} // The pendant of the tsx data.navClouds: like in the tsx it is defined but// never rendered by AppSidebar (kept anonymous so linters stay quiet).var _ = []NavCloudItem{ { Title: "Capture", Icon: icon.Camera, IsActive: true, URL: "#", Items: []NavCloudSubItem{ {Title: "Active Proposals", URL: "#"}, {Title: "Archived", URL: "#"}, }, }, { Title: "Proposal", Icon: icon.FileText, URL: "#", Items: []NavCloudSubItem{ {Title: "Active Proposals", URL: "#"}, {Title: "Archived", URL: "#"}, }, }, { Title: "Prompts", Icon: icon.FileText, URL: "#", Items: []NavCloudSubItem{ {Title: "Active Proposals", URL: "#"}, {Title: "Archived", URL: "#"}, }, },} var appSidebarNavSecondary = []NavItem{ {Title: "Settings", URL: "#", Icon: icon.Settings2}, {Title: "Get Help", URL: "#", Icon: icon.CircleQuestionMark}, {Title: "Search", URL: "#", Icon: icon.Search},} var appSidebarDocuments = []DocumentItem{ {Name: "Data Library", URL: "#", Icon: icon.Database}, {Name: "Reports", URL: "#", Icon: icon.FileChartColumn}, {Name: "Word Assistant", URL: "#", Icon: icon.File},} templ AppSidebar() { @sidebar.Sidebar(sidebar.Props{Collapsible: sidebar.CollapsibleOffcanvas, Variant: sidebar.VariantInset}) { @sidebar.Header() { @sidebar.Menu() { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Class: "data-[slot=sidebar-menu-button]:p-1.5!", Href: "#", }) { @icon.Command(icon.Props{Class: "size-5!"}) <span class="text-base font-semibold">Acme Inc.</span> } } } } @sidebar.Content() { @NavMain(appSidebarNavMain) @NavDocuments(appSidebarDocuments) @NavSecondary(appSidebarNavSecondary, "mt-auto") } @sidebar.Footer() { @NavUser(appSidebarUser) } }}package dashboard01 import ( "time" "github.com/axadrn/shadcn-templ/v2/components/card" "github.com/axadrn/shadcn-templ/v2/components/chart" selectcomp "github.com/axadrn/shadcn-templ/v2/components/select" "github.com/axadrn/shadcn-templ/v2/components/togglegroup" "github.com/axadrn/shadcn-templ/v2/utils") // Interactive area-chart data. var chartAreaInteractiveData = []chart.Datum{ {"date": "2024-04-01", "desktop": 222, "mobile": 150}, {"date": "2024-04-02", "desktop": 97, "mobile": 180}, {"date": "2024-04-03", "desktop": 167, "mobile": 120}, {"date": "2024-04-04", "desktop": 242, "mobile": 260}, {"date": "2024-04-05", "desktop": 373, "mobile": 290}, {"date": "2024-04-06", "desktop": 301, "mobile": 340}, {"date": "2024-04-07", "desktop": 245, "mobile": 180}, {"date": "2024-04-08", "desktop": 409, "mobile": 320}, {"date": "2024-04-09", "desktop": 59, "mobile": 110}, {"date": "2024-04-10", "desktop": 261, "mobile": 190}, {"date": "2024-04-11", "desktop": 327, "mobile": 350}, {"date": "2024-04-12", "desktop": 292, "mobile": 210}, {"date": "2024-04-13", "desktop": 342, "mobile": 380}, {"date": "2024-04-14", "desktop": 137, "mobile": 220}, {"date": "2024-04-15", "desktop": 120, "mobile": 170}, {"date": "2024-04-16", "desktop": 138, "mobile": 190}, {"date": "2024-04-17", "desktop": 446, "mobile": 360}, {"date": "2024-04-18", "desktop": 364, "mobile": 410}, {"date": "2024-04-19", "desktop": 243, "mobile": 180}, {"date": "2024-04-20", "desktop": 89, "mobile": 150}, {"date": "2024-04-21", "desktop": 137, "mobile": 200}, {"date": "2024-04-22", "desktop": 224, "mobile": 170}, {"date": "2024-04-23", "desktop": 138, "mobile": 230}, {"date": "2024-04-24", "desktop": 387, "mobile": 290}, {"date": "2024-04-25", "desktop": 215, "mobile": 250}, {"date": "2024-04-26", "desktop": 75, "mobile": 130}, {"date": "2024-04-27", "desktop": 383, "mobile": 420}, {"date": "2024-04-28", "desktop": 122, "mobile": 180}, {"date": "2024-04-29", "desktop": 315, "mobile": 240}, {"date": "2024-04-30", "desktop": 454, "mobile": 380}, {"date": "2024-05-01", "desktop": 165, "mobile": 220}, {"date": "2024-05-02", "desktop": 293, "mobile": 310}, {"date": "2024-05-03", "desktop": 247, "mobile": 190}, {"date": "2024-05-04", "desktop": 385, "mobile": 420}, {"date": "2024-05-05", "desktop": 481, "mobile": 390}, {"date": "2024-05-06", "desktop": 498, "mobile": 520}, {"date": "2024-05-07", "desktop": 388, "mobile": 300}, {"date": "2024-05-08", "desktop": 149, "mobile": 210}, {"date": "2024-05-09", "desktop": 227, "mobile": 180}, {"date": "2024-05-10", "desktop": 293, "mobile": 330}, {"date": "2024-05-11", "desktop": 335, "mobile": 270}, {"date": "2024-05-12", "desktop": 197, "mobile": 240}, {"date": "2024-05-13", "desktop": 197, "mobile": 160}, {"date": "2024-05-14", "desktop": 448, "mobile": 490}, {"date": "2024-05-15", "desktop": 473, "mobile": 380}, {"date": "2024-05-16", "desktop": 338, "mobile": 400}, {"date": "2024-05-17", "desktop": 499, "mobile": 420}, {"date": "2024-05-18", "desktop": 315, "mobile": 350}, {"date": "2024-05-19", "desktop": 235, "mobile": 180}, {"date": "2024-05-20", "desktop": 177, "mobile": 230}, {"date": "2024-05-21", "desktop": 82, "mobile": 140}, {"date": "2024-05-22", "desktop": 81, "mobile": 120}, {"date": "2024-05-23", "desktop": 252, "mobile": 290}, {"date": "2024-05-24", "desktop": 294, "mobile": 220}, {"date": "2024-05-25", "desktop": 201, "mobile": 250}, {"date": "2024-05-26", "desktop": 213, "mobile": 170}, {"date": "2024-05-27", "desktop": 420, "mobile": 460}, {"date": "2024-05-28", "desktop": 233, "mobile": 190}, {"date": "2024-05-29", "desktop": 78, "mobile": 130}, {"date": "2024-05-30", "desktop": 340, "mobile": 280}, {"date": "2024-05-31", "desktop": 178, "mobile": 230}, {"date": "2024-06-01", "desktop": 178, "mobile": 200}, {"date": "2024-06-02", "desktop": 470, "mobile": 410}, {"date": "2024-06-03", "desktop": 103, "mobile": 160}, {"date": "2024-06-04", "desktop": 439, "mobile": 380}, {"date": "2024-06-05", "desktop": 88, "mobile": 140}, {"date": "2024-06-06", "desktop": 294, "mobile": 250}, {"date": "2024-06-07", "desktop": 323, "mobile": 370}, {"date": "2024-06-08", "desktop": 385, "mobile": 320}, {"date": "2024-06-09", "desktop": 438, "mobile": 480}, {"date": "2024-06-10", "desktop": 155, "mobile": 200}, {"date": "2024-06-11", "desktop": 92, "mobile": 150}, {"date": "2024-06-12", "desktop": 492, "mobile": 420}, {"date": "2024-06-13", "desktop": 81, "mobile": 130}, {"date": "2024-06-14", "desktop": 426, "mobile": 380}, {"date": "2024-06-15", "desktop": 307, "mobile": 350}, {"date": "2024-06-16", "desktop": 371, "mobile": 310}, {"date": "2024-06-17", "desktop": 475, "mobile": 520}, {"date": "2024-06-18", "desktop": 107, "mobile": 170}, {"date": "2024-06-19", "desktop": 341, "mobile": 290}, {"date": "2024-06-20", "desktop": 408, "mobile": 450}, {"date": "2024-06-21", "desktop": 169, "mobile": 210}, {"date": "2024-06-22", "desktop": 317, "mobile": 270}, {"date": "2024-06-23", "desktop": 480, "mobile": 530}, {"date": "2024-06-24", "desktop": 132, "mobile": 180}, {"date": "2024-06-25", "desktop": 141, "mobile": 190}, {"date": "2024-06-26", "desktop": 434, "mobile": 380}, {"date": "2024-06-27", "desktop": 448, "mobile": 490}, {"date": "2024-06-28", "desktop": 149, "mobile": 200}, {"date": "2024-06-29", "desktop": 103, "mobile": 160}, {"date": "2024-06-30", "desktop": 446, "mobile": 400},} var chartAreaInteractiveConfig = chart.Config{ {Key: "visitors", Label: "Visitors"}, {Key: "desktop", Label: "Desktop", Color: "var(--primary)"}, {Key: "mobile", Label: "Mobile", Color: "var(--primary)"},} // chartAreaInteractiveFilteredData is the time range filter: the last n// days counted back from 2024-06-30.func chartAreaInteractiveFilteredData(days int) []chart.Datum { ref, _ := time.Parse("2006-01-02", "2024-06-30") start := ref.AddDate(0, 0, -days) var out []chart.Datum for _, d := range chartAreaInteractiveData { t, _ := time.Parse("2006-01-02", d["date"].(string)) if !t.Before(start) { out = append(out, d) } } return out} // chartAreaInteractiveShortDate formats like the tsx tickFormatter: "Apr 1".func chartAreaInteractiveShortDate(v any) string { t, _ := time.Parse("2006-01-02", v.(string)) return t.Format("Jan 2")} var chartAreaInteractiveRanges = []struct { Key string Label string Days int}{ {"90d", "Last 3 months", 90}, {"30d", "Last 30 days", 30}, {"7d", "Last 7 days", 7},} templ ChartAreaInteractive() { @card.Card(card.Props{ Class: "@container/card", Attributes: templ.Attributes{"data-dashboard01-chart-range-card": ""}, }) { @card.Header() { @card.Title() { Total Visitors } @card.Description() { <span class="hidden @[540px]/card:block"> Total for the last 3 months </span> <span class="@[540px]/card:hidden">Last 3 months</span> } @card.Action() { @togglegroup.ToggleGroup(togglegroup.Props{ Variant: togglegroup.VariantOutline, DefaultValue: []string{"90d"}, // Spacing 0 is the joined bar of the TSX ToggleGroup default; // px-4! beats the joined mode's px-2 like upstream. Spacing: utils.Ptr(0), Class: "hidden *:data-[slot=toggle-group-item]:px-4! @[767px]/card:flex", Attributes: templ.Attributes{"data-tui-chart-range-toggle": "dashboard01-area-interactive"}, }) { for _, rng := range chartAreaInteractiveRanges { @togglegroup.Item(togglegroup.ItemProps{Value: rng.Key}) { { rng.Label } } } } @selectcomp.Select(selectcomp.Props{DefaultValue: "90d"}) { @selectcomp.Trigger(selectcomp.TriggerProps{ Class: "flex w-40 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate @[767px]/card:hidden", Size: selectcomp.SizeSm, Attributes: templ.Attributes{"aria-label": "Select a value", "data-tui-chart-range-select": "dashboard01-area-interactive"}, }) { @selectcomp.Value(selectcomp.ValueProps{Placeholder: "Last 3 months"}) } @selectcomp.Content(selectcomp.ContentProps{Class: "rounded-xl"}) { @selectcomp.Group() { for _, rng := range chartAreaInteractiveRanges { @selectcomp.Item(selectcomp.ItemProps{Value: rng.Key, Class: "rounded-lg"}) { { rng.Label } } } } } } } } @card.Content(card.ContentProps{Class: "px-2 pt-4 sm:px-6 sm:pt-6"}) { @chart.Container(chart.ContainerProps{ID: "dashboard01-area-interactive", Config: chartAreaInteractiveConfig, Class: "aspect-auto h-[250px] w-full"}) { // All three ranges render up front, the chart script toggles // them on range change (the pendant of the timeRange state). for _, rng := range chartAreaInteractiveRanges { <div data-tui-chart-range={ rng.Key } hidden?={ rng.Key != "90d" } style="width:100%;height:100%"> @chart.AreaChart(chart.AreaChartProps{ Data: chartAreaInteractiveFilteredData(rng.Days), }) { @chart.Defs() { @chart.LinearGradient(chart.LinearGradientProps{ID: "fillDesktop", X1: "0", Y1: "0", X2: "0", Y2: "1"}) { <stop offset="5%" stop-color="var(--color-desktop)" stop-opacity="1.0"></stop> <stop offset="95%" stop-color="var(--color-desktop)" stop-opacity="0.1"></stop> } @chart.LinearGradient(chart.LinearGradientProps{ID: "fillMobile", X1: "0", Y1: "0", X2: "0", Y2: "1"}) { <stop offset="5%" stop-color="var(--color-mobile)" stop-opacity="0.8"></stop> <stop offset="95%" stop-color="var(--color-mobile)" stop-opacity="0.1"></stop> } } @chart.CartesianGrid(chart.CartesianGridProps{Vertical: chart.Bool(false)}) @chart.XAxis(chart.XAxisProps{ DataKey: "date", TickMargin: 8, MinTickGap: 32, TickFormatter: chartAreaInteractiveShortDate, }) @chart.Tooltip(chart.TooltipProps{ Cursor: chart.Bool(false), Content: chart.TooltipContentProps{ Indicator: "dot", LabelFormatter: chartAreaInteractiveShortDate, }, }) @chart.Area(chart.AreaProps{ DataKey: "mobile", Type: chart.CurveNatural, Fill: "url(#fillMobile)", Stroke: "var(--color-mobile)", StackID: "a", }) @chart.Area(chart.AreaProps{ DataKey: "desktop", Type: chart.CurveNatural, Fill: "url(#fillDesktop)", Stroke: "var(--color-desktop)", StackID: "a", }) } </div> } } } } <script nonce={ templ.GetNonce(ctx) }> (() => { // The two range controls share one state like the TSX's timeRange: // a toggle pick forwards to the matching select item, so select.js // stays the single owner of value, trigger label and the // select-change event chart.js flips the panels on. if (window.__tuiDashboard01ChartRange) return; window.__tuiDashboard01ChartRange = true; const mobile = window.matchMedia("(max-width: 767px)"); const selectContent = (trigger) => { const id = trigger && trigger.getAttribute("aria-controls"); if (!id) return null; const live = document.getElementById(id); if (live) return live; for (const template of document.querySelectorAll("template[data-tui-select-portal]")) { const content = [...template.content.querySelectorAll("[data-tui-select-content]")].find((item) => item.id === id); if (content) return content; } return null; }; // useIsMobile sets 7d whenever the viewport enters mobile. Update the // same uncontrolled select state without opening or focusing it. const setRange = (card, value) => { const trigger = card && card.querySelector("[data-tui-chart-range-select]"); const content = selectContent(trigger); const item = content && content.querySelector('[data-tui-select-item][data-tui-select-value="' + value + '"]'); if (!trigger || !item) return; content.querySelectorAll("[data-tui-select-item]").forEach((candidate) => { const selected = candidate === item; candidate.toggleAttribute("data-selected", selected); candidate.setAttribute("aria-selected", String(selected)); }); const label = item.getAttribute("data-tui-select-label") || item.textContent.trim(); const valueNode = trigger.querySelector("[data-tui-select-value]"); if (valueNode) valueNode.textContent = label; trigger.removeAttribute("data-placeholder"); trigger.dispatchEvent(new CustomEvent("select-change", { bubbles: true, detail: { value, label }, })); }; const syncMobileRange = () => { if (!mobile.matches) return; document.querySelectorAll("[data-dashboard01-chart-range-card]").forEach((card) => setRange(card, "7d")); }; if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", syncMobileRange, { once: true }); else syncMobileRange(); mobile.addEventListener("change", syncMobileRange); document.addEventListener("toggle-change", (e) => { if (!(e.target instanceof Element)) return; const group = e.target.closest("[data-tui-chart-range-toggle]"); if (!group) return; const value = e.detail && e.detail.value; if (!value) return; // Single-select groups allow unpressing the active item; a // time range must stay active, so re-press it. const toggle = e.target.closest("[data-tui-toggle]"); if (toggle && e.detail.pressed === false) { toggle.setAttribute("data-pressed", ""); toggle.setAttribute("aria-pressed", "true"); } const card = group.closest("[data-slot=card]"); const trigger = card && card.querySelector("[data-tui-chart-range-select]"); const content = trigger && document.getElementById(trigger.getAttribute("aria-controls")); const item = content && content.querySelector('[data-tui-select-item][data-tui-select-value="' + value + '"]'); if (item) item.click(); }); // A pick in the mobile select presses the matching toggle; // chart.js flips the panels on the same select-change. document.addEventListener("select-change", (e) => { if (!(e.target instanceof Element)) return; const trigger = e.target.closest("[data-tui-chart-range-select]"); if (!trigger) return; const card = trigger.closest("[data-slot=card]"); const group = card && card.querySelector("[data-tui-chart-range-toggle]"); if (!group) return; group.querySelectorAll("[data-tui-toggle]").forEach((t) => { const on = t.getAttribute("data-tui-toggle-value") === e.detail.value; t.toggleAttribute("data-pressed", on); t.setAttribute("aria-pressed", String(on)); }); group.setAttribute("data-tui-toggle-group-value", e.detail.value); }); })(); </script>}package dashboard01 import ( "fmt" "github.com/axadrn/shadcn-templ/v2/components/badge" "github.com/axadrn/shadcn-templ/v2/components/button" "github.com/axadrn/shadcn-templ/v2/components/chart" "github.com/axadrn/shadcn-templ/v2/components/checkbox" "github.com/axadrn/shadcn-templ/v2/components/drawer" "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/input" "github.com/axadrn/shadcn-templ/v2/components/label" selectcomp "github.com/axadrn/shadcn-templ/v2/components/select" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/skeleton" "github.com/axadrn/shadcn-templ/v2/components/table" "github.com/axadrn/shadcn-templ/v2/components/tabs" "github.com/axadrn/shadcn-templ/v2/utils") // dataTablePageSize is the pendant of the pagination state's pageSize.const dataTablePageSize = 10 // dataTableHideableColumns are the columns the Columns menu// lists in the tsx: every accessor column that can hide.var dataTableHideableColumns = []string{"type", "status", "target", "limit", "reviewer"} var dataTableViews = []struct { Value string Label string}{ {"outline", "Outline"}, {"past-performance", "Past Performance"}, {"key-personnel", "Key Personnel"}, {"focus-documents", "Focus Documents"},} templ DataTable(data []Item) { // Pagination state lives in the URL (see data_table.go); the window, // page count and button states are computed server-side. {{ page, size := dataTablePageParams(ctx, len(data)) }} {{ pageCount := dataTablePageCount(len(data), size) }} // The swappable fragment: a fragment-aware handler (?fragment=data-table // + templ.WithFragments) renders just this container for the script's // partial updates; everywhere else the wrapper is transparent. @templ.Fragment("data-table") { @tabs.Tabs(tabs.Props{ DefaultValue: "outline", Class: "w-full flex-col justify-start gap-6", Attributes: templ.Attributes{"data-dashboard01-table": ""}, }) { <div class="flex items-center justify-between px-4 lg:px-6"> @label.Label(label.Props{For: "view-selector", Class: "sr-only"}) { View } // The view select is static: it mirrors the tabs but does not // switch them (the tsx does not wire it to the tabs either). @selectcomp.Select(selectcomp.Props{DefaultValue: "outline"}) { @selectcomp.Trigger(selectcomp.TriggerProps{ Class: "flex w-fit @4xl/main:hidden", Size: selectcomp.SizeSm, ID: "view-selector", }) { @selectcomp.Value(selectcomp.ValueProps{Placeholder: "Select a view"}) } @selectcomp.Content() { @selectcomp.Group() { for _, view := range dataTableViews { @selectcomp.Item(selectcomp.ItemProps{Value: view.Value}) { { view.Label } } } } } } @tabs.List(tabs.ListProps{Class: "hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:bg-muted-foreground/30 **:data-[slot=badge]:px-1 @4xl/main:flex"}) { @tabs.Trigger(tabs.TriggerProps{Value: "outline"}) { Outline } @tabs.Trigger(tabs.TriggerProps{Value: "past-performance"}) { Past Performance @badge.Badge(badge.Props{Variant: badge.VariantSecondary}) { 3 } } @tabs.Trigger(tabs.TriggerProps{Value: "key-personnel"}) { Key Personnel @badge.Badge(badge.Props{Variant: badge.VariantSecondary}) { 2 } } @tabs.Trigger(tabs.TriggerProps{Value: "focus-documents"}) { Focus Documents } } <div class="flex items-center gap-2"> @dropdownmenu.DropdownMenu() { @button.Button(button.Props{ Variant: button.VariantOutline, Size: button.SizeSm, Attributes: dropdownmenu.Trigger(ctx), }) { @icon.Columns3(icon.Props{Attributes: templ.Attributes{"data-icon": "inline-start"}}) Columns @icon.ChevronDown(icon.Props{Attributes: templ.Attributes{"data-icon": "inline-end"}}) } @dropdownmenu.Content(dropdownmenu.ContentProps{Align: dropdownmenu.AlignEnd, Class: "w-32"}) { // The columnVisibility pendant: dataTableScript toggles // the column's cells; the attribute carries the cell // index (after the drag, select and header columns). for i, column := range dataTableHideableColumns { @dropdownmenu.CheckboxItem(dropdownmenu.CheckboxItemProps{ Class: "capitalize", DefaultChecked: true, Attributes: templ.Attributes{"data-dashboard01-column": fmt.Sprint(i + 3)}, }) { { column } } } } } @button.Button(button.Props{Variant: button.VariantOutline, Size: button.SizeSm}) { @icon.Plus() <span class="hidden lg:inline">Add Section</span> } </div> </div> @tabs.Content(tabs.ContentProps{ Value: "outline", Class: "relative flex flex-col gap-4 overflow-auto px-4 lg:px-6", }) { <div class="overflow-hidden rounded-lg border"> @table.Table() { @table.Header(table.HeaderProps{Class: "sticky top-0 z-10 bg-muted"}) { @table.Row() { @table.Head() @table.Head() { <div class="flex items-center justify-center"> @checkbox.Checkbox(checkbox.Props{ Attributes: templ.Attributes{"aria-label": "Select all"}, }) </div> } @table.Head() { Header } @table.Head() { Section Type } @table.Head() { Status } @table.Head() { <div class="w-full text-right">Target</div> } @table.Head() { <div class="w-full text-right">Limit</div> } @table.Head() { Reviewer } @table.Head() } } @table.Body(table.BodyProps{ Class: "**:data-[slot=table-cell]:first:w-8", Attributes: templ.Attributes{"data-dashboard01-sortable": ""}, }) { if len(data) > 0 { for _, item := range dataTablePageWindow(data, page, size) { @dataTableRow(item, page, size) } } else { @table.Row() { @table.Cell(table.CellProps{ Class: "h-24 text-center", Attributes: templ.Attributes{"colspan": "9"}, }) { No results. } } } } } </div> <div class="flex items-center justify-between px-4"> <div class="hidden flex-1 text-sm text-muted-foreground lg:flex"> // Selection is view-local UI state. dataTableScript keeps the // count live and resets it on page navigation. <span data-dashboard01-selected-count>0</span> of { fmt.Sprint(len(data)) } row(s) selected. </div> <div class="flex w-full items-center gap-8 lg:w-fit"> // The rows-per-page pick is declarative htmx: the select // syncs its hidden input[name=size], whose change event // bubbles to this wrapper and fetches page one with the // new size (the setPageSize pendant, reset like tanstack). <div class="hidden items-center gap-2 lg:flex" hx-get="?page=1&fragment=data-table" hx-trigger="change" hx-include="input[name='size']" hx-target="[data-dashboard01-table]" hx-swap="outerHTML" > @label.Label(label.Props{For: "rows-per-page", Class: "text-sm font-medium"}) { Rows per page } // The tsx passes side="top" to the SelectContent; the // templ select has no side prop, the content aligns to // the trigger. @selectcomp.Select(selectcomp.Props{DefaultValue: fmt.Sprint(size), Name: "size"}) { @selectcomp.Trigger(selectcomp.TriggerProps{ Size: selectcomp.SizeSm, Class: "w-20", ID: "rows-per-page", }) { @selectcomp.Value(selectcomp.ValueProps{Placeholder: fmt.Sprint(size)}) } @selectcomp.Content() { @selectcomp.Group() { for _, pageSize := range dataTablePageSizes { @selectcomp.Item(selectcomp.ItemProps{Value: fmt.Sprint(pageSize)}) { { fmt.Sprint(pageSize) } } } } } } </div> <div class="flex w-fit items-center justify-center text-sm font-medium"> Page { fmt.Sprint(page) } of { fmt.Sprint(pageCount) } </div> <div class="ml-auto flex items-center gap-2 lg:ml-0"> // The nav buttons are links onto the page's own URL // (?page=&size=), disabled states computed server-side - // the setPageIndex/getCanPreviousPage pendant. htmx // swaps just the data-table fragment; without JS the // links navigate normally. @button.Button(button.Props{ Variant: button.VariantOutline, Class: "hidden h-8 w-8 p-0 lg:flex", Href: dataTablePageHref(1, size), Disabled: page == 1, Attributes: dataTableHxAttrs(dataTablePageHref(1, size)), }) { <span class="sr-only">Go to first page</span> @icon.ChevronsLeft() } @button.Button(button.Props{ Variant: button.VariantOutline, Class: "size-8", Size: button.SizeIcon, Href: dataTablePageHref(page-1, size), Disabled: page == 1, Attributes: dataTableHxAttrs(dataTablePageHref(page-1, size)), }) { <span class="sr-only">Go to previous page</span> @icon.ChevronLeft() } @button.Button(button.Props{ Variant: button.VariantOutline, Class: "size-8", Size: button.SizeIcon, Href: dataTablePageHref(page+1, size), Disabled: page == pageCount, Attributes: dataTableHxAttrs(dataTablePageHref(page+1, size)), }) { <span class="sr-only">Go to next page</span> @icon.ChevronRight() } @button.Button(button.Props{ Variant: button.VariantOutline, Class: "hidden size-8 lg:flex", Size: button.SizeIcon, Href: dataTablePageHref(pageCount, size), Disabled: page == pageCount, Attributes: dataTableHxAttrs(dataTablePageHref(pageCount, size)), }) { <span class="sr-only">Go to last page</span> @icon.ChevronsRight() } </div> </div> </div> } @tabs.Content(tabs.ContentProps{ Value: "past-performance", Class: "flex flex-col px-4 lg:px-6", }) { <div class="aspect-video w-full flex-1 rounded-lg border border-dashed"></div> } @tabs.Content(tabs.ContentProps{Value: "key-personnel", Class: "flex flex-col px-4 lg:px-6"}) { <div class="aspect-video w-full flex-1 rounded-lg border border-dashed"></div> } @tabs.Content(tabs.ContentProps{ Value: "focus-documents", Class: "flex flex-col px-4 lg:px-6", }) { <div class="aspect-video w-full flex-1 rounded-lg border border-dashed"></div> } } } @dataTableScript()} // dataTableScript owns the view-local interactions from the TSX: column// visibility, row selection and sortable rows. Pagination stays in the URL.templ dataTableScript() { <script nonce={ templ.GetNonce(ctx) }> (() => { if (window.__tuiDashboard01DataTable) return; window.__tuiDashboard01DataTable = true; let draggedRow = null; let keyboardOrigin = null; // Pagination and rows-per-page are htmx-driven (see the hx-* // attributes on the nav links and the rows-per-page wrapper); // this script only covers the view-local UI state below. // Column visibility (the tsx's columnVisibility state): the // Columns checkbox items hide/show their column's // cells. The dropdown content is portaled to <body>, so the // table is resolved directly (one instance per page). document.addEventListener("dropdownmenu-checked-change", (e) => { if (!(e.target instanceof Element)) return; const item = e.target.closest("[data-dashboard01-column]"); if (!item) return; const root = document.querySelector("[data-dashboard01-table]"); if (!root) return; const index = parseInt(item.getAttribute("data-dashboard01-column"), 10); const show = item.hasAttribute("data-checked"); root.querySelectorAll("thead tr, tbody tr").forEach((row) => { const cell = row.children[index]; if (cell) cell.hidden = !show; }); }); // Row selection: the header checkbox toggles the page's rows and // mirrors them as checked/indeterminate (the checkbox component // renders the mixed state natively); selected rows tint via // data-state and the footer count stays live. let syncing = false; document.addEventListener("change", (e) => { if (syncing || !(e.target instanceof Element)) return; if (!e.target.matches("[data-tui-checkbox-input]")) return; const root = e.target.closest("[data-dashboard01-table]"); if (!root) return; const all = root.querySelector("thead [data-tui-checkbox-input]"); const rows = [...root.querySelectorAll("tbody [data-tui-checkbox-input]")]; if (!all || rows.length === 0) return; syncing = true; if (e.target === all) { // toggleAllPageRowsSelected pendant. rows.forEach((input) => { if (input.checked !== all.checked) { input.checked = all.checked; input.dispatchEvent(new Event("change", { bubbles: true })); } }); } const checked = rows.filter((input) => input.checked).length; rows.forEach((input) => { const row = input.closest("tr"); if (!row) return; if (input.checked) row.setAttribute("data-state", "selected"); else row.removeAttribute("data-state"); }); // getIsAllPageRowsSelected / getIsSomePageRowsSelected pendant; // checkbox.js paints the mixed state off input.indeterminate. all.checked = checked === rows.length; all.indeterminate = checked > 0 && checked < rows.length; all.dispatchEvent(new Event("change", { bubbles: true })); syncing = false; const count = root.querySelector("[data-dashboard01-selected-count]"); if (count) count.textContent = String(checked); }); // Vanilla pendant of dnd-kit's vertical SortableContext. Pointer, // touch, native drag and keyboard input all reorder the rendered rows. const finishDrag = () => { if (draggedRow) draggedRow.removeAttribute("data-dragging"); draggedRow = null; keyboardOrigin = null; }; const moveAt = (tbody, row, clientY) => { const rect = row.getBoundingClientRect(); const target = document.elementsFromPoint(rect.left + rect.width / 2, clientY) .map((element) => element.closest && element.closest("[data-dashboard01-row]")) .find((element) => element && element.parentElement === tbody && element !== row); if (!target) return; const before = clientY < target.getBoundingClientRect().top + target.offsetHeight / 2; tbody.insertBefore(row, before ? target : target.nextSibling); }; document.addEventListener("dragstart", (e) => { if (!(e.target instanceof Element)) return; const handle = e.target.closest("[data-dashboard01-drag-handle]"); if (!handle) return; draggedRow = handle.closest("[data-dashboard01-row]"); if (!draggedRow) return; draggedRow.setAttribute("data-dragging", "true"); if (e.dataTransfer) { e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", draggedRow.getAttribute("data-dashboard01-row") || ""); } }); document.addEventListener("dragover", (e) => { if (!draggedRow) return; const tbody = draggedRow.closest("[data-dashboard01-sortable]"); if (!tbody) return; e.preventDefault(); moveAt(tbody, draggedRow, e.clientY); }); document.addEventListener("dragend", finishDrag); let pointer = null; document.addEventListener("pointerdown", (e) => { if (!(e.target instanceof Element) || e.pointerType === "mouse") return; const handle = e.target.closest("[data-dashboard01-drag-handle]"); const row = handle && handle.closest("[data-dashboard01-row]"); if (!handle || !row) return; pointer = { id: e.pointerId, handle, row }; draggedRow = row; row.setAttribute("data-dragging", "true"); handle.setPointerCapture(e.pointerId); e.preventDefault(); }); document.addEventListener("pointermove", (e) => { if (!pointer || pointer.id !== e.pointerId) return; const tbody = pointer.row.closest("[data-dashboard01-sortable]"); if (tbody) moveAt(tbody, pointer.row, e.clientY); e.preventDefault(); }); const finishPointer = (e) => { if (!pointer || pointer.id !== e.pointerId) return; if (pointer.handle.hasPointerCapture(e.pointerId)) pointer.handle.releasePointerCapture(e.pointerId); pointer = null; finishDrag(); }; document.addEventListener("pointerup", finishPointer); document.addEventListener("pointercancel", finishPointer); document.addEventListener("keydown", (e) => { if (!(e.target instanceof Element)) return; const handle = e.target.closest("[data-dashboard01-drag-handle]"); if (!handle) return; const row = handle.closest("[data-dashboard01-row]"); const tbody = row && row.closest("[data-dashboard01-sortable]"); if (!row || !tbody) return; if (e.key === " " || e.key === "Enter") { e.preventDefault(); if (draggedRow === row) finishDrag(); else { finishDrag(); draggedRow = row; keyboardOrigin = row.nextSibling; row.setAttribute("data-dragging", "true"); } } else if (draggedRow === row && (e.key === "ArrowUp" || e.key === "ArrowDown")) { e.preventDefault(); const sibling = e.key === "ArrowUp" ? row.previousElementSibling : row.nextElementSibling; if (sibling) tbody.insertBefore(row, e.key === "ArrowUp" ? sibling : sibling.nextSibling); handle.focus(); } else if (draggedRow === row && e.key === "Escape") { e.preventDefault(); tbody.insertBefore(row, keyboardOrigin && keyboardOrigin.isConnected ? keyboardOrigin : null); finishDrag(); } }); // The current Base block uses toast.promise on the two inline forms. document.addEventListener("submit", (e) => { if (!(e.target instanceof HTMLFormElement) || !e.target.matches("[data-dashboard01-save]")) return; e.preventDefault(); const title = e.target.getAttribute("data-dashboard01-save") || "section"; window.tui?.toast?.promise( new Promise((resolve) => window.setTimeout(resolve, 1000)), { loading: `Saving ${title}`, success: "Done", error: "Error" }, ); }); // useIsMobile pendant for TableCellViewer: mutate the inert portal // template before drawer.js opens it, so the first open is correct. const drawerDialog = (trigger) => { const id = trigger.getAttribute("aria-controls"); if (!id) return null; const live = document.getElementById(id); if (live) return live; for (const template of document.querySelectorAll("template[data-tui-drawer-portal]")) { const dialog = [...template.content.querySelectorAll("dialog[data-tui-drawer-content]")].find((item) => item.id === id); if (dialog) return dialog; } return null; }; const syncDrawerDirection = (trigger) => { const popup = drawerDialog(trigger)?.querySelector("[data-tui-drawer-popup]"); if (!popup) return; const mobile = window.matchMedia("(max-width: 767px)").matches; popup.setAttribute("data-swipe-direction", mobile ? "down" : "right"); popup.setAttribute("data-swipe-axis", mobile ? "y" : "x"); }; document.addEventListener("pointerdown", (e) => { if (!(e.target instanceof Element)) return; const trigger = e.target.closest("[data-dashboard01-drawer-trigger]"); if (trigger) syncDrawerDirection(trigger); }, true); document.addEventListener("keydown", (e) => { if (!(e.target instanceof Element) || (e.key !== "Enter" && e.key !== " ")) return; const trigger = e.target.closest("[data-dashboard01-drawer-trigger]"); if (trigger) syncDrawerDirection(trigger); }, true); document.addEventListener("click", (e) => { if (!(e.target instanceof Element)) return; const trigger = e.target.closest("[data-dashboard01-drawer-trigger]"); if (trigger) syncDrawerDirection(trigger); }, true); })(); </script>} templ dataTableRow(item Item, page, size int) { @table.Row(table.RowProps{ Class: "relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80", Attributes: templ.Attributes{"data-dashboard01-row": fmt.Sprint(item.ID)}, }) { @table.Cell() { @button.Button(button.Props{ Variant: button.VariantGhost, Size: button.SizeIcon, Class: "size-7 text-muted-foreground hover:bg-transparent", Attributes: templ.Attributes{ "data-dashboard01-drag-handle": "", "draggable": "true", "style": "touch-action:none", }, }) { @icon.GripVertical(icon.Props{Class: "size-3 text-muted-foreground"}) <span class="sr-only">Drag to reorder</span> } } @table.Cell() { <div class="flex items-center justify-center"> @checkbox.Checkbox(checkbox.Props{ Attributes: templ.Attributes{"aria-label": "Select row"}, }) </div> } @table.Cell() { @dataTableCellViewer(item, page, size) } @table.Cell() { <div class="w-32"> @badge.Badge(badge.Props{Variant: badge.VariantOutline, Class: "px-1.5 text-muted-foreground"}) { { item.SectionType } } </div> } @table.Cell() { @badge.Badge(badge.Props{Variant: badge.VariantOutline, Class: "px-1.5 text-muted-foreground"}) { if item.Status == "Done" { @icon.CircleCheck(icon.Props{Class: "fill-green-500 dark:fill-green-400"}) } else { @icon.Loader() } { item.Status } } } @table.Cell() { <form data-dashboard01-save={ item.Header }> @label.Label(label.Props{For: fmt.Sprintf("%d-target", item.ID), Class: "sr-only"}) { Target } @input.Input(input.Props{ Class: "h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30", Value: item.Target, ID: fmt.Sprintf("%d-target", item.ID), }) </form> } @table.Cell() { <form data-dashboard01-save={ item.Header }> @label.Label(label.Props{For: fmt.Sprintf("%d-limit", item.ID), Class: "sr-only"}) { Limit } @input.Input(input.Props{ Class: "h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30", Value: item.Limit, ID: fmt.Sprintf("%d-limit", item.ID), }) </form> } @table.Cell() { if item.Reviewer != "Assign reviewer" { { item.Reviewer } } else { @label.Label(label.Props{For: fmt.Sprintf("%d-reviewer", item.ID), Class: "sr-only"}) { Reviewer } @selectcomp.Select() { @selectcomp.Trigger(selectcomp.TriggerProps{ Class: "w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate", Size: selectcomp.SizeSm, ID: fmt.Sprintf("%d-reviewer", item.ID), }) { @selectcomp.Value(selectcomp.ValueProps{Placeholder: "Assign reviewer"}) } @selectcomp.Content(selectcomp.ContentProps{Align: selectcomp.AlignEnd}) { @selectcomp.Group() { @selectcomp.Item(selectcomp.ItemProps{Value: "Eddie Lake"}) { Eddie Lake } @selectcomp.Item(selectcomp.ItemProps{Value: "Jamik Tashpulatov"}) { Jamik Tashpulatov } } } } } } @table.Cell() { @dropdownmenu.DropdownMenu() { @button.Button(button.Props{ Variant: button.VariantGhost, Class: "flex size-8 text-muted-foreground data-popup-open:bg-muted", Size: button.SizeIcon, Attributes: dropdownmenu.Trigger(ctx), }) { @icon.EllipsisVertical() <span class="sr-only">Open menu</span> } @dropdownmenu.Content(dropdownmenu.ContentProps{Align: dropdownmenu.AlignEnd, Class: "w-32"}) { @dropdownmenu.Item() { Edit } @dropdownmenu.Item() { Make a copy } @dropdownmenu.Item() { Favorite } @dropdownmenu.Separator() @dropdownmenu.Item(dropdownmenu.ItemProps{Variant: dropdownmenu.ItemVariantDestructive}) { Delete } } } } }} var dataTableCellViewerChartData = []chart.Datum{ {"month": "January", "desktop": 186, "mobile": 80}, {"month": "February", "desktop": 305, "mobile": 200}, {"month": "March", "desktop": 237, "mobile": 120}, {"month": "April", "desktop": 73, "mobile": 190}, {"month": "May", "desktop": 209, "mobile": 130}, {"month": "June", "desktop": 214, "mobile": 140},} var dataTableCellViewerChartConfig = chart.Config{ {Key: "desktop", Label: "Desktop", Color: "var(--primary)"}, {Key: "mobile", Label: "Mobile", Color: "var(--primary)"},} // dataTableCellViewerMonthShort returns the three-letter month label.func dataTableCellViewerMonthShort(v any) string { s, _ := v.(string) if len(s) > 3 { return s[:3] } return s} templ dataTableCellViewer(item Item, page, size int) { @drawer.Drawer(drawer.Props{SwipeDirection: drawer.SwipeDirectionRight}) { // Lazy-load this row's templ fragment once, so the page never ships // 68 hidden charts. @button.Button(button.Props{ Variant: button.VariantLink, Class: "w-fit px-0 text-left text-foreground", Attributes: utils.MergeAttributes(drawer.Trigger(ctx), templ.Attributes{ "data-dashboard01-drawer-trigger": "", "hx-get": dataTablePageHref(page, size) + "&fragment=" + dataTableDrawerFragment(item.ID), "hx-trigger": "click once", "hx-target": "#" + dataTableDrawerBodyID(item.ID), "hx-swap": "innerHTML", }), }) { { item.Header } } @drawer.Content() { @drawer.Header(drawer.HeaderProps{Class: "gap-1"}) { @drawer.Title() { { item.Header } } @drawer.Description() { Showing total visitors for the last 6 months } } <div class="flex flex-col gap-4 overflow-y-auto px-4 text-sm" id={ dataTableDrawerBodyID(item.ID) }> if dataTableDrawerRequested(ctx, item.ID) { @templ.Fragment(dataTableDrawerFragment(item.ID)) { @dataTableDrawerBody(item) } } else { // The skeleton the fragment swap replaces on first open. @skeleton.Skeleton(skeleton.Props{Class: "aspect-video w-full"}) @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-2/3"}) @skeleton.Skeleton(skeleton.Props{Class: "h-40 w-full"}) } </div> @drawer.Footer() { @button.Button() { Submit } @button.Button(button.Props{ Variant: button.VariantOutline, Attributes: drawer.Close(ctx), }) { Done } } } }} // dataTableDrawerBody is the drawer's heavy content (chart, trend, form),// rendered only for its own fragment request.templ dataTableDrawerBody(item Item) { <div class="hidden md:contents"> @chart.Container(chart.ContainerProps{Config: dataTableCellViewerChartConfig}) { @chart.AreaChart(chart.AreaChartProps{ AccessibilityLayer: true, Data: dataTableCellViewerChartData, Margin: &chart.Margin{Left: 0, Right: 10}, }) { @chart.CartesianGrid(chart.CartesianGridProps{Vertical: chart.Bool(false)}) @chart.XAxis(chart.XAxisProps{ DataKey: "month", TickMargin: 8, TickFormatter: dataTableCellViewerMonthShort, Hide: true, }) @chart.Tooltip(chart.TooltipProps{ Cursor: chart.Bool(false), Content: chart.TooltipContentProps{Indicator: "dot"}, }) @chart.Area(chart.AreaProps{ DataKey: "mobile", Type: chart.CurveNatural, Fill: "var(--color-mobile)", FillOpacity: 0.6, Stroke: "var(--color-mobile)", StackID: "a", }) @chart.Area(chart.AreaProps{ DataKey: "desktop", Type: chart.CurveNatural, Fill: "var(--color-desktop)", FillOpacity: 0.4, Stroke: "var(--color-desktop)", StackID: "a", }) } } @separator.Separator() <div class="grid gap-2"> <div class="flex gap-2 leading-none font-medium"> Trending up by 5.2% this month @icon.TrendingUp(icon.Props{Class: "size-4"}) </div> <div class="text-muted-foreground"> Showing total visitors for the last 6 months. This is just some random text to test the layout. It spans multiple lines and should wrap around. </div> </div> @separator.Separator() </div> <form class="flex flex-col gap-4"> <div class="flex flex-col gap-3"> @label.Label(label.Props{For: "header"}) { Header } @input.Input(input.Props{ID: "header", Value: item.Header}) </div> <div class="grid grid-cols-2 gap-4"> <div class="flex flex-col gap-3"> @label.Label(label.Props{For: "type"}) { Type } @selectcomp.Select(selectcomp.Props{DefaultValue: item.SectionType}) { @selectcomp.Trigger(selectcomp.TriggerProps{ID: "type", Class: "w-full"}) { @selectcomp.Value(selectcomp.ValueProps{Placeholder: "Select a type"}) } @selectcomp.Content() { @selectcomp.Group() { for _, sectionType := range []string{"Table of Contents", "Executive Summary", "Technical Approach", "Design", "Capabilities", "Focus Documents", "Narrative", "Cover Page"} { @selectcomp.Item(selectcomp.ItemProps{Value: sectionType}) { { sectionType } } } } } } </div> <div class="flex flex-col gap-3"> @label.Label(label.Props{For: "status"}) { Status } @selectcomp.Select(selectcomp.Props{DefaultValue: item.Status}) { @selectcomp.Trigger(selectcomp.TriggerProps{ID: "status", Class: "w-full"}) { @selectcomp.Value(selectcomp.ValueProps{Placeholder: "Select a status"}) } @selectcomp.Content() { @selectcomp.Group() { @selectcomp.Item(selectcomp.ItemProps{Value: "Done"}) { Done } @selectcomp.Item(selectcomp.ItemProps{Value: "In Progress"}) { In Progress } @selectcomp.Item(selectcomp.ItemProps{Value: "Not Started"}) { Not Started } } } } </div> </div> <div class="grid grid-cols-2 gap-4"> <div class="flex flex-col gap-3"> @label.Label(label.Props{For: "target"}) { Target } @input.Input(input.Props{ID: "target", Value: item.Target}) </div> <div class="flex flex-col gap-3"> @label.Label(label.Props{For: "limit"}) { Limit } @input.Input(input.Props{ID: "limit", Value: item.Limit}) </div> </div> <div class="flex flex-col gap-3"> @label.Label(label.Props{For: "reviewer"}) { Reviewer } @selectcomp.Select(selectcomp.Props{DefaultValue: item.Reviewer}) { @selectcomp.Trigger(selectcomp.TriggerProps{ID: "reviewer", Class: "w-full"}) { @selectcomp.Value(selectcomp.ValueProps{Placeholder: "Select a reviewer"}) } @selectcomp.Content() { @selectcomp.Group() { @selectcomp.Item(selectcomp.ItemProps{Value: "Eddie Lake"}) { Eddie Lake } @selectcomp.Item(selectcomp.ItemProps{Value: "Jamik Tashpulatov"}) { Jamik Tashpulatov } @selectcomp.Item(selectcomp.ItemProps{Value: "Emily Whalen"}) { Emily Whalen } } } } </div> </form>}package dashboard01 import ( "context" "net/http" "strconv" "github.com/a-h/templ") // The tsx keeps pagination in tanstack client state; the templ pendant is// the URL: ?page= and ?size= drive the server-rendered window (backend is// the state). The nav buttons are plain links, so the block works with no// script and no dependency - wire them to your swap layer of choice (HTMX// etc.) for partial updates. type requestKey struct{} // WithRequest puts the request into the render context so DataTable can// read its pagination params. Mount the block page like://// mux.Handle("GET /dashboard", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {// r = r.WithContext(dashboard01.WithRequest(r.Context(), r))// // The htmx requests ask for a templ fragment (?fragment=) and the// // clean URL rides back on HX-Push-Url:// if f := r.URL.Query().Get("fragment"); f != "" {// if f == "data-table" && r.Header.Get("HX-Request") == "true" {// q := r.URL.Query()// q.Del("fragment")// w.Header().Set("HX-Push-Url", r.URL.Path+"?"+q.Encode())// }// templ.Handler(dashboard01.Page(), templ.WithFragments(f)).ServeHTTP(w, r)// return// }// dashboard01.Page().Render(r.Context(), w)// }))//// Without it the block still renders, on page 1 with 10 rows.func WithRequest(ctx context.Context, r *http.Request) context.Context { return context.WithValue(ctx, requestKey{}, r)} // dataTablePageSizes mirrors the tsx rows-per-page options.var dataTablePageSizes = []int{10, 20, 30, 40, 50} // dataTablePageParams reads ?page= and ?size= from the request in ctx,// clamped to the option list and the page count (the tanstack state's// pageIndex/pageSize pendant).func dataTablePageParams(ctx context.Context, total int) (page, size int) { page, size = 1, dataTablePageSize r, ok := ctx.Value(requestKey{}).(*http.Request) if !ok { return page, size } if s, err := strconv.Atoi(r.URL.Query().Get("size")); err == nil { for _, option := range dataTablePageSizes { if s == option { size = s break } } } if p, err := strconv.Atoi(r.URL.Query().Get("page")); err == nil { page = min(max(p, 1), dataTablePageCount(total, size)) } return page, size} // dataTablePageCount is the getPageCount pendant.func dataTablePageCount(total, size int) int { return max((total+size-1)/size, 1)} // dataTablePageWindow slices the current page's rows.func dataTablePageWindow(data []Item, page, size int) []Item { start := min((page-1)*size, len(data)) return data[start:min(start+size, len(data))]} // dataTablePageHref builds the ?page=&size= link of a nav button.func dataTablePageHref(page, size int) string { return "?page=" + strconv.Itoa(page) + "&size=" + strconv.Itoa(size)} // dataTableHxAttrs are the htmx attributes of a pagination control: fetch// the data-table fragment and swap this table in place. The clean URL// (without the fragment param) is pushed by the server's HX-Push-Url// response header - see the WithRequest mount example. Datastar users// translate these attributes 1:1.func dataTableHxAttrs(href string) templ.Attributes { return templ.Attributes{ "hx-get": href + "&fragment=data-table", "hx-target": "[data-dashboard01-table]", "hx-swap": "outerHTML", }} // dataTableDrawerFragment names a row drawer's templ fragment.func dataTableDrawerFragment(id int) string { return "row-drawer-" + strconv.Itoa(id)} // dataTableDrawerBodyID is the DOM id the drawer trigger's hx-target swaps// the lazy-loaded body into.func dataTableDrawerBodyID(id int) string { return "row-drawer-body-" + strconv.Itoa(id)} // dataTableDrawerRequested reports whether this row's drawer content is the// requested fragment: only then does the heavy drawer body (chart + form)// render. Every other render ships the skeleton, and the drawer trigger// lazy-loads the body on first open.func dataTableDrawerRequested(ctx context.Context, id int) bool { r, ok := ctx.Value(requestKey{}).(*http.Request) return ok && r.URL.Query().Get("fragment") == dataTableDrawerFragment(id)}package dashboard01 import ( "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ NavDocuments(items []DocumentItem) { @sidebar.Group(sidebar.GroupProps{Class: "group-data-[collapsible=icon]:hidden"}) { @sidebar.GroupLabel() { Documents } @sidebar.Menu() { for _, item := range items { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Href: item.URL}) { @item.Icon() <span>{ item.Name }</span> } @dropdownmenu.DropdownMenu() { @sidebar.MenuAction(sidebar.MenuActionProps{ ShowOnHover: true, Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { @icon.Ellipsis() <span class="sr-only">More</span> } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "w-24", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignStart, MobileSide: dropdownmenu.SideBottom, MobileAlign: dropdownmenu.AlignEnd, }) { @dropdownmenu.Item() { @icon.Folder() <span>Open</span> } @dropdownmenu.Item() { @icon.Share() <span>Share</span> } @dropdownmenu.Separator() @dropdownmenu.Item(dropdownmenu.ItemProps{Variant: dropdownmenu.ItemVariantDestructive}) { @icon.Trash2() <span>Delete</span> } } } } } @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Class: "text-sidebar-foreground/70"}) { @icon.Ellipsis(icon.Props{Class: "text-sidebar-foreground/70"}) <span>More</span> } } } }}package dashboard01 import ( "github.com/axadrn/shadcn-templ/v2/components/button" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ NavMain(items []NavItem) { @sidebar.Group() { @sidebar.GroupContent(sidebar.GroupContentProps{Class: "flex flex-col gap-2"}) { @sidebar.Menu() { @sidebar.MenuItem(sidebar.MenuItemProps{Class: "flex items-center gap-2"}) { @sidebar.MenuButton(sidebar.MenuButtonProps{ Tooltip: "Quick Create", Class: "min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground", }) { @icon.CirclePlus() <span>Quick Create</span> } @button.Button(button.Props{ Size: button.SizeIcon, Class: "size-8 group-data-[collapsible=icon]:opacity-0", Variant: button.VariantOutline, }) { @icon.Mail() <span class="sr-only">Inbox</span> } } } @sidebar.Menu() { for _, item := range items { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Tooltip: item.Title}) { if item.Icon != nil { @item.Icon() } <span>{ item.Title }</span> } } } } } }}package dashboard01 import "github.com/axadrn/shadcn-templ/v2/components/sidebar" templ NavSecondary(items []NavItem, class string) { @sidebar.Group(sidebar.GroupProps{Class: class}) { @sidebar.GroupContent() { @sidebar.Menu() { for _, item := range items { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Href: item.URL}) { @item.Icon() <span>{ item.Title }</span> } } } } } }}package dashboard01 import ( "github.com/axadrn/shadcn-templ/v2/components/avatar" "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ NavUser(user User) { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { @avatar.Avatar(avatar.Props{Class: "size-8 rounded-lg grayscale"}) { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback(avatar.FallbackProps{Class: "rounded-lg"}) { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs text-foreground/70"> { user.Email } </span> </div> @icon.EllipsisVertical(icon.Props{Class: "ml-auto size-4"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "min-w-56", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignEnd, MobileSide: dropdownmenu.SideBottom, SideOffset: 4, }) { @dropdownmenu.Group() { @dropdownmenu.Label(dropdownmenu.LabelProps{Class: "p-0 font-normal"}) { <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> @avatar.Avatar(avatar.Props{Class: "size-8"}) { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback(avatar.FallbackProps{Class: "rounded-lg"}) { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs text-muted-foreground"> { user.Email } </span> </div> </div> } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.CircleUserRound() Account } @dropdownmenu.Item() { @icon.CreditCard() Billing } @dropdownmenu.Item() { @icon.Bell() Notifications } } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.LogOut() Log out } } } } }}package dashboard01 import ( "github.com/axadrn/shadcn-templ/v2/components/badge" "github.com/axadrn/shadcn-templ/v2/components/card" "github.com/axadrn/shadcn-templ/v2/components/icon") templ SectionCards() { <div class="grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-linear-to-t *:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4 dark:*:data-[slot=card]:bg-card"> @card.Card(card.Props{Class: "@container/card"}) { @card.Header() { @card.Description() { Total Revenue } @card.Title(card.TitleProps{Class: "text-2xl font-semibold tabular-nums @[250px]/card:text-3xl"}) { $1,250.00 } @card.Action() { @badge.Badge(badge.Props{Variant: badge.VariantOutline}) { @icon.TrendingUp() +12.5% } } } @card.Footer(card.FooterProps{Class: "flex-col items-start gap-1.5 text-sm"}) { <div class="line-clamp-1 flex gap-2 font-medium"> Trending up this month @icon.TrendingUp(icon.Props{Class: "size-4"}) </div> <div class="text-muted-foreground"> Visitors for the last 6 months </div> } } @card.Card(card.Props{Class: "@container/card"}) { @card.Header() { @card.Description() { New Customers } @card.Title(card.TitleProps{Class: "text-2xl font-semibold tabular-nums @[250px]/card:text-3xl"}) { 1,234 } @card.Action() { @badge.Badge(badge.Props{Variant: badge.VariantOutline}) { @icon.TrendingDown() -20% } } } @card.Footer(card.FooterProps{Class: "flex-col items-start gap-1.5 text-sm"}) { <div class="line-clamp-1 flex gap-2 font-medium"> Down 20% this period @icon.TrendingDown(icon.Props{Class: "size-4"}) </div> <div class="text-muted-foreground"> Acquisition needs attention </div> } } @card.Card(card.Props{Class: "@container/card"}) { @card.Header() { @card.Description() { Active Accounts } @card.Title(card.TitleProps{Class: "text-2xl font-semibold tabular-nums @[250px]/card:text-3xl"}) { 45,678 } @card.Action() { @badge.Badge(badge.Props{Variant: badge.VariantOutline}) { @icon.TrendingUp() +12.5% } } } @card.Footer(card.FooterProps{Class: "flex-col items-start gap-1.5 text-sm"}) { <div class="line-clamp-1 flex gap-2 font-medium"> Strong user retention @icon.TrendingUp(icon.Props{Class: "size-4"}) </div> <div class="text-muted-foreground">Engagement exceed targets</div> } } @card.Card(card.Props{Class: "@container/card"}) { @card.Header() { @card.Description() { Growth Rate } @card.Title(card.TitleProps{Class: "text-2xl font-semibold tabular-nums @[250px]/card:text-3xl"}) { 4.5% } @card.Action() { @badge.Badge(badge.Props{Variant: badge.VariantOutline}) { @icon.TrendingUp() +4.5% } } } @card.Footer(card.FooterProps{Class: "flex-col items-start gap-1.5 text-sm"}) { <div class="line-clamp-1 flex gap-2 font-medium"> Steady performance increase @icon.TrendingUp(icon.Props{Class: "size-4"}) </div> <div class="text-muted-foreground">Meets growth projections</div> } } </div>}package dashboard01 import ( "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ SiteHeader() { <header class="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)"> <div class="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6"> @sidebar.Trigger(sidebar.TriggerProps{Class: "-ml-1"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mx-2 h-4 data-vertical:self-auto", }) <h1 class="text-base font-medium">Documents</h1> </div> </header>}package sidebar01 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @AppSidebar() @sidebar.Inset() { <header class="flex h-16 shrink-0 items-center gap-2 border-b px-4"> @sidebar.Trigger(sidebar.TriggerProps{Class: "-ml-1"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { Build Your Application } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { Data Fetching } } } } </header> <div class="flex flex-1 flex-col gap-4 p-4"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> </div> <div class="min-h-screen flex-1 rounded-xl bg-muted/50 md:min-h-min"></div> </div> } }}package sidebar01 import "github.com/axadrn/shadcn-templ/v2/components/sidebar" // This is sample data.type navItem struct { Title string URL string Items []navSubItem} type navSubItem struct { Title string URL string IsActive bool} var versions = []string{"1.0.1", "1.1.0-alpha", "2.0.0-beta1"} var navMain = []navItem{ { Title: "Getting Started", URL: "#", Items: []navSubItem{ {Title: "Installation", URL: "#"}, {Title: "Project Structure", URL: "#"}, }, }, { Title: "Build Your Application", URL: "#", Items: []navSubItem{ {Title: "Routing", URL: "#"}, {Title: "Data Fetching", URL: "#", IsActive: true}, {Title: "Rendering", URL: "#"}, {Title: "Caching", URL: "#"}, {Title: "Styling", URL: "#"}, {Title: "Optimizing", URL: "#"}, {Title: "Configuring", URL: "#"}, {Title: "Testing", URL: "#"}, {Title: "Authentication", URL: "#"}, {Title: "Deploying", URL: "#"}, {Title: "Upgrading", URL: "#"}, {Title: "Examples", URL: "#"}, }, }, { Title: "API Reference", URL: "#", Items: []navSubItem{ {Title: "Components", URL: "#"}, {Title: "File Conventions", URL: "#"}, {Title: "Functions", URL: "#"}, {Title: "next.config.js Options", URL: "#"}, {Title: "CLI", URL: "#"}, {Title: "Edge Runtime", URL: "#"}, }, }, { Title: "Architecture", URL: "#", Items: []navSubItem{ {Title: "Accessibility", URL: "#"}, {Title: "Fast Refresh", URL: "#"}, {Title: "templ Compiler", URL: "#"}, {Title: "Supported Browsers", URL: "#"}, {Title: "Turbopack", URL: "#"}, }, },} templ AppSidebar() { @sidebar.Sidebar() { @sidebar.Header() { @VersionSwitcher(versions, versions[0]) @SearchForm() } @sidebar.Content() { // We create a SidebarGroup for each parent. for _, item := range navMain { @sidebar.Group() { @sidebar.GroupLabel() { { item.Title } } @sidebar.GroupContent() { @sidebar.Menu() { for _, subItem := range item.Items { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: subItem.URL, IsActive: subItem.IsActive, }) { { subItem.Title } } } } } } } } } @sidebar.Rail() }}package sidebar01 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/label" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ SearchForm() { <form> @sidebar.Group(sidebar.GroupProps{Class: "py-0"}) { @sidebar.GroupContent(sidebar.GroupContentProps{Class: "relative"}) { @label.Label(label.Props{For: "search", Class: "sr-only"}) { Search } @sidebar.Input(sidebar.InputProps{ ID: "search", Placeholder: "Search the docs...", Class: "pl-8", }) @icon.Search(icon.Props{Class: "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none"}) } } </form>}package sidebar01 import ( "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ VersionSwitcher(versions []string, defaultVersion string) { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Class: "data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground", Attributes: dropdownmenu.Trigger(ctx), }) { <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> @icon.GalleryVerticalEnd(icon.Props{Class: "size-4"}) </div> <div class="flex flex-col gap-0.5 leading-none"> <span class="font-medium">Documentation</span> <span class="" data-sidebar01-version-label>v{ defaultVersion }</span> </div> @icon.ChevronsUpDown(icon.Props{Class: "ml-auto"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Align: dropdownmenu.AlignStart, Attributes: templ.Attributes{"data-sidebar01-version-switcher": ""}, }) { for _, version := range versions { @dropdownmenu.Item(dropdownmenu.ItemProps{ Attributes: templ.Attributes{"data-version": version}, }) { v{ version } @icon.Check(icon.Props{Class: "ml-auto", Attributes: templ.Attributes{"hidden": version != defaultVersion}}) } } } } } } <script nonce={ templ.GetNonce(ctx) }> (() => { // The setSelectedVersion pendant: a picked version updates the // trigger label and moves the check mark. document.addEventListener("click", (e) => { if (!(e.target instanceof Element)) return; const item = e.target.closest("[data-sidebar01-version-switcher] [data-version]"); if (!item) return; const label = document.querySelector("[data-sidebar01-version-label]"); if (label) label.textContent = "v" + item.dataset.version; item.closest("[data-sidebar01-version-switcher]") .querySelectorAll("[data-version]") .forEach((el) => { const check = el.querySelector("svg"); if (check) check.toggleAttribute("hidden", el !== item); }); }); })(); </script>}package sidebar02 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @AppSidebar() @sidebar.Inset() { <header class="sticky top-0 flex h-16 shrink-0 items-center gap-2 border-b bg-background px-4"> @sidebar.Trigger(sidebar.TriggerProps{Class: "-ml-1"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { Build Your Application } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { Data Fetching } } } } </header> <div class="flex flex-1 flex-col gap-4 p-4"> for i := 0; i < 24; i++ { <div class="aspect-video h-12 w-full rounded-lg bg-muted/50"></div> } </div> } }}package sidebar02 import ( "github.com/axadrn/shadcn-templ/v2/components/collapsible" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") // This is sample data.type navItem struct { Title string URL string Items []navSubItem} type navSubItem struct { Title string URL string IsActive bool} var versions = []string{"1.0.1", "1.1.0-alpha", "2.0.0-beta1"} var navMain = []navItem{ { Title: "Getting Started", URL: "#", Items: []navSubItem{ {Title: "Installation", URL: "#"}, {Title: "Project Structure", URL: "#"}, }, }, { Title: "Build Your Application", URL: "#", Items: []navSubItem{ {Title: "Routing", URL: "#"}, {Title: "Data Fetching", URL: "#", IsActive: true}, {Title: "Rendering", URL: "#"}, {Title: "Caching", URL: "#"}, {Title: "Styling", URL: "#"}, {Title: "Optimizing", URL: "#"}, {Title: "Configuring", URL: "#"}, {Title: "Testing", URL: "#"}, {Title: "Authentication", URL: "#"}, {Title: "Deploying", URL: "#"}, {Title: "Upgrading", URL: "#"}, {Title: "Examples", URL: "#"}, }, }, { Title: "API Reference", URL: "#", Items: []navSubItem{ {Title: "Components", URL: "#"}, {Title: "File Conventions", URL: "#"}, {Title: "Functions", URL: "#"}, {Title: "next.config.js Options", URL: "#"}, {Title: "CLI", URL: "#"}, {Title: "Edge Runtime", URL: "#"}, }, }, { Title: "Architecture", URL: "#", Items: []navSubItem{ {Title: "Accessibility", URL: "#"}, {Title: "Fast Refresh", URL: "#"}, {Title: "templ Compiler", URL: "#"}, {Title: "Supported Browsers", URL: "#"}, {Title: "Turbopack", URL: "#"}, }, }, { Title: "Community", URL: "#", Items: []navSubItem{ {Title: "Contribution Guide", URL: "#"}, }, },} templ AppSidebar() { @sidebar.Sidebar() { @sidebar.Header() { @VersionSwitcher(versions, versions[0]) @SearchForm() } @sidebar.Content(sidebar.ContentProps{Class: "gap-0"}) { // We create a collapsible SidebarGroup for each parent. for _, item := range navMain { @collapsible.Collapsible(collapsible.Props{ DefaultOpen: true, Class: "group/collapsible", Attributes: templ.Attributes{"title": item.Title}, }) { @sidebar.Group() { @sidebar.GroupLabel(sidebar.GroupLabelProps{ Class: "group/label text-sm text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", Attributes: collapsible.Trigger(ctx), }) { { item.Title } @icon.ChevronRight(icon.Props{Class: "ml-auto transition-transform group-data-open/collapsible:rotate-90"}) } @collapsible.Content() { @sidebar.GroupContent() { @sidebar.Menu() { for _, subItem := range item.Items { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: subItem.URL, IsActive: subItem.IsActive, }) { { subItem.Title } } } } } } } } } } } @sidebar.Rail() }}package sidebar02 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/label" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ SearchForm() { <form> @sidebar.Group(sidebar.GroupProps{Class: "py-0"}) { @sidebar.GroupContent(sidebar.GroupContentProps{Class: "relative"}) { @label.Label(label.Props{For: "search", Class: "sr-only"}) { Search } @sidebar.Input(sidebar.InputProps{ ID: "search", Placeholder: "Search the docs...", Class: "pl-8", }) @icon.Search(icon.Props{Class: "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none"}) } } </form>}package sidebar02 import ( "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ VersionSwitcher(versions []string, defaultVersion string) { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Class: "data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground", Attributes: dropdownmenu.Trigger(ctx), }) { <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> @icon.GalleryVerticalEnd(icon.Props{Class: "size-4"}) </div> <div class="flex flex-col gap-0.5 leading-none"> <span class="font-medium">Documentation</span> <span class="" data-sidebar02-version-label>v{ defaultVersion }</span> </div> @icon.ChevronsUpDown(icon.Props{Class: "ml-auto"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Align: dropdownmenu.AlignStart, Attributes: templ.Attributes{"data-sidebar02-version-switcher": ""}, }) { for _, version := range versions { @dropdownmenu.Item(dropdownmenu.ItemProps{ Attributes: templ.Attributes{"data-version": version}, }) { v{ version } @icon.Check(icon.Props{Class: "ml-auto", Attributes: templ.Attributes{"hidden": version != defaultVersion}}) } } } } } } <script nonce={ templ.GetNonce(ctx) }> (() => { // The setSelectedVersion pendant: a picked version updates the // trigger label and moves the check mark. document.addEventListener("click", (e) => { if (!(e.target instanceof Element)) return; const item = e.target.closest("[data-sidebar02-version-switcher] [data-version]"); if (!item) return; const label = document.querySelector("[data-sidebar02-version-label]"); if (label) label.textContent = "v" + item.dataset.version; item.closest("[data-sidebar02-version-switcher]") .querySelectorAll("[data-version]") .forEach((el) => { const check = el.querySelector("svg"); if (check) check.toggleAttribute("hidden", el !== item); }); }); })(); </script>}package sidebar03 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @AppSidebar() @sidebar.Inset() { <header class="flex h-16 shrink-0 items-center gap-2 border-b"> <div class="flex items-center gap-2 px-3"> @sidebar.Trigger() @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { Build Your Application } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { Data Fetching } } } } </div> </header> <div class="flex flex-1 flex-col gap-4 p-4"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> </div> <div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min"></div> </div> } }}package sidebar03 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") // This is sample data.type navItem struct { Title string URL string Items []navSubItem} type navSubItem struct { Title string URL string IsActive bool} var navMain = []navItem{ { Title: "Getting Started", URL: "#", Items: []navSubItem{ {Title: "Installation", URL: "#"}, {Title: "Project Structure", URL: "#"}, }, }, { Title: "Build Your Application", URL: "#", Items: []navSubItem{ {Title: "Routing", URL: "#"}, {Title: "Data Fetching", URL: "#", IsActive: true}, {Title: "Rendering", URL: "#"}, {Title: "Caching", URL: "#"}, {Title: "Styling", URL: "#"}, {Title: "Optimizing", URL: "#"}, {Title: "Configuring", URL: "#"}, {Title: "Testing", URL: "#"}, {Title: "Authentication", URL: "#"}, {Title: "Deploying", URL: "#"}, {Title: "Upgrading", URL: "#"}, {Title: "Examples", URL: "#"}, }, }, { Title: "API Reference", URL: "#", Items: []navSubItem{ {Title: "Components", URL: "#"}, {Title: "File Conventions", URL: "#"}, {Title: "Functions", URL: "#"}, {Title: "next.config.js Options", URL: "#"}, {Title: "CLI", URL: "#"}, {Title: "Edge Runtime", URL: "#"}, }, }, { Title: "Architecture", URL: "#", Items: []navSubItem{ {Title: "Accessibility", URL: "#"}, {Title: "Fast Refresh", URL: "#"}, {Title: "templ Compiler", URL: "#"}, {Title: "Supported Browsers", URL: "#"}, {Title: "Turbopack", URL: "#"}, }, }, { Title: "Community", URL: "#", Items: []navSubItem{ {Title: "Contribution Guide", URL: "#"}, }, },} templ AppSidebar() { @sidebar.Sidebar() { @sidebar.Header() { @sidebar.Menu() { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Href: "#", }) { <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> @icon.GalleryVerticalEnd(icon.Props{Class: "size-4"}) </div> <div class="flex flex-col gap-0.5 leading-none"> <span class="font-medium">Documentation</span> <span class="">v1.0.0</span> </div> } } } } @sidebar.Content() { @sidebar.Group() { @sidebar.Menu() { for _, item := range navMain { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: item.URL, Class: "font-medium", }) { { item.Title } } if len(item.Items) > 0 { @sidebar.MenuSub() { for _, subItem := range item.Items { @sidebar.MenuSubItem() { @sidebar.MenuSubButton(sidebar.MenuSubButtonProps{ Href: subItem.URL, IsActive: subItem.IsActive, }) { { subItem.Title } } } } } } } } } } } @sidebar.Rail() }}package sidebar04 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider(sidebar.ProviderProps{ // The tsx sets style={{ "--sidebar-width": "19rem" }}; the icon width // keeps the provider's default since the style attribute replaces it. Attributes: templ.Attributes{"style": "--sidebar-width: 19rem; --sidebar-width-icon: 3rem;"}, }) { @AppSidebar() @sidebar.Inset() { <header class="flex h-16 shrink-0 items-center gap-2 px-4"> @sidebar.Trigger(sidebar.TriggerProps{Class: "-ml-1"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { Build Your Application } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { Data Fetching } } } } </header> <div class="flex flex-1 flex-col gap-4 p-4 pt-0"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> </div> <div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min"></div> </div> } }}package sidebar04 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") // This is sample data.type navItem struct { Title string URL string Items []navSubItem} type navSubItem struct { Title string URL string IsActive bool} var navMain = []navItem{ { Title: "Getting Started", URL: "#", Items: []navSubItem{ {Title: "Installation", URL: "#"}, {Title: "Project Structure", URL: "#"}, }, }, { Title: "Build Your Application", URL: "#", Items: []navSubItem{ {Title: "Routing", URL: "#"}, {Title: "Data Fetching", URL: "#", IsActive: true}, {Title: "Rendering", URL: "#"}, {Title: "Caching", URL: "#"}, {Title: "Styling", URL: "#"}, {Title: "Optimizing", URL: "#"}, {Title: "Configuring", URL: "#"}, {Title: "Testing", URL: "#"}, {Title: "Authentication", URL: "#"}, {Title: "Deploying", URL: "#"}, {Title: "Upgrading", URL: "#"}, {Title: "Examples", URL: "#"}, }, }, { Title: "API Reference", URL: "#", Items: []navSubItem{ {Title: "Components", URL: "#"}, {Title: "File Conventions", URL: "#"}, {Title: "Functions", URL: "#"}, {Title: "next.config.js Options", URL: "#"}, {Title: "CLI", URL: "#"}, {Title: "Edge Runtime", URL: "#"}, }, }, { Title: "Architecture", URL: "#", Items: []navSubItem{ {Title: "Accessibility", URL: "#"}, {Title: "Fast Refresh", URL: "#"}, {Title: "templ Compiler", URL: "#"}, {Title: "Supported Browsers", URL: "#"}, {Title: "Turbopack", URL: "#"}, }, }, { Title: "Community", URL: "#", Items: []navSubItem{ {Title: "Contribution Guide", URL: "#"}, }, },} templ AppSidebar() { @sidebar.Sidebar(sidebar.Props{Variant: sidebar.VariantFloating}) { @sidebar.Header() { @sidebar.Menu() { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Href: "#", }) { <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> @icon.GalleryVerticalEnd(icon.Props{Class: "size-4"}) </div> <div class="flex flex-col gap-0.5 leading-none"> <span class="font-medium">Documentation</span> <span class="">v1.0.0</span> </div> } } } } @sidebar.Content() { @sidebar.Group() { @sidebar.Menu(sidebar.MenuProps{Class: "gap-2"}) { for _, item := range navMain { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: item.URL, Class: "font-medium", }) { { item.Title } } if len(item.Items) > 0 { @sidebar.MenuSub(sidebar.MenuSubProps{Class: "ml-0 border-l-0 px-1.5"}) { for _, subItem := range item.Items { @sidebar.MenuSubItem() { @sidebar.MenuSubButton(sidebar.MenuSubButtonProps{ Href: subItem.URL, IsActive: subItem.IsActive, }) { { subItem.Title } } } } } } } } } } } }}package sidebar05 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @AppSidebar() @sidebar.Inset() { <header class="flex h-16 shrink-0 items-center gap-2 border-b px-4"> @sidebar.Trigger(sidebar.TriggerProps{Class: "-ml-1"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { Build Your Application } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { Data Fetching } } } } </header> <div class="flex flex-1 flex-col gap-4 p-4"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> </div> <div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min"></div> </div> } }}package sidebar05 import ( "github.com/axadrn/shadcn-templ/v2/components/collapsible" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") // This is sample data.type navItem struct { Title string URL string Items []navSubItem} type navSubItem struct { Title string URL string IsActive bool} var data = struct { NavMain []navItem}{ NavMain: []navItem{ { Title: "Getting Started", URL: "#", Items: []navSubItem{ {Title: "Installation", URL: "#"}, {Title: "Project Structure", URL: "#"}, }, }, { Title: "Build Your Application", URL: "#", Items: []navSubItem{ {Title: "Routing", URL: "#"}, {Title: "Data Fetching", URL: "#", IsActive: true}, {Title: "Rendering", URL: "#"}, {Title: "Caching", URL: "#"}, {Title: "Styling", URL: "#"}, {Title: "Optimizing", URL: "#"}, {Title: "Configuring", URL: "#"}, {Title: "Testing", URL: "#"}, {Title: "Authentication", URL: "#"}, {Title: "Deploying", URL: "#"}, {Title: "Upgrading", URL: "#"}, {Title: "Examples", URL: "#"}, }, }, { Title: "API Reference", URL: "#", Items: []navSubItem{ {Title: "Components", URL: "#"}, {Title: "File Conventions", URL: "#"}, {Title: "Functions", URL: "#"}, {Title: "next.config.js Options", URL: "#"}, {Title: "CLI", URL: "#"}, {Title: "Edge Runtime", URL: "#"}, }, }, { Title: "Architecture", URL: "#", Items: []navSubItem{ {Title: "Accessibility", URL: "#"}, {Title: "Fast Refresh", URL: "#"}, {Title: "templ Compiler", URL: "#"}, {Title: "Supported Browsers", URL: "#"}, {Title: "Turbopack", URL: "#"}, }, }, { Title: "Community", URL: "#", Items: []navSubItem{ {Title: "Contribution Guide", URL: "#"}, }, }, },} templ AppSidebar(props ...sidebar.Props) { @sidebar.Sidebar(props...) { @sidebar.Header() { @sidebar.Menu() { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Size: sidebar.MenuButtonSizeLg, Href: "#"}) { <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> @icon.GalleryVerticalEnd(icon.Props{Class: "size-4"}) </div> <div class="flex flex-col gap-0.5 leading-none"> <span class="font-medium">Documentation</span> <span class="">v1.0.0</span> </div> } } } @SearchForm() } @sidebar.Content() { @sidebar.Group() { @sidebar.Menu() { for index, item := range data.NavMain { @collapsible.Collapsible(collapsible.Props{ DefaultOpen: index == 1, Class: "group/collapsible", }) { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Attributes: collapsible.Trigger(ctx)}) { { item.Title } @icon.Plus(icon.Props{Class: "ml-auto group-aria-expanded/menu-button:hidden"}) @icon.Minus(icon.Props{Class: "ml-auto hidden group-aria-expanded/menu-button:block"}) } if len(item.Items) > 0 { @collapsible.Content() { @sidebar.MenuSub() { for _, subItem := range item.Items { @sidebar.MenuSubItem() { @sidebar.MenuSubButton(sidebar.MenuSubButtonProps{ Href: subItem.URL, IsActive: subItem.IsActive, }) { { subItem.Title } } } } } } } } } } } } } @sidebar.Rail() }}package sidebar05 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/label" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ SearchForm(attrs ...templ.Attributes) { {{ var a templ.Attributes }} if len(attrs) > 0 { {{ a = attrs[0] }} } <form { a... }> @sidebar.Group(sidebar.GroupProps{Class: "py-0"}) { @sidebar.GroupContent(sidebar.GroupContentProps{Class: "relative"}) { @label.Label(label.Props{For: "search", Class: "sr-only"}) { Search } @sidebar.Input(sidebar.InputProps{ ID: "search", Placeholder: "Search the docs...", Class: "pl-8", }) @icon.Search(icon.Props{Class: "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none"}) } } </form>}package sidebar06 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @AppSidebar() @sidebar.Inset() { <header class="flex h-16 shrink-0 items-center gap-2 border-b px-4"> @sidebar.Trigger(sidebar.TriggerProps{Class: "-ml-1"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { Build Your Application } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { Data Fetching } } } } </header> <div class="flex flex-1 flex-col gap-4 p-4"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> </div> <div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min"></div> </div> } }}package sidebar06 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") // This is sample data.var data = struct { NavMain []NavMainItem}{ NavMain: []NavMainItem{ { Title: "Getting Started", URL: "#", Items: []NavMainSubItem{ {Title: "Installation", URL: "#"}, {Title: "Project Structure", URL: "#"}, }, }, { Title: "Build Your Application", URL: "#", Items: []NavMainSubItem{ {Title: "Routing", URL: "#"}, {Title: "Data Fetching", URL: "#", IsActive: true}, {Title: "Rendering", URL: "#"}, {Title: "Caching", URL: "#"}, {Title: "Styling", URL: "#"}, {Title: "Optimizing", URL: "#"}, {Title: "Configuring", URL: "#"}, {Title: "Testing", URL: "#"}, {Title: "Authentication", URL: "#"}, {Title: "Deploying", URL: "#"}, {Title: "Upgrading", URL: "#"}, {Title: "Examples", URL: "#"}, }, }, { Title: "API Reference", URL: "#", Items: []NavMainSubItem{ {Title: "Components", URL: "#"}, {Title: "File Conventions", URL: "#"}, {Title: "Functions", URL: "#"}, {Title: "next.config.js Options", URL: "#"}, {Title: "CLI", URL: "#"}, {Title: "Edge Runtime", URL: "#"}, }, }, { Title: "Architecture", URL: "#", Items: []NavMainSubItem{ {Title: "Accessibility", URL: "#"}, {Title: "Fast Refresh", URL: "#"}, {Title: "templ Compiler", URL: "#"}, {Title: "Supported Browsers", URL: "#"}, {Title: "Turbopack", URL: "#"}, }, }, },} templ AppSidebar(props ...sidebar.Props) { @sidebar.Sidebar(props...) { @sidebar.Header() { @sidebar.Menu() { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Size: sidebar.MenuButtonSizeLg, Href: "#"}) { <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> @icon.GalleryVerticalEnd(icon.Props{Class: "size-4"}) </div> <div class="flex flex-col gap-0.5 leading-none"> <span class="font-medium">Documentation</span> <span class="">v1.0.0</span> </div> } } } } @sidebar.Content() { @NavMain(data.NavMain) } @sidebar.Footer() { <div class="p-1"> @SidebarOptInForm() </div> } @sidebar.Rail() }}package sidebar06 import ( "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type NavMainItem struct { Title string URL string Icon func(...icon.Props) templ.Component IsActive bool Items []NavMainSubItem} type NavMainSubItem struct { Title string URL string IsActive bool} templ NavMain(items []NavMainItem) { @sidebar.Group() { @sidebar.Menu() { for _, item := range items { @dropdownmenu.DropdownMenu() { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { { item.Title } @icon.Ellipsis(icon.Props{Class: "ml-auto"}) } if len(item.Items) > 0 { @dropdownmenu.Content(dropdownmenu.ContentProps{ Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignStart, MobileSide: dropdownmenu.SideBottom, MobileAlign: dropdownmenu.AlignEnd, Class: "min-w-56 rounded-lg", }) { for _, subItem := range item.Items { @dropdownmenu.Item(dropdownmenu.ItemProps{Href: subItem.URL}) { { subItem.Title } } } } } } } } } }}package sidebar06 import ( "github.com/axadrn/shadcn-templ/v2/components/button" "github.com/axadrn/shadcn-templ/v2/components/card" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ SidebarOptInForm() { @card.Card(card.Props{Class: "gap-2 py-4 shadow-none"}) { @card.Header(card.HeaderProps{Class: "px-4"}) { @card.Title(card.TitleProps{Class: "text-sm"}) { Subscribe to our newsletter } @card.Description() { Opt-in to receive updates and news about the sidebar. } } @card.Content(card.ContentProps{Class: "px-4"}) { <form> <div class="grid gap-2.5"> @sidebar.Input(sidebar.InputProps{ Placeholder: "Email", Attributes: templ.Attributes{"type": "email"}, }) @button.Button(button.Props{ Class: "w-full bg-sidebar-primary text-sidebar-primary-foreground shadow-none", Size: button.SizeSm, }) { Subscribe } </div> </form> } }}package sidebar07 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @AppSidebar() @sidebar.Inset() { <header class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12"> <div class="flex items-center gap-2 px-4"> @sidebar.Trigger(sidebar.TriggerProps{Class: "-ml-1"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { Build Your Application } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { Data Fetching } } } } </div> </header> <div class="flex flex-1 flex-col gap-4 p-4 pt-0"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> </div> <div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min"></div> </div> } } <script nonce={ templ.GetNonce(ctx) }> (() => { // Keep the selected team details in sync. document.addEventListener("click", (e) => { if (!(e.target instanceof Element)) return; const item = e.target.closest("#sidebar07-team-menu [data-sidebar07-team-index]"); if (!item) return; const i = item.getAttribute("data-sidebar07-team-index"); const name = item.textContent.trim().replace(/⌘\d$/, "").trim(); const plan = ["Enterprise", "Startup", "Free"][parseInt(i, 10)]; document.querySelectorAll("[data-sidebar07-team-logo]").forEach((logo) => { logo.classList.toggle("hidden", logo.getAttribute("data-sidebar07-team-logo") !== i); }); const nameEl = document.querySelector("[data-sidebar07-team-name]"); const planEl = document.querySelector("[data-sidebar07-team-plan]"); if (nameEl) nameEl.textContent = name; if (planEl) planEl.textContent = plan; }); })(); </script>}package sidebar07 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") // This is sample data.var data = struct { User User Teams []Team NavMain []NavMainItem Projects []Project}{ User: User{ Name: "Axel Adrian", Email: "[email protected]", Avatar: "https://github.com/axadrn.png", }, Teams: []Team{ {Name: "Acme Inc", Logo: icon.GalleryVerticalEnd, Plan: "Enterprise"}, {Name: "Acme Corp.", Logo: icon.AudioLines, Plan: "Startup"}, {Name: "Evil Corp.", Logo: icon.Terminal, Plan: "Free"}, }, NavMain: []NavMainItem{ { Title: "Playground", URL: "#", Icon: icon.SquareTerminal, IsActive: true, Items: []NavMainSubItem{ {Title: "History", URL: "#"}, {Title: "Starred", URL: "#"}, {Title: "Settings", URL: "#"}, }, }, { Title: "Models", URL: "#", Icon: icon.Bot, Items: []NavMainSubItem{ {Title: "Genesis", URL: "#"}, {Title: "Explorer", URL: "#"}, {Title: "Quantum", URL: "#"}, }, }, { Title: "Documentation", URL: "#", Icon: icon.BookOpen, Items: []NavMainSubItem{ {Title: "Introduction", URL: "#"}, {Title: "Get Started", URL: "#"}, {Title: "Tutorials", URL: "#"}, {Title: "Changelog", URL: "#"}, }, }, { Title: "Settings", URL: "#", Icon: icon.Settings2, Items: []NavMainSubItem{ {Title: "General", URL: "#"}, {Title: "Team", URL: "#"}, {Title: "Billing", URL: "#"}, {Title: "Limits", URL: "#"}, }, }, }, Projects: []Project{ {Name: "Design Engineering", URL: "#", Icon: icon.Frame}, {Name: "Sales & Marketing", URL: "#", Icon: icon.ChartPie}, {Name: "Travel", URL: "#", Icon: icon.Map}, },} templ AppSidebar(props ...sidebar.Props) { {{ var p sidebar.Props }} if len(props) > 0 { {{ p = props[0] }} } if p.Collapsible == "" { {{ p.Collapsible = sidebar.CollapsibleIcon }} } @sidebar.Sidebar(p) { @sidebar.Header() { @TeamSwitcher(data.Teams) } @sidebar.Content() { @NavMain(data.NavMain) @NavProjects(data.Projects) } @sidebar.Footer() { @NavUser(data.User) } @sidebar.Rail() }}package sidebar07 import ( "github.com/axadrn/shadcn-templ/v2/components/collapsible" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type NavMainItem struct { Title string URL string Icon func(...icon.Props) templ.Component IsActive bool Items []NavMainSubItem} type NavMainSubItem struct { Title string URL string} templ NavMain(items []NavMainItem) { @sidebar.Group() { @sidebar.GroupLabel() { Platform } @sidebar.Menu() { for _, item := range items { @collapsible.Collapsible(collapsible.Props{ DefaultOpen: item.IsActive, Class: "group/collapsible", }) { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Tooltip: item.Title, Attributes: collapsible.Trigger(ctx), }) { if item.Icon != nil { @item.Icon() } <span>{ item.Title }</span> @icon.ChevronRight(icon.Props{Class: "ml-auto transition-transform duration-200 group-data-open/collapsible:rotate-90"}) } @collapsible.Content() { @sidebar.MenuSub() { for _, subItem := range item.Items { @sidebar.MenuSubItem() { @sidebar.MenuSubButton(sidebar.MenuSubButtonProps{Href: subItem.URL}) { <span>{ subItem.Title }</span> } } } } } } } } } }}package sidebar07 import ( "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type Project struct { Name string URL string Icon func(...icon.Props) templ.Component} templ NavProjects(projects []Project) { @sidebar.Group(sidebar.GroupProps{Class: "group-data-[collapsible=icon]:hidden"}) { @sidebar.GroupLabel() { Projects } @sidebar.Menu() { for _, item := range projects { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Href: item.URL}) { @item.Icon() <span>{ item.Name }</span> } @dropdownmenu.DropdownMenu() { @sidebar.MenuAction(sidebar.MenuActionProps{ ShowOnHover: true, Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { @icon.Ellipsis() <span class="sr-only">More</span> } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "w-fit", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignStart, MobileSide: dropdownmenu.SideBottom, MobileAlign: dropdownmenu.AlignEnd, }) { @dropdownmenu.Item() { @icon.Folder() <span>View Project</span> } @dropdownmenu.Item() { @icon.ArrowRight() <span>Share Project</span> } @dropdownmenu.Separator() @dropdownmenu.Item(dropdownmenu.ItemProps{Variant: dropdownmenu.ItemVariantDestructive}) { @icon.Trash2() <span>Delete Project</span> } } } } } @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Class: "text-sidebar-foreground/70"}) { @icon.Ellipsis(icon.Props{Class: "text-sidebar-foreground/70"}) <span>More</span> } } } }}package sidebar07 import ( "github.com/axadrn/shadcn-templ/v2/components/avatar" "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type User struct { Name string Email string Avatar string} templ NavUser(user User) { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { @avatar.Avatar() { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback() { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> @icon.ChevronsUpDown(icon.Props{Class: "ml-auto size-4"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "w-fit", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignEnd, MobileSide: dropdownmenu.SideBottom, SideOffset: 4, }) { @dropdownmenu.Group() { @dropdownmenu.Label(dropdownmenu.LabelProps{Class: "p-0 font-normal"}) { <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> @avatar.Avatar() { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback() { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> </div> } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.Sparkles() Upgrade to Pro } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.BadgeCheck() Account } @dropdownmenu.Item() { @icon.CreditCard() Billing } @dropdownmenu.Item() { @icon.Bell() Notifications } } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.LogOut() Log out } } } } }}package sidebar07 import ( "strconv" "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type Team struct { Name string Logo func(...icon.Props) templ.Component Plan string} func teamLogoHidden(i int) string { if i == 0 { return "" } return " hidden"} templ TeamSwitcher(teams []Team) { if len(teams) > 0 { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu(dropdownmenu.Props{ID: "sidebar07-team-menu"}) { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Class: "data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground", Attributes: dropdownmenu.Trigger(ctx), }) { <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> // Render every logo up front; the page script shows the active one. for i, team := range teams { @team.Logo(icon.Props{ Class: teamLogoHidden(i), Attributes: templ.Attributes{"data-sidebar07-team-logo": strconv.Itoa(i)}, }) } </div> <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium" data-sidebar07-team-name>{ teams[0].Name }</span> <span class="truncate text-xs" data-sidebar07-team-plan>{ teams[0].Plan }</span> </div> @icon.ChevronsUpDown(icon.Props{Class: "ml-auto"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "w-fit", Align: dropdownmenu.AlignStart, Side: dropdownmenu.SideRight, MobileSide: dropdownmenu.SideBottom, SideOffset: 4, }) { @dropdownmenu.Group() { @dropdownmenu.Label(dropdownmenu.LabelProps{Class: "text-xs text-muted-foreground"}) { Teams } for i, team := range teams { @dropdownmenu.Item(dropdownmenu.ItemProps{ Class: "gap-2 p-2", Attributes: templ.Attributes{"data-sidebar07-team-index": strconv.Itoa(i)}, }) { <div class="flex size-6 items-center justify-center rounded-md border"> @team.Logo() </div> { team.Name } @dropdownmenu.Shortcut() { ⌘{ strconv.Itoa(i + 1) } } } } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item(dropdownmenu.ItemProps{Class: "gap-2 p-2"}) { <div class="flex size-6 items-center justify-center rounded-md border bg-transparent"> @icon.Plus(icon.Props{Class: "size-4"}) </div> <div class="font-medium text-muted-foreground">Add team</div> } } } } } } }}package sidebar08 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @AppSidebar() @sidebar.Inset() { <header class="flex h-16 shrink-0 items-center gap-2"> <div class="flex items-center gap-2 px-4"> @sidebar.Trigger(sidebar.TriggerProps{Class: "-ml-1"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { Build Your Application } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { Data Fetching } } } } </div> </header> <div class="flex flex-1 flex-col gap-4 p-4 pt-0"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> </div> <div class="min-h-screen flex-1 rounded-xl bg-muted/50 md:min-h-min"></div> </div> } }}package sidebar08 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") var data = struct { User User NavMain []NavMainItem NavSecondary []NavSecondaryItem Projects []Project}{ User: User{ Name: "Axel Adrian", Email: "[email protected]", Avatar: "https://github.com/axadrn.png", }, NavMain: []NavMainItem{ { Title: "Playground", URL: "#", Icon: icon.SquareTerminal, IsActive: true, Items: []NavMainSubItem{ {Title: "History", URL: "#"}, {Title: "Starred", URL: "#"}, {Title: "Settings", URL: "#"}, }, }, { Title: "Models", URL: "#", Icon: icon.Bot, Items: []NavMainSubItem{ {Title: "Genesis", URL: "#"}, {Title: "Explorer", URL: "#"}, {Title: "Quantum", URL: "#"}, }, }, { Title: "Documentation", URL: "#", Icon: icon.BookOpen, Items: []NavMainSubItem{ {Title: "Introduction", URL: "#"}, {Title: "Get Started", URL: "#"}, {Title: "Tutorials", URL: "#"}, {Title: "Changelog", URL: "#"}, }, }, { Title: "Settings", URL: "#", Icon: icon.Settings2, Items: []NavMainSubItem{ {Title: "General", URL: "#"}, {Title: "Team", URL: "#"}, {Title: "Billing", URL: "#"}, {Title: "Limits", URL: "#"}, }, }, }, NavSecondary: []NavSecondaryItem{ {Title: "Support", URL: "#", Icon: icon.LifeBuoy}, {Title: "Feedback", URL: "#", Icon: icon.Send}, }, Projects: []Project{ {Name: "Design Engineering", URL: "#", Icon: icon.Frame}, {Name: "Sales & Marketing", URL: "#", Icon: icon.ChartPie}, {Name: "Travel", URL: "#", Icon: icon.Map}, },} templ AppSidebar(props ...sidebar.Props) { {{ var p sidebar.Props }} if len(props) > 0 { {{ p = props[0] }} } if p.Variant == "" { {{ p.Variant = sidebar.VariantInset }} } @sidebar.Sidebar(p) { @sidebar.Header() { @sidebar.Menu() { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Size: sidebar.MenuButtonSizeLg, Href: "#"}) { <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> @icon.Terminal(icon.Props{Class: "size-4"}) </div> <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">Acme Inc</span> <span class="truncate text-xs">Enterprise</span> </div> } } } } @sidebar.Content() { @NavMain(data.NavMain) @NavProjects(data.Projects) @NavSecondary(data.NavSecondary, sidebar.GroupProps{Class: "mt-auto"}) } @sidebar.Footer() { @NavUser(data.User) } }}package sidebar08 import ( "github.com/axadrn/shadcn-templ/v2/components/collapsible" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type NavMainItem struct { Title string URL string Icon func(...icon.Props) templ.Component IsActive bool Items []NavMainSubItem} type NavMainSubItem struct { Title string URL string} templ NavMain(items []NavMainItem) { @sidebar.Group() { @sidebar.GroupLabel() { Platform } @sidebar.Menu() { for _, item := range items { @collapsible.Collapsible(collapsible.Props{DefaultOpen: item.IsActive}) { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: item.URL, Tooltip: item.Title, }) { @item.Icon() <span>{ item.Title }</span> } if len(item.Items) > 0 { @sidebar.MenuAction(sidebar.MenuActionProps{ Class: "aria-expanded:rotate-90", Attributes: collapsible.Trigger(ctx), }) { @icon.ChevronRight() <span class="sr-only">Toggle</span> } @collapsible.Content() { @sidebar.MenuSub() { for _, subItem := range item.Items { @sidebar.MenuSubItem() { @sidebar.MenuSubButton(sidebar.MenuSubButtonProps{Href: subItem.URL}) { <span>{ subItem.Title }</span> } } } } } } } } } } }}package sidebar08 import ( "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type Project struct { Name string URL string Icon func(...icon.Props) templ.Component} templ NavProjects(projects []Project) { @sidebar.Group(sidebar.GroupProps{Class: "group-data-[collapsible=icon]:hidden"}) { @sidebar.GroupLabel() { Projects } @sidebar.Menu() { for _, item := range projects { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Href: item.URL}) { @item.Icon() <span>{ item.Name }</span> } @dropdownmenu.DropdownMenu() { @sidebar.MenuAction(sidebar.MenuActionProps{ ShowOnHover: true, Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { @icon.Ellipsis() <span class="sr-only">More</span> } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "w-48", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignStart, MobileSide: dropdownmenu.SideBottom, MobileAlign: dropdownmenu.AlignEnd, }) { @dropdownmenu.Item() { @icon.Folder(icon.Props{Class: "text-muted-foreground"}) <span>View Project</span> } @dropdownmenu.Item() { @icon.Share(icon.Props{Class: "text-muted-foreground"}) <span>Share Project</span> } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.Trash2(icon.Props{Class: "text-muted-foreground"}) <span>Delete Project</span> } } } } } @sidebar.MenuItem() { @sidebar.MenuButton() { @icon.Ellipsis() <span>More</span> } } } }}package sidebar08 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type NavSecondaryItem struct { Title string URL string Icon func(...icon.Props) templ.Component} templ NavSecondary(items []NavSecondaryItem, props ...sidebar.GroupProps) { @sidebar.Group(props...) { @sidebar.GroupContent() { @sidebar.Menu() { for _, item := range items { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: item.URL, Size: sidebar.MenuButtonSizeSm, }) { @item.Icon() <span>{ item.Title }</span> } } } } } }}package sidebar08 import ( "github.com/axadrn/shadcn-templ/v2/components/avatar" "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type User struct { Name string Email string Avatar string} templ NavUser(user User) { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { @avatar.Avatar() { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback() { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> @icon.ChevronsUpDown(icon.Props{Class: "ml-auto size-4"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "min-w-56 rounded-lg", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignEnd, MobileSide: dropdownmenu.SideBottom, SideOffset: 4, }) { @dropdownmenu.Group() { @dropdownmenu.Label(dropdownmenu.LabelProps{Class: "p-0 font-normal"}) { <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> @avatar.Avatar() { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback() { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> </div> } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.Sparkles() Upgrade to Pro } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.BadgeCheck() Account } @dropdownmenu.Item() { @icon.CreditCard() Billing } @dropdownmenu.Item() { @icon.Bell() Notifications } } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.LogOut() Log out } } } } }}package sidebar09 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider(sidebar.ProviderProps{ // Set both custom widths because this style replaces the provider default. Attributes: templ.Attributes{"style": "--sidebar-width: 350px; --sidebar-width-icon: 3rem;"}, }) { @AppSidebar() @sidebar.Inset() { <header class="sticky top-0 flex shrink-0 items-center gap-2 border-b bg-background p-4"> @sidebar.Trigger(sidebar.TriggerProps{Class: "-ml-1"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { All Inboxes } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { Inbox } } } } </header> <div class="flex flex-1 flex-col gap-4 p-4"> for i := 0; i < 24; i++ { <div class="aspect-video h-12 w-full rounded-lg bg-muted/50"></div> } </div> } } <script nonce={ templ.GetNonce(ctx) }> (() => { // The activeItem/setMails/setOpen pendant: a picked inbox item // activates itself, retitles the mail sidebar, reshuffles the // mail list to a random 5-10 slice and expands the sidebar. document.addEventListener("click", (e) => { if (!(e.target instanceof Element)) return; const btn = e.target.closest("[data-sidebar09-nav]"); if (!btn) return; document.querySelectorAll("[data-sidebar09-nav]").forEach((el) => { el.toggleAttribute("data-active", el === btn); }); const title = document.querySelector("[data-sidebar09-active-title]"); if (title) title.textContent = btn.getAttribute("data-sidebar09-nav"); const list = document.querySelector("[data-sidebar09-mails]"); if (list) { const items = [...list.children]; items.sort(() => Math.random() - 0.5); const count = Math.max(5, Math.floor(Math.random() * 10) + 1); items.forEach((item, i) => { list.appendChild(item); item.hidden = i >= count; }); } window.tui.sidebar.setOpen(true, "sidebar09-icon-sidebar"); }); })(); </script>}package sidebar09 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/label" "github.com/axadrn/shadcn-templ/v2/components/sidebar" switchcomp "github.com/axadrn/shadcn-templ/v2/components/switch" "github.com/axadrn/shadcn-templ/v2/utils") // This is sample datatype navItem struct { Title string URL string Icon func(...icon.Props) templ.Component IsActive bool} type mail struct { Name string Email string Subject string Date string Teaser string} var user = User{ Name: "Axel Adrian", Email: "[email protected]", Avatar: "https://github.com/axadrn.png",} var navMain = []navItem{ {Title: "Inbox", URL: "#", Icon: icon.Inbox, IsActive: true}, {Title: "Drafts", URL: "#", Icon: icon.File, IsActive: false}, {Title: "Sent", URL: "#", Icon: icon.Send, IsActive: false}, {Title: "Junk", URL: "#", Icon: icon.ArchiveX, IsActive: false}, {Title: "Trash", URL: "#", Icon: icon.Trash2, IsActive: false},} var mails = []mail{ { Name: "William Smith", Email: "[email protected]", Subject: "Meeting Tomorrow", Date: "09:34 AM", Teaser: "Hi team, just a reminder about our meeting tomorrow at 10 AM.\nPlease come prepared with your project updates.", }, { Name: "Alice Smith", Email: "[email protected]", Subject: "Re: Project Update", Date: "Yesterday", Teaser: "Thanks for the update. The progress looks great so far.\nLet's schedule a call to discuss the next steps.", }, { Name: "Bob Johnson", Email: "[email protected]", Subject: "Weekend Plans", Date: "2 days ago", Teaser: "Hey everyone! I'm thinking of organizing a team outing this weekend.\nWould you be interested in a hiking trip or a beach day?", }, { Name: "Emily Davis", Email: "[email protected]", Subject: "Re: Question about Budget", Date: "2 days ago", Teaser: "I've reviewed the budget numbers you sent over.\nCan we set up a quick call to discuss some potential adjustments?", }, { Name: "Michael Wilson", Email: "[email protected]", Subject: "Important Announcement", Date: "1 week ago", Teaser: "Please join us for an all-hands meeting this Friday at 3 PM.\nWe have some exciting news to share about the company's future.", }, { Name: "Sarah Brown", Email: "[email protected]", Subject: "Re: Feedback on Proposal", Date: "1 week ago", Teaser: "Thank you for sending over the proposal. I've reviewed it and have some thoughts.\nCould we schedule a meeting to discuss my feedback in detail?", }, { Name: "David Lee", Email: "[email protected]", Subject: "New Project Idea", Date: "1 week ago", Teaser: "I've been brainstorming and came up with an interesting project concept.\nDo you have time this week to discuss its potential impact and feasibility?", }, { Name: "Olivia Wilson", Email: "[email protected]", Subject: "Vacation Plans", Date: "1 week ago", Teaser: "Just a heads up that I'll be taking a two-week vacation next month.\nI'll make sure all my projects are up to date before I leave.", }, { Name: "James Martin", Email: "[email protected]", Subject: "Re: Conference Registration", Date: "1 week ago", Teaser: "I've completed the registration for the upcoming tech conference.\nLet me know if you need any additional information from my end.", }, { Name: "Sophia White", Email: "[email protected]", Subject: "Team Dinner", Date: "1 week ago", Teaser: "To celebrate our recent project success, I'd like to organize a team dinner.\nAre you available next Friday evening? Please let me know your preferences.", },} templ AppSidebar() { // Note: I'm using state to show active item. // IRL you should use the url/router. @sidebar.Sidebar(sidebar.Props{ Collapsible: sidebar.CollapsibleIcon, // [&_[data-tui-sidebar-content]]:flex-row is shadcn-templ wiring: the // templ sidebar nests the children one wrapper deeper than the tsx, so // the *:data-[sidebar=sidebar]:flex-row alone does not reach them. Class: "overflow-hidden *:data-[sidebar=sidebar]:flex-row [&_[data-tui-sidebar-content]]:flex-row", }) { // This is the first sidebar // We disable collapsible and adjust width to icon. // This will make the sidebar appear as icons. @sidebar.Sidebar(sidebar.Props{ ID: "sidebar09-icon-sidebar", Collapsible: sidebar.CollapsibleNone, Class: "w-[calc(var(--sidebar-width-icon)+1px)]! border-r", }) { @sidebar.Header() { @sidebar.Menu() { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Class: "md:h-8 md:p-0", Href: "#", }) { <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> @icon.Terminal(icon.Props{Class: "size-4"}) </div> <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">Acme Inc</span> <span class="truncate text-xs">Enterprise</span> </div> } } } } @sidebar.Content() { @sidebar.Group() { @sidebar.GroupContent(sidebar.GroupContentProps{Class: "px-1.5 md:px-0"}) { @sidebar.Menu() { for _, item := range navMain { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Tooltip: item.Title, // tooltip={{ children, hidden: false }} forces the // tooltip in every sidebar state. TooltipHidden: utils.Ptr(false), IsActive: item.IsActive, Class: "px-2.5 md:px-2", Attributes: templ.Attributes{"data-sidebar09-nav": item.Title}, }) { @item.Icon() <span>{ item.Title }</span> } } } } } } } @sidebar.Footer() { @NavUser(user) } } // This is the second sidebar // We disable collapsible and let it fill remaining space @sidebar.Sidebar(sidebar.Props{ ID: "sidebar09-mail-sidebar", Collapsible: sidebar.CollapsibleNone, Class: "hidden flex-1 md:flex", }) { @sidebar.Header(sidebar.HeaderProps{Class: "gap-3.5 border-b p-4"}) { <div class="flex w-full items-center justify-between"> <div class="text-base font-medium text-foreground" data-sidebar09-active-title> { navMain[0].Title } </div> @label.Label(label.Props{Class: "flex items-center gap-2 text-sm"}) { <span>Unreads</span> @switchcomp.Switch(switchcomp.Props{Class: "shadow-none"}) } </div> @sidebar.Input(sidebar.InputProps{Placeholder: "Type to search..."}) } @sidebar.Content() { @sidebar.Group(sidebar.GroupProps{Class: "px-0"}) { @sidebar.GroupContent(sidebar.GroupContentProps{Attributes: templ.Attributes{"data-sidebar09-mails": ""}}) { for _, mail := range mails { <a href="#" class="flex flex-col items-start gap-2 border-b p-4 text-sm leading-tight whitespace-nowrap last:border-b-0 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground" > <div class="flex w-full items-center gap-2"> <span>{ mail.Name }</span> { " " } <span class="ml-auto text-xs">{ mail.Date }</span> </div> <span class="font-medium">{ mail.Subject }</span> <span class="line-clamp-2 w-[260px] text-xs whitespace-break-spaces"> { mail.Teaser } </span> </a> } } } } } }}package sidebar09 import ( "github.com/axadrn/shadcn-templ/v2/components/avatar" "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type User struct { Name string Email string Avatar string} templ NavUser(user User) { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Class: "md:h-8 md:p-0 data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground", Attributes: dropdownmenu.Trigger(ctx), }) { @avatar.Avatar(avatar.Props{Class: "h-8 w-8 rounded-lg"}) { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback(avatar.FallbackProps{Class: "rounded-lg"}) { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> @icon.ChevronsUpDown(icon.Props{Class: "ml-auto size-4"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "min-w-56 rounded-lg", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignEnd, MobileSide: dropdownmenu.SideBottom, SideOffset: 4, }) { @dropdownmenu.Label(dropdownmenu.LabelProps{Class: "p-0 font-normal"}) { <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> @avatar.Avatar(avatar.Props{Class: "h-8 w-8 rounded-lg"}) { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback(avatar.FallbackProps{Class: "rounded-lg"}) { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> </div> } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.Sparkles() Upgrade to Pro } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.BadgeCheck() Account } @dropdownmenu.Item() { @icon.CreditCard() Billing } @dropdownmenu.Item() { @icon.Bell() Notifications } } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.LogOut() Log out } } } } }}package sidebar10 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @AppSidebar() @sidebar.Inset() { <header class="flex h-14 shrink-0 items-center gap-2"> <div class="flex flex-1 items-center gap-2 px-3"> @sidebar.Trigger() @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item() { @breadcrumb.Page(breadcrumb.ItemProps{Class: "line-clamp-1"}) { Project Management & Task Tracking } } } } </div> <div class="ml-auto px-3"> @NavActions() </div> </header> <div class="flex flex-1 flex-col gap-4 px-4 py-10"> <div class="mx-auto h-24 w-full max-w-3xl rounded-xl bg-muted/50"></div> <div class="mx-auto h-full w-full max-w-3xl rounded-xl bg-muted/50"></div> </div> } } <script nonce={ templ.GetNonce(ctx) }> (() => { // Keep the selected team details in sync. document.addEventListener("click", (e) => { if (!(e.target instanceof Element)) return; const item = e.target.closest("#sidebar10-team-menu [data-sidebar10-team-index]"); if (!item) return; const i = item.getAttribute("data-sidebar10-team-index"); const name = item.textContent.trim().replace(/⌘\d$/, "").trim(); document.querySelectorAll("[data-sidebar10-team-logo]").forEach((logo) => { logo.classList.toggle("hidden", logo.getAttribute("data-sidebar10-team-logo") !== i); }); const nameEl = document.querySelector("[data-sidebar10-team-name]"); if (nameEl) nameEl.textContent = name; }); })(); </script>}package sidebar10 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar" "github.com/axadrn/shadcn-templ/v2/utils") // This is sample data.type Team struct { Name string Logo func(...icon.Props) templ.Component Plan string} type NavItem struct { Title string URL string Icon func(...icon.Props) templ.Component IsActive bool Badge string} type Favorite struct { Name string URL string Emoji string} type WorkspacePage struct { Name string URL string Emoji string} type Workspace struct { Name string Emoji string Pages []WorkspacePage} var teams = []Team{ {Name: "Acme Inc", Logo: icon.Terminal, Plan: "Enterprise"}, {Name: "Acme Corp.", Logo: icon.AudioLines, Plan: "Startup"}, {Name: "Evil Corp.", Logo: icon.Terminal, Plan: "Free"},} var navMain = []NavItem{ {Title: "Search", URL: "#", Icon: icon.Search}, {Title: "Ask AI", URL: "#", Icon: icon.Sparkles}, {Title: "Home", URL: "#", Icon: icon.House, IsActive: true}, {Title: "Inbox", URL: "#", Icon: icon.Inbox, Badge: "10"},} var navSecondary = []NavItem{ {Title: "Calendar", URL: "#", Icon: icon.Calendar}, {Title: "Settings", URL: "#", Icon: icon.Settings2}, {Title: "Templates", URL: "#", Icon: icon.Blocks}, {Title: "Trash", URL: "#", Icon: icon.Trash2}, {Title: "Help", URL: "#", Icon: icon.MessageCircleQuestionMark},} var favorites = []Favorite{ {Name: "Project Management & Task Tracking", URL: "#", Emoji: "📊"}, {Name: "Family Recipe Collection & Meal Planning", URL: "#", Emoji: "🍳"}, {Name: "Fitness Tracker & Workout Routines", URL: "#", Emoji: "💪"}, {Name: "Book Notes & Reading List", URL: "#", Emoji: "📚"}, {Name: "Sustainable Gardening Tips & Plant Care", URL: "#", Emoji: "🌱"}, {Name: "Language Learning Progress & Resources", URL: "#", Emoji: "🗣️"}, {Name: "Home Renovation Ideas & Budget Tracker", URL: "#", Emoji: "🏠"}, {Name: "Personal Finance & Investment Portfolio", URL: "#", Emoji: "💰"}, {Name: "Movie & TV Show Watchlist with Reviews", URL: "#", Emoji: "🎬"}, {Name: "Daily Habit Tracker & Goal Setting", URL: "#", Emoji: "✅"},} var workspaces = []Workspace{ { Name: "Personal Life Management", Emoji: "🏠", Pages: []WorkspacePage{ {Name: "Daily Journal & Reflection", URL: "#", Emoji: "📔"}, {Name: "Health & Wellness Tracker", URL: "#", Emoji: "🍏"}, {Name: "Personal Growth & Learning Goals", URL: "#", Emoji: "🌟"}, }, }, { Name: "Professional Development", Emoji: "💼", Pages: []WorkspacePage{ {Name: "Career Objectives & Milestones", URL: "#", Emoji: "🎯"}, {Name: "Skill Acquisition & Training Log", URL: "#", Emoji: "🧠"}, {Name: "Networking Contacts & Events", URL: "#", Emoji: "🤝"}, }, }, { Name: "Creative Projects", Emoji: "🎨", Pages: []WorkspacePage{ {Name: "Writing Ideas & Story Outlines", URL: "#", Emoji: "✍️"}, {Name: "Art & Design Portfolio", URL: "#", Emoji: "🖼️"}, {Name: "Music Composition & Practice Log", URL: "#", Emoji: "🎵"}, }, }, { Name: "Home Management", Emoji: "🏡", Pages: []WorkspacePage{ {Name: "Household Budget & Expense Tracking", URL: "#", Emoji: "💰"}, {Name: "Home Maintenance Schedule & Tasks", URL: "#", Emoji: "🔧"}, {Name: "Family Calendar & Event Planning", URL: "#", Emoji: "📅"}, }, }, { Name: "Travel & Adventure", Emoji: "🧳", Pages: []WorkspacePage{ {Name: "Trip Planning & Itineraries", URL: "#", Emoji: "🗺️"}, {Name: "Travel Bucket List & Inspiration", URL: "#", Emoji: "🌎"}, {Name: "Travel Journal & Photo Gallery", URL: "#", Emoji: "📸"}, }, },} templ AppSidebar(props ...sidebar.Props) { {{ p := sidebar.Props{} }} if len(props) > 0 { {{ p = props[0] }} } {{ p.Class = utils.CN("border-r-0", p.Class) }} @sidebar.Sidebar(p) { @sidebar.Header() { @TeamSwitcher(teams) @NavMain(navMain) } @sidebar.Content() { @NavFavorites(favorites) @NavWorkspaces(workspaces) @NavSecondary(navSecondary, "mt-auto") } @sidebar.Rail() }}package sidebar10 import ( "github.com/axadrn/shadcn-templ/v2/components/button" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/popover" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type action struct { Label string Icon func(...icon.Props) templ.Component} var actions = [][]action{ { {Label: "Customize Page", Icon: icon.Settings2}, {Label: "Turn into wiki", Icon: icon.FileText}, }, { {Label: "Copy Link", Icon: icon.Link}, {Label: "Duplicate", Icon: icon.Copy}, {Label: "Move to", Icon: icon.CornerUpRight}, {Label: "Move to Trash", Icon: icon.Trash2}, }, { {Label: "Undo", Icon: icon.CornerUpLeft}, {Label: "View analytics", Icon: icon.ChartLine}, {Label: "Version History", Icon: icon.GalleryVerticalEnd}, {Label: "Show delete pages", Icon: icon.Trash}, {Label: "Notifications", Icon: icon.Bell}, }, { {Label: "Import", Icon: icon.ArrowUp}, {Label: "Export", Icon: icon.ArrowDown}, },} templ NavActions() { <div class="flex items-center gap-2 text-sm"> <div class="hidden font-medium text-muted-foreground md:inline-block"> Edit Oct 08 </div> @button.Button(button.Props{ Variant: button.VariantGhost, Size: button.SizeIcon, Class: "h-7 w-7", }) { @icon.Star() } @popover.Popover(popover.Props{ ID: "sidebar10-actions-popover", DefaultOpen: true, }) { @button.Button(button.Props{ Variant: button.VariantGhost, Size: button.SizeIcon, Class: "h-7 w-7 data-popup-open:bg-accent", Attributes: popover.Trigger(ctx), }) { @icon.Ellipsis() } @popover.Content(popover.ContentProps{ Class: "w-56 overflow-hidden rounded-lg p-0", Align: popover.AlignEnd, }) { @sidebar.Sidebar(sidebar.Props{ ID: "sidebar10-nav-actions", Collapsible: sidebar.CollapsibleNone, Class: "bg-transparent", }) { @sidebar.Content() { for _, group := range actions { @sidebar.Group(sidebar.GroupProps{Class: "border-b last:border-none"}) { @sidebar.GroupContent(sidebar.GroupContentProps{Class: "gap-0"}) { @sidebar.Menu() { for _, item := range group { @sidebar.MenuItem() { @sidebar.MenuButton() { @item.Icon() <span>{ item.Label }</span> } } } } } } } } } } } </div>}package sidebar10 import ( "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ NavFavorites(favorites []Favorite) { @sidebar.Group(sidebar.GroupProps{Class: "group-data-[collapsible=icon]:hidden"}) { @sidebar.GroupLabel() { Favorites } @sidebar.Menu() { for _, item := range favorites { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: item.URL, Attributes: templ.Attributes{"title": item.Name}, }) { <span>{ item.Emoji }</span> <span>{ item.Name }</span> } @dropdownmenu.DropdownMenu() { @sidebar.MenuAction(sidebar.MenuActionProps{ ShowOnHover: true, Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { @icon.Ellipsis() <span class="sr-only">More</span> } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "w-56 rounded-lg", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignStart, MobileSide: dropdownmenu.SideBottom, MobileAlign: dropdownmenu.AlignEnd, }) { @dropdownmenu.Item() { @icon.StarOff(icon.Props{Class: "text-muted-foreground"}) <span>Remove from Favorites</span> } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.Link(icon.Props{Class: "text-muted-foreground"}) <span>Copy Link</span> } @dropdownmenu.Item() { @icon.ArrowUpRight(icon.Props{Class: "text-muted-foreground"}) <span>Open in New Tab</span> } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.Trash2(icon.Props{Class: "text-muted-foreground"}) <span>Delete</span> } } } } } @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Class: "text-sidebar-foreground/70"}) { @icon.Ellipsis() <span>More</span> } } } }}package sidebar10 import "github.com/axadrn/shadcn-templ/v2/components/sidebar" templ NavMain(items []NavItem) { @sidebar.Menu() { for _, item := range items { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: item.URL, IsActive: item.IsActive, }) { @item.Icon() <span>{ item.Title }</span> } } } }}package sidebar10 import "github.com/axadrn/shadcn-templ/v2/components/sidebar" templ NavSecondary(items []NavItem, class string) { @sidebar.Group(sidebar.GroupProps{Class: class}) { @sidebar.GroupContent() { @sidebar.Menu() { for _, item := range items { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Href: item.URL}) { @item.Icon() <span>{ item.Title }</span> } if item.Badge != "" { @sidebar.MenuBadge() { { item.Badge } } } } } } } }}package sidebar10 import ( "github.com/axadrn/shadcn-templ/v2/components/collapsible" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ NavWorkspaces(workspaces []Workspace) { @sidebar.Group() { @sidebar.GroupLabel() { Workspaces } @sidebar.GroupContent() { @sidebar.Menu() { for _, workspace := range workspaces { @collapsible.Collapsible() { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Href: "#"}) { <span>{ workspace.Emoji }</span> <span>{ workspace.Name }</span> } @sidebar.MenuAction(sidebar.MenuActionProps{ Class: "left-2 bg-sidebar-accent text-sidebar-accent-foreground data-popup-open:rotate-90", ShowOnHover: true, Attributes: collapsible.Trigger(ctx), }) { @icon.ChevronRight() } @sidebar.MenuAction(sidebar.MenuActionProps{ShowOnHover: true}) { @icon.Plus() } @collapsible.Content() { @sidebar.MenuSub() { for _, page := range workspace.Pages { @sidebar.MenuSubItem() { @sidebar.MenuSubButton(sidebar.MenuSubButtonProps{Href: "#"}) { <span>{ page.Emoji }</span> <span>{ page.Name }</span> } } } } } } } } @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Class: "text-sidebar-foreground/70"}) { @icon.Ellipsis() <span>More</span> } } } } }}package sidebar10 import ( "strconv" "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ TeamSwitcher(teams []Team) { if len(teams) > 0 { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu(dropdownmenu.Props{ID: "sidebar10-team-menu"}) { @sidebar.MenuButton(sidebar.MenuButtonProps{ Class: "w-fit px-1.5", Attributes: dropdownmenu.Trigger(ctx), }) { <div class="flex aspect-square size-5 items-center justify-center rounded-md bg-sidebar-primary text-sidebar-primary-foreground"> // Render every logo up front; the page script shows the active one. for i, team := range teams { @team.Logo(icon.Props{ Class: teamLogoHidden(i), Attributes: templ.Attributes{"data-sidebar10-team-logo": strconv.Itoa(i)}, }) } </div> <span class="truncate font-medium" data-sidebar10-team-name>{ teams[0].Name }</span> @icon.ChevronDown(icon.Props{Class: "opacity-50"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "w-64 rounded-lg", Align: dropdownmenu.AlignStart, Side: dropdownmenu.SideBottom, SideOffset: 4, }) { @dropdownmenu.Label(dropdownmenu.LabelProps{Class: "text-xs text-muted-foreground"}) { Teams } for i, team := range teams { @dropdownmenu.Item(dropdownmenu.ItemProps{ Class: "gap-2 p-2", Attributes: templ.Attributes{"data-sidebar10-team-index": strconv.Itoa(i)}, }) { <div class="flex size-6 items-center justify-center rounded-xs border"> @team.Logo() </div> { team.Name } @dropdownmenu.Shortcut() { ⌘{ strconv.Itoa(i + 1) } } } } @dropdownmenu.Separator() @dropdownmenu.Item(dropdownmenu.ItemProps{Class: "gap-2 p-2"}) { <div class="flex size-6 items-center justify-center rounded-md border bg-background"> @icon.Plus(icon.Props{Class: "size-4"}) </div> <div class="font-medium text-muted-foreground">Add team</div> } } } } } }} // teamLogoHidden hides every logo except the active one, the SSR half of the// page script's team switch.func teamLogoHidden(i int) string { if i == 0 { return "" } return " hidden"}package sidebar11 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @AppSidebar() @sidebar.Inset() { <header class="flex h-16 shrink-0 items-center gap-2 border-b px-4"> @sidebar.Trigger(sidebar.TriggerProps{Class: "-ml-1"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { components } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { ui } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { button.templ } } } } </header> <div class="flex flex-1 flex-col gap-4 p-4"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> </div> <div class="min-h-screen flex-1 rounded-xl bg-muted/50 md:min-h-min"></div> </div> } }}package sidebar11 import ( "github.com/axadrn/shadcn-templ/v2/components/collapsible" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") // This is sample data.type change struct { File string State string} var changes = []change{ {File: "README.md", State: "M"}, {File: "api/hello/route.ts", State: "U"}, {File: "app/layout.templ", State: "M"},} // treeData mirrors the tsx's TreeItem = string | TreeItem[] shape: a string is// a file, a slice is a folder whose first element is its name.var treeData = []any{ []any{ "app", []any{ "api", []any{"hello", []any{"route.ts"}}, "page.templ", "layout.templ", []any{"blog", []any{"page.templ"}}, }, }, []any{ "components", []any{"ui", "button.templ", "card.templ"}, "header.templ", "footer.templ", }, []any{"lib", []any{"util.ts"}}, []any{"public", "favicon.ico", "vercel.svg"}, ".eslintrc.json", ".gitignore", "next.config.js", "tailwind.config.js", "package.json", "README.md",} templ AppSidebar(props ...sidebar.Props) { {{ var p sidebar.Props }} if len(props) > 0 { {{ p = props[0] }} } @sidebar.Sidebar(p) { @sidebar.Content() { @sidebar.Group() { @sidebar.GroupLabel() { Changes } @sidebar.GroupContent() { @sidebar.Menu() { for _, item := range changes { @sidebar.MenuItem() { @sidebar.MenuButton() { @icon.File() { item.File } } @sidebar.MenuBadge() { { item.State } } } } } } } @sidebar.Group() { @sidebar.GroupLabel() { Files } @sidebar.GroupContent() { @sidebar.Menu() { for _, item := range treeData { @tree(item) } } } } } @sidebar.Rail() }} // treeParts is the pendant of the tsx's// const [name, ...items] = Array.isArray(item) ? item : [item]func treeParts(item any) (string, []any) { if arr, ok := item.([]any); ok { if len(arr) == 0 { return "", nil } name, _ := arr[0].(string) return name, arr[1:] } name, _ := item.(string) return name, nil} templ tree(item any) { {{ name, items := treeParts(item) }} if len(items) == 0 { @sidebar.MenuButton(sidebar.MenuButtonProps{ IsActive: name == "button.templ", Class: "data-[active=true]:bg-transparent", }) { @icon.File() { name } } } else { @sidebar.MenuItem() { @collapsible.Collapsible(collapsible.Props{ Class: "group/collapsible [&[data-open]>button>svg:first-child]:rotate-90", DefaultOpen: name == "components" || name == "ui", }) { @sidebar.MenuButton(sidebar.MenuButtonProps{ Attributes: collapsible.Trigger(ctx), }) { @icon.ChevronRight(icon.Props{Class: "transition-transform"}) @icon.Folder() { name } } @collapsible.Content() { @sidebar.MenuSub() { for _, subItem := range items { @tree(subItem) } } } } } }}package sidebar12 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @AppSidebar() @sidebar.Inset() { <header class="sticky top-0 flex h-16 shrink-0 items-center gap-2 border-b bg-background px-4"> @sidebar.Trigger(sidebar.TriggerProps{Class: "-ml-1"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item() { @breadcrumb.Page() { October 2024 } } } } </header> <div class="flex flex-1 flex-col gap-4 p-4"> <div class="grid auto-rows-min gap-4 md:grid-cols-5"> for i := 0; i < 20; i++ { <div class="aspect-square rounded-xl bg-muted/50"></div> } </div> </div> } }}package sidebar12 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") // This is sample data.var user = User{ Name: "Axel Adrian", Email: "[email protected]", Avatar: "https://github.com/axadrn.png",} var calendars = []Calendar{ {Name: "My Calendars", Items: []string{"Personal", "Work", "Family"}}, {Name: "Favorites", Items: []string{"Holidays", "Birthdays"}}, {Name: "Other", Items: []string{"Travel", "Reminders", "Deadlines"}},} templ AppSidebar(props ...sidebar.Props) { {{ var p sidebar.Props }} if len(props) > 0 { {{ p = props[0] }} } @sidebar.Sidebar(p) { @sidebar.Header(sidebar.HeaderProps{Class: "h-16 border-b border-sidebar-border"}) { @NavUser(user) } @sidebar.Content() { @DatePicker() @sidebar.Separator(sidebar.SeparatorProps{Class: "mx-0"}) @Calendars(calendars) } @sidebar.Footer() { @sidebar.Menu() { @sidebar.MenuItem() { @sidebar.MenuButton() { @icon.Plus() <span>New Calendar</span> } } } } @sidebar.Rail() }}package sidebar12 import ( "strconv" "github.com/axadrn/shadcn-templ/v2/components/collapsible" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type Calendar struct { Name string Items []string} templ Calendars(calendars []Calendar) { for index, calendar := range calendars { @sidebar.Group() { @collapsible.Collapsible(collapsible.Props{ DefaultOpen: index == 0, Class: "group/collapsible", }) { @sidebar.GroupLabel(sidebar.GroupLabelProps{ Class: "group/label w-full text-sm text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", Attributes: collapsible.Trigger(ctx), }) { { calendar.Name } @icon.ChevronRight(icon.Props{Class: "ml-auto transition-transform group-data-open/collapsible:rotate-90"}) } @collapsible.Content() { @sidebar.GroupContent() { @sidebar.Menu() { for i, item := range calendar.Items { @sidebar.MenuItem() { @sidebar.MenuButton() { <div data-active={ strconv.FormatBool(i < 2) } class="group/calendar-item flex aspect-square size-4 shrink-0 items-center justify-center rounded-sm border border-sidebar-border text-sidebar-primary-foreground data-[active=true]:border-sidebar-primary data-[active=true]:bg-sidebar-primary" > @icon.Check(icon.Props{Class: "hidden size-3 group-data-[active=true]/calendar-item:block"}) </div> { item } } } } } } } } } @sidebar.Separator(sidebar.SeparatorProps{Class: "mx-0"}) }}package sidebar12 import ( "time" "github.com/axadrn/shadcn-templ/v2/components/calendar" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ DatePicker() { {{ now := time.Now() }} {{ selected := time.Date(now.Year(), now.Month(), 12, 0, 0, 0, 0, now.Location()) }} @sidebar.Group(sidebar.GroupProps{Class: "px-0"}) { @sidebar.GroupContent() { @calendar.Calendar(calendar.Props{ Mode: calendar.ModeSingle, Selected: selected, CaptionLayout: calendar.CaptionLayoutDropdown, Class: "bg-transparent [--cell-size:2.1rem]", }) } }}package sidebar12 import ( "github.com/axadrn/shadcn-templ/v2/components/avatar" "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type User struct { Name string Email string Avatar string} templ NavUser(user User) { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { @avatar.Avatar() { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback() { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> @icon.ChevronsUpDown(icon.Props{Class: "ml-auto size-4"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "min-w-56 rounded-lg", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignStart, MobileSide: dropdownmenu.SideBottom, SideOffset: 4, }) { @dropdownmenu.Group() { @dropdownmenu.Label(dropdownmenu.LabelProps{Class: "p-0 font-normal"}) { <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> @avatar.Avatar() { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback() { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> </div> } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.Sparkles() Upgrade to Pro } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.BadgeCheck() Account } @dropdownmenu.Item() { @icon.CreditCard() Billing } @dropdownmenu.Item() { @icon.Bell() Notifications } } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.LogOut() Log out } } } } }}package sidebar13 templ Page() { <div class="flex h-svh items-center justify-center"> @SettingsDialog() </div>}package sidebar13 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/button" "github.com/axadrn/shadcn-templ/v2/components/dialog" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type navItem struct { Name string Icon func(...icon.Props) templ.Component} var data = struct { Nav []navItem}{ Nav: []navItem{ {Name: "Notifications", Icon: icon.Bell}, {Name: "Navigation", Icon: icon.Menu}, {Name: "Home", Icon: icon.House}, {Name: "Appearance", Icon: icon.Paintbrush}, {Name: "Messages & media", Icon: icon.MessageCircle}, {Name: "Language & region", Icon: icon.Globe}, {Name: "Accessibility", Icon: icon.Keyboard}, {Name: "Mark as read", Icon: icon.Check}, {Name: "Audio & video", Icon: icon.Video}, {Name: "Connected accounts", Icon: icon.Link}, {Name: "Privacy & visibility", Icon: icon.Lock}, {Name: "Advanced", Icon: icon.Settings}, },} templ SettingsDialog() { // Start this demo with the dialog open. @dialog.Dialog(dialog.Props{DefaultOpen: true}) { @button.Button(button.Props{ Size: button.SizeSm, Attributes: dialog.Trigger(ctx), }) { Open Dialog } @dialog.Content(dialog.ContentProps{Class: "overflow-hidden p-0 md:max-h-[500px] md:max-w-[700px] lg:max-w-[800px]"}) { @dialog.Title(dialog.TitleProps{Class: "sr-only"}) { Settings } @dialog.Description(dialog.DescriptionProps{Class: "sr-only"}) { Customize your settings here. } @sidebar.Provider(sidebar.ProviderProps{Class: "items-start"}) { @sidebar.Sidebar(sidebar.Props{Collapsible: sidebar.CollapsibleNone, Class: "hidden md:flex"}) { @sidebar.Content() { @sidebar.Group() { @sidebar.GroupContent() { @sidebar.Menu() { for _, item := range data.Nav { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: "#", IsActive: item.Name == "Messages & media", }) { @item.Icon() <span>{ item.Name }</span> } } } } } } } } <main class="flex h-[480px] flex-1 flex-col overflow-hidden"> <header class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12"> <div class="flex items-center gap-2 px-4"> @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { Settings } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { Messages & media } } } } </div> </header> <div class="flex flex-1 flex-col gap-4 overflow-y-auto p-4 pt-0"> for range 10 { <div class="aspect-video max-w-3xl rounded-xl bg-muted/50"></div> } </div> </main> } } }}package sidebar14 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @sidebar.Inset() { <header class="flex h-16 shrink-0 items-center gap-2 border-b px-4"> @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item(breadcrumb.ItemProps{Class: "hidden md:block"}) { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { Build Your Application } } @breadcrumb.Separator(breadcrumb.SeparatorProps{Class: "hidden md:block"}) @breadcrumb.Item() { @breadcrumb.Page() { Data Fetching } } } } @sidebar.Trigger(sidebar.TriggerProps{Class: "-mr-1 ml-auto rotate-180"}) </header> <div class="flex flex-1 flex-col gap-4 p-4"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> </div> <div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min"></div> </div> } @AppSidebar(sidebar.Props{Side: sidebar.SideRight}) }}package sidebar14 import "github.com/axadrn/shadcn-templ/v2/components/sidebar" // This is sample data.type navItem struct { Title string URL string Items []navSubItem} type navSubItem struct { Title string URL string IsActive bool} var data = struct { NavMain []navItem}{ NavMain: []navItem{ { Title: "Getting Started", URL: "#", Items: []navSubItem{ {Title: "Installation", URL: "#"}, {Title: "Project Structure", URL: "#"}, }, }, { Title: "Build Your Application", URL: "#", Items: []navSubItem{ {Title: "Routing", URL: "#"}, {Title: "Data Fetching", URL: "#", IsActive: true}, {Title: "Rendering", URL: "#"}, {Title: "Caching", URL: "#"}, {Title: "Styling", URL: "#"}, {Title: "Optimizing", URL: "#"}, {Title: "Configuring", URL: "#"}, {Title: "Testing", URL: "#"}, {Title: "Authentication", URL: "#"}, {Title: "Deploying", URL: "#"}, {Title: "Upgrading", URL: "#"}, {Title: "Examples", URL: "#"}, }, }, { Title: "API Reference", URL: "#", Items: []navSubItem{ {Title: "Components", URL: "#"}, {Title: "File Conventions", URL: "#"}, {Title: "Functions", URL: "#"}, {Title: "next.config.js Options", URL: "#"}, {Title: "CLI", URL: "#"}, {Title: "Edge Runtime", URL: "#"}, }, }, { Title: "Architecture", URL: "#", Items: []navSubItem{ {Title: "Accessibility", URL: "#"}, {Title: "Fast Refresh", URL: "#"}, {Title: "templ Compiler", URL: "#"}, {Title: "Supported Browsers", URL: "#"}, {Title: "Turbopack", URL: "#"}, }, }, { Title: "Community", URL: "#", Items: []navSubItem{ {Title: "Contribution Guide", URL: "#"}, }, }, },} templ AppSidebar(props ...sidebar.Props) { {{ var p sidebar.Props }} if len(props) > 0 { {{ p = props[0] }} } @sidebar.Sidebar(p) { @sidebar.Content() { @sidebar.Group() { @sidebar.GroupLabel() { Table of Contents } @sidebar.GroupContent() { @sidebar.Menu() { for _, item := range data.NavMain { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Href: item.URL, Class: "font-medium"}) { { item.Title } } if len(item.Items) > 0 { @sidebar.MenuSub() { for _, subItem := range item.Items { @sidebar.MenuSubItem() { @sidebar.MenuSubButton(sidebar.MenuSubButtonProps{ Href: subItem.URL, IsActive: subItem.IsActive, }) { { subItem.Title } } } } } } } } } } } } @sidebar.Rail() }}package sidebar15 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ Page() { @sidebar.Provider() { @SidebarLeft() @sidebar.Inset() { <header class="sticky top-0 flex h-14 shrink-0 items-center gap-2 bg-background"> <div class="flex flex-1 items-center gap-2 px-3"> @sidebar.Trigger() @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb() { @breadcrumb.List() { @breadcrumb.Item() { @breadcrumb.Page(breadcrumb.ItemProps{Class: "line-clamp-1"}) { Project Management & Task Tracking } } } } </div> </header> <div class="flex flex-1 flex-col gap-4 p-4"> <div class="mx-auto h-24 w-full max-w-3xl rounded-xl bg-muted/50"></div> <div class="mx-auto h-[100vh] w-full max-w-3xl rounded-xl bg-muted/50"></div> </div> } @SidebarRight() } <script nonce={ templ.GetNonce(ctx) }> (() => { // Keep the selected team details in sync. document.addEventListener("click", (e) => { if (!(e.target instanceof Element)) return; const item = e.target.closest("#sidebar15-team-menu [data-team-index]"); if (!item) return; const i = item.getAttribute("data-team-index"); const name = item.textContent.trim().replace(/⌘\d$/, "").trim(); document.querySelectorAll("[data-team-logo]").forEach((logo) => { logo.classList.toggle("hidden", logo.getAttribute("data-team-logo") !== i); }); const nameEl = document.querySelector("[data-team-name]"); if (nameEl) nameEl.textContent = name; }); })(); </script>}package sidebar15 import ( "strconv" "github.com/axadrn/shadcn-templ/v2/components/collapsible" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type Calendar struct { Name string Items []string} templ Calendars(calendars []Calendar) { for index, calendar := range calendars { @sidebar.Group() { @collapsible.Collapsible(collapsible.Props{ DefaultOpen: index == 0, Class: "group/collapsible", }) { @sidebar.GroupLabel(sidebar.GroupLabelProps{ Class: "group/label w-full text-sm text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", Attributes: collapsible.Trigger(ctx), }) { { calendar.Name } @icon.ChevronRight(icon.Props{Class: "ml-auto transition-transform group-data-open/collapsible:rotate-90"}) } @collapsible.Content() { @sidebar.GroupContent() { @sidebar.Menu() { for i, item := range calendar.Items { @sidebar.MenuItem() { @sidebar.MenuButton() { <div data-active={ strconv.FormatBool(i < 2) } class="group/calendar-item flex aspect-square size-4 shrink-0 items-center justify-center rounded-xs border border-sidebar-border text-sidebar-primary-foreground data-[active=true]:border-sidebar-primary data-[active=true]:bg-sidebar-primary" > @icon.Check(icon.Props{Class: "hidden size-3 group-data-[active=true]/calendar-item:block"}) </div> { item } } } } } } } } } @sidebar.Separator(sidebar.SeparatorProps{Class: "mx-0"}) }}package sidebar15 import ( "time" "github.com/axadrn/shadcn-templ/v2/components/calendar" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ DatePicker() { {{ now := time.Now() }} {{ selected := time.Date(now.Year(), now.Month(), 12, 0, 0, 0, 0, now.Location()) }} @sidebar.Group(sidebar.GroupProps{Class: "px-0"}) { @sidebar.GroupContent() { @calendar.Calendar(calendar.Props{ Mode: calendar.ModeSingle, Selected: selected, CaptionLayout: calendar.CaptionLayoutDropdown, Class: "bg-transparent [--cell-size:2.1rem]", }) } }}package sidebar15 import ( "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type Favorite struct { Name string URL string Emoji string} templ NavFavorites(favorites []Favorite) { @sidebar.Group(sidebar.GroupProps{Class: "group-data-[collapsible=icon]:hidden"}) { @sidebar.GroupLabel() { Favorites } @sidebar.Menu() { for _, item := range favorites { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: item.URL, Attributes: templ.Attributes{"title": item.Name}, }) { <span>{ item.Emoji }</span> <span>{ item.Name }</span> } @dropdownmenu.DropdownMenu() { @sidebar.MenuAction(sidebar.MenuActionProps{ ShowOnHover: true, Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { @icon.Ellipsis() <span class="sr-only">More</span> } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "w-56 rounded-lg", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignStart, MobileSide: dropdownmenu.SideBottom, MobileAlign: dropdownmenu.AlignEnd, }) { @dropdownmenu.Item() { @icon.StarOff(icon.Props{Class: "text-muted-foreground"}) <span>Remove from Favorites</span> } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.Link(icon.Props{Class: "text-muted-foreground"}) <span>Copy Link</span> } @dropdownmenu.Item() { @icon.ArrowUpRight(icon.Props{Class: "text-muted-foreground"}) <span>Open in New Tab</span> } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.Trash2(icon.Props{Class: "text-muted-foreground"}) <span>Delete</span> } } } } } @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Class: "text-sidebar-foreground/70"}) { @icon.Ellipsis() <span>More</span> } } } }}package sidebar15 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type NavMainItem struct { Title string URL string Icon func(...icon.Props) templ.Component IsActive bool Badge string} templ NavMain(items []NavMainItem) { @sidebar.Menu() { for _, item := range items { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: item.URL, IsActive: item.IsActive, }) { @item.Icon() <span>{ item.Title }</span> } } } }}package sidebar15 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type NavSecondaryItem struct { Title string URL string Icon func(...icon.Props) templ.Component Badge string} templ NavSecondary(items []NavSecondaryItem, props ...sidebar.GroupProps) { {{ var p sidebar.GroupProps }} if len(props) > 0 { {{ p = props[0] }} } @sidebar.Group(p) { @sidebar.GroupContent() { @sidebar.Menu() { for _, item := range items { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Href: item.URL}) { @item.Icon() <span>{ item.Title }</span> } if item.Badge != "" { @sidebar.MenuBadge() { { item.Badge } } } } } } } }}package sidebar15 import ( "github.com/axadrn/shadcn-templ/v2/components/avatar" "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type User struct { Name string Email string Avatar string} templ NavUser(user User) { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { @avatar.Avatar() { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback() { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> @icon.ChevronsUpDown(icon.Props{Class: "ml-auto size-4"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "min-w-56 rounded-lg", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignStart, MobileSide: dropdownmenu.SideBottom, SideOffset: 4, }) { @dropdownmenu.Group() { @dropdownmenu.Label(dropdownmenu.LabelProps{Class: "p-0 font-normal"}) { <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> @avatar.Avatar() { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback() { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> </div> } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.Sparkles() Upgrade to Pro } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.BadgeCheck() Account } @dropdownmenu.Item() { @icon.CreditCard() Billing } @dropdownmenu.Item() { @icon.Bell() Notifications } } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.LogOut() Log out } } } } }}package sidebar15 import ( "github.com/axadrn/shadcn-templ/v2/components/collapsible" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type Workspace struct { Name string Emoji string Pages []WorkspacePage} type WorkspacePage struct { Name string URL string Emoji string} templ NavWorkspaces(workspaces []Workspace) { @sidebar.Group() { @sidebar.GroupLabel() { Workspaces } @sidebar.GroupContent() { @sidebar.Menu() { for _, workspace := range workspaces { @collapsible.Collapsible() { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Href: "#"}) { <span>{ workspace.Emoji }</span> <span>{ workspace.Name }</span> } @sidebar.MenuAction(sidebar.MenuActionProps{ Class: "left-2 bg-sidebar-accent text-sidebar-accent-foreground data-popup-open:rotate-90", ShowOnHover: true, Attributes: collapsible.Trigger(ctx), }) { @icon.ChevronRight() } @sidebar.MenuAction(sidebar.MenuActionProps{ShowOnHover: true}) { @icon.Plus() } @collapsible.Content() { @sidebar.MenuSub() { for _, page := range workspace.Pages { @sidebar.MenuSubItem() { @sidebar.MenuSubButton(sidebar.MenuSubButtonProps{Href: "#"}) { <span>{ page.Emoji }</span> <span>{ page.Name }</span> } } } } } } } } @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Class: "text-sidebar-foreground/70"}) { @icon.Ellipsis() <span>More</span> } } } } }}package sidebar15 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") // This is sample data.var sidebarLeftData = struct { Teams []Team NavMain []NavMainItem NavSecondary []NavSecondaryItem Favorites []Favorite Workspaces []Workspace}{ Teams: []Team{ {Name: "Acme Inc", Logo: icon.Terminal, Plan: "Enterprise"}, {Name: "Acme Corp.", Logo: icon.AudioLines, Plan: "Startup"}, {Name: "Evil Corp.", Logo: icon.Terminal, Plan: "Free"}, }, NavMain: []NavMainItem{ {Title: "Search", URL: "#", Icon: icon.Search}, {Title: "Ask AI", URL: "#", Icon: icon.Sparkles}, {Title: "Home", URL: "#", Icon: icon.House, IsActive: true}, {Title: "Inbox", URL: "#", Icon: icon.Inbox, Badge: "10"}, }, NavSecondary: []NavSecondaryItem{ {Title: "Calendar", URL: "#", Icon: icon.Calendar}, {Title: "Settings", URL: "#", Icon: icon.Settings2}, {Title: "Templates", URL: "#", Icon: icon.Blocks}, {Title: "Trash", URL: "#", Icon: icon.Trash2}, {Title: "Help", URL: "#", Icon: icon.MessageCircleQuestionMark}, }, Favorites: []Favorite{ {Name: "Project Management & Task Tracking", URL: "#", Emoji: "📊"}, {Name: "Family Recipe Collection & Meal Planning", URL: "#", Emoji: "🍳"}, {Name: "Fitness Tracker & Workout Routines", URL: "#", Emoji: "💪"}, {Name: "Book Notes & Reading List", URL: "#", Emoji: "📚"}, {Name: "Sustainable Gardening Tips & Plant Care", URL: "#", Emoji: "🌱"}, {Name: "Language Learning Progress & Resources", URL: "#", Emoji: "🗣️"}, {Name: "Home Renovation Ideas & Budget Tracker", URL: "#", Emoji: "🏠"}, {Name: "Personal Finance & Investment Portfolio", URL: "#", Emoji: "💰"}, {Name: "Movie & TV Show Watchlist with Reviews", URL: "#", Emoji: "🎬"}, {Name: "Daily Habit Tracker & Goal Setting", URL: "#", Emoji: "✅"}, }, Workspaces: []Workspace{ { Name: "Personal Life Management", Emoji: "🏠", Pages: []WorkspacePage{ {Name: "Daily Journal & Reflection", URL: "#", Emoji: "📔"}, {Name: "Health & Wellness Tracker", URL: "#", Emoji: "🍏"}, {Name: "Personal Growth & Learning Goals", URL: "#", Emoji: "🌟"}, }, }, { Name: "Professional Development", Emoji: "💼", Pages: []WorkspacePage{ {Name: "Career Objectives & Milestones", URL: "#", Emoji: "🎯"}, {Name: "Skill Acquisition & Training Log", URL: "#", Emoji: "🧠"}, {Name: "Networking Contacts & Events", URL: "#", Emoji: "🤝"}, }, }, { Name: "Creative Projects", Emoji: "🎨", Pages: []WorkspacePage{ {Name: "Writing Ideas & Story Outlines", URL: "#", Emoji: "✍️"}, {Name: "Art & Design Portfolio", URL: "#", Emoji: "🖼️"}, {Name: "Music Composition & Practice Log", URL: "#", Emoji: "🎵"}, }, }, { Name: "Home Management", Emoji: "🏡", Pages: []WorkspacePage{ {Name: "Household Budget & Expense Tracking", URL: "#", Emoji: "💰"}, {Name: "Home Maintenance Schedule & Tasks", URL: "#", Emoji: "🔧"}, {Name: "Family Calendar & Event Planning", URL: "#", Emoji: "📅"}, }, }, { Name: "Travel & Adventure", Emoji: "🧳", Pages: []WorkspacePage{ {Name: "Trip Planning & Itineraries", URL: "#", Emoji: "🗺️"}, {Name: "Travel Bucket List & Inspiration", URL: "#", Emoji: "🌎"}, {Name: "Travel Journal & Photo Gallery", URL: "#", Emoji: "📸"}, }, }, },} templ SidebarLeft(props ...sidebar.Props) { {{ var p sidebar.Props }} if len(props) > 0 { {{ p = props[0] }} } if p.Class == "" { {{ p.Class = "border-r-0" }} } @sidebar.Sidebar(p) { @sidebar.Header() { @TeamSwitcher(sidebarLeftData.Teams) @NavMain(sidebarLeftData.NavMain) } @sidebar.Content() { @NavFavorites(sidebarLeftData.Favorites) @NavWorkspaces(sidebarLeftData.Workspaces) @NavSecondary(sidebarLeftData.NavSecondary, sidebar.GroupProps{Class: "mt-auto"}) } @sidebar.Rail() }}package sidebar15 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") // This is sample data.var sidebarRightData = struct { User User Calendars []Calendar}{ User: User{ Name: "Axel Adrian", Email: "[email protected]", Avatar: "https://github.com/axadrn.png", }, Calendars: []Calendar{ {Name: "My Calendars", Items: []string{"Personal", "Work", "Family"}}, {Name: "Favorites", Items: []string{"Holidays", "Birthdays"}}, {Name: "Other", Items: []string{"Travel", "Reminders", "Deadlines"}}, },} templ SidebarRight(props ...sidebar.Props) { {{ var p sidebar.Props }} if len(props) > 0 { {{ p = props[0] }} } // The explicit ID keeps this sidebar's element id distinct from the // left sidebar, which takes the provider's id for its trigger. if p.ID == "" { {{ p.ID = "sidebar15-right" }} } if p.Collapsible == "" { {{ p.Collapsible = sidebar.CollapsibleNone }} } if p.Class == "" { {{ p.Class = "sticky top-0 hidden h-svh border-l lg:flex" }} } @sidebar.Sidebar(p) { @sidebar.Header(sidebar.HeaderProps{Class: "h-16 border-b border-sidebar-border"}) { @NavUser(sidebarRightData.User) } @sidebar.Content() { @DatePicker() @sidebar.Separator(sidebar.SeparatorProps{Class: "mx-0"}) @Calendars(sidebarRightData.Calendars) } @sidebar.Footer() { @sidebar.Menu() { @sidebar.MenuItem() { @sidebar.MenuButton() { @icon.Plus() <span>New Calendar</span> } } } } }}package sidebar15 import ( "strconv" "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type Team struct { Name string Logo func(...icon.Props) templ.Component Plan string} func teamLogoHidden(i int) string { if i == 0 { return "" } return " hidden"} templ TeamSwitcher(teams []Team) { if len(teams) > 0 { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu(dropdownmenu.Props{ID: "sidebar15-team-menu"}) { @sidebar.MenuButton(sidebar.MenuButtonProps{ Class: "w-fit px-1.5 aria-expanded:bg-muted aria-expanded:text-foreground", Attributes: dropdownmenu.Trigger(ctx), }) { <div class="flex aspect-square size-5 items-center justify-center rounded-md bg-sidebar-primary text-sidebar-primary-foreground"> // Render every logo up front; the page script shows the active one. for i, team := range teams { @team.Logo(icon.Props{ Class: teamLogoHidden(i), Attributes: templ.Attributes{"data-team-logo": strconv.Itoa(i)}, }) } </div> <span class="truncate font-medium" data-team-name>{ teams[0].Name }</span> @icon.ChevronDown(icon.Props{Class: "opacity-50"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "w-64 rounded-lg", Align: dropdownmenu.AlignStart, Side: dropdownmenu.SideBottom, SideOffset: 4, }) { @dropdownmenu.Label(dropdownmenu.LabelProps{Class: "text-xs text-muted-foreground"}) { Teams } for i, team := range teams { @dropdownmenu.Item(dropdownmenu.ItemProps{ Class: "gap-2 p-2", Attributes: templ.Attributes{"data-team-index": strconv.Itoa(i)}, }) { <div class="flex size-6 items-center justify-center rounded-xs border"> @team.Logo() </div> { team.Name } @dropdownmenu.Shortcut() { ⌘{ strconv.Itoa(i + 1) } } } } @dropdownmenu.Separator() @dropdownmenu.Item(dropdownmenu.ItemProps{Class: "gap-2 p-2"}) { <div class="flex size-6 items-center justify-center rounded-md border bg-background"> @icon.Plus(icon.Props{Class: "size-4"}) </div> <div class="font-medium text-muted-foreground">Add team</div> } } } } } }}package sidebar16 import "github.com/axadrn/shadcn-templ/v2/components/sidebar" templ Page() { <div class="[--header-height:calc(--spacing(14))]"> @sidebar.Provider(sidebar.ProviderProps{Class: "flex flex-col"}) { @SiteHeader() <div class="flex flex-1"> @AppSidebar() @sidebar.Inset() { <div class="flex flex-1 flex-col gap-4 p-4"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> <div class="aspect-video rounded-xl bg-muted/50"></div> </div> <div class="min-h-screen flex-1 rounded-xl bg-muted/50 md:min-h-min"></div> </div> } </div> } </div>}package sidebar16 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") var data = struct { User User NavMain []NavMainItem NavSecondary []NavSecondaryItem Projects []Project}{ User: User{ Name: "Axel Adrian", Email: "[email protected]", Avatar: "https://github.com/axadrn.png", }, NavMain: []NavMainItem{ { Title: "Playground", URL: "#", Icon: icon.SquareTerminal, IsActive: true, Items: []NavMainSubItem{ {Title: "History", URL: "#"}, {Title: "Starred", URL: "#"}, {Title: "Settings", URL: "#"}, }, }, { Title: "Models", URL: "#", Icon: icon.Bot, Items: []NavMainSubItem{ {Title: "Genesis", URL: "#"}, {Title: "Explorer", URL: "#"}, {Title: "Quantum", URL: "#"}, }, }, { Title: "Documentation", URL: "#", Icon: icon.BookOpen, Items: []NavMainSubItem{ {Title: "Introduction", URL: "#"}, {Title: "Get Started", URL: "#"}, {Title: "Tutorials", URL: "#"}, {Title: "Changelog", URL: "#"}, }, }, { Title: "Settings", URL: "#", Icon: icon.Settings2, Items: []NavMainSubItem{ {Title: "General", URL: "#"}, {Title: "Team", URL: "#"}, {Title: "Billing", URL: "#"}, {Title: "Limits", URL: "#"}, }, }, }, NavSecondary: []NavSecondaryItem{ {Title: "Support", URL: "#", Icon: icon.LifeBuoy}, {Title: "Feedback", URL: "#", Icon: icon.Send}, }, Projects: []Project{ {Name: "Design Engineering", URL: "#", Icon: icon.Frame}, {Name: "Sales & Marketing", URL: "#", Icon: icon.ChartPie}, {Name: "Travel", URL: "#", Icon: icon.Map}, },} templ AppSidebar(props ...sidebar.Props) { {{ var p sidebar.Props }} if len(props) > 0 { {{ p = props[0] }} } if p.Class == "" { {{ p.Class = "top-(--header-height) h-[calc(100svh-var(--header-height))]!" }} } @sidebar.Sidebar(p) { @sidebar.Header() { @sidebar.Menu() { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Href: "#", }) { <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> @icon.Terminal(icon.Props{Class: "size-4"}) </div> <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">Acme Inc</span> <span class="truncate text-xs">Enterprise</span> </div> } } } } @sidebar.Content() { @NavMain(data.NavMain) @NavProjects(data.Projects) @NavSecondary(data.NavSecondary, sidebar.GroupProps{Class: "mt-auto"}) } @sidebar.Footer() { @NavUser(data.User) } }}package sidebar16 import ( "github.com/axadrn/shadcn-templ/v2/components/collapsible" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type NavMainItem struct { Title string URL string Icon func(...icon.Props) templ.Component IsActive bool Items []NavMainSubItem} type NavMainSubItem struct { Title string URL string} templ NavMain(items []NavMainItem) { @sidebar.Group() { @sidebar.GroupLabel() { Platform } @sidebar.Menu() { for _, item := range items { @collapsible.Collapsible(collapsible.Props{DefaultOpen: item.IsActive}) { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: item.URL, Tooltip: item.Title, }) { @item.Icon() <span>{ item.Title }</span> } if len(item.Items) > 0 { @sidebar.MenuAction(sidebar.MenuActionProps{ Class: "aria-expanded:rotate-90", Attributes: collapsible.Trigger(ctx), }) { @icon.ChevronRight() <span class="sr-only">Toggle</span> } @collapsible.Content() { @sidebar.MenuSub() { for _, subItem := range item.Items { @sidebar.MenuSubItem() { @sidebar.MenuSubButton(sidebar.MenuSubButtonProps{Href: subItem.URL}) { <span>{ subItem.Title }</span> } } } } } } } } } } }}package sidebar16 import ( "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type Project struct { Name string URL string Icon func(...icon.Props) templ.Component} templ NavProjects(projects []Project) { @sidebar.Group(sidebar.GroupProps{Class: "group-data-[collapsible=icon]:hidden"}) { @sidebar.GroupLabel() { Projects } @sidebar.Menu() { for _, item := range projects { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{Href: item.URL}) { @item.Icon() <span>{ item.Name }</span> } @dropdownmenu.DropdownMenu() { @sidebar.MenuAction(sidebar.MenuActionProps{ ShowOnHover: true, Class: "aria-expanded:bg-muted", Attributes: dropdownmenu.Trigger(ctx), }) { @icon.Ellipsis() <span class="sr-only">More</span> } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "w-48", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignStart, MobileSide: dropdownmenu.SideBottom, MobileAlign: dropdownmenu.AlignEnd, }) { @dropdownmenu.Item() { @icon.Folder(icon.Props{Class: "text-muted-foreground"}) <span>View Project</span> } @dropdownmenu.Item() { @icon.Share(icon.Props{Class: "text-muted-foreground"}) <span>Share Project</span> } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.Trash2(icon.Props{Class: "text-muted-foreground"}) <span>Delete Project</span> } } } } } @sidebar.MenuItem() { @sidebar.MenuButton() { @icon.Ellipsis() <span>More</span> } } } }}package sidebar16 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type NavSecondaryItem struct { Title string URL string Icon func(...icon.Props) templ.Component} templ NavSecondary(items []NavSecondaryItem, props ...sidebar.GroupProps) { {{ var p sidebar.GroupProps }} if len(props) > 0 { {{ p = props[0] }} } @sidebar.Group(p) { @sidebar.GroupContent() { @sidebar.Menu() { for _, item := range items { @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: item.URL, Size: sidebar.MenuButtonSizeSm, }) { @item.Icon() <span>{ item.Title }</span> } } } } } }}package sidebar16 import ( "github.com/axadrn/shadcn-templ/v2/components/avatar" "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type User struct { Name string Email string Avatar string} templ NavUser(user User) { @sidebar.Menu() { @sidebar.MenuItem() { @dropdownmenu.DropdownMenu() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Size: sidebar.MenuButtonSizeLg, Class: "aria-expanded:bg-muted aria-expanded:text-foreground", Attributes: dropdownmenu.Trigger(ctx), }) { @avatar.Avatar() { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback() { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> @icon.ChevronsUpDown(icon.Props{Class: "ml-auto size-4"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{ Class: "min-w-56 rounded-lg", Side: dropdownmenu.SideRight, Align: dropdownmenu.AlignEnd, MobileSide: dropdownmenu.SideBottom, SideOffset: 4, }) { @dropdownmenu.Group() { @dropdownmenu.Label(dropdownmenu.LabelProps{Class: "p-0 font-normal"}) { <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> @avatar.Avatar() { @avatar.Image(avatar.ImageProps{Src: user.Avatar, Alt: user.Name}) @avatar.Fallback() { AA } } <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{ user.Name }</span> <span class="truncate text-xs">{ user.Email }</span> </div> </div> } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.Sparkles() Upgrade to Pro } } @dropdownmenu.Separator() @dropdownmenu.Group() { @dropdownmenu.Item() { @icon.BadgeCheck() Account } @dropdownmenu.Item() { @icon.CreditCard() Billing } @dropdownmenu.Item() { @icon.Bell() Notifications } } @dropdownmenu.Separator() @dropdownmenu.Item() { @icon.LogOut() Log out } } } } }}package sidebar16 import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/label" "github.com/axadrn/shadcn-templ/v2/components/sidebar") type SearchFormProps struct { Class string Attributes templ.Attributes} templ SearchForm(props ...SearchFormProps) { {{ var p SearchFormProps }} if len(props) > 0 { {{ p = props[0] }} } <form if p.Class != "" { class={ p.Class } } { p.Attributes... } > <div class="relative"> @label.Label(label.Props{For: "search", Class: "sr-only"}) { Search } @sidebar.Input(sidebar.InputProps{ ID: "search", Placeholder: "Type to search...", Class: "h-8 pl-7", }) @icon.Search(icon.Props{Class: "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none"}) </div> </form>}package sidebar16 import ( "github.com/axadrn/shadcn-templ/v2/components/breadcrumb" "github.com/axadrn/shadcn-templ/v2/components/separator" "github.com/axadrn/shadcn-templ/v2/components/sidebar") templ SiteHeader() { <header class="sticky top-0 z-50 flex w-full items-center border-b bg-background"> <div class="flex h-(--header-height) w-full items-center gap-2 px-4"> // The onClick={toggleSidebar} ghost button with SidebarIcon, the // sidebar trigger renders the same ghost icon button. @sidebar.Trigger(sidebar.TriggerProps{Class: "h-8 w-8"}) @separator.Separator(separator.Props{ Orientation: separator.OrientationVertical, Class: "mr-2 data-vertical:h-4 data-vertical:self-auto", }) @breadcrumb.Breadcrumb(breadcrumb.Props{Class: "hidden sm:block"}) { @breadcrumb.List() { @breadcrumb.Item() { @breadcrumb.Link(breadcrumb.LinkProps{Href: "#"}) { Build Your Application } } @breadcrumb.Separator() @breadcrumb.Item() { @breadcrumb.Page() { Data Fetching } } } } @SearchForm(SearchFormProps{Class: "w-full sm:ml-auto sm:w-auto"}) </div> </header>}