While BlendDuck offers a variety of built-in widgets, you may sometimes need functionality that isn’t available out of the box. This guide will walk you through the process of creating custom widgets for the BlendDuck platform.
Please note that because the front-end code for Widgets needs to run on the BlendDuck platform, we will work closely with clients on user experience, security, and rendering. Therefore, custom Widget development is only available for enterprise-level subscribers.
Prerequisites
Before you begin, ensure you have:
- Node.js and npm installed
- Familiarity with React and TypeScript
- Basic understanding of the BlendDuck platform
Setting Up Your Development Environment
-
Create a new project directory for your widget.
-
Add the BlendDuck Widget SDK to your package.json
:
npm i @blendduck/widget-sdk
Contact us to apply for a Widget developer repository.
BlendDuck will create a private git repository on Github and grant you collaborative development permissions. You can also choose to have the BlendDuck development team handle the custom development entirely.
This git repository will include template code for Widget development, widget scope, and some build configurations.
Create a new file (e.g., WidgetA/index.ts
) and define your widget structure:
import {
ElementSchema,
ElementFieldConfig,
AppearSchema,
AppearFieldConfig,
} from "./config";
import Widget from "./Widget";
export default {
name: 'your-widget-id',
Widget: Widget,
parameters: {
schema: ElementSchema,
fieldConfig: ElementFieldConfig,
},
appear: {
schema: AppearSchema,
fieldConfig: AppearFieldConfig,
},
disappear: undefined,
defaults: {
width: 400,
height: 200,
}
}
In the image below, part A shows a custom Widget Panel, part B shows the Inspector panel, and parts C and D show the Appear animation and its corresponding Inspector panel.
- In a Widget definition, keep the
name
unique within your Widget scope.
parameters
represents the parameters of this Widget that are open for user modification. When a Widget is selected, the BlendDuck editor will automatically build an Inspector panel for user modifications. A Widget may not provide a definition for this field. Refer to part B in the image above.
appear
and disappear
represent the extended parameters this Widget provides for Appear
and Disappear
animations respectively. Like parameters
, a Widget may not provide a definition for these fields. Refer to part D in the image above.
defaults
indicates additional default parameters for this Widget when creating a new one on the stage.
Create a React component for your widget:
import React from 'react';
import { useWidgetParameters, useWidgetStyles, useWidgetAppear } from "@blendduck/widget-sdk";
export default function MyCustomWidgetComponent() {
const parameters = useWidgetParameters<typeof ElementSchema>();
const { width, height } = useWidgetStyles();
const appear = useWidgetAppear<typeof AppearSchema>();
const disappear = useWidgetAppear<typeof DisappearSchema>();
// Implement your widget logic here
return (
<div style={{ width, height }}>
{/* Render your widget UI */}
</div>
);
}
You can then build your Widget according to React
and Remotion
best practices. After using @blendduck/widget-sdk
, you can easily obtain the size of the Widget and the parameters passed by the user in the Inspector panel.
Define the schema and field configuration for your widget:
import { z } from "zod";
export const ElementSchema = z.object({
// Define your widget parameters
title: z.string().default('My Widget'),
value: z.number().default(0),
});
export const ElementFieldConfig = {
title: {
inputProps: {
inline: true,
}
},
value: {
fieldType: 'number',
inputProps: {
inline: true,
min: 0,
max: 100,
},
}
};
export const AppearSchema = z.object({
// Define appear animation parameters
duration: z.number().default(1),
});
export const AppearFieldConfig = {
duration: {
inputProps: {
inline: true,
min: 0.1,
max: 5,
step: 0.1,
}
},
};
BlendDuck provides over 40 built-in form types, which can help developers quickly build a unified UI Inspector. In addition, developers can use React
to extend new Form UIs for modifying properties.
Step 5: Handle Resource Loading
If your widget needs to load external resources, use Remotion’s delayRender
and continueRender
:
import { continueRender, delayRender } from 'remotion';
import { loadFont } from "@blendduck/widget-sdk";
// Inside your widget component
const [fontHandle] = useState(() => delayRender());
useEffect(() => {
loadFont('Your Font Family').then(() => {
continueRender(fontHandle);
});
}, []);
Step 6: Create a Custom Panel
Create a panel for your widgets:
import React from "react";
import { Text } from "@radix-ui/themes";
import { dispatchEvent } from '@blendduck/widget-sdk';
const SCOPE = process.env.BLENDDUCK_WIDGET_SCOPE;
const assetDir = process.env.BLENDDUCK_WIDGET_DIR;
const assetPath = process.env.BLENDDUCK_WIDGET_PATH;
type WidgetKey = string;
const widgets: WidgetKey[] = [
'widgetA',
]
export const Panel: React.FC = () => {
const onWidgetClick = (scope: string, key: string) => {
dispatchEvent("addWidget", {
scope,
widgetId: key,
})
}
return (
<div>
<div className='p-4 border-b'>
<Text as="div" size="3" weight="medium">Hot widgets</Text>
</div>
<div className="grid grid-cols-[repeat(3,minmax(0,1fr))] gap-2 p-4">
{
widgets.map((item) => {
const img = getImageURL(SCOPE, item);
return (
<div
key={item}
onClick={() => { onWidgetClick(scope, item) }}
className="flex items-center border aspect-square justify-center w-full h-full rounded-md hover:bg-[var(--gray-3)]">
<img className="w-full h-full" src={img} />
</div>
)
})
}
</div>
</div>
)
}
const getImageURL = (scope, img) => {
if (scope === SCOPE_CORE) {
return `${assetPath}${img}.png`
}
return `${assetDir}${scope}/${img}.png`
}
You can add or update a Speech AudioPlay by dispatchEvent
method:
const TTS_PANEL_KEY = 'your_tts_panel_key';
interface PanelState {
taskId?: string;
text: string;
language_code: string;
voiceId: string;
loading: boolean;
}
const Panel: React.FC = () => {
const {
selectedSpeech,
state,
setState,
} = usePanelState<PanelState>();
// change the panel state while a Speech AudioPlay focused
useEffect(() => {
if (selectedSpeech?.userdata?.provider === TTS_PANEL_KEY) {
const userdata = selectedSpeech.userdata;
setState({
text: userdata.text,
language_code: userdata.language_code,
voiceId: userdata.voiceId,
loading: false,
})
}
}, [selectedSpeech])
const { text, language_code, voiceId, loading } = state;
// The current id of focused AudioPlay
let selectedAudioPlayId: string | undefined;
if (selectedSpeech?.userdata?.provider === TTS_PANEL_KEY) {
selectedAudioPlayId = selectedSpeech.id;
};
const handleAddSpeech = ({ name, url }) => {
BlendDuckWidgetSDK.dispatchEvent("addAudioPlay", {
id: selectedAudioPlayId,
name,
url,
// You must pass the `userdata` parameter for editing later
userdata: {
text,
language_code,
voiceId,
provider: TTS_PANEL_KEY,
}
});
};
return (
<div>
{/* ... */}
<TextArea
rows={6}
value={text}
onChange={(e) => {
setState({
...state,
text: e.currentTarget.value,
});
}}
placeholder="Type anything in any language and turn text into natural-sounding speech"
/>
<Button
onClick={handleAddOrUpdateSpeech}>
{selectedAudioPlayId ? 'Update Speech' : 'Add Speech'}
</Button>
</div>
)
};
export default Panel;
Finally, create a WidgetLibrary
in your code and register the Widget(s)
and Panel
.
import { WidgetLibrary } from '@blendduck/widget-sdk';
import "./index.css";
import WidgetA from './WidgetA';
// import WidgetB from './WidgetB';
import Panel from './Panel';
const widgetLibrary = new WidgetLibrary();
widgetLibrary.register(WidgetA);
// widgetLibrary.register(WidgetB);
widgetLibrary.setPanel(Panel);
export default widgetLibrary;
Start the project and open https://blendduck.com/dev in your browser for online debugging.
Once you’ve developed and tested your custom widget, submit your code via git. GitHub Action will automatically build and deploy the project. You can then access your extended Panel in BlendDuck to see the list of Widgets you’ve developed.
By following this guide, you can create powerful, custom widgets that extend the capabilities of the BlendDuck platform. Remember to stay updated with the latest BlendDuck SDK documentation for any new features or changes in the development process.