Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,33 @@ class Spacing < CSS::Stylesheet
end
```

### Modern Patterns (`clamp`, `@supports`, `@layer`)

Use helpers for common modern CSS patterns while keeping the existing DSL style.

```crystal
class Modern < CSS::Stylesheet
rule h1 do
font_size clamp(1.rem, calc(2.vw + 1.rem), 3.rem)
end

supports(decl(:display, :grid) & decl(:gap, 1.rem)) do
rule ".grid" do
display :grid
gap 1.rem
end
end

layer_order :reset, :base

layer :base do
rule body do
margin 0
end
end
end
```

### Backgrounds and Gradients

Compose layered backgrounds, linear/radial gradients, and opacity values with readable builders.
Expand Down
19 changes: 19 additions & 0 deletions spec/clamp_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require "./spec_helper"

class ClampStyle < CSS::Stylesheet
rule div do
width clamp(0, 50.percent, 100.px)
end
end

describe "ClampStyle.to_s" do
it "should render clamp() function values" do
expected = <<-CSS
div {
width: clamp(0, 50%, 100px);
}
CSS

ClampStyle.to_s.should eq(expected)
end
end
55 changes: 55 additions & 0 deletions spec/layer_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require "./spec_helper"

class LayerStyle < CSS::Stylesheet
layer_order :reset, :base_layer

layer :base_layer do
rule body do
margin 0
end

supports(decl(:display, :grid)) do
rule ".grid" do
display :grid
end
end
end

media(max_width 600.px) do
layer do
rule body do
margin 10.px
end
end
end
end

describe "LayerStyle.to_s" do
it "should return the correct CSS" do
expected = <<-CSS
@layer reset, base-layer;

@layer base-layer {
body {
margin: 0;
}

@supports (display: grid) {
.grid {
display: grid;
}
}
}

@media (max-width: 600px) {
@layer {
body {
margin: 10px;
}
}
}
CSS

LayerStyle.to_s.should eq(expected)
end
end
61 changes: 61 additions & 0 deletions spec/modern_patterns_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
require "./spec_helper"

class ModernPatternsStyle < CSS::Stylesheet
rule h1 do
font_size clamp(1.rem, calc(2.vw + 1.rem), 3.rem)
end

supports(decl(:display, :grid) & decl(:gap, 1.rem)) do
rule div do
display :grid
gap 1.rem
end

media(max_width 600.px) do
rule div do
display :block
end
end
end

layer_order :reset, :base

layer :base do
rule body do
margin 0
end
end
end

describe "ModernPatternsStyle.to_s" do
it "should return the correct CSS" do
expected = <<-CSS
h1 {
font-size: clamp(1rem, calc(2vw + 1rem), 3rem);
}

@supports (display: grid) and (gap: 1rem) {
div {
display: grid;
gap: 1rem;
}

@media (max-width: 600px) {
div {
display: block;
}
}
}

@layer reset, base;

@layer base {
body {
margin: 0;
}
}
CSS

ModernPatternsStyle.to_s.should eq(expected)
end
end
75 changes: 75 additions & 0 deletions spec/supports_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
require "./spec_helper"

class SupportsStyle < CSS::Stylesheet
supports(selector("a > b")) do
rule a do
color :red
end
end

supports(raw("(display: grid)")) do
rule div do
display :grid
end
end

supports(group(decl(:display, :grid) | decl(:display, :flex)) & decl(:gap, 1.rem)) do
rule ".x" do
opacity 1
end
end

supports(negate(decl(:display, :grid) & decl(:gap, 1.rem))) do
rule ".n" do
display :block
end
end

supports(decl(:display, :grid)) do
supports(decl(:gap, 1.rem)) do
rule div do
gap 1.rem
end
end
end
end

describe "SupportsStyle.to_s" do
it "should return the correct CSS" do
expected = <<-CSS
@supports selector(a > b) {
a {
color: red;
}
}

@supports (display: grid) {
div {
display: grid;
}
}

@supports ((display: grid) or (display: flex)) and (gap: 1rem) {
.x {
opacity: 1;
}
}

@supports not ((display: grid) and (gap: 1rem)) {
.n {
display: block;
}
}

@supports (display: grid) {
@supports (gap: 1rem) {
div {
gap: 1rem;
}
}
}
CSS

SupportsStyle.to_s.should eq(expected)
end
end
35 changes: 35 additions & 0 deletions src/css/clamp_function_call.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require "./function_call"

module CSS
# Represents a `clamp()` function call.
#
# Typically used with length/percentage values (for example for responsive font sizes).
struct ClampFunctionCall
include FunctionCall

getter min : String
getter preferred : String
getter max : String

def initialize(min, preferred, max)
@min = format(min)
@preferred = format(preferred)
@max = format(max)
end

def function_name : String
"clamp"
end

def arguments : String
"#{min}, #{preferred}, #{max}"
end

private def format(value) : String
return value if value.is_a?(String)
return value.to_css_value.to_s if value.responds_to?(:to_css_value)

value.to_s
end
end
end
60 changes: 60 additions & 0 deletions src/css/layer_stylesheet.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require "../stylesheet"

module CSS
abstract class LayerStylesheet < CSS::Stylesheet
module ClassMethods
abstract def layer_name : String?
end

extend ClassMethods

def self.format_layer_name(name : String) : String
name
end

def self.format_layer_name(name : Symbol) : String
name.to_s.gsub('_', '-')
end

macro inherited
macro finished
def self.to_s(io : IO)
io << "@layer"
if (name = layer_name)
io << " "
io << name
end
io << " {\n"
previous_def
io << "\n}"
end
end
end

macro rule(*selector_expressions, &blk)
def self.to_s(io : IO)
{% if @type.class.methods.map(&.name.stringify).includes?("to_s") %}
previous_def
io << "\n\n"
{% end %}

make_rule(io, { {% for selector_expression in selector_expressions %} make_selector({{selector_expression}}), {% end %} }, 1) {{blk}}
end
end

macro embed(klass_name)
def self.to_s(io : IO)
{% if @type.class.methods.map(&.name.stringify).includes?("to_s") %}
previous_def
io << "\n\n"
{% end %}

%embedded = String.build do |%embedded_io|
{{klass_name}}.to_s(%embedded_io)
end

write_indented(io, %embedded, 1)
end
end
end
end
15 changes: 15 additions & 0 deletions src/css/media_stylesheet.cr
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,20 @@ module CSS
make_rule(io, { {% for selector_expression in selector_expressions %} make_selector({{selector_expression}}), {% end %} }, 1) {{blk}}
end
end

macro embed(klass_name)
def self.to_s(io : IO)
{% if @type.class.methods.map(&.name.stringify).includes?("to_s") %}
previous_def
io << "\n\n"
{% end %}

%embedded = String.build do |%embedded_io|
{{klass_name}}.to_s(%embedded_io)
end

write_indented(io, %embedded, 1)
end
end
end
end
Loading