Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Css args #484

Merged
merged 2 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading