Skip to content

Commit

Permalink
Merge pull request #135 from chasefleming/chasefleming/stylemanager
Browse files Browse the repository at this point in the history
Enable advanced styling with StyleManager
  • Loading branch information
chasefleming committed Apr 5, 2024
2 parents 74943ff + 13fa1bb commit 3316c9f
Show file tree
Hide file tree
Showing 12 changed files with 816 additions and 4 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -204,6 +205,12 @@ comment := elem.Comment("Section: Main Content Start")
// Generates: <!-- Section: Main Content Start -->
```

## 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).
Expand Down
38 changes: 38 additions & 0 deletions elem.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package elem

import (
"fmt"
"sort"
"strings"

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 <style> element with the generated CSS content
styleElement := fmt.Sprintf("<style>%s</style>", cssContent)

// Check if a <head> tag exists in the HTML content
headStartIndex := strings.Index(htmlContent, "<head>")
headEndIndex := strings.Index(htmlContent, "</head>")

if headStartIndex != -1 && headEndIndex != -1 {
// If <head> exists, inject the style content just before </head>
beforeHead := htmlContent[:headEndIndex]
afterHead := htmlContent[headEndIndex:]
modifiedHTML := beforeHead + styleElement + afterHead
return modifiedHTML
} else {
// If <head> does not exist, create it and inject the style content
// Assuming <html> tag exists and injecting <head> immediately after <html>
htmlTagEnd := strings.Index(htmlContent, ">") + 1
if htmlTagEnd > 0 {
beforeHTML := htmlContent[:htmlTagEnd]
afterHTML := htmlContent[htmlTagEnd:]
modifiedHTML := beforeHTML + "<head>" + styleElement + "</head>" + afterHTML
return modifiedHTML
}
}
}

// Return the original HTML content if no modifications were made
return builder.String()
}

Expand Down
40 changes: 40 additions & 0 deletions elem_test.go
Original file line number Diff line number Diff line change
@@ -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 := "<!DOCTYPE html><html><head><style>body { background-color: #fff; }</style></head><body></body></html>"

// 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 <head> section")
}
43 changes: 43 additions & 0 deletions examples/stylemanager-demo/README.md
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 7 additions & 0 deletions examples/stylemanager-demo/go.mod
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions examples/stylemanager-demo/go.sum
Original file line number Diff line number Diff line change
@@ -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=
80 changes: 80 additions & 0 deletions examples/stylemanager-demo/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
56 changes: 55 additions & 1 deletion styles/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -216,4 +220,54 @@ This function returns a string representation as a CSS variable.

```go
varValue := styles.Var("primary-color") // Returns "var(--primary-color)"
```
```

## 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).

0 comments on commit 3316c9f

Please sign in to comment.