trotl-boards
v1.0.48
Published
Reusable boards UI
Maintainers
Readme
React + Vite + trotl-boards
A simple, flexible Kanban‑style board UI for React — with drag‑and‑drop, context menus, and persistence.
DEMO: https://board.linijart.eu/
🚀 Installation
npm install trotl-boards_(ツ)_/ Versions
1.0.1 => initial release
⚡ Usage
<Board
initialColumns={columns || []} // set columns
initialCards={initialCards} // set cards
setInitialColumns={(cols) => console.log(cols)} // return updated culumns, use useCallback for set
setInitialCards={(cards) => console.log(cards)} // return updated cards, use useCallback for set
addColumnButton={true/false}
exportLayoutButton={true/false}
importLayoutButton={true/false}
collapsedButton={true/false}
addCardButton={true/false}
deleteCardButton={true/false}
lockDragColumns={true/false}
editColumnButton={true/false}
customCard={props => <BoardCard key={props?.id} {...props}/>}
/>➕ Custom card renderer that receives all card props
const BoardCard = (props) => {
console.log(props)
return (
<div
style={{
border: '1px solid #aaa',
borderRadius: 2,
padding: 8,
background: '#fff',
marginBottom: 10
}}>
<h4>Custom Card Component</h4>
<div><b>Title:</b> {props.card?.title}</div>
<div><b>Status:</b> {props.card?.status}</div>
<div><b>Allowed Move To:</b> {Array.isArray(props.card?.allowedMoveTo) ? props.card.allowedMoveTo.join(', ') : String(props.card?.allowedMoveTo)}</div>
{/* Render all props for debugging */}
<pre style={{ fontSize: 12, color: '#888', marginTop: 8 }}>{JSON.stringify(props, null, 2)}</pre>
</div>
);
}➕ Custom Add Column Button You can provide your own modal‑based add column button:
const ButtonCol = () => {
const [isOpen, setIsOpen] = useState(false);
const [title, setTitle] = useState("");
const [color, setColor] = useState("#4CAF50");
const confirmAdd = () => {
const newId = uid();
const newTitle = title.trim() || `Column ${columns.length + 1}`;
addColumn({ id: newId, title: newTitle, color });
setIsOpen(false);
};
return (
<>
<button onClick={() => setIsOpen(true)}>➕ Add Column</button>
<Modal
isOpen={isOpen}
title="Add New Column"
showCancel
showConfirm
confirmLabel="Add"
cancelLabel="Cancel"
onCancel={() => setIsOpen(false)}
onConfirm={confirmAdd}
>
<label>Column Title:</label>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<label>Column Color:</label>
<input type="color" value={color} onChange={(e) => setColor(e.target.value)} />
</Modal>
</>
);
};➕ Custom Add Card Button Same idea for cards — choose column and title via modal:
const ButtonAddCard = ({ columns = [], setState }) => {
const [isOpen, setIsOpen] = useState(false);
const [selectedCol, setSelectedCol] = useState("");
const [cardTitle, setCardTitle] = useState("");
const [allowedMoveTo, setAllowedMoveTo] = useState([]);
const [cardRest, setCardRest] = useState({ description: "" });
const openModal = () => {
setSelectedCol(columns[0]?.id || "");
setCardTitle("");
setCardRest({ description: "" });
setIsOpen(true);
};
const addCard = (statusId, title, rest) => {
const newCard = {
id: uid(),
title,
status: statusId,
allowedMoveTo: Array.from(new Set(allowedMoveTo)),
...rest
};
setState(prev => ({
...prev,
initialCards: {
...prev.initialCards,
[statusId]: [
...(prev.initialCards[statusId] || []),
newCard
]
}
}));
};
const confirmAdd = () => {
if (!selectedCol || !cardTitle.trim()) {
alert("Please select a column and enter a title.");
return;
}
addCard(selectedCol, cardTitle.trim(), cardRest);
setIsOpen(false);
};
return (
<>
<button onClick={openModal}>➕ Add Card</button>
<Modal
isOpen={isOpen}
title="Add New Card"
showCancel
showConfirm
confirmLabel="Add"
cancelLabel="Cancel"
onCancel={() => setIsOpen(false)}
onConfirm={confirmAdd}
>
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
{/* Column selector */}
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
<label style={{ fontSize: 14 }}>Column:</label>
<select
value={selectedCol}
onChange={(e) => setSelectedCol(e.target.value)}
style={{ padding: 8, borderRadius: 2, border: "1px solid #ccc" }}
>
{columns.map((col) => (
<option key={col.id} value={col.id}>
{col.title}
</option>
))}
</select>
</div>
{/* allowedMoveTo selector */}
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
<label style={{ fontSize: 14 }}>allowedMoveTo selector:</label>
<MultiSelectDropdown
className="multi-select-dropdown"
isMulti={true}
label={"components"}
options={columns.map(col => ({ id: col.id, value: col.id, label: col.title }))}
// addItem={(newOption) => console.log(newOption)}
// addItem={(newOption) => setOptions(prev => [...prev, newOption])}
closeMenuOnSelect={false}
selected={allowedMoveTo}
// onChange={(newValues) => setAllowedMoveTo(newValues.map(opt => opt))}
onChange={(newValues) => setAllowedMoveTo(prev => [...prev, ...newValues])}
// pushUrlParamObj={"ids"}
/>
</div>
{/* Card title input */}
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
<label style={{ fontSize: 14 }}>Card Title:</label>
<input
value={cardTitle}
onChange={(e) => setCardTitle(e.target.value)}
placeholder="Enter card title..."
style={{
padding: 8,
borderRadius: 2,
border: "1px solid #ccc",
width: "100%",
}}
/>
</div>
{/* Card rest input */}
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
<label style={{ fontSize: 14 }}>Card Title:</label>
<textarea
value={cardRest.description || ""}
onChange={(e) => setCardRest(prev => ({ ...prev, description: e.target.value }))}
placeholder="Enter card title..."
style={{
padding: 8,
borderRadius: 2,
border: "1px solid #ccc",
width: "100%",
}}
/>
</div>
</div>
</Modal>
</>
);
};=> then in render
<ButtonAddCard
columns={state.columns || []}
setState={setState}
/>📦 Features Drag‑and‑drop cards between columns
- Collapse/expand columns
- Customizable add buttons with modals
📝 License MIT © Dejan Trtnik
This version is structured so npm readers immediately see installation, usage, props, and customization examples. It highlights your modal add buttons as optional advanced usage.
