diff --git a/README.md b/README.md index aaf1625..4ec2e66 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ - Supports common HTML elements and attributes. - Utilities for simplified element generation and manipulation. - Advanced CSS styling capabilities with the [styles](styles/README.md) subpackage. +- Use the [`StyleManager`](styles/STYLEMANAGER.md) for advanced CSS features like pseudo-classes, animations, and media queries. ## Installation @@ -204,6 +205,12 @@ comment := elem.Comment("Section: Main Content Start") // Generates: ``` +## Advanced CSS Styling with `StyleManager` + +For projects requiring advanced CSS styling capabilities, including support for animations, pseudo-classes, and responsive design via media queries, the `stylemanager` subpackage offers a powerful solution. Integrated seamlessly with `elem-go`, it allows developers to programmatically create and manage complex CSS styles within the type-safe environment of Go. + +Explore the [`stylemanager` subpackage](stylemanager/README.md) to leverage advanced styling features in your web applications. + ## HTMX Integration We provide a subpackage for htmx integration. [Read more about htmx integration here](htmx/README.md). diff --git a/elem.go b/elem.go index 5ea6eb0..7f57d07 100644 --- a/elem.go +++ b/elem.go @@ -1,6 +1,7 @@ package elem import ( + "fmt" "sort" "strings" @@ -53,9 +54,14 @@ var booleanAttrs = map[string]struct{}{ attrs.Selected: {}, } +type CSSGenerator interface { + GenerateCSS() string // TODO: Change to CSS() +} + type RenderOptions struct { // DisableHtmlPreamble disables the doctype preamble for the HTML tag if it exists in the rendering tree DisableHtmlPreamble bool + StyleManager CSSGenerator } type Node interface { @@ -203,6 +209,38 @@ func (e *Element) Render() string { func (e *Element) RenderWithOptions(opts RenderOptions) string { var builder strings.Builder e.RenderTo(&builder, opts) + + if opts.StyleManager != nil { + htmlContent := builder.String() + cssContent := opts.StyleManager.GenerateCSS() + + // Define the ", cssContent) + + // Check if a tag exists in the HTML content + headStartIndex := strings.Index(htmlContent, "") + headEndIndex := strings.Index(htmlContent, "") + + if headStartIndex != -1 && headEndIndex != -1 { + // If exists, inject the style content just before + beforeHead := htmlContent[:headEndIndex] + afterHead := htmlContent[headEndIndex:] + modifiedHTML := beforeHead + styleElement + afterHead + return modifiedHTML + } else { + // If does not exist, create it and inject the style content + // Assuming tag exists and injecting immediately after + htmlTagEnd := strings.Index(htmlContent, ">") + 1 + if htmlTagEnd > 0 { + beforeHTML := htmlContent[:htmlTagEnd] + afterHTML := htmlContent[htmlTagEnd:] + modifiedHTML := beforeHTML + "" + styleElement + "" + afterHTML + return modifiedHTML + } + } + } + + // Return the original HTML content if no modifications were made return builder.String() } diff --git a/elem_test.go b/elem_test.go new file mode 100644 index 0000000..e5f9651 --- /dev/null +++ b/elem_test.go @@ -0,0 +1,40 @@ +package elem + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +// MockStyleManager simulates the StyleManager for testing purposes. +type MockStyleManager struct{} + +// GenerateCSS returns a fixed CSS string for testing. +func (m *MockStyleManager) GenerateCSS() string { + return "body { background-color: #fff; }" +} + +func TestRenderWithOptionsInjectsCSSIntoHead(t *testing.T) { + // Setup a simple element that represents an HTML document structure + e := &Element{ + Tag: "html", + Children: []Node{ + &Element{Tag: "head"}, + &Element{Tag: "body"}, + }, + } + + // Use the MockStyleManager + mockStyleManager := &MockStyleManager{} + + // Assuming RenderOptions expects a StyleManager interface, pass the mock + opts := RenderOptions{ + StyleManager: mockStyleManager, // This should be adjusted to how your options are structured + } + htmlOutput := e.RenderWithOptions(opts) + + // Construct the expected HTML string with the CSS injected + expectedHTML := "" + + // Use testify's assert.Equal to check if the HTML output matches the expected HTML + assert.Equal(t, expectedHTML, htmlOutput, "The generated HTML should include the CSS in the section") +} diff --git a/examples/stylemanager-demo/README.md b/examples/stylemanager-demo/README.md new file mode 100644 index 0000000..4a88be9 --- /dev/null +++ b/examples/stylemanager-demo/README.md @@ -0,0 +1,43 @@ +# Advanced Styling App with elem-go and StyleManager + +This web application demonstrates the power of advanced CSS styling within a Go server environment, using the `elem-go` library and its `StyleManager` for dynamic styling. It features a button with hover effects, an animated element, and a responsive design element that adjusts styles based on the viewport width. + +## Prerequisites + +Ensure that Go is installed on your system. + +## Installation + +Clone or download the repository, then run the following commands to download the necessary packages: + +```bash +go mod tidy +``` + +This will install `elem-go` and the `styles` subpackage required to run the application. + +## Running the Application + +To run the application, execute the following command: + +```bash +go run main.go +``` + +The server will start on `localhost` at port `8080`. You can view the application by navigating to `http://localhost:8080` in your web browser. + +## Features + +**Button Hover Effect**: The button changes color and scale when hovered over, providing visual feedback to the user. +**Animated Element**: The animated element moves across the screen in a loop, demonstrating the use of animations with `StyleManager`. +**Responsive Design**: The application adjusts the background color based on the viewport width, showcasing the use of media queries for responsive styling. + +## Advanced CSS Styling with `StyleManager` + +The `StyleManager` within the `styles` package provides a powerful solution for managing CSS styles in Go-based web applications. It enables the creation of dynamic and responsive styles, including pseudo-classes, animations, and media queries, directly in Go. + +To learn more about `StyleManager` and its advanced features, refer to the [StyleManager documentation](../../styles/STYLEMANAGER.md). + +## Stopping the Server + +To stop the application, press `Ctrl + C` in the terminal where the server is running. \ No newline at end of file diff --git a/examples/stylemanager-demo/go.mod b/examples/stylemanager-demo/go.mod new file mode 100644 index 0000000..072315c --- /dev/null +++ b/examples/stylemanager-demo/go.mod @@ -0,0 +1,7 @@ +module stylemanager_demo + +go 1.21.1 + +require github.com/chasefleming/elem-go v0.0.0 + +replace github.com/chasefleming/elem-go => ../../../elem-go diff --git a/examples/stylemanager-demo/go.sum b/examples/stylemanager-demo/go.sum new file mode 100644 index 0000000..8952aa2 --- /dev/null +++ b/examples/stylemanager-demo/go.sum @@ -0,0 +1,8 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/stylemanager-demo/main.go b/examples/stylemanager-demo/main.go new file mode 100644 index 0000000..b49d7b0 --- /dev/null +++ b/examples/stylemanager-demo/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "github.com/chasefleming/elem-go" + "github.com/chasefleming/elem-go/attrs" + "github.com/chasefleming/elem-go/styles" + "net/http" +) + +func generateWebContent() string { + // Initialize StyleManager + styleMgr := styles.NewStyleManager() + + // Button with hover effect + buttonClass := styleMgr.AddCompositeStyle(styles.CompositeStyle{ + Default: styles.Props{ + styles.Padding: "10px 20px", + styles.BackgroundColor: "blue", + styles.Color: "white", + styles.Border: "none", + styles.Cursor: "pointer", + styles.Margin: "10px", + }, + PseudoClasses: map[string]styles.Props{ + "hover": { + styles.BackgroundColor: "darkblue", + }, + }, + }) + + // Animated element + animationName := styleMgr.AddAnimation(styles.Keyframes{ + "0%": {styles.Transform: "translateY(0px)"}, + "50%": {styles.Transform: "translateY(-20px)"}, + "100%": {styles.Transform: "translateY(0px)"}, + }) + animatedClass := styleMgr.AddStyle(styles.Props{ + styles.Width: "100px", + styles.Height: "100px", + styles.BackgroundColor: "green", + styles.AnimationName: animationName, + styles.AnimationDuration: "2s", + styles.AnimationIterationCount: "infinite", + }) + + // Responsive design + responsiveClass := styleMgr.AddCompositeStyle(styles.CompositeStyle{ + Default: styles.Props{ + styles.Padding: "20px", + styles.BackgroundColor: "lightgray", + styles.Margin: "10px", + }, + MediaQueries: map[string]styles.Props{ + "@media (max-width: 600px)": { + styles.Padding: "10px", + styles.BackgroundColor: "lightblue", + }, + }, + }) + + // Composing the page + pageContent := elem.Div(nil, + elem.Button(attrs.Props{attrs.Class: buttonClass}, elem.Text("Hover Over Me")), + elem.Div(attrs.Props{attrs.Class: animatedClass}, elem.Text("I animate!")), + elem.Div(attrs.Props{attrs.Class: responsiveClass}, elem.Text("Resize the window")), + ) + + // Render with StyleManager + return pageContent.RenderWithOptions(elem.RenderOptions{StyleManager: styleMgr}) +} + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + htmlContent := generateWebContent() // Assume this returns the HTML string + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(htmlContent)) + }) + + http.ListenAndServe(":8080", nil) +} diff --git a/styles/README.md b/styles/README.md index 7c3d819..610dfec 100644 --- a/styles/README.md +++ b/styles/README.md @@ -11,6 +11,10 @@ The `styles` subpackage within `elem-go` offers enhanced functionality for CSS s - [`Style` and `CSS` Functions](#style-and-css-functions) - [`Merge` Function](#merge-function) - [Type-Safe CSS Values](#type-safe-css-values) +- [Advanced Styling with `StyleManager`](#advanced-styling-with-stylemanager) + - [Key Features of `StyleManager`](#key-features-of-stylemanager) + - [Example: Implementing a Hover Effect](#example-implementing-a-hover-effect) + - [Detailed Usage](stylemanager/README.md) ## Introduction @@ -216,4 +220,54 @@ This function returns a string representation as a CSS variable. ```go varValue := styles.Var("primary-color") // Returns "var(--primary-color)" -``` \ No newline at end of file +``` + +## Advanced Styling with `StyleManager` + +`StyleManager`, a component of the `styles` package, extends the capability of Go-based web application development by introducing a structured and type-safe approach to managing CSS styles. This integration supports dynamic styling features like pseudo-classes, animations, and responsive design through a Go-centric API, providing a novel way to apply CSS with the added benefits of Go's type system. + +### Key Features of `StyleManager` + +- **Pseudo-Classes & Animations**: Enables the application of CSS pseudo-classes and keyframe animations to elements for interactive and dynamic styling, leveraging Go's type safety for style definitions. +- **Media Queries**: Supports defining responsive styles that adapt to various screen sizes and orientations, crafted within Go's type-safe environment to enhance web application usability across devices. +- **Automatic Class Name Generation & Style Deduplication**: Improves stylesheet efficiency by automatically generating unique class names for styles and deduplicating CSS rules, all within a type-safe framework. + +### Example: Implementing a Hover Effect + +The following example showcases how to leverage `StyleManager` to add a hover effect to a button, illustrating the application of dynamic styles within a type-safe context: + +```go +// Initialize StyleManager +styleMgr := styles.NewStyleManager() + +// Define styles with a hover effect, utilizing Go's type safety +buttonClass := styleMgr.AddCompositeStyle(styles.CompositeStyle{ + Default: styles.Props{ + styles.BackgroundColor: "green", + styles.Color: "white", + styles.Padding: "10px 20px", + styles.Border: "none", + styles.Cursor: "pointer", + }, + PseudoClasses: map[string]styles.Props{ + styles.PseudoHover: { + styles.BackgroundColor: "darkgreen", + }, + }, +}) + +// Create a button and apply the generated class name +button := elem.Button( + attrs.Props{attrs.Class: buttonClass}, + elem.Text("Hover Over Me"), +) + +// Use RenderWithOptions to apply the style definitions effectively +htmlOutput := button.RenderWithOptions(elem.RenderOptions{StyleManager: styleMgr}) +``` + +This example demonstrates the use of `StyleManager` for defining and applying a set of styles that include a dynamic hover effect, all within a type-safe framework. Utilizing `RenderWithOptions` is crucial for integrating the styles managed by `StyleManager` into the HTML output. + +### Detailed Usage + +For a comprehensive guide on using `StyleManager` and its advanced features, refer to the [`StyleManager` documentation](STYLEMANAGER.md). diff --git a/styles/STYLEMANAGER.md b/styles/STYLEMANAGER.md new file mode 100644 index 0000000..b63951a --- /dev/null +++ b/styles/STYLEMANAGER.md @@ -0,0 +1,163 @@ +# Advanced CSS Features with `StyleManager` in `elem-go` + +`StyleManager` within the `styles` package, is a powerful addition to elem-go, providing developers with the tools to programmatically manage CSS styles with advanced features like pseudo-classes, animations, and media queries. This enables the creation of dynamic and responsive web applications directly in Go. + +## Table of Contents + +- [Introduction](#introduction) +- [Installation](#installation) +- [Usage](#usage) + - [Creating a StyleManager](#creating-a-stylemanager) + - [Adding Styles](#adding-styles) + - [Pseudo-Classes](#pseudo-classes) + - [Animations](#animations) + - [Media Queries](#media-queries) +- [Features](#features) +- [Integration with `elem-go`](#integration-with-elem-go) +- [Examples](#examples) + +## Introduction + +The `StyleManager` provides a robust solution for managing CSS in Go-based web applications. It simplifies the creation of styles, including complex scenarios involving pseudo-classes animations, and media queries, within the type-safe environment of Go. + +## Installation + +To leverage `StyleManager`, ensure you're importing the `styles` package along with the core `elem` package: + +```go +import ( + "github.com/chasefleming/elem-go" + "github.com/chasefleming/elem-go/styles" +) +``` + +## Usage + +### Creating a `StyleManager` + +Initialize a `StyleManager` using `NewStyleManager()` to start managing your styles: + +```go +styleMgr := stylemanager.NewStyleManager() +``` + +Now, when you render your HTML elements, all you have to do is use `RenderWithOptions` instead of `Render` and provide the `StyleManager` instance: + +```go +html := elem.Div( + nil, + elem.Text("Hello, World!"), +) + +html.RenderWithOptions(elem.RenderOptions{ + StyleManager: styleMgr, +}) +``` + +> Note: This will inject the generated CSS into the HTML output in a `