Skip to content

Commit

Permalink
feat: add support for css args (#484)
Browse files Browse the repository at this point in the history
Fixes #88
  • Loading branch information
joerdav committed Feb 1, 2024
1 parent ef6d137 commit f24922f
Show file tree
Hide file tree
Showing 14 changed files with 241 additions and 93 deletions.
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.552
0.2.557
2 changes: 1 addition & 1 deletion cmd/templ/generatecmd/testwatch/watch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ func waitForUrl(url string) (err error) {
var tries int
for {
time.Sleep(time.Second)
if tries > 5 {
if tries > 20 {
return err
}
tries++
Expand Down
2 changes: 1 addition & 1 deletion cmd/templ/migratecmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func migrateV1TemplateFileNodeToV2TemplateFileNode(in v1.TemplateFileNode) (out
}, nil
case v1.CSSTemplate:
var t v2.CSSTemplate
t.Name.Value = n.Name.Value
t.Expression.Value = n.Name.Value
t.Properties = make([]v2.CSSProperty, len(n.Properties))
for i, p := range n.Properties {
t.Properties[i], err = migrateV1CSSPropertyToV2CSSProperty(p)
Expand Down
28 changes: 28 additions & 0 deletions docs/docs/03-syntax-and-usage/10-css-style-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,34 @@ The CSS class is given a unique name the first time it is used, and only rendere
The class name is autogenerated, don't rely on it being consistent.
:::

### CSS component arguments

CSS components can also require function arguments.

```templ title="component.templ"
package main
css loading(percent int) {
width: { fmt.Sprintf("%d%%", percent) };
}
templ index() {
<div class={ loading(50) }></div>
<div class={ loading(100) }></div>
}
```

```html title="Output"
<style type="text/css">
.loading_a3cc{width:50%;}
</style>
<div class="loading_a3cc"></div>
<style type="text/css">
.loading_9ccc{width:100%;}
</style>
<div class="loading_9ccc"></div>
```

### CSS Middleware

The use of CSS templates means that `<style>` elements containing the CSS are rendered on each HTTP request.
Expand Down
10 changes: 5 additions & 5 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,12 @@ func (g *generator) writeCSS(n parser.CSSTemplate) error {
if _, err = g.w.Write("func "); err != nil {
return err
}
if r, err = g.w.Write(n.Name.Value); err != nil {
if r, err = g.w.Write(n.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(n.Name, r)
// () templ.CSSClass {
if _, err = g.w.Write("() templ.CSSClass {\n"); err != nil {
g.sourceMap.Add(n.Expression, r)
// templ.CSSClass {
if _, err = g.w.Write(" templ.CSSClass {\n"); err != nil {
return err
}
{
Expand Down Expand Up @@ -266,7 +266,7 @@ func (g *generator) writeCSS(n parser.CSSTemplate) error {
return fmt.Errorf("unknown CSS property type: %v", reflect.TypeOf(p))
}
}
if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("templ_7745c5c3_CSSID := templ.CSSID(`%s`, templ_7745c5c3_CSSBuilder.String())\n", n.Name.Value)); err != nil {
if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("templ_7745c5c3_CSSID := templ.CSSID(`%s`, templ_7745c5c3_CSSBuilder.String())\n", n.Name)); err != nil {
return err
}
// return templ.CSS {
Expand Down
13 changes: 13 additions & 0 deletions generator/test-css-usage/expected.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,16 @@
<div class="a&#34; onClick=&#34;alert(&#39;hello&#39;)&#34;">
Class names are HTML escaped.
</div>
<style type="text/css">
.loading_a3cc{width:50%;}
</style>
<div class="loading_a3cc">
CSS components can be used with arguments.
</div>
<style type="text/css">
.loading_9ccc{width:100%;}
</style>
<div class="loading_9ccc">
CSS components can be used with arguments.
</div>

14 changes: 14 additions & 0 deletions generator/test-css-usage/template.templ
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package testcssusage

import "fmt"

// Constant class.
templ StyleTagsAreSupported() {
<style>
Expand Down Expand Up @@ -59,6 +61,17 @@ templ ClassNamesAreHTMLEscaped() {
<div class={ "a\" onClick=\"alert('hello')\"" }>Class names are HTML escaped.</div>
}

// CSS components can be used with arguments.

css loading(percent int) {
width: { fmt.Sprintf("%d%%", percent) };
}

templ CSSComponentsCanBeUsedWithArguments() {
<div class={ loading(50) }>CSS components can be used with arguments.</div>
<div class={ loading(100) }>CSS components can be used with arguments.</div>
}

// Combine all tests.
templ TestComponent() {
@StyleTagsAreSupported()
Expand All @@ -68,4 +81,5 @@ templ TestComponent() {
@KVCanBeUsedToConditionallySetClasses()
@PsuedoAttributesAndComplexClassNamesAreSupported()
@ClassNamesAreHTMLEscaped()
@CSSComponentsCanBeUsedWithArguments()
}
75 changes: 73 additions & 2 deletions generator/test-css-usage/template_templ.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 16 additions & 55 deletions parser/v2/cssparser.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package parser

import (
"strings"
"unicode"

"github.com/a-h/parse"
)

Expand All @@ -21,6 +18,7 @@ var cssParser = parse.Func(func(pi *parse.Input) (r CSSTemplate, ok bool, err er
return
}
r.Name = exp.Name
r.Expression = exp.Expression

for {
var cssProperty CSSProperty
Expand Down Expand Up @@ -64,66 +62,29 @@ var cssParser = parse.Func(func(pi *parse.Input) (r CSSTemplate, ok bool, err er

// css Func() {
type cssExpression struct {
Name Expression
Expression Expression
Name string
}

var cssExpressionStartParser = parse.String("css ")

var cssExpressionNameParser = parse.Func(func(in *parse.Input) (name string, ok bool, err error) {
var c string
if c, ok = in.Peek(1); !ok || !unicode.IsLetter(rune(c[0])) {
return
}
prefix, _, _ := parse.Letter.Parse(in)
suffix, _, _ := parse.AtMost(1000, parse.Any(parse.Letter, parse.ZeroToNine)).Parse(in)
return prefix + strings.Join(suffix, ""), true, nil
})

var cssExpressionParser = parse.Func(func(pi *parse.Input) (r cssExpression, ok bool, err error) {
// Check the prefix first.
if _, ok, err = cssExpressionStartParser.Parse(pi); err != nil || !ok {
return
}

// Once we have the prefix, we must have a name and parameters.
// Read the name of the function.
from := pi.Position()
// If there's no match, the name wasn't correctly terminated.
var name string
if name, ok, err = cssExpressionNameParser.Parse(pi); err != nil || !ok {
err = parse.Error("css expression: invalid name", pi.Position())
return
}
r.Name = NewExpression(name, from, pi.Position())

// Eat the open bracket.
if _, ok, err = openBracket.Parse(pi); err != nil || !ok {
err = parse.Error("css expression: parameters missing open bracket", pi.Position())
return
}
start := pi.Index()

// Check there's no parameters.
from = pi.Position()
if _, ok, err = parse.StringUntil(closeBracket).Parse(pi); err != nil {
return
}
// If there's no match, the name wasn't correctly terminated.
if !ok {
return r, ok, parse.Error("css expression: parameters missing close bracket", pi.Position())
}
if pi.Index()-int(from.Index) > 0 {
return r, ok, parse.Error("css expression: found unexpected parameters", pi.Position())
if !peekPrefix(pi, "css ") {
return r, false, nil
}

// Eat ") {".
if _, ok, err = expressionFuncEnd.Parse(pi); err != nil || !ok {
err = parse.Error("css expression: unterminated (missing ') {')", pi.Position())
return
// Once we have the prefix, everything to the brace is Go.
// e.g.
// css (x []string) Test() {
// becomes:
// func (x []string) Test() templ.CSSComponent {
if r.Name, r.Expression, err = parseCSSFuncDecl(pi); err != nil {
return r, false, err
}

// Expect a newline.
if _, ok, err = parse.NewLine.Parse(pi); err != nil || !ok {
err = parse.Error("css expression: missing terminating newline", pi.Position())
// Eat " {\n".
if _, ok, err = parse.All(openBraceWithOptionalPadding, parse.NewLine).Parse(pi); err != nil || !ok {
err = parse.Error("css expression: parameters missing open bracket", pi.PositionAt(start))
return
}

Expand Down
Loading

0 comments on commit f24922f

Please sign in to comment.