Skip to content

Conversation

@tiye
Copy link
Member

@tiye tiye commented Dec 11, 2025

No description provided.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds enhanced CSS properties and builders to the respo_css library, introducing support for advanced CSS features like gradients, shadows, keyframe animations, and clip paths.

Key changes:

  • New enhanced CSS property types (border radius, box shadow, gradients, transitions, keyframes, clip paths, text shadows, pointer events, letter spacing)
  • Integration of enhanced properties into the main respo_style function
  • Test coverage for core enhanced features
  • Version bump from 0.1.3 to 0.1.4

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
respo_css_enhanced.mbt New file defining enhanced CSS property types and builders including CssBorderRadius, CssBoxShadow, CssGradient, CssKeyframes, CssClipPath, CssTextShadow, CssPointerEvents, CssLetterSpacing, CssTransitionProperty, and CssBackgroundImage
respo_css_enhanced_test.mbt New test file with basic tests for border radius, box shadow, linear gradient, and keyframes functionality
respo_css.mbt Integration of enhanced properties as optional parameters in the respo_style function
moon.mod.json Version bump from 0.1.3 to 0.1.4
Agents.md Added comprehensive MoonBit project documentation (appears to be generated)
.github/workflows/release.yml New GitHub Actions workflow for automated package publishing
.gitattributes Git configuration to mark certain files as linguist-generated

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

background_image? : CssBackgroundImage,
clip_path? : CssClipPath,
text_shadow? : CssTextShadow,
letter_spacing_type? : CssLetterSpacing,
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name "letter_spacing_type" is inconsistent with the naming pattern used for other similar parameters. Other parameters are named after the property they represent (e.g., "border_radius_obj", "box_shadow_obj", "background_image", "clip_path", "text_shadow", "pointer_events"). The "_type" suffix doesn't add value here and breaks the naming consistency. Consider renaming to "letter_spacing" to match the pattern.

Suggested change
letter_spacing_type? : CssLetterSpacing,
letter_spacing? : CssLetterSpacing,

Copilot uses AI. Check for mistakes.
if text_shadow is Some(value) {
style.push(("text-shadow", value.to_string()))
}
if letter_spacing_type is Some(value) {
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name "letter_spacing_type" should be consistent with the parameter name. If the parameter is renamed to "letter_spacing" (as suggested in another comment), this should also be updated to "letter_spacing" for consistency.

Suggested change
if letter_spacing_type is Some(value) {
if letter_spacing is Some(value) {

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +408
///|
/// Enhanced CSS Properties and Builders for respo_css
///|
/// Border Radius Properties
pub(all) struct CssBorderRadius {
top_left : CssSize?
top_right : CssSize?
bottom_left : CssSize?
bottom_right : CssSize?
}

///|
pub fn CssBorderRadius::new(
top_left? : CssSize,
top_right? : CssSize,
bottom_left? : CssSize,
bottom_right? : CssSize,
) -> CssBorderRadius {
CssBorderRadius::{ top_left, top_right, bottom_left, bottom_right }
}

///|
pub fn CssBorderRadius::to_string(self : CssBorderRadius) -> String {
let parts = []
if self.top_left is Some(v) {
parts.push("border-top-left-radius: " + v.to_string())
}
if self.top_right is Some(v) {
parts.push("border-top-right-radius: " + v.to_string())
}
if self.bottom_left is Some(v) {
parts.push("border-bottom-left-radius: " + v.to_string())
}
if self.bottom_right is Some(v) {
parts.push("border-bottom-right-radius: " + v.to_string())
}
parts.join(";")
}

///|
pub fn CssBorderRadius::to_styles(
self : CssBorderRadius,
) -> Array[(String, String)] {
let styles = []
if self.top_left is Some(v) {
styles.push(("border-top-left-radius", v.to_string()))
}
if self.top_right is Some(v) {
styles.push(("border-top-right-radius", v.to_string()))
}
if self.bottom_left is Some(v) {
styles.push(("border-bottom-left-radius", v.to_string()))
}
if self.bottom_right is Some(v) {
styles.push(("border-bottom-right-radius", v.to_string()))
}
styles
}

///|
/// Box Shadow Builder
pub(all) enum CssBoxShadowType {
Outer
Inset
} derive(Eq)

///|
pub(all) struct CssBoxShadow {
x : CssSize
y : CssSize
blur : CssSize
spread : CssSize?
color : CssColor
shadow_type : CssBoxShadowType
}

///|
pub fn CssBoxShadow::new(
x? : CssSize = Px(0.0),
y? : CssSize = Px(0.0),
blur? : CssSize = Px(0.0),
spread? : CssSize,
color? : CssColor = Black,
shadow_type? : CssBoxShadowType = Outer,
) -> CssBoxShadow {
CssBoxShadow::{ x, y, blur, spread, color, shadow_type }
}

///|
pub impl Show for CssBoxShadow with output(self, logger) {
let mut ret = ""
if self.shadow_type == Inset {
ret = ret + "inset "
}
ret = ret +
self.x.to_string() +
" " +
self.y.to_string() +
" " +
self.blur.to_string() +
" "
if self.spread is Some(v) {
ret = ret + v.to_string() + " "
}
ret = ret + self.color.to_string()
logger.write_string(ret)
}

///|
/// Linear Gradient Builder
pub(all) enum CssGradientAngle {
Deg(Int)
Direction(String)
} derive(Eq)

///|
pub(all) struct CssGradientStop {
color : CssColor
position : CssSize?
} derive(Eq)

///|
pub fn CssGradientStop::new(
color : CssColor,
position? : CssSize,
) -> CssGradientStop {
CssGradientStop::{ color, position }
}

///|
pub(all) enum CssGradient {
Linear(CssGradientAngle?, Array[CssGradientStop])
Radial(Array[CssGradientStop])
Conic(CssGradientAngle?, Array[CssGradientStop])
} derive(Eq)

///|
pub impl Show for CssGradient with output(self, logger) {
let ret = match self {
Linear(angle, stops) => {
let mut result = "linear-gradient("
match angle {
Some(Deg(d)) => result = result + d.to_string() + "deg, "
Some(Direction(dir)) => result = result + dir + ", "
None => ()
}
let stop_strs = stops.map(fn(stop) {
let color_str = stop.color.to_string()
match stop.position {
Some(pos) => color_str + " " + pos.to_string()
None => color_str
}
})
result = result + stop_strs.join(", ")
result + ")"
}
Radial(stops) => {
let mut result = "radial-gradient("
let stop_strs = stops.map(fn(stop) {
let color_str = stop.color.to_string()
match stop.position {
Some(pos) => color_str + " " + pos.to_string()
None => color_str
}
})
result = result + stop_strs.join(", ")
result + ")"
}
Conic(angle, stops) => {
let mut result = "conic-gradient("
match angle {
Some(Deg(d)) => result = result + "from " + d.to_string() + "deg, "
Some(Direction(dir)) => result = result + "from " + dir + ", "
None => ()
}
let stop_strs = stops.map(fn(stop) {
let color_str = stop.color.to_string()
match stop.position {
Some(pos) => color_str + " " + pos.to_string()
None => color_str
}
})
result = result + stop_strs.join(", ")
result + ")"
}
}
logger.write_string(ret)
}

///|
/// Transition Builder
pub(all) struct CssTransitionProperty {
property : String
duration : CssDuration
timing_function : CssTimingFunction?
delay : CssDuration?
}

///|
pub fn CssTransitionProperty::new(
property : String,
duration : CssDuration,
timing_function? : CssTimingFunction,
delay? : CssDuration,
) -> CssTransitionProperty {
CssTransitionProperty::{ property, duration, timing_function, delay }
}

///|
pub impl Show for CssTransitionProperty with output(self, logger) {
let mut ret = self.property + " " + self.duration.to_string()
if self.timing_function is Some(tf) {
ret = ret + " " + tf.to_string()
}
if self.delay is Some(d) {
ret = ret + " " + d.to_string()
}
logger.write_string(ret)
}

///|
/// Keyframes Definition
pub(all) struct CssKeyframe {
position : Int
styles : Array[(String, String)]
}

///|
pub fn CssKeyframe::new(
position : Int,
styles : Array[(String, String)],
) -> CssKeyframe {
CssKeyframe::{ position, styles }
}

///|
pub(all) struct CssKeyframes {
name : String
keyframes : Array[CssKeyframe]
}

///|
pub fn CssKeyframes::new(
name : String,
keyframes : Array[CssKeyframe],
) -> CssKeyframes {
CssKeyframes::{ name, keyframes }
}

///|
pub fn CssKeyframes::to_css_string(self : CssKeyframes) -> String {
let mut css = "@keyframes " + self.name + " {\n"
for keyframe in self.keyframes {
css = css + " " + keyframe.position.to_string() + "% {\n"
for pair in keyframe.styles {
let (prop, value) = pair
css = css + " " + prop + ": " + value + ";\n"
}
css = css + " }\n"
}
css = css + "}\n"
css
}

///|
/// Pointer Events
pub(all) enum CssPointerEvents {
Auto
None
Visible
Painted
Fill
Stroke
All
Inherit
} derive(Eq)

///|
pub impl Show for CssPointerEvents with output(self, logger) {
let ret = match self {
Auto => "auto"
None => "none"
Visible => "visible"
Painted => "painted"
Fill => "fill"
Stroke => "stroke"
All => "all"
Inherit => "inherit"
}
logger.write_string(ret)
}

///|
/// Letter Spacing
pub(all) enum CssLetterSpacing {
Normal
Value(CssSize)
Custom(String)
} derive(Eq)

///|
pub impl Show for CssLetterSpacing with output(self, logger) {
let ret = match self {
Normal => "normal"
Value(size) => size.to_string()
Custom(value) => value
}
logger.write_string(ret)
}

///|
/// Text Shadow
pub(all) struct CssTextShadow {
x : CssSize
y : CssSize
blur : CssSize
color : CssColor
}

///|
pub fn CssTextShadow::new(
x? : CssSize = Px(0.0),
y? : CssSize = Px(0.0),
blur? : CssSize = Px(0.0),
color? : CssColor = Black,
) -> CssTextShadow {
CssTextShadow::{ x, y, blur, color }
}

///|
pub impl Show for CssTextShadow with output(self, logger) {
let ret = self.x.to_string() +
" " +
self.y.to_string() +
" " +
self.blur.to_string() +
" " +
self.color.to_string()
logger.write_string(ret)
}

///|
/// Clip Path
pub(all) enum CssClipPath {
Circle(CssSize, CssSize, CssSize)
Ellipse(CssSize, CssSize, CssSize, CssSize)
Inset(CssSize, CssSize, CssSize, CssSize)
Polygon(Array[String])
Url(String)
Custom(String)
} derive(Eq)

///|
pub impl Show for CssClipPath with output(self, logger) {
let ret = match self {
Circle(radius, x, y) => {
let r = radius.to_string()
let px = x.to_string()
let py = y.to_string()
"circle(" + r + " at " + px + " " + py + ")"
}
Ellipse(rx, ry, x, y) => {
let rx_str = rx.to_string()
let ry_str = ry.to_string()
let px = x.to_string()
let py = y.to_string()
"ellipse(" + rx_str + " " + ry_str + " at " + px + " " + py + ")"
}
Inset(top, right, bottom, left) => {
let t = top.to_string()
let r = right.to_string()
let b = bottom.to_string()
let l = left.to_string()
"inset(" + t + " " + r + " " + b + " " + l + ")"
}
Polygon(points) => "polygon(" + points.join(", ") + ")"
Url(url) => "url(" + url + ")"
Custom(value) => value
}
logger.write_string(ret)
}

///|
/// Background Image
pub(all) enum CssBackgroundImage {
LinearGradient(CssGradient)
RadialGradient(CssGradient)
ConicGradient(CssGradient)
Url(String)
Multiple(Array[CssBackgroundImage])
None
Custom(String)
} derive(Eq)

///|
pub impl Show for CssBackgroundImage with output(self, logger) {
let ret = match self {
LinearGradient(gradient) => gradient.to_string()
RadialGradient(gradient) => gradient.to_string()
ConicGradient(gradient) => gradient.to_string()
Url(url) => "url(" + url + ")"
Multiple(images) => images.map(fn(img) { img.to_string() }).join(", ")
None => "none"
Custom(value) => value
}
logger.write_string(ret)
}
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several public types lack documentation comments explaining their purpose and usage:

  • CssBorderRadius (struct and fields)
  • CssBoxShadowType (enum and variants)
  • CssBoxShadow (struct and fields)
  • CssGradientAngle (enum and variants)
  • CssGradientStop (struct and fields)
  • CssGradient (enum and variants)
  • CssTransitionProperty (struct and fields)
  • CssKeyframe (struct and fields)
  • CssKeyframes (struct and fields)
  • CssPointerEvents (only basic comment, no variant documentation)
  • CssLetterSpacing (only basic comment, no variant documentation)
  • CssTextShadow (only basic comment, no field documentation)
  • CssClipPath (only basic comment, no variant documentation)
  • CssBackgroundImage (only basic comment, no variant documentation)

Consider adding comprehensive documentation with usage examples, similar to the documentation style used in respo_css.mbt. This will help users understand how to use these new features correctly.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +50
///|
test "enhanced css properties" {
let style = @respo_css.respo_style(
border_radius_obj=@respo_css.CssBorderRadius::new(
top_left=@respo_css.CssSize::Px(10.0),
bottom_right=@respo_css.CssSize::Px(5.0),
),
box_shadow_obj=@respo_css.CssBoxShadow::new(
x=@respo_css.CssSize::Px(2.0),
y=@respo_css.CssSize::Px(2.0),
blur=@respo_css.CssSize::Px(4.0),
color=@respo_css.CssColor::Black,
),
background_image=@respo_css.CssBackgroundImage::LinearGradient(
@respo_css.CssGradient::Linear(
Some(@respo_css.CssGradientAngle::Deg(45)),
[
@respo_css.CssGradientStop::new(@respo_css.CssColor::Red),
@respo_css.CssGradientStop::new(@respo_css.CssColor::Blue),
],
),
),
)
inspect(
style,
content="border-top-left-radius:10px; border-bottom-right-radius:5px; box-shadow:2px 2px 4px black; background-image:linear-gradient(45deg, red, blue); ",
)
}

///|
test "keyframes" {
let kf = @respo_css.CssKeyframes::new("fade-in", [
@respo_css.CssKeyframe::new(0, [("opacity", "0")]),
@respo_css.CssKeyframe::new(100, [("opacity", "1")]),
])
inspect(
kf.to_css_string(),
content=(
#|@keyframes fade-in {
#| 0% {
#| opacity: 0;
#| }
#| 100% {
#| opacity: 1;
#| }
#|}
#|
),
)
}
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test file only covers CssBorderRadius, CssBoxShadow, CssBackgroundImage with LinearGradient, and CssKeyframes. Several new types introduced in respo_css_enhanced.mbt lack test coverage:

  • CssTextShadow
  • CssClipPath (with Circle, Ellipse, Inset, Polygon variants)
  • CssPointerEvents (all enum variants)
  • CssLetterSpacing (all enum variants)
  • CssTransitionProperty
  • CssGradient Radial and Conic variants
  • CssBackgroundImage with RadialGradient, ConicGradient, Url, Multiple, None variants

Consider adding test cases for these missing types to ensure they work correctly and generate expected CSS output.

Copilot uses AI. Check for mistakes.
if self.bottom_right is Some(v) {
parts.push("border-bottom-right-radius: " + v.to_string())
}
parts.join(";")
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CssBorderRadius::to_string method joins parts with a semicolon but doesn't add a trailing semicolon or space. This is inconsistent with how CSS properties are typically formatted in the respo_style function output (which includes semicolons and spaces after each property). While to_styles is correctly used in the integration, having to_string return a different format could lead to confusion. Consider updating to_string to match the formatting pattern, or document why it differs.

Suggested change
parts.join(";")
if parts.length == 0 {
""
} else {
parts.join("; ") + "; "
}

Copilot uses AI. Check for mistakes.
}

///|
/// Linear Gradient Builder
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation comment says "Linear Gradient Builder" but the enum CssGradientAngle is actually used for both Linear and Conic gradients (as seen in the Show implementation). The comment should be updated to reflect that this is a more general gradient angle type, not specific to linear gradients. Consider changing to "Gradient Angle" or "Gradient Direction/Angle".

Suggested change
/// Linear Gradient Builder
/// Gradient Angle/Direction for Linear and Conic Gradients

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +186
pub impl Show for CssGradient with output(self, logger) {
let ret = match self {
Linear(angle, stops) => {
let mut result = "linear-gradient("
match angle {
Some(Deg(d)) => result = result + d.to_string() + "deg, "
Some(Direction(dir)) => result = result + dir + ", "
None => ()
}
let stop_strs = stops.map(fn(stop) {
let color_str = stop.color.to_string()
match stop.position {
Some(pos) => color_str + " " + pos.to_string()
None => color_str
}
})
result = result + stop_strs.join(", ")
result + ")"
}
Radial(stops) => {
let mut result = "radial-gradient("
let stop_strs = stops.map(fn(stop) {
let color_str = stop.color.to_string()
match stop.position {
Some(pos) => color_str + " " + pos.to_string()
None => color_str
}
})
result = result + stop_strs.join(", ")
result + ")"
}
Conic(angle, stops) => {
let mut result = "conic-gradient("
match angle {
Some(Deg(d)) => result = result + "from " + d.to_string() + "deg, "
Some(Direction(dir)) => result = result + "from " + dir + ", "
None => ()
}
let stop_strs = stops.map(fn(stop) {
let color_str = stop.color.to_string()
match stop.position {
Some(pos) => color_str + " " + pos.to_string()
None => color_str
}
})
result = result + stop_strs.join(", ")
result + ")"
}
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for mapping gradient stops to strings is duplicated three times (lines 148-154, 160-165, and 177-182). This code could be extracted into a helper function to reduce duplication and improve maintainability. For example:

fn format_gradient_stops(stops : Array[CssGradientStop]) -> String {
  let stop_strs = stops.map(fn(stop) {
    let color_str = stop.color.to_string()
    match stop.position {
      Some(pos) => color_str + " " + pos.to_string()
      None => color_str
    }
  })
  stop_strs.join(", ")
}

Then use it in all three gradient variants.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants