08_preview_tab_.md - ganeshramakrishnanbr/Jumbo_NoCodeBuilder GitHub Wiki
Welcome back, future No-Code Builder expert! So far, we've learned about the basic building blocks: the different ControlTypes available, the individual Control instances with their unique IDs and properties, how the entire form is held in a single Questionnaire object, how the QuestionnaireContext centrally manages this data, and how the Drag and Drop System lets you visually add and move controls.
You've used drag and drop to add a Text Box and a Checkbox to your form canvas. Great! But right now, they probably just say "New Text Box" and "New Checkbox". How do you change the label of the Text Box to "Your Name"? How do you make the Checkbox required? How do you add the list of choices to a Dropdown control?
You need a way to configure the specific details of each individual Control instance you've placed. This is where the Properties Panel comes in.
Imagine each Control you place on your form is like an electronic gadget. When you want to change settings on your phone or your TV, you go into a "Settings" or "Properties" menu, right? The Properties Panel is exactly that for your selected Control in the Jumbo_NoCodeBuilder
.
It's typically a sidebar area in the designer interface. When you click on a specific Control on your Design Canvas, the Properties Panel updates to show you all the settings you can change for that particular control instance.
The options you see in the panel aren't always the same. They change dynamically based on the ControlType of the control you've selected. A Text Box will have options like "Placeholder Text", while a Dropdown will have an "Options" list you can manage.
The Properties Panel allows you to:
- Change common properties like the
label
(the text displayed next to the control). - Toggle settings like
required
(making the field mandatory) orvisible
(showing/hiding the control). - Configure type-specific settings that are stored in the
properties
field of the Control object (like adding options to a dropdown).
Any changes you make in the Properties Panel are immediately applied to the selected Control object within the central Questionnaire data managed by the QuestionnaireContext.
Let's walk through the process of changing the label of a Text Box you've added to the canvas, and see how the Properties Panel makes this possible.
You drag a Text Box from the Control Palette onto the Design Canvas. It appears with a default label like "New Text Box". You want to change this to "Your Name".
- You click on the "New Text Box" control on the Design Canvas.
- The system detects which Control instance you clicked (using its unique
id
). - The Properties Panel updates to show the settings for this specific Text Box control.
- You see an input field labeled "Label" (or similar) in the Properties Panel, and it currently shows "New Text Box".
- You type "Your Name" into this input field.
- As you type (or when you finish editing), the Properties Panel tells the QuestionnaireContext to update the
label
property of that specific Control object you have selected. - The QuestionnaireContext updates the data for the Text Box Control.
- The Design Canvas, which is displaying the control based on the data from the QuestionnaireContext, automatically re-renders and now shows the updated label "Your Name".
Let's trace the flow when you click a control on the canvas and its properties appear in the panel. This process heavily relies on the QuestionnaireContext.
sequenceDiagram
Participant U as User
Participant CC as CanvasControl (Clicked)
Participant QC as QuestionnaireContext
Participant PP as PropertiesPanel
U->>CC: Clicks on a Control (e.g., Text Box)
CC->>QC: Calls setSelectedControlId(control.id)
Note over QC: Updates the internal state: selectedControlId = clicked_control_id
QC-->>PP: Notifies PropertiesPanel that state changed
PP->>QC: Reads the new selectedControlId from context
PP->>QC: Reads the full Questionnaire object from context (which contains all Controls)
PP->>PP: Searches the Questionnaire's controls list (and nested children) to find the Control object with the matching selectedControlId
Note over PP: Now PP has the specific Control object (the Text Box) with all its properties (id, type, label, required, properties, etc.)
PP->>PP: Determines which property fields to display based on the Control's type (e.g., sees type is Text Box)
PP->>PP: Populates the property fields (e.g., sets Label field value to Control.label)
PP->>U: PropertiesPanel UI displays the configuration form for the selected Text Box
This diagram shows how clicking a control triggers an update in the central QuestionnaireContext
, which the PropertiesPanel
is listening for. Once notified, the panel retrieves the necessary data (the selectedControlId
and the full questionnaire
data) from the context to find the exact Control
object and display its settings.
The core of the Properties Panel logic resides in src/components/designer/properties/PropertiesPanel.tsx
. It needs access to the selectedControlId
and the questionnaire
data from the QuestionnaireContext.
// --- File: src/components/designer/properties/PropertiesPanel.tsx ---
import React from 'react';
import { useQuestionnaire } from '../../../contexts/QuestionnaireContext'; // Get access to context
import { Control, ControlType } from '../../../types'; // Import Control and ControlType types
// Import components that render specific property forms
import CommonProperties from './CommonProperties';
import TabProperties from './TabProperties';
import TextBoxProperties from './TextBoxProperties';
import AccordionProperties from './AccordionProperties';
// ... import other property components ...
const PropertiesPanel: React.FC = () => {
// Use the hook to get relevant state and functions from the context
const { questionnaire, selectedControlId, setSelectedControlId, updateControl } = useQuestionnaire();
// Helper function to get a flat list of all controls, including those nested in containers.
// This is needed because selectedControlId could point to a control inside a Tab or Accordion.
const flattenControls = (controls: Control[]): Control[] => {
let result: Control[] = []; // Start with an empty list
if (!Array.isArray(controls)) return result; // Handle cases where controls isn't an array
for (const control of controls) {
result.push(control); // Add the current control to the list
// If it's a container control, recursively flatten its children
if (control.type === ControlType.Tab) {
const tabControl = control as any; // Cast to handle container specific properties
tabControl.tabs?.forEach((tab: any) => { // Iterate through tabs
if (Array.isArray(tab.controls)) {
result = [...result, ...flattenControls(tab.controls)]; // Add flattened tab controls
}
});
}
if (control.type === ControlType.Accordion) {
const accordionControl = control as any;
accordionControl.sections?.forEach((section: any) => { // Iterate through sections
if (Array.isArray(section.controls)) {
result = [...result, ...flattenControls(section.controls)]; // Add flattened section controls
}
});
}
// Handle ColumnLayout similarly, iterating through columnControls
if (control.type === ControlType.ColumnLayout) {
const columnControl = control as any;
if (Array.isArray(columnControl.columnControls)) {
columnControl.columnControls.forEach((columnControlArray: any) => { // Iterate through column arrays
if (Array.isArray(columnControlArray)) {
result = [...result, ...flattenControls(columnControlArray)]; // Add flattened column controls
}
});
}
}
// ... Add logic for other container types if necessary ...
}
return result; // Return the complete flat list
};
// Get a flat list of *all* controls in the questionnaire
const allControls = flattenControls(questionnaire.controls);
// Find the specific control object that is currently selected
const selectedControl = allControls.find(c => c.id === selectedControlId);
// This function is called by the property input fields whenever a value changes
const updateSelectedControl = (updates: Partial<Control>) => {
if (selectedControlId) {
console.log('Updating control:', selectedControlId, 'with:', updates);
// Call the updateControl function from QuestionnaireContext
// This updates the Control object in the central state
updateControl(selectedControlId, updates);
}
};
// This function decides which set of property forms to display
const renderPropertiesForm = () => {
// If no control is selected, show a message
if (!selectedControl) {
return (
<div className="text-gray-500 text-center p-4">
Select a control to edit its properties
</div>
);
}
// Filter out the selected control itself, so it can't be selected as a parent for 'move to' logic in CommonProperties
const availableControls = allControls.filter(c => c.id !== selectedControl.id);
// Now, based on the type of the selected control, render the appropriate components
return (
<div className="space-y-6">
{/* Show properties common to most controls (Label, Required, etc.) */}
{/* The CommonProperties component reads selectedControl.label, selectedControl.required etc. */}
{/* When you change the label, CommonProperties calls updateSelectedControl({ label: 'New Value' }) */}
<CommonProperties
control={selectedControl}
onChange={updateSelectedControl}
availableControls={availableControls}
/>
{/* IF the selected control is a Tab, ONLY show Tab-specific properties */}
{selectedControl.type === ControlType.Tab && (
<TabProperties
control={selectedControl}
onChange={updateSelectedControl}
/>
)}
{/* IF the selected control is a ColumnLayout, ONLY show ColumnLayout-specific properties */}
{selectedControl.type === ControlType.ColumnLayout && (
<ColumnLayoutProperties
control={selectedControl}
onChange={updateSelectedControl}
/>
)}
{/* IF the selected control is one of these types, show TextBox-like properties */}
{(selectedControl.type === ControlType.TextBox ||
selectedControl.type === ControlType.Checkbox || // Checkbox properties might be similar to TextBox in some ways, or share a component
selectedControl.type === ControlType.RadioButton ||
selectedControl.type === ControlType.Dropdown) && (
<TextBoxProperties // Note: In a real app, Checkbox, Radio, Dropdown might have their *own* dedicated components like CheckboxProperties, etc.
control={selectedControl}
onChange={updateSelectedControl}
/>
)}
{/* IF the selected control is Numeric or ToggleSlider, show Numeric properties */}
{(selectedControl.type === ControlType.Numeric ||
selectedControl.type === ControlType.ToggleSlider) && (
<NumericProperties
control={selectedControl}
onChange={updateSelectedControl}
/>
)}
{/* IF selected control is Address */}
{selectedControl.type === ControlType.Address && (
<AddressProperties
control={selectedControl}
onChange={updateSelectedControl}
/>
)}
{/* IF selected control is ProtectedNumber */}
{selectedControl.type === ControlType.ProtectedNumber && (
<ProtectedNumberProperties
control={selectedControl}
onChange={updateSelectedControl}
/>
)}
{/* IF selected control is Accordion */}
{selectedControl.type === ControlType.Accordion && (
<AccordionProperties
control={selectedControl}
onChange={updateSelectedControl}
/>
)}
{/* ... add checks and components for other ControlTypes ... */}
</div>
);
};
return (
<div className="h-full overflow-auto p-4">
{/* A dropdown is also provided to select controls, useful for nested controls */}
<div className="mb-4 border-b pb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Selected Control
</label>
<select
value={selectedControlId || ''}
onChange={(e) => setSelectedControlId(e.target.value || null)}
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<option value="">Select a control</option>
{/* Populate the dropdown with all controls found by flattenControls */}
{allControls.map((control) => (
<option key={control.id} value={control.id}>
{/* Show label or type in the dropdown */}
{control.label || control.type}
</option>
))}
</select>
</div>
{/* Render the appropriate property forms */}
{renderPropertiesForm()}
</div>
);
};
export default PropertiesPanel;
This code snippet shows the main parts of the PropertiesPanel
. It gets the questionnaire
data and selectedControlId
from useQuestionnaire
. The flattenControls
helper function is important because controls can be nested inside containers (Container Controls). This function creates a single list of all controls in the questionnaire, regardless of nesting level, so the panel can find any selected control by its ID.
Once the selectedControl
object is found, the renderPropertiesForm
function uses if
conditions (or sometimes a switch
statement, as seen in previous chapters) to check selectedControl.type
. Based on the type (e.g., ControlType.Tab
, ControlType.TextBox
), it renders the appropriate specific property component (TabProperties
, TextBoxProperties
, etc.).
These specific property components (like TextBoxProperties
) receive the selectedControl
object and the updateSelectedControl
function as props. They display input fields for properties relevant to their type (like placeholder
for Text Box, which is read from selectedControl.properties.placeholder
). When a value in one of these input fields changes, the specific property component calls the updateSelectedControl
function, passing the ID of the control being edited and the new value(s). updateSelectedControl
then calls the updateControl
function from the QuestionnaireContext
, completing the cycle and updating the central data.
The DesignTab.tsx
component simply places the PropertiesPanel
alongside the ControlPalette
and DesignCanvas
:
// --- File: src/components/designer/tabs/DesignTab.tsx ---
import React from 'react';
import ControlPalette from '../controls/ControlPalette';
import DesignCanvas from '../canvas/DesignCanvas';
import PropertiesPanel from '../properties/PropertiesPanel';
import { DragDropProvider } from '../../../contexts/DragDropContext'; // Needed for drag/drop
const DesignTab: React.FC = () => {
return (
// DragDropProvider wraps the whole designer area
<DragDropProvider>
<div className="h-full flex">
{/* Control Palette */}
<div className="w-64 border-r p-4 bg-white shadow-sm overflow-auto">
<h2 className="text-lg font-medium text-gray-900 mb-4">Controls</h2>
<ControlPalette />
</div>
{/* Design Canvas */}
<div className="flex-1 p-4 bg-gray-50 overflow-auto">
<DesignCanvas />
</div>
{/* Properties Panel */}
<div className="w-72 border-l p-4 bg-white shadow-sm overflow-auto">
<h2 className="text-lg font-medium text-gray-900 mb-4">Properties</h2>
<PropertiesPanel /> {/* The PropertiesPanel component is rendered here */}
</div>
</div>
</DragDropProvider>
);
};
export default DesignTab;
This code shows that the PropertiesPanel
is just another component rendered within the main designer layout, sitting in its designated sidebar area. It gets all the data it needs via the useQuestionnaire
hook provided by the QuestionnaireContext
, which typically wraps the entire designer area higher up in the application structure (though not shown directly in this snippet, you saw the QuestionnaireProvider
in Chapter 4).
Aspect | What it Does | Key Interactions |
---|---|---|
Purpose | Customize settings for a single, selected Control . |
User selects a Control on the canvas or in the dropdown. |
Input | The selectedControlId from QuestionnaireContext . |
Uses ID to find the specific Control object in questionnaire.controls (or nested). |
Dynamic Content | Displays different forms/fields based on the Control 's type . |
Checks selectedControl.type to conditionally render components like TextBoxProperties . |
Output/Action | Updates properties of the selected Control . |
Calls updateControl(selectedControl.id, { property: newValue }) on QuestionnaireContext . |
Key Data | Reads/writes label , required , visible , and fields within properties of the selected Control . |
Directly interacts with the specific Control object found. |
In this chapter, we explored the Properties Panel, the essential tool for customizing your building blocks after placing them on the canvas. We learned that it's a dynamic area that changes based on the selected Control's ControlType, displaying relevant options for common and type-specific settings. By interacting with the fields in the Properties Panel, you are directly updating the properties of the corresponding Control object within the central Questionnaire data managed by the QuestionnaireContext.
We've now covered adding, arranging (via drag and drop), and configuring individual controls. But what about controls that are meant to hold other controls? Things like Tabs, Accordions, or sections within a layout? These are special types of building blocks called Container Controls, and they are the subject of our next chapter.