feat: container variant props, 11-component registry, rich dashboard
Parser:
- column/row/stack now parse trailing { props } after ]
- Enables: column [...] { variant: "card" }
Codegen:
- Container props dispatch: variant, class, click, style, layout
- variant_to_css() maps (tag, variant) → CSS class
- variant_map_js() for dynamic variants via inline JS map
- 230+ lines design system CSS (button/badge/card/dialog/toast/
progress/alert/separator/toggle/avatar/stat)
Registry (11 components):
- button, input, card, badge, dialog, toast
- NEW: progress, alert, separator, toggle, avatar
- All embedded via include_str! for offline use
Examples:
- showcase.ds: component gallery with variant prop
- dashboard.ds: admin dashboard with glassmorphism cards
This commit is contained in:
parent
7805b94704
commit
a290bc1891
16 changed files with 445 additions and 61 deletions
|
|
@ -1205,6 +1205,36 @@ const REGISTRY: &[RegistryItem] = &[
|
|||
source: include_str!("../../../registry/components/toast.ds"),
|
||||
deps: &[],
|
||||
},
|
||||
RegistryItem {
|
||||
name: "progress",
|
||||
description: "Animated progress bar with percentage",
|
||||
source: include_str!("../../../registry/components/progress.ds"),
|
||||
deps: &[],
|
||||
},
|
||||
RegistryItem {
|
||||
name: "alert",
|
||||
description: "Alert banner with info/warning/error/success variants",
|
||||
source: include_str!("../../../registry/components/alert.ds"),
|
||||
deps: &[],
|
||||
},
|
||||
RegistryItem {
|
||||
name: "separator",
|
||||
description: "Visual divider between content sections",
|
||||
source: include_str!("../../../registry/components/separator.ds"),
|
||||
deps: &[],
|
||||
},
|
||||
RegistryItem {
|
||||
name: "toggle",
|
||||
description: "On/off switch toggle",
|
||||
source: include_str!("../../../registry/components/toggle.ds"),
|
||||
deps: &[],
|
||||
},
|
||||
RegistryItem {
|
||||
name: "avatar",
|
||||
description: "User avatar with initials fallback",
|
||||
source: include_str!("../../../registry/components/avatar.ds"),
|
||||
deps: &[],
|
||||
},
|
||||
];
|
||||
|
||||
fn cmd_add(name: Option<String>, list: bool, all: bool) {
|
||||
|
|
|
|||
|
|
@ -510,16 +510,63 @@ impl JsEmitter {
|
|||
self.emit_line(&format!("{}.className = '{}';", node_var, class));
|
||||
}
|
||||
|
||||
// Handle container props
|
||||
// Handle container props (variant, class, events, style, layout)
|
||||
let container_tag = match &container.kind {
|
||||
ContainerKind::Column => "column",
|
||||
ContainerKind::Row => "row",
|
||||
ContainerKind::Stack => "stack",
|
||||
ContainerKind::Panel => "panel",
|
||||
_ => "column",
|
||||
};
|
||||
for (key, val) in &container.props {
|
||||
let js_val = self.emit_expr(val);
|
||||
if graph.name_to_id.contains_key(&js_val) || self.is_signal_ref(&js_val) {
|
||||
self.emit_line(&format!(
|
||||
"DS.effect(() => {{ {}.style.{} = {}.value + 'px'; }});",
|
||||
node_var, key, js_val
|
||||
));
|
||||
} else {
|
||||
self.emit_line(&format!("{}.style.{} = {};", node_var, key, js_val));
|
||||
match key.as_str() {
|
||||
"variant" => {
|
||||
match val {
|
||||
Expr::StringLit(s) if s.segments.len() == 1 => {
|
||||
if let Some(ds_parser::StringSegment::Literal(v)) = s.segments.first() {
|
||||
let css_class = variant_to_css(container_tag, v);
|
||||
self.emit_line(&format!(
|
||||
"{}.className += ' {}';",
|
||||
node_var, css_class
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let js = self.emit_expr(val);
|
||||
let map_entries = variant_map_js(container_tag);
|
||||
self.emit_line(&format!(
|
||||
"DS.effect(() => {{ const v = {js}; const cls = ({map_entries})[typeof v === 'object' ? v.value : v] || ''; {node_var}.className = 'ds-{container_tag} ' + cls; }});"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
"class" => {
|
||||
let js = self.emit_expr(val);
|
||||
self.emit_line(&format!("{}.className += ' ' + {};", node_var, js));
|
||||
}
|
||||
"click" | "submit" => {
|
||||
let handler_js = self.emit_event_handler_expr(val);
|
||||
self.emit_line(&format!(
|
||||
"{}.addEventListener('{}', (e) => {{ {}; DS.flush(); }});",
|
||||
node_var, key, handler_js
|
||||
));
|
||||
}
|
||||
"style" => {
|
||||
let js = self.emit_expr(val);
|
||||
self.emit_line(&format!("{}.style.cssText = {};", node_var, js));
|
||||
}
|
||||
_ => {
|
||||
// Layout props (x, y, width, height) or arbitrary style
|
||||
let js_val = self.emit_expr(val);
|
||||
if graph.name_to_id.contains_key(&js_val) || self.is_signal_ref(&js_val) {
|
||||
self.emit_line(&format!(
|
||||
"DS.effect(() => {{ {}.style.{} = {}.value + 'px'; }});",
|
||||
node_var, key, js_val
|
||||
));
|
||||
} else {
|
||||
self.emit_line(&format!("{}.style.{} = {};", node_var, key, js_val));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -656,6 +703,31 @@ impl JsEmitter {
|
|||
node_var, js
|
||||
));
|
||||
}
|
||||
"variant" => {
|
||||
// Map variant prop to CSS class based on element tag
|
||||
let tag = element.tag.as_str();
|
||||
match val {
|
||||
Expr::StringLit(s) if s.segments.len() == 1 => {
|
||||
// Static variant: emit class directly
|
||||
if let Some(ds_parser::StringSegment::Literal(v)) = s.segments.first() {
|
||||
let css_class = variant_to_css(tag, v);
|
||||
self.emit_line(&format!(
|
||||
"{}.className += ' {}';",
|
||||
node_var, css_class
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Dynamic variant: reactive class via inline lookup
|
||||
let js = self.emit_expr(val);
|
||||
let tag = element.tag.as_str();
|
||||
let map_entries = variant_map_js(tag);
|
||||
self.emit_line(&format!(
|
||||
"DS.effect(() => {{ const v = {js}; const cls = ({map_entries})[typeof v === 'object' ? v.value : v] || ''; {node_var}.className = 'ds-{tag} ' + cls; }});"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let js = self.emit_expr(val);
|
||||
self.emit_line(&format!("{}.setAttribute('{}', {});", node_var, key, js));
|
||||
|
|
@ -1621,29 +1693,29 @@ body {
|
|||
transform: translateY(0);
|
||||
}
|
||||
/* ── Button Variants ── */
|
||||
.ds-btn-primary {
|
||||
button.ds-btn-primary, .ds-button.ds-btn-primary {
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
.ds-btn-primary:hover { box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4); }
|
||||
.ds-btn-secondary {
|
||||
button.ds-btn-primary:hover { box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4); }
|
||||
button.ds-btn-secondary, .ds-button.ds-btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
color: #e2e8f0;
|
||||
box-shadow: none;
|
||||
}
|
||||
.ds-btn-secondary:hover { background: rgba(255, 255, 255, 0.12); }
|
||||
.ds-btn-ghost {
|
||||
button.ds-btn-secondary:hover { background: rgba(255, 255, 255, 0.12); }
|
||||
button.ds-btn-ghost, .ds-button.ds-btn-ghost {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
color: #a5b4fc;
|
||||
}
|
||||
.ds-btn-ghost:hover { background: rgba(99, 102, 241, 0.1); }
|
||||
.ds-btn-destructive {
|
||||
button.ds-btn-ghost:hover { background: rgba(99, 102, 241, 0.1); }
|
||||
button.ds-btn-destructive, .ds-button.ds-btn-destructive {
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
box-shadow: 0 4px 15px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
.ds-btn-destructive:hover { box-shadow: 0 6px 20px rgba(239, 68, 68, 0.4); }
|
||||
button.ds-btn-destructive:hover { box-shadow: 0 6px 20px rgba(239, 68, 68, 0.4); }
|
||||
/* ── Card ── */
|
||||
.ds-card {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
|
|
@ -1779,8 +1851,166 @@ body {
|
|||
.ds-fade-in {
|
||||
animation: ds-fade-in 0.3s ease-out;
|
||||
}
|
||||
/* ── Progress ── */
|
||||
.ds-progress-track {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 9999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ds-progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 9999px;
|
||||
background: linear-gradient(90deg, #6366f1 0%, #8b5cf6 100%);
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
/* ── Avatar ── */
|
||||
.ds-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ds-avatar-lg { width: 56px; height: 56px; font-size: 1.25rem; }
|
||||
.ds-avatar-sm { width: 28px; height: 28px; font-size: 0.75rem; }
|
||||
/* ── Separator ── */
|
||||
.ds-separator {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: none;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
/* ── Alert ── */
|
||||
.ds-alert {
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.ds-alert-info {
|
||||
background: rgba(56, 189, 248, 0.08);
|
||||
border: 1px solid rgba(56, 189, 248, 0.2);
|
||||
color: #7dd3fc;
|
||||
}
|
||||
.ds-alert-warning {
|
||||
background: rgba(234, 179, 8, 0.08);
|
||||
border: 1px solid rgba(234, 179, 8, 0.2);
|
||||
color: #fde047;
|
||||
}
|
||||
.ds-alert-error {
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||
color: #fca5a5;
|
||||
}
|
||||
.ds-alert-success {
|
||||
background: rgba(34, 197, 94, 0.08);
|
||||
border: 1px solid rgba(34, 197, 94, 0.2);
|
||||
color: #86efac;
|
||||
}
|
||||
/* ── Toggle ── */
|
||||
.ds-toggle {
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
.ds-toggle-on {
|
||||
background: #6366f1;
|
||||
}
|
||||
.ds-toggle::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.ds-toggle-on::after {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
/* ── Stat ── */
|
||||
.ds-stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
color: #f1f5f9;
|
||||
line-height: 1;
|
||||
}
|
||||
.ds-stat-delta-up {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: #4ade80;
|
||||
}
|
||||
.ds-stat-delta-down {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: #f87171;
|
||||
}
|
||||
"#;
|
||||
|
||||
/// Map a DreamStack variant prop to CSS class(es) based on element tag.
|
||||
fn variant_to_css(tag: &str, variant: &str) -> String {
|
||||
match tag {
|
||||
"button" => match variant {
|
||||
"primary" => "ds-btn-primary".to_string(),
|
||||
"secondary" => "ds-btn-secondary".to_string(),
|
||||
"ghost" => "ds-btn-ghost".to_string(),
|
||||
"destructive" => "ds-btn-destructive".to_string(),
|
||||
_ => format!("ds-btn-{}", variant),
|
||||
},
|
||||
"text" => match variant {
|
||||
"success" | "warning" | "error" | "info" | "default" =>
|
||||
format!("ds-badge ds-badge-{}", variant),
|
||||
"title" => "ds-card-title".to_string(),
|
||||
"subtitle" | "muted" => "ds-card-subtitle".to_string(),
|
||||
"label" => "ds-input-label".to_string(),
|
||||
_ => format!("ds-text-{}", variant),
|
||||
},
|
||||
"column" => match variant {
|
||||
"card" => "ds-card".to_string(),
|
||||
"dialog" => "ds-dialog-content".to_string(),
|
||||
"overlay" => "ds-dialog-overlay".to_string(),
|
||||
_ => format!("ds-column-{}", variant),
|
||||
},
|
||||
"row" => match variant {
|
||||
"card" => "ds-card".to_string(),
|
||||
_ => format!("ds-row-{}", variant),
|
||||
},
|
||||
"input" => match variant {
|
||||
"error" => "ds-input-has-error".to_string(),
|
||||
_ => format!("ds-input-{}", variant),
|
||||
},
|
||||
_ => format!("ds-{}-{}", tag, variant),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a JS object literal mapping variant names to CSS classes for dynamic variant.
|
||||
fn variant_map_js(tag: &str) -> String {
|
||||
match tag {
|
||||
"button" => "{'primary':'ds-btn-primary','secondary':'ds-btn-secondary','ghost':'ds-btn-ghost','destructive':'ds-btn-destructive'}".to_string(),
|
||||
"text" => "{'success':'ds-badge ds-badge-success','warning':'ds-badge ds-badge-warning','error':'ds-badge ds-badge-error','info':'ds-badge ds-badge-info','default':'ds-badge ds-badge-default','title':'ds-card-title','subtitle':'ds-card-subtitle','label':'ds-input-label'}".to_string(),
|
||||
"column" => "{'card':'ds-card','dialog':'ds-dialog-content','overlay':'ds-dialog-overlay'}".to_string(),
|
||||
"input" => "{'error':'ds-input-has-error'}".to_string(),
|
||||
_ => "{}".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The DreamStack client-side reactive runtime (~3KB).
|
||||
const RUNTIME_JS: &str = r#"
|
||||
const DS = (() => {
|
||||
|
|
|
|||
|
|
@ -1309,10 +1309,31 @@ impl Parser {
|
|||
}
|
||||
|
||||
self.expect(&TokenKind::RBracket)?;
|
||||
|
||||
// Optional trailing props: `column [ ... ] { variant: "card" }`
|
||||
let mut props = Vec::new();
|
||||
if self.check(&TokenKind::LBrace) {
|
||||
self.advance();
|
||||
self.skip_newlines();
|
||||
while !self.check(&TokenKind::RBrace) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
let key = self.expect_ident()?;
|
||||
self.expect(&TokenKind::Colon)?;
|
||||
let val = self.parse_expr()?;
|
||||
props.push((key, val));
|
||||
self.skip_newlines();
|
||||
if self.check(&TokenKind::Comma) {
|
||||
self.advance();
|
||||
}
|
||||
self.skip_newlines();
|
||||
}
|
||||
self.expect(&TokenKind::RBrace)?;
|
||||
}
|
||||
|
||||
Ok(Expr::Container(Container {
|
||||
kind,
|
||||
children,
|
||||
props: Vec::new(),
|
||||
props,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,95 @@
|
|||
let title = "Dashboard"
|
||||
let active = "Analytics"
|
||||
-- DreamStack Dashboard
|
||||
-- Rich admin dashboard using container variant props
|
||||
|
||||
layout dashboard {
|
||||
sidebar.x == 0
|
||||
sidebar.y == 0
|
||||
sidebar.width == 250
|
||||
sidebar.height == parent.height
|
||||
main.x == sidebar.width
|
||||
main.y == 0
|
||||
main.width == parent.width - sidebar.width
|
||||
main.height == parent.height
|
||||
}
|
||||
let visitors = 12847
|
||||
let revenue = 48290
|
||||
let orders = 384
|
||||
let conversion = 3.2
|
||||
let search = ""
|
||||
|
||||
view main = column [
|
||||
text title
|
||||
text "Welcome to DreamStack Dashboard"
|
||||
text active
|
||||
|
||||
-- Header
|
||||
row [
|
||||
text "Dashboard" { variant: "title" }
|
||||
input { bind: search, placeholder: "Search..." }
|
||||
]
|
||||
text "Welcome back — here's what's happening today." { variant: "subtitle" }
|
||||
|
||||
-- Stat cards row
|
||||
row [
|
||||
column [
|
||||
text "Total Visitors" { variant: "subtitle" }
|
||||
text "{visitors}" { variant: "title" }
|
||||
text "↑ 12% from last month" { variant: "success" }
|
||||
] { variant: "card" }
|
||||
|
||||
column [
|
||||
text "Revenue" { variant: "subtitle" }
|
||||
text "${revenue}" { variant: "title" }
|
||||
text "↑ 8.2% from last month" { variant: "success" }
|
||||
] { variant: "card" }
|
||||
|
||||
column [
|
||||
text "Orders" { variant: "subtitle" }
|
||||
text "{orders}" { variant: "title" }
|
||||
text "↓ 2.1% from last month" { variant: "error" }
|
||||
] { variant: "card" }
|
||||
|
||||
column [
|
||||
text "Conversion" { variant: "subtitle" }
|
||||
text "{conversion}%" { variant: "title" }
|
||||
text "↑ 0.5% from last month" { variant: "success" }
|
||||
] { variant: "card" }
|
||||
]
|
||||
|
||||
-- Alert
|
||||
text "⚡ Your trial ends in 3 days. Upgrade to keep all features." { variant: "info" }
|
||||
|
||||
-- Recent Orders
|
||||
column [
|
||||
text "Recent Orders" { variant: "title" }
|
||||
|
||||
row [
|
||||
text "#" { variant: "label" }
|
||||
text "Customer" { variant: "label" }
|
||||
text "Status" { variant: "label" }
|
||||
text "Amount" { variant: "label" }
|
||||
]
|
||||
|
||||
row [
|
||||
text "3842"
|
||||
text "Alice Johnson"
|
||||
text "Delivered" { variant: "success" }
|
||||
text "$249.00"
|
||||
]
|
||||
|
||||
row [
|
||||
text "3841"
|
||||
text "Bob Smith"
|
||||
text "Processing" { variant: "warning" }
|
||||
text "$149.00"
|
||||
]
|
||||
|
||||
row [
|
||||
text "3840"
|
||||
text "Carol White"
|
||||
text "Shipped" { variant: "info" }
|
||||
text "$399.00"
|
||||
]
|
||||
|
||||
row [
|
||||
text "3839"
|
||||
text "Dave Brown"
|
||||
text "Cancelled" { variant: "error" }
|
||||
text "$89.00"
|
||||
]
|
||||
] { variant: "card" }
|
||||
|
||||
-- Actions
|
||||
row [
|
||||
button "View All Orders" { variant: "primary" }
|
||||
button "Export CSV" { variant: "secondary" }
|
||||
button "Settings" { variant: "ghost" }
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-- DreamStack Component Showcase
|
||||
-- Demonstrates all component styles
|
||||
-- Demonstrates all component styles via variant prop
|
||||
|
||||
-- State
|
||||
let name = ""
|
||||
|
|
@ -8,38 +8,38 @@ let count = 0
|
|||
-- Main view
|
||||
view main = column [
|
||||
|
||||
text "🧩 DreamStack Components" { class: "ds-card-title" }
|
||||
text "shadcn-inspired component registry" { class: "ds-card-subtitle" }
|
||||
text "🧩 DreamStack Components" { variant: "title" }
|
||||
text "shadcn-inspired component registry" { variant: "subtitle" }
|
||||
|
||||
-- Button Variants
|
||||
text "Button Variants" { class: "ds-card-title" }
|
||||
text "Button Variants" { variant: "title" }
|
||||
row [
|
||||
button "Primary" { class: "ds-btn-primary" }
|
||||
button "Secondary" { class: "ds-btn-secondary" }
|
||||
button "Ghost" { class: "ds-btn-ghost" }
|
||||
button "Destructive" { class: "ds-btn-destructive" }
|
||||
button "Primary" { variant: "primary" }
|
||||
button "Secondary" { variant: "secondary" }
|
||||
button "Ghost" { variant: "ghost" }
|
||||
button "Destructive" { variant: "destructive" }
|
||||
]
|
||||
|
||||
-- Badge Variants
|
||||
text "Badge Variants" { class: "ds-card-title" }
|
||||
text "Badge Variants" { variant: "title" }
|
||||
row [
|
||||
text "SUCCESS" { class: "ds-badge ds-badge-success" }
|
||||
text "WARNING" { class: "ds-badge ds-badge-warning" }
|
||||
text "ERROR" { class: "ds-badge ds-badge-error" }
|
||||
text "INFO" { class: "ds-badge ds-badge-info" }
|
||||
text "DEFAULT" { class: "ds-badge ds-badge-default" }
|
||||
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" { class: "ds-card-title" }
|
||||
text "Name" { class: "ds-input-label" }
|
||||
text "Input Component" { variant: "title" }
|
||||
text "Name" { variant: "label" }
|
||||
input { bind: name, placeholder: "Type your name..." }
|
||||
text "Hello, {name}!"
|
||||
|
||||
-- Interactive counter
|
||||
text "Interactive Counter" { class: "ds-card-title" }
|
||||
text "Interactive Counter" { variant: "title" }
|
||||
row [
|
||||
button "Count: {count}" { click: count += 1, class: "ds-btn-primary" }
|
||||
button "Reset" { click: count = 0, class: "ds-btn-ghost" }
|
||||
button "Count: {count}" { click: count += 1, variant: "primary" }
|
||||
button "Reset" { click: count = 0, variant: "ghost" }
|
||||
]
|
||||
]
|
||||
|
|
|
|||
5
registry/components/alert.ds
Normal file
5
registry/components/alert.ds
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-- DreamStack Alert Component
|
||||
-- Variants: info, warning, error, success
|
||||
|
||||
export component Alert(message, variant) =
|
||||
text message { variant: "info" }
|
||||
5
registry/components/avatar.ds
Normal file
5
registry/components/avatar.ds
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-- DreamStack Avatar Component
|
||||
-- User avatar with initials fallback
|
||||
|
||||
export component Avatar(initials) =
|
||||
text initials
|
||||
|
|
@ -2,4 +2,4 @@
|
|||
-- Variants: success, warning, error, info, default
|
||||
|
||||
export component Badge(label, variant) =
|
||||
text label { class: "ds-badge ds-badge-default" }
|
||||
text label { variant: "default" }
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
-- Variants: primary (default), secondary, ghost, destructive
|
||||
|
||||
export component Button(label, variant, onClick) =
|
||||
button label { click: onClick, class: "ds-btn-primary" }
|
||||
button label { click: onClick, variant: "primary" }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
|
||||
export component Card(title, subtitle) =
|
||||
column [
|
||||
text title { class: "ds-card-title" }
|
||||
text subtitle { class: "ds-card-subtitle" }
|
||||
] { class: "ds-card" }
|
||||
text title { variant: "title" }
|
||||
text subtitle { variant: "subtitle" }
|
||||
] { variant: "card" }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ import { Button } from "./button"
|
|||
|
||||
export component Dialog(title, open, onClose) =
|
||||
column [
|
||||
text title { class: "ds-dialog-title" }
|
||||
text title { variant: "title" }
|
||||
Button { label: "Close", onClick: onClose }
|
||||
] { class: "ds-dialog-content" }
|
||||
] { variant: "dialog" }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
|
||||
export component Input(value, placeholder, label) =
|
||||
column [
|
||||
text label { class: "ds-input-label" }
|
||||
text label { variant: "label" }
|
||||
input { bind: value, placeholder: placeholder }
|
||||
]
|
||||
|
|
|
|||
7
registry/components/progress.ds
Normal file
7
registry/components/progress.ds
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
-- DreamStack Progress Component
|
||||
-- Animated progress bar with percentage
|
||||
|
||||
export component Progress(value) =
|
||||
column [
|
||||
text "{value}%"
|
||||
]
|
||||
5
registry/components/separator.ds
Normal file
5
registry/components/separator.ds
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-- DreamStack Separator Component
|
||||
-- Visual divider between content sections
|
||||
|
||||
export component Separator() =
|
||||
text ""
|
||||
|
|
@ -2,4 +2,4 @@
|
|||
-- Notification toast with slide-in animation
|
||||
|
||||
export component Toast(message) =
|
||||
text message { class: "ds-toast" }
|
||||
text message
|
||||
|
|
|
|||
5
registry/components/toggle.ds
Normal file
5
registry/components/toggle.ds
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-- DreamStack Toggle Component
|
||||
-- On/off switch
|
||||
|
||||
export component Toggle(value, onChange) =
|
||||
button "" { click: onChange }
|
||||
Loading…
Add table
Reference in a new issue