Files
headscale/hscontrol/templates/general.go
Kristoffer Dalby 3918020551 templates: use CSS variables in all shared components
Replace hardcoded Go color constants with var(--hs-*) and
var(--md-*) CSS custom properties in externalLink, orDivider,
card, warningBox, downloadButton, and pageFooter. This ensures
all components follow the dark mode theme automatically.

Also switch pageFooter from div to semantic footer element and
simplify externalLink by letting CSS handle link styling.

Updates juanfont/headscale#3182
2026-04-13 17:23:47 +01:00

211 lines
6.4 KiB
Go

package templates
import (
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
"github.com/chasefleming/elem-go/styles"
"github.com/juanfont/headscale/hscontrol/assets"
)
// mdTypesetBody creates a body element with md-typeset styling
// that matches the official Headscale documentation design.
// Uses CSS classes with styles defined in assets.CSS.
func mdTypesetBody(children ...elem.Node) *elem.Element {
return elem.Body(attrs.Props{
attrs.Style: styles.Props{
styles.MinHeight: "100vh",
styles.Display: "flex",
styles.FlexDirection: "column",
styles.AlignItems: "center",
styles.BackgroundColor: "var(--hs-bg)",
styles.Padding: "3rem 1.5rem",
}.ToInline(),
"translate": "no",
},
elem.Main(attrs.Props{
attrs.Class: "md-typeset",
attrs.Style: styles.Props{
styles.MaxWidth: "min(800px, 90vw)",
styles.Width: "100%",
}.ToInline(),
}, children...),
)
}
// Styled Element Wrappers
// These functions wrap elem-go elements using CSS classes.
// Styling is handled by the CSS in assets.CSS.
// H1 creates a H1 element styled by .md-typeset h1
func H1(children ...elem.Node) *elem.Element {
return elem.H1(nil, children...)
}
// H2 creates a H2 element styled by .md-typeset h2
func H2(children ...elem.Node) *elem.Element {
return elem.H2(nil, children...)
}
// H3 creates a H3 element styled by .md-typeset h3
func H3(children ...elem.Node) *elem.Element {
return elem.H3(nil, children...)
}
// P creates a paragraph element styled by .md-typeset p
func P(children ...elem.Node) *elem.Element {
return elem.P(nil, children...)
}
// Ol creates an ordered list element styled by .md-typeset ol
func Ol(children ...elem.Node) *elem.Element {
return elem.Ol(nil, children...)
}
// Ul creates an unordered list element styled by .md-typeset ul
func Ul(children ...elem.Node) *elem.Element {
return elem.Ul(nil, children...)
}
// A creates a link element styled by .md-typeset a
func A(href string, children ...elem.Node) *elem.Element {
return elem.A(attrs.Props{attrs.Href: href}, children...)
}
// Code creates an inline code element styled by .md-typeset code
func Code(children ...elem.Node) *elem.Element {
return elem.Code(nil, children...)
}
// Pre creates a preformatted text block styled by .md-typeset pre
func Pre(children ...elem.Node) *elem.Element {
return elem.Pre(nil, children...)
}
// PreCode creates a code block inside Pre styled by .md-typeset pre > code
func PreCode(code string) *elem.Element {
return elem.Code(nil, elem.Text(code))
}
// Deprecated: use H1, H2, H3 instead
func headerOne(text string) *elem.Element {
return H1(elem.Text(text))
}
// Deprecated: use H1, H2, H3 instead
func headerTwo(text string) *elem.Element {
return H2(elem.Text(text))
}
// Deprecated: use H1, H2, H3 instead
func headerThree(text string) *elem.Element {
return H3(elem.Text(text))
}
// contentContainer wraps page content with proper width.
// Content inside is left-aligned by default.
func contentContainer(children ...elem.Node) *elem.Element {
containerStyle := styles.Props{
styles.MaxWidth: "720px",
styles.Width: "100%",
styles.Display: "flex",
styles.FlexDirection: "column",
styles.AlignItems: "flex-start", // Left-align all children
}
return elem.Div(attrs.Props{attrs.Style: containerStyle.ToInline()}, children...)
}
// headscaleLogo returns the Headscale SVG logo for consistent branding across all pages.
// The logo is styled by the .headscale-logo CSS class.
func headscaleLogo() elem.Node {
// Return the embedded SVG as-is
return elem.Raw(assets.SVG)
}
// pageFooter creates a consistent footer for all pages.
func pageFooter() *elem.Element {
return elem.Footer(attrs.Props{
attrs.Style: styles.Props{
styles.MarginTop: space3XL,
styles.TextAlign: "center",
styles.FontSize: fontSizeSmall,
styles.Color: "var(--md-default-fg-color--light)",
styles.LineHeight: lineHeightBase,
}.ToInline(),
},
elem.Text("Powered by "),
elem.A(attrs.Props{
attrs.Href: "https://github.com/juanfont/headscale",
attrs.Rel: "noreferrer noopener",
attrs.Target: "_blank",
}, elem.Text("Headscale")),
)
}
// listStyle provides consistent styling for ordered and unordered lists
// EXTRACTED FROM: .md-typeset ol, .md-typeset ul CSS rules
var listStyle = styles.Props{
styles.LineHeight: lineHeightBase, // 1.6 - From .md-typeset
styles.MarginTop: "1em", // From CSS: margin-top: 1em
styles.MarginBottom: "1em", // From CSS: margin-bottom: 1em
styles.PaddingLeft: "clamp(1.5rem, 5vw, 2.5rem)", // Responsive indentation
}
// HtmlStructure creates a complete HTML document structure with proper meta tags
// and semantic HTML5 structure. The head and body elements are passed as parameters
// to allow for customization of each page.
// Styling is provided via a CSS stylesheet (Material for MkDocs design system) with
// minimal inline styles for layout and positioning.
func HtmlStructure(head, body *elem.Element) *elem.Element {
return elem.Html(attrs.Props{attrs.Lang: "en"},
elem.Head(nil,
elem.Meta(attrs.Props{
attrs.Charset: "UTF-8",
}),
elem.Meta(attrs.Props{
attrs.HTTPequiv: "X-UA-Compatible",
attrs.Content: "IE=edge",
}),
elem.Meta(attrs.Props{
attrs.Name: "viewport",
attrs.Content: "width=device-width, initial-scale=1.0",
}),
elem.Link(attrs.Props{
attrs.Rel: "icon",
attrs.Href: "/favicon.ico",
}),
// Google Fonts for Roboto and Roboto Mono
elem.Link(attrs.Props{
attrs.Rel: "preconnect",
attrs.Href: "https://fonts.gstatic.com",
"crossorigin": "",
}),
elem.Link(attrs.Props{
attrs.Rel: "stylesheet",
attrs.Href: "https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Roboto+Mono:wght@400;700&display=swap",
}),
// Material for MkDocs CSS styles
elem.Style(attrs.Props{attrs.Type: "text/css"}, elem.Raw(assets.CSS)),
head,
),
body,
)
}
// BlankPage creates a minimal blank HTML page with favicon.
// Used for endpoints that need to return a valid HTML page with no content.
func BlankPage() *elem.Element {
return elem.Html(attrs.Props{attrs.Lang: "en"},
elem.Head(nil,
elem.Meta(attrs.Props{
attrs.Charset: "UTF-8",
}),
elem.Link(attrs.Props{
attrs.Rel: "icon",
attrs.Href: "/favicon.ico",
}),
),
elem.Body(nil),
)
}