├── .github └── workflows │ └── go-test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── banner.jpg ├── examples ├── css-resets │ ├── README.md │ ├── main.go │ └── resets.css ├── full │ ├── README.md │ ├── main.go │ └── styles │ │ ├── buttons.go │ │ ├── layout.go │ │ ├── media.go │ │ ├── resets.go │ │ ├── stylesheet.go │ │ └── theme.go ├── integration-templ │ ├── README.md │ ├── components.templ │ ├── components_templ.go │ ├── go.mod │ ├── go.sum │ ├── home.html │ ├── home.templ │ ├── home_templ.go │ ├── main.go │ ├── styles.go │ └── stylesheet.go ├── media-queries │ ├── main.go │ └── media.css ├── template-function │ ├── home.gohtml │ └── main.go ├── to-file │ └── main.go ├── to-http-handler │ └── main.go └── to-stdout │ └── main.go ├── go.mod ├── go.sum ├── props ├── align_content.go ├── align_items.go ├── align_self.go ├── appearance.go ├── background_image.go ├── background_position.go ├── background_repeat.go ├── background_size.go ├── border.go ├── border_collapse.go ├── border_style.go ├── box_sizing.go ├── caption_side.go ├── colors.go ├── colors_test.go ├── cursor.go ├── display.go ├── flex_direction.go ├── flex_wrap.go ├── float.go ├── font_family.go ├── font_style.go ├── font_weight.go ├── justify_content.go ├── justify_items.go ├── justify_self.go ├── list_style_position.go ├── list_style_type.go ├── overflow.go ├── position.go ├── print_color_adjust.go ├── text_align.go ├── text_decoration_line.go ├── text_decoration_style.go ├── text_overflow.go ├── text_transform.go ├── text_wrap.go ├── unit.go ├── verticle_align.go ├── visibility.go └── white_space.go ├── style.go ├── style_test.go └── variables ├── colors.go └── sizes.go /.github/workflows/go-test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Set up Go 14 | uses: actions/setup-go@v5 15 | with: 16 | go-version: '>=1.22.0' 17 | 18 | - name: Check out code 19 | uses: actions/checkout@v4 20 | 21 | - name: Install dependencies 22 | run: | 23 | go mod tidy 24 | 25 | - name: Run vet 26 | run: | 27 | go vet ./... 28 | 29 | - name: Run tests 30 | run: | 31 | go test ./... -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | public 4 | resources 5 | tmp 6 | 7 | .hugo_build.lock -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ### v0.1.2 6 | 7 | - Move the `Props` CSS writing into a `Props.CSS` func, so it can be used in isolation of the `Style` struct. This allows for the `Props` CSS to be used in other contexts, such as in a `style` attribute in HTML. 8 | - Don't write empty values in the CSS string. eg: `.test{}` will not be written if there are no props to write. 9 | 10 | ### v0.1.1 11 | 12 | Added new props: 13 | 14 | - align-content 15 | - align-self 16 | - border-collapse 17 | 18 | Expanded props: 19 | 20 | - justify-content 21 | - justify-items 22 | - justify-self 23 | 24 | ### v0.1.0 25 | 26 | - Allow a color to be mixed with another color. 27 | - Change `props.Color` to allow any type of golang's `color.Color` interface. 28 | 29 | ### v0.0.9 30 | 31 | Added new variables: 32 | 33 | - extra sizes 34 | 35 | ### v0.0.8 36 | 37 | Added new variables: 38 | 39 | - colors 40 | - sizes 41 | 42 | Added new props: 43 | 44 | - max-height 45 | - max-width 46 | 47 | Added new units: 48 | 49 | - vh 50 | - vw 51 | 52 | ### v0.0.7 53 | 54 | Added new props: 55 | 56 | - font-family (basic defaults from tailwindcss) 57 | - text-decoration-color 58 | - text-decoration-line 59 | - text-decoration-style 60 | - text-decoration-thickness 61 | - text-indent 62 | - text-transform 63 | - text-underline-offset 64 | - text-wrap 65 | 66 | ### v0.0.6 67 | 68 | Added new props: 69 | 70 | - column-gap 71 | - row-gap 72 | - box-sizing 73 | - list-style-position 74 | - list-style-type 75 | - vertical-align 76 | - z-index 77 | - visibility 78 | - opacity 79 | 80 | ### v0.0.5 81 | 82 | Added explicit CustomProp type and make CustomProps a slice to preserve ordering. 83 | 84 | ### v0.0.4 85 | 86 | Added support for custom props. 87 | 88 | Added new props: 89 | 90 | - cursor 91 | - border-bottom-left-radius 92 | - border-bottom-right-radius 93 | - border-top-left-radius 94 | - border-top-right-radius 95 | 96 | ### v0.0.3 97 | 98 | Added new props: 99 | 100 | - bottom 101 | - left 102 | - right 103 | - top 104 | - gap 105 | - text-overflow 106 | - white-space 107 | 108 | ### v0.0.2 109 | 110 | Added licensing information. 111 | 112 | ### v0.0.1 113 | 114 | Initial release. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Accent Design Group Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Test](https://github.com/AccentDesign/gcss/actions/workflows/go-test.yml/badge.svg)](https://github.com/AccentDesign/gcss/actions/workflows/go-test.yml) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/AccentDesign/gcss)](https://goreportcard.com/report/github.com/AccentDesign/gcss) 3 | 4 | 5 | banner 6 | 7 | # gcss 8 | 9 | CSS written in Pure Go. 10 | 11 | ## Motivation 12 | 13 | This is really just a bit of fun and a way to write CSS in Go. I wanted to see if it was possible and what would it look like. 14 | I wanted to find a way to easily control the CSS from the server side and not have to worry about pre-building the css to take variables and stuff. 15 | I didnt want to use UI libraries that are written for JS frameworks and I didn't want to use preprocessors or linters that add more steps to the build process. 16 | 17 | Could I just use CSS? Yes of course and I will, but I wanted to see if I could write CSS in Go as this is what is compiling the rest of the project. 18 | 19 | ## Gopher 20 | 21 | No it looks nothing like the Go gopher, but it's a gopher and I like it. It's the best I could get from the LM without giving up, [ideogram.ai (1400097641)](https://ideogram.ai/g/E-5MQp7QTPO4uyF9PvERzw/3). 22 | ## Next steps 23 | 24 | The next steps for this project are to add more features to the CSS package. 25 | This includes adding support for more CSS properties when the need arises. 26 | What I don't want to do is to add support for all CSS functionality as some things are better in CSS, but I do want to be able to create 27 | a few UI components that are configurable using Go. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | go get github.com/AccentDesign/gcss 33 | ``` 34 | 35 | ## Quickstart 36 | 37 | There is a separate repo with the full example [here](https://github.com/AccentDesign/gcss-starter) which will evolve over time. 38 | 39 | ```bash 40 | git clone https://github.com/AccentDesign/gcss-starter.git 41 | ``` 42 | 43 | install the dependencies: 44 | 45 | ```bash 46 | # for hot reloading 47 | go install github.com/cosmtrek/air@latest 48 | ``` 49 | 50 | ```bash 51 | go mod tidy 52 | ``` 53 | 54 | run the server: 55 | 56 | ```bash 57 | air 58 | ``` 59 | 60 | ## Usage 61 | 62 | ### Basic usage 63 | 64 | `gcss` defines a `Style` type that can be used to hold the properties for a specific css selector, eg: 65 | 66 | ```go 67 | style := gcss.Style{ 68 | Selector: "body", 69 | Props: gcss.Props{ 70 | BackgroundColor: props.ColorRGBA(0, 0, 0, 128), 71 | }, 72 | } 73 | ``` 74 | 75 | The `CSS` function on the `Style` is used to write the style to a `io.Writer`: 76 | 77 | ```go 78 | style.CSS(os.Stdout) 79 | ``` 80 | 81 | which gives you: 82 | 83 | ```css 84 | body{background-color:rgba(0,0,0,0.50);} 85 | ``` 86 | 87 | That's all there is to it. But it's not very useful on it's own I hear you say. 88 | 89 | ### Multiple styles 90 | 91 | Well you can then use that to define a `Styles` type that can be used to hold multiple `Style` types: 92 | 93 | ```go 94 | type Styles []gcss.Style 95 | 96 | func (s Styles) CSS(w io.Writer) error { 97 | // handle your errors 98 | for _, style := range s { 99 | style.CSS(w) 100 | } 101 | return nil 102 | } 103 | 104 | styles := Styles{ 105 | { 106 | Selector: "body", 107 | Props: gcss.Props{ 108 | BackgroundColor: props.ColorRGBA(0, 0, 0, 128), 109 | }, 110 | }, 111 | { 112 | Selector: "main", 113 | Props: gcss.Props{ 114 | Padding: props.UnitRem(8.5), 115 | }, 116 | }, 117 | } 118 | 119 | styles.CSS(os.Stdout) 120 | ``` 121 | 122 | which gives you: 123 | 124 | ```css 125 | /* formatted for visibility */ 126 | body{ 127 | background-color:rgba(0,0,0,0.50); 128 | } 129 | main{ 130 | padding:8.500rem; 131 | } 132 | ``` 133 | 134 | ### Need a bit more? what about a dark and light theme? keep the last example in mind and read on. 135 | 136 | Define a `Theme` type that can be used to hold attributes for a specific theme, eg: 137 | 138 | ```go 139 | type Theme struct { 140 | MediaQuery string 141 | Background props.Color 142 | } 143 | 144 | func (t *Theme) CSS(w io.Writer) error { 145 | // handle your errors 146 | fmt.Fprintf(w, "%s{", t.MediaQuery) 147 | for _, style := range t.Styles() { 148 | style.CSS(w) 149 | } 150 | fmt.Fprint(w, "}") 151 | } 152 | 153 | // Styles returns the styles for the theme. 154 | // Can be any number of styles you want and any number of functions 155 | // you just need them in the CSS function to loop over. 156 | func (t *Theme) Styles() Styles { 157 | return Styles{ 158 | { 159 | Selector: "body", 160 | Props: gcss.Props{ 161 | BackgroundColor: t.Background, 162 | }, 163 | }, 164 | } 165 | } 166 | ``` 167 | 168 | Then you can define a `Stylesheet` type that can be used to hold multiple `Theme` types: 169 | 170 | ```go 171 | type Stylesheet struct { 172 | Dark *Theme 173 | Light *Theme 174 | } 175 | 176 | func (s *Stylesheet) CSS(w io.Writer) error { 177 | // handle your errors 178 | s.Dark.CSS(w) 179 | s.Light.CSS(w) 180 | return nil 181 | } 182 | ``` 183 | 184 | Finally, you can use the `Stylesheet` type to write the css to a `io.Writer`: 185 | 186 | ```go 187 | styles := Stylesheet{ 188 | Dark: &Theme{ 189 | MediaQuery: "@media (prefers-color-scheme: dark)", 190 | Background: props.ColorRGBA(0, 0, 0, 255), 191 | }, 192 | Light: &Theme{ 193 | MediaQuery: "@media (prefers-color-scheme: light)", 194 | Background: props.ColorRGBA(255, 255, 255, 255), 195 | }, 196 | } 197 | 198 | styles.CSS(os.Stdout) 199 | ``` 200 | 201 | gives you: 202 | 203 | ```css 204 | /* formatted for visibility */ 205 | @media (prefers-color-scheme: dark) { 206 | body{ 207 | background-color:rgba(0,0,0,1.00); 208 | } 209 | } 210 | @media (prefers-color-scheme: light) { 211 | body{ 212 | background-color:rgba(255,255,255,1.00); 213 | } 214 | } 215 | ``` 216 | 217 | Hopefully this will get you going. The rest is up to you. 218 | 219 | * Maybe create a button function that takes a `props.Color` and returns a Style. 220 | * Or add extra `Styles` to the `Stylesheet` to additionally include non themed styles. 221 | * It's all about how you construct the `Stylesheet` and use the `gcss.Style` type. 222 | * If I could have created a `Stylesheet` type that fits well any use case at all I would have, but there is a world of possibility, so I left it up to you. 223 | 224 | ## The benefits 225 | 226 | * Total control of the CSS from the server side. 227 | * CSS doesn't have mixins, but you can create a function that returns a `Style` type and reuse it. 228 | * Keeps the css free of variables. 229 | * Keeps html free of classes like `bg-gray-50 text-black dark:bg-slate-800 dark:text-white` and eliminates the need to remember to add the dark variant. 230 | * I recently saw a button component on an html page 10 times with over 1800 characters in the class attribute of each. This is not maintainable nor debuggable. 231 | * Keeps the css clean and easy to debug with no overrides like the above. 232 | 233 | ## Examples 234 | 235 | For example usage see the [examples](./examples) directory that include: 236 | 237 | * [Full example](./examples/full) - A full example including base, media and themed styles with a `sync.Mutex` for caching the css in a `http.HandleFunc`. 238 | * [CSS resets](./examples/css-resets) - A simple example collection of css resets. 239 | * [Templ integration](./examples/integration-templ) - An example of how to load styles from gcss with the [templ](https://templ.guide) package. 240 | * [Media queries](./examples/media-queries) - An example of how to use media queries. 241 | * [Template function](./examples/template-function) - An example of how to write css to the template directly via `template.FuncMap` and `template.CSS`. 242 | * [Write to a file](./examples/to-file) - An example of how to write to a file. 243 | * [Write to an HTTP handler](./examples/to-http-handler) - An example of how to write to an http handler. 244 | * [Write to stdout](./examples/to-stdout) - An example of how to write to stdout. 245 | 246 | ## Contributing 247 | 248 | If you would like to contribute to this project, please open an issue or a pull request. We welcome all contributions and ideas. 249 | 250 | ## Mix it up with other CSS frameworks 251 | 252 | You can mix `gcss` with other CSS frameworks like `tailwindcss` for example: 253 | 254 | separate the css files into base and utils: 255 | 256 | ```css 257 | /* base.css */ 258 | @tailwind base; 259 | ``` 260 | 261 | ```css 262 | /* utils.css */ 263 | @tailwind utilities; 264 | ``` 265 | 266 | Then add the `gcss` styles in between in your html: 267 | 268 | ```html 269 | 270 | 271 | 272 | ``` 273 | 274 | Try to keep the specificity of the `gcss` styles to 1 by using single classes this will ensure any `tailwindcss` utilities 275 | will be able to overwrite your styles where required. 276 | -------------------------------------------------------------------------------- /banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AccentDesign/gcss/3b5aa22d0680d512ed6f3d2ae7807fddd80d9610/banner.jpg -------------------------------------------------------------------------------- /examples/css-resets/README.md: -------------------------------------------------------------------------------- 1 | # CSS Resets 2 | 3 | A useful collection of CSS resets to start your projects with a clean slate. 4 | -------------------------------------------------------------------------------- /examples/css-resets/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/AccentDesign/gcss" 5 | "github.com/AccentDesign/gcss/props" 6 | "github.com/AccentDesign/gcss/variables" 7 | "os" 8 | ) 9 | 10 | var resets = []gcss.Style{ 11 | { 12 | Selector: "*,::after,::before,::backdrop,::file-selector-button", 13 | Props: gcss.Props{ 14 | Border: props.Border{ 15 | Width: variables.Size0, 16 | Style: props.BorderStyleSolid, 17 | }, 18 | BoxSizing: props.BoxSizingBorderBox, 19 | Margin: variables.Size0, 20 | Padding: variables.Size0, 21 | }, 22 | }, 23 | { 24 | Selector: "html,:host", 25 | Props: gcss.Props{ 26 | FontFamily: props.FontFamilySans, 27 | LineHeight: props.UnitRaw(1.5), 28 | }, 29 | CustomProps: []gcss.CustomProp{ 30 | {Attr: "-webkit-text-size-adjust", Value: "100%"}, 31 | {Attr: "tab-size", Value: "4"}, 32 | }, 33 | }, 34 | { 35 | Selector: "body", 36 | Props: gcss.Props{ 37 | LineHeight: props.UnitInherit(), 38 | }, 39 | }, 40 | { 41 | Selector: "hr", 42 | Props: gcss.Props{ 43 | Color: props.ColorInherit(), 44 | Height: variables.Size0, 45 | }, 46 | CustomProps: []gcss.CustomProp{ 47 | {Attr: "border-top-width", Value: "1px"}, 48 | }, 49 | }, 50 | { 51 | Selector: "abbr:where([title])", 52 | Props: gcss.Props{ 53 | TextDecorationLine: props.TextDecorationLineUnderline, 54 | TextDecorationStyle: props.TextDecorationStyleDotted, 55 | }, 56 | }, 57 | { 58 | Selector: "h1,h2,h3,h4,h5,h6", 59 | Props: gcss.Props{ 60 | FontSize: props.UnitInherit(), 61 | FontWeight: "inherit", 62 | }, 63 | }, 64 | { 65 | Selector: "a", 66 | Props: gcss.Props{ 67 | Color: props.ColorInherit(), 68 | TextDecorationLine: "inherit", 69 | TextDecorationStyle: "inherit", 70 | }, 71 | }, 72 | { 73 | Selector: "b,strong", 74 | Props: gcss.Props{ 75 | FontWeight: "bolder", 76 | }, 77 | }, 78 | { 79 | Selector: "code,kbd,samp,pre", 80 | Props: gcss.Props{ 81 | FontFamily: props.FontFamilyMono, 82 | FontSize: props.UnitEm(1), 83 | }, 84 | }, 85 | { 86 | Selector: "small", 87 | Props: gcss.Props{ 88 | FontSize: props.UnitPercent(80), 89 | }, 90 | }, 91 | { 92 | Selector: "sub,sup", 93 | Props: gcss.Props{ 94 | FontSize: props.UnitPercent(75), 95 | LineHeight: variables.Size0, 96 | Position: props.PositionRelative, 97 | VerticalAlign: props.VerticalAlignBaseline, 98 | }, 99 | }, 100 | { 101 | Selector: "sub", 102 | Props: gcss.Props{ 103 | Bottom: props.UnitEm(-0.25), 104 | }, 105 | }, 106 | { 107 | Selector: "sup", 108 | Props: gcss.Props{ 109 | Top: props.UnitEm(-0.5), 110 | }, 111 | }, 112 | { 113 | Selector: "table", 114 | Props: gcss.Props{ 115 | BorderCollapse: props.BorderCollapseCollapse, 116 | BorderColor: props.ColorInherit(), 117 | TextIndent: variables.Size0, 118 | }, 119 | }, 120 | { 121 | Selector: "button,input,optgroup,select,textarea,::file-selector-button", 122 | Props: gcss.Props{ 123 | BackgroundColor: props.ColorTransparent(), 124 | Color: props.ColorInherit(), 125 | FontFamily: "inherit", 126 | FontSize: props.UnitInherit(), 127 | }, 128 | CustomProps: []gcss.CustomProp{ 129 | {Attr: "letter-spacing", Value: "inherit"}, 130 | }, 131 | }, 132 | { 133 | Selector: "input:where(:not([type='button'],[type='reset'],[type='submit'])),select,textarea", 134 | Props: gcss.Props{ 135 | Border: props.Border{ 136 | Width: props.UnitPx(1), 137 | Style: props.BorderStyleSolid, 138 | }, 139 | }, 140 | }, 141 | { 142 | Selector: "button,input:where([type='button'],[type='reset'],[type='submit']),::file-selector-button", 143 | Props: gcss.Props{ 144 | Appearance: props.AppearanceButton, 145 | }, 146 | }, 147 | { 148 | Selector: ":-moz-focusring", 149 | CustomProps: []gcss.CustomProp{ 150 | {Attr: "outline", Value: "auto"}, 151 | }, 152 | }, 153 | { 154 | Selector: ":-moz-ui-invalid", 155 | CustomProps: []gcss.CustomProp{ 156 | {Attr: "box-shadow", Value: "none"}, 157 | }, 158 | }, 159 | { 160 | Selector: "progress", 161 | Props: gcss.Props{ 162 | VerticalAlign: props.VerticalAlignBaseline, 163 | }, 164 | }, 165 | { 166 | Selector: "::-webkit-inner-spin-button,::-webkit-outer-spin-button", 167 | Props: gcss.Props{ 168 | Height: props.UnitAuto(), 169 | }, 170 | }, 171 | { 172 | Selector: "::-webkit-search-decoration", 173 | CustomProps: []gcss.CustomProp{ 174 | {Attr: "-webkit-appearance", Value: "none"}, 175 | }, 176 | }, 177 | { 178 | Selector: "summary", 179 | Props: gcss.Props{ 180 | Display: props.DisplayListItem, 181 | }, 182 | }, 183 | { 184 | Selector: "ol,ul,menu", 185 | Props: gcss.Props{ 186 | ListStyleType: props.ListStyleTypeNone, 187 | ListStylePosition: props.ListStylePositionInside, 188 | }, 189 | }, 190 | { 191 | Selector: "textarea", 192 | CustomProps: []gcss.CustomProp{ 193 | {Attr: "resize", Value: "vertical"}, 194 | }, 195 | }, 196 | { 197 | Selector: "::placeholder", 198 | Props: gcss.Props{ 199 | Color: props.Color{Keyword: "color-mix(in srgb, currentColor 50%, transparent)"}, 200 | Opacity: props.UnitRaw(1), 201 | }, 202 | }, 203 | { 204 | Selector: ":disabled", 205 | Props: gcss.Props{ 206 | Cursor: props.CursorDefault, 207 | }, 208 | }, 209 | { 210 | Selector: "img,svg,video,canvas,audio,iframe,embed,object", 211 | Props: gcss.Props{ 212 | Display: props.DisplayBlock, 213 | VerticalAlign: props.VerticalAlignMiddle, 214 | }, 215 | }, 216 | { 217 | Selector: "img,video", 218 | Props: gcss.Props{ 219 | Height: props.UnitAuto(), 220 | MaxWidth: variables.Full, 221 | }, 222 | }, 223 | { 224 | Selector: "[hidden]", 225 | Props: gcss.Props{ 226 | Display: props.DisplayNone, 227 | }, 228 | }, 229 | } 230 | 231 | func main() { 232 | file, err := os.Create("resets.css") 233 | if err != nil { 234 | panic(err) 235 | } 236 | defer file.Close() 237 | 238 | for _, style := range resets { 239 | if err := style.CSS(file); err != nil { 240 | panic(err) 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /examples/css-resets/resets.css: -------------------------------------------------------------------------------- 1 | *,::after,::before,::backdrop,::file-selector-button{border:0 solid;box-sizing:border-box;margin:0;padding:0;}html,:host{font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";line-height:1.5;-webkit-text-size-adjust:100%;tab-size:4;}body{line-height:inherit;}hr{color:inherit;height:0;border-top-width:1px;}abbr:where([title]){text-decoration-line:underline;text-decoration-style:dotted;}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit;}a{color:inherit;text-decoration-line:inherit;text-decoration-style:inherit;}b,strong{font-weight:bolder;}code,kbd,samp,pre{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-size:1.000em;}small{font-size:80.00%;}sub,sup{font-size:75.00%;line-height:0;position:relative;vertical-align:baseline;}sub{bottom:-0.250em;}sup{top:-0.500em;}table{border-collapse:collapse;border-color:inherit;text-indent:0;}button,input,optgroup,select,textarea,::file-selector-button{background-color:transparent;color:inherit;font-family:inherit;font-size:inherit;letter-spacing:inherit;}input:where(:not([type='button'],[type='reset'],[type='submit'])),select,textarea{border:1px solid;}button,input:where([type='button'],[type='reset'],[type='submit']),::file-selector-button{appearance:button;}:-moz-focusring{outline:auto;}:-moz-ui-invalid{box-shadow:none;}progress{vertical-align:baseline;}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto;}::-webkit-search-decoration{-webkit-appearance:none;}summary{display:list-item;}ol,ul,menu{list-style-position:inside;list-style-type:none;}textarea{resize:vertical;}::placeholder{color:color-mix(in srgb, currentColor 50%, transparent);opacity:1;}:disabled{cursor:default;}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle;}img,video{height:auto;max-width:100.00%;}[hidden]{display:none;} -------------------------------------------------------------------------------- /examples/full/README.md: -------------------------------------------------------------------------------- 1 | # Full example with mutex 2 | 3 | This example hase the following: 4 | 5 | * A `StyleSheet` that has has global resets, base styles, media queries for devices, themes and a `Mutex` that generates the CSS only once to avoid multiple builds. 6 | * The `main` element has base styles and media queries for different screen sizes. 7 | * Both `body` and `buttons` have base styles as well as themed for the likes of background and foreground variations. 8 | 9 | ## Usage 10 | 11 | For an example of adding form styles. 12 | 13 | 1. Create a new `form.go` file. and add the following code (or add what you need): 14 | 15 | ```go 16 | // Form returns the styles for buttons for the base stylesheet. 17 | func (ss *StyleSheet) Form() Styles { 18 | return Styles{} 19 | } 20 | 21 | // Form returns the styles for the layout for the media. 22 | func (m *Media) Form() Styles { 23 | return Styles{} 24 | } 25 | 26 | // Form returns the styles for the layout for the theme. 27 | func (t *Theme) Form() Styles { 28 | return Styles{} 29 | } 30 | ``` 31 | 32 | 2. Then add the functions to the `CSS` function in `StyleSheet`, `Media` and `Theme`. 33 | 34 | 35 | ## HTML 36 | 37 | ```html 38 | 39 | 40 | 41 | 42 | 43 | 44 | Theme 45 | 46 | 47 | 48 |
49 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

50 |
51 | 52 |
53 |
54 | 55 | 56 | ``` 57 | 58 | ## CSS 59 | 60 | ```css 61 | /* formatted for visual clarity */ 62 | 63 | /* resets */ 64 | *, ::after, ::before, ::backdrop, ::file-selector-button { 65 | border: 0 solid; 66 | box-sizing: border-box; 67 | margin: 0; 68 | padding: 0; 69 | } 70 | 71 | html, :host { 72 | font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 73 | line-height: 1.5; 74 | -webkit-text-size-adjust: 100%; 75 | tab-size: 4; 76 | } 77 | 78 | body { 79 | line-height: inherit; 80 | } 81 | 82 | /* base */ 83 | body { 84 | min-height: 100vh; 85 | } 86 | 87 | main { 88 | display: grid; 89 | } 90 | 91 | .button { 92 | align-items: center; 93 | border-radius: 0.375rem; 94 | display: inline-flex; 95 | font-size: 0.875rem; 96 | font-weight: 500; 97 | height: 2.500rem; 98 | justify-content: center; 99 | line-height: 1.250rem; 100 | padding-bottom: 0.500rem; 101 | padding-left: 1.000rem; 102 | padding-right: 1.000rem; 103 | padding-top: 0.500rem; 104 | } 105 | 106 | /* media */ 107 | @media (max-width: 768px) { 108 | main { 109 | padding: 2.000rem; 110 | row-gap: 1.500rem; 111 | } 112 | } 113 | 114 | @media (min-width: 769px) { 115 | main { 116 | padding: 4.000rem; 117 | row-gap: 2.000rem; 118 | } 119 | } 120 | 121 | /* themes */ 122 | @media (prefers-color-scheme: light) { 123 | body { 124 | background-color: rgba(255, 255, 255, 1.00); 125 | color: rgba(23, 23, 23, 1.00); 126 | } 127 | 128 | .button-primary { 129 | background-color: rgba(23, 23, 23, 1.00); 130 | color: rgba(255, 255, 255, 1.00); 131 | } 132 | } 133 | 134 | @media (prefers-color-scheme: dark) { 135 | body { 136 | background-color: rgba(23, 23, 23, 1.00); 137 | color: rgba(245, 245, 245, 1.00); 138 | } 139 | 140 | .button-primary { 141 | background-color: rgba(255, 255, 255, 1.00); 142 | color: rgba(23, 23, 23, 1.00); 143 | } 144 | } 145 | 146 | ``` -------------------------------------------------------------------------------- /examples/full/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AccentDesign/gcss/examples/full/styles" 6 | "net/http" 7 | ) 8 | 9 | var ( 10 | stylesheet = styles.NewStyleSheet() 11 | html = ` 12 | 13 | 14 | 15 | 16 | 17 | 18 | Theme 19 | 20 | 21 | 22 |
23 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

24 |
25 | 26 |
27 |
28 | 29 | 30 | ` 31 | ) 32 | 33 | func main() { 34 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 35 | if _, err := fmt.Fprint(w, html); err != nil { 36 | http.Error(w, err.Error(), http.StatusInternalServerError) 37 | } 38 | }) 39 | 40 | http.HandleFunc("/stylesheet.css", func(w http.ResponseWriter, r *http.Request) { 41 | w.Header().Set("Content-Type", "text/css") 42 | if err := stylesheet.CSS(w); err != nil { 43 | http.Error(w, err.Error(), http.StatusInternalServerError) 44 | } 45 | }) 46 | 47 | fmt.Println("Server started at http://localhost:8080") 48 | 49 | if err := http.ListenAndServe(":8080", nil); err != nil { 50 | panic(err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/full/styles/buttons.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "github.com/AccentDesign/gcss" 5 | "github.com/AccentDesign/gcss/props" 6 | "github.com/AccentDesign/gcss/variables" 7 | ) 8 | 9 | // Buttons returns the styles for buttons for the base stylesheet. 10 | func (ss *StyleSheet) Buttons() Styles { 11 | return Styles{ 12 | { 13 | Selector: ".button", 14 | Props: gcss.Props{ 15 | AlignItems: props.AlignItemsCenter, 16 | BorderRadius: variables.Size1H, 17 | Display: props.DisplayInlineFlex, 18 | FontSize: variables.Size3H, 19 | FontWeight: props.FontWeightMedium, 20 | Height: variables.Size10, 21 | JustifyContent: props.JustifyContentCenter, 22 | LineHeight: variables.Size5, 23 | PaddingTop: variables.Size2, 24 | PaddingRight: variables.Size4, 25 | PaddingBottom: variables.Size2, 26 | PaddingLeft: variables.Size4, 27 | }, 28 | }, 29 | } 30 | } 31 | 32 | // Buttons returns the styles for buttons for the theme. 33 | func (t *Theme) Buttons() Styles { 34 | return Styles{ 35 | { 36 | Selector: ".button-primary", 37 | Props: gcss.Props{ 38 | BackgroundColor: t.Primary, 39 | Color: t.PrimaryForeground, 40 | }, 41 | }, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/full/styles/layout.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "github.com/AccentDesign/gcss" 5 | "github.com/AccentDesign/gcss/props" 6 | "github.com/AccentDesign/gcss/variables" 7 | ) 8 | 9 | // Layout returns the styles for the layout for the base stylesheet. 10 | func (ss *StyleSheet) Layout() Styles { 11 | return Styles{ 12 | { 13 | Selector: "body", 14 | Props: gcss.Props{ 15 | MinHeight: variables.FullScreenHeight, 16 | }, 17 | }, 18 | { 19 | Selector: "main", 20 | Props: gcss.Props{ 21 | Display: props.DisplayGrid, 22 | }, 23 | }, 24 | } 25 | } 26 | 27 | // Layout returns the styles for the layout for the media. 28 | func (m *Media) Layout() Styles { 29 | return Styles{ 30 | { 31 | Selector: "main", 32 | Props: gcss.Props{ 33 | Padding: m.Padding, 34 | RowGap: m.VerticalGap, 35 | }, 36 | }, 37 | } 38 | } 39 | 40 | // Layout returns the styles for the layout for the theme. 41 | func (t *Theme) Layout() Styles { 42 | return Styles{ 43 | { 44 | Selector: "body", 45 | Props: gcss.Props{ 46 | BackgroundColor: t.Background, 47 | Color: t.Foreground, 48 | }, 49 | }, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/full/styles/media.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/AccentDesign/gcss/props" 7 | "github.com/AccentDesign/gcss/variables" 8 | "io" 9 | "slices" 10 | ) 11 | 12 | type ( 13 | Media struct { 14 | Query string 15 | Padding props.Unit 16 | VerticalGap props.Unit 17 | } 18 | ) 19 | 20 | var ( 21 | mobileMedia = &Media{ 22 | Query: "@media (max-width: 768px)", 23 | Padding: variables.Size8, 24 | VerticalGap: variables.Size6, 25 | } 26 | desktopMedia = &Media{ 27 | Query: "@media (min-width: 769px)", 28 | Padding: variables.Size16, 29 | VerticalGap: variables.Size8, 30 | } 31 | ) 32 | 33 | // CSS Writes the CSS for the media to the writer. 34 | func (m *Media) CSS(w io.Writer) error { 35 | var buf bytes.Buffer 36 | for _, style := range slices.Concat( 37 | m.Layout(), 38 | ) { 39 | if err := style.CSS(&buf); err != nil { 40 | return err 41 | } 42 | } 43 | if buf.Len() > 0 { 44 | _, err := fmt.Fprintf(w, "%s{%s}", m.Query, buf.String()) 45 | return err 46 | } 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /examples/full/styles/resets.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "github.com/AccentDesign/gcss" 5 | "github.com/AccentDesign/gcss/props" 6 | "github.com/AccentDesign/gcss/variables" 7 | ) 8 | 9 | // Resets returns the styles for resets for the base stylesheet. 10 | func (ss *StyleSheet) Resets() Styles { 11 | return Styles{ 12 | { 13 | Selector: "*,::after,::before,::backdrop,::file-selector-button", 14 | Props: gcss.Props{ 15 | Border: props.Border{ 16 | Width: variables.Size0, 17 | Style: props.BorderStyleSolid, 18 | }, 19 | BoxSizing: props.BoxSizingBorderBox, 20 | Margin: variables.Size0, 21 | Padding: variables.Size0, 22 | }, 23 | }, 24 | { 25 | Selector: "html,:host", 26 | Props: gcss.Props{ 27 | FontFamily: props.FontFamilySans, 28 | LineHeight: props.UnitRaw(1.5), 29 | }, 30 | CustomProps: []gcss.CustomProp{ 31 | {Attr: "-webkit-text-size-adjust", Value: "100%"}, 32 | {Attr: "tab-size", Value: "4"}, 33 | }, 34 | }, 35 | { 36 | Selector: "body", 37 | Props: gcss.Props{ 38 | LineHeight: props.UnitInherit(), 39 | }, 40 | }, 41 | // more resets 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/full/styles/stylesheet.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "bytes" 5 | "github.com/AccentDesign/gcss" 6 | "io" 7 | "slices" 8 | "sync" 9 | ) 10 | 11 | type ( 12 | Styles []gcss.Style 13 | StyleSheet struct { 14 | Media []*Media 15 | Themes []*Theme 16 | css bytes.Buffer 17 | mutex sync.Mutex 18 | } 19 | ) 20 | 21 | // CSS Writes the CSS for the stylesheet to the writer. 22 | func (ss *StyleSheet) CSS(w io.Writer) error { 23 | ss.mutex.Lock() 24 | defer ss.mutex.Unlock() 25 | 26 | if ss.css.Len() > 0 { 27 | _, err := w.Write(ss.css.Bytes()) 28 | return err 29 | } 30 | 31 | for _, style := range slices.Concat( 32 | ss.Resets(), 33 | ss.Layout(), 34 | ss.Buttons(), 35 | ) { 36 | if err := style.CSS(&ss.css); err != nil { 37 | return err 38 | } 39 | } 40 | 41 | for _, media := range ss.Media { 42 | if err := media.CSS(&ss.css); err != nil { 43 | return err 44 | } 45 | } 46 | 47 | for _, theme := range ss.Themes { 48 | if err := theme.CSS(&ss.css); err != nil { 49 | return err 50 | } 51 | } 52 | 53 | _, err := w.Write(ss.css.Bytes()) 54 | return err 55 | } 56 | 57 | // NewStyleSheet returns a new stylesheet. It includes the media queries and themes. 58 | func NewStyleSheet() *StyleSheet { 59 | return &StyleSheet{ 60 | Media: []*Media{mobileMedia, desktopMedia}, 61 | Themes: []*Theme{lightTheme, darkTheme}, 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/full/styles/theme.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/AccentDesign/gcss/props" 7 | "github.com/AccentDesign/gcss/variables" 8 | "io" 9 | "slices" 10 | ) 11 | 12 | type ( 13 | Theme struct { 14 | MediaQuery string 15 | Background props.Color 16 | Foreground props.Color 17 | Primary props.Color 18 | PrimaryForeground props.Color 19 | } 20 | ) 21 | 22 | var ( 23 | lightTheme = &Theme{ 24 | MediaQuery: "@media (prefers-color-scheme: light)", 25 | Background: variables.White, 26 | Foreground: variables.Neutral900, 27 | Primary: variables.Neutral900, 28 | PrimaryForeground: variables.White, 29 | } 30 | darkTheme = &Theme{ 31 | MediaQuery: "@media (prefers-color-scheme: dark)", 32 | Background: variables.Neutral900, 33 | Foreground: variables.Neutral100, 34 | Primary: variables.White, 35 | PrimaryForeground: variables.Neutral900, 36 | } 37 | ) 38 | 39 | // CSS Writes the CSS for the theme to the writer. 40 | func (t *Theme) CSS(w io.Writer) error { 41 | var buf bytes.Buffer 42 | for _, style := range slices.Concat( 43 | t.Layout(), 44 | t.Buttons(), 45 | ) { 46 | if err := style.CSS(&buf); err != nil { 47 | return err 48 | } 49 | } 50 | if buf.Len() > 0 { 51 | _, err := fmt.Fprintf(w, "%s{%s}", t.MediaQuery, buf.String()) 52 | return err 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /examples/integration-templ/README.md: -------------------------------------------------------------------------------- 1 | # Templ Integration 2 | 3 | This is an example of how to integrate the awesome [Templ](https://templ.guide) with `gcss`. 4 | It's on the basic side, but it should give you a good idea of how to get started. 5 | 6 | I will include a better example in the future, but for now, this should be enough to get you started. 7 | 8 | ## Notes 9 | 10 | The Stylesheet type is there till we decide if it will be in the package itself or not. 11 | Though this is unlikely to change till there is a clear need for it. 12 | 13 | When you render the template, you will see that it renders two buttons that share the same style. 14 | This is done through the usage of the [Once](https://templ.guide/syntax-and-usage/render-once) handler provided by templ 15 | and allows you to selectively render styles when they are required. 16 | -------------------------------------------------------------------------------- /examples/integration-templ/components.templ: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | templ ButtonCSS() { 4 | @buttonStylesHandle.Once() { 5 | @buttonStyles 6 | } 7 | } 8 | 9 | templ Button(name string) { 10 | @ButtonCSS() 11 | 12 | } 13 | -------------------------------------------------------------------------------- /examples/integration-templ/components_templ.go: -------------------------------------------------------------------------------- 1 | // Code generated by templ - DO NOT EDIT. 2 | 3 | // templ: version: v0.2.707 4 | package main 5 | 6 | //lint:file-ignore SA4006 This context is only used if a nested component is present. 7 | 8 | import "github.com/a-h/templ" 9 | import "context" 10 | import "io" 11 | import "bytes" 12 | 13 | func ButtonCSS() templ.Component { 14 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 15 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 16 | if !templ_7745c5c3_IsBuffer { 17 | templ_7745c5c3_Buffer = templ.GetBuffer() 18 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 19 | } 20 | ctx = templ.InitializeContext(ctx) 21 | templ_7745c5c3_Var1 := templ.GetChildren(ctx) 22 | if templ_7745c5c3_Var1 == nil { 23 | templ_7745c5c3_Var1 = templ.NopComponent 24 | } 25 | ctx = templ.ClearChildren(ctx) 26 | templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 27 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 28 | if !templ_7745c5c3_IsBuffer { 29 | templ_7745c5c3_Buffer = templ.GetBuffer() 30 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 31 | } 32 | templ_7745c5c3_Err = buttonStyles.Render(ctx, templ_7745c5c3_Buffer) 33 | if templ_7745c5c3_Err != nil { 34 | return templ_7745c5c3_Err 35 | } 36 | if !templ_7745c5c3_IsBuffer { 37 | _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer) 38 | } 39 | return templ_7745c5c3_Err 40 | }) 41 | templ_7745c5c3_Err = buttonStylesHandle.Once().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) 42 | if templ_7745c5c3_Err != nil { 43 | return templ_7745c5c3_Err 44 | } 45 | if !templ_7745c5c3_IsBuffer { 46 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 47 | } 48 | return templ_7745c5c3_Err 49 | }) 50 | } 51 | 52 | func Button(name string) templ.Component { 53 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 54 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 55 | if !templ_7745c5c3_IsBuffer { 56 | templ_7745c5c3_Buffer = templ.GetBuffer() 57 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 58 | } 59 | ctx = templ.InitializeContext(ctx) 60 | templ_7745c5c3_Var3 := templ.GetChildren(ctx) 61 | if templ_7745c5c3_Var3 == nil { 62 | templ_7745c5c3_Var3 = templ.NopComponent 63 | } 64 | ctx = templ.ClearChildren(ctx) 65 | templ_7745c5c3_Err = ButtonCSS().Render(ctx, templ_7745c5c3_Buffer) 66 | if templ_7745c5c3_Err != nil { 67 | return templ_7745c5c3_Err 68 | } 69 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 83 | if templ_7745c5c3_Err != nil { 84 | return templ_7745c5c3_Err 85 | } 86 | if !templ_7745c5c3_IsBuffer { 87 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 88 | } 89 | return templ_7745c5c3_Err 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /examples/integration-templ/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/AccentDesign/gcss/examples/integration-templ 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/AccentDesign/gcss v0.1.0 7 | github.com/a-h/templ v0.2.707 8 | ) 9 | 10 | replace github.com/AccentDesign/gcss => ../../ 11 | -------------------------------------------------------------------------------- /examples/integration-templ/go.sum: -------------------------------------------------------------------------------- 1 | github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U= 2 | github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8= 3 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 4 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 5 | -------------------------------------------------------------------------------- /examples/integration-templ/home.html: -------------------------------------------------------------------------------- 1 | Home -------------------------------------------------------------------------------- /examples/integration-templ/home.templ: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | templ Page() { 4 | 5 | 6 | 7 | Home 8 | 9 | 10 | @Button("Button One") 11 | @Button("Button Two") 12 | 13 | 14 | } -------------------------------------------------------------------------------- /examples/integration-templ/home_templ.go: -------------------------------------------------------------------------------- 1 | // Code generated by templ - DO NOT EDIT. 2 | 3 | // templ: version: v0.2.707 4 | package main 5 | 6 | //lint:file-ignore SA4006 This context is only used if a nested component is present. 7 | 8 | import "github.com/a-h/templ" 9 | import "context" 10 | import "io" 11 | import "bytes" 12 | 13 | func Page() templ.Component { 14 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 15 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 16 | if !templ_7745c5c3_IsBuffer { 17 | templ_7745c5c3_Buffer = templ.GetBuffer() 18 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 19 | } 20 | ctx = templ.InitializeContext(ctx) 21 | templ_7745c5c3_Var1 := templ.GetChildren(ctx) 22 | if templ_7745c5c3_Var1 == nil { 23 | templ_7745c5c3_Var1 = templ.NopComponent 24 | } 25 | ctx = templ.ClearChildren(ctx) 26 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Home") 27 | if templ_7745c5c3_Err != nil { 28 | return templ_7745c5c3_Err 29 | } 30 | templ_7745c5c3_Err = Button("Button One").Render(ctx, templ_7745c5c3_Buffer) 31 | if templ_7745c5c3_Err != nil { 32 | return templ_7745c5c3_Err 33 | } 34 | templ_7745c5c3_Err = Button("Button Two").Render(ctx, templ_7745c5c3_Buffer) 35 | if templ_7745c5c3_Err != nil { 36 | return templ_7745c5c3_Err 37 | } 38 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 39 | if templ_7745c5c3_Err != nil { 40 | return templ_7745c5c3_Err 41 | } 42 | if !templ_7745c5c3_IsBuffer { 43 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 44 | } 45 | return templ_7745c5c3_Err 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /examples/integration-templ/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | file, err := os.Create("home.html") 10 | if err != nil { 11 | panic(err) 12 | } 13 | defer file.Close() 14 | 15 | home := Page() 16 | home.Render(context.Background(), file) 17 | } 18 | -------------------------------------------------------------------------------- /examples/integration-templ/styles.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/AccentDesign/gcss" 5 | "github.com/AccentDesign/gcss/variables" 6 | "github.com/a-h/templ" 7 | ) 8 | 9 | var ( 10 | buttonStyles = Stylesheet{ 11 | { 12 | Selector: ".button", 13 | Props: gcss.Props{ 14 | BackgroundColor: variables.Zinc800, 15 | BorderRadius: variables.Size1H, 16 | Color: variables.White, 17 | FontSize: variables.Size4, 18 | PaddingBottom: variables.Size3, 19 | PaddingLeft: variables.Size5, 20 | PaddingRight: variables.Size5, 21 | PaddingTop: variables.Size3, 22 | }, 23 | }, 24 | { 25 | Selector: ".button:hover", 26 | Props: gcss.Props{ 27 | BackgroundColor: variables.Zinc900, 28 | }, 29 | }, 30 | } 31 | buttonStylesHandle = templ.NewOnceHandle() 32 | ) 33 | -------------------------------------------------------------------------------- /examples/integration-templ/stylesheet.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/AccentDesign/gcss" 6 | "io" 7 | ) 8 | 9 | // Stylesheet is a collection of styles. 10 | type Stylesheet []gcss.Style 11 | 12 | // Render writes the CSS representation of the stylesheet to the writer. 13 | // this is to ensure that it implements a templ.Component. 14 | func (ss Stylesheet) Render(ctx context.Context, w io.Writer) error { 15 | //TODO: Get a CSP nonce from the context. 16 | if _, err := io.WriteString(w, ""); err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /examples/media-queries/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/AccentDesign/gcss" 5 | "github.com/AccentDesign/gcss/props" 6 | "io" 7 | "os" 8 | ) 9 | 10 | type ( 11 | Styles []gcss.Style 12 | Media struct { 13 | Query string 14 | Styles Styles 15 | } 16 | Stylesheet struct { 17 | Styles Styles 18 | Medias []Media 19 | } 20 | ) 21 | 22 | // WriteCSS writes the CSS for the media query to the writer 23 | func (m Media) WriteCSS(w io.Writer) error { 24 | if _, err := io.WriteString(w, m.Query); err != nil { 25 | return err 26 | } 27 | if _, err := io.WriteString(w, "{"); err != nil { 28 | return err 29 | } 30 | for _, style := range m.Styles { 31 | if err := style.CSS(w); err != nil { 32 | return err 33 | } 34 | } 35 | if _, err := io.WriteString(w, "}"); err != nil { 36 | return err 37 | } 38 | return nil 39 | } 40 | 41 | // WriteCSS writes the CSS for the stylesheet to the writer 42 | func (ss Stylesheet) WriteCSS(w io.Writer) error { 43 | // Write the base styles first 44 | for _, style := range ss.Styles { 45 | if err := style.CSS(w); err != nil { 46 | return err 47 | } 48 | } 49 | // Write the media queries next 50 | for _, media := range ss.Medias { 51 | if err := media.WriteCSS(w); err != nil { 52 | return err 53 | } 54 | } 55 | return nil 56 | } 57 | 58 | var ( 59 | base = Styles{ 60 | { 61 | Selector: "body", 62 | Props: gcss.Props{ 63 | Margin: props.UnitRaw(0), 64 | }, 65 | }, 66 | } 67 | screen736 = Media{ 68 | Query: "@media only screen and (max-device-width: 736px)", 69 | Styles: Styles{ 70 | { 71 | Selector: "main", 72 | Props: gcss.Props{ 73 | Padding: props.UnitRaw(0), 74 | }, 75 | }, 76 | }, 77 | } 78 | stylesheet = Stylesheet{ 79 | Styles: base, 80 | Medias: []Media{screen736}, 81 | } 82 | ) 83 | 84 | // This is just a basic idea of how you could structure your CSS 85 | // the goal hear is just to wrap the css how you wish with what ever you wish 86 | // construct your stylesheet to suit your needs. 87 | // The end goal is just to call CSS on each style with the object to write to. 88 | func main() { 89 | file, err := os.Create("media.css") 90 | if err != nil { 91 | panic(err) 92 | } 93 | defer file.Close() 94 | 95 | if err := stylesheet.WriteCSS(file); err != nil { 96 | panic(err) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /examples/media-queries/media.css: -------------------------------------------------------------------------------- 1 | body{margin:0;}@media only screen and (max-device-width: 736px){main{padding:0;}} -------------------------------------------------------------------------------- /examples/template-function/home.gohtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/template-function/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AccentDesign/gcss" 6 | "github.com/AccentDesign/gcss/props" 7 | "github.com/AccentDesign/gcss/variables" 8 | "html/template" 9 | "net/http" 10 | "strings" 11 | ) 12 | 13 | type Stylesheet []gcss.Style 14 | 15 | func (ss Stylesheet) CSS() (template.CSS, error) { 16 | var sb strings.Builder 17 | for _, style := range ss { 18 | if err := style.CSS(&sb); err != nil { 19 | return "", err 20 | } 21 | } 22 | return template.CSS(sb.String()), nil 23 | } 24 | 25 | var ( 26 | styles = Stylesheet{ 27 | { 28 | Selector: "html", 29 | Props: gcss.Props{ 30 | FontFamily: props.FontFamilySans, 31 | }, 32 | }, 33 | { 34 | Selector: ".button", 35 | Props: gcss.Props{ 36 | BackgroundColor: variables.Zinc800, 37 | Border: props.Border{ 38 | Width: props.UnitPx(1), 39 | Style: props.BorderStyleSolid, 40 | Color: variables.Zinc900.Alpha(128), 41 | }, 42 | BorderRadius: variables.Size1H, 43 | Color: variables.White, 44 | FontSize: variables.Size4, 45 | PaddingBottom: variables.Size3, 46 | PaddingLeft: variables.Size5, 47 | PaddingRight: variables.Size5, 48 | PaddingTop: variables.Size3, 49 | }, 50 | }, 51 | { 52 | Selector: ".button:hover", 53 | Props: gcss.Props{ 54 | BackgroundColor: variables.Zinc900, 55 | }, 56 | }, 57 | } 58 | ) 59 | 60 | var homeTemplate *template.Template 61 | 62 | func main() { 63 | var err error 64 | homeTemplate, err = template.New("home.gohtml").Funcs(template.FuncMap{ 65 | "css": func() template.CSS { 66 | // todo: handle error 67 | css, _ := styles.CSS() 68 | return css 69 | }, 70 | }).ParseFiles("home.gohtml") 71 | if err != nil { 72 | panic(err) 73 | } 74 | 75 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 76 | w.Header().Set("Content-Type", "text/html") 77 | err := homeTemplate.Execute(w, nil) 78 | if err != nil { 79 | http.Error(w, err.Error(), http.StatusInternalServerError) 80 | } 81 | }) 82 | 83 | fmt.Println("Server started at http://localhost:8080") 84 | 85 | if err := http.ListenAndServe(":8080", nil); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/to-file/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/AccentDesign/gcss" 5 | "github.com/AccentDesign/gcss/props" 6 | "github.com/AccentDesign/gcss/variables" 7 | "os" 8 | ) 9 | 10 | type Stylesheet []gcss.Style 11 | 12 | var styles = Stylesheet{ 13 | { 14 | Selector: "html", 15 | Props: gcss.Props{ 16 | FontFamily: props.FontFamilySans, 17 | }, 18 | }, 19 | { 20 | Selector: ".button", 21 | Props: gcss.Props{ 22 | BackgroundColor: variables.Zinc800, 23 | Border: props.Border{ 24 | Width: props.UnitPx(1), 25 | Style: props.BorderStyleSolid, 26 | Color: variables.Zinc900.Alpha(128), 27 | }, 28 | BorderRadius: variables.Size1H, 29 | Color: variables.White, 30 | FontSize: variables.Size4, 31 | PaddingBottom: variables.Size3, 32 | PaddingLeft: variables.Size5, 33 | PaddingRight: variables.Size5, 34 | PaddingTop: variables.Size3, 35 | }, 36 | }, 37 | { 38 | Selector: ".button:hover", 39 | Props: gcss.Props{ 40 | BackgroundColor: variables.Zinc900, 41 | }, 42 | }, 43 | } 44 | 45 | func main() { 46 | file, err := os.Create("stylesheet.css") 47 | if err != nil { 48 | panic(err) 49 | } 50 | defer file.Close() 51 | 52 | for _, style := range styles { 53 | if err := style.CSS(file); err != nil { 54 | panic(err) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/to-http-handler/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AccentDesign/gcss" 6 | "github.com/AccentDesign/gcss/props" 7 | "github.com/AccentDesign/gcss/variables" 8 | "net/http" 9 | ) 10 | 11 | type Stylesheet []gcss.Style 12 | 13 | var ( 14 | styles = Stylesheet{ 15 | { 16 | Selector: "html", 17 | Props: gcss.Props{ 18 | FontFamily: props.FontFamilySans, 19 | }, 20 | }, 21 | { 22 | Selector: ".button", 23 | Props: gcss.Props{ 24 | BackgroundColor: variables.Zinc800, 25 | Border: props.Border{ 26 | Width: props.UnitPx(1), 27 | Style: props.BorderStyleSolid, 28 | Color: variables.Zinc900.Alpha(128), 29 | }, 30 | BorderRadius: variables.Size1H, 31 | Color: variables.White, 32 | FontSize: variables.Size4, 33 | PaddingBottom: variables.Size3, 34 | PaddingLeft: variables.Size5, 35 | PaddingRight: variables.Size5, 36 | PaddingTop: variables.Size3, 37 | }, 38 | }, 39 | { 40 | Selector: ".button:hover", 41 | Props: gcss.Props{ 42 | BackgroundColor: variables.Zinc900, 43 | }, 44 | }, 45 | } 46 | 47 | html = ` 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ` 58 | ) 59 | 60 | func main() { 61 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 62 | if _, err := fmt.Fprint(w, html); err != nil { 63 | http.Error(w, err.Error(), http.StatusInternalServerError) 64 | } 65 | }) 66 | 67 | http.HandleFunc("/stylesheet.css", func(w http.ResponseWriter, r *http.Request) { 68 | w.Header().Set("Content-Type", "text/css") 69 | for _, style := range styles { 70 | if err := style.CSS(w); err != nil { 71 | http.Error(w, err.Error(), http.StatusInternalServerError) 72 | } 73 | } 74 | }) 75 | 76 | fmt.Println("Server started at http://localhost:8080") 77 | 78 | if err := http.ListenAndServe(":8080", nil); err != nil { 79 | panic(err) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /examples/to-stdout/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/AccentDesign/gcss" 5 | "github.com/AccentDesign/gcss/props" 6 | "github.com/AccentDesign/gcss/variables" 7 | "os" 8 | ) 9 | 10 | type Stylesheet []gcss.Style 11 | 12 | var styles = Stylesheet{ 13 | { 14 | Selector: "html", 15 | Props: gcss.Props{ 16 | FontFamily: props.FontFamilySans, 17 | }, 18 | }, 19 | { 20 | Selector: ".button", 21 | Props: gcss.Props{ 22 | BackgroundColor: variables.Zinc800, 23 | Border: props.Border{ 24 | Width: props.UnitPx(1), 25 | Style: props.BorderStyleSolid, 26 | Color: variables.Zinc900.Alpha(128), 27 | }, 28 | BorderRadius: variables.Size1H, 29 | Color: variables.White, 30 | FontSize: variables.Size4, 31 | PaddingBottom: variables.Size3, 32 | PaddingLeft: variables.Size5, 33 | PaddingRight: variables.Size5, 34 | PaddingTop: variables.Size3, 35 | }, 36 | }, 37 | { 38 | Selector: ".button:hover", 39 | Props: gcss.Props{ 40 | BackgroundColor: variables.Zinc900, 41 | }, 42 | }, 43 | } 44 | 45 | func main() { 46 | for _, style := range styles { 47 | if err := style.CSS(os.Stdout); err != nil { 48 | panic(err) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/AccentDesign/gcss 2 | 3 | go 1.22 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AccentDesign/gcss/3b5aa22d0680d512ed6f3d2ae7807fddd80d9610/go.sum -------------------------------------------------------------------------------- /props/align_content.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type AlignContent string 4 | 5 | const ( 6 | AlignContentNormal AlignContent = "normal" 7 | AlignContentStart AlignContent = "start" 8 | AlignContentCenter AlignContent = "center" 9 | AlignContentEnd AlignContent = "end" 10 | AlignContentFlexStart AlignContent = "flex-start" 11 | AlignContentFlexEnd AlignContent = "flex-end" 12 | AlignContentBaseline AlignContent = "baseline" 13 | AlignContentFirstBaseline AlignContent = "first baseline" 14 | AlignContentLastBaseline AlignContent = "last baseline" 15 | AlignContentSpaceBetween AlignContent = "space-between" 16 | AlignContentSpaceAround AlignContent = "space-around" 17 | AlignContentSpaceEvenly AlignContent = "space-evenly" 18 | AlignContentStretch AlignContent = "stretch" 19 | ) 20 | 21 | func (a AlignContent) String() string { 22 | return string(a) 23 | } 24 | -------------------------------------------------------------------------------- /props/align_items.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type AlignItems string 4 | 5 | const ( 6 | AlignItemsNormal AlignItems = "normal" 7 | AlignItemsStretch AlignItems = "stretch" 8 | AlignItemsStart AlignItems = "start" 9 | AlignItemsCenter AlignItems = "center" 10 | AlignItemsEnd AlignItems = "end" 11 | AlignItemsFlexStart AlignItems = "flex-start" 12 | AlignItemsFlexEnd AlignItems = "flex-end" 13 | AlignItemsSelfStart AlignItems = "self-start" 14 | AlignItemsSelfEnd AlignItems = "self-end" 15 | AlignItemsBaseline AlignItems = "baseline" 16 | ) 17 | 18 | func (a AlignItems) String() string { 19 | return string(a) 20 | } 21 | -------------------------------------------------------------------------------- /props/align_self.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type AlignSelf string 4 | 5 | const ( 6 | AlignSelfAuto AlignSelf = "auto" 7 | AlignSelfNormal AlignSelf = "normal" 8 | AlignSelfStretch AlignSelf = "stretch" 9 | AlignSelfCenter AlignSelf = "center" 10 | AlignSelfStart AlignSelf = "start" 11 | AlignSelfEnd AlignSelf = "end" 12 | AlignSelfFlexStart AlignSelf = "flex-start" 13 | AlignSelfFlexEnd AlignSelf = "flex-end" 14 | AlignSelfSelfStart AlignSelf = "self-start" 15 | AlignSelfSelfEnd AlignSelf = "self-end" 16 | AlignSelfBaseline AlignSelf = "baseline" 17 | ) 18 | 19 | func (a AlignSelf) String() string { 20 | return string(a) 21 | } 22 | -------------------------------------------------------------------------------- /props/appearance.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type Appearance string 4 | 5 | const ( 6 | AppearanceNone Appearance = "none" 7 | AppearanceAuto Appearance = "auto" 8 | AppearanceMenuListButton Appearance = "menulist-button" 9 | AppearanceTextField Appearance = "textfield" 10 | AppearanceInherit Appearance = "inherit" 11 | AppearanceInitial Appearance = "initial" 12 | AppearanceRevert Appearance = "revert" 13 | AppearanceRevertLater Appearance = "revert-later" 14 | AppearanceUnset Appearance = "unset" 15 | AppearanceButton Appearance = "button" 16 | AppearanceCheckbox Appearance = "checkbox" 17 | ) 18 | 19 | func (a Appearance) String() string { 20 | return string(a) 21 | } 22 | -------------------------------------------------------------------------------- /props/background_image.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type BackgroundImage string 9 | 10 | func (b BackgroundImage) String() string { 11 | return string(b) 12 | } 13 | 14 | func BackgroundImages(images ...BackgroundImage) BackgroundImage { 15 | im := make([]string, len(images)) 16 | for i, image := range images { 17 | im[i] = string(image) 18 | } 19 | return BackgroundImage(strings.Join(im, ",")) 20 | } 21 | 22 | func BackgroundImageLinearGradient(segments ...string) BackgroundImage { 23 | return BackgroundImage(fmt.Sprintf("linear-gradient(%s)", strings.Join(segments, ","))) 24 | } 25 | 26 | func BackgroundImageURL(url string) BackgroundImage { 27 | return BackgroundImage(fmt.Sprintf(`url("%s")`, url)) 28 | } 29 | -------------------------------------------------------------------------------- /props/background_position.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type ( 9 | BackgroundPosition string 10 | BackgroundPositionEdge struct { 11 | Position BackgroundPosition 12 | Unit Unit 13 | } 14 | ) 15 | 16 | const ( 17 | BackgroundPositionTop BackgroundPosition = "top" 18 | BackgroundPositionBottom BackgroundPosition = "bottom" 19 | BackgroundPositionLeft BackgroundPosition = "left" 20 | BackgroundPositionRight BackgroundPosition = "right" 21 | BackgroundPositionCenter BackgroundPosition = "center" 22 | BackgroundPositionTopLeft BackgroundPosition = "top left" 23 | BackgroundPositionTopRight BackgroundPosition = "top right" 24 | BackgroundPositionBottomLeft BackgroundPosition = "bottom left" 25 | BackgroundPositionBottomRight BackgroundPosition = "bottom right" 26 | ) 27 | 28 | func (b BackgroundPosition) String() string { 29 | return string(b) 30 | } 31 | 32 | func (b BackgroundPositionEdge) String() string { 33 | return strings.TrimSpace(fmt.Sprintf("%s %s", b.Position, b.Unit)) 34 | } 35 | 36 | func BackgroundPositionXY(x, y Unit) BackgroundPosition { 37 | return BackgroundPosition(fmt.Sprintf("%s %s", x.String(), y.String())) 38 | } 39 | 40 | func BackgroundPositionEdges(edges ...BackgroundPositionEdge) BackgroundPosition { 41 | ed := make([]string, len(edges)) 42 | for i, edge := range edges { 43 | ed[i] = edge.String() 44 | } 45 | return BackgroundPosition(strings.Join(ed, " ")) 46 | } 47 | 48 | func BackgroundPositions(positions ...BackgroundPosition) BackgroundPosition { 49 | po := make([]string, len(positions)) 50 | for i, position := range positions { 51 | po[i] = string(position) 52 | } 53 | return BackgroundPosition(strings.Join(po, ",")) 54 | } 55 | -------------------------------------------------------------------------------- /props/background_repeat.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | import "strings" 4 | 5 | type BackgroundRepeat string 6 | 7 | const ( 8 | BackgroundRepeatRepeatX BackgroundRepeat = "repeat-x" 9 | BackgroundRepeatRepeatY BackgroundRepeat = "repeat-y" 10 | BackgroundRepeatRepeat BackgroundRepeat = "repeat" 11 | BackgroundRepeatSpace BackgroundRepeat = "space" 12 | BackgroundRepeatRound BackgroundRepeat = "round" 13 | BackgroundRepeatNoRepeat BackgroundRepeat = "no-repeat" 14 | ) 15 | 16 | func (b BackgroundRepeat) String() string { 17 | return string(b) 18 | } 19 | 20 | func BackgroundRepeats(repeats ...BackgroundRepeat) BackgroundRepeat { 21 | var r []string 22 | for _, repeat := range repeats { 23 | r = append(r, string(repeat)) 24 | } 25 | return BackgroundRepeat(strings.Join(r, ",")) 26 | } 27 | -------------------------------------------------------------------------------- /props/background_size.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | import "strings" 4 | 5 | type BackgroundSize string 6 | 7 | func (b BackgroundSize) String() string { 8 | return string(b) 9 | } 10 | 11 | func BackgroundSizeWidth(unit Unit) BackgroundSize { 12 | return BackgroundSize(unit.String()) 13 | } 14 | 15 | func BackgroundSizeDimension(width, height Unit) BackgroundSize { 16 | return BackgroundSize(width.String() + " " + height.String()) 17 | } 18 | 19 | func BackgroundSizes(sizes ...BackgroundSize) BackgroundSize { 20 | var s []string 21 | for _, size := range sizes { 22 | s = append(s, string(size)) 23 | } 24 | return BackgroundSize(strings.Join(s, ",")) 25 | } 26 | 27 | func BackgroundSizeCover() BackgroundSize { 28 | return BackgroundSize("cover") 29 | } 30 | 31 | func BackgroundSizeContain() BackgroundSize { 32 | return BackgroundSize("contain") 33 | } 34 | -------------------------------------------------------------------------------- /props/border.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type ( 8 | Border struct { 9 | Width Unit 10 | Style BorderStyle 11 | Color Color 12 | } 13 | ) 14 | 15 | func (b Border) String() string { 16 | var parts []string 17 | 18 | if b.Width != (Unit{}) { 19 | parts = append(parts, b.Width.String()) 20 | } 21 | 22 | if b.Style != "" { 23 | parts = append(parts, b.Style.String()) 24 | } 25 | 26 | if b.Color != (Color{}) { 27 | parts = append(parts, b.Color.String()) 28 | } 29 | 30 | return strings.TrimSpace(strings.Join(parts, " ")) 31 | } 32 | -------------------------------------------------------------------------------- /props/border_collapse.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type BorderCollapse string 4 | 5 | const ( 6 | BorderCollapseCollapse BorderCollapse = "collapse" 7 | BorderCollapseSeparate BorderCollapse = "separate" 8 | ) 9 | 10 | func (b BorderCollapse) String() string { 11 | return string(b) 12 | } 13 | -------------------------------------------------------------------------------- /props/border_style.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type ( 4 | BorderStyle string 5 | ) 6 | 7 | const ( 8 | BorderStyleSolid BorderStyle = "solid" 9 | BorderStyleDashed BorderStyle = "dashed" 10 | BorderStyleDotted BorderStyle = "dotted" 11 | BorderStyleDouble BorderStyle = "double" 12 | BorderStyleHidden BorderStyle = "hidden" 13 | BorderStyleNone BorderStyle = "none" 14 | ) 15 | 16 | func (b BorderStyle) String() string { 17 | return string(b) 18 | } 19 | -------------------------------------------------------------------------------- /props/box_sizing.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type BoxSizing string 4 | 5 | const ( 6 | BoxSizingBorderBox BoxSizing = "border-box" 7 | BoxSizingContentBox BoxSizing = "content-box" 8 | ) 9 | 10 | func (b BoxSizing) String() string { 11 | return string(b) 12 | } 13 | -------------------------------------------------------------------------------- /props/caption_side.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type CaptionSide string 4 | 5 | const ( 6 | CaptionSideTop CaptionSide = "top" 7 | CaptionSideBottom CaptionSide = "bottom" 8 | ) 9 | 10 | func (c CaptionSide) String() string { 11 | return string(c) 12 | } 13 | -------------------------------------------------------------------------------- /props/colors.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | import ( 4 | "fmt" 5 | "image/color" 6 | ) 7 | 8 | type Color struct { 9 | Keyword string 10 | Color color.Color 11 | } 12 | 13 | // String returns a string representation of the color. 14 | // If the color is a keyword, the keyword is returned. 15 | // Otherwise, the color is returned as an rgba string. 16 | func (c Color) String() string { 17 | if c.Keyword != "" { 18 | return c.Keyword 19 | } 20 | r, g, b, a := c.Color.RGBA() 21 | return fmt.Sprintf("rgba(%d,%d,%d,%.2f)", r>>8, g>>8, b>>8, float32(a>>8)/255.0) 22 | } 23 | 24 | // Alpha returns a new color with the alpha channel set to the given value. 25 | func (c Color) Alpha(a uint8) Color { 26 | r, g, b, _ := c.Color.RGBA() 27 | c.Color = color.RGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: a} 28 | return c 29 | } 30 | 31 | // Mix returns a new color that is a mixture of the two colors. 32 | // The percentage parameter is a float between 0 and 1 33 | // and determines how much of the second color to mix with the first color. 34 | func (c Color) Mix(with Color, percentage float64) Color { 35 | if percentage < 0 { 36 | percentage = 0 37 | } 38 | if percentage > 1 { 39 | percentage = 1 40 | } 41 | r1, g1, b1, a1 := c.Color.RGBA() 42 | r2, g2, b2, a2 := with.Color.RGBA() 43 | r := uint8(float64(r1>>8)*(1-percentage) + float64(r2>>8)*percentage) 44 | g := uint8(float64(g1>>8)*(1-percentage) + float64(g2>>8)*percentage) 45 | b := uint8(float64(b1>>8)*(1-percentage) + float64(b2>>8)*percentage) 46 | a := uint8(float64(a1>>8)*(1-percentage) + float64(a2>>8)*percentage) 47 | return Color{Color: color.RGBA{R: r, G: g, B: b, A: a}} 48 | } 49 | 50 | // ColorRGBA returns a new color with the given red, green, blue, and alpha values. 51 | func ColorRGBA(r, g, b, a uint8) Color { 52 | return Color{Color: color.RGBA{R: r, G: g, B: b, A: a}} 53 | } 54 | 55 | func ColorCurrentColor() Color { 56 | return Color{Keyword: "currentColor"} 57 | } 58 | 59 | func ColorInherit() Color { 60 | return Color{Keyword: "inherit"} 61 | } 62 | 63 | func ColorTransparent() Color { 64 | return Color{Keyword: "transparent"} 65 | } 66 | -------------------------------------------------------------------------------- /props/colors_test.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | import ( 4 | "image/color" 5 | "testing" 6 | ) 7 | 8 | func TestColor_String(t *testing.T) { 9 | testCases := map[Color]string{ 10 | {Keyword: "currentColor"}: "currentColor", 11 | {Keyword: "inherit"}: "inherit", 12 | {Color: color.Gray{0}}: "rgba(0,0,0,1.00)", 13 | {Color: color.Gray{255}}: "rgba(255,255,255,1.00)", 14 | {Color: color.Gray16{0}}: "rgba(0,0,0,1.00)", 15 | {Color: color.Gray16{0xffff}}: "rgba(255,255,255,1.00)", 16 | ColorRGBA(62, 131, 248, 255): "rgba(62,131,248,1.00)", 17 | ColorRGBA(0, 0, 0, 0): "rgba(0,0,0,0.00)", 18 | ColorRGBA(255, 255, 255, 255): "rgba(255,255,255,1.00)", 19 | } 20 | for c, expected := range testCases { 21 | if c.String() != expected { 22 | t.Errorf("expected %v, got %v", expected, c.String()) 23 | } 24 | } 25 | } 26 | 27 | func TestColor_Alpha(t *testing.T) { 28 | c := ColorRGBA(62, 131, 248, 255) 29 | expected := ColorRGBA(62, 131, 248, 128) 30 | if c.Alpha(128) != expected { 31 | t.Errorf("expected %v, got %v", expected, c.Alpha(128)) 32 | } 33 | } 34 | 35 | func TestColor_MixToWhite(t *testing.T) { 36 | base := ColorRGBA(62, 131, 248, 255) 37 | with := ColorRGBA(255, 255, 255, 255) 38 | testCases := map[float64]Color{ 39 | -1: ColorRGBA(62, 131, 248, 255), 40 | 0: ColorRGBA(62, 131, 248, 255), 41 | 0.1: ColorRGBA(81, 143, 248, 255), 42 | 0.2: ColorRGBA(100, 155, 249, 255), 43 | 0.3: ColorRGBA(119, 168, 250, 255), 44 | 0.4: ColorRGBA(139, 180, 250, 255), 45 | 0.5: ColorRGBA(158, 193, 251, 255), 46 | 0.6: ColorRGBA(177, 205, 252, 255), 47 | 0.7: ColorRGBA(197, 217, 252, 255), 48 | 0.8: ColorRGBA(216, 230, 253, 255), 49 | 0.9: ColorRGBA(235, 242, 254, 255), 50 | 1: ColorRGBA(255, 255, 255, 255), 51 | 1.1: ColorRGBA(255, 255, 255, 255), 52 | } 53 | for percentage, expected := range testCases { 54 | mixed := base.Mix(with, percentage) 55 | if mixed != expected { 56 | t.Errorf("%v expected %v, got %v", percentage, expected, mixed) 57 | } 58 | } 59 | } 60 | 61 | func TestColor_MixToBlack(t *testing.T) { 62 | base := ColorRGBA(62, 131, 248, 255) 63 | with := ColorRGBA(0, 0, 0, 255) 64 | testCases := map[float64]Color{ 65 | -1: ColorRGBA(62, 131, 248, 255), 66 | 0: ColorRGBA(62, 131, 248, 255), 67 | 0.1: ColorRGBA(55, 117, 223, 255), 68 | 0.2: ColorRGBA(49, 104, 198, 255), 69 | 0.3: ColorRGBA(43, 91, 173, 255), 70 | 0.4: ColorRGBA(37, 78, 148, 255), 71 | 0.5: ColorRGBA(31, 65, 124, 255), 72 | 0.6: ColorRGBA(24, 52, 99, 255), 73 | 0.7: ColorRGBA(18, 39, 74, 255), 74 | 0.8: ColorRGBA(12, 26, 49, 255), 75 | 0.9: ColorRGBA(6, 13, 24, 255), 76 | 1: ColorRGBA(0, 0, 0, 255), 77 | 1.1: ColorRGBA(0, 0, 0, 255), 78 | } 79 | for percentage, expected := range testCases { 80 | mixed := base.Mix(with, percentage) 81 | if mixed != expected { 82 | t.Errorf("%v expected %v, got %v", percentage, expected, mixed) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /props/cursor.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type Cursor string 4 | 5 | const ( 6 | CursorAuto Cursor = "auto" 7 | CursorDefault Cursor = "default" 8 | CursorPointer Cursor = "pointer" 9 | CursorWait Cursor = "wait" 10 | CursorText Cursor = "text" 11 | CursorMove Cursor = "move" 12 | CursorHelp Cursor = "help" 13 | CursorNotAllowed Cursor = "not-allowed" 14 | CursorNone Cursor = "none" 15 | ) 16 | 17 | func (c Cursor) String() string { 18 | return string(c) 19 | } 20 | -------------------------------------------------------------------------------- /props/display.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type Display string 4 | 5 | const ( 6 | DisplayBlock Display = "block" 7 | DisplayInlineBlock Display = "inline-block" 8 | DisplayInline Display = "inline" 9 | DisplayFlex Display = "flex" 10 | DisplayInlineFlex Display = "inline-flex" 11 | DisplayTable Display = "table" 12 | DisplayInlineTable Display = "inline-table" 13 | DisplayTableCaption Display = "table-caption" 14 | DisplayTableCell Display = "table-cell" 15 | DisplayTableColumn Display = "table-column" 16 | DisplayTableColumnGroup Display = "table-column-group" 17 | DisplayTableFooterGroup Display = "table-footer-group" 18 | DisplayTableHeaderGroup Display = "table-header-group" 19 | DisplayTableRowGroup Display = "table-row-group" 20 | DisplayTableRow Display = "table-row" 21 | DisplayFlowRoot Display = "flow-root" 22 | DisplayGrid Display = "grid" 23 | DisplayInlineGrid Display = "inline-grid" 24 | DisplayContents Display = "contents" 25 | DisplayListItem Display = "list-item" 26 | DisplayNone Display = "none" 27 | ) 28 | 29 | func (d Display) String() string { 30 | return string(d) 31 | } 32 | -------------------------------------------------------------------------------- /props/flex_direction.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type FlexDirection string 4 | 5 | const ( 6 | FlexDirectionColumn FlexDirection = "column" 7 | FlexDirectionColumnReverse FlexDirection = "column-reverse" 8 | FlexDirectionRow FlexDirection = "row" 9 | FlexDirectionRowReverse FlexDirection = "row-reverse" 10 | ) 11 | 12 | func (f FlexDirection) String() string { 13 | return string(f) 14 | } 15 | -------------------------------------------------------------------------------- /props/flex_wrap.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type FlexWrap string 4 | 5 | const ( 6 | FlexWrapNoWrap FlexWrap = "nowrap" 7 | FlexWrapWrap FlexWrap = "wrap" 8 | FlexWrapWrapReverse FlexWrap = "wrap-reverse" 9 | ) 10 | 11 | func (f FlexWrap) String() string { 12 | return string(f) 13 | } 14 | -------------------------------------------------------------------------------- /props/float.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type Float string 4 | 5 | const ( 6 | FloatNone Float = "none" 7 | FloatLeft Float = "left" 8 | FloatRight Float = "right" 9 | FloatInlineStart Float = "inline-start" 10 | FloatInlineEnd Float = "inline-end" 11 | ) 12 | 13 | func (f Float) String() string { 14 | return string(f) 15 | } 16 | -------------------------------------------------------------------------------- /props/font_family.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type FontFamily string 4 | 5 | const ( 6 | FontFamilySans FontFamily = "ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"" 7 | FontFamilySerif FontFamily = "ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif" 8 | FontFamilyMono FontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace" 9 | ) 10 | 11 | func (f FontFamily) String() string { 12 | return string(f) 13 | } 14 | -------------------------------------------------------------------------------- /props/font_style.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type FontStyle string 4 | 5 | const ( 6 | FontStyleNormal FontStyle = "normal" 7 | FontStyleItalic FontStyle = "italic" 8 | ) 9 | 10 | func (f FontStyle) String() string { 11 | return string(f) 12 | } 13 | -------------------------------------------------------------------------------- /props/font_weight.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type FontWeight string 4 | 5 | const ( 6 | FontWeightThin FontWeight = "100" 7 | FontWeightExtraLight FontWeight = "200" 8 | FontWeightLight FontWeight = "300" 9 | FontWeightNormal FontWeight = "400" 10 | FontWeightMedium FontWeight = "500" 11 | FontWeightSemiBold FontWeight = "600" 12 | FontWeightBold FontWeight = "700" 13 | FontWeightExtraBold FontWeight = "800" 14 | FontWeightBlack FontWeight = "900" 15 | ) 16 | 17 | func (f FontWeight) String() string { 18 | return string(f) 19 | } 20 | -------------------------------------------------------------------------------- /props/justify_content.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type JustifyContent string 4 | 5 | const ( 6 | JustifyContentNormal JustifyContent = "normal" 7 | JustifyContentCenter JustifyContent = "center" 8 | JustifyContentStart JustifyContent = "start" 9 | JustifyContentEnd JustifyContent = "end" 10 | JustifyContentFlexStart JustifyContent = "flex-start" 11 | JustifyContentFlexEnd JustifyContent = "flex-end" 12 | JustifyContentLeft JustifyContent = "left" 13 | JustifyContentRight JustifyContent = "right" 14 | JustifyContentSpaceBetween JustifyContent = "space-between" 15 | JustifyContentSpaceAround JustifyContent = "space-around" 16 | JustifyContentSpaceEvenly JustifyContent = "space-evenly" 17 | JustifyContentStretch JustifyContent = "stretch" 18 | ) 19 | 20 | func (j JustifyContent) String() string { 21 | return string(j) 22 | } 23 | -------------------------------------------------------------------------------- /props/justify_items.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type JustifyItems string 4 | 5 | const ( 6 | JustifyItemsNormal JustifyItems = "normal" 7 | JustifyItemsStretch JustifyItems = "stretch" 8 | JustifyItemsCenter JustifyItems = "center" 9 | JustifyItemsStart JustifyItems = "start" 10 | JustifyItemsEnd JustifyItems = "end" 11 | JustifyItemsFlexStart JustifyItems = "flex-start" 12 | JustifyItemsFlexEnd JustifyItems = "flex-end" 13 | JustifyItemsSelfStart JustifyItems = "self-start" 14 | JustifyItemsSelfEnd JustifyItems = "self-end" 15 | JustifyItemsLeft JustifyItems = "left" 16 | JustifyItemsRight JustifyItems = "right" 17 | JustifyItemsBaseline JustifyItems = "baseline" 18 | ) 19 | 20 | func (j JustifyItems) String() string { 21 | return string(j) 22 | } 23 | -------------------------------------------------------------------------------- /props/justify_self.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type JustifySelf string 4 | 5 | const ( 6 | JustifySelfAuto JustifySelf = "auto" 7 | JustifySelfNormal JustifySelf = "normal" 8 | JustifySelfStretch JustifySelf = "stretch" 9 | JustifySelfCenter JustifySelf = "center" 10 | JustifySelfStart JustifySelf = "start" 11 | JustifySelfEnd JustifySelf = "end" 12 | JustifySelfFlexStart JustifySelf = "flex-start" 13 | JustifySelfFlexEnd JustifySelf = "flex-end" 14 | JustifySelfSelfStart JustifySelf = "self-start" 15 | JustifySelfSelfEnd JustifySelf = "self-end" 16 | JustifySelfLeft JustifySelf = "left" 17 | JustifySelfRight JustifySelf = "right" 18 | JustifySelfBaseline JustifySelf = "baseline" 19 | ) 20 | 21 | func (j JustifySelf) String() string { 22 | return string(j) 23 | } 24 | -------------------------------------------------------------------------------- /props/list_style_position.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type ListStylePosition string 4 | 5 | const ( 6 | ListStylePositionInside ListStylePosition = "inside" 7 | ListStylePositionOutside ListStylePosition = "outside" 8 | ) 9 | 10 | func (l ListStylePosition) String() string { 11 | return string(l) 12 | } 13 | -------------------------------------------------------------------------------- /props/list_style_type.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type ListStyleType string 4 | 5 | const ( 6 | ListStyleTypeNone ListStyleType = "none" 7 | ListStyleTypeDisc ListStyleType = "disc" 8 | ListStyleTypeDecimal ListStyleType = "decimal" 9 | ) 10 | 11 | func (l ListStyleType) String() string { 12 | return string(l) 13 | } 14 | -------------------------------------------------------------------------------- /props/overflow.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type Overflow string 4 | 5 | const ( 6 | OverflowVisible Overflow = "visible" 7 | OverflowHidden Overflow = "hidden" 8 | OverflowScroll Overflow = "scroll" 9 | OverflowAuto Overflow = "auto" 10 | OverflowClip Overflow = "clip" 11 | ) 12 | 13 | func (o Overflow) String() string { 14 | return string(o) 15 | } 16 | -------------------------------------------------------------------------------- /props/position.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type Position string 4 | 5 | const ( 6 | PositionStatic Position = "static" 7 | PositionRelative Position = "relative" 8 | PositionAbsolute Position = "absolute" 9 | PositionFixed Position = "fixed" 10 | PositionSticky Position = "sticky" 11 | ) 12 | 13 | func (p Position) String() string { 14 | return string(p) 15 | } 16 | -------------------------------------------------------------------------------- /props/print_color_adjust.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type PrintColorAdjust string 4 | 5 | const ( 6 | PrintColorAdjustEconomy PrintColorAdjust = "economy" 7 | PrintColorAdjustExact PrintColorAdjust = "exact" 8 | ) 9 | 10 | func (c PrintColorAdjust) String() string { 11 | return string(c) 12 | } 13 | -------------------------------------------------------------------------------- /props/text_align.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type TextAlign string 4 | 5 | const ( 6 | TextAlignLeft TextAlign = "left" 7 | TextAlignRight TextAlign = "right" 8 | TextAlignCenter TextAlign = "center" 9 | TextAlignJustify TextAlign = "justify" 10 | TextAlignStart TextAlign = "start" 11 | TextAlignEnd TextAlign = "end" 12 | ) 13 | 14 | func (t TextAlign) String() string { 15 | return string(t) 16 | } 17 | -------------------------------------------------------------------------------- /props/text_decoration_line.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type TextDecorationLine string 4 | 5 | const ( 6 | TextDecorationLineNone TextDecorationLine = "none" 7 | TextDecorationLineUnderline TextDecorationLine = "underline" 8 | TextDecorationLineOverline TextDecorationLine = "overline" 9 | TextDecorationLineLineThrough TextDecorationLine = "line-through" 10 | ) 11 | 12 | func (t TextDecorationLine) String() string { 13 | return string(t) 14 | } 15 | -------------------------------------------------------------------------------- /props/text_decoration_style.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type TextDecorationStyle string 4 | 5 | const ( 6 | TextDecorationStyleSolid TextDecorationStyle = "solid" 7 | TextDecorationStyleDouble TextDecorationStyle = "double" 8 | TextDecorationStyleDotted TextDecorationStyle = "dotted" 9 | TextDecorationStyleDashed TextDecorationStyle = "dashed" 10 | TextDecorationStyleWavy TextDecorationStyle = "wavy" 11 | ) 12 | 13 | func (t TextDecorationStyle) String() string { 14 | return string(t) 15 | } 16 | -------------------------------------------------------------------------------- /props/text_overflow.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type TextOverflow string 4 | 5 | const ( 6 | TextOverflowClip TextOverflow = "clip" 7 | TextOverflowEllipsis TextOverflow = "ellipsis" 8 | ) 9 | 10 | func (t TextOverflow) String() string { 11 | return string(t) 12 | } 13 | -------------------------------------------------------------------------------- /props/text_transform.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type TextTransform string 4 | 5 | const ( 6 | TextTransformNone TextTransform = "none" 7 | TextTransformCapitalize TextTransform = "capitalize" 8 | TextTransformUppercase TextTransform = "uppercase" 9 | TextTransformLowercase TextTransform = "lowercase" 10 | ) 11 | 12 | func (t TextTransform) String() string { 13 | return string(t) 14 | } 15 | -------------------------------------------------------------------------------- /props/text_wrap.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type TextWrap string 4 | 5 | const ( 6 | TextWrapWrap TextWrap = "wrap" 7 | TextWrapNoWrap TextWrap = "nowrap" 8 | TextWrapBalance TextWrap = "balance" 9 | ) 10 | 11 | func (t TextWrap) String() string { 12 | return string(t) 13 | } 14 | -------------------------------------------------------------------------------- /props/unit.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | import "fmt" 4 | 5 | type ( 6 | UnitType int 7 | Unit struct { 8 | Size interface{} 9 | Type UnitType 10 | } 11 | ) 12 | 13 | const ( 14 | _ UnitType = iota 15 | UnitTypeRaw 16 | UnitTypePx 17 | UnitTypePercent 18 | UnitTypeRem 19 | UnitTypeEm 20 | UnitTypeVh 21 | UnitTypeVw 22 | UnitTypeAuto 23 | UnitTypeInherit 24 | UnitTypeInitial 25 | ) 26 | 27 | func (u Unit) String() string { 28 | switch u.Type { 29 | case UnitTypeRaw: 30 | return fmt.Sprintf("%v", u.Size) 31 | case UnitTypePx, UnitTypePercent, UnitTypeRem, UnitTypeEm, UnitTypeVh, UnitTypeVw: 32 | return formatSize(u) 33 | case UnitTypeAuto: 34 | return "auto" 35 | case UnitTypeInherit: 36 | return "inherit" 37 | case UnitTypeInitial: 38 | return "initial" 39 | } 40 | return "" 41 | } 42 | 43 | func formatSize(u Unit) string { 44 | format := getFormat(u.Type) 45 | switch v := u.Size.(type) { 46 | case int: 47 | return fmt.Sprintf(format, v) 48 | case float64: 49 | return fmt.Sprintf(format, v) 50 | default: 51 | return "" 52 | } 53 | } 54 | 55 | func getFormat(unitType UnitType) string { 56 | switch unitType { 57 | case UnitTypePx: 58 | return "%dpx" 59 | case UnitTypePercent: 60 | return "%.2f%%" 61 | case UnitTypeRem: 62 | return "%.3frem" 63 | case UnitTypeEm: 64 | return "%.3fem" 65 | case UnitTypeVh: 66 | return "%dvh" 67 | case UnitTypeVw: 68 | return "%dvw" 69 | default: 70 | return "" 71 | } 72 | } 73 | 74 | func UnitRaw(size interface{}) Unit { 75 | return Unit{Size: size, Type: UnitTypeRaw} 76 | } 77 | 78 | func UnitPx(size int) Unit { 79 | return Unit{Size: size, Type: UnitTypePx} 80 | } 81 | 82 | func UnitPercent(size float64) Unit { 83 | return Unit{Size: size, Type: UnitTypePercent} 84 | } 85 | 86 | func UnitRem(size float64) Unit { 87 | return Unit{Size: size, Type: UnitTypeRem} 88 | } 89 | 90 | func UnitEm(size float64) Unit { 91 | return Unit{Size: size, Type: UnitTypeEm} 92 | } 93 | 94 | func UnitVh(size int) Unit { 95 | return Unit{Size: size, Type: UnitTypeVh} 96 | } 97 | 98 | func UnitVw(size int) Unit { 99 | return Unit{Size: size, Type: UnitTypeVw} 100 | } 101 | 102 | func UnitAuto() Unit { 103 | return Unit{Type: UnitTypeAuto} 104 | } 105 | 106 | func UnitInherit() Unit { 107 | return Unit{Type: UnitTypeInherit} 108 | } 109 | 110 | func UnitInitial() Unit { 111 | return Unit{Type: UnitTypeInitial} 112 | } 113 | -------------------------------------------------------------------------------- /props/verticle_align.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type VerticalAlign string 4 | 5 | const ( 6 | VerticalAlignBaseline VerticalAlign = "baseline" 7 | VerticalAlignSub VerticalAlign = "sub" 8 | VerticalAlignSuper VerticalAlign = "super" 9 | VerticalAlignTextTop VerticalAlign = "text-top" 10 | VerticalAlignTextBottom VerticalAlign = "text-bottom" 11 | VerticalAlignMiddle VerticalAlign = "middle" 12 | VerticalAlignTop VerticalAlign = "top" 13 | VerticalAlignBottom VerticalAlign = "bottom" 14 | ) 15 | 16 | func (v VerticalAlign) String() string { 17 | return string(v) 18 | } 19 | -------------------------------------------------------------------------------- /props/visibility.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type Visibility string 4 | 5 | const ( 6 | VisibilityVisible Visibility = "visible" 7 | VisibilityHidden Visibility = "hidden" 8 | VisibilityCollapse Visibility = "collapse" 9 | ) 10 | 11 | func (v Visibility) String() string { 12 | return string(v) 13 | } 14 | -------------------------------------------------------------------------------- /props/white_space.go: -------------------------------------------------------------------------------- 1 | package props 2 | 3 | type WhiteSpace string 4 | 5 | const ( 6 | WhiteSpaceNormal WhiteSpace = "normal" 7 | WhiteSpaceNowrap WhiteSpace = "nowrap" 8 | WhiteSpacePre WhiteSpace = "pre" 9 | WhiteSpacePreLine WhiteSpace = "pre-line" 10 | WhiteSpacePreWrap WhiteSpace = "pre-wrap" 11 | ) 12 | 13 | func (w WhiteSpace) String() string { 14 | return string(w) 15 | } 16 | -------------------------------------------------------------------------------- /style.go: -------------------------------------------------------------------------------- 1 | package gcss 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/AccentDesign/gcss/props" 7 | "io" 8 | "reflect" 9 | ) 10 | 11 | type ( 12 | // Props represents the standard CSS properties that can be applied to a CSS rule. 13 | Props struct { 14 | AlignContent props.AlignContent `css:"align-content"` 15 | AlignItems props.AlignItems `css:"align-items"` 16 | AlignSelf props.AlignSelf `css:"align-self"` 17 | Appearance props.Appearance `css:"appearance"` 18 | BackgroundColor props.Color `css:"background-color"` 19 | BackgroundImage props.BackgroundImage `css:"background-image"` 20 | BackgroundPosition props.BackgroundPosition `css:"background-position"` 21 | BackgroundRepeat props.BackgroundRepeat `css:"background-repeat"` 22 | BackgroundSize props.BackgroundSize `css:"background-size"` 23 | Border props.Border `css:"border"` 24 | BorderBottom props.Border `css:"border-bottom"` 25 | BorderBottomLeftRadius props.Unit `css:"border-bottom-left-radius"` 26 | BorderBottomRightRadius props.Unit `css:"border-bottom-right-radius"` 27 | BorderCollapse props.BorderCollapse `css:"border-collapse"` 28 | BorderColor props.Color `css:"border-color"` 29 | BorderLeft props.Border `css:"border-left"` 30 | BorderRadius props.Unit `css:"border-radius"` 31 | BorderRight props.Border `css:"border-right"` 32 | BorderStyle props.BorderStyle `css:"border-style"` 33 | BorderTop props.Border `css:"border-top"` 34 | BorderTopLeftRadius props.Unit `css:"border-top-left-radius"` 35 | BorderTopRightRadius props.Unit `css:"border-top-right-radius"` 36 | BorderWidth props.Unit `css:"border-width"` 37 | Bottom props.Unit `css:"bottom"` 38 | BoxSizing props.BoxSizing `css:"box-sizing"` 39 | CaptionSide props.CaptionSide `css:"caption-side"` 40 | Color props.Color `css:"color"` 41 | ColumnGap props.Unit `css:"column-gap"` 42 | Cursor props.Cursor `css:"cursor"` 43 | Display props.Display `css:"display"` 44 | FlexBasis props.Unit `css:"flex-basis"` 45 | FlexDirection props.FlexDirection `css:"flex-direction"` 46 | FlexGrow props.Unit `css:"flex-grow"` 47 | FlexShrink props.Unit `css:"flex-shrink"` 48 | FlexWrap props.FlexWrap `css:"flex-wrap"` 49 | Float props.Float `css:"float"` 50 | FontFamily props.FontFamily `css:"font-family"` 51 | FontSize props.Unit `css:"font-size"` 52 | FontStyle props.FontStyle `css:"font-style"` 53 | FontWeight props.FontWeight `css:"font-weight"` 54 | Gap props.Unit `css:"gap"` 55 | Height props.Unit `css:"height"` 56 | JustifyContent props.JustifyContent `css:"justify-content"` 57 | JustifyItems props.JustifyItems `css:"justify-items"` 58 | JustifySelf props.JustifySelf `css:"justify-self"` 59 | Left props.Unit `css:"left"` 60 | LineHeight props.Unit `css:"line-height"` 61 | ListStylePosition props.ListStylePosition `css:"list-style-position"` 62 | ListStyleType props.ListStyleType `css:"list-style-type"` 63 | Margin props.Unit `css:"margin"` 64 | MarginBottom props.Unit `css:"margin-bottom"` 65 | MarginLeft props.Unit `css:"margin-left"` 66 | MarginRight props.Unit `css:"margin-right"` 67 | MarginTop props.Unit `css:"margin-top"` 68 | MaxHeight props.Unit `css:"max-height"` 69 | MaxWidth props.Unit `css:"max-width"` 70 | MinHeight props.Unit `css:"min-height"` 71 | MinWidth props.Unit `css:"min-width"` 72 | Opacity props.Unit `css:"opacity"` 73 | Overflow props.Overflow `css:"overflow"` 74 | OverflowX props.Overflow `css:"overflow-x"` 75 | OverflowY props.Overflow `css:"overflow-y"` 76 | Padding props.Unit `css:"padding"` 77 | PaddingBottom props.Unit `css:"padding-bottom"` 78 | PaddingLeft props.Unit `css:"padding-left"` 79 | PaddingRight props.Unit `css:"padding-right"` 80 | PaddingTop props.Unit `css:"padding-top"` 81 | Position props.Position `css:"position"` 82 | PrintColorAdjust props.PrintColorAdjust `css:"print-color-adjust"` 83 | Right props.Unit `css:"right"` 84 | RowGap props.Unit `css:"row-gap"` 85 | TextAlign props.TextAlign `css:"text-align"` 86 | TextDecorationColor props.Color `css:"text-decoration-color"` 87 | TextDecorationLine props.TextDecorationLine `css:"text-decoration-line"` 88 | TextDecorationStyle props.TextDecorationStyle `css:"text-decoration-style"` 89 | TextDecorationThickness props.Unit `css:"text-decoration-thickness"` 90 | TextIndent props.Unit `css:"text-indent"` 91 | TextOverflow props.TextOverflow `css:"text-overflow"` 92 | TextTransform props.TextTransform `css:"text-transform"` 93 | TextUnderlineOffset props.Unit `css:"text-underline-offset"` 94 | TextWrap props.TextWrap `css:"text-wrap"` 95 | Top props.Unit `css:"top"` 96 | VerticalAlign props.VerticalAlign `css:"vertical-align"` 97 | WhiteSpace props.WhiteSpace `css:"white-space"` 98 | Width props.Unit `css:"width"` 99 | Visibility props.Visibility `css:"visibility"` 100 | ZIndex props.Unit `css:"z-index"` 101 | } 102 | // CustomProp represents an additional CSS property that is not covered by the Props struct. 103 | CustomProp struct { 104 | Attr, Value string 105 | } 106 | // Style represents a CSS style rule. 107 | Style struct { 108 | // Selector is the CSS selector to which the properties will be applied. 109 | // It can be any valid CSS selector like class, id, element type, etc. 110 | Selector string 111 | 112 | // Props contains the standard CSS properties that will be applied to the selector. 113 | // These properties are represented by the Props struct and are strongly typed. 114 | Props Props 115 | 116 | // CustomProps contains any additional CSS properties that are not covered by the Props struct. 117 | // These properties are directly added to the CSS rule as is. 118 | CustomProps []CustomProp 119 | } 120 | ) 121 | 122 | // CSS writes the CSS representation of the props to the writer. 123 | func (p *Props) CSS(w io.Writer) error { 124 | value := reflect.ValueOf(*p) 125 | typ := reflect.TypeOf(*p) 126 | 127 | // Iterate over the fields of the Props struct and write the CSS properties to the writer. 128 | for i := 0; i < value.NumField(); i++ { 129 | fieldValue := value.Field(i) 130 | fieldType := typ.Field(i) 131 | 132 | if fieldValue.IsZero() { 133 | continue 134 | } 135 | 136 | fieldName := fieldType.Tag.Get("css") 137 | 138 | if v, ok := fieldValue.Interface().(fmt.Stringer); ok { 139 | if _, err := fmt.Fprintf(w, "%s:%s;", fieldName, v.String()); err != nil { 140 | return err 141 | } 142 | } 143 | } 144 | 145 | return nil 146 | } 147 | 148 | // CSS writes the CSS representation of the style to the writer. 149 | func (s *Style) CSS(w io.Writer) error { 150 | var buf bytes.Buffer 151 | 152 | // Write the standard properties to the writer. 153 | if err := s.Props.CSS(&buf); err != nil { 154 | return err 155 | } 156 | 157 | // Write the custom properties to the writer. 158 | for _, prop := range s.CustomProps { 159 | if _, err := fmt.Fprintf(&buf, "%s:%s;", prop.Attr, prop.Value); err != nil { 160 | return err 161 | } 162 | } 163 | 164 | if buf.Len() > 0 { 165 | _, err := fmt.Fprintf(w, "%s{%s}", s.Selector, buf.String()) 166 | return err 167 | } 168 | 169 | return nil 170 | } 171 | -------------------------------------------------------------------------------- /style_test.go: -------------------------------------------------------------------------------- 1 | package gcss 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/AccentDesign/gcss/props" 7 | "image/color" 8 | "testing" 9 | ) 10 | 11 | func runTest(t *testing.T, st *Style, expected string) { 12 | var buf bytes.Buffer 13 | err := st.CSS(&buf) 14 | if err != nil { 15 | t.Errorf("unexpected error: %v", err) 16 | } 17 | 18 | if buf.String() != expected { 19 | t.Errorf("expected %q, got %q", expected, buf.String()) 20 | } 21 | } 22 | 23 | func TestProps_CSS(t *testing.T) { 24 | testCases := map[Props]string{ 25 | {}: "", 26 | {BackgroundColor: props.ColorRGBA(0, 0, 0, 255)}: "background-color:rgba(0,0,0,1.00);", 27 | {Margin: props.UnitPx(10), Padding: props.UnitPx(10)}: "margin:10px;padding:10px;", 28 | } 29 | for prop, expected := range testCases { 30 | var buf bytes.Buffer 31 | err := prop.CSS(&buf) 32 | if err != nil { 33 | t.Errorf("unexpected error: %v", err) 34 | } 35 | 36 | if buf.String() != expected { 37 | t.Errorf("expected %q, got %q", expected, buf.String()) 38 | } 39 | } 40 | } 41 | 42 | func TestStyle_Empty(t *testing.T) { 43 | st := &Style{Selector: ".test", Props: Props{}} 44 | css := "" 45 | runTest(t, st, css) 46 | } 47 | 48 | func TestStyle_MultipleProps(t *testing.T) { 49 | st := &Style{Selector: ".test", Props: Props{ 50 | BackgroundColor: props.ColorRGBA(0, 0, 0, 255), 51 | Height: props.UnitPx(100), 52 | Width: props.UnitPx(100), 53 | }} 54 | css := ".test{background-color:rgba(0,0,0,1.00);height:100px;width:100px;}" 55 | runTest(t, st, css) 56 | } 57 | 58 | func TestStyle_CustomProps(t *testing.T) { 59 | st := &Style{ 60 | Selector: ".test", 61 | CustomProps: []CustomProp{ 62 | {Attr: "--color", Value: "red"}, 63 | {Attr: "background-color", Value: "var(--color)"}, 64 | }, 65 | } 66 | css := ".test{--color:red;background-color:var(--color);}" 67 | runTest(t, st, css) 68 | } 69 | 70 | func TestStyle_AlignContent(t *testing.T) { 71 | testCases := map[props.AlignContent]string{ 72 | props.AlignContentNormal: "normal", 73 | props.AlignContentStart: "start", 74 | props.AlignContentCenter: "center", 75 | props.AlignContentEnd: "end", 76 | props.AlignContentFlexStart: "flex-start", 77 | props.AlignContentFlexEnd: "flex-end", 78 | props.AlignContentBaseline: "baseline", 79 | props.AlignContentFirstBaseline: "first baseline", 80 | props.AlignContentLastBaseline: "last baseline", 81 | props.AlignContentSpaceBetween: "space-between", 82 | props.AlignContentSpaceAround: "space-around", 83 | props.AlignContentSpaceEvenly: "space-evenly", 84 | props.AlignContentStretch: "stretch", 85 | props.AlignContent("inherit"): "inherit", 86 | } 87 | 88 | for prop, expected := range testCases { 89 | st := &Style{Selector: ".test", Props: Props{AlignContent: prop}} 90 | css := fmt.Sprintf(".test{align-content:%s;}", expected) 91 | runTest(t, st, css) 92 | } 93 | } 94 | 95 | func TestStyle_AlignItems(t *testing.T) { 96 | testCases := map[props.AlignItems]string{ 97 | props.AlignItemsNormal: "normal", 98 | props.AlignItemsStart: "start", 99 | props.AlignItemsCenter: "center", 100 | props.AlignItemsEnd: "end", 101 | props.AlignItemsFlexStart: "flex-start", 102 | props.AlignItemsFlexEnd: "flex-end", 103 | props.AlignItemsSelfStart: "self-start", 104 | props.AlignItemsSelfEnd: "self-end", 105 | props.AlignItemsBaseline: "baseline", 106 | props.AlignItemsStretch: "stretch", 107 | props.AlignItems("inherit"): "inherit", 108 | } 109 | 110 | for prop, expected := range testCases { 111 | st := &Style{Selector: ".test", Props: Props{AlignItems: prop}} 112 | css := fmt.Sprintf(".test{align-items:%s;}", expected) 113 | runTest(t, st, css) 114 | } 115 | } 116 | 117 | func TestStyle_AlignSelf(t *testing.T) { 118 | testCases := map[props.AlignSelf]string{ 119 | props.AlignSelfAuto: "auto", 120 | props.AlignSelfNormal: "normal", 121 | props.AlignSelfStretch: "stretch", 122 | props.AlignSelfCenter: "center", 123 | props.AlignSelfStart: "start", 124 | props.AlignSelfEnd: "end", 125 | props.AlignSelfFlexStart: "flex-start", 126 | props.AlignSelfFlexEnd: "flex-end", 127 | props.AlignSelfSelfStart: "self-start", 128 | props.AlignSelfSelfEnd: "self-end", 129 | props.AlignSelfBaseline: "baseline", 130 | props.AlignSelf("inherit"): "inherit", 131 | } 132 | 133 | for prop, expected := range testCases { 134 | st := &Style{Selector: ".test", Props: Props{AlignSelf: prop}} 135 | css := fmt.Sprintf(".test{align-self:%s;}", expected) 136 | runTest(t, st, css) 137 | } 138 | } 139 | 140 | func TestStyle_Appearance(t *testing.T) { 141 | testCases := map[props.Appearance]string{ 142 | props.AppearanceNone: "none", 143 | props.AppearanceAuto: "auto", 144 | props.AppearanceMenuListButton: "menulist-button", 145 | props.AppearanceTextField: "textfield", 146 | props.AppearanceInherit: "inherit", 147 | props.AppearanceInitial: "initial", 148 | props.AppearanceRevert: "revert", 149 | props.AppearanceRevertLater: "revert-later", 150 | props.AppearanceUnset: "unset", 151 | props.AppearanceButton: "button", 152 | props.AppearanceCheckbox: "checkbox", 153 | } 154 | 155 | for prop, expected := range testCases { 156 | st := &Style{Selector: ".test", Props: Props{Appearance: prop}} 157 | css := fmt.Sprintf(".test{appearance:%s;}", expected) 158 | runTest(t, st, css) 159 | } 160 | } 161 | 162 | func TestStyle_BackgroundColor(t *testing.T) { 163 | testCases := map[props.Color]string{ 164 | props.ColorRGBA(0, 0, 0, 255): "rgba(0,0,0,1.00)", 165 | props.ColorRGBA(255, 255, 255, 230): "rgba(255,255,255,0.90)", 166 | props.ColorRGBA(255, 255, 255, 255).Alpha(230): "rgba(255,255,255,0.90)", 167 | props.ColorCurrentColor(): "currentColor", 168 | props.ColorInherit(): "inherit", 169 | props.ColorTransparent(): "transparent", 170 | {Keyword: "#efefef"}: "#efefef", 171 | {Keyword: "red"}: "red", 172 | {Color: color.Gray16{0}}: "rgba(0,0,0,1.00)", 173 | } 174 | 175 | for prop, expected := range testCases { 176 | st := &Style{Selector: ".test", Props: Props{BackgroundColor: prop}} 177 | css := fmt.Sprintf(".test{background-color:%s;}", expected) 178 | runTest(t, st, css) 179 | } 180 | } 181 | 182 | func TestStyle_BackgroundImage(t *testing.T) { 183 | testCases := map[props.BackgroundImage]string{ 184 | props.BackgroundImageURL("image.jpg"): `url("image.jpg")`, 185 | props.BackgroundImageLinearGradient("red", "blue"): "linear-gradient(red,blue)", 186 | props.BackgroundImage("inherit"): "inherit", 187 | props.BackgroundImages( 188 | props.BackgroundImageLinearGradient("red", "blue"), 189 | props.BackgroundImageURL("image.jpg"), 190 | ): `linear-gradient(red,blue),url("image.jpg")`, 191 | } 192 | 193 | for prop, expected := range testCases { 194 | st := &Style{Selector: ".test", Props: Props{BackgroundImage: prop}} 195 | css := fmt.Sprintf(".test{background-image:%s;}", expected) 196 | runTest(t, st, css) 197 | } 198 | } 199 | 200 | func TestStyle_BackgroundPosition(t *testing.T) { 201 | testCases := map[props.BackgroundPosition]string{ 202 | props.BackgroundPositionXY(props.UnitPx(10), props.UnitPx(20)): "10px 20px", 203 | props.BackgroundPositionEdges( 204 | props.BackgroundPositionEdge{Position: props.BackgroundPositionTop, Unit: props.UnitPx(10)}, 205 | props.BackgroundPositionEdge{Position: props.BackgroundPositionRight, Unit: props.UnitPx(20)}, 206 | ): "top 10px right 20px", 207 | props.BackgroundPositionTop: "top", 208 | props.BackgroundPositionBottom: "bottom", 209 | props.BackgroundPositionLeft: "left", 210 | props.BackgroundPositionRight: "right", 211 | props.BackgroundPositionCenter: "center", 212 | props.BackgroundPositionTopLeft: "top left", 213 | props.BackgroundPositionTopRight: "top right", 214 | props.BackgroundPositionBottomLeft: "bottom left", 215 | props.BackgroundPositionBottomRight: "bottom right", 216 | props.BackgroundPositions( 217 | props.BackgroundPositionTop, 218 | props.BackgroundPositionLeft, 219 | ): "top,left", 220 | props.BackgroundPosition("inherit"): "inherit", 221 | } 222 | 223 | for prop, expected := range testCases { 224 | st := &Style{Selector: ".test", Props: Props{BackgroundPosition: prop}} 225 | css := fmt.Sprintf(".test{background-position:%s;}", expected) 226 | runTest(t, st, css) 227 | } 228 | } 229 | 230 | func TestStyle_BackgroundRepeat(t *testing.T) { 231 | testCases := map[props.BackgroundRepeat]string{ 232 | props.BackgroundRepeatRepeat: "repeat", 233 | props.BackgroundRepeatRepeatX: "repeat-x", 234 | props.BackgroundRepeatRepeatY: "repeat-y", 235 | props.BackgroundRepeatNoRepeat: "no-repeat", 236 | props.BackgroundRepeatSpace: "space", 237 | props.BackgroundRepeatRound: "round", 238 | props.BackgroundRepeats( 239 | props.BackgroundRepeatRepeat, 240 | props.BackgroundRepeatNoRepeat, 241 | ): "repeat,no-repeat", 242 | props.BackgroundRepeat("inherit"): "inherit", 243 | } 244 | 245 | for prop, expected := range testCases { 246 | st := &Style{Selector: ".test", Props: Props{BackgroundRepeat: prop}} 247 | css := fmt.Sprintf(".test{background-repeat:%s;}", expected) 248 | runTest(t, st, css) 249 | } 250 | } 251 | 252 | func TestStyle_BackgroundSize(t *testing.T) { 253 | testCases := map[props.BackgroundSize]string{ 254 | props.BackgroundSizeWidth(props.UnitPx(10)): "10px", 255 | props.BackgroundSizeDimension( 256 | props.UnitPx(10), 257 | props.UnitPx(20), 258 | ): "10px 20px", 259 | props.BackgroundSizes( 260 | props.BackgroundSizeWidth(props.UnitPx(10)), 261 | props.BackgroundSizeDimension(props.UnitPx(10), props.UnitPx(20)), 262 | ): "10px,10px 20px", 263 | props.BackgroundSizeCover(): "cover", 264 | props.BackgroundSizeContain(): "contain", 265 | props.BackgroundSize("inherit"): "inherit", 266 | } 267 | 268 | for prop, expected := range testCases { 269 | st := &Style{Selector: ".test", Props: Props{BackgroundSize: prop}} 270 | css := fmt.Sprintf(".test{background-size:%s;}", expected) 271 | runTest(t, st, css) 272 | } 273 | } 274 | 275 | func TestStyle_Border(t *testing.T) { 276 | testCases := map[props.Border]string{ 277 | { 278 | Width: props.UnitPx(10), 279 | Style: props.BorderStyleSolid, 280 | Color: props.ColorRGBA(0, 0, 0, 255), 281 | }: "10px solid rgba(0,0,0,1.00)", 282 | { 283 | Width: props.UnitPx(10), 284 | Style: props.BorderStyleDouble, 285 | Color: props.ColorRGBA(0, 0, 0, 255), 286 | }: "10px double rgba(0,0,0,1.00)", 287 | { 288 | Style: props.BorderStyleNone, 289 | }: "none", 290 | { 291 | Style: props.BorderStyle("initial"), 292 | }: "initial", 293 | } 294 | 295 | for prop, expected := range testCases { 296 | st := &Style{Selector: ".test", Props: Props{Border: prop}} 297 | css := fmt.Sprintf(".test{border:%s;}", expected) 298 | runTest(t, st, css) 299 | } 300 | } 301 | 302 | func TestStyle_BorderBottom(t *testing.T) { 303 | testCases := map[props.Border]string{ 304 | { 305 | Width: props.UnitPx(10), 306 | Style: props.BorderStyleSolid, 307 | Color: props.ColorRGBA(0, 0, 0, 255), 308 | }: "10px solid rgba(0,0,0,1.00)", 309 | { 310 | Width: props.UnitPx(10), 311 | Style: props.BorderStyleDouble, 312 | Color: props.ColorRGBA(0, 0, 0, 255), 313 | }: "10px double rgba(0,0,0,1.00)", 314 | { 315 | Style: props.BorderStyleNone, 316 | }: "none", 317 | { 318 | Style: props.BorderStyle("initial"), 319 | }: "initial", 320 | } 321 | 322 | for prop, expected := range testCases { 323 | st := &Style{Selector: ".test", Props: Props{BorderBottom: prop}} 324 | css := fmt.Sprintf(".test{border-bottom:%s;}", expected) 325 | runTest(t, st, css) 326 | } 327 | } 328 | 329 | func TestStyle_BorderBottomLeftRadius(t *testing.T) { 330 | testCases := map[props.Unit]string{ 331 | props.UnitPx(10): "10px", 332 | props.UnitPercent(50): "50.00%", 333 | props.UnitRem(10): "10.000rem", 334 | props.UnitRaw("20% 10%"): "20% 10%", 335 | } 336 | 337 | for prop, expected := range testCases { 338 | st := &Style{Selector: ".test", Props: Props{BorderBottomLeftRadius: prop}} 339 | css := fmt.Sprintf(".test{border-bottom-left-radius:%s;}", expected) 340 | runTest(t, st, css) 341 | } 342 | } 343 | 344 | func TestStyle_BorderBottomRightRadius(t *testing.T) { 345 | testCases := map[props.Unit]string{ 346 | props.UnitPx(10): "10px", 347 | props.UnitPercent(50): "50.00%", 348 | props.UnitRem(10): "10.000rem", 349 | props.UnitRaw("20% 10%"): "20% 10%", 350 | } 351 | 352 | for prop, expected := range testCases { 353 | st := &Style{Selector: ".test", Props: Props{BorderBottomRightRadius: prop}} 354 | css := fmt.Sprintf(".test{border-bottom-right-radius:%s;}", expected) 355 | runTest(t, st, css) 356 | } 357 | } 358 | 359 | func TestStyle_BorderCollapse(t *testing.T) { 360 | testCases := map[props.BorderCollapse]string{ 361 | props.BorderCollapseSeparate: "separate", 362 | props.BorderCollapseCollapse: "collapse", 363 | props.BorderCollapse("initial"): "initial", 364 | } 365 | 366 | for prop, expected := range testCases { 367 | st := &Style{Selector: ".test", Props: Props{BorderCollapse: prop}} 368 | css := fmt.Sprintf(".test{border-collapse:%s;}", expected) 369 | runTest(t, st, css) 370 | } 371 | } 372 | 373 | func TestStyle_BorderColor(t *testing.T) { 374 | testCases := map[props.Color]string{ 375 | props.ColorRGBA(0, 0, 0, 255): "rgba(0,0,0,1.00)", 376 | props.ColorRGBA(255, 255, 255, 230): "rgba(255,255,255,0.90)", 377 | } 378 | 379 | for prop, expected := range testCases { 380 | st := &Style{Selector: ".test", Props: Props{BorderColor: prop}} 381 | css := fmt.Sprintf(".test{border-color:%s;}", expected) 382 | runTest(t, st, css) 383 | } 384 | } 385 | 386 | func TestStyle_BorderLeft(t *testing.T) { 387 | testCases := map[props.Border]string{ 388 | { 389 | Width: props.UnitPx(10), 390 | Style: props.BorderStyleSolid, 391 | Color: props.ColorRGBA(0, 0, 0, 255), 392 | }: "10px solid rgba(0,0,0,1.00)", 393 | { 394 | Width: props.UnitPx(10), 395 | Style: props.BorderStyleDouble, 396 | Color: props.ColorRGBA(0, 0, 0, 255), 397 | }: "10px double rgba(0,0,0,1.00)", 398 | { 399 | Style: props.BorderStyleNone, 400 | }: "none", 401 | { 402 | Style: props.BorderStyle("initial"), 403 | }: "initial", 404 | } 405 | 406 | for prop, expected := range testCases { 407 | st := &Style{Selector: ".test", Props: Props{BorderLeft: prop}} 408 | css := fmt.Sprintf(".test{border-left:%s;}", expected) 409 | runTest(t, st, css) 410 | } 411 | } 412 | 413 | func TestStyle_BorderRadius(t *testing.T) { 414 | testCases := map[props.Unit]string{ 415 | props.UnitPx(10): "10px", 416 | props.UnitPercent(50): "50.00%", 417 | props.UnitRem(10): "10.000rem", 418 | } 419 | 420 | for prop, expected := range testCases { 421 | st := &Style{Selector: ".test", Props: Props{BorderRadius: prop}} 422 | css := fmt.Sprintf(".test{border-radius:%s;}", expected) 423 | runTest(t, st, css) 424 | } 425 | } 426 | 427 | func TestStyle_BorderRight(t *testing.T) { 428 | testCases := map[props.Border]string{ 429 | { 430 | Width: props.UnitPx(10), 431 | Style: props.BorderStyleSolid, 432 | Color: props.ColorRGBA(0, 0, 0, 255), 433 | }: "10px solid rgba(0,0,0,1.00)", 434 | { 435 | Width: props.UnitPx(5), 436 | Style: props.BorderStyleDouble, 437 | Color: props.ColorRGBA(0, 0, 0, 255), 438 | }: "5px double rgba(0,0,0,1.00)", 439 | { 440 | Style: props.BorderStyleNone, 441 | }: "none", 442 | { 443 | Style: props.BorderStyle("initial"), 444 | }: "initial", 445 | } 446 | 447 | for prop, expected := range testCases { 448 | st := &Style{Selector: ".test", Props: Props{BorderRight: prop}} 449 | css := fmt.Sprintf(".test{border-right:%s;}", expected) 450 | runTest(t, st, css) 451 | } 452 | } 453 | 454 | func TestStyle_BorderStyle(t *testing.T) { 455 | testCases := map[props.BorderStyle]string{ 456 | props.BorderStyleNone: "none", 457 | props.BorderStyleHidden: "hidden", 458 | props.BorderStyleDotted: "dotted", 459 | props.BorderStyleDashed: "dashed", 460 | props.BorderStyleSolid: "solid", 461 | props.BorderStyleDouble: "double", 462 | props.BorderStyle("initial"): "initial", 463 | } 464 | 465 | for prop, expected := range testCases { 466 | st := &Style{Selector: ".test", Props: Props{BorderStyle: prop}} 467 | css := fmt.Sprintf(".test{border-style:%s;}", expected) 468 | runTest(t, st, css) 469 | } 470 | } 471 | 472 | func TestStyle_BorderTop(t *testing.T) { 473 | testCases := map[props.Border]string{ 474 | { 475 | Width: props.UnitPx(10), 476 | Style: props.BorderStyleSolid, 477 | Color: props.ColorRGBA(0, 0, 0, 255), 478 | }: "10px solid rgba(0,0,0,1.00)", 479 | { 480 | Width: props.UnitPx(5), 481 | Style: props.BorderStyleDouble, 482 | Color: props.ColorRGBA(0, 0, 0, 255), 483 | }: "5px double rgba(0,0,0,1.00)", 484 | { 485 | Style: props.BorderStyleNone, 486 | }: "none", 487 | { 488 | Style: props.BorderStyle("initial"), 489 | }: "initial", 490 | } 491 | 492 | for prop, expected := range testCases { 493 | st := &Style{Selector: ".test", Props: Props{BorderTop: prop}} 494 | css := fmt.Sprintf(".test{border-top:%s;}", expected) 495 | runTest(t, st, css) 496 | } 497 | } 498 | 499 | func TestStyle_BorderTopLeftRadius(t *testing.T) { 500 | testCases := map[props.Unit]string{ 501 | props.UnitPx(10): "10px", 502 | props.UnitPercent(50): "50.00%", 503 | props.UnitRem(10): "10.000rem", 504 | props.UnitRaw("20% 10%"): "20% 10%", 505 | } 506 | 507 | for prop, expected := range testCases { 508 | st := &Style{Selector: ".test", Props: Props{BorderTopLeftRadius: prop}} 509 | css := fmt.Sprintf(".test{border-top-left-radius:%s;}", expected) 510 | runTest(t, st, css) 511 | } 512 | } 513 | 514 | func TestStyle_BorderTopRightRadius(t *testing.T) { 515 | testCases := map[props.Unit]string{ 516 | props.UnitPx(10): "10px", 517 | props.UnitPercent(50): "50.00%", 518 | props.UnitRem(10): "10.000rem", 519 | props.UnitRaw("20% 10%"): "20% 10%", 520 | } 521 | 522 | for prop, expected := range testCases { 523 | st := &Style{Selector: ".test", Props: Props{BorderTopRightRadius: prop}} 524 | css := fmt.Sprintf(".test{border-top-right-radius:%s;}", expected) 525 | runTest(t, st, css) 526 | } 527 | } 528 | 529 | func TestStyle_BorderWidth(t *testing.T) { 530 | testCases := map[props.Unit]string{ 531 | props.UnitPx(10): "10px", 532 | props.UnitPercent(50): "50.00%", 533 | props.UnitRem(10): "10.000rem", 534 | } 535 | 536 | for prop, expected := range testCases { 537 | st := &Style{Selector: ".test", Props: Props{BorderWidth: prop}} 538 | css := fmt.Sprintf(".test{border-width:%s;}", expected) 539 | runTest(t, st, css) 540 | } 541 | } 542 | 543 | func TestStyle_Bottom(t *testing.T) { 544 | testCases := map[props.Unit]string{ 545 | props.UnitPx(10): "10px", 546 | props.UnitPercent(50): "50.00%", 547 | props.UnitRem(10): "10.000rem", 548 | } 549 | 550 | for prop, expected := range testCases { 551 | st := &Style{Selector: ".test", Props: Props{Bottom: prop}} 552 | css := fmt.Sprintf(".test{bottom:%s;}", expected) 553 | runTest(t, st, css) 554 | } 555 | } 556 | 557 | func TestStyle_BoxSizing(t *testing.T) { 558 | testCases := map[props.BoxSizing]string{ 559 | props.BoxSizingBorderBox: "border-box", 560 | props.BoxSizingContentBox: "content-box", 561 | props.BoxSizing("initial"): "initial", 562 | } 563 | 564 | for prop, expected := range testCases { 565 | st := &Style{Selector: ".test", Props: Props{BoxSizing: prop}} 566 | css := fmt.Sprintf(".test{box-sizing:%s;}", expected) 567 | runTest(t, st, css) 568 | } 569 | } 570 | 571 | func TestStyle_CaptionSide(t *testing.T) { 572 | testCases := map[props.CaptionSide]string{ 573 | props.CaptionSideTop: "top", 574 | props.CaptionSideBottom: "bottom", 575 | props.CaptionSide("initial"): "initial", 576 | } 577 | 578 | for prop, expected := range testCases { 579 | st := &Style{Selector: ".test", Props: Props{CaptionSide: prop}} 580 | css := fmt.Sprintf(".test{caption-side:%s;}", expected) 581 | runTest(t, st, css) 582 | } 583 | } 584 | 585 | func TestStyle_Color(t *testing.T) { 586 | testCases := map[props.Color]string{ 587 | props.ColorRGBA(0, 0, 0, 255): "rgba(0,0,0,1.00)", 588 | props.ColorRGBA(255, 255, 255, 230): "rgba(255,255,255,0.90)", 589 | props.ColorRGBA(255, 255, 255, 255).Alpha(230): "rgba(255,255,255,0.90)", 590 | props.ColorCurrentColor(): "currentColor", 591 | props.ColorInherit(): "inherit", 592 | props.ColorTransparent(): "transparent", 593 | {Keyword: "#efefef"}: "#efefef", 594 | {Keyword: "red"}: "red", 595 | {Color: color.Gray16{0}}: "rgba(0,0,0,1.00)", 596 | } 597 | 598 | for prop, expected := range testCases { 599 | st := &Style{Selector: ".test", Props: Props{Color: prop}} 600 | css := fmt.Sprintf(".test{color:%s;}", expected) 601 | runTest(t, st, css) 602 | } 603 | } 604 | 605 | func TestStyle_ColumnGap(t *testing.T) { 606 | testCases := map[props.Unit]string{ 607 | props.UnitPx(10): "10px", 608 | props.UnitPercent(50): "50.00%", 609 | props.UnitRem(10): "10.000rem", 610 | } 611 | 612 | for prop, expected := range testCases { 613 | st := &Style{Selector: ".test", Props: Props{ColumnGap: prop}} 614 | css := fmt.Sprintf(".test{column-gap:%s;}", expected) 615 | runTest(t, st, css) 616 | } 617 | } 618 | 619 | func TestStyle_Cursor(t *testing.T) { 620 | testCases := map[props.Cursor]string{ 621 | props.CursorAuto: "auto", 622 | props.CursorDefault: "default", 623 | props.CursorNone: "none", 624 | props.CursorHelp: "help", 625 | props.CursorPointer: "pointer", 626 | props.CursorWait: "wait", 627 | props.CursorText: "text", 628 | props.CursorMove: "move", 629 | props.CursorNotAllowed: "not-allowed", 630 | props.Cursor("inherit"): "inherit", 631 | } 632 | 633 | for prop, expected := range testCases { 634 | st := &Style{Selector: ".test", Props: Props{Cursor: prop}} 635 | css := fmt.Sprintf(".test{cursor:%s;}", expected) 636 | runTest(t, st, css) 637 | } 638 | } 639 | 640 | func TestStyle_Display(t *testing.T) { 641 | testCases := map[props.Display]string{ 642 | props.DisplayBlock: "block", 643 | props.DisplayInline: "inline", 644 | props.DisplayInlineBlock: "inline-block", 645 | props.DisplayInlineFlex: "inline-flex", 646 | props.DisplayInlineGrid: "inline-grid", 647 | props.DisplayFlex: "flex", 648 | props.DisplayGrid: "grid", 649 | props.DisplayNone: "none", 650 | props.DisplayTable: "table", 651 | props.DisplayInlineTable: "inline-table", 652 | props.DisplayTableCaption: "table-caption", 653 | props.DisplayTableCell: "table-cell", 654 | props.DisplayTableColumn: "table-column", 655 | props.DisplayTableColumnGroup: "table-column-group", 656 | props.DisplayTableFooterGroup: "table-footer-group", 657 | props.DisplayTableHeaderGroup: "table-header-group", 658 | props.DisplayTableRowGroup: "table-row-group", 659 | props.DisplayTableRow: "table-row", 660 | props.DisplayFlowRoot: "flow-root", 661 | props.DisplayContents: "contents", 662 | props.DisplayListItem: "list-item", 663 | props.Display("inherit"): "inherit", 664 | } 665 | 666 | for prop, expected := range testCases { 667 | st := &Style{Selector: ".test", Props: Props{Display: prop}} 668 | css := fmt.Sprintf(".test{display:%s;}", expected) 669 | runTest(t, st, css) 670 | } 671 | } 672 | 673 | func TestStyle_FlexBasis(t *testing.T) { 674 | testCases := map[props.Unit]string{ 675 | props.UnitPx(10): "10px", 676 | props.UnitAuto(): "auto", 677 | props.UnitRaw(0): "0", 678 | } 679 | 680 | for prop, expected := range testCases { 681 | st := &Style{Selector: ".test", Props: Props{FlexBasis: prop}} 682 | css := fmt.Sprintf(".test{flex-basis:%s;}", expected) 683 | runTest(t, st, css) 684 | } 685 | } 686 | 687 | func TestStyle_FlexDirection(t *testing.T) { 688 | testCases := map[props.FlexDirection]string{ 689 | props.FlexDirectionRow: "row", 690 | props.FlexDirectionRowReverse: "row-reverse", 691 | props.FlexDirectionColumn: "column", 692 | props.FlexDirectionColumnReverse: "column-reverse", 693 | props.FlexDirection("initial"): "initial", 694 | } 695 | 696 | for prop, expected := range testCases { 697 | st := &Style{Selector: ".test", Props: Props{FlexDirection: prop}} 698 | css := fmt.Sprintf(".test{flex-direction:%s;}", expected) 699 | runTest(t, st, css) 700 | } 701 | } 702 | 703 | func TestStyle_FlexGrow(t *testing.T) { 704 | testCases := map[props.Unit]string{ 705 | props.UnitRaw(1): "1", 706 | props.UnitRaw(0): "0", 707 | props.UnitRaw("initial"): "initial", 708 | } 709 | 710 | for prop, expected := range testCases { 711 | st := &Style{Selector: ".test", Props: Props{FlexGrow: prop}} 712 | css := fmt.Sprintf(".test{flex-grow:%s;}", expected) 713 | runTest(t, st, css) 714 | } 715 | } 716 | 717 | func TestStyle_FlexShrink(t *testing.T) { 718 | testCases := map[props.Unit]string{ 719 | props.UnitRaw(1): "1", 720 | props.UnitRaw(0): "0", 721 | props.UnitRaw("initial"): "initial", 722 | } 723 | 724 | for prop, expected := range testCases { 725 | st := &Style{Selector: ".test", Props: Props{FlexShrink: prop}} 726 | css := fmt.Sprintf(".test{flex-shrink:%s;}", expected) 727 | runTest(t, st, css) 728 | } 729 | } 730 | 731 | func TestStyle_FlexWrap(t *testing.T) { 732 | testCases := map[props.FlexWrap]string{ 733 | props.FlexWrapNoWrap: "nowrap", 734 | props.FlexWrapWrap: "wrap", 735 | props.FlexWrapWrapReverse: "wrap-reverse", 736 | props.FlexWrap("initial"): "initial", 737 | } 738 | 739 | for prop, expected := range testCases { 740 | st := &Style{Selector: ".test", Props: Props{FlexWrap: prop}} 741 | css := fmt.Sprintf(".test{flex-wrap:%s;}", expected) 742 | runTest(t, st, css) 743 | } 744 | } 745 | 746 | func TestStyle_Float(t *testing.T) { 747 | testCases := map[props.Float]string{ 748 | props.FloatLeft: "left", 749 | props.FloatRight: "right", 750 | props.FloatNone: "none", 751 | props.Float("initial"): "initial", 752 | } 753 | 754 | for prop, expected := range testCases { 755 | st := &Style{Selector: ".test", Props: Props{Float: prop}} 756 | css := fmt.Sprintf(".test{float:%s;}", expected) 757 | runTest(t, st, css) 758 | } 759 | } 760 | 761 | func TestStyle_FontFamily(t *testing.T) { 762 | testCases := map[props.FontFamily]string{ 763 | props.FontFamilySans: "ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"", 764 | props.FontFamilySerif: "ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif", 765 | props.FontFamilyMono: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace", 766 | props.FontFamily("\"Gill Sans Extrabold\", sans-serif"): "\"Gill Sans Extrabold\", sans-serif", 767 | props.FontFamily("sans-serif"): "sans-serif", 768 | } 769 | 770 | for prop, expected := range testCases { 771 | st := &Style{Selector: ".test", Props: Props{FontFamily: prop}} 772 | css := fmt.Sprintf(".test{font-family:%s;}", expected) 773 | runTest(t, st, css) 774 | } 775 | 776 | } 777 | func TestStyle_FontSize(t *testing.T) { 778 | testCases := map[props.Unit]string{ 779 | props.UnitPx(20): "20px", 780 | props.UnitRem(2): "2.000rem", 781 | } 782 | 783 | for prop, expected := range testCases { 784 | st := &Style{Selector: ".test", Props: Props{FontSize: prop}} 785 | css := fmt.Sprintf(".test{font-size:%s;}", expected) 786 | runTest(t, st, css) 787 | } 788 | } 789 | 790 | func TestStyle_FontStyle(t *testing.T) { 791 | testCases := map[props.FontStyle]string{ 792 | props.FontStyleNormal: "normal", 793 | props.FontStyleItalic: "italic", 794 | props.FontStyle("initial"): "initial", 795 | } 796 | 797 | for prop, expected := range testCases { 798 | st := &Style{Selector: ".test", Props: Props{FontStyle: prop}} 799 | css := fmt.Sprintf(".test{font-style:%s;}", expected) 800 | runTest(t, st, css) 801 | } 802 | } 803 | 804 | func TestStyle_FontWeight(t *testing.T) { 805 | testCases := map[props.FontWeight]string{ 806 | props.FontWeightThin: "100", 807 | props.FontWeightExtraLight: "200", 808 | props.FontWeightLight: "300", 809 | props.FontWeightNormal: "400", 810 | props.FontWeightMedium: "500", 811 | props.FontWeightSemiBold: "600", 812 | props.FontWeightBold: "700", 813 | props.FontWeightExtraBold: "800", 814 | props.FontWeightBlack: "900", 815 | props.FontWeight("initial"): "initial", 816 | } 817 | 818 | for prop, expected := range testCases { 819 | st := &Style{Selector: ".test", Props: Props{FontWeight: prop}} 820 | css := fmt.Sprintf(".test{font-weight:%s;}", expected) 821 | runTest(t, st, css) 822 | } 823 | } 824 | 825 | func TestStyle_Gap(t *testing.T) { 826 | testCases := map[props.Unit]string{ 827 | props.UnitPx(10): "10px", 828 | props.UnitPercent(50): "50.00%", 829 | props.UnitRem(10): "10.000rem", 830 | } 831 | 832 | for prop, expected := range testCases { 833 | st := &Style{Selector: ".test", Props: Props{Gap: prop}} 834 | css := fmt.Sprintf(".test{gap:%s;}", expected) 835 | runTest(t, st, css) 836 | } 837 | } 838 | 839 | func TestStyle_Height(t *testing.T) { 840 | testCases := map[props.Unit]string{ 841 | props.UnitPx(20): "20px", 842 | props.UnitRem(2): "2.000rem", 843 | props.UnitPercent(50): "50.00%", 844 | props.UnitAuto(): "auto", 845 | props.UnitRaw(0): "0", 846 | } 847 | 848 | for prop, expected := range testCases { 849 | st := &Style{Selector: ".test", Props: Props{Height: prop}} 850 | css := fmt.Sprintf(".test{height:%s;}", expected) 851 | runTest(t, st, css) 852 | } 853 | } 854 | 855 | func TestStyle_JustifyContent(t *testing.T) { 856 | testCases := map[props.JustifyContent]string{ 857 | props.JustifyContentNormal: "normal", 858 | props.JustifyContentCenter: "center", 859 | props.JustifyContentStart: "start", 860 | props.JustifyContentEnd: "end", 861 | props.JustifyContentFlexStart: "flex-start", 862 | props.JustifyContentFlexEnd: "flex-end", 863 | props.JustifyContentLeft: "left", 864 | props.JustifyContentRight: "right", 865 | props.JustifyContentSpaceBetween: "space-between", 866 | props.JustifyContentSpaceAround: "space-around", 867 | props.JustifyContentSpaceEvenly: "space-evenly", 868 | props.JustifyContentStretch: "stretch", 869 | props.JustifyContent("initial"): "initial", 870 | } 871 | 872 | for prop, expected := range testCases { 873 | st := &Style{Selector: ".test", Props: Props{JustifyContent: prop}} 874 | css := fmt.Sprintf(".test{justify-content:%s;}", expected) 875 | runTest(t, st, css) 876 | } 877 | } 878 | 879 | func TestStyle_JustifyItems(t *testing.T) { 880 | testCases := map[props.JustifyItems]string{ 881 | props.JustifyItemsNormal: "normal", 882 | props.JustifyItemsStretch: "stretch", 883 | props.JustifyItemsCenter: "center", 884 | props.JustifyItemsStart: "start", 885 | props.JustifyItemsEnd: "end", 886 | props.JustifyItemsFlexStart: "flex-start", 887 | props.JustifyItemsFlexEnd: "flex-end", 888 | props.JustifyItemsSelfStart: "self-start", 889 | props.JustifyItemsSelfEnd: "self-end", 890 | props.JustifyItemsLeft: "left", 891 | props.JustifyItemsRight: "right", 892 | props.JustifyItemsBaseline: "baseline", 893 | props.JustifyItems("inherit"): "inherit", 894 | } 895 | 896 | for prop, expected := range testCases { 897 | st := &Style{Selector: ".test", Props: Props{JustifyItems: prop}} 898 | css := fmt.Sprintf(".test{justify-items:%s;}", expected) 899 | runTest(t, st, css) 900 | } 901 | } 902 | 903 | func TestStyle_JustifySelf(t *testing.T) { 904 | testCases := map[props.JustifySelf]string{ 905 | props.JustifySelfAuto: "auto", 906 | props.JustifySelfNormal: "normal", 907 | props.JustifySelfStretch: "stretch", 908 | props.JustifySelfCenter: "center", 909 | props.JustifySelfStart: "start", 910 | props.JustifySelfEnd: "end", 911 | props.JustifySelfFlexStart: "flex-start", 912 | props.JustifySelfFlexEnd: "flex-end", 913 | props.JustifySelfSelfStart: "self-start", 914 | props.JustifySelfSelfEnd: "self-end", 915 | props.JustifySelfLeft: "left", 916 | props.JustifySelfRight: "right", 917 | props.JustifySelfBaseline: "baseline", 918 | props.JustifySelf("initial"): "initial", 919 | } 920 | 921 | for prop, expected := range testCases { 922 | st := &Style{Selector: ".test", Props: Props{JustifySelf: prop}} 923 | css := fmt.Sprintf(".test{justify-self:%s;}", expected) 924 | runTest(t, st, css) 925 | } 926 | } 927 | 928 | func TestStyle_Left(t *testing.T) { 929 | testCases := map[props.Unit]string{ 930 | props.UnitPx(10): "10px", 931 | props.UnitPercent(50): "50.00%", 932 | props.UnitRem(10): "10.000rem", 933 | } 934 | 935 | for prop, expected := range testCases { 936 | st := &Style{Selector: ".test", Props: Props{Left: prop}} 937 | css := fmt.Sprintf(".test{left:%s;}", expected) 938 | runTest(t, st, css) 939 | } 940 | } 941 | 942 | func TestStyle_ListStylePosition(t *testing.T) { 943 | testCases := map[props.ListStylePosition]string{ 944 | props.ListStylePositionInside: "inside", 945 | props.ListStylePositionOutside: "outside", 946 | } 947 | 948 | for prop, expected := range testCases { 949 | st := &Style{Selector: ".test", Props: Props{ListStylePosition: prop}} 950 | css := fmt.Sprintf(".test{list-style-position:%s;}", expected) 951 | runTest(t, st, css) 952 | } 953 | } 954 | 955 | func TestStyle_ListStyleType(t *testing.T) { 956 | testCases := map[props.ListStyleType]string{ 957 | props.ListStyleTypeNone: "none", 958 | props.ListStyleTypeDisc: "disc", 959 | props.ListStyleTypeDecimal: "decimal", 960 | } 961 | 962 | for prop, expected := range testCases { 963 | st := &Style{Selector: ".test", Props: Props{ListStyleType: prop}} 964 | css := fmt.Sprintf(".test{list-style-type:%s;}", expected) 965 | runTest(t, st, css) 966 | } 967 | } 968 | 969 | func TestStyle_LineHeight(t *testing.T) { 970 | testCases := map[props.Unit]string{ 971 | props.UnitRaw(1.5): "1.5", 972 | props.UnitPx(20): "20px", 973 | props.UnitEm(3): "3.000em", 974 | } 975 | 976 | for prop, expected := range testCases { 977 | st := &Style{Selector: ".test", Props: Props{LineHeight: prop}} 978 | css := fmt.Sprintf(".test{line-height:%s;}", expected) 979 | runTest(t, st, css) 980 | } 981 | } 982 | 983 | func TestStyle_Margin(t *testing.T) { 984 | testCases := map[props.Unit]string{ 985 | props.UnitPx(10): "10px", 986 | props.UnitRem(2): "2.000rem", 987 | props.UnitAuto(): "auto", 988 | } 989 | 990 | for prop, expected := range testCases { 991 | st := &Style{Selector: ".test", Props: Props{Margin: prop}} 992 | css := fmt.Sprintf(".test{margin:%s;}", expected) 993 | runTest(t, st, css) 994 | } 995 | } 996 | 997 | func TestStyle_MarginBottom(t *testing.T) { 998 | testCases := map[props.Unit]string{ 999 | props.UnitPx(10): "10px", 1000 | props.UnitRem(2): "2.000rem", 1001 | props.UnitAuto(): "auto", 1002 | } 1003 | 1004 | for prop, expected := range testCases { 1005 | st := &Style{Selector: ".test", Props: Props{MarginBottom: prop}} 1006 | css := fmt.Sprintf(".test{margin-bottom:%s;}", expected) 1007 | runTest(t, st, css) 1008 | } 1009 | } 1010 | 1011 | func TestStyle_MarginLeft(t *testing.T) { 1012 | testCases := map[props.Unit]string{ 1013 | props.UnitPx(10): "10px", 1014 | props.UnitRem(2): "2.000rem", 1015 | props.UnitAuto(): "auto", 1016 | } 1017 | 1018 | for prop, expected := range testCases { 1019 | st := &Style{Selector: ".test", Props: Props{MarginLeft: prop}} 1020 | css := fmt.Sprintf(".test{margin-left:%s;}", expected) 1021 | runTest(t, st, css) 1022 | } 1023 | } 1024 | 1025 | func TestStyle_MarginRight(t *testing.T) { 1026 | testCases := map[props.Unit]string{ 1027 | props.UnitPx(10): "10px", 1028 | props.UnitRem(2): "2.000rem", 1029 | props.UnitAuto(): "auto", 1030 | } 1031 | 1032 | for prop, expected := range testCases { 1033 | st := &Style{Selector: ".test", Props: Props{MarginRight: prop}} 1034 | css := fmt.Sprintf(".test{margin-right:%s;}", expected) 1035 | runTest(t, st, css) 1036 | } 1037 | } 1038 | 1039 | func TestStyle_MarginTop(t *testing.T) { 1040 | testCases := map[props.Unit]string{ 1041 | props.UnitPx(10): "10px", 1042 | props.UnitRem(2): "2.000rem", 1043 | props.UnitAuto(): "auto", 1044 | } 1045 | 1046 | for prop, expected := range testCases { 1047 | st := &Style{Selector: ".test", Props: Props{MarginTop: prop}} 1048 | css := fmt.Sprintf(".test{margin-top:%s;}", expected) 1049 | runTest(t, st, css) 1050 | } 1051 | } 1052 | 1053 | func TestStyle_MaxHeight(t *testing.T) { 1054 | testCases := map[props.Unit]string{ 1055 | props.UnitPx(10): "10px", 1056 | props.UnitRem(2): "2.000rem", 1057 | props.UnitVh(100): "100vh", 1058 | props.UnitAuto(): "auto", 1059 | } 1060 | 1061 | for prop, expected := range testCases { 1062 | st := &Style{Selector: ".test", Props: Props{MaxHeight: prop}} 1063 | css := fmt.Sprintf(".test{max-height:%s;}", expected) 1064 | runTest(t, st, css) 1065 | } 1066 | } 1067 | 1068 | func TestStyle_MaxWidth(t *testing.T) { 1069 | testCases := map[props.Unit]string{ 1070 | props.UnitPx(10): "10px", 1071 | props.UnitRem(2): "2.000rem", 1072 | props.UnitVw(100): "100vw", 1073 | props.UnitAuto(): "auto", 1074 | } 1075 | 1076 | for prop, expected := range testCases { 1077 | st := &Style{Selector: ".test", Props: Props{MaxWidth: prop}} 1078 | css := fmt.Sprintf(".test{max-width:%s;}", expected) 1079 | runTest(t, st, css) 1080 | } 1081 | } 1082 | 1083 | func TestStyle_MinHeight(t *testing.T) { 1084 | testCases := map[props.Unit]string{ 1085 | props.UnitPx(10): "10px", 1086 | props.UnitRem(2): "2.000rem", 1087 | props.UnitVh(100): "100vh", 1088 | props.UnitAuto(): "auto", 1089 | } 1090 | 1091 | for prop, expected := range testCases { 1092 | st := &Style{Selector: ".test", Props: Props{MinHeight: prop}} 1093 | css := fmt.Sprintf(".test{min-height:%s;}", expected) 1094 | runTest(t, st, css) 1095 | } 1096 | } 1097 | 1098 | func TestStyle_MinWidth(t *testing.T) { 1099 | testCases := map[props.Unit]string{ 1100 | props.UnitPx(10): "10px", 1101 | props.UnitRem(2): "2.000rem", 1102 | props.UnitVw(100): "100vw", 1103 | props.UnitAuto(): "auto", 1104 | } 1105 | 1106 | for prop, expected := range testCases { 1107 | st := &Style{Selector: ".test", Props: Props{MinWidth: prop}} 1108 | css := fmt.Sprintf(".test{min-width:%s;}", expected) 1109 | runTest(t, st, css) 1110 | } 1111 | } 1112 | 1113 | func TestStyle_Opacity(t *testing.T) { 1114 | testCases := map[props.Unit]string{ 1115 | props.UnitRaw(0): "0", 1116 | props.UnitRaw(0.33): "0.33", 1117 | props.UnitPercent(90): "90.00%", 1118 | props.UnitRaw("initial"): "initial", 1119 | } 1120 | 1121 | for prop, expected := range testCases { 1122 | st := &Style{Selector: ".test", Props: Props{Opacity: prop}} 1123 | css := fmt.Sprintf(".test{opacity:%s;}", expected) 1124 | runTest(t, st, css) 1125 | } 1126 | } 1127 | 1128 | func TestStyle_Overflow(t *testing.T) { 1129 | testCases := map[props.Overflow]string{ 1130 | props.OverflowVisible: "visible", 1131 | props.OverflowHidden: "hidden", 1132 | props.OverflowScroll: "scroll", 1133 | props.OverflowAuto: "auto", 1134 | props.OverflowClip: "clip", 1135 | props.Overflow("initial"): "initial", 1136 | } 1137 | 1138 | for prop, expected := range testCases { 1139 | st := &Style{Selector: ".test", Props: Props{Overflow: prop}} 1140 | css := fmt.Sprintf(".test{overflow:%s;}", expected) 1141 | runTest(t, st, css) 1142 | } 1143 | } 1144 | 1145 | func TestStyle_OverflowX(t *testing.T) { 1146 | testCases := map[props.Overflow]string{ 1147 | props.OverflowVisible: "visible", 1148 | props.OverflowHidden: "hidden", 1149 | props.OverflowScroll: "scroll", 1150 | props.OverflowAuto: "auto", 1151 | props.OverflowClip: "clip", 1152 | props.Overflow("initial"): "initial", 1153 | } 1154 | 1155 | for prop, expected := range testCases { 1156 | st := &Style{Selector: ".test", Props: Props{OverflowX: prop}} 1157 | css := fmt.Sprintf(".test{overflow-x:%s;}", expected) 1158 | runTest(t, st, css) 1159 | } 1160 | } 1161 | 1162 | func TestStyle_OverflowY(t *testing.T) { 1163 | testCases := map[props.Overflow]string{ 1164 | props.OverflowVisible: "visible", 1165 | props.OverflowHidden: "hidden", 1166 | props.OverflowScroll: "scroll", 1167 | props.OverflowAuto: "auto", 1168 | props.OverflowClip: "clip", 1169 | props.Overflow("initial"): "initial", 1170 | } 1171 | 1172 | for prop, expected := range testCases { 1173 | st := &Style{Selector: ".test", Props: Props{OverflowY: prop}} 1174 | css := fmt.Sprintf(".test{overflow-y:%s;}", expected) 1175 | runTest(t, st, css) 1176 | } 1177 | } 1178 | 1179 | func TestStyle_Padding(t *testing.T) { 1180 | testCases := map[props.Unit]string{ 1181 | props.UnitPx(10): "10px", 1182 | props.UnitRem(2): "2.000rem", 1183 | props.UnitAuto(): "auto", 1184 | props.UnitRaw(0): "0", 1185 | } 1186 | 1187 | for prop, expected := range testCases { 1188 | st := &Style{Selector: ".test", Props: Props{Padding: prop}} 1189 | css := fmt.Sprintf(".test{padding:%s;}", expected) 1190 | runTest(t, st, css) 1191 | } 1192 | } 1193 | 1194 | func TestStyle_PaddingBottom(t *testing.T) { 1195 | testCases := map[props.Unit]string{ 1196 | props.UnitPx(10): "10px", 1197 | props.UnitRem(2): "2.000rem", 1198 | props.UnitAuto(): "auto", 1199 | props.UnitRaw(0): "0", 1200 | } 1201 | 1202 | for prop, expected := range testCases { 1203 | st := &Style{Selector: ".test", Props: Props{PaddingBottom: prop}} 1204 | css := fmt.Sprintf(".test{padding-bottom:%s;}", expected) 1205 | runTest(t, st, css) 1206 | } 1207 | } 1208 | 1209 | func TestStyle_PaddingLeft(t *testing.T) { 1210 | testCases := map[props.Unit]string{ 1211 | props.UnitPx(10): "10px", 1212 | props.UnitRem(2): "2.000rem", 1213 | props.UnitAuto(): "auto", 1214 | props.UnitRaw(0): "0", 1215 | } 1216 | 1217 | for prop, expected := range testCases { 1218 | st := &Style{Selector: ".test", Props: Props{PaddingLeft: prop}} 1219 | css := fmt.Sprintf(".test{padding-left:%s;}", expected) 1220 | runTest(t, st, css) 1221 | } 1222 | } 1223 | 1224 | func TestStyle_PaddingRight(t *testing.T) { 1225 | testCases := map[props.Unit]string{ 1226 | props.UnitPx(10): "10px", 1227 | props.UnitRem(2): "2.000rem", 1228 | props.UnitAuto(): "auto", 1229 | props.UnitRaw(0): "0", 1230 | } 1231 | 1232 | for prop, expected := range testCases { 1233 | st := &Style{Selector: ".test", Props: Props{PaddingRight: prop}} 1234 | css := fmt.Sprintf(".test{padding-right:%s;}", expected) 1235 | runTest(t, st, css) 1236 | } 1237 | } 1238 | 1239 | func TestStyle_PaddingTop(t *testing.T) { 1240 | testCases := map[props.Unit]string{ 1241 | props.UnitPx(10): "10px", 1242 | props.UnitRem(2): "2.000rem", 1243 | props.UnitAuto(): "auto", 1244 | props.UnitRaw(0): "0", 1245 | } 1246 | 1247 | for prop, expected := range testCases { 1248 | st := &Style{Selector: ".test", Props: Props{PaddingTop: prop}} 1249 | css := fmt.Sprintf(".test{padding-top:%s;}", expected) 1250 | runTest(t, st, css) 1251 | } 1252 | } 1253 | 1254 | func TestStyle_Position(t *testing.T) { 1255 | testCases := map[props.Position]string{ 1256 | props.PositionStatic: "static", 1257 | props.PositionRelative: "relative", 1258 | props.PositionAbsolute: "absolute", 1259 | props.PositionFixed: "fixed", 1260 | props.PositionSticky: "sticky", 1261 | props.Position("initial"): "initial", 1262 | } 1263 | 1264 | for prop, expected := range testCases { 1265 | st := &Style{Selector: ".test", Props: Props{Position: prop}} 1266 | css := fmt.Sprintf(".test{position:%s;}", expected) 1267 | runTest(t, st, css) 1268 | } 1269 | } 1270 | 1271 | func TestStyle_PrintColorAdjust(t *testing.T) { 1272 | testCases := map[props.PrintColorAdjust]string{ 1273 | props.PrintColorAdjustEconomy: "economy", 1274 | props.PrintColorAdjustExact: "exact", 1275 | props.PrintColorAdjust("auto"): "auto", 1276 | } 1277 | 1278 | for prop, expected := range testCases { 1279 | st := &Style{Selector: ".test", Props: Props{PrintColorAdjust: prop}} 1280 | css := fmt.Sprintf(".test{print-color-adjust:%s;}", expected) 1281 | runTest(t, st, css) 1282 | } 1283 | } 1284 | 1285 | func TestStyle_Right(t *testing.T) { 1286 | testCases := map[props.Unit]string{ 1287 | props.UnitPx(10): "10px", 1288 | props.UnitPercent(50): "50.00%", 1289 | props.UnitRem(10): "10.000rem", 1290 | } 1291 | 1292 | for prop, expected := range testCases { 1293 | st := &Style{Selector: ".test", Props: Props{Right: prop}} 1294 | css := fmt.Sprintf(".test{right:%s;}", expected) 1295 | runTest(t, st, css) 1296 | } 1297 | } 1298 | 1299 | func TestStyle_RowGap(t *testing.T) { 1300 | testCases := map[props.Unit]string{ 1301 | props.UnitPx(10): "10px", 1302 | props.UnitPercent(50): "50.00%", 1303 | props.UnitRem(10): "10.000rem", 1304 | } 1305 | 1306 | for prop, expected := range testCases { 1307 | st := &Style{Selector: ".test", Props: Props{RowGap: prop}} 1308 | css := fmt.Sprintf(".test{row-gap:%s;}", expected) 1309 | runTest(t, st, css) 1310 | } 1311 | } 1312 | 1313 | func TestStyle_TextAlign(t *testing.T) { 1314 | testCases := map[props.TextAlign]string{ 1315 | props.TextAlignLeft: "left", 1316 | props.TextAlignRight: "right", 1317 | props.TextAlignCenter: "center", 1318 | props.TextAlignJustify: "justify", 1319 | props.TextAlignStart: "start", 1320 | props.TextAlignEnd: "end", 1321 | props.TextAlign("initial"): "initial", 1322 | } 1323 | 1324 | for prop, expected := range testCases { 1325 | st := &Style{Selector: ".test", Props: Props{TextAlign: prop}} 1326 | css := fmt.Sprintf(".test{text-align:%s;}", expected) 1327 | runTest(t, st, css) 1328 | } 1329 | } 1330 | 1331 | func TestStyle_TextDecorationColor(t *testing.T) { 1332 | testCases := map[props.Color]string{ 1333 | props.ColorRGBA(0, 0, 0, 255): "rgba(0,0,0,1.00)", 1334 | props.ColorRGBA(255, 255, 255, 230): "rgba(255,255,255,0.90)", 1335 | props.ColorRGBA(255, 255, 255, 255).Alpha(230): "rgba(255,255,255,0.90)", 1336 | props.ColorCurrentColor(): "currentColor", 1337 | } 1338 | 1339 | for prop, expected := range testCases { 1340 | st := &Style{Selector: ".test", Props: Props{TextDecorationColor: prop}} 1341 | css := fmt.Sprintf(".test{text-decoration-color:%s;}", expected) 1342 | runTest(t, st, css) 1343 | } 1344 | } 1345 | 1346 | func TestStyle_TextDecorationLine(t *testing.T) { 1347 | testCases := map[props.TextDecorationLine]string{ 1348 | props.TextDecorationLineNone: "none", 1349 | props.TextDecorationLineUnderline: "underline", 1350 | props.TextDecorationLineOverline: "overline", 1351 | props.TextDecorationLineLineThrough: "line-through", 1352 | props.TextDecorationLine("initial"): "initial", 1353 | } 1354 | 1355 | for prop, expected := range testCases { 1356 | st := &Style{Selector: ".test", Props: Props{TextDecorationLine: prop}} 1357 | css := fmt.Sprintf(".test{text-decoration-line:%s;}", expected) 1358 | runTest(t, st, css) 1359 | } 1360 | } 1361 | 1362 | func TestStyle_TextDecorationStyle(t *testing.T) { 1363 | testCases := map[props.TextDecorationStyle]string{ 1364 | props.TextDecorationStyleSolid: "solid", 1365 | props.TextDecorationStyleDouble: "double", 1366 | props.TextDecorationStyleDotted: "dotted", 1367 | props.TextDecorationStyleDashed: "dashed", 1368 | props.TextDecorationStyleWavy: "wavy", 1369 | props.TextDecorationStyle("initial"): "initial", 1370 | } 1371 | 1372 | for prop, expected := range testCases { 1373 | st := &Style{Selector: ".test", Props: Props{TextDecorationStyle: prop}} 1374 | css := fmt.Sprintf(".test{text-decoration-style:%s;}", expected) 1375 | runTest(t, st, css) 1376 | } 1377 | } 1378 | 1379 | func TestStyle_TextDecorationThickness(t *testing.T) { 1380 | testCases := map[props.Unit]string{ 1381 | props.UnitPx(1): "1px", 1382 | props.UnitAuto(): "auto", 1383 | props.UnitRaw("from-font"): "from-font", 1384 | props.UnitInherit(): "inherit", 1385 | } 1386 | 1387 | for prop, expected := range testCases { 1388 | st := &Style{Selector: ".test", Props: Props{TextDecorationThickness: prop}} 1389 | css := fmt.Sprintf(".test{text-decoration-thickness:%s;}", expected) 1390 | runTest(t, st, css) 1391 | } 1392 | } 1393 | 1394 | func TestStyle_TextIndent(t *testing.T) { 1395 | testCases := map[props.Unit]string{ 1396 | props.UnitPx(0): "0px", 1397 | props.UnitRem(0.875): "0.875rem", 1398 | props.UnitInherit(): "inherit", 1399 | } 1400 | 1401 | for prop, expected := range testCases { 1402 | st := &Style{Selector: ".test", Props: Props{TextIndent: prop}} 1403 | css := fmt.Sprintf(".test{text-indent:%s;}", expected) 1404 | runTest(t, st, css) 1405 | } 1406 | } 1407 | 1408 | func TestStyle_TextUnderlineOffset(t *testing.T) { 1409 | testCases := map[props.Unit]string{ 1410 | props.UnitPx(1): "1px", 1411 | props.UnitAuto(): "auto", 1412 | } 1413 | 1414 | for prop, expected := range testCases { 1415 | st := &Style{Selector: ".test", Props: Props{TextUnderlineOffset: prop}} 1416 | css := fmt.Sprintf(".test{text-underline-offset:%s;}", expected) 1417 | runTest(t, st, css) 1418 | } 1419 | } 1420 | 1421 | func TestStyle_TextOverflow(t *testing.T) { 1422 | testCases := map[props.TextOverflow]string{ 1423 | props.TextOverflowClip: "clip", 1424 | props.TextOverflowEllipsis: "ellipsis", 1425 | props.TextOverflow("initial"): "initial", 1426 | } 1427 | 1428 | for prop, expected := range testCases { 1429 | st := &Style{Selector: ".test", Props: Props{TextOverflow: prop}} 1430 | css := fmt.Sprintf(".test{text-overflow:%s;}", expected) 1431 | runTest(t, st, css) 1432 | } 1433 | } 1434 | 1435 | func TestStyle_TextTransform(t *testing.T) { 1436 | testCases := map[props.TextTransform]string{ 1437 | props.TextTransformNone: "none", 1438 | props.TextTransformCapitalize: "capitalize", 1439 | props.TextTransformUppercase: "uppercase", 1440 | props.TextTransformLowercase: "lowercase", 1441 | props.TextTransform("initial"): "initial", 1442 | } 1443 | 1444 | for prop, expected := range testCases { 1445 | st := &Style{Selector: ".test", Props: Props{TextTransform: prop}} 1446 | css := fmt.Sprintf(".test{text-transform:%s;}", expected) 1447 | runTest(t, st, css) 1448 | } 1449 | } 1450 | 1451 | func TestStyle_TextWrap(t *testing.T) { 1452 | testCases := map[props.TextWrap]string{ 1453 | props.TextWrapWrap: "wrap", 1454 | props.TextWrapNoWrap: "nowrap", 1455 | props.TextWrapBalance: "balance", 1456 | props.TextWrap("inherit"): "inherit", 1457 | } 1458 | 1459 | for prop, expected := range testCases { 1460 | st := &Style{Selector: ".test", Props: Props{TextWrap: prop}} 1461 | css := fmt.Sprintf(".test{text-wrap:%s;}", expected) 1462 | runTest(t, st, css) 1463 | } 1464 | } 1465 | 1466 | func TestStyle_Top(t *testing.T) { 1467 | testCases := map[props.Unit]string{ 1468 | props.UnitPx(10): "10px", 1469 | props.UnitPercent(50): "50.00%", 1470 | props.UnitRem(10): "10.000rem", 1471 | } 1472 | 1473 | for prop, expected := range testCases { 1474 | st := &Style{Selector: ".test", Props: Props{Top: prop}} 1475 | css := fmt.Sprintf(".test{top:%s;}", expected) 1476 | runTest(t, st, css) 1477 | } 1478 | } 1479 | 1480 | func TestStyle_VerticalAlign(t *testing.T) { 1481 | testCases := map[props.VerticalAlign]string{ 1482 | props.VerticalAlignBaseline: "baseline", 1483 | props.VerticalAlignSub: "sub", 1484 | props.VerticalAlignSuper: "super", 1485 | props.VerticalAlignTextTop: "text-top", 1486 | props.VerticalAlignTextBottom: "text-bottom", 1487 | props.VerticalAlignMiddle: "middle", 1488 | props.VerticalAlignTop: "top", 1489 | props.VerticalAlignBottom: "bottom", 1490 | props.VerticalAlign("initial"): "initial", 1491 | } 1492 | 1493 | for prop, expected := range testCases { 1494 | st := &Style{Selector: ".test", Props: Props{VerticalAlign: prop}} 1495 | css := fmt.Sprintf(".test{vertical-align:%s;}", expected) 1496 | runTest(t, st, css) 1497 | } 1498 | } 1499 | 1500 | func TestStyle_WhiteSpace(t *testing.T) { 1501 | testCases := map[props.WhiteSpace]string{ 1502 | props.WhiteSpaceNormal: "normal", 1503 | props.WhiteSpaceNowrap: "nowrap", 1504 | props.WhiteSpacePre: "pre", 1505 | props.WhiteSpacePreLine: "pre-line", 1506 | props.WhiteSpacePreWrap: "pre-wrap", 1507 | props.WhiteSpace("initial"): "initial", 1508 | } 1509 | 1510 | for prop, expected := range testCases { 1511 | st := &Style{Selector: ".test", Props: Props{WhiteSpace: prop}} 1512 | css := fmt.Sprintf(".test{white-space:%s;}", expected) 1513 | runTest(t, st, css) 1514 | } 1515 | } 1516 | 1517 | func TestStyle_Width(t *testing.T) { 1518 | testCases := map[props.Unit]string{ 1519 | props.UnitPx(20): "20px", 1520 | props.UnitRem(2): "2.000rem", 1521 | props.UnitPercent(50): "50.00%", 1522 | props.UnitAuto(): "auto", 1523 | props.UnitRaw(0): "0", 1524 | } 1525 | 1526 | for prop, expected := range testCases { 1527 | st := &Style{Selector: ".test", Props: Props{Width: prop}} 1528 | css := fmt.Sprintf(".test{width:%s;}", expected) 1529 | runTest(t, st, css) 1530 | } 1531 | } 1532 | 1533 | func TestStyle_Visibility(t *testing.T) { 1534 | testCases := map[props.Visibility]string{ 1535 | props.VisibilityVisible: "visible", 1536 | props.VisibilityHidden: "hidden", 1537 | props.VisibilityCollapse: "collapse", 1538 | props.Visibility("initial"): "initial", 1539 | } 1540 | 1541 | for prop, expected := range testCases { 1542 | st := &Style{Selector: ".test", Props: Props{Visibility: prop}} 1543 | css := fmt.Sprintf(".test{visibility:%s;}", expected) 1544 | runTest(t, st, css) 1545 | } 1546 | } 1547 | 1548 | func TestStyle_ZIndex(t *testing.T) { 1549 | testCases := map[props.Unit]string{ 1550 | props.UnitRaw(1): "1", 1551 | props.UnitRaw(10): "10", 1552 | props.UnitAuto(): "auto", 1553 | } 1554 | 1555 | for prop, expected := range testCases { 1556 | st := &Style{Selector: ".test", Props: Props{ZIndex: prop}} 1557 | css := fmt.Sprintf(".test{z-index:%s;}", expected) 1558 | runTest(t, st, css) 1559 | } 1560 | } 1561 | -------------------------------------------------------------------------------- /variables/colors.go: -------------------------------------------------------------------------------- 1 | package variables 2 | 3 | import "github.com/AccentDesign/gcss/props" 4 | 5 | var ( 6 | Black = props.ColorRGBA(0, 0, 0, 255) 7 | White = props.ColorRGBA(255, 255, 255, 255) 8 | Slate50 = props.ColorRGBA(248, 250, 252, 255) 9 | Slate100 = props.ColorRGBA(241, 245, 249, 255) 10 | Slate200 = props.ColorRGBA(226, 232, 240, 255) 11 | Slate300 = props.ColorRGBA(203, 213, 225, 255) 12 | Slate400 = props.ColorRGBA(148, 163, 184, 255) 13 | Slate500 = props.ColorRGBA(100, 116, 139, 255) 14 | Slate600 = props.ColorRGBA(71, 85, 105, 255) 15 | Slate700 = props.ColorRGBA(51, 65, 85, 255) 16 | Slate800 = props.ColorRGBA(30, 41, 59, 255) 17 | Slate900 = props.ColorRGBA(15, 23, 42, 255) 18 | Slate950 = props.ColorRGBA(2, 6, 23, 255) 19 | Gray50 = props.ColorRGBA(249, 250, 251, 255) 20 | Gray100 = props.ColorRGBA(243, 244, 246, 255) 21 | Gray200 = props.ColorRGBA(229, 231, 235, 255) 22 | Gray300 = props.ColorRGBA(209, 213, 219, 255) 23 | Gray400 = props.ColorRGBA(156, 163, 175, 255) 24 | Gray500 = props.ColorRGBA(107, 114, 128, 255) 25 | Gray600 = props.ColorRGBA(75, 85, 99, 255) 26 | Gray700 = props.ColorRGBA(55, 65, 81, 255) 27 | Gray800 = props.ColorRGBA(31, 41, 55, 255) 28 | Gray900 = props.ColorRGBA(17, 24, 39, 255) 29 | Gray950 = props.ColorRGBA(3, 7, 18, 255) 30 | Zinc50 = props.ColorRGBA(250, 250, 250, 255) 31 | Zinc100 = props.ColorRGBA(244, 244, 245, 255) 32 | Zinc200 = props.ColorRGBA(228, 228, 231, 255) 33 | Zinc300 = props.ColorRGBA(212, 212, 216, 255) 34 | Zinc400 = props.ColorRGBA(161, 161, 170, 255) 35 | Zinc500 = props.ColorRGBA(113, 113, 122, 255) 36 | Zinc600 = props.ColorRGBA(82, 82, 91, 255) 37 | Zinc700 = props.ColorRGBA(63, 63, 70, 255) 38 | Zinc800 = props.ColorRGBA(39, 39, 42, 255) 39 | Zinc900 = props.ColorRGBA(24, 24, 27, 255) 40 | Zinc950 = props.ColorRGBA(9, 9, 11, 255) 41 | Neutral50 = props.ColorRGBA(250, 250, 250, 255) 42 | Neutral100 = props.ColorRGBA(245, 245, 245, 255) 43 | Neutral200 = props.ColorRGBA(229, 229, 229, 255) 44 | Neutral300 = props.ColorRGBA(212, 212, 212, 255) 45 | Neutral400 = props.ColorRGBA(163, 163, 163, 255) 46 | Neutral500 = props.ColorRGBA(115, 115, 115, 255) 47 | Neutral600 = props.ColorRGBA(82, 82, 82, 255) 48 | Neutral700 = props.ColorRGBA(64, 64, 64, 255) 49 | Neutral800 = props.ColorRGBA(38, 38, 38, 255) 50 | Neutral900 = props.ColorRGBA(23, 23, 23, 255) 51 | Neutral950 = props.ColorRGBA(10, 10, 10, 255) 52 | Stone50 = props.ColorRGBA(250, 250, 249, 255) 53 | Stone100 = props.ColorRGBA(245, 245, 244, 255) 54 | Stone200 = props.ColorRGBA(231, 229, 228, 255) 55 | Stone300 = props.ColorRGBA(214, 211, 209, 255) 56 | Stone400 = props.ColorRGBA(168, 162, 158, 255) 57 | Stone500 = props.ColorRGBA(120, 113, 108, 255) 58 | Stone600 = props.ColorRGBA(87, 83, 78, 255) 59 | Stone700 = props.ColorRGBA(68, 64, 60, 255) 60 | Stone800 = props.ColorRGBA(41, 37, 36, 255) 61 | Stone900 = props.ColorRGBA(28, 25, 23, 255) 62 | Stone950 = props.ColorRGBA(12, 10, 9, 255) 63 | Red50 = props.ColorRGBA(254, 242, 242, 255) 64 | Red100 = props.ColorRGBA(254, 226, 226, 255) 65 | Red200 = props.ColorRGBA(254, 202, 202, 255) 66 | Red300 = props.ColorRGBA(252, 165, 165, 255) 67 | Red400 = props.ColorRGBA(248, 113, 113, 255) 68 | Red500 = props.ColorRGBA(239, 68, 68, 255) 69 | Red600 = props.ColorRGBA(220, 38, 38, 255) 70 | Red700 = props.ColorRGBA(185, 28, 28, 255) 71 | Red800 = props.ColorRGBA(153, 27, 27, 255) 72 | Red900 = props.ColorRGBA(127, 29, 29, 255) 73 | Red950 = props.ColorRGBA(69, 10, 10, 255) 74 | Orange50 = props.ColorRGBA(255, 247, 237, 255) 75 | Orange100 = props.ColorRGBA(255, 237, 213, 255) 76 | Orange200 = props.ColorRGBA(254, 215, 170, 255) 77 | Orange300 = props.ColorRGBA(253, 186, 116, 255) 78 | Orange400 = props.ColorRGBA(251, 146, 60, 255) 79 | Orange500 = props.ColorRGBA(249, 115, 22, 255) 80 | Orange600 = props.ColorRGBA(234, 88, 12, 255) 81 | Orange700 = props.ColorRGBA(194, 65, 12, 255) 82 | Orange800 = props.ColorRGBA(154, 52, 18, 255) 83 | Orange900 = props.ColorRGBA(124, 45, 18, 255) 84 | Orange950 = props.ColorRGBA(67, 20, 7, 255) 85 | Amber50 = props.ColorRGBA(255, 251, 235, 255) 86 | Amber100 = props.ColorRGBA(254, 243, 199, 255) 87 | Amber200 = props.ColorRGBA(253, 230, 138, 255) 88 | Amber300 = props.ColorRGBA(252, 211, 77, 255) 89 | Amber400 = props.ColorRGBA(251, 191, 36, 255) 90 | Amber500 = props.ColorRGBA(245, 158, 11, 255) 91 | Amber600 = props.ColorRGBA(217, 119, 6, 255) 92 | Amber700 = props.ColorRGBA(180, 83, 9, 255) 93 | Amber800 = props.ColorRGBA(146, 64, 14, 255) 94 | Amber900 = props.ColorRGBA(120, 53, 15, 255) 95 | Amber950 = props.ColorRGBA(69, 26, 3, 255) 96 | Yellow50 = props.ColorRGBA(254, 252, 232, 255) 97 | Yellow100 = props.ColorRGBA(254, 249, 195, 255) 98 | Yellow200 = props.ColorRGBA(254, 240, 138, 255) 99 | Yellow300 = props.ColorRGBA(253, 224, 71, 255) 100 | Yellow400 = props.ColorRGBA(250, 204, 21, 255) 101 | Yellow500 = props.ColorRGBA(234, 179, 8, 255) 102 | Yellow600 = props.ColorRGBA(202, 138, 4, 255) 103 | Yellow700 = props.ColorRGBA(161, 98, 7, 255) 104 | Yellow800 = props.ColorRGBA(133, 77, 14, 255) 105 | Yellow900 = props.ColorRGBA(113, 63, 18, 255) 106 | Yellow950 = props.ColorRGBA(66, 32, 6, 255) 107 | Lime50 = props.ColorRGBA(247, 254, 231, 255) 108 | Lime100 = props.ColorRGBA(236, 252, 203, 255) 109 | Lime200 = props.ColorRGBA(217, 249, 157, 255) 110 | Lime300 = props.ColorRGBA(190, 242, 100, 255) 111 | Lime400 = props.ColorRGBA(163, 230, 53, 255) 112 | Lime500 = props.ColorRGBA(132, 204, 22, 255) 113 | Lime600 = props.ColorRGBA(101, 163, 13, 255) 114 | Lime700 = props.ColorRGBA(77, 124, 15, 255) 115 | Lime800 = props.ColorRGBA(63, 98, 18, 255) 116 | Lime900 = props.ColorRGBA(54, 83, 20, 255) 117 | Lime950 = props.ColorRGBA(26, 46, 5, 255) 118 | Green50 = props.ColorRGBA(240, 253, 244, 255) 119 | Green100 = props.ColorRGBA(220, 252, 231, 255) 120 | Green200 = props.ColorRGBA(187, 247, 208, 255) 121 | Green300 = props.ColorRGBA(134, 239, 172, 255) 122 | Green400 = props.ColorRGBA(74, 222, 128, 255) 123 | Green500 = props.ColorRGBA(34, 197, 94, 255) 124 | Green600 = props.ColorRGBA(22, 163, 74, 255) 125 | Green700 = props.ColorRGBA(21, 128, 61, 255) 126 | Green800 = props.ColorRGBA(22, 101, 52, 255) 127 | Green900 = props.ColorRGBA(20, 83, 45, 255) 128 | Green950 = props.ColorRGBA(5, 46, 22, 255) 129 | Emerald50 = props.ColorRGBA(236, 253, 245, 255) 130 | Emerald100 = props.ColorRGBA(209, 250, 229, 255) 131 | Emerald200 = props.ColorRGBA(167, 243, 208, 255) 132 | Emerald300 = props.ColorRGBA(110, 231, 183, 255) 133 | Emerald400 = props.ColorRGBA(52, 211, 153, 255) 134 | Emerald500 = props.ColorRGBA(16, 185, 129, 255) 135 | Emerald600 = props.ColorRGBA(5, 150, 105, 255) 136 | Emerald700 = props.ColorRGBA(4, 120, 87, 255) 137 | Emerald800 = props.ColorRGBA(6, 95, 70, 255) 138 | Emerald900 = props.ColorRGBA(6, 78, 59, 255) 139 | Emerald950 = props.ColorRGBA(2, 44, 34, 255) 140 | Teal50 = props.ColorRGBA(240, 253, 250, 255) 141 | Teal100 = props.ColorRGBA(204, 251, 241, 255) 142 | Teal200 = props.ColorRGBA(153, 246, 228, 255) 143 | Teal300 = props.ColorRGBA(94, 234, 212, 255) 144 | Teal400 = props.ColorRGBA(45, 212, 191, 255) 145 | Teal500 = props.ColorRGBA(20, 184, 166, 255) 146 | Teal600 = props.ColorRGBA(13, 148, 136, 255) 147 | Teal700 = props.ColorRGBA(15, 118, 110, 255) 148 | Teal800 = props.ColorRGBA(17, 94, 89, 255) 149 | Teal900 = props.ColorRGBA(19, 78, 74, 255) 150 | Teal950 = props.ColorRGBA(4, 47, 46, 255) 151 | Cyan50 = props.ColorRGBA(236, 254, 255, 255) 152 | Cyan100 = props.ColorRGBA(207, 250, 254, 255) 153 | Cyan200 = props.ColorRGBA(165, 243, 252, 255) 154 | Cyan300 = props.ColorRGBA(103, 232, 249, 255) 155 | Cyan400 = props.ColorRGBA(34, 211, 238, 255) 156 | Cyan500 = props.ColorRGBA(6, 182, 212, 255) 157 | Cyan600 = props.ColorRGBA(8, 145, 178, 255) 158 | Cyan700 = props.ColorRGBA(14, 116, 144, 255) 159 | Cyan800 = props.ColorRGBA(21, 94, 117, 255) 160 | Cyan900 = props.ColorRGBA(22, 78, 99, 255) 161 | Cyan950 = props.ColorRGBA(8, 51, 68, 255) 162 | Sky50 = props.ColorRGBA(240, 249, 255, 255) 163 | Sky100 = props.ColorRGBA(224, 242, 254, 255) 164 | Sky200 = props.ColorRGBA(186, 230, 253, 255) 165 | Sky300 = props.ColorRGBA(125, 211, 252, 255) 166 | Sky400 = props.ColorRGBA(56, 189, 248, 255) 167 | Sky500 = props.ColorRGBA(14, 165, 233, 255) 168 | Sky600 = props.ColorRGBA(2, 132, 199, 255) 169 | Sky700 = props.ColorRGBA(3, 105, 161, 255) 170 | Sky800 = props.ColorRGBA(7, 89, 133, 255) 171 | Sky900 = props.ColorRGBA(12, 74, 110, 255) 172 | Sky950 = props.ColorRGBA(8, 47, 73, 255) 173 | Blue50 = props.ColorRGBA(239, 246, 255, 255) 174 | Blue100 = props.ColorRGBA(219, 234, 254, 255) 175 | Blue200 = props.ColorRGBA(191, 219, 254, 255) 176 | Blue300 = props.ColorRGBA(147, 197, 253, 255) 177 | Blue400 = props.ColorRGBA(96, 165, 250, 255) 178 | Blue500 = props.ColorRGBA(59, 130, 246, 255) 179 | Blue600 = props.ColorRGBA(37, 99, 235, 255) 180 | Blue700 = props.ColorRGBA(29, 78, 216, 255) 181 | Blue800 = props.ColorRGBA(30, 64, 175, 255) 182 | Blue900 = props.ColorRGBA(30, 58, 138, 255) 183 | Blue950 = props.ColorRGBA(23, 37, 84, 255) 184 | Indigo50 = props.ColorRGBA(238, 242, 255, 255) 185 | Indigo100 = props.ColorRGBA(224, 231, 255, 255) 186 | Indigo200 = props.ColorRGBA(199, 210, 254, 255) 187 | Indigo300 = props.ColorRGBA(165, 180, 252, 255) 188 | Indigo400 = props.ColorRGBA(129, 140, 248, 255) 189 | Indigo500 = props.ColorRGBA(99, 102, 241, 255) 190 | Indigo600 = props.ColorRGBA(79, 70, 229, 255) 191 | Indigo700 = props.ColorRGBA(67, 56, 202, 255) 192 | Indigo800 = props.ColorRGBA(55, 48, 163, 255) 193 | Indigo900 = props.ColorRGBA(49, 46, 129, 255) 194 | Indigo950 = props.ColorRGBA(30, 27, 75, 255) 195 | Violet50 = props.ColorRGBA(245, 243, 255, 255) 196 | Violet100 = props.ColorRGBA(237, 233, 254, 255) 197 | Violet200 = props.ColorRGBA(221, 214, 254, 255) 198 | Violet300 = props.ColorRGBA(196, 181, 253, 255) 199 | Violet400 = props.ColorRGBA(167, 139, 250, 255) 200 | Violet500 = props.ColorRGBA(139, 92, 246, 255) 201 | Violet600 = props.ColorRGBA(124, 58, 237, 255) 202 | Violet700 = props.ColorRGBA(109, 40, 217, 255) 203 | Violet800 = props.ColorRGBA(91, 33, 182, 255) 204 | Violet900 = props.ColorRGBA(76, 29, 149, 255) 205 | Violet950 = props.ColorRGBA(46, 16, 101, 255) 206 | Purple50 = props.ColorRGBA(250, 245, 255, 255) 207 | Purple100 = props.ColorRGBA(243, 232, 255, 255) 208 | Purple200 = props.ColorRGBA(233, 213, 255, 255) 209 | Purple300 = props.ColorRGBA(216, 180, 254, 255) 210 | Purple400 = props.ColorRGBA(192, 132, 252, 255) 211 | Purple500 = props.ColorRGBA(168, 85, 247, 255) 212 | Purple600 = props.ColorRGBA(147, 51, 234, 255) 213 | Purple700 = props.ColorRGBA(126, 34, 206, 255) 214 | Purple800 = props.ColorRGBA(107, 33, 168, 255) 215 | Purple900 = props.ColorRGBA(88, 28, 135, 255) 216 | Purple950 = props.ColorRGBA(59, 7, 100, 255) 217 | Fuchsia50 = props.ColorRGBA(253, 244, 255, 255) 218 | Fuchsia100 = props.ColorRGBA(250, 232, 255, 255) 219 | Fuchsia200 = props.ColorRGBA(245, 208, 254, 255) 220 | Fuchsia300 = props.ColorRGBA(240, 171, 252, 255) 221 | Fuchsia400 = props.ColorRGBA(232, 121, 249, 255) 222 | Fuchsia500 = props.ColorRGBA(217, 70, 239, 255) 223 | Fuchsia600 = props.ColorRGBA(192, 38, 211, 255) 224 | Fuchsia700 = props.ColorRGBA(162, 28, 175, 255) 225 | Fuchsia800 = props.ColorRGBA(134, 25, 143, 255) 226 | Fuchsia900 = props.ColorRGBA(112, 26, 117, 255) 227 | Fuchsia950 = props.ColorRGBA(74, 4, 78, 255) 228 | Pink50 = props.ColorRGBA(253, 242, 248, 255) 229 | Pink100 = props.ColorRGBA(252, 231, 243, 255) 230 | Pink200 = props.ColorRGBA(251, 207, 232, 255) 231 | Pink300 = props.ColorRGBA(249, 168, 212, 255) 232 | Pink400 = props.ColorRGBA(244, 114, 182, 255) 233 | Pink500 = props.ColorRGBA(236, 72, 153, 255) 234 | Pink600 = props.ColorRGBA(219, 39, 119, 255) 235 | Pink700 = props.ColorRGBA(190, 24, 93, 255) 236 | Pink800 = props.ColorRGBA(157, 23, 77, 255) 237 | Pink900 = props.ColorRGBA(131, 24, 67, 255) 238 | Pink950 = props.ColorRGBA(80, 7, 36, 255) 239 | Rose50 = props.ColorRGBA(255, 241, 242, 255) 240 | Rose100 = props.ColorRGBA(255, 228, 230, 255) 241 | Rose200 = props.ColorRGBA(254, 205, 211, 255) 242 | Rose300 = props.ColorRGBA(253, 164, 175, 255) 243 | Rose400 = props.ColorRGBA(251, 113, 133, 255) 244 | Rose500 = props.ColorRGBA(244, 63, 94, 255) 245 | Rose600 = props.ColorRGBA(225, 29, 72, 255) 246 | Rose700 = props.ColorRGBA(190, 18, 60, 255) 247 | Rose800 = props.ColorRGBA(159, 18, 57, 255) 248 | Rose900 = props.ColorRGBA(136, 19, 55, 255) 249 | Rose950 = props.ColorRGBA(76, 5, 25, 255) 250 | ) 251 | -------------------------------------------------------------------------------- /variables/sizes.go: -------------------------------------------------------------------------------- 1 | package variables 2 | 3 | import "github.com/AccentDesign/gcss/props" 4 | 5 | var ( 6 | Size0 = props.UnitRaw(0) 7 | Size0H = props.UnitRem(0.125) 8 | Size1 = props.UnitRem(0.25) 9 | Size1H = props.UnitRem(0.375) 10 | Size2 = props.UnitRem(0.5) 11 | Size2H = props.UnitRem(0.625) 12 | Size3 = props.UnitRem(0.75) 13 | Size3H = props.UnitRem(0.875) 14 | Size4 = props.UnitRem(1) 15 | Size5 = props.UnitRem(1.25) 16 | Size6 = props.UnitRem(1.5) 17 | Size7 = props.UnitRem(1.75) 18 | Size8 = props.UnitRem(2) 19 | Size9 = props.UnitRem(2.25) 20 | Size10 = props.UnitRem(2.5) 21 | Size11 = props.UnitRem(2.75) 22 | Size12 = props.UnitRem(3) 23 | Size14 = props.UnitRem(3.5) 24 | Size16 = props.UnitRem(4) 25 | Size20 = props.UnitRem(5) 26 | Size24 = props.UnitRem(6) 27 | Size28 = props.UnitRem(7) 28 | Size32 = props.UnitRem(8) 29 | Size36 = props.UnitRem(9) 30 | Size40 = props.UnitRem(10) 31 | Size44 = props.UnitRem(11) 32 | Size48 = props.UnitRem(12) 33 | Size52 = props.UnitRem(13) 34 | Size56 = props.UnitRem(14) 35 | Size60 = props.UnitRem(15) 36 | Size64 = props.UnitRem(16) 37 | Size72 = props.UnitRem(18) 38 | Size80 = props.UnitRem(20) 39 | Size96 = props.UnitRem(24) 40 | Full = props.UnitPercent(100) 41 | FullRounded = props.UnitPx(9999) 42 | FullScreenHeight = props.UnitVh(100) 43 | FullScreenWidth = props.UnitVw(100) 44 | ) 45 | --------------------------------------------------------------------------------