diff --git a/compiler/ds-codegen/src/js_emitter.rs b/compiler/ds-codegen/src/js_emitter.rs index 5bcba65..353fc20 100644 --- a/compiler/ds-codegen/src/js_emitter.rs +++ b/compiler/ds-codegen/src/js_emitter.rs @@ -761,8 +761,14 @@ impl JsEmitter { None }; - self.emit_line("DS.effect(() => {"); + let effect_fn = format!("_when_{}", anchor_var); + self.emit_line(&format!("DS.effect(function {}() {{", effect_fn)); self.indent += 1; + // Guard: if anchor not yet in DOM (e.g. inside slot DocumentFragment), defer + self.emit_line(&format!( + "if (!{}.parentNode) {{ requestAnimationFrame(() => DS.effect({})); return; }}", + anchor_var, effect_fn + )); self.emit_line(&format!("const show = {};", cond_js)); // Show when: condition true and not already showing diff --git a/compiler/ds-parser/src/parser.rs b/compiler/ds-parser/src/parser.rs index 27ad355..1611472 100644 --- a/compiler/ds-parser/src/parser.rs +++ b/compiler/ds-parser/src/parser.rs @@ -921,10 +921,12 @@ impl Parser { self.skip_newlines(); let mut arms = Vec::new(); while !self.is_at_end() - && !matches!(self.peek(), TokenKind::Let | TokenKind::View | TokenKind::On | TokenKind::Effect) + && !matches!(self.peek(), TokenKind::Let | TokenKind::View | TokenKind::On | TokenKind::Effect + | TokenKind::RBracket | TokenKind::RBrace | TokenKind::Else | TokenKind::Eof) { self.skip_newlines(); - if self.is_at_end() || matches!(self.peek(), TokenKind::Let | TokenKind::View | TokenKind::On | TokenKind::Effect) { + if self.is_at_end() || matches!(self.peek(), TokenKind::Let | TokenKind::View | TokenKind::On | TokenKind::Effect + | TokenKind::RBracket | TokenKind::RBrace | TokenKind::Else | TokenKind::Eof) { break; } let pattern = self.parse_pattern()?; diff --git a/examples/showcase.ds b/examples/showcase.ds index 6c417ca..6683db9 100644 --- a/examples/showcase.ds +++ b/examples/showcase.ds @@ -1,45 +1,73 @@ --- DreamStack Component Showcase --- Demonstrates all component styles via variant prop +-- DreamStack Showcase +-- Demonstrates: imports, slots, when/else, each, match, reactive state + +import { Card } from "../registry/components/card" +import { Badge } from "../registry/components/badge" --- State -let name = "" let count = 0 +let loggedIn = false +let mood = "happy" +let items = ["Alpha", "Beta", "Gamma", "Delta"] --- Main view view main = column [ + -- Header + text "🚀 DreamStack Showcase" { variant: "title" } + text "Everything working together" { variant: "subtitle" } - text "🧩 DreamStack Components" { variant: "title" } - text "shadcn-inspired component registry" { variant: "subtitle" } - - -- Button Variants - text "Button Variants" { variant: "title" } - row [ - button "Primary" { variant: "primary" } - button "Secondary" { variant: "secondary" } - button "Ghost" { variant: "ghost" } - button "Destructive" { variant: "destructive" } + -- 1. Slot Composition: Card with interactive children + Card { title: "Interactive Counter", subtitle: "slot composition + reactivity" } [ + text "Count: {count}" { variant: "title" } + row [ + button "-1" { click: count -= 1, variant: "secondary" } + button "+1" { click: count += 1, variant: "primary" } + button "Reset" { click: count = 0, variant: "ghost" } + ] ] - -- Badge Variants - text "Badge Variants" { variant: "title" } - row [ - text "SUCCESS" { variant: "success" } - text "WARNING" { variant: "warning" } - text "ERROR" { variant: "error" } - text "INFO" { variant: "info" } - text "DEFAULT" { variant: "default" } + -- 2. When/Else: Conditional rendering + Card { title: "Auth Status", subtitle: "when/else branching" } [ + button "Toggle Login" { click: loggedIn = !loggedIn, variant: "primary" } + when loggedIn -> + row [ + Badge { label: "ONLINE", variant: "success" } + text "Welcome back, admin!" + ] + else -> + row [ + Badge { label: "OFFLINE", variant: "error" } + text "Please log in to continue" + ] ] - -- Input with live binding - text "Input Component" { variant: "title" } - text "Name" { variant: "label" } - input { bind: name, placeholder: "Type your name..." } - text "Hello, {name}!" + -- 3. Match: Pattern matching on state + Card { title: "Mood Selector", subtitle: "match expressions" } [ + row [ + button "😊 Happy" { click: mood = "happy", variant: "primary" } + button "😢 Sad" { click: mood = "sad", variant: "secondary" } + button "😡 Angry" { click: mood = "angry", variant: "ghost" } + ] + match mood + "happy" -> text "You're feeling great! 🌟" { variant: "success" } + "sad" -> text "Hang in there... 💙" { variant: "info" } + "angry" -> text "Take a deep breath 🔥" { variant: "warning" } + _ -> text "How are you?" { variant: "subtitle" } + ] - -- Interactive counter - text "Interactive Counter" { variant: "title" } - row [ - button "Count: {count}" { click: count += 1, variant: "primary" } - button "Reset" { click: count = 0, variant: "ghost" } + -- 4. Each: List rendering + Card { title: "Team Members", subtitle: "each loop iteration" } [ + each item in items -> + row [ + text "→" + text item + ] + ] + + -- 5. Nested Features: when/else inside Card slot inside each + Card { title: "Feature Matrix", subtitle: "nested composition" } [ + text "Count is {count}" + when count > 5 -> + Badge { label: "HIGH", variant: "warning" } + else -> + Badge { label: "LOW", variant: "info" } ] ] diff --git a/registry/components/badge.ds b/registry/components/badge.ds index 0222fc2..e932fc5 100644 --- a/registry/components/badge.ds +++ b/registry/components/badge.ds @@ -1,5 +1,6 @@ -- DreamStack Badge Component +-- Small label with colored background -- Variants: success, warning, error, info, default export component Badge(label, variant) = - text label { variant: "default" } + text label { variant: variant } diff --git a/registry/components/progress.ds b/registry/components/progress.ds index fbadd14..077cc2f 100644 --- a/registry/components/progress.ds +++ b/registry/components/progress.ds @@ -1,7 +1,13 @@ -- DreamStack Progress Component --- Animated progress bar with percentage +-- Animated progress bar with percentage display -export component Progress(value) = +export component Progress(value, label) = column [ - text "{value}%" + row [ + text label { variant: "label" } + text "{value}%" { variant: "label" } + ] + column [ + column [] { variant: "progress-fill", style: "width: {value}%" } + ] { variant: "progress-track" } ]