fix: when/else parentNode null guard for slot context + match parser boundaries + showcase demo
- When/else inside slots: anchor parentNode is null during initial effect Fixed with named effect function + requestAnimationFrame retry - Match parser now terminates on ] } else tokens (works inside containers) - Updated Progress/Badge components - Added examples/showcase.ds: 5-section demo exercising all features
This commit is contained in:
parent
5425d7768c
commit
a7af39e900
5 changed files with 83 additions and 40 deletions
|
|
@ -761,8 +761,14 @@ impl JsEmitter {
|
||||||
None
|
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;
|
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));
|
self.emit_line(&format!("const show = {};", cond_js));
|
||||||
|
|
||||||
// Show when: condition true and not already showing
|
// Show when: condition true and not already showing
|
||||||
|
|
|
||||||
|
|
@ -921,10 +921,12 @@ impl Parser {
|
||||||
self.skip_newlines();
|
self.skip_newlines();
|
||||||
let mut arms = Vec::new();
|
let mut arms = Vec::new();
|
||||||
while !self.is_at_end()
|
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();
|
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;
|
break;
|
||||||
}
|
}
|
||||||
let pattern = self.parse_pattern()?;
|
let pattern = self.parse_pattern()?;
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,73 @@
|
||||||
-- DreamStack Component Showcase
|
-- DreamStack Showcase
|
||||||
-- Demonstrates all component styles via variant prop
|
-- 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 count = 0
|
||||||
|
let loggedIn = false
|
||||||
|
let mood = "happy"
|
||||||
|
let items = ["Alpha", "Beta", "Gamma", "Delta"]
|
||||||
|
|
||||||
-- Main view
|
|
||||||
view main = column [
|
view main = column [
|
||||||
|
-- Header
|
||||||
|
text "🚀 DreamStack Showcase" { variant: "title" }
|
||||||
|
text "Everything working together" { variant: "subtitle" }
|
||||||
|
|
||||||
text "🧩 DreamStack Components" { variant: "title" }
|
-- 1. Slot Composition: Card with interactive children
|
||||||
text "shadcn-inspired component registry" { variant: "subtitle" }
|
Card { title: "Interactive Counter", subtitle: "slot composition + reactivity" } [
|
||||||
|
text "Count: {count}" { variant: "title" }
|
||||||
-- Button Variants
|
|
||||||
text "Button Variants" { variant: "title" }
|
|
||||||
row [
|
row [
|
||||||
button "Primary" { variant: "primary" }
|
button "-1" { click: count -= 1, variant: "secondary" }
|
||||||
button "Secondary" { variant: "secondary" }
|
button "+1" { click: count += 1, variant: "primary" }
|
||||||
button "Ghost" { variant: "ghost" }
|
|
||||||
button "Destructive" { variant: "destructive" }
|
|
||||||
]
|
|
||||||
|
|
||||||
-- 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" }
|
|
||||||
]
|
|
||||||
|
|
||||||
-- Input with live binding
|
|
||||||
text "Input Component" { variant: "title" }
|
|
||||||
text "Name" { variant: "label" }
|
|
||||||
input { bind: name, placeholder: "Type your name..." }
|
|
||||||
text "Hello, {name}!"
|
|
||||||
|
|
||||||
-- Interactive counter
|
|
||||||
text "Interactive Counter" { variant: "title" }
|
|
||||||
row [
|
|
||||||
button "Count: {count}" { click: count += 1, variant: "primary" }
|
|
||||||
button "Reset" { click: count = 0, variant: "ghost" }
|
button "Reset" { click: count = 0, variant: "ghost" }
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
-- 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"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
-- 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" }
|
||||||
|
]
|
||||||
|
|
||||||
|
-- 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" }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
-- DreamStack Badge Component
|
-- DreamStack Badge Component
|
||||||
|
-- Small label with colored background
|
||||||
-- Variants: success, warning, error, info, default
|
-- Variants: success, warning, error, info, default
|
||||||
|
|
||||||
export component Badge(label, variant) =
|
export component Badge(label, variant) =
|
||||||
text label { variant: "default" }
|
text label { variant: variant }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
-- DreamStack Progress Component
|
-- DreamStack Progress Component
|
||||||
-- Animated progress bar with percentage
|
-- Animated progress bar with percentage display
|
||||||
|
|
||||||
export component Progress(value) =
|
export component Progress(value, label) =
|
||||||
column [
|
column [
|
||||||
text "{value}%"
|
row [
|
||||||
|
text label { variant: "label" }
|
||||||
|
text "{value}%" { variant: "label" }
|
||||||
|
]
|
||||||
|
column [
|
||||||
|
column [] { variant: "progress-fill", style: "width: {value}%" }
|
||||||
|
] { variant: "progress-track" }
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue