MelonyWidget

Component for rendering individual widgets or component definitions without markdown context.

Import

import { MelonyWidget } from "melony";

Basic Usage

MelonyWidget renders component markup directly, without mixing with markdown:

import { MelonyWidget } from "melony";

const widget = `
<card title="Weather">
  <text value="Sunny, 72°F" />
  <badge label="Clear" variant="success" />
</card>
`;

function Component() {
  return <MelonyWidget>{widget}</MelonyWidget>;
}

Props

children

string | ComponentDef • Required

Either a string containing Melony component markup, or a ComponentDef object defining the component structure programmatically.

String Format:

<MelonyWidget>
  <card title="Hello">
    <text value="World" />
  </card>
</MelonyWidget>

ComponentDef Format:

const componentDef = {
  component: "Card",
  props: { title: "Hello" },
  children: [
    {
      component: "Text",
      props: { value: "World" },
    },
  ],
};

<MelonyWidget>{componentDef}</MelonyWidget>

context

Record<string, any> • Optional

Custom data object for template variable substitution.

const context = {
  user: { name: "Alice" },
  temperature: 72,
};

const widget = `
<card title="{{user.name}}'s Dashboard">
  <text value="Current temp: {{temperature}}°F" />
</card>
`;

<MelonyWidget context={context}>{widget}</MelonyWidget>

Use Cases

1. Rendering Pure Components

When you have component markup without any markdown text:

const componentMarkup = `
<card title="User Profile">
  <row gap="md">
    <text value="Name: John Doe" />
    <badge label="Active" variant="success" />
  </row>
  <button label="Edit Profile" action='{"type":"edit"}' />
</card>
`;

<MelonyWidget>{componentMarkup}</MelonyWidget>

2. Dynamic Widget Generation

Generate widgets programmatically with context:

function WeatherWidget({ location, temp, condition }) {
  const context = { location, temp, condition };
  
  const template = `
<card title="{{location}} Weather">
  <row gap="sm">
    <text value="{{temp}}°F" size="xl" weight="bold" />
    <badge label="{{condition}}" variant="primary" />
  </row>
</card>
  `;

  return <MelonyWidget context={context}>{template}</MelonyWidget>;
}

<WeatherWidget location="SF" temp={72} condition="Sunny" />

3. Testing Components

Useful for testing component rendering in isolation:

import { render } from "@testing-library/react";
import { MelonyProvider, MelonyWidget } from "melony";

test("renders button component", () => {
  const { getByText } = render(
    <MelonyProvider>
      <MelonyWidget>
        <button label="Click Me" variant="primary" />
      </MelonyWidget>
    </MelonyProvider>
  );
  
  expect(getByText("Click Me")).toBeInTheDocument();
});

ComponentDef Format

For programmatic component generation, use the ComponentDef object format:

interface ComponentDef {
  component: string;           // Component name (e.g., "Card", "Text")
  props?: Record<string, any>; // Component props
  children?: (ComponentDef | string)[]; // Nested components or text
}

const def: ComponentDef = {
  component: "Card",
  props: { title: "Weather", padding: "lg" },
  children: [
    {
      component: "Text",
      props: { value: "Sunny", size: "xl" },
    },
    {
      component: "Badge",
      props: { label: "Clear", variant: "success" },
    },
  ],
};

<MelonyWidget>{def}</MelonyWidget>

MelonyWidget vs MelonyMarkdown

Use MelonyWidget when:

  • You have pure component markup without markdown
  • You're rendering a single, focused widget
  • You're building programmatic component definitions
  • You're testing components in isolation

Use MelonyMarkdown when:

  • You have mixed markdown text and components
  • You're rendering AI streaming responses
  • You need markdown formatting (headings, lists, etc.)
  • You're building chat interfaces

Complete Example

import { MelonyProvider, MelonyWidget } from "melony";
import { useState } from "react";

function WeatherDashboard() {
  const [weather] = useState({
    location: "San Francisco",
    temperature: 72,
    condition: "Sunny",
    humidity: 65,
  });

  const handleAction = (action) => {
    if (action.type === "refresh") {
      console.log("Refreshing weather...");
    }
  };

  const context = {
    ...weather,
    lastUpdated: new Date().toLocaleTimeString(),
  };

  const widgetTemplate = `
<card title="{{location}} Weather" padding="lg">
  <row gap="lg">
    <column>
      <text value="Temperature" size="sm" color="muted" />
      <text value="{{temperature}}°F" size="xl" weight="bold" />
    </column>
    
    <column>
      <text value="Condition" size="sm" color="muted" />
      <badge label="{{condition}}" variant="success" />
    </column>
    
    <column>
      <text value="Humidity" size="sm" color="muted" />
      <text value="{{humidity}}%" weight="medium" />
    </column>
  </row>
  
  <text value="Last updated: {{lastUpdated}}" size="sm" color="muted" />
  
  <row gap="sm">
    <button 
      label="Refresh" 
      variant="primary"
      action='{"type":"refresh","location":"{{location}}"}' 
    />
    <button label="Details" variant="outline" />
  </row>
</card>
  `;

  return (
    <MelonyProvider onAction={handleAction}>
      <MelonyWidget context={context}>
        {widgetTemplate}
      </MelonyWidget>
    </MelonyProvider>
  );
}

export default WeatherDashboard;

Notes

  • Must be used within a MelonyProvider to access theme and actions
  • Accepts either string markup or ComponentDef objects
  • Context variables work the same as in MelonyMarkdown
  • No markdown parsing—only component rendering
  • Useful for building reusable widget wrappers

See Also

MelonyMarkdown - For markdown with embedded components

MelonyProvider - Root provider component

Widget Templates - Learn about widget templates

Context System - Learn about context variables