Week 5 · Component Thinking
A small parts list of reusable interface elements — Button, Card, Badge, Input, Modal, and Toast — each built as a self-contained, prop-driven JS function instead of one-off, hand-wired markup. Every piece below is rendered live by calling its factory function with different arguments, and every panel has a "Copy usage" button with the exact call used.
createButton({ text, variant, size, onClick, disabled, icon, loading }) — returns a single <button> element. A separate setButtonLoading(btn, isLoading) helper toggles a spinner on an existing button for async actions.
| Prop | Type | Default |
|---|---|---|
text | string | required |
variant | primary · secondary · ghost · danger | primary |
size | sm · md · lg | md |
onClick | function | — |
disabled | boolean | false |
loading | boolean | false |
createCard({ title, description, tag, variant, footerButtons }) — footerButtons accepts an array of button prop-objects, so cards compose the Button factory internally.
| Prop | Type | Default |
|---|---|---|
title | string | required |
description | string | "" |
tag | string | — |
variant | default · outlined · elevated | default |
footerButtons | array<ButtonProps> | [] |
createBadge({ text, tone }) — a small status pill, useful for labeling card tags, list rows, or notification counts with a semantic color.
| Prop | Type | Default |
|---|---|---|
text | string | required |
tone | neutral · info · success · warning · danger | neutral |
createInput({ label, type, placeholder, value, helper, error, onChange }) — a labeled text field with either helper text or a validation error state.
| Prop | Type | Default |
|---|---|---|
label | string | "" |
type | string (input type) | text |
placeholder | string | "" |
helper | string | — |
error | string (overrides helper) | — |
onChange | function(value) | — |
createModal({ title, body, actions }) returns { open(), close() }. Modals stack — opening one from inside another layers it on top with its own backdrop, and Escape or a backdrop click closes only the topmost.
| Prop | Type | Default |
|---|---|---|
title | string | required |
body | string / HTML | "" |
actions | array<ButtonProps> | [Close] |
.open() / .close() | methods | — |
showToast({ message, type, duration }) pushes onto a fixed corner stack. Each toast auto-dismisses on its own timer (shown by the shrinking bar) and can also be dismissed early — firing several keeps them stacked independently.
| Prop | Type | Default |
|---|---|---|
message | string | required |
type | info · success · warning · danger | info |
duration | ms (0 = sticky) | 4000 |
Earlier tasks started from a page and I wrote markup and logic wherever they were needed, so the same button or card ended up re-typed with small inconsistencies each time. Building this kit forced me to start from the element instead: decide what varies (text, variant, size) versus what stays fixed, and write one function that returns a DOM node for any combination of those props. That made the demo page itself much thinner — it's mostly just calls to createButton/createCard/createModal/showToast with different arguments, not hand-written HTML. It also pushed state and behavior (open/close, auto-dismiss timers, stacking order) inside each component's own function instead of scattering them across page-level event listeners, which helped me understand the separation between reusable component logic and the page that uses it — one of the core ideas behind React.